pax_global_header00006660000000000000000000000064150072475010014513gustar00rootroot0000000000000052 comment=7c576c5ffe240da7d4aae9741162a4583efff6b0 savvycan-220/000077500000000000000000000000001500724750100132125ustar00rootroot00000000000000savvycan-220/.github/000077500000000000000000000000001500724750100145525ustar00rootroot00000000000000savvycan-220/.github/workflows/000077500000000000000000000000001500724750100166075ustar00rootroot00000000000000savvycan-220/.github/workflows/build.yml000066400000000000000000000152741500724750100204420ustar00rootroot00000000000000name: Build on: push: branches: - "master" jobs: buildlinux: name: Linux x64 runs-on: ubuntu-22.04 steps: - name: Prepare Environment run: | sudo apt-get install libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-randr0 libxkbcommon-x11-0 libfuse2 - name: Prepare Qt Libraries uses: jurplel/install-qt-action@v3 - name: Clone uses: actions/checkout@v3 - name: Compile run: | qmake CONFIG+=release PREFIX=/usr SavvyCAN.pro make -j`grep -c ^processor /proc/cpuinfo` - name: Package run: | make INSTALL_ROOT=appdir install 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@v4 with: name: SavvyCAN-Linux_x64.AppImage path: SavvyCAN-*x86_64.AppImage buildmacos86: name: macOS x64 runs-on: macos-13 steps: - name: Prepare macOS Environment run: | brew install qt5 brew link qt5 --force - name: Clone uses: actions/checkout@v3 - name: Compile run: | qmake CONFIG+=release CONFIG+=sdk_no_version_check SavvyCAN.pro make -j`sysctl kern.aioprocmax | awk '{print $2}'` - name: Package run: | mkdir -p SavvyCAN.app/Contents/MacOS/help cp -R help/* SavvyCAN.app/Contents/MacOS/help macdeployqt SavvyCAN.app -dmg mv SavvyCAN.dmg SavvyCAN_x64.dmg - uses: actions/upload-artifact@v4 with: name: SavvyCAN-macOS_x64.dmg path: SavvyCAN_x64.dmg buildmacos-arm64: name: macOS ARM64 runs-on: macos-14 steps: - name: Prepare macOS Environment run: | brew install qt5 brew link qt5 --force - name: Clone uses: actions/checkout@v3 - name: Compile run: | qmake CONFIG+=release CONFIG+=sdk_no_version_check SavvyCAN.pro make -j`sysctl kern.aioprocmax | awk '{print $2}'` - name: Package run: | mkdir -p SavvyCAN.app/Contents/MacOS/help cp -R help/* SavvyCAN.app/Contents/MacOS/help macdeployqt SavvyCAN.app -dmg mv SavvyCAN.dmg SavvyCAN_arm64.dmg - uses: actions/upload-artifact@v4 with: name: SavvyCAN-macOS_ARM64.dmg path: SavvyCAN_arm64.dmg buildwindows: name: Windows x64 runs-on: windows-2019 steps: - name: Prepare Qt Libraries uses: jurplel/install-qt-action@v3 - name: Clone uses: actions/checkout@v3 - name: Compile shell: cmd run: | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" qmake CONFIG+=release SavvyCAN.pro nmake /C - name: Package run: | mkdir package copy "release/SavvyCAN.exe" package/ mkdir package/help copy help/*.md package/help/ mkdir package/help/images copy help/images/*.* package/help/images/ mkdir package/examples copy examples/*.* package/examples/ copy "${Env:Qt5_Dir}/bin/Qt5Core.dll" package/ copy "${Env:Qt5_Dir}/bin/Qt5Gui.dll" package/ copy "${Env:Qt5_Dir}/bin/Qt5Network.dll" package/ copy "${Env:Qt5_Dir}/bin/Qt5PrintSupport.dll" package/ copy "${Env:Qt5_Dir}/bin/Qt5Qml.dll" package/ copy "${Env:Qt5_Dir}/bin/Qt5SerialBus.dll" package/ copy "${Env:Qt5_Dir}/bin/Qt5SerialPort.dll" package/ copy "${Env:Qt5_Dir}/bin/Qt5Widgets.dll" package/ mkdir package/imageformats copy "${Env:Qt5_Dir}/plugins/imageformats/*.*" package/imageformats/ mkdir package/platforms copy "${Env:Qt5_Dir}/plugins/platforms/*.*" package/platforms/ mkdir package/styles copy "${Env:Qt5_Dir}/plugins/styles/*.*" package/styles/ mkdir package/canbus copy "${Env:Qt5_Dir}/plugins/canbus/*.*" package/canbus/ - uses: actions/upload-artifact@v4 with: name: SavvyCAN-Windows_x64 path: package pre-release: name: "pre-release" runs-on: "ubuntu-latest" needs: [buildwindows, buildmacos86, buildmacos-arm64, buildlinux] steps: - uses: actions/checkout@v3 - uses: actions/download-artifact@v4.1.7 - name: Display structure of downloaded files run: zip -r SavvyCAN-Windows_x64_CIBuild.zip SavvyCAN-Windows_x64 - uses: "marvinpinto/action-automatic-releases@latest" with: repo_token: "${{ secrets.GITHUB_TOKEN }}" automatic_release_tag: "continuous" prerelease: true title: "Development Build" files: | SavvyCAN-Linux_x64.AppImage/SavvyCAN-*x86_64.AppImage SavvyCAN-Windows_x64_CIBuild.zip SavvyCAN-macOS_x64.dmg/SavvyCAN_x64.dmg SavvyCAN-macOS_ARM64.dmg/SavvyCAN_arm64.dmg notify: name: Notify Discord runs-on: ubuntu-latest needs: [buildwindows, buildmacos86, buildmacos-arm64, buildlinux] steps: - id: msg_var name: remove newlines run: | export release_msg=$(cat << EOF ${{ github.event.head_commit.message }} EOF ) 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" - name: Notify Discord run: | curl -i -H "Accept: application/json" \ -H "Content-Type:application/json" \ -X POST \ --data "{\"content\": null,\"embeds\": [{\"title\": \"${{ github.repository }} Build Complete\", \"description\": \"${{ steps.msg_var.outputs.msg }} \\nCommit: ${{ github.sha }}\\n[Build Artifacts](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}#artifacts)\", \"color\": 5131997, \"author\": { \"name\": \"Github Builds\" } } ]}" \ https://discord.com/api/webhooks/${{ secrets.WEBHOOK_ID }}/${{ secrets.WEBHOOK_TOKEN }} savvycan-220/.gitignore000066400000000000000000000007151500724750100152050ustar00rootroot00000000000000#Build directory build/ # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.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 savvycan-220/.travis.yml000066400000000000000000000055541500724750100153340ustar00rootroot00000000000000language: cpp # Any time a commit is done to either master or WIP we do a CI build on all three platforms to make # sure it really builds. The linux version will be saved to the continuous tag and thus available # for testing. All three platforms will get a proper release upon a tagged commit. matrix: include: - os: linux dist: xenial compiler: gcc sudo: require before_install: - sudo add-apt-repository ppa:beineri/opt-qt-5.14.1-xenial -y - sudo apt-get update -qq install: - sudo apt-get -y install qt514base qt514connectivity qt514imageformats qt514serialport qt514serialbus qt514doc qt514tools libgl1-mesa-dev - source /opt/qt*/bin/qt*-env.sh script: - qmake CONFIG+=release PREFIX=/usr - make -j$(nproc) - make INSTALL_ROOT=appdir -j$(nproc) install ; find appdir/ - wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" - chmod a+x linuxdeployqt-continuous-x86_64.AppImage # export VERSION=... # linuxdeployqt uses this for naming the file - ./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/local/share/applications/SavvyCAN.desktop -appimage -extra-plugins=iconengines,platformthemes/libqgtk3.so,canbus after_success: # find appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " -f 2-3 | sort | uniq # for debugging # curl --upload-file APPNAME*.AppImage https://transfer.sh/APPNAME-git.$(git rev-parse --short HEAD)-x86_64.AppImage - wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh - bash upload.sh SavvyCAN*.AppImage* deploy: provider: releases skip_cleanup: true api_key: $GITHUB_TOKEN file_glob: true file: SavvyCAN*.AppImage on: tags: true #deploy is only done on tagged builds draft: false # - os: osx # osx_image: xcode10 # before_install: # - brew install qt5 # - brew link qt5 --force # script: # - qmake CONFIG+=release -spec macx-xcode SavvyCAN.pro # - xcodebuild # - mkdir Release/SavvyCAN.app/Contents/MacOS/help # - cp -R help/*.* Release/SavvyCAN.app/Contents/MacOS/help # - cd Release # - ls SavvyCAN.app/Contents/MacOS # - macdeployqt SavvyCAN.app -dmg # deploy: # provider: releases # skip_cleanup: true # api_key: $GITHUB_TOKEN # file: SavvyCAN.dmg # on: # tags: true # draft: false branches: except: - # Do not build tags that we create when we upload to GitHub Releases - /^(?i:continuous)/ savvycan-220/Info.plist.template000066400000000000000000000025001500724750100167710ustar00rootroot00000000000000 CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile ${ASSETCATALOG_COMPILER_APPICON_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundlePackageType APPL CFBundleSignature ${QMAKE_PKGINFO_TYPEINFO} LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSPrincipalClass NSApplication NSSupportsAutomaticGraphicsSwitching CFBundleDocumentTypes CFBundleTypeExtensions asc avc blf can crt crtd csv evc log qcc trace trc txt CFBundleTypeRole Viewer savvycan-220/LICENSE000066400000000000000000000020711500724750100142170ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Collin Kidder Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. savvycan-220/README.md000066400000000000000000000062101500724750100144700ustar00rootroot00000000000000# SavvyCAN 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 capturing of canbus frames. Please use the "Discussions" tab here on GitHub to ask questions and interact with the community. Requires a resolution of at least 1024x768. Fully multi-monitor capable. Works on 4K monitors as well. 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). 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: 1. BusMaster log file 2. Microchip log file 3. CRTD format (OVMS log file format from Mark Webb-Johnson) 4. GVRET native format 5. Generic CSV file (ID, D0 D1 D2 D3 D4 D5 D6 D7) 6. Vector Trace files 7. IXXAT Minilog files 8. CAN-DO Logs 9. Vehicle Spy log files 10. CANDump / Kayak (Read only) 11. PCAN Viewer (Read Only) 12. Wireshark socketcan PCAP file (Read only) ## Dependencies Now this code does not depend on anything other than what is in the source tree or available from the Qt installer. Uses QCustomPlot available at: http://www.qcustomplot.com/ 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. 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: [Download the newest stable version of Qt directly from qt.io](https://www.qt.io/download/) (You need 5.14.x or newer) ```sh cd ~ git clone https://github.com/collin80/SavvyCAN.git cd SavvyCAN ~/Qt/5.14/gcc_64/bin/qmake make ``` 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 qmake CONFIG+=debug make ``` ## What to do if your compile failed? The very first thing to do is try: ``` qmake make clean make ``` Did that fix it? Great! If not, ensure that you selected SerialBUS support 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 libqt5serialport5-dev qtdeclarative5-dev qttools5-dev` ### Used Items Requiring Attribution nodes by Adrien Coquet from the Noun Project message by Vectorstall from the Noun Project signal by shashank singh from the Noun Project signal by juli from the Noun Project signal by yudi from the Noun Project Death by Adrien Coquet from the Noun Project savvycan-220/SavvyCAN.desktop000066400000000000000000000005141500724750100162370ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=SavvyCAN GenericName=CANBus reverse engineering tool Comment=Facilitates reverse engineering of canbus captures Exec=SavvyCAN Icon=SavvyCAN Terminal=false Categories=Development;Electronics;IDE; MimeType=text/x-application; Keywords=embedded electronics;electronics;canbus;reverse engineering; savvycan-220/SavvyCAN.pro000066400000000000000000000164621500724750100153770ustar00rootroot00000000000000#------------------------------------------------- # # Project created by QtCreator 2015-04-25T22:57:44 # #------------------------------------------------- !versionAtLeast(QT_VERSION, 5.14.0) { error("Current version of Qt ($${QT_VERSION}) is too old, this project requires Qt 5.14 or newer") } QT = core gui printsupport qml serialbus serialport widgets help network opengl CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT CONFIG += c++17 CONFIG += NO_UNIT_TESTS DEFINES += QCUSTOMPLOT_USE_OPENGL TARGET = SavvyCAN TEMPLATE = app 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 \ mqtt/qmqtt_message.cpp \ mqtt/qmqtt_network.cpp \ mqtt/qmqtt_router.cpp \ mqtt/qmqtt_routesubscription.cpp \ mqtt/qmqtt_socket.cpp \ mqtt/qmqtt_ssl_socket.cpp \ mqtt/qmqtt_timer.cpp \ mqtt/qmqtt_websocket.cpp \ mqtt/qmqtt_websocketiodevice.cpp \ qcpaxistickerhex.cpp \ re/dbccomparatorwindow.cpp \ mainwindow.cpp \ canframemodel.cpp \ simplecrypt.cpp \ triggerdialog.cpp \ utility.cpp \ qcustomplot.cpp \ frameplaybackwindow.cpp \ candatagrid.cpp \ framesenderwindow.cpp \ framefileio.cpp \ mainsettingsdialog.cpp \ firmwareuploaderwindow.cpp \ scriptingwindow.cpp \ scriptcontainer.cpp \ canfilter.cpp \ can_structs.cpp \ motorcontrollerconfigwindow.cpp \ connections/canconnection.cpp \ connections/serialbusconnection.cpp \ connections/canconfactory.cpp \ connections/gvretserial.cpp \ connections/socketcand.cpp \ connections/canconmanager.cpp \ re/sniffer/snifferitem.cpp \ re/sniffer/sniffermodel.cpp \ re/sniffer/snifferwindow.cpp \ dbc/dbcmessageeditor.cpp \ dbc/dbc_classes.cpp \ dbc/dbchandler.cpp \ dbc/dbcloadsavewindow.cpp \ dbc/dbcmaineditor.cpp \ dbc/dbcnodeeditor.cpp \ dbc/dbcsignaleditor.cpp \ dbc/dbcnoderebaseeditor.cpp \ re/discretestatewindow.cpp \ re/filecomparatorwindow.cpp \ re/flowviewwindow.cpp \ re/frameinfowindow.cpp \ re/fuzzingwindow.cpp \ re/isotp_interpreterwindow.cpp \ re/rangestatewindow.cpp \ re/udsscanwindow.cpp \ connections/canbus.cpp \ connections/canconnectionmodel.cpp \ connections/connectionwindow.cpp \ re/graphingwindow.cpp \ re/newgraphdialog.cpp \ bisectwindow.cpp \ signalviewerwindow.cpp \ bus_protocols/isotp_handler.cpp \ bus_protocols/j1939_handler.cpp \ bus_protocols/uds_handler.cpp \ jsedit.cpp \ frameplaybackobject.cpp \ helpwindow.cpp \ blfhandler.cpp \ re/sniffer/SnifferDelegate.cpp \ connections/newconnectiondialog.cpp \ re/temporalgraphwindow.cpp \ filterutility.cpp \ pcaplite.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 \ mqtt/qmqtt_frame.h \ mqtt/qmqtt_global.h \ mqtt/qmqtt_message.h \ mqtt/qmqtt_message_p.h \ mqtt/qmqtt_network_p.h \ mqtt/qmqtt_networkinterface.h \ mqtt/qmqtt_routedmessage.h \ mqtt/qmqtt_router.h \ mqtt/qmqtt_routesubscription.h \ mqtt/qmqtt_socket_p.h \ mqtt/qmqtt_socketinterface.h \ mqtt/qmqtt_ssl_socket_p.h \ mqtt/qmqtt_timer_p.h \ mqtt/qmqtt_timerinterface.h \ mqtt/qmqtt_websocket_p.h \ mqtt/qmqtt_websocketiodevice_p.h \ qcpaxistickerhex.h \ re/dbccomparatorwindow.h \ simplecrypt.h \ triggerdialog.h \ utility.h \ qcustomplot.h \ frameplaybackwindow.h \ candatagrid.h \ framesenderwindow.h \ can_trigger_structs.h \ framefileio.h \ config.h \ mainsettingsdialog.h \ firmwareuploaderwindow.h \ scriptingwindow.h \ scriptcontainer.h \ canfilter.h \ utils/lfqueue.h \ motorcontrollerconfigwindow.h \ connections/canconnection.h \ connections/serialbusconnection.h \ connections/canconconst.h \ connections/canconfactory.h \ connections/gvretserial.h \ connections/canconmanager.h \ re/sniffer/snifferitem.h \ re/sniffer/sniffermodel.h \ re/sniffer/snifferwindow.h \ dbc/dbc_classes.h \ dbc/dbchandler.h \ dbc/dbcloadsavewindow.h \ dbc/dbcmaineditor.h \ dbc/dbcsignaleditor.h \ dbc/dbcmessageeditor.h \ dbc/dbcnodeeditor.h \ re/discretestatewindow.h \ re/filecomparatorwindow.h \ re/flowviewwindow.h \ re/frameinfowindow.h \ re/fuzzingwindow.h \ re/isotp_interpreterwindow.h \ re/rangestatewindow.h \ re/udsscanwindow.h \ connections/canbus.h \ connections/canconnectionmodel.h \ connections/connectionwindow.h \ re/graphingwindow.h \ re/newgraphdialog.h \ bisectwindow.h \ signalviewerwindow.h \ bus_protocols/isotp_handler.h \ bus_protocols/j1939_handler.h \ bus_protocols/uds_handler.h \ bus_protocols/isotp_message.h \ jsedit.h \ frameplaybackobject.h \ helpwindow.h \ blfhandler.h \ re/sniffer/SnifferDelegate.h \ connections/newconnectiondialog.h \ re/temporalgraphwindow.h \ filterutility.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 \ ui/filecomparatorwindow.ui \ ui/firmwareuploaderwindow.ui \ ui/flowviewwindow.ui \ ui/frameinfowindow.ui \ ui/frameplaybackwindow.ui \ ui/framesenderwindow.ui \ ui/fuzzingwindow.ui \ ui/graphingwindow.ui \ ui/isotp_interpreterwindow.ui \ ui/mainsettingsdialog.ui \ ui/mainwindow.ui \ ui/motorcontrollerconfigwindow.ui \ ui/newgraphdialog.ui \ ui/rangestatewindow.ui \ ui/scriptingwindow.ui \ ui/snifferwindow.ui \ ui/udsscanwindow.ui \ ui/bisectwindow.ui \ ui/signalviewerwindow.ui \ ui/helpwindow.ui \ ui/newconnectiondialog.ui \ ui/temporalgraphwindow.ui RESOURCES += \ icons.qrc \ images.qrc win32-msvc* { LIBS += opengl32.lib } win32-g++ { LIBS += libopengl32 } unix { isEmpty(PREFIX) { PREFIX=/usr/local } target.path = $$PREFIX/bin shortcutfiles.files=SavvyCAN.desktop shortcutfiles.path = $$PREFIX/share/applications INSTALLS += shortcutfiles DISTFILES += SavvyCAN.desktop } windows { RC_ICONS=icons/SavvyIcon.ico } examplefiles.files=examples examplefiles.path = $$PREFIX/share/savvycan/examples INSTALLS += examplefiles iconfiles.files=icons iconfiles.path = $$PREFIX/share INSTALLS += iconfiles helpfiles.files=help/* helpfiles.path = $$PREFIX/bin/help INSTALLS += helpfiles INSTALLS += target savvycan-220/bisectwindow.cpp000066400000000000000000000160211500724750100164170ustar00rootroot00000000000000#include "bisectwindow.h" #include "ui_bisectwindow.h" #include "mainwindow.h" #include "framefileio.h" #include "helpwindow.h" #include #include BisectWindow::BisectWindow(const QVector *frames, QWidget *parent) : QDialog(parent), ui(new Ui::BisectWindow) { ui->setupUi(this); setWindowFlags(Qt::Window); modelFrames = frames; connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int))); connect(ui->btnCalculate, &QAbstractButton::clicked, this, &BisectWindow::handleCalculateButton); connect(ui->btnReplaceFrames, &QAbstractButton::clicked, this, &BisectWindow::handleReplaceButton); connect(ui->btnSaveFrames, &QAbstractButton::clicked, this, &BisectWindow::handleSaveButton); connect(ui->slideFrameNumber, &QSlider::sliderReleased, this, &BisectWindow::updateFrameNumText); 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() { removeEventFilter(this); delete ui; } void BisectWindow::showEvent(QShowEvent* event) { QDialog::showEvent(event); refreshIDList(); refreshFrameNumbers(); updateFrameNumText(); updatePercentText(); } bool BisectWindow::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("bisector.md"); break; } return true; } else { // standard event processing return QObject::eventFilter(obj, 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; foundID.clear(); ui->cbIDLower->clear(); ui->cbIDUpper->clear(); for (int i = 0; i < modelFrames->count(); i++) { id = modelFrames->at(i).frameId(); if (!foundID.contains(id)) { foundID.append(id); } } std::sort(foundID.begin(), foundID.end()); foreach (int id, foundID) { ui->cbIDLower->addItem(Utility::formatCANID(id)); ui->cbIDUpper->addItem(Utility::formatCANID(id)); } } void BisectWindow::refreshFrameNumbers() { ui->labelMainListNum->setText(QString::number(modelFrames->count())); ui->labelSplitNum->setText(QString::number(splitFrames.count())); ui->slideFrameNumber->setMaximum(modelFrames->count()); } void BisectWindow::updatedFrames(int numFrames) { if (numFrames == -1) //all frames deleted { refreshFrameNumbers(); } else if (numFrames == -2) //all new set of frames. Reset { refreshFrameNumbers(); refreshIDList(); } else //just got some new frames. See if they are relevant. { } } void BisectWindow::handleCalculateButton() { splitFrames.clear(); bool saveLower = ui->rbLowerSection->isChecked(); 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.0); qDebug() << "Target frame num " << targetFrameNum; if (saveLower) { for (int i = 0; i < targetFrameNum; i++) splitFrames.append(modelFrames->at(i)); } else { for (int i = targetFrameNum; i < modelFrames->count(); i++) splitFrames.append(modelFrames->at(i)); } } else if (ui->rbIDRange->isChecked()) { uint32_t lowerID = Utility::ParseStringToNum2(ui->cbIDLower->currentText()); 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) { 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(); } void BisectWindow::handleReplaceButton() { CANFrameModel *model; model = MainWindow::getReference()->getCANFrameModel(); model->clearFrames(); model->insertFrames(splitFrames); refreshFrameNumbers(); refreshIDList(); } void BisectWindow::handleSaveButton() { QMessageBox msg; QString filename; if (FrameFileIO::saveFrameFile(filename, &splitFrames)) { msg.setText(tr("Successfully saved file")); } else { msg.setText(tr("Error while attempting to save.")); } msg.exec(); } void BisectWindow::updateFrameNumSlider() { ui->slideFrameNumber->setValue(ui->editFrameNumber->text().toInt()); } void BisectWindow::updatePercentSlider() { ui->slidePercentage->setValue(ui->editPercentage->text().toFloat() * 100); } void BisectWindow::updateFrameNumText() { ui->editFrameNumber->setText(QString::number(ui->slideFrameNumber->value())); } void BisectWindow::updatePercentText() { ui->editPercentage->setText(QString::number(ui->slidePercentage->value() / 100.0f)); } savvycan-220/bisectwindow.h000066400000000000000000000017701500724750100160710ustar00rootroot00000000000000#ifndef BISECTWINDOW_H #define BISECTWINDOW_H #include #include "can_structs.h" namespace Ui { class BisectWindow; } class BisectWindow : public QDialog { Q_OBJECT public: explicit BisectWindow(const QVector *frames, QWidget *parent = 0); ~BisectWindow(); void showEvent(QShowEvent*); signals: void sendCANFrame(const CANFrame *, int); void sendFrameBatch(const QList *); private slots: void updatedFrames(int numFrames); void handleSaveButton(); void handleReplaceButton(); void handleCalculateButton(); void updateFrameNumSlider(); void updatePercentSlider(); void updateFrameNumText(); void updatePercentText(); void updateSectionsText(); private: Ui::BisectWindow *ui; const QVector *modelFrames; QVector splitFrames; QList foundID; void refreshIDList(); void refreshFrameNumbers(); bool eventFilter(QObject *obj, QEvent *event); }; #endif // BISECTWINDOW_H savvycan-220/blfhandler.cpp000066400000000000000000000204561500724750100160260ustar00rootroot00000000000000#include "blfhandler.h" #include #include #include #include #define BLF_REMOTE_FLAG 0x80 BLFHandler::BLFHandler() { } /* Written while peeking at source code here: https://python-can.readthedocs.io/en/latest/_modules/can/io/blf.html https://bitbucket.org/tobylorenz/vector_blf/ All the code actually below is freshly written but heavily based upon things seen in those two source repos. */ bool BLFHandler::loadBLF(QString filename, QVector* frames) { BLF_OBJ_HEADER objHeader; QByteArray fileData; QByteArray uncompressedData; QByteArray junk; BLF_OBJECT obj; uint32_t pos; BLF_CAN_OBJ canObject; BLF_CAN_OBJ2 canObject2; QFile *inFile = new QFile(filename); if (!inFile->open(QIODevice::ReadOnly)) { delete inFile; return false; } inFile->read((char *)&header, sizeof(header)); if (qFromLittleEndian(header.sig) == 0x47474F4C) { qDebug() << "Proper BLF file header token"; } else return false; while (!inFile->atEnd()) { qDebug() << "Position within file: " << inFile->pos(); inFile->read((char *)&objHeader.base, sizeof(BLF_OBJ_HEADER_BASE)); if (qFromLittleEndian(objHeader.base.sig) == 0x4A424F4C) { int readSize = objHeader.base.objSize - sizeof(BLF_OBJ_HEADER_BASE); qDebug() << "Proper object header token. Read Size: " << readSize; fileData = inFile->read(readSize); junk = inFile->read(readSize % 4); //file is padded so sizes must always end up on even multiple of 4 //qDebug() << "Fudge bytes in readSize: " << (readSize % 4); switch (objHeader.base.objType) { case BLF_CONTAINER: qDebug() << "Object is a container."; memcpy(&objHeader.containerObj, fileData.constData(), sizeof(BLF_OBJ_HEADER_CONTAINER)); fileData.remove(0, sizeof(BLF_OBJ_HEADER_CONTAINER)); if (objHeader.containerObj.compressionMethod == BLF_CONT_NO_COMPRESSION) { qDebug() << "Container is not compressed"; uncompressedData = fileData; } else if (objHeader.containerObj.compressionMethod == BLF_CONT_ZLIB_COMPRESSION) { qDebug() << "Compressed container. Unpacking it."; fileData.prepend(objHeader.containerObj.uncompressedSize & 0xFF); fileData.prepend((objHeader.containerObj.uncompressedSize >> 8) & 0xFF); fileData.prepend((objHeader.containerObj.uncompressedSize >> 16) & 0xFF); fileData.prepend((objHeader.containerObj.uncompressedSize >> 24) & 0xFF); uncompressedData += qUncompress(fileData); } else { qDebug() << "Dunno what this is... " << objHeader.containerObj.compressionMethod; } qDebug() << "Uncompressed size: " << uncompressedData.count(); qDebug() << "Currently loaded frames at this point: " << frames->count(); pos = 0; //bool foundHeader = false; //first skip forward to find a header signature - usually not necessary while ( (int)(pos + sizeof(BLF_OBJ_HEADER)) < uncompressedData.count()) { int32_t *headerSig = (int32_t *)(uncompressedData.constData() + pos); if (*headerSig == 0x4A424F4C) break; pos += 4; } //then process all the objects while ( (int)(pos + sizeof(BLF_OBJ_HEADER)) < uncompressedData.count()) { memcpy(&obj.header.base, (uncompressedData.constData() + pos), sizeof(BLF_OBJ_HEADER_BASE)); memcpy(&obj.header.v1Obj, (uncompressedData.constData() + pos) + sizeof(BLF_OBJ_HEADER_BASE), sizeof(BLF_OBJ_HEADER_V1)); //if (obj.header.base.objType != 1) //qDebug() << "Pos: " << pos << " Type: " << obj.header.base.objType << "Obj Size: " << obj.header.base.objSize; if (qFromLittleEndian(objHeader.base.sig) == 0x4A424F4C) { fileData = uncompressedData.mid(pos + sizeof(BLF_OBJ_HEADER_BASE) + sizeof(BLF_OBJ_HEADER_V1), obj.header.base.objSize - sizeof(BLF_OBJ_HEADER_BASE) - sizeof(BLF_OBJ_HEADER_V1)); if (obj.header.base.objType == BLF_CAN_MSG) { memcpy(&canObject, fileData.constData(), sizeof(BLF_CAN_OBJ)); CANFrame frame; frame.bus = canObject.channel; frame.setExtendedFrameFormat((canObject.id & 0x80000000ull)?true:false); frame.setFrameId(canObject.id & 0x1FFFFFFFull); frame.isReceived = true; QByteArray bytes(canObject.dlc, 0); if (canObject.flags & BLF_REMOTE_FLAG) { frame.setFrameType(QCanBusFrame::RemoteRequestFrame); } else { frame.setFrameType(QCanBusFrame::DataFrame); for (int i = 0; i < canObject.dlc; i++) bytes[i] = canObject.data[i]; } frame.setPayload(bytes); //Should we divide by a thousand or a million? Unsure here. It appears some logs are stamped in microseconds and some in milliseconds? frame.setTimeStamp(QCanBusFrame::TimeStamp(0, obj.header.v1Obj.uncompSize / 1000.0)); //uncompsize field also used for timestamp oddly enough frames->append(frame); } else if (obj.header.base.objType == BLF_CAN_MSG2) { memcpy(&canObject2, fileData.constData(), sizeof(BLF_CAN_OBJ2)); CANFrame frame; frame.bus = canObject2.channel; frame.setExtendedFrameFormat((canObject2.id & 0x80000000ull)?true:false); frame.setFrameId(canObject2.id & 0x1FFFFFFFull); frame.isReceived = true; QByteArray bytes(canObject2.dlc, 0); if (canObject2.flags & BLF_REMOTE_FLAG) { frame.setFrameType(QCanBusFrame::RemoteRequestFrame); } else { frame.setFrameType(QCanBusFrame::DataFrame); for (int i = 0; i < canObject2.dlc; i++) bytes[i] = canObject2.data[i]; } frame.setPayload(bytes); //Should we divide by a thousand or a million? Unsure here. It appears some logs are stamped in microseconds and some in milliseconds? frame.setTimeStamp(QCanBusFrame::TimeStamp(0, obj.header.v1Obj.uncompSize / 1000.0)); //uncompsize field also used for timestamp oddly enough frames->append(frame); } else { qDebug() << "Not a can frame! ObjType: " << obj.header.base.objType; if (obj.header.base.objType > 0xFFFF) return false; } pos += obj.header.base.objSize + (obj.header.base.objSize % 4); } else { qDebug() << "Unexpected object header signature, aborting"; return false; } } uncompressedData.remove(0, pos); qDebug() << "After removing used data uncompressedData is now this big: " << uncompressedData.count(); break; } } else return false; } return true; } bool BLFHandler::saveBLF(QString filename, QVector *frames) { Q_UNUSED(filename) Q_UNUSED(frames) return false; } savvycan-220/blfhandler.h000066400000000000000000000075731500724750100155000ustar00rootroot00000000000000#ifndef BLFHANDLER_H #define BLFHANDLER_H #include #include #include #include "can_structs.h" enum { BLF_CAN_MSG = 1, BLF_CAN_ERR = 2, BLF_CAN_OVERLOAD = 3, BLF_STATISTIC = 4, BLF_APP_TRIGGER = 5, BLF_ENV_INT = 6, BLF_ENV_DOUBLE = 7, BLF_ENV_STRING = 8, BLF_ENV_DATA = 9, BLF_CONTAINER = 10, BLF_CAN_DRV_ERR = 31, // can driver error BLF_CAN_DRV_SYNC = 44, BLF_ERROR_EXT = 73, BLF_DRV_ERR_EXT = 74, BLF_CAN_MSG2 = 86, BLF_OVERRUN = 91, BLF_EVENT_COMMENT = 92, BLF_GLOBAL_MARKER = 96, BLF_CAN_FD_MSG = 100, BLF_CAN_FD_MSG64 = 101, BLF_CAN_FD_ERR64 = 104 }; enum { BLF_CONT_NO_COMPRESSION = 0, BLF_CONT_ZLIB_COMPRESSION = 2 }; struct BLF_FILE_HEADER { uint32_t sig; uint32_t headerSize; uint8_t appID; uint8_t appVerMajor; uint8_t appVerMinor; uint8_t appVerBuild; uint8_t binLogVerMajor; uint8_t binLogVerMinor; uint8_t binLogVerBuild; uint8_t binLogVerPatch; uint64_t fileSize; uint64_t uncompressedFileSize; uint32_t countObjs; uint32_t countObjsRead; uint8_t startTime[16]; uint8_t stopTime[16]; uint8_t unused[72]; }; //144 bytes struct BLF_OBJ_HEADER_BASE { uint32_t sig; //0 offset from start uint16_t headerSize; //4 uint16_t headerVersion; //6 uint32_t objSize; //8 uint32_t objType; //12 }; //16 bytes in common //the next three are continuations of the above base and so are numbered in comments accordingly struct BLF_OBJ_HEADER_V1 { uint32_t flags; //16 uint16_t clientIdx; //20 uint16_t objVer; //22 uint64_t uncompSize; //24 could be the timestamp too }; //ends up as 32 bytes for this whole header including base struct BLF_OBJ_HEADER_V2 { uint32_t flags; //16 uint16_t timestampStatus; //20 uint16_t objVer; //22 uint64_t uncompSize; //24 uint64_t origTimestamp; //32 }; //40 bytes including base header struct BLF_OBJ_HEADER_CONTAINER { uint16_t compressionMethod; //16 uint8_t notused[6]; //18 uint32_t uncompressedSize; //24 uint8_t notused2[4]; //28 }; //32 bytes for the whole header struct BLF_OBJ_HEADER { BLF_OBJ_HEADER_BASE base; union { BLF_OBJ_HEADER_V1 v1Obj; BLF_OBJ_HEADER_V2 V2Obj; BLF_OBJ_HEADER_CONTAINER containerObj; }; }; struct BLF_OBJECT { BLF_OBJ_HEADER header; QByteArray data; }; struct BLF_CAN_OBJ { uint16_t channel; uint8_t flags; uint8_t dlc; uint32_t id; uint8_t data[8]; }; struct BLF_CAN_OBJ2 { uint16_t channel; uint8_t flags; //0 = Tx, 5 = Err 6 = WU, 7 = RTR uint8_t dlc; uint32_t id; uint8_t data[8]; uint32_t frameLength; //length of transmission in nanoseconds not covering IF or EOF uint8_t bitCount; //# of bits in this message including IF and EOF uint8_t dummy1; uint16_t dummy2; }; struct BLF_CANFD_OBJ { uint16_t channel; uint8_t flags; uint8_t dlc; uint32_t id; uint32_t frameLen; uint8_t bitCount; uint8_t fdFlags; uint8_t validDataBytes; uint8_t nonUseful[5]; uint8_t data[64]; }; struct BLF_ERROR_EXT { uint16_t channel; uint16_t len; uint32_t flags; uint8_t ecc; uint8_t position; uint8_t dlc; uint8_t ignored; uint32_t frameLen; uint32_t id; uint16_t extFlags; uint16_t ignore2; uint8_t data; }; struct BLF_GLOBAL_MARKER { uint32_t eventType; uint32_t foregroundColor; uint32_t backgroundColor; uint8_t ignore[3]; uint8_t relocatable; uint32_t groupNameLen; uint32_t markerNameLen; uint32_t descLen; uint8_t ignore2[12]; }; class BLFHandler { public: BLFHandler(); bool loadBLF(QString filename, QVector* frames); bool saveBLF(QString filename, QVector* frames); private: BLF_FILE_HEADER header; QList objects; }; #endif // BLFHANDLER_H savvycan-220/bus_protocols/000077500000000000000000000000001500724750100161075ustar00rootroot00000000000000savvycan-220/bus_protocols/isotp_handler.cpp000066400000000000000000000324461500724750100214570ustar00rootroot00000000000000#include "isotp_handler.h" #include "connections/canconmanager.h" ISOTP_HANDLER::ISOTP_HANDLER() { useExtendedAddressing = false; isReceiving = false; issueFlowMsgs = false; processAll = false; sendPartialMessages = false; lastSenderBus = 0; lastSenderID = 0; modelFrames = MainWindow::getReference()->getCANFrameModel()->getListReference(); connect(&frameTimer, SIGNAL(timeout()), this, SLOT(frameTimerTick())); } ISOTP_HANDLER::~ISOTP_HANDLER() { disconnect(&frameTimer, SIGNAL(timeout()), this, SLOT(frameTimerTick())); } void ISOTP_HANDLER::setExtendedAddressing(bool mode) { useExtendedAddressing = mode; } void ISOTP_HANDLER::setFlowCtrl(bool state) { issueFlowMsgs = state; } void ISOTP_HANDLER::setReception(bool mode) { if (isReceiving == mode) return; isReceiving = mode; if (isReceiving) { connect(CANConManager::getInstance(), &CANConManager::framesReceived, this, &ISOTP_HANDLER::rapidFrames); qDebug() << "Enabling reception in ISOTP handler"; } else { disconnect(CANConManager::getInstance(), &CANConManager::framesReceived, this, &ISOTP_HANDLER::rapidFrames); qDebug() << "Disabling reception in ISOTP handler"; } } void ISOTP_HANDLER::sendISOTPFrame(int bus, int ID, QByteArray data) { CANFrame frame; frame.setFrameType(QCanBusFrame::DataFrame); int currByte = 0; int sequence = 1; //Initial Sequence number is 1 if (bus < 0) return; if (bus >= CANConManager::getInstance()->getNumBuses()) return; lastSenderID = ID; lastSenderBus = bus; frame.bus = bus; frame.setFrameId(ID); if (ID > 0x7FF) frame.setExtendedFrameFormat(true); else frame.setExtendedFrameFormat(false); if (data.length() < 8) { QByteArray bytes(8,0); bytes.resize(8); bytes[0] = data.length(); for (int i = 0; i < data.length(); i++) bytes[i + 1] = data[i]; frame.setPayload(bytes); CANConManager::getInstance()->sendFrame(frame); } else //need to send a multi-part ISO_TP message - Respects timing and frame number based flow control { QByteArray bytes(8, 0); bytes[0] = 0x10 + (data.length() / 256); bytes[1] = data.length() & 0xFF; for (int i = 0; i < 6; i++) bytes[2 + i] = data[currByte++]; frame.setPayload(bytes); CANConManager::getInstance()->sendFrame(frame); //Queue up the rest of the frames waitingForFlow = true; frameTimer.setInterval(200); //wait a while for the flow frame to come in frameTimer.setTimerType(Qt::PreciseTimer); frameTimer.start(); while (currByte < data.length()) { for (int b = 0; b < 8; b++) bytes[b] = 0x00; bytes[0] = 0x20 + sequence; //Consecutive Frame starts from 0x20 + 1 (2: Frame type, 1: Sequence number) sequence = (sequence + 1) & 0xF; int bytesToGo = data.length() - currByte; if (bytesToGo > 7) bytesToGo = 7; for (int i = 0; i < bytesToGo; i++) bytes[1 + i] = data[currByte++]; frame.setPayload(bytes); sendingFrames.append(frame); //CANConManager::getInstance()->sendFrame(frame); } } } //remember, negative numbers are special -1 = all frames deleted, -2 = totally new set of frames. void ISOTP_HANDLER::updatedFrames(int numFrames) { if (numFrames == -1) //all frames deleted. Kill the display { messageBuffer.clear(); } else if (numFrames == -2) //all new set of frames. Reset { messageBuffer.clear(); for (int i = 0; i < modelFrames->length(); i++) processFrame(modelFrames->at(i)); } else //just got some new frames. See if they are relevant. { for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) { //processFrame(modelFrames->at(i)); //accepting these frames in rapidFrames instead } } } void ISOTP_HANDLER::rapidFrames(const CANConnection* conn, const QVector& pFrames) { Q_UNUSED(conn) if (pFrames.length() <= 0) return; qDebug() << "received " << QString::number(pFrames.count()) << " messages in ISOTP handler"; foreach(const CANFrame& thisFrame, pFrames) { //only process frames that we've marked are ISOTP frames //unless processAll is true if (processAll) processFrame(thisFrame); else { for (int i = 0; i < filters.count(); i++) { if ((thisFrame.bus == filters[i].bus) && ((thisFrame.frameId() & filters[i].mask) == filters[i].ID)) { processFrame(thisFrame); break; } } } } } void ISOTP_HANDLER::processFrame(const CANFrame &frame) { uint64_t ID = frame.frameId(); int frameType; int frameLen; int ln; //int offset; ISOTP_MESSAGE msg; ISOTP_MESSAGE *pMsg; QByteArray dataBytes; const unsigned char *data = reinterpret_cast(frame.payload().constData()); //qDebug() << frame.payload().count(); //int dataLen = frame.payload().count(); frameType = 0; frameLen = 0; if (useExtendedAddressing) { ID = ID << 8; ID += data[0]; frameType = data[1] >> 4; frameLen = data[1] & 0xF; } else { frameType = data[0] >> 4; frameLen = data[0] & 0xF; } switch(frameType) { case 0: //single frame message checkNeedFlush(ID); if (frameLen == 0) return; //length of zero isn't valid. if (frameLen > 6 && useExtendedAddressing) return; //impossible if (frameLen > 7) return; msg.bus = frame.bus; msg.setFrameType(QCanBusFrame::FrameType::DataFrame); msg.setExtendedFrameFormat( frame.hasExtendedFrameFormat() ); msg.setFrameId(ID); msg.isReceived = frame.isReceived; dataBytes.reserve(frameLen); msg.reportedLength = frameLen; msg.setTimeStamp(frame.timeStamp()); msg.isMultiframe = false; if (useExtendedAddressing) { for (int j = 0; j < frameLen; j++) { if (frame.payload().count() > (j+2)) dataBytes.append(data[j+2]); } } else { for (int j = 0; j < frameLen; j++) { if (frame.payload().count() > (j+1)) dataBytes.append(data[j+1]); } } qDebug() << "Emitting single frame ISOTP message"; msg.setPayload(dataBytes); emit newISOMessage(msg); break; case 1: //first frame of a multi-frame message checkNeedFlush(ID); msg.bus = frame.bus; if (frame.payload().count() < 8) return; //MUST have all 8 data bytes in this first frame. msg.setExtendedFrameFormat( frame.hasExtendedFrameFormat() ); msg.setFrameId(ID); msg.setTimeStamp(frame.timeStamp()); msg.isReceived = frame.isReceived; msg.isMultiframe = true; frameLen = frameLen << 8; if (useExtendedAddressing) { frameLen += data[2]; frameLen = frameLen & 0xFFF; dataBytes.reserve(frameLen); msg.reportedLength = frameLen; for (int j = 0; j < 5; j++) dataBytes.append(frame.payload()[3 + j]); } else { frameLen += data[1]; frameLen = frameLen & 0xFFF; msg.payload().reserve(frameLen); msg.reportedLength = frameLen; for (int j = 0; j < 6; j++) dataBytes.append(frame.payload()[2 + j]); } msg.lastSequence = -1; msg.setPayload(dataBytes); 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 && lastSenderBus==static_cast(frame.bus)) { CANFrame outFrame; outFrame.bus = lastSenderBus; outFrame.setExtendedFrameFormat(false); outFrame.setFrameId(lastSenderID); QByteArray bytes(8, 0); bytes[0] = 0x30; //flow control, go ahead and send bytes[1] = 0; //dont ask again about flow control bytes[2] = 3; //separation time in milliseconds between messages. outFrame.setPayload(bytes); CANConManager::getInstance()->sendFrame(outFrame); } break; case 2: //subsequent frames for multi-frame messages pMsg = nullptr; if (messageBuffer.contains(ID)) { pMsg = &messageBuffer[ID]; } if (!pMsg) return; if (!pMsg->isMultiframe) return; //if we didn't get a frame type 1 (start of multiframe) first then ignore this frame. dataBytes.clear(); dataBytes.append(pMsg->payload()); ln = pMsg->reportedLength - pMsg->payload().count(); //offset = pMsg->data.count(); if (useExtendedAddressing) { if (ln > 6) ln = 6; for (int j = 0; j < ln; j++) dataBytes.append(frame.payload()[j+2]); } else { if (ln > 7) ln = 7; for (int j = 0; j < ln; j++) dataBytes.append(frame.payload()[j+1]); } pMsg->setPayload(dataBytes); if (pMsg->reportedLength <= pMsg->payload().count()) { qDebug() << "Emitting multiframe ISOTP message"; checkNeedFlush(pMsg->frameId()); } break; case 3: //flow control messages switch (frameLen) //actually flow control type in this case { case 0: //continue to send frames but maybe change inter-frame delay waitingForFlow = false; //data[1] contains number of frames to send before waiting for next flow control framesUntilFlow = data[1]; if (framesUntilFlow == 0) framesUntilFlow = -1; //-1 means don't count frames and just keep going //data[2] contains the interframe delay to use (0xF1 through 0xF9 are special through - 100 to 900us) if (data[2] < 0xF1) frameTimer.start(data[2]); //set proper delay between frames else frameTimer.start(1); //can't do sub-millisecond sending with this code so just use 1ms timing break; case 1: //wait - do not send any more frames until other side says so waitingForFlow = true; frameTimer.stop(); //quit sending frames for now break; case 2: //overflow or abort. Assume this means abort and quit sending frameTimer.stop(); sendingFrames.clear(); waitingForFlow = false; break; } waitingForFlow = false; break; } } void ISOTP_HANDLER::checkNeedFlush(uint64_t ID) { ISOTP_MESSAGE *msg; if (messageBuffer.contains(ID)) { msg = &messageBuffer[ID]; if (msg->reportedLength <= msg->payload().count()) { qDebug() << "Flushing full frame" << QString::number(msg->frameId(), 16) << " " << msg->reportedLength << " " << msg->payload().count(); if (msg->reportedLength > 0) emit newISOMessage(*msg); } else { if (sendPartialMessages) { qDebug() << "Flushing a partial frame " << QString::number(msg->frameId(), 16) << " " << msg->reportedLength << " " << msg->payload().count(); if (msg->reportedLength > 0) emit newISOMessage(*msg); } else qDebug() << "Have a partial message but sending of such is disabled. Throwing it away"; } messageBuffer.remove(ID); } } void ISOTP_HANDLER::frameTimerTick() { CANFrame frame; if (!waitingForFlow) { if (!sendingFrames.isEmpty()) { frame = sendingFrames.takeFirst(); CANConManager::getInstance()->sendFrame(frame); if (framesUntilFlow > -1) framesUntilFlow--; if (framesUntilFlow == 0) //stop sending and wait for another flow control message { frameTimer.stop(); //we absolutely will not send anything until other side says to. waitingForFlow = true; } } else //no more frames to send { frameTimer.stop(); } } else //while waiting for a flow frame we didn't get one during timeout period. Try to send anyway with default timeout { waitingForFlow = false; framesUntilFlow = -1; //don't count frames, just keep sending frameTimer.setInterval(20); //pretty slow sending which should be OK as a default } } void ISOTP_HANDLER::setProcessAll(bool state) { processAll = state; } void ISOTP_HANDLER::addFilter(int pBusId, uint32_t ID, uint32_t mask) { CANFilter filt; filt.ID = ID; filt.bus = pBusId; filt.mask = mask; filters.append(filt); } void ISOTP_HANDLER::removeFilter(int pBusId, uint32_t ID, uint32_t mask) { for (int i = 0; i < filters.count(); i++) { if (filters[i].bus == pBusId && filters[i].ID == ID && filters[i].mask == mask) filters.removeAt(i); } } void ISOTP_HANDLER::clearAllFilters() { filters.clear(); } void setEmitPartials(bool mode); savvycan-220/bus_protocols/isotp_handler.h000066400000000000000000000027051500724750100211170ustar00rootroot00000000000000#pragma once #include #include #include #include #include "can_structs.h" #include "mainwindow.h" #include "canframemodel.h" #include "isotp_message.h" #include "canfilter.h" class ISOTP_HANDLER : public QObject { Q_OBJECT public: ISOTP_HANDLER(); ~ISOTP_HANDLER(); void setExtendedAddressing(bool mode); void setReception(bool mode); //set whether to accept and forward frames or not void setEmitPartials(bool mode); void sendISOTPFrame(int bus, int ID, QByteArray data); void setProcessAll(bool state); void setFlowCtrl(bool state); void addFilter(int pBusId, uint32_t ID, uint32_t mask); void removeFilter(int pBusId, uint32_t ID, uint32_t mask); void clearAllFilters(); public slots: void updatedFrames(int); void rapidFrames(const CANConnection* conn, const QVector& pFrames); void frameTimerTick(); signals: void newISOMessage(ISOTP_MESSAGE msg); private: QHash messageBuffer; QList sendingFrames; QList filters; const QVector *modelFrames; bool useExtendedAddressing; bool isReceiving; bool waitingForFlow; int framesUntilFlow; bool processAll; bool issueFlowMsgs; bool sendPartialMessages; QTimer frameTimer; uint32_t lastSenderID; uint32_t lastSenderBus; void processFrame(const CANFrame &frame); void checkNeedFlush(uint64_t ID); }; savvycan-220/bus_protocols/isotp_message.h000066400000000000000000000006361500724750100211270ustar00rootroot00000000000000#ifndef ISOTP_MESSAGE_H #define ISOTP_MESSAGE_H #include #include #include //Now a child class of CANFrame. We just add the ability to track how long it was supposed to be and other //ISOTP related details. But, mostly just CANFrame. class ISOTP_MESSAGE : public CANFrame { public: int reportedLength; int lastSequence; bool isMultiframe; }; #endif // ISOTP_MESSAGE_H savvycan-220/bus_protocols/j1939_handler.cpp000066400000000000000000000000341500724750100210640ustar00rootroot00000000000000#include "j1939_handler.h" savvycan-220/bus_protocols/j1939_handler.h000066400000000000000000000004351500724750100205360ustar00rootroot00000000000000#ifndef J1939_HANDLER_H #define J1939_HANDLER_H #include #include #include #include "can_structs.h" struct J1939ID { public: int src; int dest; int pgn; int pf; int ps; int priority; bool isBroadcast; }; #endif // J1939_HANDLER_H savvycan-220/bus_protocols/uds_handler.cpp000066400000000000000000000651341500724750100211140ustar00rootroot00000000000000#include "uds_handler.h" #include "connections/canconmanager.h" #include "mainwindow.h" #include "isotp_handler.h" #include static QVector UDS_DIAG_CTRL_SUB = { {1,"DFLT_SESS", "Default session"}, {2,"PROG_SESS", "Programming Session"}, {3,"EXT_SESS", "Extended Diagnostics Session"}, {4,"SAFETY_SESS", "Safety System Diagnostics Session"}, }; static QVector UDS_ECU_RESET_SUB = { {1,"HARD_RESET", "Hard reset of ECU"}, {2,"KEYOFFON_RESET", "Simulated key off then on reset"}, {3,"SOFT_RESET", "Soft reset - leaving RAM intact"}, {4,"EN_POWERDOWN_RESET", "Enable sleep mode"}, {5,"DIS_POWERDOWN_RESET", "Disable sleep mode"}, }; static QVector UDS_COMM_CTRL_SUB = { {0,"COMM_NORMAL", "Enable both Rx and Tx of normal messages"}, {1,"COMM_DIS_TX", "Enable reception of normal messages but don't Tx them"}, {3,"COMM_DIS_ALL", "Disable both Rx and Tx of non-diagnostics messages"}, {4,"COMM_DIS_TX_ENH", "Addressed bus master should turn off TX on related sub-bus"}, {5,"COMM_ENHANC", "Addressed bus master should set related sub-bus to app scheduling mode"}, }; static QVector UDS_ROUTINE_SUB = { {1,"START_ROUTINE", "Start routine by given ID"}, {2,"STOP_ROUTINE", "Stop routine by given ID"}, {3,"GET_ROUTINE_RESULTS", "Get results from routine specified by ID"}, }; static QVector UDS_FILE_MODEOFOP = { {1, "ADDFILE", "Add file to file system"}, {2, "DELETEFILE", "Add file to file system"}, {3, "REPLACEFILE", "Add file to file system"}, {4, "READFILE", "Add file to file system"}, {5, "READDIR", "Add file to file system"} }; static QVector UDS_SERVICE_DESC = { {1, "OBDII_SHOW_CURRENT", "OBDII - Show current data"}, {2, "OBDII_SHOW_FREEZE", "OBDII - Show freeze data"}, {3, "OBDII_SHOW_STORED_DTC", "OBDII - Show stored DTC codes"}, {4, "OBDII_CLEAR_DTC", "OBDII - Clear current DTC codes"}, {5, "OBDII_TEST_O2", "OBDII - O2 sensor testing"}, {6, "OBDII_TEST_RESULTS", "OBDII - Show emissions testing results"}, {7, "OBDII_SHOW_PENDING_DTC", "OBDII - Show pending DTC codes"}, {8, "OBDII_CONTROL_DEVICES", "OBDII - Control vehicle devices"}, {9, "OBDII_VEH_INFO", "OBDII - Retrieve vehicle information"}, {0xA, "OBDII_PERM_DTC", "OBDII - Show permanent DTC codes"}, {0x10, "DIAG_CONTROL", "Diagnostic session control"}, {0x11, "ECU_RESET", "Reset ECU"}, {0x12, "GMLAN_READ_FAILURE_RECORD", "GMLAN - Read Fail"}, {0x14, "CLEAR_DIAG", "Clear diagnostic trouble codes"}, {0x19, "READ_DTC", "Read diagnostic trouble codes"}, {0x1A, "GMLAN_READ_DIAGNOSTIC_ID", "GMLAN - Read diagnostics ID"}, {0x20, "RETURN_TO_NORMAL", "Return to normal mode"}, {0x21, "READ_BY_LOCALID", "Read data by Local ID"}, {0x22, "READ_BY_ID", "Read data by ID"}, {0x23, "READ_BY_ADDR", "Read data by address"}, {0x24, "READ_SCALING_ID", "Read scaling data by ID"}, {0x27, "SECURITY_ACCESS", "Request security access"}, {0x28, "COMM_CTRL", "Communication control"}, {0x2A, "READ_DATA_ID_PERIODIC", "Read data by ID periodically"}, {0x2C, "DYNAMIC_DATA_DEFINE", "Create dynamic data ID"}, {0x2D, "DEFINE_PID_BY_ADDR", "Create a PID for a given memory address"}, {0x2E, "WRITE_BY_ID", "Write data by ID"}, {0x2F, "IO_CTRL", "Input/Output control (force)"}, {0x31, "ROUTINE_CTRL", "Call a service routine"}, {0x34, "REQUEST_DOWNLOAD", "Request data download (from PC to ECU)"}, {0x35, "REQUEST_UPLOAD", "Request data upload (from ECU to PC)"}, {0x36, "TRANSFER_DATA", "Transfer data"}, {0x37, "REQ_TRANS_EXIT", "Request that data transfer cease"}, {0x38, "REQ_FILE_TRANS", "Request file transfer"}, {0x3B, "GMLAN_WRITE_DID", "GMLAN - Write DID"}, {0x3D, "WRITE_BY_ADDR", "Write data by address"}, {0x3E, "TESTER_PRESENT", "Tester is present"}, {0x7F, "NEG_RESPONSE","Negative Response"}, {0x83, "ACCESS_TIMING", "Read or write comm timing parameters"}, {0x84, "SECURED_DATA_TRANS", "Secured data transmission"}, {0x85, "CTRL_DTC_SETTINGS", "Control DTC settings"}, {0x86, "RESPONSE_ON_EVENT", "Request start/stop transmission on event"}, {0x87, "RESPONSE_LINK_CTRL", "Control comm link"}, {0xA2, "GMLAN_REPORT_PROG_STATE", "GMLAN - Report programming state"}, {0xA5, "GMLAN_ENTER_PROG_MODE", "GMLAN - Enter programming mode"}, {0xA9, "GMLAN_CHECK_CODES", "GMLAN - Check codes"}, {0xAA, "GMLAN_READ_DPID", "GMLAN - Read dynamic PID"}, {0xAE, "GMLAN_DEVICE_CTRL", "GMLAN - Device control"}, {0xFF, "UNKNOWN_CODE", "Unknown, likely proprietary UDS function code"} }; static QVector UDS_NEG_RESPONSE = { {0x10, "GENERAL_REJECT", "General rejection (no other codes matched)"}, {0x11, "SERVICE_NOTSUPP", "ECU does not support this service code"}, {0x12, "SUBFUNCT_NOTSUPP", "ECU does not support the requested sub function"}, {0x13, "INVALID_FORMAT", "Invalid request length or format error"}, {0x14, "RESPONSE_TOOLONG", "Response would be too long to send"}, {0x21, "BUSY", "ECU is busy. Try again later"}, {0x22, "COND_INCORR", "A prereq. condition was not met"}, {0x24, "REQ_SEQ_ERR", "Invalid sequence of requests"}, {0x25, "SUBNET_NORESP", "ECU tried to gateway request but response timed out"}, {0x26, "FAILURE", "A failure (indicated in a DTC) is preventing a reply"}, {0x31, "REQ_OUTOFRANGE", "A parameter is outside of the valid range"}, {0x33, "SECURITY_DENIED", "Security access was denied. (invalid seq or ECU not unlocked?)"}, {0x35, "INVALID_KEY", "Key passed was invalid. Failure counter has been incremented."}, {0x36, "EXCEED_ATTEMPTS", "Key failed too many times. ECU security access locked out"}, {0x37, "TIMEDELAY", "Security access too soon after last attempt"}, {0x38, "EXT_SECUR_1", "Extended security failure code 1"}, {0x39, "EXT_SECUR_2", "Extended security failure code 2"}, {0x3A, "EXT_SECUR_3", "Extended security failure code 3"}, {0x3B, "EXT_SECUR_4", "Extended security failure code 4"}, {0x3C, "EXT_SECUR_5", "Extended security failure code 5"}, {0x3D, "EXT_SECUR_6", "Extended security failure code 6"}, {0x3E, "EXT_SECUR_7", "Extended security failure code 7"}, {0x3F, "EXT_SECUR_8", "Extended security failure code 8"}, {0x40, "EXT_SECUR_9", "Extended security failure code 9"}, {0x41, "EXT_SECUR_10", "Extended security failure code 10"}, {0x42, "EXT_SECUR_11", "Extended security failure code 11"}, {0x43, "EXT_SECUR_12", "Extended security failure code 12"}, {0x44, "EXT_SECUR_13", "Extended security failure code 13"}, {0x45, "EXT_SECUR_14", "Extended security failure code 14"}, {0x46, "EXT_SECUR_15", "Extended security failure code 15"}, {0x47, "EXT_SECUR_16", "Extended security failure code 16"}, {0x48, "EXT_SECUR_17", "Extended security failure code 17"}, {0x49, "EXT_SECUR_18", "Extended security failure code 18"}, {0x4A, "EXT_SECUR_19", "Extended security failure code 19"}, {0x4B, "EXT_SECUR_20", "Extended security failure code 20"}, {0x4C, "EXT_SECUR_21", "Extended security failure code 21"}, {0x4D, "EXT_SECUR_22", "Extended security failure code 22"}, {0x4E, "EXT_SECUR_23", "Extended security failure code 23"}, {0x4F, "EXT_SECUR_24", "Extended security failure code 24"}, {0x70, "UPLOAD_DOWNLOAD", "Fault when attempting to start upload/download"}, {0x71, "TRX_SUSPENDED", "Transfer aborting due to a fault"}, {0x72, "GEN_PROGRAMMING", "Fault while attempting to write to ECU memory"}, {0x73, "WRONG_BLOCK_SEQ", "Invalid sequence value detected during transfer"}, {0x78, "RESP_PENDING", "Request successful but ECU still busy - Response pending"}, {0x7E, "SUBFUNCT_CURRSESS", "ECU does not support this subfunction in current session type"}, {0x7F, "SERVICE_CURRSESS", "ECU does not support this service in current session type"}, {0x81, "RPM_TOOHIGH", "RPM is too high to execute request"}, {0x82, "RPM_TOOLOW", "RPM is too low to execute request"}, {0x83, "ENGINE_RUNNING", "Cannot execute request while engine is running"}, {0x84, "ENGINE_NOTRUNNING", "Cannot execute request while engine is off"}, {0x85, "ENG_RUNTIME_LOW", "Cannot execute request until engine has run for longer"}, {0x86, "TEMPERATURE_HIGH", "Cannot execute request until temperature is lower"}, {0x87, "TEMPERATURE_LOW", "Cannot execute request until temperature is higher"}, {0x88, "SPEED_HIGH", "Cannot execute request until vehicle slows down"}, {0x89, "SPEED_LOW", "Cannot execute request until vehicle is going faster"}, {0x8A, "PEDAL_HIGH", "Cannot execute request until throttle is lower"}, {0x8B, "PEDAL_LOW", "Cannot execute request until throttle is higher"}, {0x8C, "NOT_NEUTRAL", "Cannot execute request until transmission is in neutral"}, {0x8D, "NOT_INGEAR", "Cannot execute request until vehicle is in gear"}, {0x8F, "BRAKE_NOTPRESSED", "Cannot execute request until brake pedal is pressed (Hold down)"}, {0x90, "NOT_PARK", "Cannot execute request until vehicle is in park"}, {0x91, "CLUTCH_LOCKED", "Cannot execute request while clutch is locked"}, {0x92, "VOLTAGE_HIGH", "Cannot execute request until voltage is lower"}, {0x93, "VOLTAGE_LOW", "Cannot execute request until voltage is higher"}, }; UDS_MESSAGE::UDS_MESSAGE() { subFunc = 0; service = 0; subFuncLen = 1; isErrorReply = false; } UDS_HANDLER::UDS_HANDLER() { isReceiving = false; useExtendedAddressing = false; modelFrames = MainWindow::getReference()->getCANFrameModel()->getListReference(); isoHandler = new ISOTP_HANDLER(); } UDS_HANDLER::~UDS_HANDLER() { delete isoHandler; } UDS_MESSAGE UDS_HANDLER::tryISOtoUDS(ISOTP_MESSAGE msg, bool *result) { const unsigned char *data = reinterpret_cast(msg.payload().constData()); int dataLen = msg.payload().count(); *result = true; UDS_MESSAGE udsMsg; udsMsg.bus = msg.bus; udsMsg.setExtendedFrameFormat(msg.hasExtendedFrameFormat()); udsMsg.setFrameId(msg.frameId()); udsMsg.isReceived = msg.isReceived; udsMsg.setTimeStamp(msg.timeStamp()); udsMsg.reportedLength = msg.reportedLength; udsMsg.service = 0; udsMsg.subFunc = 0; udsMsg.subFuncLen = 0; udsMsg.isErrorReply = false; udsMsg.setPayload(msg.payload()); if (dataLen > 0) { udsMsg.service = data[0]; if (udsMsg.service == 0x7F) { udsMsg.isErrorReply = true; if (dataLen > 1) { udsMsg.service = data[1]; if (dataLen > 2) udsMsg.subFunc = data[2]; else { *result = false; return udsMsg; }; } else { *result = false; return udsMsg; }; udsMsg.payload().remove(0, 2); } else { udsMsg.isErrorReply = false; if (dataLen > 1) udsMsg.subFunc = data[1]; udsMsg.payload().remove(0, 1); } } else { *result = false; }; return udsMsg; } void UDS_HANDLER::gotISOTPFrame(ISOTP_MESSAGE msg) { qDebug() << "UDS handler got ISOTP frame"; UDS_MESSAGE udsMsg; bool result; udsMsg = tryISOtoUDS(msg, &result); if (result) emit newUDSMessage(udsMsg); } void UDS_HANDLER::setFlowCtrl(bool state) { isoHandler->setFlowCtrl(state); } void UDS_HANDLER::setReception(bool mode) { if (isReceiving == mode) return; isReceiving = mode; if (isReceiving) { connect(isoHandler, SIGNAL(newISOMessage(ISOTP_MESSAGE)), this, SLOT(gotISOTPFrame(ISOTP_MESSAGE))); isoHandler->setReception(true); //must enable ISOTP reception too. qDebug() << "Enabling reception of ISO-TP frames in UDS handler"; } else { disconnect(isoHandler, SIGNAL(newISOMessage(ISOTP_MESSAGE)), this, SLOT(gotISOTPFrame(ISOTP_MESSAGE))); isoHandler->setReception(false); qDebug() << "Disabling reception of ISOTP frames in UDS handler"; } } void UDS_HANDLER::sendUDSFrame(const UDS_MESSAGE &msg) { QByteArray data; if (msg.bus < 0) return; if (msg.bus >= CANConManager::getInstance()->getNumBuses()) return; if (msg.service > 0xFF) return; data.append(msg.service); for (int b = msg.subFuncLen - 1; b >= 0; b--) { data.append((msg.subFunc >> (8 * b)) & 0xFF); } data.append(msg.payload()); isoHandler->sendISOTPFrame(msg.bus, msg.frameId(), data); //qDebug() << "Data sending: " << data; qDebug() << "Sent UDS service: " << getServiceShortDesc(msg.service) << " on bus " << msg.bus; } QString UDS_HANDLER::getShortDesc(QVector &codeVector, int code) { foreach (CODE_STRUCT codeRec, codeVector) { if (codeRec.code == code) return codeRec.shortDesc; } return QString(); } QString UDS_HANDLER::getLongDesc(QVector &codeVector, int code) { foreach (CODE_STRUCT codeRec, codeVector) { if (codeRec.code == code) return codeRec.longDesc; } return QString(); } QString UDS_HANDLER::getServiceShortDesc(int service) { QString ret; ret = getShortDesc(UDS_SERVICE_DESC, service); if (ret.length() == 0) ret = getShortDesc(UDS_SERVICE_DESC, service + 0x40); return ret; } QString UDS_HANDLER::getServiceLongDesc(int service) { QString ret; ret = getLongDesc(UDS_SERVICE_DESC, service); if (ret.length() == 0) ret = getLongDesc(UDS_SERVICE_DESC, service + 0x40); return ret; } QString UDS_HANDLER::getNegativeResponseShort(int respCode) { QString ret; ret = getShortDesc(UDS_NEG_RESPONSE, respCode); return ret; } QString UDS_HANDLER::getNegativeResponseLong(int respCode) { QString ret; ret = getLongDesc(UDS_NEG_RESPONSE, respCode); return ret; } /* * This function kind of breaks the hands off approach of the rest of the class. In here * we dig deep into nitty gritty details of UDS and much more of this is hardcoded * to the UDS standard than the rest of the code where the code doesn't really know anything * about what it is doing - it just reads arrays and such. * No, in here we end up with hard coded handlers to figure out each type of message and how to * interpret it to a fine level of detail. Look away code purists, this is likely to be a mess. * The output here is potentially multi-line and is useful for debugging a UDS problem. You'll * get a finely detailed dump of exactly what the message means. */ QString UDS_HANDLER::getDetailedMessageAnalysis(const UDS_MESSAGE &msg) { QString buildString; int dataSize; int addrSize; int compType, encType; const unsigned char *data = reinterpret_cast(msg.payload().constData()); int dataLen = msg.payload().length(); if (msg.isErrorReply) { buildString.append("UDS ERROR Response\n"); buildString.append("Service: " + getServiceLongDesc(msg.service) + "\n"); } else if (msg.service < 0x3F || (msg.service > 0x7F && msg.service < 0xAF)) { buildString.append("UDS Request\n"); buildString.append("Service: " + getServiceLongDesc(msg.service) + "\n"); } else { buildString.append("UDS Positive Response\n"); buildString.append("Service: " + getServiceLongDesc(msg.service - 0x40) + "\n"); } if (msg.isErrorReply) { //Negative responses replace the sub function with an error code instead buildString.append("Negative response: " + getLongDesc(UDS_NEG_RESPONSE, msg.subFunc) + "\n"); } else { switch (msg.service) { case UDS_SERVICES::DIAG_CONTROL: //diag control requests have one parameter - which type of session we want. buildString.append("Session Request: " + getLongDesc(UDS_DIAG_CTRL_SUB, msg.subFunc)); break; case UDS_SERVICES::DIAG_CONTROL + 0x40: //positive response buildString.append("Session Request: " + getLongDesc(UDS_DIAG_CTRL_SUB, msg.subFunc)); //there should be four extra bytes now if (dataLen < 5) //5 because subfunc codes are left in data so it starts with one subfunc byte { //buildString.append("\nReturned data payload wasn't at least \n4 bytes like it should have been"); } else { int p2 = data[1] * 256 + data[2]; buildString.append("\nP2MAX (Max Wait / Resp Time): " + QString::number(p2) + "ms"); p2 = (data[3] * 256 + data[4]) * 10; buildString.append("\nP2 Ext MAX: " + QString::number(p2) + "ms"); } break; case UDS_SERVICES::ECU_RESET: //ECU reset has one parameter - which reset type to ask for buildString.append("Reset Type: " + getLongDesc(UDS_ECU_RESET_SUB, msg.subFunc)); break; case UDS_SERVICES::ECU_RESET + 0x40: buildString.append("Reset Type: " + getLongDesc(UDS_ECU_RESET_SUB, msg.subFunc)); //There should be one additional byte which encodes power down time if (dataLen > 1) { if (data[1] < 0xFF) { buildString.append("\nMinimum powered down time: " + QString::number(data[1])); } else buildString.append("\nPowerdown time not available"); } else buildString.append("\nNo powerdown time returned"); break; case UDS_SERVICES::COMM_CTRL: //Comm control has potentially a lot of parameters. control type, comm type, nodeID buildString.append("Control type: " + getLongDesc(UDS_COMM_CTRL_SUB, msg.subFunc)); if (dataLen > 1) buildString.append("\nComm Type: " + QString::number(data[1])); //TODO: no attempt to interpret yet if (dataLen > 3) { int nodeID = (data[2] * 256 + data[3]); buildString.append("\nNode ID: " + Utility::formatHexNum(nodeID)); } break; case UDS_SERVICES::SECURITY_ACCESS: if ((msg.subFunc % 2) == 1) { buildString.append("Seed request for security level: " + QString::number(msg.subFunc) + "\n"); if (dataLen > 1) { buildString.append("Data payload: "); for (int j = 2; j < dataLen; j++) buildString.append(Utility::formatHexNum(data[j]) + " "); } } else { buildString.append("Key sending for security level: " + QString::number(msg.subFunc - 1)); if (dataLen > 1) //and it sure as hell should be! { buildString.append(" KEY: "); for (int j = 2; j < dataLen; j++) buildString.append(Utility::formatHexNum(data[j]) + " "); } } break; case UDS_SERVICES::SECURITY_ACCESS + 0x40: if ((msg.subFunc % 2) == 1) { buildString.append("Seed response for security level: " + QString::number(msg.subFunc) + "\n"); if (dataLen > 1) //be kinda pointless if it weren't { buildString.append("SEED: "); for (int j = 2; j < dataLen; j++) buildString.append(Utility::formatHexNum(data[j]) + " "); } } else { buildString.append("Positive response to key for security level: " + QString::number(msg.subFunc - 1)); buildString.append("\nECU is now unlocked"); } break; case UDS_SERVICES::READ_BY_ID: //parameter is groups of two bytes, each of which specify an ID to read if (dataLen > 2) { uint32_t id; for (int i = 1; i < dataLen; i = i + 2) { id = (data[i] * 256) + data[i+1]; buildString.append("\nID to read: " + Utility::formatHexNum(id)); } } break; case UDS_SERVICES::READ_BY_ID + 0x40: //reply buildString.append("Reply is non-standard and so no decoding is done. The format is (ID) followed by how ever much data that ID returns, followed by more ID/data pairs if applicable.\nPayload: "); for (int i = 1; i < dataLen; i++) { buildString.append(Utility::formatHexNum(data[i]) + " "); } break; case UDS_SERVICES::READ_BY_ADDR: //subfunc byte specifies address and length format, then address, then size dataSize = msg.subFunc >> 4; addrSize = msg.subFunc & 0xF; if (dataLen > (dataSize + addrSize)) { buildString.append("Address: 0x"); for (int i = 0; i < addrSize; i++) buildString.append(QString::number(data[1+i], 16).toUpper().rightJustified(2,'0')); buildString.append("\nSize: 0x"); for (int i = 0; i < dataSize; i++) buildString.append(QString::number(data[1+i+addrSize], 16).toUpper().rightJustified(2,'0')); } else { buildString.append("Message has insufficient bytes to properly decode address and size!"); } break; case UDS_SERVICES::READ_BY_ADDR + 0x40: buildString.append("Reply is a raw packet of data of the size requested.\nPayload: "); for (int i = 1; i < dataLen; i++) { buildString.append(Utility::formatHexNum(data[i]) + " "); } break; case UDS_SERVICES::WRITE_BY_ID: if (dataLen > 3) { int writeID = (data[1] * 256 + data[2]); buildString.append("ID to write to: " + Utility::formatHexNum(writeID) + "\nPayload: "); for (int i = 3; i < dataLen; i++) { buildString.append(Utility::formatHexNum(data[i]) + " "); } } break; case UDS_SERVICES::WRITE_BY_ID + 0x40: if (dataLen > 2) { int writeID = (data[1] * 256 + data[2]); buildString.append("ID written to: " + Utility::formatHexNum(writeID)); } break; case UDS_SERVICES::ROUTINE_CTRL: buildString.append("Routine Control: " + getLongDesc(UDS_ROUTINE_SUB, msg.subFunc)); if (dataLen > 3) { int routineID; routineID = (data[2] * 256 + data[3]); buildString.append("\nRoutine ID: " + Utility::formatHexNum(routineID)); } if (dataLen > 4) { buildString.append("\nParameter bytes to routine: "); for (int i = 4; i < dataLen; i++) { buildString.append(Utility::formatHexNum(data[i]) + " "); } } break; case UDS_SERVICES::ROUTINE_CTRL + 0x40: buildString.append("Routine Control: " + getLongDesc(UDS_ROUTINE_SUB, msg.subFunc)); if (dataLen > 2) { int routineID; routineID = (data[2] * 256 + data[3]); buildString.append("\nRoutine ID: " + Utility::formatHexNum(routineID)); } if (dataLen > 4) { buildString.append("\nBytes returned by routine: "); for (int i = 4; i < dataLen; i++) { buildString.append(Utility::formatHexNum(data[i]) + " "); } } break; case UDS_SERVICES::REQUEST_DOWNLOAD: case UDS_SERVICES::REQUEST_UPLOAD: compType = data[1] >> 4; encType = data[1] & 0xF; buildString.append("Compression Type: " + QString::number(compType)); if (compType == 0) buildString.append(" (none)"); buildString.append("\n"); buildString.append("Encryption Type: " + QString::number(encType)); if (encType == 0) buildString.append(" (none)"); buildString.append("\n"); //subfunc byte specifies address and length format, then address, then size dataSize = data[2] >> 4; addrSize = data[2] & 0xF; if (dataLen > (dataSize + addrSize)) { buildString.append("Address: 0x"); for (int i = 0; i < addrSize; i++) buildString.append(QString::number(data[3 + i], 16).toUpper().rightJustified(2,'0')); buildString.append("\nSize: 0x"); for (int i = 0; i < dataSize; i++) buildString.append(QString::number(data[3 + i + addrSize], 16).toUpper().rightJustified(2,'0')); } else { buildString.append("Message has insufficient bytes to properly decode address and size!"); } break; case UDS_SERVICES::REQUEST_DOWNLOAD + 0x40: case UDS_SERVICES::REQUEST_UPLOAD + 0x40: dataSize = data[1] >> 4; buildString.append("\nMax Size of data block: 0x"); for (int i = 0; i < dataSize; i++) buildString.append(QString::number(data[2 + i], 16).toUpper().rightJustified(2,'0')); break; case UDS_SERVICES::TRANSFER_DATA: case UDS_SERVICES::TRANSFER_DATA + 0x40: buildString.append("\nBlock Sequence: " + QString::number(data[1], 16) + "\nPayload: "); for (int i = 2; i < dataLen; i++) { buildString.append(Utility::formatHexNum(data[i]) + " "); } break; case UDS_SERVICES::REQ_TRANS_EXIT: case UDS_SERVICES::REQ_TRANS_EXIT + 0x40: buildString.append("\nPayload: "); for (int i = 1; i < dataLen; i++) { buildString.append(Utility::formatHexNum(data[i]) + " "); } break; case UDS_SERVICES::REQ_FILE_TRANS: break; case UDS_SERVICES::WRITE_BY_ADDR: break; } } //qDebug() << buildString; return buildString; } //Little shim functions that drop straight through to the ISO_TP handler void UDS_HANDLER::setProcessAllIDs(bool state) { isoHandler->setProcessAll(state); } void UDS_HANDLER::addFilter(uint32_t pBusId, uint32_t ID, uint32_t mask) { isoHandler->addFilter(pBusId, ID, mask); } void UDS_HANDLER::removeFilter(uint32_t pBusId, uint32_t ID, uint32_t mask) { isoHandler->removeFilter(pBusId, ID, mask); } void UDS_HANDLER::clearAllFilters() { isoHandler->clearAllFilters(); } savvycan-220/bus_protocols/uds_handler.h000066400000000000000000000060751500724750100205600ustar00rootroot00000000000000#ifndef UDS_HANDLER_H #define UDS_HANDLER_H #include #include #include #include "can_structs.h" #include "isotp_message.h" class ISOTP_HANDLER; namespace UDS_SERVICES { enum { OBDII_SHOW_CURRENT = 1, OBDII_SHOW_FREEZE = 2, OBDII_SHOW_STORED_DTC = 3, OBDII_CLEAR_DTC = 4, OBDII_TEST_O2 = 5, OBDII_TEST_RESULTS = 6, OBDII_SHOW_PENDING_DTC = 7, OBDII_CONTROL_DEVICES = 8, OBDII_VEH_INFO = 9, OBDII_PERM_DTC = 0xA, DIAG_CONTROL = 0x10, ECU_RESET = 0x11, GMLAN_READ_FAILURE_RECORD = 0x12, CLEAR_DIAG = 0x14, READ_DTC = 0x19, GMLAN_READ_DIAGNOSTIC_ID = 0x1A, RETURN_TO_NORMAL = 0x20, READ_BY_ID = 0x22, READ_BY_ADDR = 0x23, READ_SCALING_ID = 0x24, SECURITY_ACCESS = 0x27, COMM_CTRL = 0x28, READ_DATA_ID_PERIODIC = 0x2A, DYNAMIC_DATA_DEFINE = 0x2C, DEFINE_PID_BY_ADDR = 0x2D, WRITE_BY_ID = 0x2E, IO_CTRL = 0x2F, ROUTINE_CTRL = 0x31, REQUEST_DOWNLOAD = 0x34, REQUEST_UPLOAD = 0x35, TRANSFER_DATA = 0x36, REQ_TRANS_EXIT = 0x37, REQ_FILE_TRANS = 0x38, GMLAN_WRITE_DID = 0x3B, WRITE_BY_ADDR = 0x3D, TESTER_PRESENT = 0x3E, NEG_RESPONSE = 0x7F, ACCESS_TIMING = 0x83, SECURED_DATA_TRANS = 0x84, CTRL_DTC_SETTINGS = 0x85, RESPONSE_ON_EVENT = 0x86, RESPONSE_LINK_CTRL = 0x87, GMLAN_REPORT_PROG_STATE = 0xA2, GMLAN_ENTER_PROG_MODE = 0xA5, GMLAN_CHECK_CODES = 0xA9, GMLAN_READ_DPID = 0xAA, GMLAN_DEVICE_CTRL = 0xAE }; } struct CODE_STRUCT { int code; QString shortDesc; QString longDesc; }; class UDS_MESSAGE: public ISOTP_MESSAGE { public: int service; int subFunc; int subFuncLen; bool isErrorReply; UDS_MESSAGE(); }; class UDS_HANDLER : public QObject { Q_OBJECT public: UDS_HANDLER(); ~UDS_HANDLER(); void setExtendedAddressing(bool mode); static UDS_HANDLER* getInstance(); void setReception(bool mode); //set whether to accept and forward frames or not void sendUDSFrame(const UDS_MESSAGE &msg); void setProcessAllIDs(bool state); void setFlowCtrl(bool state); void addFilter(uint32_t pBusId, uint32_t ID, uint32_t mask); void removeFilter(uint32_t pBusId, uint32_t ID, uint32_t mask); void clearAllFilters(); QString getServiceShortDesc(int service); QString getServiceLongDesc(int service); QString getNegativeResponseShort(int respCode); QString getNegativeResponseLong(int respCode); QString getShortDesc(QVector &codeVector, int code); QString getLongDesc(QVector &codeVector, int code); QString getDetailedMessageAnalysis(const UDS_MESSAGE &msg); UDS_MESSAGE tryISOtoUDS(ISOTP_MESSAGE msg, bool *result); public slots: void gotISOTPFrame(ISOTP_MESSAGE msg); signals: void newUDSMessage(UDS_MESSAGE msg); private: QList messageBuffer; const QVector *modelFrames; bool isReceiving; bool useExtendedAddressing; void processFrame(const CANFrame &frame); ISOTP_HANDLER *isoHandler; }; #endif // UDS_HANDLER_H savvycan-220/can_structs.cpp000066400000000000000000000000321500724750100162410ustar00rootroot00000000000000#include "can_structs.h" savvycan-220/can_structs.h000066400000000000000000000024561500724750100157220ustar00rootroot00000000000000#ifndef CAN_STRUCTS_H #define CAN_STRUCTS_H #include #include #include #include //Now inherits from the built-in CAN frame class from Qt. This should be more future proof and easier to integrate with other code struct CANFrame : public QCanBusFrame { public: int bus; bool isReceived; //did we receive this or send it? uint64_t timedelta; uint32_t frameCount; //used in overwrite mode friend bool operator<(const CANFrame& l, const CANFrame& r) { qint64 lStamp = l.timeStamp().seconds() * 1000000 + l.timeStamp().microSeconds(); qint64 rStamp = r.timeStamp().seconds() * 1000000 + r.timeStamp().microSeconds(); return lStamp < rStamp; } CANFrame() { setFrameId(0); bus = 0; setExtendedFrameFormat(false); setFrameType(QCanBusFrame::DataFrame); isReceived = true; timedelta = 0; frameCount = 1; } }; class CANFltObserver { public: quint32 id; quint32 mask; QObject * observer; //used to target the specific object that setup this filter bool operator ==(const CANFltObserver &b) const { if ( (id == b.id) && (mask == b.mask) && (observer == b.observer) ) return true; return false; } }; #endif // CAN_STRUCTS_H savvycan-220/can_trigger_structs.h000066400000000000000000000050041500724750100174350ustar00rootroot00000000000000#ifndef CAN_TRIGGER_STRUCTS_H #define CAN_TRIGGER_STRUCTS_H #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 { public: bool readyCount; //ready to do millisecond ticks? int ID; //which ID to match against int milliseconds; //interval for triggering int msCounter; //how many MS have ticked since last trigger 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. //If ID is zero then this is a numeric operand. The number is then //stored in databyte //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: int ID; 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 enum ModifierOperationType { ADDITION, SUBTRACTION, MULTIPLICATION, DIVISION, AND, OR, XOR, MOD }; //A single modifier operation class ModifierOp { public: ModifierOperand first; ModifierOperand second; ModifierOperationType operation; }; //All the operations that entail a single modifier. For instance D0+1+D2 is two operations class Modifier { public: int destByte; //if -1 then target one of this ID's signals instead QString signalName; QList operations; }; //A single line from the data grid. Inherits from CANFrame so it stores a canbus frame //plus the extra stuff for the data grid class FrameSendData : public CANFrame { public: bool enabled; int count; QList triggers; QList modifiers; }; #endif // CAN_TRIGGER_STRUCTS_H savvycan-220/canbridgewindow.cpp000066400000000000000000000111411500724750100170620ustar00rootroot00000000000000#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(); } } savvycan-220/canbridgewindow.h000066400000000000000000000013651500724750100165360ustar00rootroot00000000000000#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 savvycan-220/candatagrid.cpp000066400000000000000000000550351500724750100161670ustar00rootroot00000000000000#include "candatagrid.h" #include "ui_candatagrid.h" #include #include #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) { ui->setupUi(this); gridMode = GridMode::CHANGED_BITS; 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 < 64; y++) { textStates[y][x] = GridTextState::NORMAL; heatData[y * 8 + x] = 0; } blackBrush = QBrush(Qt::black); whiteBrush = QBrush(Qt::white); redBrush = QBrush(Qt::red); greenBrush = QBrush(Qt::green); 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() { delete ui; } GridMode CANDataGrid::getMode() { return gridMode; } 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(); if (event->button() == Qt::LeftButton) { //qDebug() << "Mouse Loc " << clickedPoint; clickedPoint -= upperLeft; if (clickedPoint.x() < 0 || clickedPoint.y() < 0) { //qDebug() << "Clicked outside the grid"; return; } int x = clickedPoint.x() / gridSize.x(); int y = clickedPoint.y() / gridSize.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 bitPos, GridTextState 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 bitPos) { int byte = bitPos / 8; int bit = bitPos & 7; return textStates[byte][bit]; } void CANDataGrid::setSignalNames(int sigIdx, const QString sigName) { if (sigIdx < 0) return; if (sigIdx >= signalNames.size()) { signalNames.resize(sigIdx * 2); } signalNames[sigIdx] = sigName; } void CANDataGrid::clearSignalNames() { signalNames.clear(); signalNames.resize(40); } void CANDataGrid::setUsedSignalNum(int bit, int signal) { if (bit < 0) return; if (bit > 511) return; usedSignalNum[bit] = signal; } int CANDataGrid::getUsedSignalNum(int bit) { if (bit < 0) return 0; if (bit > 511) return 0; return usedSignalNum[bit]; } void CANDataGrid::paintEvent(QPaintEvent *event) { Q_UNUSED(event); paintCommonBeginning(); 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; painter = new QPainter(this); viewport = painter->viewport(); neededXDivisions = 8; neededYDivisions = 8; int textRestrict = qMax(neededXDivisions, neededYDivisions); if (bytesToDraw > 8) { neededXDivisions = 16; } if (bytesToDraw > 16) { neededXDivisions = 16; neededYDivisions = 16; } 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); painter->setFont(mainFont); smallFont.setPixelSize(smallTextSize); boldFont.setPixelSize(bigTextSize); boldFont.setBold(true); 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); //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->drawLine(nearX, nearY + (y * ySector), farX, nearY + (y * ySector) ); } 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)); } 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::paintGridCells() { int x, y, bit; unsigned char prevByte, thisByte; bool thisBit, prevBit; int usedSigNum; QString prevSigName; //now, color the bitfield by seeing if a given bit is freshly set/unset in the new data //compared to the old. Bits that are not set in either are white, bits set in both are black //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 < neededYDivisions; y++) { for (x = 0; x < neededXDivisions; 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 << bitIdx)) == (1 << bitIdx)) thisBit = true; if ((prevByte & (1 << bitIdx)) == (1 << bitIdx)) prevBit = true; if (gridMode == GridMode::HEAT_VIEW) { painter->setBrush(QBrush(fire[heatData[bit]])); } else { if (thisBit) { 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 { if (prevBit) { 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); } } } 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)); 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); 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 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 * 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 < neededYDivisions; y++) { for (x = 0; x < neededXDivisions; x++) { int byteIdx = (y * (neededXDivisions / 8) + (x / 8)); int bitIdx = ((neededXDivisions - 1) - x) & 7; bit = (byteIdx * 8) + bitIdx; usedSigNum = -1; if ((usedData[byteIdx] & (1 << bitIdx)) == (1 << bitIdx)) { usedSigNum = getUsedSignalNum(bit); if ((usedSigNum > -1) && (prevSigName != signalNames[usedSigNum]) ) { prevSigName = signalNames[usedSigNum]; int textWidth = smallMetric->horizontalAdvance(prevSigName); 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(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(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); } } } } } } } } //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() { //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 Q_UNUSED(height); //can't quite do the below commented out stuff //it works but doesn't scale the image into that pixmap. Need to //figure out how to draw the size of the pixmap /* QSize pSize; if (width == 0) pSize.setWidth(this->size().width()); else pSize.setWidth(width); if (height == 0) pSize.setHeight(this->size().height()); else pSize.setHeight(height); QPixmap pixmap(pSize); */ QPixmap pixmap(this->size()); this->render(&pixmap); 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) { 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) { 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) { 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(); } savvycan-220/candatagrid.h000066400000000000000000000101151500724750100156220ustar00rootroot00000000000000#ifndef CANDATAGRID_H #define CANDATAGRID_H #include namespace Ui { class CANDataGrid; } /* * CANDataGrid is a custom QT widget that proves that it's possible to shoehorn 12x too much crap into one widget. The purported * goal of the widget was to show an 8x8 grid of bits that show whether each bit in a given message is set or not. It allows one to quickly * visualize the bits within a CAN frame's data bytes. From there it evolved to also show whether a given bit was freshly set recently, freshly * unset recently, or has stayed set or unset for a while. It can also gray out bytes that don't exist in the current CAN frame. * This is handled by the calling code. * * After that functionality was added so it could also emit a signal when it is clicked. The signal will tell you which "bit" cell was clicked. * * Then, support was added for modifying the text color of each cell. This can be used for whatever the calling code wants. An example is * FlowView that uses all of the above functionality plus this functionality in order to show which bits are set as triggers for stopping * the flowview playback. * * 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 { NORMAL, BOLD_BLUE, INVERT }; enum GridMode { 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 public: explicit CANDataGrid(QWidget *parent = 0); ~CANDataGrid(); 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 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 bitClicked); void gridRightClicked(int bitClicked); private: Ui::CANDataGrid *ui; 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; GridTextState textStates[64][8]; //first dimension is bytes, second is bits QPoint upperLeft, gridSize; GridMode gridMode; QBrush blackBrush, whiteBrush, redBrush, greenBrush, grayBrush; QBrush greenHashBrush, blackHashBrush; QPainter *painter; QRect viewport; int xSpan; int ySpan; 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 paintGridCells(); void paintCommonBeginning(); void paintCommonEnding(); int gridToBitPosition(int x, int y); QPoint getGridPointFromBitPosition(int bitPos); int getSignalRowRun(int sigNum, int startBit); }; #endif // CANDATAGRID_H savvycan-220/canfilter.cpp000066400000000000000000000006641500724750100156730ustar00rootroot00000000000000#include "canfilter.h" CANFilter::CANFilter() { ID = 0; mask = 0; bus = -1; } void CANFilter::setFilter(uint32_t id, uint32_t mask, int bus) { this->ID = id; this->mask = mask; this->bus = bus; } bool CANFilter::checkFilter(uint32_t id, int bus) { if (bus == -1 || bus == this->bus) { uint32_t result = id & this->mask; if (result == this->ID) return true; } return false; } savvycan-220/canfilter.h000066400000000000000000000004421500724750100153320ustar00rootroot00000000000000#ifndef CANFILTER_H #define CANFILTER_H #include class CANFilter { public: CANFilter(); void setFilter(uint32_t id, uint32_t mask, int bus); bool checkFilter(uint32_t id, int bus); public: uint32_t ID; uint32_t mask; int bus; }; #endif // CANFILTER_H savvycan-220/canframemodel.cpp000066400000000000000000001002111500724750100165060ustar00rootroot00000000000000#include "canframemodel.h" #include #include #include #include #include #include "utility.h" CANFrameModel::~CANFrameModel() { frames.clear(); filteredFrames.clear(); filters.clear(); busFilters.clear(); } int CANFrameModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if (filteredFrames.data()) { int rows = filteredFrames.count(); return rows; } //just in case somehow data is invalid which I have seen before. //But, this should not happen so issue a debugging message too qDebug() << "Invalid data for filteredFrames. Returning 0."; return 0; } int CANFrameModel::totalFrameCount() { int count; count = frames.count(); return count; } int CANFrameModel::columnCount(const QModelIndex &index) const { Q_UNUSED(index); return (int)Column::NUM_COLUMN; } CANFrameModel::CANFrameModel(QObject *parent) : QAbstractTableModel(parent) { 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; } 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); //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; 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) { if (useHexMode != mode) { this->beginResetModel(); useHexMode = mode; Utility::decimalMode = !useHexMode; this->endResetModel(); } } void CANFrameModel::setTimeStyle(TimeStyle newStyle) { if (timeStyle != newStyle) { this->beginResetModel(); timeStyle = newStyle; Utility::timeStyle = newStyle; this->endResetModel(); } } void CANFrameModel::setInterpretMode(bool mode) { //if the state of interpretFrames changes then we need to reset the model //so that QT will refresh the view properly if (interpretFrames != mode) { this->beginResetModel(); interpretFrames = mode; this->endResetModel(); } } bool CANFrameModel::getInterpretMode() { return interpretFrames; } void CANFrameModel::setTimeFormat(QString format) { Utility::timeFormat = format; timeFormat = format; beginResetModel(); //reset model to show new time format endResetModel(); } void CANFrameModel::setIgnoreDBCColors(bool mode) { if(ignoreDBCColors != mode) { beginResetModel(); //reset model to update the view ignoreDBCColors = mode; endResetModel(); } } /* * Scan all frames for the smallest timestamp and offset all timestamps so that smallest one is at 0 */ void CANFrameModel::normalizeTiming() { mutex.lock(); if (frames.count() == 0) { mutex.unlock(); return; } timeOffset = frames[0].timeStamp().microSeconds(); qint64 prevStamp = 0; //find the absolute lowest timestamp in the whole time. Needed because maybe timestamp was reset in the middle. for (int j = 0; j < frames.count(); j++) { if (frames[j].timeStamp().microSeconds() < timeOffset) timeOffset = frames[j].timeStamp().microSeconds(); } for (int i = 0; i < frames.count(); i++) { qint64 thisStamp = frames[i].timeStamp().microSeconds() - timeOffset; if (thisStamp <= prevStamp) { timeOffset -= prevStamp; } frames[i].setTimeStamp(QCanBusFrame::TimeStamp(0, thisStamp)); } this->beginResetModel(); for (int i = 0; i < filteredFrames.count(); i++) { filteredFrames[i].setTimeStamp(QCanBusFrame::TimeStamp(0, filteredFrames[i].timeStamp().microSeconds() - timeOffset)); } this->endResetModel(); mutex.unlock(); } void CANFrameModel::setOverwriteMode(bool mode) { beginResetModel(); overwriteDups = mode; recalcOverwrite(); endResetModel(); } void CANFrameModel::setClearMode(bool mode) { filtersPersistDuringClear = mode; } void CANFrameModel::setFilterState(unsigned int ID, bool state) { if (!filters.contains(ID)) return; filters[ID] = state; sendRefresh(); } void CANFrameModel::setBusFilterState(unsigned int BusID, bool state) { if (!busFilters.contains(BusID)) return; busFilters[BusID] = state; sendRefresh(); } void CANFrameModel::setAllFilters(bool state) { QMap::iterator it; for (it = filters.begin(); it != filters.end(); ++it) { it.value() = state; } sendRefresh(); } /* * There is probably a more correct way to have done this but below are several functions that collectively implement * 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(QVector *frames, int row, Column col) { uint64_t temp = 0; if (row >= frames->count()) return 0; CANFrame frame = frames->at(row); switch (col) { case Column::TimeStamp: if (overwriteDups) return frame.timedelta; return frame.timeStamp().microSeconds(); case Column::FrameId: return frame.frameId(); case Column::Extended: if (frame.hasExtendedFrameFormat()) return 1; return 0; case Column::Remote: if (overwriteDups) return frame.frameCount; if (frame.frameType() == QCanBusFrame::RemoteRequestFrame) return 1; return 0; case Column::Direction: if (frame.isReceived) return 1; return 0; case Column::Bus: return static_cast(frame.bus); case Column::Length: return static_cast(frame.payload().length()); case Column::ASCII: //sort both the same for now case Column::Data: 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: return 0; } return 0; } void CANFrameModel::qSortCANFrameAsc(QVector *frames, Column column, int lowerBound, int upperBound) { int p, i, j; qDebug() << "Lower " << lowerBound << " Upper" << upperBound; if (lowerBound < upperBound) { uint64_t piv = getCANFrameVal(frames, lowerBound + (upperBound - lowerBound) / 2, column); i = lowerBound - 1; j = upperBound + 1; for (;;){ do { i++; } while ((i < upperBound) && getCANFrameVal(frames, i, column) < piv); do { j--; } while ((j > lowerBound) && getCANFrameVal(frames, j, column) > piv); if (i < j) { CANFrame temp = frames->at(i); frames->replace(i, frames->at(j)); frames->replace(j, temp); } else {p = j; break;} } qSortCANFrameAsc(frames, column, lowerBound, p); qSortCANFrameAsc(frames, column, p+1, upperBound); } } void CANFrameModel::qSortCANFrameDesc(QVector *frames, Column column, int lowerBound, int upperBound) { int p, i, j; qDebug() << "Lower " << lowerBound << " Upper" << upperBound; if (lowerBound < upperBound) { uint64_t piv = getCANFrameVal(frames, lowerBound + (upperBound - lowerBound) / 2, column); i = lowerBound - 1; j = upperBound + 1; for (;;){ do { i++; } while ((i < upperBound) && getCANFrameVal(frames, i, column) > piv); do { j--; } while ((j > lowerBound) && getCANFrameVal(frames, j, column) < piv); if (i < j) { CANFrame temp = frames->at(i); frames->replace(i, frames->at(j)); frames->replace(j, temp); } else {p = j; break;} } qSortCANFrameDesc(frames, column, lowerBound, p); qSortCANFrameDesc(frames, column, p+1, upperBound); } } void CANFrameModel::sortByColumn(int column) { sortDirAsc = !sortDirAsc; 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 void CANFrameModel::recalcOverwrite() { if (!overwriteDups) return; //no need to do a thing if mode is disabled qDebug() << "recalcOverwrite called in model"; mutex.lock(); beginResetModel(); //Look at the current list of frames and turn it into just a list of unique IDs QHash overWriteFrames; uint64_t idAugmented; //id in lower 29 bits, bus number shifted up 29 bits foreach(CANFrame frame, frames) { if (frame.frameType() != frame.DataFrame) continue; idAugmented = frame.frameId(); idAugmented = idAugmented + (frame.bus << 29ull); if (filters[frame.frameId()] && busFilters[frame.bus]) { 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.reserve(preallocSize); filteredFrames.clear(); filteredFrames.append(overWriteFrames.values().toVector()); filteredFrames.reserve(preallocSize); /*for (int i = 0; i < frames.count(); i++) { if (filters[frames[i].frameId()] && busFilters[frames[i].bus]) { filteredFrames.append(frames[i]); } }*/ endResetModel(); mutex.unlock(); } QVariant CANFrameModel::data(const QModelIndex &index, int role) const { QString tempString; CANFrame thisFrame; static bool rowFlip = false; QVariant ts; if (!index.isValid()) return QVariant(); if (index.row() >= (filteredFrames.count())) return QVariant(); thisFrame = filteredFrames.at(index.row()); const unsigned char *data = reinterpret_cast(thisFrame.payload().constData()); int dataLen = thisFrame.payload().count(); if (role == Qt::BackgroundRole) { if (dbcHandler != nullptr && interpretFrames && !ignoreDBCColors) { DBC_MESSAGE *msg = dbcHandler->findMessage(thisFrame); if (msg != nullptr) { return msg->bgColor; } } rowFlip = (index.row() % 2); if (rowFlip) return QApplication::palette().color(QPalette::Base); else return QApplication::palette().color(QPalette::AlternateBase); } if (role == Qt::TextAlignmentRole) { switch(Column(index.column())) { case Column::TimeStamp: return Qt::AlignRight; case Column::FrameId: case Column::Direction: case Column::Extended: case Column::Bus: case Column::Remote: case Column::Length: return Qt::AlignHCenter; default: return Qt::AlignLeft; } } if (role == Qt::ForegroundRole) { if (dbcHandler != nullptr && interpretFrames && !ignoreDBCColors) { DBC_MESSAGE *msg = dbcHandler->findMessage(thisFrame); if (msg != nullptr) { return msg->fgColor; } } return QApplication::palette().color(QPalette::WindowText); } if (role == Qt::DisplayRole) { switch (Column(index.column())) { case Column::TimeStamp: //Reformatting the output a bit with custom code if (overwriteDups) { 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()); if (ts.type() == QVariant::Double) return QString::number(ts.toDouble(), 'f', 5); //never scientific notation, 5 decimal places if (ts.type() == QVariant::LongLong) return QString::number(ts.toLongLong()); //never scientific notion, all digits shown if (ts.type() == QVariant::DateTime) return ts.toDateTime().toString(timeFormat); //custom set format for dates and times return Utility::formatTimestamp(thisFrame.timeStamp().microSeconds()); case Column::FrameId: return Utility::formatCANID(thisFrame.frameId(), thisFrame.hasExtendedFrameFormat()); case Column::Extended: return QString::number(thisFrame.hasExtendedFrameFormat()); case Column::Remote: if (!overwriteDups) return QString::number(thisFrame.frameType() == QCanBusFrame::RemoteRequestFrame); return QString::number(thisFrame.frameCount); case Column::Direction: if (thisFrame.isReceived) return QString(tr("Rx")); return QString(tr("Tx")); case Column::Bus: return QString::number(thisFrame.bus); case Column::Length: return QString::number(dataLen); case Column::ASCII: if (thisFrame.frameId() >= 0x7FFFFFF0ull) { tempString.append("MARK "); tempString.append(QString::number(thisFrame.frameId() & 0x7)); return tempString; } if (thisFrame.frameType() == QCanBusFrame::DataFrame) { if (dataLen < 0) dataLen = 0; //if (dLen > 8) dLen = 8; for (int i = 0; i < dataLen; i++) { char byt = thisFrame.payload()[i]; //0x20 through 0x7E are printable characters. Outside of that range they aren't. So use dots instead 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) { tempString = "ERROR"; } return tempString; case Column::Data: if (dataLen < 0) dataLen = 0; //if (useHexMode) tempString.append("0x "); if (thisFrame.frameType() == QCanBusFrame::RemoteRequestFrame) { return tempString; } for (int i = 0; i < dataLen; i++) { if (useHexMode) tempString.append( QString::number(data[i], 16).toUpper().rightJustified(2, '0')); else tempString.append(QString::number(data[i], 10)); if (!((i+1) % bytesPerLine) && (i != (dataLen - 1))) tempString.append("\n"); else tempString.append(" "); } if (thisFrame.frameType() == thisFrame.ErrorFrame) { if (thisFrame.error() & thisFrame.TransmissionTimeoutError) tempString.append("\nTX Timeout"); if (thisFrame.error() & thisFrame.LostArbitrationError) tempString.append("\nLost Arbitration"); if (thisFrame.error() & thisFrame.ControllerError) tempString.append("\nController Error"); if (thisFrame.error() & thisFrame.ProtocolViolationError) tempString.append("\nProtocol Violation"); if (thisFrame.error() & thisFrame.TransceiverError) tempString.append("\nTransceiver Error"); if (thisFrame.error() & thisFrame.MissingAcknowledgmentError) tempString.append("\nMissing ACK"); if (thisFrame.error() & thisFrame.BusOffError) tempString.append("\nBus OFF"); if (thisFrame.error() & thisFrame.BusError) tempString.append("\nBus ERR"); if (thisFrame.error() & thisFrame.ControllerRestartError) tempString.append("\nController restart err"); if (thisFrame.error() & thisFrame.UnknownError) tempString.append("\nUnknown error type"); } //TODO: technically the actual returned bytes for an error frame encode some more info. Not interpreting it yet. //now, if we're supposed to interpret the data and the DBC handler is loaded then use it if ( (dbcHandler != nullptr) && interpretFrames && (thisFrame.frameType() == thisFrame.DataFrame) ) { DBC_MESSAGE *msg = dbcHandler->findMessage(thisFrame); if (msg != nullptr) { tempString.append(" <" + msg->name + ">\n"); if (msg->comment.length() > 1) tempString.append(msg->comment + "\n"); for (int j = 0; j < msg->sigHandler->getCount(); j++) { QString sigString; DBC_SIGNAL* sig = msg->sigHandler->findSignalByIdx(j); if ( (sig->multiplexParent == nullptr) && sig->processAsText(thisFrame, sigString)) { tempString.append(sigString); tempString.append("\n"); if (sig->isMultiplexor) { qDebug() << "Multiplexor. Diving into the tree"; tempString.append(sig->processSignalTree(thisFrame)); } } else if (sig->isMultiplexed && overwriteDups) //wasn't in this exact frame but is in the message. Use cached value { bool isInteger = false; if (sig->valType == UNSIGNED_INT || sig->valType == SIGNED_INT) isInteger = true; tempString.append(sig->makePrettyOutput(sig->cachedValue.toDouble(), sig->cachedValue.toLongLong(), true, isInteger)); tempString.append("\n"); } } } } return tempString; default: return tempString; } } return QVariant(); } QVariant CANFrameModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) { switch (Column(section)) { case Column::TimeStamp: if (overwriteDups) return QString(tr("Time Delta")); return QString(tr("Timestamp")); case Column::FrameId: return QString(tr("ID")); case Column::Extended: return QString(tr("Ext")); case Column::Remote: if (!overwriteDups) return QString(tr("RTR")); return QString(tr("Cnt")); case Column::Direction: return QString(tr("Dir")); case Column::Bus: return QString(tr("Bus")); case Column::Length: return QString(tr("Len")); case Column::ASCII: return QString(tr("ASCII")); case Column::Data: return QString(tr("Data")); default: return QString(""); } } else return QString::number(section + 1); return QVariant(); } bool CANFrameModel::any_filters_are_configured(void) { for (auto const &val : filters) { if (val == true) continue; else return true; } return false; } bool CANFrameModel::any_busfilters_are_configured(void) { for (auto const &val : busFilters) { if (val == true) continue; else return true; } return false; } void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false) { /*TODO: remove mutex */ mutex.lock(); CANFrame tempFrame; tempFrame = frame; tempFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, tempFrame.timeStamp().microSeconds() - timeOffset)); lastUpdateNumFrames++; //if this ID isn't found in the filters list then add it and show it by default if (!filters.contains(tempFrame.frameId())) { // if there are any filters already configured, leave the new filter disabled if (any_filters_are_configured()) filters.insert(tempFrame.frameId(), false); else filters.insert(tempFrame.frameId(), true); needFilterRefresh = true; } //if this BusID isn't found in the busFilters list then add it and show it by default if (!busFilters.contains(tempFrame.bus)) { // if there are any busFilters already configured, leave the new filter disabled if (any_busfilters_are_configured()) busFilters.insert(tempFrame.bus, false); else busFilters.insert(tempFrame.bus, true); needFilterRefresh = true; } if (!overwriteDups) { try { 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++) // { // 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 ( (filteredFrames[i].frameId() == tempFrame.frameId()) && (filteredFrames[i].bus == tempFrame.bus) ) { 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); if (filters[tempFrame.frameId()] && busFilters[tempFrame.bus]) { if (autoRefresh) beginInsertRows(QModelIndex(), filteredFrames.count(), filteredFrames.count()); tempFrame.frameCount = 1; tempFrame.timedelta = 0; filteredFrames.append(tempFrame); if (autoRefresh) endInsertRows(); } } else { for (int j = 0; j < filteredFrames.count(); j++) { if ( (filteredFrames[j].frameId() == tempFrame.frameId()) && (filteredFrames[j].bus == tempFrame.bus) ) { if (autoRefresh) beginResetModel(); filteredFrames.replace(j, tempFrame); if (autoRefresh) endResetModel(); } } } } mutex.unlock(); } 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); } if (overwriteDups) //if in overwrite mode we'll update every time frames come in { beginResetModel(); endResetModel(); } } void CANFrameModel::sendRefresh() { qDebug() << "Sending mass refresh"; 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) { beginInsertRows(QModelIndex(), pos, pos); endInsertRows(); } //issue a refresh for the last num entries in the model. //used by the serial worker to do batch updates so it doesn't //have to send thousands of messages per second int CANFrameModel::sendBulkRefresh() { //int num = filteredFrames.count() - lastUpdateNumFrames; if (lastUpdateNumFrames <= 0) return 0; if (lastUpdateNumFrames == 0 && !overwriteDups) return 0; //if (filteredFrames.count() == 0) return 0; //qDebug() << "Bulk refresh of " << lastUpdateNumFrames; beginResetModel(); endResetModel(); int num = lastUpdateNumFrames; lastUpdateNumFrames = 0; return num; } void CANFrameModel::clearFrames() { mutex.lock(); this->beginResetModel(); frames.clear(); filteredFrames.clear(); if(filtersPersistDuringClear == false) { filters.clear(); busFilters.clear(); } frames.reserve(preallocSize); filteredFrames.reserve(preallocSize); this->endResetModel(); lastUpdateNumFrames = 0; mutex.unlock(); emit updatedFiltersList(); } /* * Since the getListReference function returns readonly * you can't insert frames with it. Instead this function * allows for a mass import of frames into the model */ void CANFrameModel::insertFrames(const QVector &newFrames) { //not resetting the model here because the serial worker automatically does a bulk refresh every 1/4 second //and that refresh will cause the view to update. If you do both it usually ends up thinking you have //double the number of frames. //beginResetModel(); mutex.lock(); int insertedFiltered = 0; for (int i = 0; i < newFrames.count(); i++) { frames.append(newFrames[i]); if (!filters.contains(newFrames[i].frameId())) { filters.insert(newFrames[i].frameId(), true); needFilterRefresh = true; } if (filters[newFrames[i].frameId()]) { busFilters.insert(newFrames[i].bus, true); needFilterRefresh = true; } if (filters[newFrames[i].frameId()] && busFilters[newFrames[i].bus]) { insertedFiltered++; filteredFrames.append(newFrames[i]); } } lastUpdateNumFrames = newFrames.count(); mutex.unlock(); //endResetModel(); //beginInsertRows(QModelIndex(), filteredFrames.count() + 1, filteredFrames.count() + insertedFiltered); //endInsertRows(); if (needFilterRefresh) emit updatedFiltersList(); } int CANFrameModel::getIndexFromTimeID(unsigned int ID, double timestamp) { int bestIndex = -1; int64_t intTimeStamp = static_cast (timestamp * 1000000l); for (int i = 0; i < frames.count(); i++) { if ((frames[i].frameId() == ID)) { if (frames[i].timeStamp().microSeconds() <= intTimeStamp) bestIndex = i; else break; //drop out of loop as soon as we pass the proper timestamp } } return bestIndex; } void CANFrameModel::loadFilterFile(QString filename) { QFile *inFile = new QFile(filename); QByteArray line; int ID; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) return; filters.clear(); busFilters.clear(); while (!inFile->atEnd()) { line = inFile->readLine().simplified(); if (line.length() > 2) { QList tokens = line.split(','); ID = tokens[0].toInt(nullptr, 16); if (tokens[1].toUpper() == "T") filters.insert(ID, true); else filters.insert(ID, false); } } inFile->close(); sendRefresh(); emit updatedFiltersList(); } void CANFrameModel::saveFilterFile(QString filename) { QFile *outFile = new QFile(filename); if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) return; QMap::const_iterator it; for (it = filters.begin(); it != filters.end(); ++it) { outFile->write(QString::number(it.key(), 16).toUtf8()); outFile->putChar(','); if (it.value()) outFile->putChar('T'); else outFile->putChar('F'); outFile->write("\n"); } outFile->close(); } bool CANFrameModel::needsFilterRefresh() { bool temp = needFilterRefresh; needFilterRefresh = false; return temp; } /* *This used to not be const correct but it is now. So, there's little harm in * allowing external code to peek at our frames. There's just no touching. * This ability to get a direct read-only reference speeds up a variety of * external code that needs to access frames directly and doesn't care about * this model's normal output mechanism. */ const QVector* CANFrameModel::getListReference() const { return &frames; } const QVector* CANFrameModel::getFilteredListReference() const { return &filteredFrames; } const QMap* CANFrameModel::getFiltersReference() const { return &filters; } const QMap* CANFrameModel::getBusFiltersReference() const { return &busFilters; } savvycan-220/canframemodel.h000066400000000000000000000073271500724750100161710ustar00rootroot00000000000000#ifndef CANFRAMEMODEL_H #define CANFRAMEMODEL_H #include #include #include #include #include #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 FrameId = 1, ///< The frames CAN identifier (Standard: 11 or Extended: 29 bit) Extended = 2, ///< True if the frames CAN identifier is 29 bit Remote = 3, ///< True if the frames is a remote frame Direction = 4, ///< Whether the frame was transmitted or received Bus = 5, ///< The bus where the frame was transmitted or received Length = 6, ///< The frames payload data length ASCII = 7, ///< The payload interpreted as ASCII characters Data = 8, ///< The frames payload data NUM_COLUMN }; class CANFrameModel: public QAbstractTableModel { Q_OBJECT public: CANFrameModel(QObject *parent = 0); virtual ~CANFrameModel(); int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; int columnCount(const QModelIndex &) const; int totalFrameCount(); void sendRefresh(); void sendRefresh(int); int sendBulkRefresh(); void clearFrames(); void setInterpretMode(bool); bool getInterpretMode(); void setOverwriteMode(bool); void setHexMode(bool); 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 setTimeFormat(QString); void setBytesPerLine(int bpl); void loadFilterFile(QString filename); void saveFilterFile(QString filename); void normalizeTiming(); void recalcOverwrite(); bool needsFilterRefresh(); void insertFrames(const QVector &newFrames); void sortByColumn(int column); int getIndexFromTimeID(unsigned int ID, double timestamp); const QVector *getListReference() const; //thou shalt not modify these frames externally! const QVector *getFilteredListReference() const; //Thus saith the Lord, NO. const QMap *getFiltersReference() const; //this neither const QMap *getBusFiltersReference() const; //this neither public slots: void addFrame(const CANFrame&, bool); void addFrames(const CANConnection*, const QVector&); signals: void updatedFiltersList(); private: void qSortCANFrameAsc(QVector* frames, Column column, int lowerBound, int upperBound); void qSortCANFrameDesc(QVector* frames, Column column, int lowerBound, int upperBound); uint64_t getCANFrameVal(QVector *frames, int row, Column col); bool any_filters_are_configured(void); bool any_busfilters_are_configured(void); QVector frames; QVector filteredFrames; QMap filters; QMap busFilters; DBCHandler *dbcHandler; 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 needFilterRefresh; bool ignoreDBCColors; int64_t timeOffset; int lastUpdateNumFrames; uint32_t preallocSize; bool sortDirAsc; int bytesPerLine; }; #endif // CANFRAMEMODEL_H savvycan-220/config.h000066400000000000000000000003561500724750100146340ustar00rootroot00000000000000#ifndef CONFIG #define CONFIG #define VERSION 220 //try to keep this in sync. //SavvyCAN will complain if you connect a GVRET board with a revision //less than this number. #define CURRENT_GVRET_VER 343 #endif // CONFIG savvycan-220/connections/000077500000000000000000000000001500724750100155345ustar00rootroot00000000000000savvycan-220/connections/canbus.cpp000066400000000000000000000037441500724750100175230ustar00rootroot00000000000000#include #include #include "canbus.h" CANBus::CANBus() { speed = 500000; listenOnly = false; singleWire = false; active = false; canFD = false; dataRate = 2000000; } bool CANBus::operator==(const CANBus& bus) const{ return speed == bus.speed && listenOnly == bus.listenOnly && singleWire == bus.singleWire && active == bus.active && canFD == bus.canFD && dataRate == bus.dataRate; } void CANBus::setSpeed(int newSpeed){ //qDebug() << "CANBUS SetSpeed = " << newSpeed; speed = newSpeed; } void CANBus::setListenOnly(bool mode){ //qDebug() << "CANBUS SetListenOnly = " << mode; listenOnly = mode; } void CANBus::setSingleWire(bool mode){ //qDebug() << "CANBUS SetSingleWire = " << mode; singleWire = mode; } void CANBus::setActive(bool mode){ //qDebug() << "CANBUS SetEnabled = " << mode; active = mode; } void CANBus::setCanFD(bool mode){ //qDebug() << "CANBUS setCanFD = " << mode; canFD = mode; } int CANBus::getSpeed() const { return speed; } 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() const { return singleWire; } bool CANBus::isActive() const { return active; } bool CANBus::isCanFD() const { return canFD; } 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; } QDataStream & operator>>(QDataStream & pStream, CANBus& pCanBus) { pStream >> pCanBus.speed; pStream >> pCanBus.listenOnly; pStream >> pCanBus.singleWire; pStream >> pCanBus.active; return pStream; } savvycan-220/connections/canbus.h000066400000000000000000000024211500724750100171570ustar00rootroot00000000000000#ifndef CANBus_H #define CANBus_H #include #include "can_structs.h" class 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. 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); Q_DECLARE_METATYPE(CANBus); struct BusData { CANBus mBus; bool mConfigured = {}; QVector mTargettedFrames; }; #endif // CANBus_H savvycan-220/connections/canconconst.h000066400000000000000000000010651500724750100202170ustar00rootroot00000000000000#ifndef CANCONCONST_H #define CANCONCONST_H namespace CANCon { /** * @brief The status enum */ enum status { NOT_CONNECTED, /*!< device is not connected */ CONNECTED /*!< device is connected */ }; enum type { GVRET_SERIAL, KVASER, SERIALBUS, REMOTE, KAYAK, MQTT, LAWICEL, CANSERVER, CANLOGSERVER, NONE }; } class CANConStatus { public: CANCon::status conStatus; int numHardwareBuses; }; #endif // CANCONCONST_H savvycan-220/connections/canconfactory.cpp000066400000000000000000000024361500724750100210760ustar00rootroot00000000000000#include #include "canconfactory.h" #include "serialbusconnection.h" #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, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate) { switch(pType) { case SERIALBUS: return new SerialBusConnection(pPortName, pDriverName, pBusSpeed, pDataRate, pCanFd); 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: {} } return nullptr; } savvycan-220/connections/canconfactory.h000066400000000000000000000004671500724750100205450ustar00rootroot00000000000000#ifndef CANCONFACTORY_H #define CANCONFACTORY_H #include "canconconst.h" #include "canconnection.h" class CanConFactory { public: static CANConnection* create(CANCon::type, QString pPortName, QString pDriverName, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate); }; #endif // CANCONFACTORY_H savvycan-220/connections/canconmanager.cpp000066400000000000000000000200041500724750100210300ustar00rootroot00000000000000#include #include #include #include "canconmanager.h" #include "canconfactory.h" CANConManager* CANConManager::mInstance = nullptr; CANConManager* CANConManager::getInstance() { if(!mInstance) mInstance = new CANConManager(); return mInstance; } CANConManager::CANConManager(QObject *parent): QObject(parent) { connect(&mTimer, SIGNAL(timeout()), this, SLOT(refreshCanList())); mTimer.setInterval(20); /*Tick 50 times per second to allow for good resolution in reception where needed. GUI updates *MUCH* more slowly*/ mTimer.setSingleShot(false); mTimer.start(); mNumActiveBuses = 0; resetTimeBasis(); QSettings settings; if (settings.value("Main/TimeClock", false).toBool()) { useSystemTime = true; } else useSystemTime = false; } void CANConManager::resetTimeBasis() { mTimestampBasis = QDateTime::currentMSecsSinceEpoch() * 1000; mElapsedTimer.restart(); } CANConManager::~CANConManager() { mTimer.stop(); mInstance = nullptr; } void CANConManager::stopAllConnections() { foreach (CANConnection *conn, mConns) { conn->stop(); } } void CANConManager::add(CANConnection* pConn_p) { mConns.append(pConn_p); } void CANConManager::remove(CANConnection* pConn_p) { //disconnect(pConn_p, 0, this, 0); mConns.removeOne(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 int CANConManager::getNumBuses() { int buses = 0; foreach(CANConnection* conn_p, mConns) { buses += conn_p->getNumBuses(); } return buses; } int CANConManager::getBusBase(CANConnection *which) { int buses = 0; foreach(CANConnection* conn_p, mConns) { if (conn_p != which) buses += conn_p->getNumBuses(); else return buses; } return -1; } void CANConManager::refreshCanList() { QObject* sender_p = QObject::sender(); if (mConns.count() == 0) { tempFrames.clear(); //TODO: Seems to crash under heavy load. Find out why. if(buslessFrames.size()) { tempFrames = buslessFrames; //make a copy and pass that copy buslessFrames.clear(); //delete all frames from the original emit framesReceived(nullptr, tempFrames); } return; } if( sender_p != &mTimer) { /* if we are not the sender, the signal is coming from a connection */ /* refresh only the given connection */ if(mConns.contains((CANConnection*) sender_p)) refreshConnection((CANConnection*)sender_p); } else { foreach (CANConnection* conn_p, mConns) refreshConnection((CANConnection*)conn_p); } } uint64_t CANConManager::getTimeBasis() { return mTimestampBasis; } QList& CANConManager::getConnections() { return mConns; } CANConnection* CANConManager::getByName(const QString& pName) const { foreach(CANConnection* conn_p, mConns) { if(conn_p->getPort() == pName) return conn_p; } return nullptr; } void CANConManager::refreshConnection(CANConnection* pConn_p) { unsigned int buses = 0; foreach(CANConnection* conn_p, mConns) { if (conn_p->getStatus() == CANCon::CONNECTED) buses += conn_p->getNumBuses(); } if (buses != mNumActiveBuses) { mNumActiveBuses = buses; emit connectionStatusUpdated(buses); } if (pConn_p->getQueue().peek() == nullptr) return; CANFrame* frame_p = nullptr; QVector frames; //Each connection only knows about its own bus numbers //so this variable is used to fix that up to turn local bus numbers //into system global bus numbers for display. int busBase = 0; foreach (CANConnection* conn, mConns) { if (conn != pConn_p) busBase += conn->getNumBuses(); else break; } //qDebug() << "Bus fixup number: " << busBase; while( (frame_p = pConn_p->getQueue().peek() ) ) { frame_p->bus += busBase; //qDebug() << "Rx of frame from bus: " << frame_p->bus; frames.append(*frame_p); pConn_p->getQueue().dequeue(); } if(frames.size()) emit framesReceived(pConn_p, frames); } /* * Uses the requested bus to look up which CANConnection object handles this bus based on the order of * the objects and how many buses they implement. For instance, if the request is to send on bus 2 * and there is a GVRET object first then a socketcan object it'll send on the socketcan object as * gvret will have claimed buses 0 and 1 and socketcan bus 2. But, each actual CANConnection expects * its own bus numbers to start at zero so the frame bus number has to be offset accordingly. * Also keep in mind that the CANConnection "sendFrame" function uses a blocking queued connection * and so will force the frame to be delivered before it keeps going. This allows on the stack variables * to be used but is slow. This function uses an on the stack copy of the frame so the way it works * is a good thing but performance will suffer. TODO: Investigate a way to use non-blocking calls. */ bool CANConManager::sendFrame(const CANFrame& pFrame) { int busBase = 0; CANFrame workingFrame = pFrame; if (mConns.count() == 0) { buslessFrames.append(pFrame); return true; } foreach (CANConnection* conn, mConns) { //check if this CAN connection is supposed to handle the requested bus if (pFrame.bus < (busBase + conn->getNumBuses())) { workingFrame.bus -= busBase; workingFrame.isReceived = false; if (useSystemTime) { workingFrame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(QDateTime::currentMSecsSinceEpoch() * 1000ul)); } else { workingFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, mElapsedTimer.nsecsElapsed() / 1000)); //workingFrame.timestamp -= mTimestampBasis; } return conn->sendFrame(workingFrame); } busBase += conn->getNumBuses(); } return false; } bool CANConManager::sendFrames(const QList& pFrames) { foreach(const CANFrame& frame, pFrames) { if(!sendFrame(frame)) return false; } return true; } //For each device associated with buses go through and see if that device has a bus //that the filter should apply to. If so forward the data on but fudge //the bus numbers if bus wasn't -1 so that they're local to the device bool CANConManager::addTargettedFrame(int pBusId, uint32_t ID, uint32_t mask, QObject *receiver) { //int tempBusVal; int busBase = 0; foreach (CANConnection* conn, mConns) { if (pBusId == -1) conn->addTargettedFrame(pBusId, ID, mask, receiver); else if (pBusId < (busBase + conn->getNumBuses())) { qDebug() << "Forwarding targetted frame setting to a connection object"; conn->addTargettedFrame(pBusId - busBase, ID, mask, receiver); } busBase += conn->getNumBuses(); } return true; } bool CANConManager::removeTargettedFrame(int pBusId, uint32_t ID, uint32_t mask, QObject *receiver) { //int tempBusVal; int busBase = 0; foreach (CANConnection* conn, mConns) { if (pBusId == -1) conn->removeTargettedFrame(pBusId, ID, mask, receiver); else if (pBusId < (busBase + conn->getNumBuses())) { qDebug() << "Forwarding targetted frame setting to a connection object"; conn->removeTargettedFrame(pBusId - busBase, ID, mask, receiver); } busBase += conn->getNumBuses(); } return true; } bool CANConManager::removeAllTargettedFrames(QObject *receiver) { foreach (CANConnection* conn, mConns) { conn->removeAllTargettedFrames(receiver); } return true; } savvycan-220/connections/canconmanager.h000066400000000000000000000062201500724750100205010ustar00rootroot00000000000000#ifndef CANCONMANAGER_H #define CANCONMANAGER_H #include #include #include #include "canconnection.h" class CANConManager : public QObject { Q_OBJECT public: static CANConManager* getInstance(); virtual ~CANConManager(); void add(CANConnection* pConn_p); void remove(CANConnection* pConn_p); void replace(int idx, CANConnection* pConn_p); QList& getConnections(); void stopAllConnections(); CANConnection* getByName(const QString& pName) const; uint64_t getTimeBasis(); void resetTimeBasis(); int getNumBuses(); int getBusBase(CANConnection *); /** * @brief sendFrame sends a single frame out the desired bus * @param pFrame - reference to a CANFrame struct that has been filled out for sending * @return bool specifying whether the send succeeded or not * @note Finds which CANConnection object is responsible for this bus and automatically converts bus number to pass properly to CANConnection */ bool sendFrame(const CANFrame& pFrame); //just the multi-frame version of above function. bool sendFrames(const QList& pFrames); /** * @brief Add a new filter for the targetted frames. If a frame matches it will immediately be sent via the targettedFrameReceived signal * @param pBusId - Which bus to bond to. -1 for any, otherwise a bitfield of buses (but 0 = first bus, etc) * @param ID - 11 or 29 bit ID to match against * @param mask - 11 or 29 bit mask used for filter * @param receiver - Pointer to a QObject that wants to receive notification when filter is matched * @return true if filter was able to be added, false otherwise. */ bool addTargettedFrame(int pBusId, uint32_t ID, uint32_t mask, QObject *receiver); /** * @brief Try to find a matching filter in the list and remove it, no longer targetting those frames * @param pBusId - Which bus to bond to. Doesn't have to match the call to addTargettedFrame exactly. You could disconnect just one bus for instance. * @param ID - 11 or 29 bit ID to match against * @param mask - 11 or 29 bit mask used for filter * @param receiver - Pointer to a QObject that wants to receive notification when filter is matched * @return true if filter was found and deleted, false otherwise. */ bool removeTargettedFrame(int pBusId, uint32_t ID, uint32_t mask, QObject *receiver); bool removeAllTargettedFrames(QObject *receiver); signals: void framesReceived(CANConnection* pConn_p, QVector& pFrames); void connectionStatusUpdated(int conns); private slots: void refreshCanList(); private: explicit CANConManager(QObject *parent = 0); void refreshConnection(CANConnection* pConn_p); static CANConManager* mInstance; QList mConns; QTimer mTimer; QElapsedTimer mElapsedTimer; uint64_t mTimestampBasis; uint32_t mNumActiveBuses; bool useSystemTime; QVector buslessFrames; QVector tempFrames; }; #endif // CANCONNECTIONMODEL_H savvycan-220/connections/canconnection.cpp000066400000000000000000000253021500724750100210630ustar00rootroot00000000000000#include #include #include "canconnection.h" 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), mType(pType), mIsCapSuspended(false), mStatus(CANCon::NOT_CONNECTED), mStarted(false), mThread_p(nullptr) { /* register types */ qRegisterMetaType("CANBus"); qRegisterMetaType("CANFrame"); qRegisterMetaType("CANConStatus"); qRegisterMetaType("CANFlt"); /* set queue size */ mQueue.setSize(pQueueLen); /*TODO add check on returned value */ /* allocate buses */ /* TODO: change those tables for a vector */ mBusData.resize(mNumBuses); for(int i=0 ; i 0) mBusData[0].mBus.setSpeed(pBusSpeed); mBusData[0].mBus.setCanFD(pCanFd); if (pDataRate > 0) { mBusData[0].mBus.setDataRate(pDataRate); if (pCanFd) { mBusData[0].mBus.setCanFD(pCanFd); } } /* if needed, create a thread and move ourself into it */ if(pUseThread) { mThread_p = new QThread(); } } CANConnection::~CANConnection() { /* stop and delete thread */ if(mThread_p) { mThread_p->quit(); mThread_p->wait(); delete mThread_p; mThread_p = nullptr; } mBusData.clear(); } void CANConnection::start() { 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(start())); /* start the thread */ mThread_p->start(QThread::HighPriority); return; } /* set started flag */ mStarted = true; QSettings settings; if (settings.value("Main/TimeClock", false).toBool()) { useSystemTime = true; } else useSystemTime = false; /* in multithread case, this will be called before entering thread event loop */ return piStarted(); } void CANConnection::suspend(bool pSuspend) { /* execute in mThread_p context */ if( mThread_p && (mThread_p != QThread::currentThread()) ) { QMetaObject::invokeMethod(this, "suspend", Qt::BlockingQueuedConnection, Q_ARG(bool, pSuspend)); return; } return piSuspend(pSuspend); } void CANConnection::stop() { /* 1) execute in mThread_p context */ if( mThread_p && mStarted && (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, "stop", 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(); } bool CANConnection::getBusSettings(int pBusIdx, CANBus& pBus) { /* make sure we execute in mThread context */ if( mThread_p && (mThread_p != QThread::currentThread()) ) { bool ret; QMetaObject::invokeMethod(this, "getBusSettings", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret), Q_ARG(int , pBusIdx), Q_ARG(CANBus& , pBus)); return ret; } return piGetBusSettings(pBusIdx, pBus); } void CANConnection::setBusSettings(int pBusIdx, CANBus pBus) { /* make sure we execute in mThread context */ if( mThread_p && (mThread_p != QThread::currentThread()) ) { QMetaObject::invokeMethod(this, "setBusSettings", Qt::BlockingQueuedConnection, Q_ARG(int, pBusIdx), Q_ARG(CANBus, pBus)); return; } return piSetBusSettings(pBusIdx, pBus); } bool CANConnection::sendFrame(const CANFrame& pFrame) { /* make sure we execute in mThread context */ if( mThread_p && (mThread_p != QThread::currentThread()) ) { bool ret; QMetaObject::invokeMethod(this, "sendFrame", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret), Q_ARG(const CANFrame&, pFrame)); return ret; } CANFrame *txFrame; txFrame = getQueue().get(); if (txFrame) { *txFrame = pFrame; } getQueue().queue(); return piSendFrame(pFrame); } bool CANConnection::sendFrames(const QList& pFrames) { /* make sure we execute in mThread context */ if( mThread_p && (mThread_p != QThread::currentThread()) ) { bool ret; QMetaObject::invokeMethod(this, "sendFrames", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret), Q_ARG(const QList&, pFrames)); return ret; } return piSendFrames(pFrames); } int CANConnection::getNumBuses() const{ return mNumBuses; } int CANConnection::getSerialSpeed() const{ return mSerialSpeed; } bool CANConnection::isConfigured(int pBusId) { if( pBusId < 0 || pBusId >= getNumBuses()) return false; return mBusData[pBusId].mConfigured; } void CANConnection::setConfigured(int pBusId, bool pConfigured) { if( pBusId < 0 || pBusId >= getNumBuses()) return; mBusData[pBusId].mConfigured = pConfigured; } bool CANConnection::getBusConfig(int pBusId, CANBus& pBus) { if( pBusId < 0 || pBusId >= getNumBuses() || !isConfigured(pBusId)) return false; qDebug() << "getBusConfig id: " << pBusId; pBus = mBusData[pBusId].mBus; return true; } void CANConnection::setBusConfig(int pBusId, CANBus& pBus) { if( pBusId < 0 || pBusId >= getNumBuses()) return; mBusData[pBusId].mConfigured = true; mBusData[pBusId].mBus = pBus; } QString CANConnection::getPort() { return mPort; } QString CANConnection::getDriver() { return mDriver; } LFQueue& CANConnection::getQueue() { return mQueue; } CANCon::type CANConnection::getType() { return mType; } CANCon::status CANConnection::getStatus() { return (CANCon::status) mStatus.loadRelaxed(); } void CANConnection::setStatus(CANCon::status pStatus) { mStatus.storeRelaxed(pStatus); } bool CANConnection::isCapSuspended() { return mIsCapSuspended; } void CANConnection::setCapSuspended(bool pIsSuspended) { mIsCapSuspended = pIsSuspended; } void CANConnection::debugInput(QByteArray bytes) { Q_UNUSED(bytes) } bool CANConnection::addTargettedFrame(int pBusId, uint32_t ID, uint32_t mask, QObject *receiver) { /* if( mThread_p && (mThread_p != QThread::currentThread()) ) { bool ret; QMetaObject::invokeMethod(this, "addTargettedFrame", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret), Q_ARG(int, pBusId), Q_ARG(uint32_t , ID), Q_ARG(uint32_t , mask), Q_ARG(QObject *, receiver)); return ret; } */ /* sanity checks */ if(pBusId < -1 || pBusId >= getNumBuses()) return false; qDebug() << "Connection is registering a new targetted frame filter, local bus " << pBusId; CANFltObserver target; target.id = ID; target.mask = mask; target.observer = receiver; if (pBusId > -1) mBusData[pBusId].mTargettedFrames.append(target); else { for (int i = 0; i < mBusData.count(); i++) mBusData[i].mTargettedFrames.append(target); } return true; } bool CANConnection::removeTargettedFrame(int pBusId, uint32_t ID, uint32_t mask, QObject *receiver) { /* if( mThread_p && (mThread_p != QThread::currentThread()) ) { bool ret; QMetaObject::invokeMethod(this, "removeTargettedFrame", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret), Q_ARG(int, pBusId), Q_ARG(uint32_t , ID), Q_ARG(uint32_t , mask), Q_ARG(QObject *, receiver)); return ret; } */ /* sanity checks */ if(pBusId < -1 || pBusId >= getNumBuses()) return false; CANFltObserver target; target.id = ID; target.mask = mask; target.observer = receiver; mBusData[pBusId].mTargettedFrames.removeAll(target); return true; } bool CANConnection::removeAllTargettedFrames(QObject *receiver) { for (int i = 0; i < getNumBuses(); i++) { foreach (const CANFltObserver filt, mBusData[i].mTargettedFrames) { if (filt.observer == receiver) mBusData[i].mTargettedFrames.removeOne(filt); } } return true; } void CANConnection::checkTargettedFrame(CANFrame &frame) { unsigned int maskedID; //qDebug() << "Got frame with ID " << frame.ID << " on bus " << frame.bus; if (mBusData.count() == 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; maskedID = frame.frameId() & filt.mask; if (maskedID == filt.id) { qDebug() << "In connection object I got a targetted frame. Forwarding it."; QMetaObject::invokeMethod(filt.observer, "gotTargettedFrame",Qt::QueuedConnection, Q_ARG(CANFrame, frame)); } } } bool CANConnection::piSendFrames(const QList& pFrames) { foreach(const CANFrame& frame, pFrames) { if(!piSendFrame(frame)) return false; } return true; } savvycan-220/connections/canconnection.h000066400000000000000000000255721500724750100205410ustar00rootroot00000000000000#ifndef CANCONNECTION_H #define CANCONNECTION_H #include #include #include "utils/lfqueue.h" #include "can_structs.h" #include "canbus.h" #include "canconconst.h" struct BusData; class CANConnection : public QObject { Q_OBJECT protected: /** * @brief CANConnection constructor * @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, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate, int pNumBuses, int pQueueLen, bool pUseThread); public: /** * @brief CANConnection destructor */ virtual ~CANConnection(); /** * @brief getNumBuses * @return returns the number of buses of the device */ int getNumBuses() const; /** * @brief getserialSpeed * @return returns the serial speed of the device */ int getSerialSpeed() const; /** * @brief getPort * @return returns the port name of the device */ QString getPort(); /** * @brief getDriver * @return returns the name of the driver used for this device */ QString getDriver(); /** * @brief getQueue * @return the lock free queue of the device */ LFQueue& getQueue(); /** * @brief getType * @return the @ref CANCon::type of the device */ CANCon::type getType(); /** * @brief getStatus * @return the @ref CANCon::status of the device (either connected or not) */ CANCon::status getStatus(); /** * @brief setConsoleOutput * @param state - set whether to send debugging info to the console or not */ void setConsoleOutput(bool state); signals: /*not implemented yet */ void error(const QString &); void deviceInfo(int, int); //First param = driver version (or version of whatever you want), second param a status byte //bus number, bus speed, status (bit 0 = enabled, 1 = single wire, 2 = listen only) //3 = Use value stored for enabled, 4 = use value passed for single wire, 5 = use value passed for listen only //6 = use value passed for speed. This allows bus status to be updated but set that some things aren't really //being passed. Just set for things that really are being updated. void busStatus(int, int, int); /** * @brief event sent when a frame matching a filter is received */ void targettedFrameReceived(CANFrame frame); /** * @brief event emitted when the CANCon::status of the connection changes (connected->not_connected or the other way round) * @param pStatus: the new status of the device */ void status(CANConStatus pStatus); /** * @brief Event sent when device has done something worthy of debugging output. * @param debugString: String based output to show for debugging purposes */ void debugOutput(QString debugString); public slots: /** * @brief start the device. This calls piStarted * @note starts the working thread if required (piStarted is called in the working thread context) */ void start(); /** * @brief stop the device. This calls piStop * @note if a working thread is used, piStop is called before exiting the working thread */ void stop(); /** * @brief setBusSettings * @param pBusIdx: the index of the bus for which settings have to be set * @param pBus: the settings to set * @note this calls piSetBusSettings in the working thread context (if one has been started) */ void setBusSettings(int pBusIdx, CANBus pBus); /** * @brief getBusSettings * @param pBusIdx: the index of the bus for which settings have to be retrieved * @param pBus: the CANBus struct to fill with information * @return true if operation succeeds, false if pBusIdx is invalid or bus has not been configured yet * @note this calls piGetBusSettings in the working thread context (if one has been started) */ bool getBusSettings(int pBusIdx, CANBus& pBus); /** * @brief suspends/restarts data capture * @param pSuspend: suspends capture if true else restarts it * @note this calls piSuspend (in the working thread context if one has been started) * @note the caller shall not access the queue when capture is suspended, * @note the callee is expected to flush the queue */ void suspend(bool pSuspend); /** * @brief provides device with the frame to send * @param pFrame: the frame to send * @return false if parameter is invalid (bus id for instance) * @note this calls piSendFrame (in the working thread context if one has been started) */ bool sendFrame(const CANFrame& pFrame); /** * @brief provides device with a list of frames to send * @param pFrame: the list of frames to send * @return false if parameter is invalid (bus id for instance) * @note this calls piSendFrameBatch (in the working thread context if one has been started) */ bool sendFrames(const QList& pFrames); /** * @brief Add a new filter for the targetted frames. If a frame matches it will immediately be sent via the targettedFrameReceived signal * @param pBusId - Which bus to bond to. -1 for any, otherwise a bitfield of buses (but 0 = first bus, etc) * @param ID - 11 or 29 bit ID to match against * @param mask - 11 or 29 bit mask used for filter * @param receiver - Pointer to a QObject that wants to receive notification when filter is matched * @return true if filter was able to be added, false otherwise. */ bool addTargettedFrame(int pBusId, uint32_t ID, uint32_t mask, QObject *receiver); /** * @brief Try to find a matching filter in the list and remove it, no longer targetting those frames * @param pBusId - Which bus to bond to. Doesn't have to match the call to addTargettedFrame exactly. You could disconnect just one bus for instance. * @param ID - 11 or 29 bit ID to match against * @param mask - 11 or 29 bit mask used for filter * @param receiver - Pointer to a QObject that wants to receive notification when filter is matched * @return true if filter was found and deleted, false otherwise. */ bool removeTargettedFrame(int pBusId, uint32_t ID, uint32_t mask, QObject *receiver); /** * @brief Removes all registered filters for the passed receiver * @param receiver - Pointer to a QObject that registered one or more filters * @return true if filter(s) was/were found and deleted, false otherwise. */ bool removeAllTargettedFrames(QObject *receiver); void debugInput(QByteArray bytes); 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); /** * @brief setStatus * @param pStatus: the status to set */ void setStatus(CANCon::status pStatus); /** * @brief isConfigured * @param pBusId * @return true if bus is configured */ bool isConfigured(int pBusId); /** * @brief setConfigured * @param pBusId * @param pConfigured * @note it is not necessary to call this function to set pBusId configured, it is enough to call @ref setBusConfig */ void setConfigured(int pBusId, bool pConfigured); /** * @brief getBusConfig * @param pBusId * @param pBus * @return true if operation succeeds, false if pBusIdx is invalid or bus has not been configured yet */ bool getBusConfig(int pBusId, CANBus& pBus); /** * @brief setBusConfig * @param pBusId: the index of the bus for which settings have to be set * @param pBus: the settings to set */ void setBusConfig(int pBusId, CANBus& pBus); /** * @brief isCapSuspended * @return true if the capture is suspended */ bool isCapSuspended(); /** * @brief setCapSuspended * @param pIsSuspended */ void setCapSuspended(bool pIsSuspended); protected: bool useSystemTime; /**************************************************************/ /*********** protected interface to implement *******/ /**************************************************************/ /** * @brief starts the device */ virtual void piStarted() = 0; /** * @brief stops the device */ virtual void piStop() = 0; /** * @brief piSetBusSettings * @param pBusIdx: the index of the bus for which settings have to be set * @param pBus: the settings to set */ virtual void piSetBusSettings(int pBusIdx, CANBus pBus) = 0; /** * @brief piGetBusSettings * @param pBusIdx: the index of the bus for which settings have to be retrieved * @param pBus: the CANBus struct to fill with information * @return true if operation succeeds, false if pBusIdx is invalid or bus has not been configured yet */ virtual bool piGetBusSettings(int pBusIdx, CANBus& pBus) = 0; /** * @brief suspends/restarts data capture * @param pSuspend: suspends capture if true else restarts it * @note the caller will not access the queue when capture is suspended, so it is safe for callee to flush the queue */ virtual void piSuspend(bool pSuspend) = 0; /** * @brief provides device with the frame to send * @param pFrame: the frame to send * @return false if parameter is invalid (bus id for instance) */ virtual bool piSendFrame(const CANFrame&) = 0; /** * @brief provides device with a list of frames to send * @param pFrame: the list of frames to send * @return false if parameter is invalid (bus id for instance) * @note implementing this function is optional */ virtual bool piSendFrames(const QList&); private: LFQueue mQueue; const QString mPort; const QString mDriver; const CANCon::type mType; bool mIsCapSuspended; QAtomicInt mStatus; bool mStarted; QThread* mThread_p; }; #endif // CANCONNECTION_H savvycan-220/connections/canconnectionmodel.cpp000066400000000000000000000112051500724750100221010ustar00rootroot00000000000000#include "canconnectionmodel.h" #include "connections/canconnection.h" #include "connections/canconmanager.h" CANConnectionModel::CANConnectionModel(QObject *parent) : QAbstractTableModel(parent) { } CANConnectionModel::~CANConnectionModel() { } enum class Column { Type = 0, ///< The CAN driver/backend type, e.g. GVRET, peakcan, or socketcan Subtype = 1, ///< Mostly used by SerialBus devices to pick the sub type Port = 2, ///< The CAN hardware port, e.g. can0 for socketcan NumBuses = 3, ///< Number of buses exposed by this device. Usually non-GVRET devices will just have one Status = 4 ///< The bus status as text message }; QVariant CANConnectionModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) { switch (Column(section)) { case Column::Type: return QString(tr("Type")); case Column::Subtype: return QString(tr("Subtype")); case Column::Port: return QString(tr("Port")); case Column::NumBuses: return QString(tr("Buses")); case Column::Status: return QString(tr("Status")); } } else return QString::number(section + 1); return QVariant(); } int CANConnectionModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 5; } int CANConnectionModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); QList& conns = CANConManager::getInstance()->getConnections(); return conns.count(); } QVariant CANConnectionModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); //qDebug() << "Row: " << index.row(); CANConnection *conn_p = getAtIdx(index.row()); if (!conn_p) return QVariant(); //bool isSocketCAN = (conn_p->getType() == CANCon::SERIALBUS) ? true: false; if (role == Qt::DisplayRole) { switch (Column(index.column())) { case Column::Type: if (conn_p) switch (conn_p->getType()) { case CANCon::KVASER: return "KVASER"; 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"; break; case Column::Port: if (conn_p) return conn_p->getPort(); else qDebug() << "Tried to show connection port but connection was nullptr"; break; case Column::Subtype: return conn_p->getDriver(); break; case Column::NumBuses: return conn_p->getNumBuses(); break; case Column::Status: return (conn_p->getStatus()==CANCon::CONNECTED) ? "Connected" : "Not Connected"; } } return QVariant(); } void CANConnectionModel::add(CANConnection* pConn_p) { CANConManager* manager = CANConManager::getInstance(); beginResetModel(); manager->add(pConn_p); endResetModel(); } void CANConnectionModel::remove(CANConnection* pConn_p) { CANConManager* manager = CANConManager::getInstance(); beginResetModel(); manager->remove(pConn_p); endResetModel(); } void CANConnectionModel::replace(int idx , CANConnection* pConn_p) { CANConManager* manager = CANConManager::getInstance(); beginResetModel(); manager->replace(idx, pConn_p); endResetModel(); } CANConnection* CANConnectionModel::getAtIdx(int pIdx) const { if (pIdx < 0) return nullptr; QList& conns = CANConManager::getInstance()->getConnections(); return conns.at(pIdx); } void CANConnectionModel::refresh(int pIndex) { Q_UNUSED(pIndex) beginResetModel(); endResetModel(); /* QModelIndex begin; QModelIndex end; if(pIndex>=0) { begin = createIndex(pIndex, 0); end = createIndex(pIndex, columnCount()-1); } else { begin = createIndex(0, 0); end = createIndex(rowCount()-1, columnCount()-1); } dataChanged(begin, end, QVector(Qt::DisplayRole)); */ } savvycan-220/connections/canconnectionmodel.h000066400000000000000000000016351500724750100215540ustar00rootroot00000000000000#ifndef CANCONNECTIONMODEL_H #define CANCONNECTIONMODEL_H #include #include "canbus.h" class CANConnection; class CANConnectionModel : public QAbstractTableModel { Q_OBJECT public: explicit CANConnectionModel(QObject *parent = 0); virtual ~CANConnectionModel(); // from abstractmodel: QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; void add(CANConnection* pConn_p); void remove(CANConnection* pConn_p); void replace(int idx , CANConnection* pConn_p); CANConnection* getAtIdx(int) const; void refresh(int pIndex=-1); }; #endif // CANCONNECTIONMODEL_H savvycan-220/connections/canlogserver.cpp000066400000000000000000000131171500724750100207350ustar00rootroot00000000000000#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(); } savvycan-220/connections/canlogserver.h000066400000000000000000000021621500724750100204000ustar00rootroot00000000000000#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 savvycan-220/connections/canserver.cpp000066400000000000000000000146541500724750100202420ustar00rootroot00000000000000// // 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::fromMicroSeconds(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; } savvycan-220/connections/canserver.h000066400000000000000000000021461500724750100177000ustar00rootroot00000000000000// // 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 */ savvycan-220/connections/connectionwindow.cpp000066400000000000000000000532241500724750100216350ustar00rootroot00000000000000#include #include #include #include "connectionwindow.h" #include "mainwindow.h" #include "helpwindow.h" #include "ui_connectionwindow.h" #include "connections/canconfactory.h" #include "connections/canconmanager.h" #include "canbus.h" #include #include ConnectionWindow::ConnectionWindow(QWidget *parent) : QDialog(parent), ui(new Ui::ConnectionWindow) { ui->setupUi(this); setWindowFlags(Qt::Window); QSettings settings; qRegisterMetaType("CANBus"); qRegisterMetaType("const CANFrame *"); qRegisterMetaType *>("const QList *"); //List of devices with details. None of it can be edited. connection type, serialbus type, port name, number of buses, status connModel = new CANConnectionModel(this); ui->tableConnections->setModel(connModel); ui->tableConnections->setColumnWidth(0, 100); ui->tableConnections->setColumnWidth(1, 100); ui->tableConnections->setColumnWidth(2, 130); ui->tableConnections->setColumnWidth(3, 70); ui->tableConnections->setColumnWidth(4, 200); QHeaderView *HorzHdr = ui->tableConnections->horizontalHeader(); HorzHdr->setStretchLastSection(true); //causes the data column to automatically fill the tableview ui->textConsole->setEnabled(false); ui->btnClearDebug->setEnabled(false); ui->btnSendHex->setEnabled(false); ui->btnSendText->setEnabled(false); ui->lineSend->setEnabled(false); if (settings.value("Main/SaveRestoreConnections", false).toBool()) { /* load connection configuration */ loadConnections(); } connect(ui->btnDisconnect, &QPushButton::clicked, this, &ConnectionWindow::handleRemoveConn); connect(ui->btnSendHex, &QPushButton::clicked, this, &ConnectionWindow::handleSendHex); connect(ui->btnSendText, &QPushButton::clicked, this, &ConnectionWindow::handleSendText); connect(ui->ckEnableConsole, &QCheckBox::toggled, this, &ConnectionWindow::consoleEnableChanged); connect(ui->btnClearDebug, &QPushButton::clicked, this, &ConnectionWindow::handleClearDebugText); connect(ui->btnNewConnection, &QPushButton::clicked, this, &ConnectionWindow::handleNewConn); connect(ui->btnResetConn, &QPushButton::clicked, this, &ConnectionWindow::handleResetConn); connect(ui->tableConnections->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &ConnectionWindow::currentRowChanged); connect(ui->tabBuses, &QTabBar::currentChanged, this, &ConnectionWindow::currentTabChanged); connect(ui->btnSaveBus, &QPushButton::clicked, this, &ConnectionWindow::saveBusSettings); connect(ui->btnMoveUp, &QPushButton::clicked, this, &ConnectionWindow::moveConnUp); connect(ui->btnMoveDown, &QPushButton::clicked, this, &ConnectionWindow::moveConnDown); ui->cbBusSpeed->addItem("33333"); ui->cbBusSpeed->addItem("50000"); ui->cbBusSpeed->addItem("83333"); ui->cbBusSpeed->addItem("100000"); ui->cbBusSpeed->addItem("125000"); ui->cbBusSpeed->addItem("250000"); ui->cbBusSpeed->addItem("500000"); ui->cbBusSpeed->addItem("1000000"); //ui->cbBusSpeed->addItem("75000"); //ui->cbBusSpeed->addItem("166666"); //ui->cbBusSpeed->addItem("233333"); //ui->cbBusSpeed->addItem("400000"); rxBroadcastGVRET = new QUdpSocket(this); //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, &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, &QUdpSocket::readyRead, this, &ConnectionWindow::readPendingDatagrams); } void ConnectionWindow::readPendingDatagrams() { //qDebug() << "Got a UDP frame!"; while (rxBroadcastGVRET->hasPendingDatagrams()) { QNetworkDatagram datagram = rxBroadcastGVRET->receiveDatagram(); if (!remoteDeviceIPGVRET.contains(datagram.senderAddress().toString())) { remoteDeviceIPGVRET.append(datagram.senderAddress().toString()); //qDebug() << "Add new remote IP " << datagram.senderAddress().toString(); } } while (rxBroadcastKayak->hasPendingDatagrams()) { QNetworkDatagram datagram = rxBroadcastKayak->receiveDatagram(); //qDebug() << "Broadcast Datagram: " << QString::fromUtf8(datagram.data()); QXmlStreamReader CANBeaconXml(QString::fromUtf8(datagram.data())); QString KayakHost; QString KayakBus; while(!CANBeaconXml.atEnd() && !CANBeaconXml.hasError()) { CANBeaconXml.readNext(); if(CANBeaconXml.name() == QString("CANBeacon") && !CANBeaconXml.isEndElement()) KayakHost.append(CANBeaconXml.attributes().value("name")); 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() == QString("Bus") && !CANBeaconXml.isEndElement()) KayakBus.append(CANBeaconXml.attributes().value("name").toUtf8() + ","); } KayakHost = KayakBus.left(KayakBus.length() - 1) + "@" + KayakHost; QVector connectedPorts; if (connModel->rowCount() > 0) { for (int i = 0; i < connModel->rowCount(); i++) { CANConnection *var_conn = connModel->getAtIdx(i); connectedPorts.append(var_conn->getPort()); } } if (connectedPorts.contains(KayakHost)) { remoteDeviceKayak.removeOne(KayakHost); } if (!remoteDeviceKayak.contains(KayakHost) && !connectedPorts.contains(KayakHost)) { remoteDeviceKayak.append(KayakHost); //qDebug() << "Add new remote IP " << datagram.senderAddress().toString(); } } } ConnectionWindow::~ConnectionWindow() { QList& conns = CANConManager::getInstance()->getConnections(); CANConnection* conn_p; /* save configuration */ saveConnections(); /* delete connections */ while(!conns.isEmpty()) { conn_p = conns.takeFirst(); conn_p->stop(); delete conn_p; } delete ui; } void ConnectionWindow::showEvent(QShowEvent* event) { QDialog::showEvent(event); qDebug() << "Show connectionwindow"; installEventFilter(this); readSettings(); ui->tableConnections->selectRow(0); currentRowChanged(ui->tableConnections->currentIndex(), ui->tableConnections->currentIndex()); } void ConnectionWindow::closeEvent(QCloseEvent *event) { Q_UNUSED(event); removeEventFilter(this); writeSettings(); } bool ConnectionWindow::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("connectionwindow.md"); break; } return true; } else { // standard event processing return QObject::eventFilter(obj, event); } //return false; } void ConnectionWindow::readSettings() { QSettings settings; if (settings.value("Main/SaveRestorePositions", false).toBool()) { resize(settings.value("ConnWindow/WindowSize", QSize(956, 665)).toSize()); move(Utility::constrainedWindowPos(settings.value("ConnWindow/WindowPos", QPoint(100, 100)).toPoint())); } } void ConnectionWindow::writeSettings() { QSettings settings; if (settings.value("Main/SaveRestorePositions", false).toBool()) { settings.setValue("ConnWindow/WindowSize", size()); settings.setValue("ConnWindow/WindowPos", pos()); } } void ConnectionWindow::consoleEnableChanged(bool checked) { ui->textConsole->setEnabled(checked); ui->btnClearDebug->setEnabled(checked); ui->btnSendHex->setEnabled(checked); ui->btnSendText->setEnabled(checked); ui->lineSend->setEnabled(checked); int selIdx = ui->tableConnections->currentIndex().row(); if (selIdx == -1) return; CANConnection* conn_p = connModel->getAtIdx(selIdx); if (checked) { //enable console 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, &CANConnection::debugOutput, nullptr, nullptr); disconnect(this, &ConnectionWindow::sendDebugData, conn_p, &CANConnection::debugInput); } } void ConnectionWindow::handleNewConn() { NewConnectionDialog *thisDialog = new NewConnectionDialog(&remoteDeviceIPGVRET, &remoteDeviceKayak); CANCon::type newType; QString newPort; QString newDriver; int newSerialSpeed; int newBusSpeed; bool newCanFd; int newDataRate; CANConnection *conn; if (thisDialog->exec() == QDialog::Accepted) { newType = thisDialog->getConnectionType(); newPort = thisDialog->getPortName(); newDriver = thisDialog->getDriverName(); 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); ui->tableConnections->setCurrentIndex(connModel->index(connModel->rowCount() - 1, 1)); } } delete thisDialog; } void ConnectionWindow::handleRemoveConn() { int selIdx = ui->tableConnections->selectionModel()->currentIndex().row(); if (selIdx <0) return; qDebug() << "remove connection at index: " << selIdx; CANConnection* conn_p = connModel->getAtIdx(selIdx); if(!conn_p) return; /* remove connection from model & manager */ connModel->remove(conn_p); /* stop and delete connection */ conn_p->stop(); delete conn_p; /* select first connection in list */ ui->tableConnections->selectRow(0); } 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; qDebug() << "remove connection at index: " << selIdx; CANConnection* conn_p = connModel->getAtIdx(selIdx); if(!conn_p) return; 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, serSpeed, busSpeed,canFd,dataRate); if (conn_p) connModel->replace(selIdx, conn_p); } /* status */ void ConnectionWindow::connectionStatus(CANConStatus pStatus) { Q_UNUSED(pStatus); qDebug() << "Connectionstatus changed"; int selIdx = ui->tableConnections->selectionModel()->currentIndex().row(); connModel->refresh(); ui->tableConnections->selectRow(selIdx); } void ConnectionWindow::setSuspendAll(bool pSuspend) { QList& conns = CANConManager::getInstance()->getConnections(); foreach(CANConnection* conn_p, conns) conn_p->suspend(pSuspend); connModel->refresh(); } void ConnectionWindow::saveBusSettings() { int selIdx = ui->tableConnections->currentIndex().row(); int offset = ui->tabBuses->currentIndex(); /* set parameters */ if (selIdx == -1) { return; } else { CANConnection* conn_p = connModel->getAtIdx(selIdx); CANBus bus; if(!conn_p) return; if (!conn_p->getBusSettings(offset, bus)) { qDebug() << "Could not retrieve bus settings!"; return; } 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); } } void ConnectionWindow::populateBusDetails(int offset) { int selIdx = ui->tableConnections->currentIndex().row(); /* set parameters */ if (selIdx == -1) { return; } else { //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; if (!conn_p->getBusSettings(offset, bus)) { qDebug() << "Could not retrieve bus settings!"; return; } //int busBase = CANConManager::getInstance()->getBusBase(conn_p); //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++) { if (bus.getSpeed() == ui->cbBusSpeed->itemText(i).toInt()) { found = true; ui->cbBusSpeed->setCurrentIndex(i); 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())); } } void ConnectionWindow::currentTabChanged(int newIdx) { populateBusDetails(newIdx); } void ConnectionWindow::currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous) { int selIdx = current.row(); 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) { ui->groupBus->setEnabled(false); return; } else { //bool ret; ui->groupBus->setEnabled(true); int numBuses; CANConnection* conn_p = connModel->getAtIdx(selIdx); 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, &CANConnection::debugOutput, 0, 0); numBuses = conn_p->getNumBuses(); int numB = ui->tabBuses->count(); for (int i = 0; i < numB; i++) ui->tabBuses->removeTab(0); int busBase = CANConManager::getInstance()->getBusBase(conn_p); /*if (numBuses > 1)*/ for (int i = 0; i < numBuses; i++) ui->tabBuses->addTab(QString::number(busBase + i)); populateBusDetails(0); if (ui->ckEnableConsole->isChecked()) { connect(conn_p, &CANConnection::debugOutput, this, &ConnectionWindow::getDebugText, Qt::UniqueConnection); connect(this, &ConnectionWindow::sendDebugData, conn_p, &CANConnection::debugInput, Qt::UniqueConnection); } } } void ConnectionWindow::getDebugText(QString debugText) { ui->textConsole->append(debugText); } void ConnectionWindow::handleClearDebugText() { ui->textConsole->clear(); } void ConnectionWindow::handleSendHex() { QByteArray bytes; QStringList tokens = ui->lineSend->text().split(' '); foreach (QString token, tokens) { bytes.append(token.toInt(nullptr, 16)); } emit sendDebugData(bytes); } void ConnectionWindow::handleSendText() { QByteArray bytes; bytes = ui->lineSend->text().toLatin1(); bytes.append('\r'); //add carriage return for line ending emit sendDebugData(bytes); } 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, pSerialSpeed, pBusSpeed, pCanFd, pDataRate); if(conn_p) { /* connect signal */ 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, &CANConnection::debugOutput, this, &ConnectionWindow::getDebugText, Qt::UniqueConnection); } /*TODO add return value and checks */ conn_p->start(); } return conn_p; } void ConnectionWindow::loadConnections() { #if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) qRegisterMetaTypeStreamOperators(); qRegisterMetaTypeStreamOperators>(); #endif QSettings settings; /* fill connection list */ QVector portNames = settings.value("connections/portNames").value>(); QVector driverNames = settings.value("connections/driverNames").value>(); QVector devTypes = settings.value("connections/types").value>(); QVector busSpeeds = settings.value("connections/busSpeeds_0").value>(); QVector DataRates = settings.value("connections/DataRates_0").value>(); QVector isCanFds = settings.value("connections/isCanFds_0").value>(); QVector serialSpeeds = settings.value("connections/serialSpeeds").value>(); //don't load the connections if the three setting arrays above aren't all the same size. if (portNames.count() != driverNames.count() || devTypes.count() != driverNames.count() || busSpeeds.count() != driverNames.count() || isCanFds.count() != driverNames.count() || DataRates.count() != driverNames.count() || serialSpeeds.count() != driverNames.count() ) return; for(int i = 0 ; i < portNames.count() ; i++) { CANConnection* conn_p = create((CANCon::type)devTypes[i], portNames[i], driverNames[i], serialSpeeds[i], busSpeeds[i], isCanFds[i] ? true : false, DataRates[i]); /* add connection to model */ connModel->add(conn_p); } if (connModel->rowCount() > 0) { ui->tableConnections->selectRow(0); } } void ConnectionWindow::saveConnections() { QList& conns = CANConManager::getInstance()->getConnections(); QSettings settings; QVector portNames; QVector devTypes; QVector driverNames; QVector serialSpeeds; QVector busSpeeds; QVector DataRates; QVector CanFds; /* save connections */ foreach(CANConnection* conn_p, conns) { CANBus bus; if (conn_p->getBusSettings(0, bus)) { busSpeeds.append(bus.getSpeed()); CanFds.append(bus.isCanFD() ? 1 : 0); DataRates.append(bus.getDataRate()); } serialSpeeds.append(conn_p->getSerialSpeed()); portNames.append(conn_p->getPort()); devTypes.append(conn_p->getType()); driverNames.append(conn_p->getDriver()); } settings.setValue("connections/portNames", QVariant::fromValue(portNames)); settings.setValue("connections/types", QVariant::fromValue(devTypes)); settings.setValue("connections/driverNames", QVariant::fromValue(driverNames)); settings.setValue("connections/busSpeeds_0", QVariant::fromValue(busSpeeds)); settings.setValue("connections/isCanFds_0", QVariant::fromValue(CanFds)); settings.setValue("connections/DataRates_0", QVariant::fromValue(DataRates)); settings.setValue("connections/serialSpeeds", QVariant::fromValue(serialSpeeds)); } void ConnectionWindow::moveConnUp() { int selIdx = ui->tableConnections->selectionModel()->currentIndex().row(); if (selIdx > 0) { CANConnection* selConn = connModel->getAtIdx(selIdx); CANConnection* prevConn = connModel->getAtIdx(selIdx - 1); connModel->replace(selIdx - 1, selConn); connModel->replace(selIdx, prevConn); ui->tableConnections->selectRow(selIdx - 1); } } void ConnectionWindow::moveConnDown() { int selIdx = ui->tableConnections->selectionModel()->currentIndex().row(); if (selIdx < connModel->rowCount() - 1) { CANConnection* selConn = connModel->getAtIdx(selIdx); CANConnection* nextConn = connModel->getAtIdx(selIdx + 1); connModel->replace(selIdx + 1, selConn); connModel->replace(selIdx, nextConn); ui->tableConnections->selectRow(selIdx + 1); } } savvycan-220/connections/connectionwindow.h000066400000000000000000000036501500724750100213000ustar00rootroot00000000000000#ifndef CONNECTIONWINDOW_H #define CONNECTIONWINDOW_H #include #include #include #include #include #include #include #include #include "canconnectionmodel.h" #include "connections/canconnection.h" class CANConnectionModel; namespace Ui { class ConnectionWindow; } class ConnectionWindow : public QDialog { Q_OBJECT public: explicit ConnectionWindow(QWidget *parent = 0); ~ConnectionWindow(); signals: void updateBusSettings(CANBus *bus); void updatePortName(QString port); void sendDebugData(QByteArray bytes); public slots: void getDebugText(QString debugText); void setSuspendAll(bool pSuspend); private slots: void currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous); void currentTabChanged(int newIdx); void consoleEnableChanged(bool checked); void handleRemoveConn(); void handleNewConn(); void handleResetConn(); void handleClearDebugText(); void handleSendHex(); void handleSendText(); void saveBusSettings(); void moveConnUp(); void moveConnDown(); void connectionStatus(CANConStatus); void readPendingDatagrams(); private: Ui::ConnectionWindow *ui; QSettings *settings; CANConnectionModel *connModel; QUdpSocket *rxBroadcastGVRET; QUdpSocket *rxBroadcastKayak; QVector remoteDeviceIPGVRET; QVector remoteDeviceKayak; 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(); void showEvent(QShowEvent *); void closeEvent(QCloseEvent *event); bool eventFilter(QObject *obj, QEvent *event); void readSettings(); void writeSettings(); }; #endif // CONNECTIONWINDOW_H savvycan-220/connections/gvretserial.cpp000066400000000000000000001032271500724750100205740ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "gvretserial.h" GVRetSerial::GVRetSerial(QString portName, bool useTcp) : 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) { sendDebug("GVRetSerial()"); serial = nullptr; tcpClient = nullptr; udpClient = nullptr; rx_state = IDLE; rx_step = 0; validationCounter = 10; //how many times we can miss validation before we die isAutoRestart = false; espSerialMode = true; timeBasis = 0; lastSystemTimeBasis = 0; timeAtGVRETSync = 0; readSettings(); } GVRetSerial::~GVRetSerial() { stop(); sendDebug("~GVRetSerial()"); } void GVRetSerial::sendDebug(const QString debugText) { qDebug() << debugText; debugOutput(debugText); } void GVRetSerial::sendToSerial(const QByteArray &bytes) { if (serial == nullptr && tcpClient == nullptr && udpClient == 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; } if (tcpClient && !tcpClient->isOpen()) { sendDebug("Attempt to write to TCP/IP port when it is not open!"); return; } if (udpClient && !udpClient->isOpen()) { sendDebug("Attempt to write to UDP Socket 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); if (tcpClient) tcpClient->write(bytes); if (udpClient) udpClient->write(bytes); } void GVRetSerial::piStarted() { connectDevice(); } void GVRetSerial::piSuspend(bool pSuspend) { /* update capSuspended */ setCapSuspended(pSuspend); /* flush queue if we are suspended */ if(isCapSuspended()) getQueue().flush(); } void GVRetSerial::piStop() { mTimer.stop(); disconnectDevice(); } bool GVRetSerial::piGetBusSettings(int pBusIdx, CANBus& pBus) { return getBusConfig(pBusIdx, pBus); } void GVRetSerial::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; } else if (pBusIdx == 1) { can1Baud = bus.getSpeed(); can1Baud |= 0x80000000; if (bus.isActive()) { can1Baud |= 0x40000000; can1Enabled = true; } else can1Enabled = false; if (bus.isListenOnly()) { can1Baud |= 0x20000000; can1ListenOnly = true; } else can1ListenOnly = false; if (bus.isSingleWire()) { can1Baud |= 0x10000000; deviceSingleWireMode = 1; } else deviceSingleWireMode = 0; } else if (pBusIdx == 2) { swcanBaud = bus.getSpeed(); swcanBaud |= 0x80000000; if (bus.isActive()) { swcanBaud |= 0x40000000; swcanEnabled = true; } else swcanEnabled = false; if (bus.isListenOnly()) { swcanBaud |= 0x20000000; swcanListenOnly = true; } else swcanListenOnly = false; } if (pBusIdx < 2) { /* update baud rates */ QByteArray buffer; sendDebug("Got signal to update bauds. 1: " + QString::number((can0Baud & 0xFFFFFFF)) + " 2: " + QString::number((can1Baud & 0xFFFFFFF))); buffer[0] = (char)0xF1; //start of a command over serial buffer[1] = 5; //setup canbus buffer[2] = (char)(can0Baud & 0xFF); //four bytes of ID LSB first buffer[3] = (char)(can0Baud >> 8); buffer[4] = (char)(can0Baud >> 16); buffer[5] = (char)(can0Baud >> 24); buffer[6] = (char)(can1Baud & 0xFF); //four bytes of ID LSB first buffer[7] = (char)(can1Baud >> 8); buffer[8] = (char)(can1Baud >> 16); buffer[9] = (char)(can1Baud >> 24); buffer[10] = 0; sendToSerial(buffer); } else { /* update baud rates */ QByteArray buffer; sendDebug("Got signal to update extended bus speeds SWCAN: " + QString::number(swcanBaud) + " LIN1: " + QString::number(lin1Baud) + " LIN2: " + QString::number(lin2Baud)); buffer[0] = (char)0xF1; //start of a command over serial buffer[1] = 14; //setup extended buses buffer[2] = (char)(swcanBaud & 0xFF); //four bytes of ID LSB first buffer[3] = (char)(swcanBaud >> 8); buffer[4] = (char)(swcanBaud >> 16); buffer[5] = (char)(swcanBaud >> 24); buffer[6] = (char)(lin1Baud & 0xFF); //four bytes of ID LSB first buffer[7] = (char)(lin1Baud >> 8); buffer[8] = (char)(lin1Baud >> 16); buffer[9] = (char)(lin1Baud >> 24); buffer[10] = (char)(lin2Baud & 0xFF); //four bytes of ID LSB first buffer[11] = (char)(lin2Baud >> 8); buffer[12] = (char)(lin2Baud >> 16); buffer[13] = (char)(lin2Baud >> 24); buffer[14] = 0; sendToSerial(buffer); } } bool GVRetSerial::piSendFrame(const CANFrame& frame) { QByteArray buffer; int c; quint32 ID; //qDebug() << "Sending out GVRET frame with id " << frame.ID << " on bus " << frame.bus; framesRapid++; if (serial == nullptr && tcpClient == nullptr && udpClient == nullptr) return false; if (serial && !serial->isOpen()) return false; if (tcpClient && !tcpClient->isOpen()) return false; if (udpClient && !udpClient->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; buffer[0] = (char)0xF1; //start of a command over serial buffer[1] = 0; //command ID for sending a CANBUS frame buffer[2] = (char)(ID & 0xFF); //four bytes of ID LSB first buffer[3] = (char)(ID >> 8); buffer[4] = (char)(ID >> 16); buffer[5] = (char)(ID >> 24); buffer[6] = (char)((frame.bus) & 3); buffer[7] = (char)frame.payload().length(); for (c = 0; c < frame.payload().length(); c++) { buffer[8 + c] = frame.payload()[c]; } buffer[8 + frame.payload().length()] = 0; sendToSerial(buffer); return true; } /****************************************************************/ void GVRetSerial::readSettings() { QSettings settings; if (settings.value("Main/ValidateComm", true).toBool()) { doValidation = true; } else doValidation = false; } void GVRetSerial::connectDevice() { QSettings settings; /* disconnect device */ if(serial) disconnectDevice(); if(tcpClient) disconnectDevice(); if (udpClient) disconnectDevice(); /* open new device */ if (useTcp) { // /* sendDebug("TCP Connection to a GVRET device"); tcpClient = new QTcpSocket(); tcpClient->connectToHost(getPort(), 23); connect(tcpClient, SIGNAL(readyRead()), this, SLOT(readSerialData())); connect(tcpClient, SIGNAL(connected()), this, SLOT(deviceConnected())); sendDebug("Created TCP Socket"); // */ /* qDebug() << "UDP Connection to a GVRET device"; udpClient = new QUdpSocket(); udpClient->connectToHost(getPort(), 17222); connect(udpClient, SIGNAL(readyRead()), this, SLOT(readSerialData())); //connect(udpClient, SIGNAL(connected()), this, SLOT(tcpConnected())); debugOutput("Created UDP Socket"); tcpConnected(); */ } else { sendDebug("Serial connection to a GVRET device"); 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(1000000); //most GVRET devices ignore baud, ESP32 needs it set explicitly to the proper value serial->setDataBits(serial->Data8); if (espSerialMode) { sendDebug("Trying ESP32 Serial Mode"); serial->setFlowControl(serial->NoFlowControl); if (!serial->open(QIODevice::ReadWrite)) { //sendDebug("Error returned during port opening: " + serial->errorString()); } else { serial->setDataTerminalReady(false); //ESP32 uses these for bootloader selection and reset so turn them off serial->setRequestToSend(false); QTimer::singleShot(3000, this, SLOT(deviceConnected())); //give ESP32 some time as it could have rebooted } } else { sendDebug("Trying Standard Serial Mode"); serial->setFlowControl(serial->HardwareControl); //Most GVRET style devices use hardware flow control 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 GVRetSerial::deviceConnected() { sendDebug("Connecting to GVRET Device!"); QByteArray output; output.append((char)0xE7); //this puts the device into binary comm mode output.append((char)0xE7); output.append((char)0xF1); output.append((char)0x0C); //get number of actually implemented buses. Not implemented except on M2RET mNumBuses = 2; //the proper number if C/12 is not implemented output.append((char)0xF1); //signal we want to issue a command output.append((char)0x06); //request canbus stats from the board output.append((char)0xF1); //another command to the GVRET output.append((char)0x07); //request device information /*output.append((char)0xF1); output.append((char)0x08); //setting singlewire mode if (settings.value("Main/SingleWireMode", false).toBool()) { output.append((char)0x10); //signal that we do want single wire mode } else { output.append((char)0xFF); //signal we don't want single wire mode }*/ output.append((char)0xF1); //and another command output.append((char)0x01); //Time Sync - Not implemented until 333 but we can try output.append((char)0xF1); //yet another command output.append((char)0x09); //comm validation command continuousTimeSync = true; sendToSerial(output); if(doValidation) { QTimer::singleShot(5000, this, SLOT(connectionTimeout())); } else { setStatus(CANCon::CONNECTED); CANConStatus stats; stats.conStatus = getStatus(); stats.numHardwareBuses = mNumBuses; emit status(stats); } } void GVRetSerial::disconnectDevice() { if (serial != nullptr) { if (serial->isOpen()) { //serial->clear(); serial->close(); } serial->disconnect(); //disconnect all signals delete serial; serial = nullptr; } if (tcpClient != nullptr) { if (tcpClient->isOpen()) { tcpClient->close(); } tcpClient->disconnect(); delete tcpClient; tcpClient = nullptr; } if (udpClient != nullptr) { if (udpClient->isOpen()) { udpClient->close(); } udpClient->disconnect(); delete udpClient; udpClient = nullptr; } setStatus(CANCon::NOT_CONNECTED); CANConStatus stats; stats.conStatus = getStatus(); stats.numHardwareBuses = mNumBuses; emit status(stats); } void GVRetSerial::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; #if QT_VERSION <= QT_VERSION_CHECK( 6, 0, 0 ) 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; #endif 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 GVRetSerial::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 GVRET at that com port"); //toggle the serial mode and try again espSerialMode = !espSerialMode; 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 GVRetSerial::readSerialData() { QByteArray data; unsigned char c; QString debugBuild; if (serial) data = serial->readAll(); if (tcpClient) data = tcpClient->readAll(); if (udpClient) data = udpClient->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); } debugOutput(debugBuild); //qDebug() << debugBuild; } //Debugging data sent from connection window. Inject it into Comm traffic. void GVRetSerial::debugInput(QByteArray bytes) { sendToSerial(bytes); } void GVRetSerial::procRXChar(unsigned char c) { CANConStatus stats; int oldBuses; QByteArray output; switch (rx_state) { case IDLE: if (c == 0xF1) rx_state = GET_COMMAND; break; case GET_COMMAND: switch (c) { case 0: //receiving a can frame rx_state = BUILD_CAN_FRAME; rx_step = 0; break; case 1: //time sync rx_state = TIME_SYNC; rx_step = 0; break; case 2: //process a return reply for digital input states. rx_state = GET_DIG_INPUTS; rx_step = 0; break; case 3: //process a return reply for analog inputs rx_state = GET_ANALOG_INPUTS; break; case 4: //we set digital outputs we don't accept replies so nothing here. rx_state = IDLE; break; case 5: //we set canbus specs we don't accept replies. rx_state = IDLE; break; case 6: //get canbus parameters from GVRET rx_state = GET_CANBUS_PARAMS; rx_step = 0; break; case 7: //get device info rx_state = GET_DEVICE_INFO; rx_step = 0; break; case 9: validationCounter = 10; qDebug() << "Got validated"; rx_state = IDLE; break; case 12: rx_state = GET_NUM_BUSES; qDebug() << "Got num buses reply"; rx_step = 0; break; case 13: rx_state = GET_EXT_BUSES; 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: 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) { buildFrame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(QDateTime::currentMSecsSinceEpoch() * 1000ul)); } else { 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 & 0xF); buildFrame.bus = (c & 0xF0) >> 4; 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) { buildFrame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(QDateTime::currentMSecsSinceEpoch() * 1000ul)); } else { 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; } else { 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); } } break; } rx_step++; break; case TIME_SYNC: //gives a pretty good base guess for the proper timestamp. Can be refined when traffic starts to flow (if wanted) switch (rx_step) { case 0: buildTimeBasis = c; break; case 1: buildTimeBasis += ((uint32_t)c << 8); break; case 2: buildTimeBasis += ((uint32_t)c << 16); break; case 3: buildTimeBasis += ((uint32_t)c << 24); qDebug() << "GVRET firmware reports timestamp of " << buildTimeBasis; timeAtGVRETSync = QDateTime::currentMSecsSinceEpoch() * 1000; rebuildLocalTimeBasis(); continuousTimeSync = false; rx_state = IDLE; break; } rx_step++; break; case GET_ANALOG_INPUTS: //get 9 bytes - 2 per analog input plus checksum switch (rx_step) { case 0: break; } rx_step++; break; case GET_DIG_INPUTS: //get two bytes. One for digital in status and one for checksum. switch (rx_step) { case 0: break; case 1: rx_state = IDLE; break; } rx_step++; break; case GET_CANBUS_PARAMS: switch (rx_step) { case 0: can0Enabled = (c & 0xF); can0ListenOnly = (c >> 4); break; case 1: can0Baud = c; break; case 2: can0Baud |= c << 8; break; case 3: can0Baud |= c << 16; break; case 4: can0Baud |= c << 24; break; case 5: can1Enabled = (c & 0xF); can1ListenOnly = (c >> 4); deviceSingleWireMode = (c >> 6); break; case 6: can1Baud = c; break; case 7: can1Baud |= c << 8; break; case 8: can1Baud |= c << 16; break; case 9: can1Baud |= c << 24; rx_state = IDLE; qDebug() << "Baud 0 = " << can0Baud; qDebug() << "Baud 1 = " << can1Baud; mBusData[0].mBus.setSpeed(can0Baud); mBusData[0].mBus.setActive(can0Enabled); mBusData[0].mConfigured = true; if (mBusData.count() > 1) { mBusData[1].mBus.setSpeed(can1Baud); mBusData[1].mBus.setActive(can1Enabled); mBusData[1].mConfigured = true; } can0Baud |= 0x80000000; if (can0Enabled) can0Baud |= 0x40000000; if (can0ListenOnly) can0Baud |= 0x20000000; can1Baud |= 0x80000000; if (can1Enabled) can1Baud |= 0x40000000; if (can1ListenOnly) can1Baud |= 0x20000000; if (deviceSingleWireMode > 0) can1Baud |= 0x10000000; setStatus(CANCon::CONNECTED); stats.conStatus = getStatus(); stats.numHardwareBuses = mNumBuses; emit status(stats); int can0Status = 0x78; //updating everything we can update int can1Status = 0x78; if (can0Enabled) can0Status +=1; if (can0ListenOnly) can0Status += 4; if (can1Enabled) can1Status += 1; if (deviceSingleWireMode > 0) can1Status += 2; if (can1ListenOnly) can1Status += 4; //emit busStatus(busBase, can0Baud & 0xFFFFF, can0Status); //emit busStatus(busBase + 1, can1Baud & 0xFFFFF, can1Status); break; } rx_step++; break; case GET_DEVICE_INFO: switch (rx_step) { case 0: deviceBuildNum = c; break; case 1: deviceBuildNum |= c << 8; break; case 2: break; //don't care about eeprom version case 3: break; //don't care about file type case 4: break; //don't care about whether it auto logs or not case 5: deviceSingleWireMode = c; rx_state = IDLE; qDebug() << "build num: " << deviceBuildNum; qDebug() << "single wire can: " << deviceSingleWireMode; emit deviceInfo(deviceBuildNum, deviceSingleWireMode); break; } rx_step++; break; case SET_DIG_OUTPUTS: rx_state = IDLE; break; case SETUP_CANBUS: rx_state = IDLE; break; case SET_SINGLEWIRE_MODE: rx_state = IDLE; break; case GET_NUM_BUSES: oldBuses = mNumBuses; mNumBuses = c; rx_state = IDLE; qDebug() << "Get number of buses = " << mNumBuses; stats.conStatus = getStatus(); stats.numHardwareBuses = mNumBuses; mBusData.resize(mNumBuses); if (mNumBuses > oldBuses) { for (int i = oldBuses; i < mNumBuses; i++) { mBusData[i].mConfigured = true; mBusData[i].mBus = mBusData[0].mBus; } } output.append((unsigned char)0xF1); //start a new command output.append((unsigned char)13); //get extended buses sendToSerial(output); emit status(stats); break; case GET_FD_SETTINGS: break; case GET_EXT_BUSES: switch (rx_step) { case 0: swcanEnabled = (c & 0xF); swcanListenOnly = (c >> 4); break; case 1: swcanBaud = c; break; case 2: swcanBaud |= c << 8; break; case 3: swcanBaud |= c << 16; break; case 4: swcanBaud |= c << 24; break; case 5: lin1Enabled = (c & 0xF); break; case 6: lin1Baud = c; break; case 7: lin1Baud |= c << 8; break; case 8: lin1Baud |= c << 16; break; case 9: lin1Baud |= c << 24; break; case 10: lin2Enabled = (c & 0xF); break; case 11: lin2Baud = c; break; case 12: lin2Baud |= c << 8; break; case 13: lin2Baud |= c << 16; break; case 14: lin2Baud |= c << 24; rx_state = IDLE; qDebug() << "SWCAN Baud = " << swcanBaud; qDebug() << "LIN1 Baud = " << lin1Baud; qDebug() << "LIN2 Baud = " << lin2Baud; if (getNumBuses() > 2) { mBusData[2].mBus.setSpeed(swcanBaud); mBusData[2].mBus.setActive(swcanEnabled); } setStatus(CANCon::CONNECTED); stats.conStatus = getStatus(); stats.numHardwareBuses = mNumBuses; emit status(stats); break; } rx_step++; break; } } void GVRetSerial::rebuildLocalTimeBasis() { qDebug() << "Rebuilding GVRET time base. GVRET local base = " << buildTimeBasis; /* our time basis is the value we have to modulate the main system basis by in order to sync the GVRET timestamps to the rest of the system. The rest of the system uses CANConManager::getInstance()->getTimeBasis as the basis. GVRET returns to us the current time since boot up in microseconds. timeAtGVRETSync stores the "system" timestamp when the GVRET timestamp was retrieved. */ lastSystemTimeBasis = CANConManager::getInstance()->getTimeBasis(); int64_t systemDelta = timeAtGVRETSync - lastSystemTimeBasis; int32_t localDelta = buildTimeBasis - systemDelta; timeBasis = -localDelta; } void GVRetSerial::handleTick() { if (lastSystemTimeBasis != CANConManager::getInstance()->getTimeBasis()) rebuildLocalTimeBasis(); //qDebug() << "Tick!"; if( CANCon::CONNECTED == getStatus() ) { if (doValidation) validationCounter--; //qDebug() << validationCounter; if (validationCounter == 0 && doValidation) { if (serial == nullptr && tcpClient == nullptr) return; if ( (serial && serial->isOpen()) || (tcpClient && tcpClient->isOpen()) || (udpClient && udpClient->isOpen())) //if it's still false we have a problem... { sendDebug("Comm validation failed."); setStatus(CANCon::NOT_CONNECTED); //emit status(getStatus()); disconnectDevice(); //start by stopping everything. //Then wait 500ms and restart the connection automatically //QTimer::singleShot(500, this, SLOT(connectDevice())); return; } } else if (doValidation) { //qDebug() << "Comm connection validated"; } } if (doValidation && serial && serial->isOpen()) sendCommValidation(); if (doValidation && tcpClient && tcpClient->isOpen()) sendCommValidation(); if (doValidation && udpClient && udpClient->isOpen()) sendCommValidation(); } void GVRetSerial::sendCommValidation() { QByteArray output; output.append((unsigned char)0xF1); //another command to the GVRET output.append((unsigned char)0x09); //request a reply to get validation sendToSerial(output); } savvycan-220/connections/gvretserial.h000066400000000000000000000050041500724750100202330ustar00rootroot00000000000000#ifndef GVRETSERIAL_H #define GVRETSERIAL_H #include #include #include #include #include #include /*************/ #include /*************/ #include "canframemodel.h" #include "canconnection.h" #include "canconmanager.h" namespace SERIALSTATE { enum STATE { IDLE, GET_COMMAND, BUILD_CAN_FRAME, TIME_SYNC, GET_DIG_INPUTS, GET_ANALOG_INPUTS, SET_DIG_OUTPUTS, SETUP_CANBUS, GET_CANBUS_PARAMS, GET_DEVICE_INFO, SET_SINGLEWIRE_MODE, GET_NUM_BUSES, GET_EXT_BUSES, BUILD_FD_FRAME, GET_FD_SETTINGS }; } using namespace SERIALSTATE; class GVRetSerial : public CANConnection { Q_OBJECT public: GVRetSerial(QString portName, bool useTcp); virtual ~GVRetSerial(); 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 procRXChar(unsigned char); void sendCommValidation(); void rebuildLocalTimeBasis(); void sendToSerial(const QByteArray &bytes); void sendDebug(const QString debugText); protected: QTimer mTimer; QThread mThread; bool doValidation; int validationCounter; bool isAutoRestart; bool continuousTimeSync; bool useTcp; bool espSerialMode; //special serial mode for ESP32 based boards - no flow control and much slower serial baud speed QSerialPort *serial; QTcpSocket *tcpClient; QUdpSocket *udpClient; int framesRapid; STATE rx_state; int rx_step; CANFrame buildFrame; qint64 buildTimestamp; quint32 buildId; QByteArray buildData; int can0Baud, can1Baud, swcanBaud, lin1Baud, lin2Baud; bool can0Enabled, can1Enabled, swcanEnabled, lin1Enabled, lin2Enabled; bool can0ListenOnly, can1ListenOnly, swcanListenOnly; int deviceBuildNum; int deviceSingleWireMode; uint32_t buildTimeBasis; int32_t timeBasis; uint64_t lastSystemTimeBasis; uint64_t timeAtGVRETSync; }; #endif // GVRETSERIAL_H savvycan-220/connections/lawicel_serial.cpp000066400000000000000000000502621500724750100212240ustar00rootroot00000000000000#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!"; if (useSystemTime) { buildFrame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(QDateTime::currentMSecsSinceEpoch() * 1000ul)); } else { //If total length is greater than command, header and data, timestamps must be enabled. if (data.length() > (5 + mBuildLine.mid(4, 1).toInt() * 2 + 1)) { //Four bytes after the end of the data bytes. buildTimestamp = mBuildLine.mid(5 + mBuildLine.mid(4, 1).toInt() * 2, 4).toInt(nullptr, 16) * 1000l; buildFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, buildTimestamp)); } else { //Default to system time if timestamps are disabled. buildFrame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(QDateTime::currentMSecsSinceEpoch() * 1000ul)); } } 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; } } } savvycan-220/connections/lawicel_serial.h000066400000000000000000000031451500724750100206670ustar00rootroot00000000000000#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; qint64 buildTimestamp; bool can0Enabled; bool can0ListenOnly; bool canFd; int dataRate; }; #endif // LAWICELSERIAL_H savvycan-220/connections/mqtt_bus.cpp000066400000000000000000000246131500724750100201040ustar00rootroot00000000000000#include #include #include #include #include #include #include "utility.h" #include "mqtt_bus.h" MQTT_BUS::MQTT_BUS(QString topicName) : 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 */ { sendDebug("MQTT_BUS()"); crypto = new SimpleCrypt(Q_UINT64_C(0xdeadbeefface6285)); isAutoRestart = false; this->topicName = topicName; timeBasis = 0; lastSystemTimeBasis = 0; readSettings(); } MQTT_BUS::~MQTT_BUS() { delete crypto; stop(); sendDebug("~MQTT_BUS"); } void MQTT_BUS::sendDebug(const QString debugText) { qDebug() << debugText; debugOutput(debugText); } void MQTT_BUS::piStarted() { QSettings settings; QString userName = settings.value("Remote/User", "Anonymous").toString(); QString host = settings.value("Remote/Host", "api.savvycan.com").toString(); int port = settings.value("Remote/Port", 8883).toInt(); QByteArray encPass = settings.value("Remote/Pass", "").toByteArray(); QByteArray password; if (encPass.length() > 0) password = crypto->decryptToByteArray(encPass); if (port == 8883) { QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); mqttClient = new QMQTT::Client(host, port, sslConfig); } else { QHostAddress hAddr = QHostInfo::fromName(host).addresses()[0]; sendDebug("IP Address of Host: " + hAddr.toString()); mqttClient = new QMQTT::Client(hAddr, port); } connect(mqttClient, &QMQTT::Client::connected, this, &MQTT_BUS::clientConnected); connect(mqttClient, &QMQTT::Client::error, this, &MQTT_BUS::clientErrored); //qDebug() << "User: " << userName << " Pass: " << password; mqttClient->setClientId(genRandomClientID()); if (userName.length() > 0) mqttClient->setUsername(userName); if (password.length() > 0) mqttClient->setPassword(password); sendDebug("Attempting to connect to MQTT."); mqttClient->connectToHost(); } //MQTT required a client ID and they cannot be the same for any two clients. But, they really aren't super exciting //or important to be named explicitly. Perhaps it might be nice to be able to see it for debugging though. QString MQTT_BUS::genRandomClientID() { QString output; output.reserve(12); QRandomGenerator gen = QRandomGenerator::securelySeeded(); for (int i = 0; i < 12; i++) { int val = gen.bounded(0, 62); if (val < 26) output.append(QChar('A'+val)); else if (val < 52) output.append(QChar('a'+val-26)); else output.append(QChar('0'+val-52)); } qDebug() << "Client ID: " << output; return output; } void MQTT_BUS::clientErrored(const QMQTT::ClientError error) { switch (error) { case QMQTT::UnknownError: sendDebug("MQTT Unknown Error"); break; case QMQTT::SocketConnectionRefusedError: sendDebug("MQTT Connection Refused"); break; case QMQTT::SocketRemoteHostClosedError: sendDebug("MQTT Remote Host Closed The Connection"); break; case QMQTT::SocketHostNotFoundError: sendDebug("MQTT Remote Host Not Found"); break; case QMQTT::SocketAccessError: sendDebug("MQTT Socket Access Error"); break; case QMQTT::SocketResourceError: sendDebug("MQTT Resource Error"); break; case QMQTT::SocketTimeoutError: sendDebug("MQTT Timeout Error"); break; case QMQTT::SocketDatagramTooLargeError: sendDebug("MQTT Datagram too large"); break; case QMQTT::SocketNetworkError: sendDebug("MQTT Network Error"); break; case QMQTT::SocketAddressInUseError: sendDebug("MQTT Address In Use ERROR"); break; case QMQTT::SocketAddressNotAvailableError: sendDebug("MQTT Address Not Available"); break; case QMQTT::SocketUnsupportedSocketOperationError: sendDebug("MQTT Unsupported Operation"); break; case QMQTT::SocketUnfinishedSocketOperationError: sendDebug("MQTT Unfinished Socket Operation"); break; case QMQTT::SocketProxyAuthenticationRequiredError: sendDebug("MQTT Proxy Auth Required"); break; case QMQTT::SocketSslHandshakeFailedError: sendDebug("MQTT SSL Handshake Failure"); break; case QMQTT::SocketProxyConnectionRefusedError: sendDebug("MQTT Proxy Connection Refused"); break; case QMQTT::SocketProxyConnectionClosedError: sendDebug("MQTT Proxy Connection Closed"); break; case QMQTT::SocketProxyConnectionTimeoutError: sendDebug("MQTT Proxy Connection Timeout"); break; case QMQTT::SocketProxyNotFoundError: sendDebug("MQTT Proxy Not Found"); break; case QMQTT::SocketProxyProtocolError: sendDebug("MQTT Proxy Protocol Error"); break; case QMQTT::SocketOperationError: sendDebug("MQTT Socket Operation Error"); break; case QMQTT::SocketSslInternalError: sendDebug("MQTT SSL Internal Error"); break; case QMQTT::SocketSslInvalidUserDataError: sendDebug("MQTT SSL Invalid User Data"); break; case QMQTT::SocketTemporaryError: sendDebug("MQTT Temporary Error"); break; case QMQTT::MqttUnacceptableProtocolVersionError: sendDebug("MQTT Unaccepted Protocol Version"); break; case QMQTT::MqttIdentifierRejectedError: sendDebug("MQTT Identifier Rejected"); break; case QMQTT::MqttServerUnavailableError: sendDebug("MQTT Server Unavailable"); break; case QMQTT::MqttBadUserNameOrPasswordError: sendDebug("MQTT Bad Access Credentials"); break; case QMQTT::MqttNotAuthorizedError: sendDebug("MQTT Not Authorized"); break; case QMQTT::MqttNoPingResponse: sendDebug("MQTT No PING Response"); break; } } void MQTT_BUS::piSuspend(bool pSuspend) { /* update capSuspended */ setCapSuspended(pSuspend); /* flush queue if we are suspended */ if(isCapSuspended()) getQueue().flush(); } void MQTT_BUS::piStop() { mTimer.stop(); disconnectDevice(); } bool MQTT_BUS::piGetBusSettings(int pBusIdx, CANBus& pBus) { return getBusConfig(pBusIdx, pBus); } void MQTT_BUS::piSetBusSettings(int pBusIdx, CANBus bus) { /* sanity checks */ if( (pBusIdx < 0) || pBusIdx >= getNumBuses()) return; /* copy bus config */ setBusConfig(pBusIdx, bus); //we don't really update anything. We're just here to listen and perhaps send frames. } bool MQTT_BUS::piSendFrame(const CANFrame& frame) { QByteArray buffer; //int c; //quint32 ID; //qDebug() << "Sending out GVRET frame with id " << frame.ID << " on bus " << frame.bus; framesRapid++; // Doesn't make sense to send an error frame // to an adapter if (frame.frameId() & 0x20000000) { return true; } QMQTT::Message msg; QByteArray bytes; msg.setTopic(topicName + "/s/" + QString::number(frame.frameId())); uint8_t flags = 0; if (frame.hasExtendedFrameFormat()) flags += 1; if (frame.frameType() == QCanBusFrame::RemoteRequestFrame) flags += 2; if (frame.hasFlexibleDataRateFormat()) flags += 4; if (frame.frameType() == QCanBusFrame::ErrorFrame) flags += 8; uint64_t micros = QDateTime::currentMSecsSinceEpoch() * 1000ull; for (int x = 0; x < 8; x++) { bytes.append(micros & 0xFF); micros = micros / 256; } bytes.append(flags); bytes.append(frame.payload()); msg.setPayload(bytes); mqttClient->publish(msg); return true; } /****************************************************************/ void MQTT_BUS::readSettings() { QSettings settings; } void MQTT_BUS::clientMessageReceived(const QMQTT::Message& message) { //uint64_t timeBasis = CANConManager::getInstance()->getTimeBasis(); /* drop frame if capture is suspended */ if(isCapSuspended()) return; CANFrame* frame_p = getQueue().get(); if(frame_p) { uint32_t frameID = message.topic().split("/")[1].toInt(); 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; frame_p->setExtendedFrameFormat(flags & 1); frame_p->setFrameId(frameID); frame_p->setFrameType(QCanBusFrame::DataFrame); frame_p->isReceived = true; if (useSystemTime) { frame_p->setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(QDateTime::currentMSecsSinceEpoch() * 1000ul)); } else frame_p->setTimeStamp(QCanBusFrame::TimeStamp(0, timeStamp)); checkTargettedFrame(*frame_p); /* enqueue frame */ getQueue().queue(); } } void MQTT_BUS::clientConnected() { sendDebug("Connected to MQTT Broker!"); mqttClient->subscribe(topicName + "/+", 0); //subscribe to all sub topics to grab the frames. connect(mqttClient, &QMQTT::Client::received, this, &MQTT_BUS::clientMessageReceived); setStatus(CANCon::CONNECTED); CANConStatus stats; stats.conStatus = getStatus(); stats.numHardwareBuses = 1;//mNumBuses; emit status(stats); } void MQTT_BUS::disconnectDevice() { setStatus(CANCon::NOT_CONNECTED); CANConStatus stats; stats.conStatus = getStatus(); stats.numHardwareBuses = mNumBuses; emit status(stats); } void MQTT_BUS::rebuildLocalTimeBasis() { //qDebug() << "Rebuilding GVRET time base. GVRET local base = " << buildTimeBasis; /* our time basis is the value we have to modulate the main system basis by in order to sync the GVRET timestamps to the rest of the system. The rest of the system uses CANConManager::getInstance()->getTimeBasis as the basis. GVRET returns to us the current time since boot up in microseconds. timeAtGVRETSync stores the "system" timestamp when the GVRET timestamp was retrieved. */ /* lastSystemTimeBasis = CANConManager::getInstance()->getTimeBasis(); int64_t systemDelta = timeAtGVRETSync - lastSystemTimeBasis; int32_t localDelta = buildTimeBasis - systemDelta; timeBasis = -localDelta; */ } savvycan-220/connections/mqtt_bus.h000066400000000000000000000026401500724750100175450ustar00rootroot00000000000000#ifndef MQTTBUS_H #define MQTTBUS_H #include #include #include #include "mqtt/qmqtt.h" /*************/ #include /*************/ #include "canframemodel.h" #include "canconnection.h" #include "canconmanager.h" #include "simplecrypt.h" class MQTT_BUS : public CANConnection { Q_OBJECT public: MQTT_BUS(QString topicName); virtual ~MQTT_BUS(); 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(); private slots: void clientConnected(); void clientErrored(const QMQTT::ClientError error); void clientMessageReceived(const QMQTT::Message& message); private: void readSettings(); void rebuildLocalTimeBasis(); void sendDebug(const QString debugText); QString genRandomClientID(); SimpleCrypt *crypto; protected: QTimer mTimer; QThread mThread; QMQTT::Client *mqttClient; QString topicName; bool isAutoRestart; int framesRapid; CANFrame buildFrame; qint64 buildTimestamp; quint32 buildId; QByteArray buildData; uint32_t buildTimeBasis; int32_t timeBasis; uint64_t lastSystemTimeBasis; }; #endif // MQTT_BUS_H savvycan-220/connections/newconnectiondialog.cpp000066400000000000000000000314511500724750100222750ustar00rootroot00000000000000#include #include "newconnectiondialog.h" #include "ui_newconnectiondialog.h" NewConnectionDialog::NewConnectionDialog(QVector* gvretips, QVector* kayakhosts, QWidget *parent) : QDialog(parent), ui(new Ui::NewConnectionDialog), remoteDeviceIPGVRET(gvretips), remoteBusKayak(kayakhosts) { ui->setupUi(this); if (isSerialBusAvailable()) { ui->rbSocketCAN->setEnabled(true); } else { ui->rbSocketCAN->setEnabled(false); QString errorString; const QList devices = QCanBus::instance()->availableDevices(QStringLiteral("socketcan"), &errorString); if (!errorString.isEmpty()) ui->rbSocketCAN->setToolTip(errorString); } connect(ui->rbGVRET, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged); connect(ui->rbSocketCAN, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged); connect(ui->rbRemote, &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"; qDebug() << "Was passed " << remoteBusKayak->count() << " remote Kayak Busses"; } NewConnectionDialog::~NewConnectionDialog() { delete ui; } void NewConnectionDialog::handleCreateButton() { accept(); } 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() { ui->cbPort->clear(); canDevices = QCanBus::instance()->availableDevices(ui->cbDeviceType->currentText()); for (int i = 0; i < canDevices.count(); i++) 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(); for (int i = 0; i < ports.count(); i++) ui->cbPort->addItem(ports[i].portName()); } 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; plugins = QCanBus::instance()->plugins(); for (int i = 0; i < plugins.count(); i++) ui->cbDeviceType->addItem(plugins[i]); } 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) { ui->cbPort->addItem(pName); } } 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) { ui->cbPort->addItem(pName); } } 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(); } void NewConnectionDialog::setPortName(CANCon::type pType, QString pPortName, QString pDriver) { switch(pType) { case CANCon::GVRET_SERIAL: ui->rbGVRET->setChecked(true); break; case CANCon::SERIALBUS: ui->rbSocketCAN->setChecked(true); break; case CANCon::REMOTE: ui->rbRemote->setChecked(true); break; case CANCon::KAYAK: ui->rbKayak->setChecked(true); break; 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: {} } /* refresh names whenever needed */ //handleConnTypeChanged(); switch(pType) { case CANCon::GVRET_SERIAL: case CANCon::LAWICEL: { int idx = ui->cbPort->findText(pPortName); if( idx<0 ) idx=0; ui->cbPort->setCurrentIndex(idx); break; } case CANCon::SERIALBUS: { int idx = ui->cbDeviceType->findText(pDriver); if (idx < 0) idx = 0; ui->cbDeviceType->setCurrentIndex(idx); idx = ui->cbPort->findText(pPortName); if( idx < 0 ) idx = 0; ui->cbPort->setCurrentIndex(idx); break; } case CANCon::REMOTE: { int idx = ui->cbPort->findText(pPortName); if (idx > -1) ui->cbPort->setCurrentIndex(idx); else ui->cbPort->addItem(pPortName); break; } case CANCon::KAYAK: { int idx = ui->cbPort->findText(pPortName); if (idx > -1) ui->cbPort->setCurrentIndex(idx); else ui->cbPort->addItem(pPortName); break; } case CANCon::MQTT: ui->cbPort->setCurrentText(pPortName); break; case CANCon::CANSERVER: case CANCon::CANLOGSERVER: { ui->cbPort->setCurrentText(pPortName); break; } default: {} } } QString NewConnectionDialog::getPortName() { switch( getConnectionType() ) { case CANCon::GVRET_SERIAL: 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"; } return ""; } QString NewConnectionDialog::getDriverName() { if (getConnectionType() == CANCon::SERIALBUS) { return ui->cbDeviceType->currentText(); } 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; if (ui->rbSocketCAN->isChecked()) return CANCon::SERIALBUS; 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; } 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; } savvycan-220/connections/newconnectiondialog.h000066400000000000000000000025561500724750100217460ustar00rootroot00000000000000#ifndef NEWCONNECTIONDIALOG_H #define NEWCONNECTIONDIALOG_H #include #include #include #include #include #include "canconnectionmodel.h" #include "connections/canconnection.h" namespace Ui { class NewConnectionDialog; } class NewConnectionDialog : public QDialog { Q_OBJECT public: explicit NewConnectionDialog(QVector* gvretips, QVector* kayakips, QWidget *parent = nullptr); ~NewConnectionDialog(); CANCon::type getConnectionType(); QString getPortName(); QString getDriverName(); int getSerialSpeed(); int getBusSpeed(); bool isCanFd(); int getDataRate(); public slots: void handleConnTypeChanged(); void handleDeviceTypeChanged(); void handleCreateButton(); private: Ui::NewConnectionDialog *ui; QList ports; QList canDevices; QVector* remoteDeviceIPGVRET; QVector* remoteBusKayak; void selectSerial(); void selectKvaser(); void selectSocketCan(); void selectRemote(); void selectKayak(); void selectMQTT(); void selectLawicel(); void selectCANserver(); void selectCANlogserver(); bool isSerialBusAvailable(); void setPortName(CANCon::type pType, QString pPortName, QString pDriver); }; #endif // NEWCONNECTIONDIALOG_H savvycan-220/connections/serialbusconnection.cpp000066400000000000000000000170741500724750100223220ustar00rootroot00000000000000#include "serialbusconnection.h" #include "canconmanager.h" #include #include #include #include /***********************************/ /**** class definition ****/ /***********************************/ SerialBusConnection::SerialBusConnection(QString portName, QString driverName, int pBusSpeed, int pDataRate, bool pCanFd) : CANConnection(portName, driverName, CANCon::SERIALBUS,0 ,pBusSpeed, pCanFd, pDataRate ,1, 4000, true), mTimer(this) /*NB: set connection as parent of timer to manage it from working thread */ { } SerialBusConnection::~SerialBusConnection() { stop(); } void SerialBusConnection::piStarted() { qDebug() << "piStarted()"; /* create device */ QString errorString; qDebug() << "Creating device instance"; mDev_p = QCanBus::instance()->createDevice(getDriver(), getPort(), &errorString); if (!mDev_p) { disconnectDevice(); qDebug() << "Error: createDevice(" << getType() << getDriver() << getPort() << "):" << errorString; return; } /* connect slots */ connect(mDev_p, &QCanBusDevice::errorOccurred, this, &SerialBusConnection::errorReceived); connect(mDev_p, &QCanBusDevice::framesWritten, this, &SerialBusConnection::framesWritten); connect(mDev_p, &QCanBusDevice::framesReceived, this, &SerialBusConnection::framesReceived); connect(&mTimer, SIGNAL(timeout()), this, SLOT(testConnection())); mTimer.setInterval(1000); mTimer.setSingleShot(false); //keep ticking mTimer.start(); mBusData[0].mBus.setActive(true); mBusData[0].mBus.setCanFD(false); mBusData[0].mConfigured = true; } void SerialBusConnection::piSuspend(bool pSuspend) { /* update capSuspended */ setCapSuspended(pSuspend); /* flush queue if we are suspended */ if(isCapSuspended()) getQueue().flush(); } void SerialBusConnection::piStop() { qDebug() << "piStop()"; mTimer.stop(); disconnectDevice(); } bool SerialBusConnection::piGetBusSettings(int pBusIdx, CANBus& pBus) { return getBusConfig(pBusIdx, pBus); } void SerialBusConnection::piSetBusSettings(int pBusIdx, CANBus bus) { quint32 sbusconfig = 0; //CANConStatus stats; /* sanity checks */ if(0 != pBusIdx) return; if (!mDev_p) return; /* disconnect device if we have one connected */ disconnectDevice(); /* copy bus config */ setBusConfig(0, bus); /* if bus is not active we are done */ if(!bus.isActive()) return; /* set configuration */ /*if (p.useConfigurationEnabled) { foreach (const SettingsDialog::ConfigurationItem &item, p.configurations) mDev->setConfigurationParameter(item.first, item.second); }*/ //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.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()) { disconnectDevice(); qDebug() << "can't connect device"; } } bool SerialBusConnection::piSendFrame(const CANFrame& pFrame) { /* sanity checks */ if(0 != pFrame.bus /*|| pFrame.len>8*/) return false; if (!mDev_p) return false; return mDev_p->writeFrame(pFrame); } /***********************************/ /**** private methods ****/ /***********************************/ /* disconnect device */ void SerialBusConnection::disconnectDevice() { if(mDev_p) { mDev_p->disconnectDevice(); } } void SerialBusConnection::errorReceived(QCanBusDevice::CanBusError error) const { switch (error) { case QCanBusDevice::ReadError: case QCanBusDevice::WriteError: case QCanBusDevice::ConnectionError: case QCanBusDevice::ConfigurationError: case QCanBusDevice::UnknownError: qWarning() << mDev_p->errorString(); break; default: break; } } void SerialBusConnection::framesWritten(qint64 count) { Q_UNUSED(count); //qDebug() << "Number of frames written:" << count; } void SerialBusConnection::framesReceived() { uint64_t timeBasis = CANConManager::getInstance()->getTimeBasis(); /* sanity checks */ if(!mDev_p) return; /* read frame */ while(true) { const QCanBusFrame recFrame = mDev_p->readFrame(); /* exit case */ if(!recFrame.isValid()) break; /* drop frame if capture is suspended */ if(isCapSuspended()) continue; /* check frame */ //if (recFrame.payload().length() <= 8) { if (true) { CANFrame* frame_p = getQueue().get(); if(frame_p) { frame_p->setPayload(recFrame.payload()); frame_p->bus = 0; if (recFrame.frameType() == recFrame.ErrorFrame) { frame_p->setExtendedFrameFormat(recFrame.hasExtendedFrameFormat()); frame_p->setFrameId(recFrame.frameId() + 0x20000000ull); frame_p->isReceived = true; } else { frame_p->setExtendedFrameFormat(recFrame.hasExtendedFrameFormat()); frame_p->setFrameId(recFrame.frameId()); } 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 */ frame_p->isReceived = !recFrame.hasLocalEcho(); if (useSystemTime) { frame_p->setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(QDateTime::currentMSecsSinceEpoch() * 1000ul)); } else frame_p->setTimeStamp(QCanBusFrame::TimeStamp(0, (recFrame.timeStamp().seconds() * 1000000ul + recFrame.timeStamp().microSeconds()) - timeBasis)); checkTargettedFrame(*frame_p); /* enqueue frame */ getQueue().queue(); } else qDebug() << "can't get a frame, ERROR"; } } } void SerialBusConnection::testConnection() { CANConStatus stats; switch(getStatus()) { case CANCon::CONNECTED: if (!mDev_p || mDev_p->state() == QCanBusDevice::UnconnectedState) { /* we have lost connectivity */ disconnectDevice(); setStatus(CANCon::NOT_CONNECTED); stats.conStatus = getStatus(); stats.numHardwareBuses = mNumBuses; emit status(stats); piStop(); } break; case CANCon::NOT_CONNECTED: if (mDev_p && mDev_p->state() == QCanBusDevice::UnconnectedState) { /* try to reconnect */ CANBus bus; if(getBusConfig(0, bus)) { bus.setActive(true); setBusSettings(0, bus); } setStatus(CANCon::CONNECTED); stats.conStatus = getStatus(); stats.numHardwareBuses = mNumBuses; emit status(stats); } break; default: {} } } savvycan-220/connections/serialbusconnection.h000066400000000000000000000026551500724750100217660ustar00rootroot00000000000000#ifndef SERIALBUSCONNECTION_H #define SERIALBUSCONNECTION_H #include "canconnection.h" #include "canframemodel.h" #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 public: SerialBusConnection(QString portName, QString driverName, int pBusSpeed, int pDataRate, bool pCanFd); virtual ~SerialBusConnection(); 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(); private slots: void errorReceived(QCanBusDevice::CanBusError) const; void framesWritten(qint64 count); void framesReceived(); void testConnection(); protected: QCanBusDevice *mDev_p = nullptr; QTimer mTimer; }; #endif // SERIALBUSCONNECTION_H savvycan-220/connections/socketcand.cpp000066400000000000000000000321701500724750100203610ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "socketcand.h" SocketCANd::SocketCANd(QString portName) : 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 */ { mTimer.setInterval(10000); //tick every 10 seconds mTimer.setSingleShot(false); //keep ticking connect(&mTimer, SIGNAL(timeout()), this, SLOT(checkConnection())); sendDebug("SocketCANd()"); //tcpClient = nullptr; hostCanIDs = portName.left(portName.indexOf("@")).split(','); QString hostIPandPort = portName.mid(portName.indexOf("can://")+6, portName.length() - portName.indexOf("can://") - 7); //7 is lenght of 'can://' and ')' hostIP = QHostAddress(hostIPandPort.left(hostIPandPort.indexOf(":"))); hostPort = (hostIPandPort.right(hostIPandPort.length() - hostIPandPort.lastIndexOf(":") -1)).toInt(); QVarLengthArray tcpClient(0); mNumBuses = hostCanIDs.length(); mBusData.resize(mNumBuses); reconnecting = false; for (int i = 0; i < mNumBuses; i++) { rx_state.append(IDLE); unprocessedData.append(""); } } SocketCANd::~SocketCANd() { stop(); sendDebug("~SocketCANd()"); } void SocketCANd::sendDebug(const QString debugText) { qDebug() << debugText; debugOutput(debugText); } void SocketCANd::sendBytesToTCP(const QByteArray &bytes, int busNum) { if (tcpClient[busNum] && !tcpClient[busNum]->isOpen()) { sendDebug("Attempt to write to TCP/IP port when it is not open!"); return; } QString buildDebug; buildDebug = "Send data to " + hostIP.toString() + ":" + QString::number(hostPort) + " -> "; foreach (int byt, bytes) { byt = (unsigned char)byt; buildDebug = buildDebug % QString::number(byt, 16) % " "; } //sendDebug(buildDebug); if (tcpClient[busNum]) tcpClient[busNum]->write(bytes); } void SocketCANd::sendStringToTCP(const char* data, int busNum) { if (tcpClient[busNum] && !tcpClient[busNum]->isOpen()) { sendDebug("Attempt to write to TCP/IP port when it is not open!"); return; } //QString buildDebug; //buildDebug = "Send data to " + hostIP.toString() + ":" + QString::number(hostPort) + " -> " + data; //sendDebug(buildDebug); //qInfo() << buildDebug; if (tcpClient[busNum]) tcpClient[busNum]->write(data); } void SocketCANd::piStarted() { connectDevice(); } void SocketCANd::piSuspend(bool pSuspend) { /* update capSuspended */ setCapSuspended(pSuspend); /* flush queue if we are suspended */ if(isCapSuspended()) getQueue().flush(); } void SocketCANd::piStop() { mTimer.stop(); disconnectDevice(); } bool SocketCANd::piGetBusSettings(int pBusIdx, CANBus& pBus) { return getBusConfig(pBusIdx, pBus); } void SocketCANd::piSetBusSettings(int pBusIdx, CANBus bus) { setBusConfig(pBusIdx, bus); bus.setSpeed(250000); } bool SocketCANd::piSendFrame(const CANFrame& frame) { QByteArray buffer; int c; int ID; // //calculate bus number offset (in case of multiple connections) // //useless since SavvyCAN already delivers the right index in frame.bus // QList connList = CANConManager::getInstance()->getConnections(); // int currentConnPos = connList.indexOf(this); // int busOffset = 0; // for (int i = 0; i < currentConnPos; i++) // { // busOffset += connList[i]->getNumBuses(); // } // int busNum = frame.bus - busOffset; int busNum = frame.bus; framesRapid++; if (tcpClient[busNum] && !tcpClient[busNum]->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 |= 1 << 31; QString sendStr = "< send " + QString::number(ID, 16) + " " + QString::number(frame.payload().length()) + " "; for (c = 0; c < frame.payload().length(); c++) { sendStr.append(QString::number(frame.payload()[c], 16) + " "); } sendStr.append(">"); std::string str = sendStr.toStdString(); const char* sendCmd = str.c_str(); sendStringToTCP(sendCmd, busNum); return true; } /****************************************************************/ void SocketCANd::connectDevice() { QSettings settings; /* disconnect device */ /* avoiding this solution since it removes connection selection in case of connection check */ //if(tcpClient) // disconnectDevice(); // only resetting tcpClient for (int i = 0; i < tcpClient.length(); i++) { if (tcpClient[i]) { if (tcpClient[i]->isOpen()) { tcpClient[i]->close(); } tcpClient[i]->disconnect(); delete tcpClient[i]; tcpClient[i] = nullptr; } } tcpClient.clear(); mTimer.start(); //sendDebug("TCP Connection to a Kayak device"); for (int i = 0; i < mNumBuses; i++) { rx_state[i] = IDLE; tcpClient.append(new QTcpSocket()); tcpClient[i]->connectToHost(hostIP, hostPort); //connect(tcpClient[i], SIGNAL(readyRead()), this, SLOT(readTCPData())); connect(tcpClient[i], SIGNAL(readyRead()), this, SLOT(invokeReadTCPData())); sendDebug("Created TCP Socket to Kayak device " + hostCanIDs.at(i)); } setStatus(CANCon::CONNECTED); CANConStatus stats; stats.conStatus = getStatus(); stats.numHardwareBuses = mNumBuses; //signalSender = qobject_cast(QObject::sender()); if (!reconnecting) emit status(stats); } void SocketCANd::deviceConnected(int busNum) { sendDebug("Opening CAN on Kayak Device!"); QString openCanCmd("< open " % hostCanIDs[busNum] % " >"); sendStringToTCP(openCanCmd.toUtf8().data(), busNum); QCoreApplication::processEvents(); } void SocketCANd::checkConnection() { int bufSize = 0; for (int i = 0; i < tcpClient.length(); i++) { bufSize += tcpClient[i]->readBufferSize(); //sendDebug("Buffer size: " + QString::number(bufSize)); } if (bufSize == 0) { reconnecting = true; connectDevice(); sendDebug("Reconnecting to TCP Host " + hostIP.toString()); } } void SocketCANd::switchToRawMode(int busNum) { sendDebug("Switching to rawmode..."); const char* rawmodeCmd = "< rawmode >"; sendStringToTCP(rawmodeCmd, busNum); QCoreApplication::processEvents(); } QString SocketCANd::decodeFrames(QString data, int busNum) { if (data.indexOf("<") == -1) return ""; else if(data.length() >= 8 && data.indexOf("< frame ") == -1) return ""; int firstIndex = data.indexOf("< frame "); if(firstIndex > 0) { QString framePartial = data.left(firstIndex); qDebug() << "Received datagramm that starts with fragment (missing '< frame'), this should only occur on startup, removing...: " << framePartial; } 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) { //qDebug() << "Received datagramm is an incomplete frame: " << data; //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; } buildFrame.setFrameId(frameParsed[1].toUInt(nullptr, 16)); buildFrame.bus = busNum; if (buildFrame.frameId() > 0x7FF) buildFrame.setExtendedFrameFormat(true); else buildFrame.setExtendedFrameFormat(false); buildFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, frameParsed[2].toDouble() * 1000000l)); //buildFrame.len = frameParsed[3].length() * 0.5; 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(); } } //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() { for (int i = 0; i < tcpClient.length(); i++) { if (tcpClient[i]) { if (tcpClient[i]->isOpen()) { tcpClient[i]->close(); } tcpClient[i]->disconnect(); delete tcpClient[i]; tcpClient[i] = nullptr; } } tcpClient.clear(); setStatus(CANCon::NOT_CONNECTED); CANConStatus stats; stats.conStatus = getStatus(); stats.numHardwareBuses = mNumBuses; emit status(stats); } void SocketCANd::invokeReadTCPData() { QTcpSocket* signalSender = qobject_cast(QObject::sender()); int busNum = tcpClient.indexOf(signalSender); QMetaObject::invokeMethod(this, "readTCPData", Qt::QueuedConnection, Q_ARG(int, busNum)); } void SocketCANd::readTCPData(int busNum) { QString data; 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 != "") { mTimer.stop(); mTimer.start(); } switch (rx_state.at(busNum)) { case IDLE: qDebug() << "Received datagramm: " << data; if (data == "< hi >") { deviceConnected(busNum); rx_state[busNum] = BCM; } else qInfo() << hostCanIDs[busNum] << ": Could not open bus. Host did not greet with ""< hi >"": " << data; break; case BCM: qDebug() << "Received datagramm: " << data; if (data == "< ok >") { 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; case SWITCHING2RAW: qDebug() << "Received datagramm: " << data; if (data == "< ok >") { 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: 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; } } savvycan-220/connections/socketcand.h000066400000000000000000000032661500724750100200320ustar00rootroot00000000000000#ifndef SOCKETCAND_H #define SOCKETCAND_H //#include //#include #include #include #include //#include /*************/ #include /*************/ #include "canframemodel.h" #include "canconnection.h" #include "canconmanager.h" namespace KAYAKSTATE { enum MODE { IDLE, //NO_BUS in socketcand doc BCM, SWITCHING2RAW, RAWMODE, ISOTP }; } using namespace KAYAKSTATE; class SocketCANd : public CANConnection { Q_OBJECT public: SocketCANd(QString portName); virtual ~SocketCANd(); 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(); private slots: void connectDevice(); void checkConnection(); void readTCPData(int busNum); void invokeReadTCPData(); void deviceConnected(int busNum); void switchToRawMode(int busNum); QString decodeFrames(QString, int busNum); private: void procRXData(QString, int busNum); void sendBytesToTCP(const QByteArray &bytes, int busNum); void sendStringToTCP(const char* data, int busNum); void sendDebug(const QString debugText); protected: QTimer mTimer; bool reconnecting; QVarLengthArray tcpClient; QHostAddress hostIP; int hostPort; QList hostCanIDs; int framesRapid; QVarLengthArray rx_state; CANFrame buildFrame; QVarLengthArray unprocessedData; }; #endif // SOCKETCAND_H savvycan-220/dbc/000077500000000000000000000000001500724750100137425ustar00rootroot00000000000000savvycan-220/dbc/dbc_classes.cpp000066400000000000000000000421161500724750100167170ustar00rootroot00000000000000#include "dbc_classes.h" #include "dbchandler.h" #include "utility.h" #include DBC_MESSAGE::DBC_MESSAGE() { sigHandler = new DBCSignalHandler; ID = 0; len = 0; multiplexorSignal = nullptr; sender = nullptr; } void DBC_SIGNAL::addMultiplexRange(int min, int max) { if (min != max) hasExtendedMultiplexing = true; isMultiplexed = true; //it is very likely that things that include extended multiplexing will have at least two definitions //which are identical. So, detect this and don't store on subsequent calls if (!multiplexLowAndHighValues.contains(QPair(min, max))) multiplexLowAndHighValues.append(QPair(min, max)); } bool DBC_SIGNAL::isSignalInMessage(const CANFrame &frame) { if (isMultiplexor && !isMultiplexed) return true; //the root multiplexor is always in the message. if (isMultiplexed) { if (parentMessage->multiplexorSignal != nullptr) { if (multiplexParent->isSignalInMessage(frame)) //parent is in message so check if value is correct { int val; if (!multiplexParent->processAsInt(frame, val)) return false; return isValueMatchingMultiplex(val); } else return false; } else return false; } else return true; //if signal isn't multiplexed then it's definitely in the message } bool DBC_SIGNAL::isValueMatchingMultiplex(int val) const { for (auto limitPair : multiplexLowAndHighValues) { if ((limitPair.first <= val) && (val <= limitPair.second)) return true; } return false; } QString DBC_SIGNAL::multiplexDbcString(DbcMuxStringFormat fmt) const { QString ret; for (auto limitPair : multiplexLowAndHighValues) { if (fmt == MuxStringFormat_DbcFile) { if (!ret.isEmpty()) ret.append(QStringLiteral(" ")); ret.append(QString("%1-%2").arg(limitPair.first).arg(limitPair.second)); } else { if (!ret.isEmpty()) ret.append(QStringLiteral(";")); ret.append(QString::number(limitPair.first)); if (limitPair.first != limitPair.second) ret.append(QString("-%1").arg(limitPair.second)); } } return ret; } void DBC_SIGNAL::copyMultiplexValuesFromSignal(const DBC_SIGNAL &signal) { isMultiplexed = signal.isMultiplexed; for (auto multiplexSignalPair : signal.multiplexLowAndHighValues) { multiplexLowAndHighValues.append(multiplexSignalPair); } } /* * multiplexLowAndHighValues is private but the DBC saving code needs to be able to save a reasonable value for the multiplex value * for non-extended multiplex values. So, we grab the first element of the first item which should be the non-extended value */ int DBC_SIGNAL::getSimpleMultiplexValue() { return multiplexLowAndHighValues.at(0).first; } /** * @brief DBC_SIGNAL::parseDbcMultiplexUiString * Method for parsing the Vector CANDB++ like multiplex definitions: * * Single values has to be separated by semicolons * and the value ranges by hypens * (e.g.: 1;2;5-7) * @param multiplexes in the format from the UI * @param errorString output parameter to pass the failure text if parsing failed * @return true if the multiplexes parameter could be parsed properly */ bool DBC_SIGNAL::parseDbcMultiplexUiString(const QString &multiplexes, QString &errorString) { QList> multiplexLowAndHighValuesTemp; const auto muxes = multiplexes.split(QChar(';'), Qt::SkipEmptyParts); for (const auto &mux : muxes) { bool ok = false; if (mux.contains(QChar('-'))) { const auto minMax = mux.split(QChar('-')); if (minMax.count() != 2) { errorString = QObject::tr("The %1 part contains more than one '-' sign").arg(mux); return false; } int min = minMax.at(0).toInt(&ok); if (!ok) { errorString = QObject::tr("Unable to parse the %1 part to integer").arg(minMax.at(0)); return false; } int max = minMax.at(1).toInt(&ok); if (!ok) { errorString = QObject::tr("Unable to parse the %1 part to integer").arg(minMax.at(1)); return false; } multiplexLowAndHighValuesTemp.append(QPair(min, max)); } else { int value = mux.toInt(&ok); if (!ok) { errorString = QObject::tr("Unable to parse the %1 part to integer").arg(mux); return false; } multiplexLowAndHighValuesTemp.append(QPair(value, value)); } } multiplexLowAndHighValues.clear(); multiplexLowAndHighValues = multiplexLowAndHighValuesTemp; return true; } bool DBC_SIGNAL::multiplexesIdenticalToSignal(DBC_SIGNAL *other) const { auto othersMuxes = other->multiplexLowAndHighValues; for (auto myMux : multiplexLowAndHighValues) { bool found = false; for (auto otherMux : othersMuxes) { if (myMux == otherMux) { othersMuxes.removeAll(myMux); found = true; break; } } if (!found) return false; } return othersMuxes.isEmpty(); } //Take all the children of this signal and see if they exist in the message. Can be called recursively to descend the dependency tree QString DBC_SIGNAL::processSignalTree(const CANFrame &frame) { QString build; int val; if (!this->processAsInt(frame, val)) { qDebug() << "Could not process multiplexor as an integer."; return build; } qDebug() << val; foreach (DBC_SIGNAL *sig, multiplexedChildren) { if (sig->isValueMatchingMultiplex(val)) { qDebug() << "Found match for multiplex value range - " << sig->name; QString sigString; if (sig->processAsText(frame, sigString)) { qDebug() << "Returned value: " << sigString; if (!build.isEmpty() && !sigString.isEmpty()) build.append("\n"); build.append(sigString); if (sig->isMultiplexor) { qDebug() << "Spelunkin!"; auto subTreeString = sig->processSignalTree(frame); if (!build.isEmpty() && !subTreeString.isEmpty()) build.append("\n"); build.append(subTreeString); } } } } return build; } /* The way that the DBC file format works is kind of weird... For intel format signals you count up from the start bit to the end bit which is (startbit + signallength - 1). At each point bits are numbered in a sawtooth manner. What that means is that the very first bit is 0 and you count up from there all of the way to 63 with each byte being 8 bits so bit 0 is the lowest bit in the first byte and 8 is the lowest bit in the next byte up. The whole thing looks like this: Bits 7 6 5 4 3 2 1 0 0 7 6 5 4 3 2 1 0 b 1 15 14 13 12 11 10 9 8 y 2 23 22 21 20 19 18 17 16 t 3 31 30 29 28 27 26 25 24 e 4 39 38 37 36 35 34 33 32 s 5 47 46 45 44 43 42 41 40 6 55 54 53 52 51 50 49 48 7 63 62 61 60 59 58 57 56 For intel format you start at the start bit and keep counting up. If you have a signal size of 8 and start at bit 12 then the bits are 12, 13, 14, 15, 16, 17, 18, 19 which spans across two bytes. In this format each bit is worth twice as much as the last and you just keep counting up. Bit 12 is worth 1, 13 is worth 2, 14 is worth 4, etc all of the way to bit 19 is worth 128. Motorola format turns most everything on its head. You count backward from the start bit but only within the current byte. If you are about to exit the current byte you go one higher and then keep going backward as before. Using the same example as for intel, start bit of 12 and a signal length of 8. 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 outputUnit) { int64_t result = 0; bool isSigned = false; bool isInteger = false; double endResult; //if (!isSignalInMessage(frame)) return false; if (valType == STRING) { QString buildString; int startByte = startBit / 8; int bytes = signalSize / 8; for (int x = 0; x < bytes; x++) buildString.append(frame.payload().data()[startByte + x]); outString = buildString; cachedValue = outString; return true; } if (valType == SIGNED_INT) isSigned = true; if (valType == SIGNED_INT || valType == UNSIGNED_INT) { result = Utility::processIntegerSignal(frame.payload(), startBit, signalSize, intelByteOrder, isSigned); endResult = ((double)result * factor) + bias; result = (int64_t)endResult; // if factor is an integer, we don't need the possibly human-unreadable float representation isInteger = (factor == qFloor(factor)); } else if (valType == SP_FLOAT) { //The theory here is that we force the integer signal code to treat this as //a 32 bit unsigned integer. This integer is then cast into a float in such a way //that the bytes that make up the integer are instead treated as having made up //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; //look away! This is awful. I don't even know for sure if it works. Should test that. } else //double precision float { if ( frame.payload().length() < 8 ) { result = 0; return false; } //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(), startBit, 64, intelByteOrder, false); endResult = (*((double *)(&result)) * factor) + bias; } outString = makePrettyOutput(endResult, result, outputName, isInteger, outputUnit); cachedValue = endResult; return true; } bool DBC_SIGNAL::getValueString(int64_t intVal, QString &outString) { if (valList.count() > 0) //if this is a value list type then look it up and display the proper string { for (int x = 0; x < valList.count(); x++) { if (valList.at(x).value == intVal) { outString = valList.at(x).descript; return true; } } } return false; } QString DBC_SIGNAL::makePrettyOutput(double floatVal, int64_t intVal, bool outputName, bool isInteger, bool outputUnit) { QString outputString; if (outputName) outputString = name + ": "; if (valList.count() > 0) //if this is a value list type then look it up and display the proper string { bool foundVal = false; for (int x = 0; x < valList.count(); x++) { if (valList.at(x).value == intVal) { outputString += valList.at(x).descript; foundVal = true; break; } } 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)); if (outputUnit) outputString += " " + unitName; } return outputString; } //Works quite a bit like the above version but this one is cut down and only will return int32_t which is perfect for //uses like calculating a multiplexor value or if you know you are going to get an integer returned //from a signal and you want to use it as-is and not have to convert back from a string. Use with caution though //as this basically assumes the signal is an integer. //The call syntax is different from the more generic processSignal. Instead of returning the value we return //true or false to show whether the function succeeded. The variable to fill out is passed by reference. bool DBC_SIGNAL::processAsInt(const CANFrame &frame, int32_t &outValue) { int32_t result = 0; bool isSigned = false; if (valType == STRING || valType == SP_FLOAT || valType == DP_FLOAT) { return false; } //if (!isSignalInMessage(frame)) return false; if (valType == SIGNED_INT) isSigned = true; /*if ( static_cast(frame.payload().length() * 8) <= (startBit + signalSize) ) { result = 0; return false; }*/ result = static_cast(Utility::processIntegerSignal(frame.payload(), startBit, signalSize, intelByteOrder, isSigned)); double endResult = (result * factor) + bias; result = static_cast(endResult); cachedValue = result; outValue = result; return true; } //Another cut down version that will only return double precision data. This can be used on any of the types //except STRING. Useful for when you know you'll need floating point data and don't want to incur a conversion //back and forth to double or float. Such a use is the graphing window. //Similar syntax to processSignalInt but with double instead. bool DBC_SIGNAL::processAsDouble(const CANFrame &frame, double &outValue) { int64_t result = 0; bool isSigned = false; double endResult; if (valType == STRING) { return false; } //if (!isSignalInMessage(frame)) return false; if (valType == SIGNED_INT) isSigned = true; if (valType == SIGNED_INT || valType == UNSIGNED_INT) { if ( frame.payload().length() * 8 < (startBit+signalSize) ) { result = 0; return false; } result = Utility::processIntegerSignal(frame.payload(), startBit, signalSize, intelByteOrder, isSigned); endResult = ((double)result * factor) + bias; result = (int64_t)endResult; } /*TODO: It should be noted that the below floating point has not even been tested. For shame! Test it!*/ else if (valType == SP_FLOAT) { if ( frame.payload().length() * 8 < (startBit + 32) ) { result = 0; return false; } //The theory here is that we force the integer signal code to treat this as //a 32 bit unsigned integer. This integer is then cast into a float in such a way //that the bytes that make up the integer are instead treated as having made up //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, false, false); endResult = (*((float *)(&result)) * factor) + bias; } else //double precision float { if ( frame.payload().length() < 8 ) { result = 0; return false; } //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(), startBit, 64, false, false); endResult = (*((double *)(&result)) * factor) + bias; } cachedValue = endResult; outValue = endResult; return true; } DBC_ATTRIBUTE_VALUE *DBC_SIGNAL::findAttrValByName(QString name) { if (attributes.length() == 0) return nullptr; for (int i = 0; i < attributes.length(); i++) { if (attributes[i].attrName.compare(name, Qt::CaseInsensitive) == 0) { return &attributes[i]; } } return nullptr; } DBC_ATTRIBUTE_VALUE *DBC_SIGNAL::findAttrValByIdx(int idx) { if (idx < 0) return nullptr; if (idx >= attributes.count()) return nullptr; return &attributes[idx]; } DBC_ATTRIBUTE_VALUE *DBC_MESSAGE::findAttrValByName(QString name) { if (attributes.length() == 0) return nullptr; for (int i = 0; i < attributes.length(); i++) { if (attributes[i].attrName.compare(name, Qt::CaseInsensitive) == 0) { return &attributes[i]; } } return nullptr; } DBC_ATTRIBUTE_VALUE *DBC_MESSAGE::findAttrValByIdx(int idx) { if (idx < 0) return nullptr; if (idx >= attributes.count()) return nullptr; return &attributes[idx]; } DBC_ATTRIBUTE_VALUE *DBC_NODE::findAttrValByName(QString name) { if (attributes.length() == 0) return nullptr; for (int i = 0; i < attributes.length(); i++) { if (attributes[i].attrName.compare(name, Qt::CaseInsensitive) == 0) { return &attributes[i]; } } return nullptr; } DBC_ATTRIBUTE_VALUE *DBC_NODE::findAttrValByIdx(int idx) { if (idx < 0) return nullptr; if (idx >= attributes.count()) return nullptr; return &attributes[idx]; } savvycan-220/dbc/dbc_classes.h000066400000000000000000000114131500724750100163600ustar00rootroot00000000000000#ifndef DBC_CLASSES_H #define DBC_CLASSES_H #include #include #include #include #include "can_structs.h" /*classes to encapsulate data from a DBC file. Really, the stuff of interest are the nodes, messages, signals, attributes, and comments. These things sort of form a hierarchy. Nodes send and receive messages. Messages are comprised of signals. Nodes, signals, and messages potentially have attribute values. All of them can have comments. */ enum DBC_SIG_VAL_TYPE { UNSIGNED_INT, SIGNED_INT, SP_FLOAT, DP_FLOAT, STRING }; enum DBC_ATTRIBUTE_VAL_TYPE { ATTR_INT, ATTR_FLOAT, ATTR_STRING, ATTR_ENUM, }; enum DBC_ATTRIBUTE_TYPE { ATTR_TYPE_GENERAL, ATTR_TYPE_NODE, ATTR_TYPE_MESSAGE, ATTR_TYPE_SIG, ATTR_TYPE_ANY }; class DBC_ATTRIBUTE { public: QString name; DBC_ATTRIBUTE_VAL_TYPE valType; DBC_ATTRIBUTE_TYPE attrType; double upper, lower; QStringList enumVals; QVariant defaultValue; }; class DBC_ATTRIBUTE_VALUE { public: QString attrName; QVariant value; }; class DBC_VAL_ENUM_ENTRY { public: int value; QString descript; }; class DBC_NODE { public: QString name; QString comment; QString sourceFileName; QList attributes; DBC_ATTRIBUTE_VALUE *findAttrValByName(QString name); DBC_ATTRIBUTE_VALUE *findAttrValByIdx(int idx); friend bool operator<(const DBC_NODE& l, const DBC_NODE& r) { return (l.name.toLower() < r.name.toLower()); } }; class DBC_MESSAGE; //forward reference so that DBC_SIGNAL can compile before we get to real definition of DBC_MESSAGE class DBC_SIGNAL; class DBC_SIGNAL { public: //TODO: Clean up this class so that not everything is public. There is one private member which is a start... DBC_SIGNAL() = default; enum DbcMuxStringFormat { MuxStringFormat_DbcFile, MuxStringFormat_UI }; QString name; int startBit = 1; int signalSize = 1; bool intelByteOrder = false; //true is obviously little endian. False is big endian bool isMultiplexor = false; bool isMultiplexed = false; void addMultiplexRange(int min, int max); bool hasExtendedMultiplexing = false; QList multiplexedChildren; DBC_SIGNAL *multiplexParent = nullptr; QString multiplexDbcString(DbcMuxStringFormat fmt = MuxStringFormat_DbcFile) const; void copyMultiplexValuesFromSignal(const DBC_SIGNAL &signal); bool parseDbcMultiplexUiString(const QString &multiplexes, QString &errorString); bool multiplexesIdenticalToSignal(DBC_SIGNAL *other) const; DBC_SIG_VAL_TYPE valType = DBC_SIG_VAL_TYPE::UNSIGNED_INT; double factor = 1.0; double bias = 0; double min = 0; double max = 1; DBC_NODE *receiver = nullptr; //it is fast to have a pointer but dangerous... Make sure to walk the whole tree and delete everything so nobody has stale references. DBC_MESSAGE *parentMessage = nullptr; QString unitName; QString comment; QVariant cachedValue; QList attributes; QList valList; DBC_SIGNAL *self; 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, bool outputUnit = true); QString processSignalTree(const CANFrame &frame); DBC_ATTRIBUTE_VALUE *findAttrValByName(QString name); DBC_ATTRIBUTE_VALUE *findAttrValByIdx(int idx); bool isSignalInMessage(const CANFrame &frame); bool isValueMatchingMultiplex(int val) const; int getSimpleMultiplexValue(); friend bool operator<(const DBC_SIGNAL& l, const DBC_SIGNAL& r) { return (l.name.toLower() < r.name.toLower()); } private: QList> multiplexLowAndHighValues; }; class DBCSignalHandler; //forward declaration to keep from having to include dbchandler.h in this file and thus create a loop class DBC_MESSAGE { public: DBC_MESSAGE(); uint32_t ID; bool extendedID; QString name; QString comment; unsigned int len; DBC_NODE *sender; QColor bgColor; QColor fgColor; QList attributes; DBCSignalHandler *sigHandler; DBC_SIGNAL* multiplexorSignal; DBC_ATTRIBUTE_VALUE *findAttrValByName(QString name); DBC_ATTRIBUTE_VALUE *findAttrValByIdx(int idx); friend bool operator<(const DBC_MESSAGE& l, const DBC_MESSAGE& r) { return (l.name.toLower() < r.name.toLower()); } }; #endif // DBC_CLASSES_H savvycan-220/dbc/dbchandler.cpp000066400000000000000000002426411500724750100165450ustar00rootroot00000000000000#include "dbchandler.h" #include #include #include #include #include #include #include #include #include #include #include #include "utility.h" #include "connections/canconmanager.h" DBCHandler* DBCHandler::instance = nullptr; DBC_SIGNAL* DBCSignalHandler::findSignalByIdx(int idx) { if (sigs.count() == 0) return nullptr; if (idx < 0) return nullptr; if (idx >= sigs.count()) return nullptr; return &sigs[idx]; } DBC_SIGNAL* DBCSignalHandler::findSignalByName(QString name) { if (sigs.count() == 0) return nullptr; for (int i = 0; i < sigs.count(); i++) { if (sigs[i].name.compare(name, Qt::CaseInsensitive) == 0) { return &sigs[i]; } } return nullptr; } bool DBCSignalHandler::addSignal(DBC_SIGNAL &sig) { sigs.append(sig); return true; } bool DBCSignalHandler::removeSignal(DBC_SIGNAL *sig) { qDebug() << "Total # of signals: " << getCount(); for (int i = 0; i < getCount(); i++) { if (sigs[i].name == sig->name) { sigs.removeAt(i); qDebug() << "Removed signal at idx " << i; } } return true; } bool DBCSignalHandler::removeSignal(int idx) { if (sigs.count() == 0) return false; if (idx < 0) return false; if (idx >= sigs.count()) return false; sigs.removeAt(idx); return true; } bool DBCSignalHandler::removeSignal(QString name) { bool foundSome = false; if (sigs.count() == 0) return false; for (int i = sigs.count() - 1; i >= 0; i--) { if (sigs[i].name.compare(name, Qt::CaseInsensitive) == 0) { sigs.removeAt(i); foundSome = true; } } return foundSome; } void DBCSignalHandler::removeAllSignals() { sigs.clear(); } int DBCSignalHandler::getCount() { return sigs.count(); } void DBCSignalHandler::sort() { std::sort(sigs.begin(), sigs.end()); } 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 uint32_t pgn = (id & 0x3FFFF00) >> 8; if ( (pgn & 0xFF00) <= 0xEF00 ) { // PDU1 format pgn &= 0x3FF00; if ((messages[i].ID & 0x3FF0000) == (pgn << 8)) { bestMatch = &messages[i]; } } else { // PDU2 format if ((messages[i].ID & 0x3FFFF00) == (pgn << 8)) { bestMatch = &messages[i]; } } } else if (matchingCriteria == GMLAN) { // Match the bits 14-26 (Arbitration Id) of GMLAN 29bit header uint32_t arbId = id &0x3FFE000; if ( (arbId != 0) && (messages[i].ID & 0x3FFE000) == arbId ) bestMatch = &messages[i]; } } return bestMatch; } DBC_MESSAGE* DBCMessageHandler::findMsgByIdx(int idx) { if (messages.count() == 0) return nullptr; if (idx < 0) return nullptr; if (idx >= messages.count()) return nullptr; return &messages[idx]; } DBC_MESSAGE* DBCMessageHandler::findMsgByName(QString name) { if (messages.count() == 0) return nullptr; for (int i = 0; i < messages.count(); i++) { if (messages[i].name.compare(name, Qt::CaseInsensitive) == 0) { return &messages[i]; } } return nullptr; } //allow for finding a message just by part of the name DBC_MESSAGE* DBCMessageHandler::findMsgByPartialName(QString name) { if (messages.count() == 0) return nullptr; for (int i = 0; i < messages.count(); i++) { if (messages[i].name.contains(name, Qt::CaseInsensitive)) { return &messages[i]; } } 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); return true; } bool DBCMessageHandler::removeMessage(DBC_MESSAGE *msg) { qDebug() << "Total # of messages: " << getCount(); for (int i = 0; i < getCount(); i++) { if (messages[i].name == msg->name) { messages.removeAt(i); qDebug() << "Removed message at idx " << i; break; } } return true; } bool DBCMessageHandler::removeMessageByIndex(int idx) { if (messages.count() == 0) return false; if (idx < 0) return false; if (idx >= messages.count()) return false; messages.removeAt(idx); return true; } bool DBCMessageHandler::removeMessage(uint32_t ID) { bool foundSome = false; if (messages.count() == 0) return false; for (int i = messages.count() - 1; i >= 0; i--) { if (messages[i].ID == ID) { messages.removeAt(i); foundSome = true; } } return foundSome; } bool DBCMessageHandler::removeMessage(QString name) { bool foundSome = false; if (messages.count() == 0) return false; for (int i = messages.count() - 1; i >= 0; i--) { if (messages[i].name.compare(name, Qt::CaseInsensitive) == 0) { messages.removeAt(i); foundSome = true; } } return foundSome; } void DBCMessageHandler::removeAllMessages() { messages.clear(); } int DBCMessageHandler::getCount() { return messages.count(); } void DBCMessageHandler::sort() { std::sort(messages.begin(), messages.end()); for (int i = 0; i < messages.count(); i++) { messages[i].sigHandler->sort(); } } bool DBCMessageHandler::filterLabeling() { return filterLabelingEnabled; } void DBCMessageHandler::setFilterLabeling(bool filterLabeling) { filterLabelingEnabled = filterLabeling; } MatchingCriteria_t DBCMessageHandler::getMatchingCriteria() { return matchingCriteria; } void DBCMessageHandler::setMatchingCriteria(MatchingCriteria_t _matchingCriteria) { matchingCriteria = _matchingCriteria; } DBCFile::DBCFile() { messageHandler = new DBCMessageHandler; messageHandler->setMatchingCriteria(EXACT); messageHandler->setFilterLabeling(false); isDirty = false; fileName = ""; } DBCFile::DBCFile(const DBCFile& cpy) : QObject() { messageHandler = new DBCMessageHandler; for (int i = 0 ; i < cpy.messageHandler->getCount() ; i++) messageHandler->addMessage(*cpy.messageHandler->findMsgByIdx(i)); messageHandler->setMatchingCriteria(cpy.messageHandler->getMatchingCriteria()); messageHandler->setFilterLabeling(cpy.messageHandler->filterLabeling()); fileName = cpy.fileName; filePath = cpy.filePath; assocBuses = cpy.assocBuses; dbc_nodes.clear(); dbc_nodes.append(cpy.dbc_nodes); dbc_attributes.clear(); dbc_attributes.append(cpy.dbc_attributes); isDirty = cpy.isDirty; } DBCFile& DBCFile::operator=(const DBCFile& cpy) { if (this != &cpy) // protect against invalid self-assignment { messageHandler = cpy.messageHandler; fileName = cpy.fileName; filePath = cpy.filePath; assocBuses = cpy.assocBuses; dbc_nodes.clear(); dbc_nodes.append(cpy.dbc_nodes); dbc_attributes.clear(); dbc_attributes.append(cpy.dbc_attributes); } return *this; } void DBCFile::sort() { std::sort(dbc_nodes.begin(), dbc_nodes.end()); //sort node names messageHandler->sort(); //sort messages, each of which sorts its signals too } DBC_NODE* DBCFile::findNodeByIdx(int idx) { if (idx < 0) return nullptr; if (idx >= dbc_nodes.count()) return nullptr; return &dbc_nodes[idx]; } DBC_NODE* DBCFile::findNodeByName(QString name) { if (dbc_nodes.length() == 0) return nullptr; for (int i = 0; i < dbc_nodes.length(); i++) { 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]; } } return nullptr; } QString DBCFile::getFullFilename() { return filePath + fileName; } QString DBCFile::getFilename() { return fileName; } QString DBCFile::getFilenameNoExt() { return fileName.split(".dbc")[0]; } QString DBCFile::getPath() { return filePath; } int DBCFile::getAssocBus() { return assocBuses; } void DBCFile::setAssocBus(int bus) { if (bus < -1) return; // To allow setting bus numbers even before connection is configured, do not enforce "valid" bus numbers //int numBuses = CANConManager::getInstance()->getNumBuses(); //if (bus >= numBuses) return; assocBuses = bus; } DBC_ATTRIBUTE *DBCFile::findAttributeByName(QString name, DBC_ATTRIBUTE_TYPE type) { if (dbc_attributes.length() == 0) return nullptr; for (int i = 0; i < dbc_attributes.length(); i++) { if (dbc_attributes[i].name.compare(name, Qt::CaseInsensitive) == 0 && ((type == dbc_attributes[i].attrType) || (type == ATTR_TYPE_ANY) ) ) { return &dbc_attributes[i]; } } return nullptr; } DBC_ATTRIBUTE *DBCFile::findAttributeByIdx(int idx) { if (idx < 0) return nullptr; if (idx >= dbc_attributes.count()) return nullptr; return &dbc_attributes[idx]; } void DBCFile::findAttributesByType(DBC_ATTRIBUTE_TYPE typ, QList *list) { if (!list) return; list->clear(); foreach (DBC_ATTRIBUTE attr, dbc_attributes) { if (attr.attrType == typ) list->append(attr); } } 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; } DBC_MESSAGE* DBCFile::parseMessageLine(QString line) { QRegularExpression regex; QRegularExpressionMatch match; DBC_MESSAGE *msgPtr; qDebug() << "Found a BO line"; regex.setPattern("^BO\\_ (\\w+) ([-\\w]+) *: (\\w+) ([-\\w]+)"); match = regex.match(line); //captured 1 = the ID in decimal //captured 2 = The message name //captured 3 = the message length //captured 4 = the NODE responsible for this message if (match.hasMatch()) { 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 & 0x80000000ul) ? true : false; msg.name = match.captured(2); msg.len = match.captured(3).toUInt(); msg.sender = findNodeByName(match.captured(4)); if (!msg.sender) msg.sender = findNodeByIdx(0); messageHandler->addMessage(msg); msgPtr = messageHandler->findMsgByID(msg.ID); } else msgPtr = nullptr; return msgPtr; } DBC_SIGNAL* DBCFile::parseSignalLine(QString line, DBC_MESSAGE *msg) { QRegularExpression regex; QRegularExpressionMatch match; int offset = 0; bool isMessageMultiplexor = false; //bool isMultiplexed = false; DBC_SIGNAL sig; 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]+)\\] \\\"(.*)\\\" (.*)"); match = regex.match(line); if (match.hasMatch()) { qDebug() << "Multiplexor signal"; isMessageMultiplexor = true; sig.isMultiplexor = true; } else { 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()) { qDebug() << "Multiplexed signal"; sig.addMultiplexRange(match.captured(2).toInt(), match.captured(2).toInt()); offset = 1; } else { 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()) { qDebug() << "Extended Multiplexor Signal"; sig.isMultiplexor = true; //we don't set the local isMessageMultiplexor variable because this isn't the top level multiplexor sig.addMultiplexRange(match.captured(2).toInt(), match.captured(2).toInt()); offset = 1; } else { qDebug() << "standard signal"; 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; } } } //captured 1 is the signal name //captured 2 would be multiplex value if this is a multiplex signal. Then offset the rest of these by 1 //captured 2 is the starting bit //captured 3 is the length in bits //captured 4 is the byte order / value type //captured 5 specifies signed/unsigned for ints //captured 6 is the scaling factor //captured 7 is the offset //captured 8 is the minimum value //captured 9 is the maximum value //captured 10 is the unit //captured 11 is the receiving node if (match.hasMatch()) { sig.name = match.captured(1); sig.startBit = match.captured(2 + offset).toInt(); sig.signalSize = match.captured(3 + offset).toInt(); int val = match.captured(4 + offset).toInt(); if (val < 2) { if (match.captured(5 + offset) == "+") sig.valType = UNSIGNED_INT; else sig.valType = SIGNED_INT; } switch (val) { case 0: //big endian mode sig.intelByteOrder = false; break; case 1: //little endian mode sig.intelByteOrder = true; break; case 2: sig.valType = SP_FLOAT; break; case 3: sig.valType = DP_FLOAT; break; case 4: sig.valType = STRING; break; case 5: //single point float in little endian sig.valType = SP_FLOAT; sig.intelByteOrder = true; break; case 6: //double point float in little endian sig.valType = DP_FLOAT; sig.intelByteOrder = true; break; } sig.factor = match.captured(6 + offset).toDouble(); sig.bias = match.captured(7 + offset).toDouble(); sig.min = match.captured(8 + offset).toDouble(); sig.max = match.captured(9 + offset).toDouble(); sig.unitName = match.captured(10 + offset); if (match.captured(11 + offset).contains(',')) { QString tmp = match.captured(11 + offset).split(',')[0]; sig.receiver = findNodeByName(tmp); } else sig.receiver = findNodeByName(match.captured(11 + offset)); if (!sig.receiver) sig.receiver = findNodeByIdx(0); //apply default if there was no match sig.parentMessage = msg; if (msg) { msg->sigHandler->addSignal(sig); if (isMessageMultiplexor) msg->multiplexorSignal = msg->sigHandler->findSignalByName(sig.name); return msg->sigHandler->findSignalByName(sig.name); } else return nullptr; } return nullptr; } //SG_MUL_VAL_ 2024 S1_PID_0D_VehicleSpeed S1 13-13; bool DBCFile::parseSignalMultiplexValueLine(QString line) { QRegularExpression regex; QRegularExpressionMatch match; qDebug() << "Found a multiplex definition line"; regex.setPattern("^SG\\_MUL\\_VAL\\_ (\\d+) ([-\\w]+) ([-\\w]+) ((?:\\d+\\-\\d+[\\s]*[\\,]?[\\s]*)*)"); match = regex.match(line); //captured 1 is message ID //Captured 2 is signal name //Captured 3 is parent multiplexor //captured 4 is the lower bound //captured 5 is the upper bound if (match.hasMatch()) { DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toULong() & 0x1FFFFFFFUL); if (msg != nullptr) { DBC_SIGNAL *thisSignal = msg->sigHandler->findSignalByName(match.captured(2)); if (thisSignal != nullptr) { DBC_SIGNAL *parentSignal = msg->sigHandler->findSignalByName(match.captured(3)); if (parentSignal != nullptr) { const QStringList ranges = match.captured(4).split(QChar(','), Qt::SkipEmptyParts); for (const QString &range : ranges) { //now need to add "thisSignal" to the children multiplexed signals of "parentSignal" const QStringList rangeSides = range.trimmed().split(QChar('-')); if (rangeSides.count() != 2) { qDebug() << QString("Malformed range definition: '%2' found in the multiplexed signal: %1") .arg(match.captured(1).arg(range.trimmed())); return false; } int rangeMin = rangeSides.at(0).toInt(); int rangeMax = rangeSides.at(1).toInt(); thisSignal->multiplexParent = parentSignal; thisSignal->addMultiplexRange(rangeMin, rangeMax); } parentSignal->multiplexedChildren.append(thisSignal); return true; } } } } 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]+) (.*);"); match = regex.match(line); //captured 1 is the ID to match against //captured 2 is the signal name to match against //captured 3 is a series of values in the form (number "text") that is, all sep'd by spaces if (match.hasMatch()) { //qDebug() << "Data was: " << match.captured(3); DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toULong() & 0x1FFFFFFFul); if (msg != nullptr) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(match.captured(2)); if (sig != nullptr) { QString tokenString = match.captured(3); DBC_VAL_ENUM_ENTRY val; while (tokenString.length() > 2) { regex.setPattern("(\\d+) \\\"(.*?)\\\"(.*)"); match = regex.match(tokenString); if (match.hasMatch()) { val.value = match.captured(1).toULong() & 0x1FFFFFFFul; val.descript = match.captured(2); //qDebug() << "sig val " << val.value << " desc " <valList.append(val); int rightSize = tokenString.length() - match.captured(1).length() - match.captured(2).length() - 4; if (rightSize > 0) tokenString = tokenString.right(rightSize); else tokenString = ""; //qDebug() << "New token string: " << tokenString; } else tokenString = ""; } return true; } } } return false; } bool DBCFile::parseAttributeLine(QString line) { QRegularExpression regex; QRegularExpressionMatch match; 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) //captured 3 is the attribute value if (match.hasMatch()) { qDebug() << "Found an attribute setting line for a message"; DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1)); if (foundAttr) { qDebug() << "That message attribute does exist"; DBC_MESSAGE *foundMsg = messageHandler->findMsgByID(match.captured(2).toULong() & 0x1FFFFFFFul); if (foundMsg) { qDebug() << "It references a valid, registered message"; DBC_ATTRIBUTE_VALUE *foundAttrVal = foundMsg->findAttrValByName(match.captured(1)); if (foundAttrVal) { foundAttrVal->value = processAttributeVal(match.captured(3), foundAttr->valType); } else { DBC_ATTRIBUTE_VALUE val; val.attrName = match.captured(1); val.value = processAttributeVal(match.captured(3), foundAttr->valType); foundMsg->attributes.append(val); } } } } 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) //captured 3 is the signal name to bind to //captured 4 is the attribute value if (match.hasMatch()) { qDebug() << "Found an attribute setting line for a signal"; DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1)); if (foundAttr) { qDebug() << "That signal attribute does exist"; DBC_MESSAGE *foundMsg = messageHandler->findMsgByID(match.captured(2).toULong() & 0x1FFFFFFFUL); if (foundMsg) { qDebug() << "It references a valid, registered message"; DBC_SIGNAL *foundSig = foundMsg->sigHandler->findSignalByName(match.captured(3)); if (foundSig) { DBC_ATTRIBUTE_VALUE *foundAttrVal = foundSig->findAttrValByName(match.captured(1)); if (foundAttrVal) foundAttrVal->value = processAttributeVal(match.captured(3), foundAttr->valType); else { DBC_ATTRIBUTE_VALUE val; val.attrName = match.captured(1); val.value = processAttributeVal(match.captured(3), foundAttr->valType); foundSig->attributes.append(val); } } } } } 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 //captured 3 is the attribute value if (match.hasMatch()) { qDebug() << "Found an attribute setting line for a node"; DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1)); if (foundAttr) { qDebug() << "That node attribute does exist"; DBC_NODE *foundNode = findNodeByName(match.captured(2)); if (foundNode) { qDebug() << "References a valid node name"; DBC_ATTRIBUTE_VALUE *foundAttrVal = foundNode->findAttrValByName(match.captured(1)); if (foundAttrVal) foundAttrVal->value = processAttributeVal(match.captured(3), foundAttr->valType); else { DBC_ATTRIBUTE_VALUE val; val.attrName = match.captured(1); val.value = processAttributeVal(match.captured(3), foundAttr->valType); foundNode->attributes.append(val); } } return true; } } return false; } bool DBCFile::parseDefaultAttrLine(QString line) { QRegularExpression regex; QRegularExpressionMatch match; 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 if (match.hasMatch()) { qDebug() << "Found an attribute default value line, searching for an attribute named " << match.captured(1) << "with data " << match.captured(2); DBC_ATTRIBUTE *found = findAttributeByName(match.captured(1)); if (found) { switch (found->valType) { case ATTR_STRING: found->defaultValue = match.captured(2); break; case ATTR_FLOAT: found->defaultValue = match.captured(2).toFloat(); break; case ATTR_INT: found->defaultValue = match.captured(2).toInt(); break; case ATTR_ENUM: QString temp = match.captured(2); found->defaultValue = 0; for (int x = 0; x < found->enumVals.count(); x++) { if (!found->enumVals[x].compare(temp, Qt::CaseInsensitive)) { found->defaultValue = x; break; } } } qDebug() << "Matched an attribute. Setting default value to " << found->defaultValue; return true; } } return false; } bool DBCFile::loadFile(QString fileName) { QFile *inFile = new QFile(fileName); QString line, rawLine; QRegularExpression regex; QRegularExpressionMatch match; DBC_MESSAGE *currentMessage = nullptr; DBC_ATTRIBUTE attr; int numSigFaults = 0, numMsgFaults = 0; int linesSinceYield = 0; QString fileBaseName = QFileInfo(fileName).baseName(); bool inMultilineBU = false; qDebug() << "DBC File: " << fileName; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; qDebug() << "Could not load the file!"; return false; } qDebug() << "Starting DBC load"; dbc_nodes.clear(); messageHandler->removeAllMessages(); messageHandler->setMatchingCriteria(EXACT); messageHandler->setFilterLabeling(false); DBC_NODE falseNode; falseNode.name = "Vector__XXX"; falseNode.comment = "Default node if none specified"; dbc_nodes.append(falseNode); while (!inFile->atEnd()) { rawLine = QString(inFile->readLine()); line = rawLine.simplified(); linesSinceYield++; if (linesSinceYield > 100) { qApp->processEvents(); } if (inMultilineBU) { if (rawLine.startsWith("\t") || rawLine.startsWith(" ")) { DBC_NODE node; node.sourceFileName = fileBaseName; node.name = line; dbc_nodes.append(node); } else inMultilineBU = false; } if (!inMultilineBU) { if (line.startsWith("BO_ ")) //defines a message { currentMessage = parseMessageLine(line); if (currentMessage == nullptr) numMsgFaults++; } if (line.startsWith("SG_ ")) //defines a signal { if (!parseSignalLine(line, currentMessage)) numSigFaults++; } if (line.startsWith("SG_MUL_VAL_ ")) //defines a signal multiplexing value definition { 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"; regex.setPattern("^BU\\_\\:(.*)"); match = regex.match(line); //captured 1 = a list of node names separated by spaces. No idea how many yet if (match.hasMatch()) { QStringList nodeStrings = match.captured(1).split(' '); qDebug() << "Found " << nodeStrings.count() << " node names"; for (int i = 0; i < nodeStrings.count(); i++) { //qDebug() << nodeStrings[i]; if (nodeStrings[i].length() > 1) { DBC_NODE node; node.sourceFileName = fileBaseName; node.name = nodeStrings[i]; dbc_nodes.append(node); } } inMultilineBU = true; //we might be... Need to check next line. } } if (line.startsWith("CM_ SG_ ")) { qDebug() << "Found an SG comment line"; 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 //captured 3 is the comment itself if (match.hasMatch()) { //qDebug() << "Comment was: " << match.captured(3); DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toULong() & 0x1FFFFFFFul); if (msg != nullptr) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(match.captured(2)); if (sig != nullptr) { sig->comment = match.captured(3); } } } } if (line.startsWith("CM_ BO_ ")) { qDebug() << "Found a BO comment line"; regex.setPattern("^CM\\_ BO\\_ *(\\w+) *\\\"(.*)\\\";"); match = regex.match(line); //captured 1 is the ID to match against to get to the message //captured 2 is the comment itself if (match.hasMatch()) { //qDebug() << "Comment was: " << match.captured(2); DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toULong() & 0x1FFFFFFFul); if (msg != nullptr) { msg->comment = match.captured(2); } } } if (line.startsWith("CM_ BU_ ")) { qDebug() << "Found a BU comment line"; regex.setPattern("^CM\\_ BU\\_ *([-\\w]+) *\\\"(.*)\\\";"); match = regex.match(line); //captured 1 is the Node name //captured 2 is the comment itself if (match.hasMatch()) { //qDebug() << "Comment was: " << match.captured(2); DBC_NODE *node = findNodeByName(match.captured(1)); if (node != nullptr) { node->comment = match.captured(2); } } } //VAL_ (1090) (VCUPresentParkLightOC) (1 "Error present" 0 "Error not present") ; if (line.startsWith("VAL_ ")) { parseValueLine(line); } if (line.startsWith("BA_DEF_ SG_ ")) { qDebug() << "Found a SG attribute line"; if (parseAttribute(line.right(line.length() - 12), attr)) { //qDebug() << "Success"; attr.attrType = ATTR_TYPE_SIG; dbc_attributes.append(attr); } } else if (line.startsWith("BA_DEF_ BO_ ")) //definition of a message attribute { qDebug() << "Found a BO attribute line"; if (parseAttribute(line.right(line.length() - 12), attr)) { qDebug() << "Success"; attr.attrType = ATTR_TYPE_MESSAGE; dbc_attributes.append(attr); } } else if (line.startsWith("BA_DEF_ BU_ ")) //definition of a node attribute { qDebug() << "Found a BU attribute line"; if (parseAttribute(line.right(line.length() - 12), attr)) { //qDebug() << "Success"; attr.attrType = ATTR_TYPE_NODE; dbc_attributes.append(attr); } } else if (line.startsWith("BA_DEF_ ")) //definition of a root attribute { qDebug() << "Found a BU attribute line"; if (parseAttribute(line.right(line.length() - 9), attr)) { //qDebug() << "Success"; attr.attrType = ATTR_TYPE_GENERAL; dbc_attributes.append(attr); } } if (line.startsWith("BA_DEF_DEF_ ")) //definition of default value for an attribute { parseDefaultAttrLine(line); } //BA_ "GenMsgCycleTime" BO_ 101 100; if (line.startsWith("BA_ ")) //set value of attribute { parseAttributeLine(line); } } } //upon loading the file add our custom foreground and background color attributes if they don't exist already DBC_ATTRIBUTE *bgAttr = findAttributeByName("GenMsgBackgroundColor"); if (!bgAttr) { attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = QApplication::palette().color(QPalette::Base).name(); attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "GenMsgBackgroundColor"; attr.valType = ATTR_STRING; dbc_attributes.append(attr); bgAttr = findAttributeByName("GenMsgBackgroundColor"); } DBC_ATTRIBUTE *fgAttr = findAttributeByName("GenMsgForegroundColor"); if (!fgAttr) { attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = QApplication::palette().color(QPalette::WindowText).name(); attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "GenMsgForegroundColor"; attr.valType = ATTR_STRING; dbc_attributes.append(attr); fgAttr = findAttributeByName("GenMsgForegroundColor"); } DBC_ATTRIBUTE *mc_attr = findAttributeByName("matchingcriteria"); if (mc_attr) { messageHandler->setMatchingCriteria((MatchingCriteria_t)mc_attr->defaultValue.toInt()); } else { messageHandler->setMatchingCriteria(EXACT); } DBC_ATTRIBUTE *fl_attr = findAttributeByName("filterlabeling"); if (fl_attr) { messageHandler->setFilterLabeling(fl_attr->defaultValue.toInt()); } else { messageHandler->setFilterLabeling(false); } QColor DefaultBG = QColor(bgAttr->defaultValue.toString()); QColor DefaultFG = QColor(fgAttr->defaultValue.toString()); DBC_ATTRIBUTE_VALUE *thisBG; DBC_ATTRIBUTE_VALUE *thisFG; for (int x = 0; x < messageHandler->getCount(); x++) { DBC_MESSAGE *msg = messageHandler->findMsgByIdx(x); msg->bgColor = DefaultBG; msg->fgColor = DefaultFG; thisBG = msg->findAttrValByName("GenMsgBackgroundColor"); thisFG = msg->findAttrValByName("GenMsgForegroundColor"); if (thisBG) msg->bgColor = QColor(thisBG->value.toString()); if (thisFG) msg->fgColor = QColor(thisFG->value.toString()); for (int y = 0; y < msg->sigHandler->getCount(); y++) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(y); //if this doesn't have a multiplex parent set but is multiplexed then it must have used //simple multiplexing instead of any extended specification. So, fill in the multiplexor signal here //and also write the extended entry for it too. if (sig->isMultiplexed && (sig->multiplexParent == nullptr) && (msg->multiplexorSignal) ) { sig->multiplexParent = msg->multiplexorSignal; msg->multiplexorSignal->multiplexedChildren.append(sig); } if ( sig->isMultiplexed && (!msg->multiplexorSignal) ) //marked multiplexed but there is no multiplexor. { sig->isMultiplexed = false; //can't multiplex if there is no multiplexor! } } } if (numSigFaults > 0 || numMsgFaults > 0) { QMessageBox msgBox; QString msg = "DBC file loaded with errors!\n"; msg += "Number of faulty message entries: " + QString::number(numMsgFaults) + "\n"; msg += "Number of faulty signal entries: " + QString::number(numSigFaults) + "\n\n"; msg += "Faulty entries have not been loaded.\n\n"; msg += "All other entries are, however, loaded."; msgBox.setText(msg); msgBox.exec(); } inFile->close(); delete inFile; QStringList fileList = fileName.split('/'); this->fileName = fileList[fileList.length() - 1]; //whoops... same name as parameter in this function. filePath = fileName.left(fileName.length() - this->fileName.length()); assocBuses = -1; isDirty = false; return true; } QVariant DBCFile::processAttributeVal(QString input, DBC_ATTRIBUTE_VAL_TYPE typ) { QVariant out; switch (typ) { case ATTR_STRING: out = input; break; case ATTR_FLOAT: out = input.toFloat(); break; case ATTR_INT: case ATTR_ENUM: out = input.toInt(); break; } return out; } bool DBCFile::parseAttribute(QString inpString, DBC_ATTRIBUTE &attr) { bool goodAttr = false; QRegularExpression regex; QRegularExpressionMatch match; regex.setPattern("\\\"*(\\w+)\\\"* \\\"*(\\w+)\\\"* (\\d+) (\\d+)"); match = regex.match(inpString); //captured 1 is the name of the attribute to set up //captured 2 is the type of signal attribute to create. //captured 3 is the lower bound value for this attribute //captured 4 is the upper bound value for this attribute if (match.hasMatch()) { qDebug() << "Parsing an attribute with low/high values"; attr.name = match.captured(1); QString typ = match.captured(2); attr.attrType = ATTR_TYPE_SIG; attr.lower = 0; attr.upper = 0; attr.valType = ATTR_STRING; if (!typ.compare("INT", Qt::CaseInsensitive)) { qDebug() << "INT attribute named " << attr.name; attr.valType = ATTR_INT; attr.lower = match.captured(3).toInt(); attr.upper = match.captured(4).toInt(); goodAttr = true; } if (!typ.compare("FLOAT", Qt::CaseInsensitive)) { qDebug() << "FLOAT attribute named " << attr.name; attr.valType = ATTR_FLOAT; attr.lower = match.captured(3).toDouble(); attr.upper = match.captured(4).toDouble(); goodAttr = true; } if (!typ.compare("STRING", Qt::CaseInsensitive)) { qDebug() << "STRING attribute named " << attr.name; attr.valType = ATTR_STRING; goodAttr = true; } } else { regex.setPattern("\\\"*(\\w+)\\\"* \\\"*(\\w+)\\\"* (.*)"); match = regex.match(inpString); //Same as above but no upper/lower bound values. if (match.hasMatch()) { qDebug() << "Parsing an attribute without boundaries"; attr.name = match.captured(1); QString typ = match.captured(2); attr.lower = 0; attr.upper = 0; attr.attrType = ATTR_TYPE_SIG; if (!typ.compare("INT", Qt::CaseInsensitive)) { qDebug() << "INT attribute named " << attr.name; attr.valType = ATTR_INT; goodAttr = true; } if (!typ.compare("FLOAT", Qt::CaseInsensitive)) { qDebug() << "FLOAT attribute named " << attr.name; attr.valType = ATTR_FLOAT; goodAttr = true; } if (!typ.compare("STRING", Qt::CaseInsensitive)) { qDebug() << "STRING attribute named " << attr.name; attr.valType = ATTR_STRING; goodAttr = true; } if (!typ.compare("ENUM", Qt::CaseInsensitive)) { qDebug() << "ENUM attribute named " << attr.name; QStringList enumLst = match.captured(3).split(','); foreach (QString enumStr, enumLst) { attr.enumVals.append(Utility::unQuote(enumStr)); qDebug() << "Enum value: " << enumStr; } attr.valType = ATTR_ENUM; goodAttr = true; } } } return goodAttr; } bool DBCFile::saveFile(QString fileName) { int nodeNumber = 1; int msgNumber = 1; int sigNumber = 1; QFile *outFile = new QFile(fileName); QString nodesOutput, msgOutput, commentsOutput, valuesOutput, extMultiplexOutput; QString defaultsOutput, attrValOutput; bool hasExtendedMultiplexing = false; if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) { delete outFile; return false; } //right now it outputs a standard hard coded boilerplate outFile->write("VERSION \"\"\n"); outFile->write("\n"); outFile->write("\n"); outFile->write("NS_ :\n"); outFile->write(" NS_DESC_\n"); outFile->write(" CM_\n"); outFile->write(" BA_DEF_\n"); outFile->write(" BA_\n"); outFile->write(" VAL_\n"); outFile->write(" CAT_DEF_\n"); outFile->write(" CAT_\n"); outFile->write(" FILTER\n"); outFile->write(" BA_DEF_DEF_\n"); outFile->write(" EV_DATA_\n"); outFile->write(" ENVVAR_DATA_\n"); outFile->write(" SGTYPE_\n"); outFile->write(" SGTYPE_VAL_\n"); outFile->write(" BA_DEF_SGTYPE_\n"); outFile->write(" BA_SGTYPE_\n"); outFile->write(" SIG_TYPE_REF_\n"); outFile->write(" VAL_TABLE_\n"); outFile->write(" SIG_GROUP_\n"); outFile->write(" SIG_VALTYPE_\n"); outFile->write(" SIGTYPE_VALTYPE_\n"); outFile->write(" BO_TX_BU_\n"); outFile->write(" BA_DEF_REL_\n"); outFile->write(" BA_REL_\n"); outFile->write(" BA_DEF_DEF_REL_\n"); outFile->write(" BU_SG_REL_\n"); outFile->write(" BU_EV_REL_\n"); outFile->write(" BU_BO_REL_\n"); outFile->write(" SG_MUL_VAL_\n"); outFile->write("\n"); outFile->write("BS_: \n"); //Build list of nodes line nodesOutput.append("BU_: "); for (int x = 0; x < dbc_nodes.count(); x++) { DBC_NODE node = dbc_nodes[x]; if (node.name.compare("Vector__XXX", Qt::CaseInsensitive) != 0) { if (node.name.length() < 1) //detect an empty string and fill it out with something { node.name = "NODE" + QString::number(nodeNumber); nodeNumber++; } nodesOutput.append(node.name + " "); if (node.comment.length() > 0) { commentsOutput.append("CM_ BU_ " + node.name + " \"" + node.comment + "\";\n"); } if (node.attributes.count() > 0) { foreach (DBC_ATTRIBUTE_VALUE val, node.attributes) { attrValOutput.append("BA_ \"" + val.attrName + "\" BU_ "); switch (val.value.type()) { case QVariant::Type::String: attrValOutput.append("\"" + val.value.toString() + "\";\n"); break; default: attrValOutput.append(val.value.toString() + ";\n"); break; } } } } } nodesOutput.append("\n"); outFile->write(nodesOutput.toUtf8()); //Go through all messages one at at time issuing the message line then all signals in there too. for (int x = 0; x < messageHandler->getCount(); x++) { DBC_MESSAGE *msg = messageHandler->findMsgByIdx(x); if (msg->name.length() < 1) //detect an empty string and fill it out with something { msg->name = "MSG" + QString::number(msgNumber); msgNumber++; } uint32_t ID = msg->ID; 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"); if (msg->comment.length() > 0) { commentsOutput.append("CM_ BO_ " + QString::number(ID) + " \"" + msg->comment + "\";\n"); } //If this message has attributes then compile them into attributes list to output later on. if (msg->attributes.count() > 0) { foreach (DBC_ATTRIBUTE_VALUE val, msg->attributes) { attrValOutput.append("BA_ \"" + val.attrName + "\" BO_ " + QString::number(ID) + " "); switch (val.value.type()) { case QVariant::Type::String: attrValOutput.append("\"" + val.value.toString() + "\";\n"); break; default: attrValOutput.append(val.value.toString() + ";\n"); break; } } } for (int s = 0; s < msg->sigHandler->getCount(); s++) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(s); if (sig->name.length() < 1) //detect an empty string and fill it out with something { sig->name = "SIG" + QString::number(sigNumber); sigNumber++; } msgOutput.append(" SG_ " + sig->name); if (sig->isMultiplexed) { msgOutput.append(" m" + QString::number(sig->getSimpleMultiplexValue())); } if (sig->isMultiplexor) { if (!sig->isMultiplexed) msgOutput.append(" "); msgOutput.append("M"); } //check for the two telltale signs that we've got extended multiplexing going on. if (sig->isMultiplexed && sig->isMultiplexor) hasExtendedMultiplexing = true; if (sig->hasExtendedMultiplexing) hasExtendedMultiplexing = true; msgOutput.append(" : " + QString::number(sig->startBit) + "|" + QString::number(sig->signalSize) + "@"); switch (sig->valType) { case UNSIGNED_INT: if (sig->intelByteOrder) msgOutput.append("1+"); else msgOutput.append("0+"); break; case SIGNED_INT: if (sig->intelByteOrder) msgOutput.append("1-"); else msgOutput.append("0-"); break; case SP_FLOAT: if (sig->intelByteOrder) msgOutput.append("5-"); else msgOutput.append("2-"); break; case DP_FLOAT: if (sig->intelByteOrder) msgOutput.append("6-"); else msgOutput.append("3-"); break; case STRING: msgOutput.append("4-"); break; default: msgOutput.append("0-"); break; } msgOutput.append(" (" + QString::number(sig->factor) + "," + QString::number(sig->bias) + ") [" + QString::number(sig->min) + "|" + QString::number(sig->max) + "] \"" + sig->unitName + "\" " + sig->receiver->name + "\n"); if (sig->comment.length() > 0) { commentsOutput.append("CM_ SG_ " + QString::number(ID) + " " + sig->name + " \"" + sig->comment + "\";\n"); } //if this signal has attributes then compile them in a special list of attributes if (sig->attributes.count() > 0) { foreach (DBC_ATTRIBUTE_VALUE val, sig->attributes) { attrValOutput.append("BA_ \"" + val.attrName + "\" SG_ " + QString::number(ID) + " " + sig->name + " "); switch (val.value.type()) { case QVariant::Type::String: attrValOutput.append("\"" + val.value.toString() + "\";\n"); break; default: attrValOutput.append(val.value.toString() + ";\n"); break; } } } if (sig->valList.count() > 0) { valuesOutput.append("VAL_ " + QString::number(ID) + " " + sig->name); for (int v = 0; v < sig->valList.count(); v++) { DBC_VAL_ENUM_ENTRY val = sig->valList[v]; valuesOutput.append(" " + QString::number(val.value) + " \"" + val.descript +"\""); } valuesOutput.append(";\n"); } } msgOutput.append("\n"); //write it out every message so the string doesn't end up too huge outFile->write(msgOutput.toUtf8()); 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++) { msgOutput.append("BA_DEF_ "); switch (dbc_attributes[x].attrType) { case ATTR_TYPE_GENERAL: break; case ATTR_TYPE_NODE: msgOutput.append("BU_ "); break; case ATTR_TYPE_MESSAGE: msgOutput.append("BO_ "); break; case ATTR_TYPE_SIG: msgOutput.append("SG_ "); break; case ATTR_TYPE_ANY: break; } msgOutput.append("\"" + dbc_attributes[x].name + "\" "); switch (dbc_attributes[x].valType) { case ATTR_INT: msgOutput.append("INT " + QString::number(dbc_attributes[x].lower) + " " + QString::number(dbc_attributes[x].upper)); break; case ATTR_FLOAT: msgOutput.append("FLOAT " + QString::number(dbc_attributes[x].lower) + " " + QString::number(dbc_attributes[x].upper)); break; case ATTR_STRING: msgOutput.append("STRING "); break; case ATTR_ENUM: msgOutput.append("ENUM "); foreach (QString str, dbc_attributes[x].enumVals) { msgOutput.append("\"" + str + "\","); } msgOutput.truncate(msgOutput.length() - 1); //remove trailing , break; } msgOutput.append(";\n"); outFile->write(msgOutput.toUtf8()); msgOutput.clear(); //got to reset it after writing if (dbc_attributes[x].defaultValue.isValid()) { defaultsOutput.append("BA_DEF_DEF_ \"" + dbc_attributes[x].name + "\" "); switch (dbc_attributes[x].valType) { case ATTR_STRING: defaultsOutput.append("\"" + dbc_attributes[x].defaultValue.toString() + "\";\n"); break; case ATTR_ENUM: 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; } } } //extended multiplexing uses SG_MUL_VAL_ to specify the relationships. If a signal is marked //as multiplexed then output a record for it. That's the most complete option. We've already //given the single value multiplex above for backward compatibility with things that don't support extended mode if (hasExtendedMultiplexing) { for (int x = 0; x < messageHandler->getCount(); x++) { DBC_MESSAGE *msg = messageHandler->findMsgByIdx(x); uint32_t ID = msg->ID; 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++) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(s); if (sig->isMultiplexed) { msgOutput.append("SG_MUL_VAL_ " + QString::number(ID) + " "); msgOutput.append(sig->name + " " + sig->multiplexParent->name + " "); msgOutput.append(sig->multiplexDbcString() + ";"); msgOutput.append("\n"); extMultiplexOutput.append(msgOutput); msgOutput.clear(); //got to reset it after writing } } } } //now write out all of the accumulated comments and value tables from above outFile->write(defaultsOutput.toUtf8()); outFile->write(attrValOutput.toUtf8()); outFile->write(valuesOutput.toUtf8()); outFile->write(extMultiplexOutput.toUtf8()); attrValOutput.clear(); defaultsOutput.clear(); valuesOutput.clear(); extMultiplexOutput.clear(); outFile->close(); delete outFile; isDirty = false; QStringList fileList = fileName.split('/'); this->fileName = fileList[fileList.length() - 1]; //whoops... same name as parameter in this function. filePath = fileName.left(fileName.length() - this->fileName.length()); return true; } void DBCHandler::saveDBCFile(int idx) { QSettings settings; if (loadedFiles.count() == 0) return; if (idx < 0) return; if (idx >= loadedFiles.count()) return; QString filename; QFileDialog dialog; QStringList filters; filters.append(QString(tr("DBC File (*.dbc)"))); dialog.setDirectory(settings.value("DBC/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::AnyFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.selectFile(loadedFiles[idx].getFullFilename()); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; if (!filename.contains('.')) filename += ".dbc"; loadedFiles[idx].saveFile(filename); settings.setValue("DBC/LoadSaveDirectory", dialog.directory().path()); } } int DBCHandler::createBlankFile() { DBCFile newFile; DBC_ATTRIBUTE attr; //add our custom attributes to the new file so that we know they're already there. attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = QApplication::palette().color(QPalette::Base).name(); qDebug() << attr.defaultValue; attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "GenMsgBackgroundColor"; attr.valType = ATTR_STRING; newFile.dbc_attributes.append(attr); attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = QApplication::palette().color(QPalette::WindowText).name(); qDebug() << attr.defaultValue; attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "GenMsgForegroundColor"; attr.valType = ATTR_STRING; newFile.dbc_attributes.append(attr); attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = 0; attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "matchingcriteria"; attr.valType = ATTR_INT; newFile.dbc_attributes.append(attr); attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = 0; attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "filterlabeling"; attr.valType = ATTR_INT; newFile.dbc_attributes.append(attr); DBC_NODE falseNode; falseNode.name = "Vector__XXX"; falseNode.comment = "Default node if none specified"; newFile.dbc_nodes.append(falseNode); newFile.setAssocBus(-1); loadedFiles.append(newFile); return loadedFiles.count(); } DBCFile* DBCHandler::loadDBCFile(QString filename) { DBCFile newFile; if (newFile.loadFile(filename)) { loadedFiles.append(newFile); } else { //createBlankFile(); } if (loadedFiles.count()> 0) return &loadedFiles.last(); else return nullptr; } //the only reason to even bother sending the index is to see if //the user wants to replace an already loaded DBC. //Otherwise add a new one. Well, always add a new one. //If a valid index is passed we'll remove that one and then commence //adding. Otherwise, just go straight to adding. DBCFile* DBCHandler::loadDBCFile(int idx) { if (idx > -1 && idx < loadedFiles.count()) removeDBCFile(idx); QString filename; QFileDialog dialog; QSettings settings; QStringList filters; filters.append(QString(tr("DBC File (*.dbc)"))); dialog.setDirectory(settings.value("DBC/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; //right now there is only one file type that can be loaded here so just do it. settings.setValue("DBC/LoadSaveDirectory", dialog.directory().path()); return loadDBCFile(filename); } return nullptr; } DBCFile* DBCHandler::loadSecretCSVFile(QString filename) { DBCFile *thisFile; DBC_MESSAGE *pMsg; DBC_SIGNAL *pSig; QByteArray line; int lineCounter = 0; createBlankFile(); thisFile = &loadedFiles.last(); QFile *inFile = new QFile(filename); if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return nullptr; } //burn first two lines. contains header line = inFile->readLine().simplified().toUpper(); line = inFile->readLine().simplified().toUpper(); while (!inFile->atEnd()) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } line = inFile->readLine().simplified().toUpper(); qDebug() << line; QList tokens = line.split(','); if (tokens.length() == 10) { //Message,CAN ID,Signal,Short Name,Start Byte,Start Bit,Len,Data,Range,Conversion //Whl_Rotational_Stat_CheckVal_CE,$0C0,Wheel Rotational Status Check Data,WhlRotatStatChkData, 0, 7, 40,PKT,N/A,N/A // 0 1 2 3 4 5 6 7 8 9 if (tokens[0].length() > 2) //start of a signal def that starts a new message { DBC_MESSAGE msg; msg.name = tokens[0]; msg.ID = tokens[1].mid(1).toLong(nullptr, 16); msg.comment = ""; msg.len = 8; msg.sender = thisFile->findNodeByIdx(0); msg.bgColor = QColor(thisFile->findAttributeByName("GenMsgBackgroundColor")->defaultValue.toString()); msg.fgColor = QColor(thisFile->findAttributeByName("GenMsgForegroundColor")->defaultValue.toString()); thisFile->messageHandler->addMessage(msg); pMsg = thisFile->messageHandler->findMsgByID(msg.ID); DBC_SIGNAL sig; sig.parentMessage = pMsg; sig.name = tokens[3]; sig.comment = tokens[2]; int startBit = (tokens[4].toInt() * 8) + tokens[5].toInt(); sig.startBit = startBit; sig.signalSize = tokens[6].toInt(); sig.intelByteOrder = false; //always for global-a? sig.receiver = thisFile->findNodeByIdx(0); QList rangeToks = tokens[8].split('-'); if (rangeToks.length() == 2) { sig.min = rangeToks[0].simplified().toDouble(); sig.max = rangeToks[1].simplified().toDouble(); } if (tokens[9].startsWith("E = N")) //not a value table, instead do scaling and bias { QList scalingToks = tokens[9].simplified().split(' '); sig.factor = scalingToks[4].toDouble(); if (scalingToks.count() > 5) { if (scalingToks[5] == "+") { sig.bias = scalingToks[6].toDouble(); } if (scalingToks[5] == "-") { sig.bias = scalingToks[6].toDouble() * 1.0; } } else { sig.factor = 1; sig.bias = 0; } } else if (tokens[9].startsWith("$")) //one or more values table entries { //$0=Inactive QList valToks = tokens[9].simplified().mid(1).split('='); DBC_VAL_ENUM_ENTRY entry; entry.value = valToks[0].toInt(); entry.descript = valToks[1]; sig.valList.append(entry); } pMsg->sigHandler->addSignal(sig); pSig = pMsg->sigHandler->findSignalByIdx(pMsg->sigHandler->getCount()-1); } // 0 1 2 3 4 5 6 7 8 9 //Message,CAN ID,Signal,Short Name,Start Byte,Start Bit,Len,Data,Range,Conversion //,,Wheel Rotational Status Check Data : Left Driven Sequence Number,WRSCD_LftDrvnSqNm, 0, 7, 2,UNM,0 - 3 ,E = N * 1 // 2 3 4 5 6 7 8 9 else if (tokens[2].length() > 2) //signal definition continuation of previous message { DBC_SIGNAL sig; sig.parentMessage = pMsg; sig.name = tokens[3]; sig.comment = tokens[2]; int startBit = (tokens[4].toInt() * 8) + tokens[5].toInt(); sig.startBit = startBit; sig.signalSize = tokens[6].toInt(); sig.intelByteOrder = false; //always for global-a? sig.receiver = thisFile->findNodeByIdx(0); QList rangeToks = tokens[8].split('-'); if (rangeToks.length() == 2) { sig.min = rangeToks[0].simplified().toDouble(); sig.max = rangeToks[1].simplified().toDouble(); } if (tokens[9].startsWith("E = N")) //not a value table, instead do scaling and bias { QList scalingToks = tokens[9].simplified().split(' '); sig.factor = scalingToks[4].toDouble(); if (scalingToks.count() > 5) { if (scalingToks[5] == "+") { sig.bias = scalingToks[6].toDouble(); } if (scalingToks[5] == "-") { sig.bias = scalingToks[6].toDouble() * 1.0; } } else { sig.bias = 0; sig.factor = 1; } } else if (tokens[9].startsWith("$")) //one or more values table entries { //$0=Inactive QList valToks = tokens[9].simplified().mid(1).split('='); DBC_VAL_ENUM_ENTRY entry; entry.value = valToks[0].toInt(); entry.descript = valToks[1]; sig.valList.append(entry); } pMsg->sigHandler->addSignal(sig); pSig = pMsg->sigHandler->findSignalByIdx(pMsg->sigHandler->getCount()-1); } else if (tokens[9].length() > 2) //additional values { //$0=Inactive QList valToks = tokens[9].simplified().mid(1).split('='); DBC_VAL_ENUM_ENTRY entry; entry.value = valToks[0].toInt(); entry.descript = valToks[1]; pSig->valList.append(entry); } } } thisFile->setDirtyFlag(); return thisFile; } DBCFile* DBCHandler::loadJSONFile(QString filename) { QSettings settings; DBCFile *thisFile; DBC_MESSAGE *pMsg; createBlankFile(); thisFile = &loadedFiles.last(); QFile *inFile = new QFile(filename); if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "Could not open JSON file for reading."; delete inFile; return nullptr; } QByteArray wholeFileData = inFile->readAll(); inFile->close(); delete inFile; //qDebug() << "Data Length: " << wholeFileData.length(); QJsonDocument jsonDoc = QJsonDocument::fromJson(wholeFileData); if (jsonDoc.isNull()) { qDebug() << "Couldn't load and parse the JSON file for some reason."; return nullptr; } qDebug() << "Loaded JSON"; QJsonObject jsonObj = jsonDoc.object(); QJsonObject jsonMessages = jsonObj["messages"].toObject(); QJsonObject::iterator iter; for (iter = jsonMessages.begin(); iter != jsonMessages.end(); iter++) { qDebug() << iter.key(); DBC_MESSAGE msg; msg.ID = static_cast(iter->toObject().find("message_id").value().toInt()); msg.name = QString(iter.key().toUtf8()); msg.len = static_cast(iter->toObject().find("length_bytes").value().toInt()); msg.sender = thisFile->findNodeByIdx(0); QString nodeName = iter->toObject().find("originNode").value().toString(); msg.sender = thisFile->findNodeByName(nodeName); msg.bgColor = QColor(thisFile->findAttributeByName("GenMsgBackgroundColor")->defaultValue.toString()); msg.fgColor = QColor(thisFile->findAttributeByName("GenMsgForegroundColor")->defaultValue.toString()); if (!msg.sender && nodeName.length() > 1) { DBC_NODE node; node.name = nodeName; thisFile->dbc_nodes.append(node); msg.sender = thisFile->findNodeByName(nodeName); } if (nodeName.length() < 2) msg.sender = thisFile->findNodeByIdx(0); thisFile->messageHandler->addMessage(msg); pMsg = thisFile->messageHandler->findMsgByID(msg.ID); if (!pMsg) { qDebug() << "pMsg was null... I have no idea how that is possible. DEBUG ME"; return nullptr; } QJsonObject jsonSigs = iter->toObject().find("signals").value().toObject(); QJsonObject::iterator sigIter; for (sigIter = jsonSigs.begin(); sigIter != jsonSigs.end(); sigIter++) { qDebug() << sigIter.key(); DBC_SIGNAL sig; QJsonObject sigObj = sigIter->toObject(); if (sigObj.isEmpty()) { qDebug() << "EMPTY!?"; } sig.name = QString(sigIter.key().toUtf8()); sig.factor = sigObj.find("scale").value().toDouble(); sig.bias = sigObj.find("offset").value().toDouble(); sig.max = sigObj.find("max").value().toDouble(); sig.min = sigObj.find("min").value().toDouble(); sig.startBit = sigObj.find("start_position").value().toInt(); sig.unitName = sigObj.find("units").value().toString(); sig.signalSize = sigObj.find("width").value().toInt(); sig.isMultiplexed = false; sig.isMultiplexor = false; sig.parentMessage = pMsg; if (!sigObj.find("mux_id")->isUndefined()) { QJsonValue muxVal = sigObj.find("mux_id").value(); sig.addMultiplexRange(muxVal.toInt(), muxVal.toInt()); } QJsonValue muxerVal = sigObj.find("is_muxer").value(); if (!muxerVal.isNull()) { if (muxerVal.toBool()) { sig.isMultiplexor = true; } } QJsonObject valuesObj = sigObj.find("value_description")->toObject(); if (!valuesObj.isEmpty()) { QJsonObject::iterator valIter; for (valIter = valuesObj.begin(); valIter != valuesObj.end(); valIter++) { DBC_VAL_ENUM_ENTRY valEnum; valEnum.value = valIter.value().toInt(); valEnum.descript = valIter.key().toUtf8(); sig.valList.append(valEnum); } } QJsonArray rxArray = sigObj.find("receivers")->toArray(); if (rxArray.size() < 1) sig.receiver = thisFile->findNodeByIdx(0); else { qDebug() << rxArray[0].toString(); sig.receiver = thisFile->findNodeByName(rxArray[0].toString()); if (!sig.receiver && rxArray[0].toString().length() > 1) { DBC_NODE node; node.name = rxArray[0].toString(); thisFile->dbc_nodes.append(node); sig.receiver = thisFile->findNodeByName(rxArray[0].toString()); } if (!sig.receiver) sig.receiver = thisFile->findNodeByIdx(0); } if (!sigObj.find("endianness").value().toString().compare("BIG")) { sig.intelByteOrder = false; } else sig.intelByteOrder = true; if (!sigObj.find("signedness").value().toString().compare("UNSIGNED")) { sig.valType = DBC_SIG_VAL_TYPE::UNSIGNED_INT; } else sig.valType = DBC_SIG_VAL_TYPE::SIGNED_INT; pMsg->sigHandler->addSignal(sig); if (sig.isMultiplexor) //if this signal was the multiplexor then store that info { DBC_SIGNAL *pSig = pMsg->sigHandler->findSignalByName(sig.name); if (pSig) pMsg->multiplexorSignal = pSig; } } } for (int x = 0; x < thisFile->messageHandler->getCount(); x++) { DBC_MESSAGE *msg = thisFile->messageHandler->findMsgByIdx(x); for (int y = 0; y < msg->sigHandler->getCount(); y++) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(y); //if this doesn't have a multiplex parent set but is multiplexed then it must have used //simple multiplexing instead of any extended specification. So, fill in the multiplexor signal here //and also write the extended entry for it too. if (sig->isMultiplexed && (sig->multiplexParent == nullptr) ) { sig->multiplexParent = msg->multiplexorSignal; msg->multiplexorSignal->multiplexedChildren.append(sig); } } } thisFile->setDirtyFlag(); return thisFile; } void DBCHandler::removeDBCFile(int idx) { if (loadedFiles.count() == 0) return; if (idx < 0) return; if (idx >= loadedFiles.count()) return; loadedFiles.removeAt(idx); } void DBCHandler::removeAllFiles() { loadedFiles.clear(); } void DBCHandler::swapFiles(int pos1, int pos2) { if (loadedFiles.count() < 2) return; if (pos1 < 0) return; if (pos1 >= loadedFiles.count()) return; if (pos2 < 0) return; if (pos2 >= loadedFiles.count()) return; loadedFiles.swapItemsAt(pos1, pos2); } /* * Convenience function that encapsulates a whole lot of the details. * You give it a canbus frame and it'll tell you whether there is a loaded DBC file that can * interpret that frame for you. * Returns nullptr if there is no message definition that matches. */ DBC_MESSAGE* DBCHandler::findMessage(const CANFrame &frame) { for(int i = 0; i < loadedFiles.count(); i++) { if (loadedFiles[i].getAssocBus() == -1 || frame.bus == loadedFiles[i].getAssocBus()) { DBC_MESSAGE* msg = loadedFiles[i].messageHandler->findMsgByID(frame.frameId()); if (msg != nullptr) return msg; } } return nullptr; } DBC_MESSAGE* DBCHandler::findMessage(uint32_t id) { for(int i = 0; i < loadedFiles.count(); i++) { DBC_MESSAGE* msg = loadedFiles[i].messageHandler->findMsgByID(id); if (msg != nullptr) { return msg; } } return nullptr; } // This function won't care which bus the DBC file is associated, but will return any message as long as ID matches and the file // has filter labeling enabled. // Returns the found message as well as the matching criteria (exact/J1939/GMLAN) // Used for quickly populating the Frame Filtering section with interpreted values DBC_MESSAGE* DBCHandler::findMessageForFilter(uint32_t id, MatchingCriteria_t * matchingCriteria) { for(int i = 0; i < loadedFiles.count(); i++) { if (loadedFiles[i].messageHandler->filterLabeling()) { DBC_MESSAGE* msg = loadedFiles[i].messageHandler->findMsgByID(id); if (msg != nullptr) { if (matchingCriteria) *matchingCriteria = loadedFiles[i].messageHandler->getMatchingCriteria(); return msg; } } } return nullptr; } /* * 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) { DBC_MESSAGE *msg = nullptr; for(int i = 0; i < loadedFiles.count(); i++) { DBCFile * file = getFileByIdx(i); msg = file->messageHandler->findMsgByName(msgName); if (msg) return msg; //if it's not null then we have a match so return it } 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(); } DBCFile* DBCHandler::getFileByIdx(int idx) { if (loadedFiles.count() == 0) return nullptr; if (idx < 0) return nullptr; if (idx >= loadedFiles.count()) return nullptr; return &loadedFiles[idx]; } DBCFile* DBCHandler::getFileByName(QString name) { if (loadedFiles.count() == 0) return nullptr; for (int i = 0; i < loadedFiles.count(); i++) { if (loadedFiles[i].getFilename().compare(name, Qt::CaseInsensitive) == 0) { return &loadedFiles[i]; } } return nullptr; } 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::number(i),0).toInt(); DBC_ATTRIBUTE attr; attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = matchingCriteria; attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "matchingcriteria"; attr.valType = ATTR_INT; file->dbc_attributes.append(attr); file->messageHandler->setMatchingCriteria(matchingCriteria); int filterLabeling = settings.value("DBC/FilterLabeling_" + QString::number(i),0).toInt(); attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = filterLabeling; attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "filterlabeling"; attr.valType = ATTR_INT; file->dbc_attributes.append(attr); file->messageHandler->setFilterLabeling(filterLabeling); qInfo() << "Loaded DBC file" << filename << " (bus:" << bus << ", Matching Criteria:" << (int)matchingCriteria << "Filter labeling: " << (filterLabeling?"enabled":"disabled") << ")"; } } } DBCHandler* DBCHandler::getReference() { if (!instance) instance = new DBCHandler(); return instance; } savvycan-220/dbc/dbchandler.h000066400000000000000000000104751500724750100162100ustar00rootroot00000000000000#ifndef DBCHANDLER_H #define DBCHANDLER_H #include #include "dbc_classes.h" #include "can_structs.h" typedef enum { EXACT, J1939, GMLAN } MatchingCriteria_t; /* * TODO: * Finish coding up the decoupled design * */ class DBCSignalHandler: public QObject { Q_OBJECT public: DBC_SIGNAL *findSignalByName(QString name); DBC_SIGNAL *findSignalByIdx(int idx); bool addSignal(DBC_SIGNAL &sig); bool removeSignal(DBC_SIGNAL *sig); bool removeSignal(int idx); bool removeSignal(QString name); void removeAllSignals(); int getCount(); void sort(); private: QList sigs; //signals is a reserved word or I'd have used that }; class DBCMessageHandler: public QObject { Q_OBJECT public: DBC_MESSAGE *findMsgByID(uint32_t id); 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); bool removeMessage(uint32_t ID); bool removeMessage(QString name); void removeAllMessages(); int getCount(); MatchingCriteria_t getMatchingCriteria(); void setMatchingCriteria(MatchingCriteria_t mc); void setFilterLabeling( bool labelFiltering ); bool filterLabeling(); void sort(); private: QList messages; MatchingCriteria_t matchingCriteria; bool filterLabelingEnabled; }; //technically there should be a node handler too but I'm sort of treating nodes as second class //citizens since they aren't really all that important (to me anyway) class DBCFile: public QObject { Q_OBJECT public: DBCFile(); 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); void findAttributesByType(DBC_ATTRIBUTE_TYPE typ, QList *list); bool saveFile(QString); 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; QList dbc_nodes; QList dbc_attributes; private: QString fileName; QString filePath; int assocBuses; //-1 = all buses, 0 = first bus, 1 = second bus, etc. bool isDirty; //has the file been modified? bool parseAttribute(QString inpString, DBC_ATTRIBUTE &attr); QVariant processAttributeVal(QString input, DBC_ATTRIBUTE_VAL_TYPE typ); DBC_SIGNAL* parseSignalLine(QString line, DBC_MESSAGE *msg); 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); }; class DBCHandler: public QObject { Q_OBJECT public: DBCFile* loadDBCFile(QString filename); DBCFile* loadDBCFile(int); void saveDBCFile(int); void removeDBCFile(int); void removeAllFiles(); 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(); DBCFile* getFileByIdx(int idx); DBCFile* getFileByName(QString name); int createBlankFile(); DBCFile* loadJSONFile(QString); DBCFile* loadSecretCSVFile(QString); static DBCHandler *getReference(); private: QList loadedFiles; DBCHandler(); static DBCHandler *instance; }; #endif // DBCHANDLER_H savvycan-220/dbc/dbcloadsavewindow.cpp000066400000000000000000000340471500724750100201550ustar00rootroot00000000000000#include "dbcloadsavewindow.h" #include "ui_dbcloadsavewindow.h" #include #include #include #include #include "helpwindow.h" #include "connections/canconmanager.h" DBCLoadSaveWindow::DBCLoadSaveWindow(const QVector *frames, QWidget *parent) : QDialog(parent), ui(new Ui::DBCLoadSaveWindow) { setWindowFlags(Qt::Window); dbcHandler = DBCHandler::getReference(); referenceFrames = frames; ui->setupUi(this); inhibitCellProcessing = true; QStringList header; header << "Filename" << "Associated Bus" << "Matching criteria" << "Label filters"; ui->tableFiles->setColumnCount(4); ui->tableFiles->setHorizontalHeaderLabels(header); ui->tableFiles->setColumnWidth(0, 265); ui->tableFiles->setColumnWidth(1, 125); ui->tableFiles->setColumnWidth(2, 120); ui->tableFiles->setColumnWidth(3, 90); ui->tableFiles->horizontalHeader()->setStretchLastSection(true); // Populate table for (int idx=0; idxgetFileCount(); idx++) { DBCFile * file = dbcHandler->getFileByIdx(idx); ui->tableFiles->insertRow(ui->tableFiles->rowCount()); ui->tableFiles->setItem(idx, 0, new QTableWidgetItem(file->getFilename())); QString bus = QString::number(file->getAssocBus() ); ui->tableFiles->setItem(idx, 1, new QTableWidgetItem(bus)); QComboBox * mc_item = addMatchingCriteriaCombobox(idx); int mc = (int)file->messageHandler->getMatchingCriteria(); mc_item->setCurrentIndex(mc); QTableWidgetItem *item = new QTableWidgetItem(""); ui->tableFiles->setItem(idx, 3, item); bool filterLabeling = file->messageHandler->filterLabeling(); if (filterLabeling) { item->setCheckState(Qt::Checked); } else { item->setCheckState(Qt::Unchecked); } qDebug() << "Populate DBC table:" << file->getFullFilename() << " (bus:" << bus << " - Matching Criteria:" << mc << "Filter labeling: " << (filterLabeling?"enabled":"disabled") << ")"; } connect(ui->btnEdit, &QAbstractButton::clicked, this, &DBCLoadSaveWindow::editFile); connect(ui->btnLoad, &QAbstractButton::clicked, this, &DBCLoadSaveWindow::loadFile); connect(ui->btnMoveDown, &QAbstractButton::clicked, this, &DBCLoadSaveWindow::moveDown); connect(ui->btnMoveUp, &QAbstractButton::clicked, this, &DBCLoadSaveWindow::moveUp); connect(ui->btnRemove, &QAbstractButton::clicked, this, &DBCLoadSaveWindow::removeFile); connect(ui->btnSave, &QAbstractButton::clicked, this, &DBCLoadSaveWindow::saveFile); connect(ui->btnNewDBC, &QAbstractButton::clicked, this, &DBCLoadSaveWindow::newFile); connect(ui->tableFiles, &QTableWidget::cellChanged, this, &DBCLoadSaveWindow::cellChanged); connect(ui->tableFiles, &QTableWidget::cellDoubleClicked, this, &DBCLoadSaveWindow::cellDoubleClicked); editorWindow = new DBCMainEditor(frames, this); currentlyEditingFile = nullptr; inhibitCellProcessing = false; installEventFilter(this); } QComboBox * DBCLoadSaveWindow::addMatchingCriteriaCombobox(int row) { QComboBox *item = new QComboBox(); item->addItem("Exact"); item->addItem("J1939"); item->addItem("GMLAN"); ui->tableFiles->setCellWidget(row, 2, item); connect(item, static_cast(&QComboBox::currentIndexChanged), [this](int box_idx) { matchingCriteriaChanged(box_idx); } ); return item; } DBCLoadSaveWindow::~DBCLoadSaveWindow() { removeEventFilter(this); delete ui; } void DBCLoadSaveWindow::updateSettings() { QSettings settings; int filecount = ui->tableFiles->rowCount(); settings.setValue("DBC/FileCount", filecount); for (int i=0; igetFileByIdx(i); if (file) { 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::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(); } bool DBCLoadSaveWindow::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("dbc_manager.md"); break; } return true; } else { // standard event processing return QObject::eventFilter(obj, event); } return false; } void DBCLoadSaveWindow::newFile() { int idx = dbcHandler->createBlankFile(); idx = ui->tableFiles->rowCount(); ui->tableFiles->insertRow(ui->tableFiles->rowCount()); ui->tableFiles->setItem(idx, 0, new QTableWidgetItem("UNNAMEDFILE")); ui->tableFiles->setItem(idx, 1, new QTableWidgetItem("-1")); QComboBox * mc_item = addMatchingCriteriaCombobox(idx); mc_item->setCurrentIndex(EXACT); QTableWidgetItem *item = new QTableWidgetItem(""); item->setCheckState(Qt::Checked); ui->tableFiles->setItem(idx, 3, item); } void DBCLoadSaveWindow::loadFile() { DBCFile *file = nullptr; QString filename; QFileDialog dialog; QSettings settings; QStringList filters; filters.append(QString(tr("DBC File (*.dbc)"))); filters.append(QString(tr("Tesla JSON File (*.json)"))); filters.append(QString(tr("Secret CSV Signal Defs (*.csv)"))); dialog.setDirectory(settings.value("DBC/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; //right now there is only one file type that can be loaded here so just do it. settings.setValue("DBC/LoadSaveDirectory", dialog.directory().path()); if (dialog.selectedNameFilter() == filters[0]) { if (!filename.contains('.')) filename += ".dbc"; file = dbcHandler->loadDBCFile(filename); } if (dialog.selectedNameFilter() == filters[1]) { if (!filename.contains('.')) filename += ".json"; file = dbcHandler->loadJSONFile(filename); } if (dialog.selectedNameFilter() == filters[2]) { if (!filename.contains('.')) filename += ".csv"; file = dbcHandler->loadSecretCSVFile(filename); } } if(file) { inhibitCellProcessing=true; int idx = ui->tableFiles->rowCount(); ui->tableFiles->insertRow(ui->tableFiles->rowCount()); ui->tableFiles->setItem(idx, 0, new QTableWidgetItem(file->getFilename())); ui->tableFiles->setItem(idx, 1, new QTableWidgetItem("-1")); DBC_ATTRIBUTE *attr = file->findAttributeByName("matchingcriteria"); QComboBox * mc_item = addMatchingCriteriaCombobox(idx); if (attr && attr->defaultValue.toInt() > 0) { mc_item->setCurrentIndex(attr->defaultValue.toInt()); } attr = file->findAttributeByName("filterlabeling"); QTableWidgetItem *item = new QTableWidgetItem(""); ui->tableFiles->setItem(idx, 3, item); if (attr && attr->defaultValue.toInt() > 0) { item->setCheckState(Qt::Checked); } else { item->setCheckState(Qt::Unchecked); } inhibitCellProcessing=false; updateSettings(); } } void DBCLoadSaveWindow::loadJSON() { } void DBCLoadSaveWindow::saveFile() { int idx = ui->tableFiles->currentRow(); if (idx < 0) return; dbcHandler->saveDBCFile(idx); //then update the list to show the new file name (if it changed) ui->tableFiles->setItem(idx, 0, new QTableWidgetItem(dbcHandler->getFileByIdx(idx)->getFilename())); } void DBCLoadSaveWindow::removeFile() { bool bContinue = true; int idx = ui->tableFiles->currentRow(); if (idx < 0) return; if (currentlyEditingFile == dbcHandler->getFileByIdx(idx)) { bContinue = false; QMessageBox::StandardButton confirmDialog; confirmDialog = QMessageBox::question(this, "Confirm Deletion", "This DBC is currently open for editing.\nMake sure you've saved any changes!\nAre you sure you want to remove this DBC?", QMessageBox::Yes|QMessageBox::No); if (confirmDialog == QMessageBox::Yes) bContinue = true; } if (bContinue) { editorWindow->close(); dbcHandler->removeDBCFile(idx); ui->tableFiles->removeRow(idx); } updateSettings(); } void DBCLoadSaveWindow::moveUp() { int idx = ui->tableFiles->currentRow(); if (idx < 1) return; dbcHandler->swapFiles(idx - 1, idx); swapTableRows(true); updateSettings(); } void DBCLoadSaveWindow::moveDown() { int idx = ui->tableFiles->currentRow(); if (idx < 0) return; if (idx > (dbcHandler->getFileCount() - 2)) return; dbcHandler->swapFiles(idx, idx + 1); swapTableRows(false); updateSettings(); } void DBCLoadSaveWindow::editFile() { int idx = ui->tableFiles->currentRow(); if (idx < 0) return; editorWindow->setFileIdx(idx); editorWindow->show(); } void DBCLoadSaveWindow::matchingCriteriaChanged(int index) { Q_UNUSED(index) if (inhibitCellProcessing) return; // We don't know which combobox changed, so we just update all of them for (int row=0; rowtableFiles->rowCount(); row++) { DBCFile *file = dbcHandler->getFileByIdx(row); if (file) { QComboBox *item = (QComboBox*)ui->tableFiles->cellWidget(row, 2); MatchingCriteria_t matchingCriteria = (MatchingCriteria_t) item->currentIndex(); DBC_ATTRIBUTE *attr = file->findAttributeByName("matchingcriteria"); if (attr) { attr->defaultValue = matchingCriteria; file->messageHandler->setMatchingCriteria(matchingCriteria); } else { DBC_ATTRIBUTE attr; attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = matchingCriteria; attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "matchingcriteria"; attr.valType = ATTR_INT; file->dbc_attributes.append(attr); file->messageHandler->setMatchingCriteria(matchingCriteria); } } } updateSettings(); } void DBCLoadSaveWindow::cellChanged(int row, int col) { if (inhibitCellProcessing) return; if (col == 1) //the bus column { DBCFile *file = dbcHandler->getFileByIdx(row); int bus = ui->tableFiles->item(row, col)->text().toInt(); //int numBuses = CANConManager::getInstance()->getNumBuses(); if (bus > -2) { file->setAssocBus(bus); } updateSettings(); } else if (col == 3) // labelfilters { DBCFile *file = dbcHandler->getFileByIdx(row); if (file) { bool labelFilters = ui->tableFiles->item(row, col)->checkState() == Qt::Checked; DBC_ATTRIBUTE *attr = file->findAttributeByName("filterlabeling"); if (attr) { attr->defaultValue = labelFilters ? 1 : 0; file->messageHandler->setFilterLabeling(labelFilters); } else { DBC_ATTRIBUTE attr; attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = labelFilters ? 1 : 0; attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "labelfilters"; attr.valType = ATTR_INT; file->dbc_attributes.append(attr); file->messageHandler->setFilterLabeling(labelFilters); } updateSettings(); } } } void DBCLoadSaveWindow::cellDoubleClicked(int row, int col) { Q_UNUSED(col) currentlyEditingFile = dbcHandler->getFileByIdx(row); editorWindow->setFileIdx(row); editorWindow->show(); } void DBCLoadSaveWindow::swapTableRows(bool up) { int idx = ui->tableFiles->currentRow(); const int destIdx = (up ? idx-1 : idx+1); Q_ASSERT(destIdx >= 0 && destIdx < ui->tableFiles->rowCount()); inhibitCellProcessing = true; // take whole rows QList sourceItems = takeRow(idx); QList destItems = takeRow(destIdx); // QCombobox needs separate handling int sourceMC = ((QComboBox*)ui->tableFiles->cellWidget(idx,2))->currentIndex(); int destMC = ((QComboBox*)ui->tableFiles->cellWidget(destIdx,2))->currentIndex(); // set back in reverse order setRow(idx, destItems); setRow(destIdx, sourceItems); ((QComboBox*)ui->tableFiles->cellWidget(idx,2))->setCurrentIndex(destMC); ((QComboBox*)ui->tableFiles->cellWidget(destIdx,2))->setCurrentIndex(sourceMC); inhibitCellProcessing = false; } QList DBCLoadSaveWindow::takeRow(int row) { QList rowItems; for (int col = 0; col < ui->tableFiles->columnCount(); ++col) { rowItems << ui->tableFiles->takeItem(row, col); } return rowItems; } void DBCLoadSaveWindow::setRow(int row, const QList& rowItems) { for (int col = 0; col < ui->tableFiles->columnCount(); ++col) { ui->tableFiles->setItem(row, col, rowItems.at(col)); } } savvycan-220/dbc/dbcloadsavewindow.h000066400000000000000000000023771500724750100176230ustar00rootroot00000000000000#ifndef DBCLOADSAVEWINDOW_H #define DBCLOADSAVEWINDOW_H #include #include #include #include "dbchandler.h" #include "dbcmaineditor.h" namespace Ui { class DBCLoadSaveWindow; } class DBCLoadSaveWindow : public QDialog { Q_OBJECT public: explicit DBCLoadSaveWindow(const QVector *frames, QWidget *parent = 0); ~DBCLoadSaveWindow(); private slots: void loadFile(); void loadJSON(); void saveFile(); void removeFile(); void moveUp(); void moveDown(); void editFile(); void cellChanged(int row, int col); void cellDoubleClicked(int row, int col); void matchingCriteriaChanged(int index); void newFile(); signals: void updatedDBCSettings(); private: Ui::DBCLoadSaveWindow *ui; DBCHandler *dbcHandler; DBCFile *currentlyEditingFile; const QVector *referenceFrames; DBCMainEditor *editorWindow; bool inhibitCellProcessing; void swapTableRows(bool up); QList takeRow(int row); void setRow(int row, const QList& rowItems); bool eventFilter(QObject *obj, QEvent *event); void updateSettings(); QComboBox * addMatchingCriteriaCombobox(int row); }; #endif // DBCLOADSAVEWINDOW_H savvycan-220/dbc/dbcmaineditor.cpp000066400000000000000000001054731500724750100172640ustar00rootroot00000000000000#include "dbcmaineditor.h" #include "ui_dbcmaineditor.h" #include #include #include #include #include #include #include #include "helpwindow.h" DBCMainEditor::DBCMainEditor( const QVector *frames, QWidget *parent) : QDialog(parent), ui(new Ui::DBCMainEditor) { ui->setupUi(this); readSettings(); 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, 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"); signalIcon = QIcon(":/icons/images/signal.png"); multiplexedSignalIcon = QIcon(":/icons/images/multiplexed-signal.png"); multiplexorSignalIcon = QIcon(":/icons/images/multiplexor-signal.png"); //ui->btnDelete->setFixedSize(32,32); ui->btnDelete->setIconSize(QSize(32, 32)); //ui->btnNewNode->setFixedSize(32,32); ui->btnNewNode->setIconSize(QSize(32, 32)); //ui->btnNewMessage->setFixedSize(32,32); ui->btnNewMessage->setIconSize(QSize(32, 32)); //ui->btnNewSignal->setFixedSize(32,32); ui->btnNewSignal->setIconSize(QSize(32, 32)); installEventFilter(this); } void DBCMainEditor::showEvent(QShowEvent* event) { QDialog::showEvent(event); refreshTree(); } DBCMainEditor::~DBCMainEditor() { removeEventFilter(this); delete ui; delete sigEditor; delete msgEditor; delete nodeEditor; } bool DBCMainEditor::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("dbc_editor.md"); break; case Qt::Key_F3: handleSearchForward(); break; case Qt::Key_F4: handleSearchBackward(); break; case Qt::Key_F5: newNode(); break; case Qt::Key_F6: newMessage(); break; case Qt::Key_F7: newSignal(); break; case Qt::Key_Delete: deleteCurrentTreeItem(); } return true; } else { // standard event processing return QObject::eventFilter(obj, event); } return false; } void DBCMainEditor::setFileIdx(int idx) { if (idx < 0 || idx > dbcHandler->getFileCount() - 1) return; dbcFile = dbcHandler->getFileByIdx(idx); fileIdx = idx; } void DBCMainEditor::closeEvent(QCloseEvent *event) { Q_UNUSED(event) writeSettings(); sigEditor->close(); } void DBCMainEditor::readSettings() { QSettings settings; if (settings.value("Main/SaveRestorePositions", false).toBool()) { resize(settings.value("DBCMainEditor/WindowSize", QSize(1103, 571)).toSize()); move(Utility::constrainedWindowPos(settings.value("DBCMainEditor/WindowPos", QPoint(50, 50)).toPoint())); } } void DBCMainEditor::writeSettings() { QSettings settings; if (settings.value("Main/SaveRestorePositions", false).toBool()) { settings.setValue("DBCMainEditor/WindowSize", size()); settings.setValue("DBCMainEditor/WindowPos", pos()); } } void DBCMainEditor::onCustomMenuTree(QPoint point) { Q_UNUSED(point); QMenu *menu = new QMenu(this); menu->setAttribute(Qt::WA_DeleteOnClose); menu->addAction(tr("Delete currently selected message"), this, SLOT(deleteCurrentMessage())); //menu->popup(ui->MessagesTable->mapToGlobal(point)); } void DBCMainEditor::handleSearch() { searchItems = ui->treeDBC->findItems(ui->lineSearch->text(),Qt::MatchContains | Qt::MatchRecursive); qDebug() << "Search returned " << searchItems.count() << "items."; if (searchItems.count() > 0) { ui->treeDBC->setCurrentItem(searchItems[0]); searchItemPos = 0; ui->lblSearchPos->setText("Search Results: " + QString::number(searchItemPos + 1) + " of " + QString::number(searchItems.count())); } else { ui->lblSearchPos->setText("Search Results: 0 of 0"); } } void DBCMainEditor::handleSearchForward() { if (searchItems.count() == 0) return; if (searchItemPos < searchItems.count() - 1) searchItemPos++; else searchItemPos = 0; ui->treeDBC->setCurrentItem(searchItems[searchItemPos]); ui->lblSearchPos->setText("Search Results: " + QString::number(searchItemPos + 1) + " of " + QString::number(searchItems.count())); } void DBCMainEditor::handleSearchBackward() { if (searchItems.count() == 0) return; if (searchItemPos > 0) searchItemPos--; else searchItemPos = searchItems.count() - 1; ui->treeDBC->setCurrentItem(searchItems[searchItemPos]); ui->lblSearchPos->setText("Search Results: " + QString::number(searchItemPos + 1) + " of " + QString::number(searchItems.count())); } void DBCMainEditor::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev) { Q_UNUSED(prev) if (!current) { ui->btnNewMessage->setEnabled(false); ui->btnNewSignal->setEnabled(false); ui->btnDelete->setEnabled(false); return; } switch (current->data(0, Qt::UserRole).toInt()) { case DBCItemTypes::NODE: //node ui->btnNewNode->setEnabled(true); ui->btnNewMessage->setEnabled(true); ui->btnNewSignal->setEnabled(false); ui->btnDelete->setEnabled(true); break; case DBCItemTypes::MESG: //message ui->btnNewNode->setEnabled(true); ui->btnNewMessage->setEnabled(true); ui->btnNewSignal->setEnabled(true); ui->btnDelete->setEnabled(true); break; case DBCItemTypes::SIG: //signal ui->btnNewNode->setEnabled(true); ui->btnNewMessage->setEnabled(true); ui->btnNewSignal->setEnabled(true); ui->btnDelete->setEnabled(true); break; default: ui->btnNewMessage->setEnabled(false); ui->btnNewSignal->setEnabled(false); ui->btnDelete->setEnabled(false); } } uint32_t DBCMainEditor::getParentMessageID(QTreeWidgetItem *cell) { if (cell->data(0, Qt::UserRole) == DBCItemTypes::MESG) { return static_cast(Utility::ParseStringToNum(cell->text(0).split(" ")[0])); } else { if (cell->parent()) return getParentMessageID(cell->parent()); else return 0; } } //Double clicking is interpreted as a desire to edit the given item. void DBCMainEditor::onTreeDoubleClicked(const QModelIndex &index) { Q_UNUSED(index) QTreeWidgetItem* firstCol = ui->treeDBC->currentItem(); //bool ret = false; DBC_MESSAGE *msg; DBC_SIGNAL *sig; DBC_NODE *node; uint32_t msgID; 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); nodeEditor->setFileIdx(fileIdx); nodeEditor->setNodeRef(node); nodeEditor->refreshView(); nodeEditor->show(); break; case DBCItemTypes::MESG: //a message idString = firstCol->text(0).split(" ")[0]; msgID = static_cast(Utility::ParseStringToNum(idString)); msg = dbcFile->messageHandler->findMsgByID(msgID); msgEditor->setMessageRef(msg); msgEditor->setFileIdx(fileIdx); //msgEditor->setWindowModality(Qt::WindowModal); msgEditor->refreshView(); msgEditor->show(); //show allows the rest of the forms to keep going break; case DBCItemTypes::SIG: //a signal msgID = getParentMessageID(firstCol); msg = dbcFile->messageHandler->findMsgByID(msgID); QString nameString = firstCol->text(0); if (nameString.contains("(")) { nameString = nameString.split(")")[1].trimmed(); //remove (1-2) type stuff from beginning of string } nameString = nameString.split(" ")[0]; //get rid of [32m 8] type stuff after the name sig = msg->sigHandler->findSignalByName(nameString); if (sig) { sigEditor->setSignalRef(sig); sigEditor->setMessageRef(msg); sigEditor->setFileIdx(fileIdx); //sigEditor->setWindowModality(Qt::WindowModal); sigEditor->refreshView(); sigEditor->show(); } break; } } 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 * is a node, message, or signal. */ void DBCMainEditor::refreshTree() { ui->treeDBC->clear(); nodeToItem.clear(); messageToItem.clear(); signalToItem.clear(); itemToNode.clear(); itemToMessage.clear(); itemToSignal.clear(); if (dbcFile->findNodeByName("Vector__XXX") == nullptr) { DBC_NODE newNode; newNode.name = "Vector__XXX"; newNode.comment = "Default node if no other node is specified"; dbcFile->dbc_nodes.append(newNode); } for (int n = 0; n < dbcFile->dbc_nodes.count(); n++) { DBC_NODE *node = &dbcFile->dbc_nodes[n]; QTreeWidgetItem *nodeItem = new QTreeWidgetItem(); QString nodeInfo = node->name; if (node->comment.count() > 0) nodeInfo.append(" - ").append(node->comment); nodeItem->setText(0, nodeInfo); nodeItem->setIcon(0, nodeIcon); nodeItem->setData(0, Qt::UserRole, DBCItemTypes::NODE); nodeToItem.insert(node, nodeItem); itemToNode.insert(nodeItem, node); for (int x = 0; x < dbcFile->messageHandler->getCount(); x++) { DBC_MESSAGE *msg = dbcFile->messageHandler->findMsgByIdx(x); if (msg->sender->name == node->name) { QTreeWidgetItem *msgItem = new QTreeWidgetItem(nodeItem); QString msgInfo = Utility::formatCANID(msg->ID) + " " + msg->name; if (msg->comment.count() > 0) msgInfo.append(" - ").append(msg->comment); msgItem->setText(0, msgInfo); msgItem->setIcon(0, messageIcon); msgItem->setData(0, Qt::UserRole, DBCItemTypes::MESG); messageToItem.insert(msg, msgItem); itemToMessage.insert(msgItem, msg); for (int i = 0; i < msg->sigHandler->getCount(); i++) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(i); //only process signals here which are "top" level if (sig->multiplexParent == nullptr) processSignalToTree(msgItem, sig); } } } ui->treeDBC->addTopLevelItem(nodeItem); } ui->treeDBC->sortItems(0, Qt::SortOrder::AscendingOrder); //sort the display list for ease in viewing by mere mortals, helps me a lot. } QString DBCMainEditor::createSignalText(DBC_SIGNAL *sig) { QString sigInfo; if (sig->isMultiplexed) { sigInfo = "(" + sig->multiplexDbcString() + ") "; } sigInfo.append(sig->name); if (sig->intelByteOrder) sigInfo.append(" [" + QString::number(sig->startBit) + "i " + QString::number(sig->signalSize) + "]"); else sigInfo.append(" [" + QString::number(sig->startBit) + "m " + QString::number(sig->signalSize) + "]"); if (sig->comment.count() > 0) sigInfo.append(" - ").append(sig->comment); return sigInfo; } //Signals can have a hierarchial relationship with other signals so this function is separate and calls itself recursively to build the tree void DBCMainEditor::processSignalToTree(QTreeWidgetItem *parent, DBC_SIGNAL *sig) { QTreeWidgetItem *sigItem = new QTreeWidgetItem(parent); QString sigInfo = createSignalText(sig); sigItem->setText(0, sigInfo); 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, DBCItemTypes::SIG); signalToItem.insert(sig, sigItem); itemToSignal.insert(sigItem, sig); if (sig->multiplexedChildren.count() > 0) { for (int i = 0; i < sig->multiplexedChildren.count(); i++) { processSignalToTree(sigItem, sig->multiplexedChildren[i]); } } } void DBCMainEditor::updatedNode(DBC_NODE *node) { if (nodeToItem.contains(node)) { QTreeWidgetItem *item = nodeToItem[node]; QString nodeInfo = node->name; if (node->comment.count() > 0) nodeInfo.append(" - ").append(node->comment); item->setText(0, nodeInfo); } else qDebug() << "That node doesn't exist. That's a bug dude."; } void DBCMainEditor::updatedMessage(DBC_MESSAGE *msg) { if (messageToItem.contains(msg)) { QTreeWidgetItem *item = messageToItem.value(msg); QString msgInfo = Utility::formatCANID(msg->ID) + " " + msg->name; if (msg->comment.count() > 0) msgInfo.append(" - ").append(msg->comment); item->setText(0, msgInfo); //editor could have changed the parent Node too. Have to figure out which node //is parent in the GUI and compare that to parent in the data. DBC_NODE *oldParent = dbcFile->findNodeByName(item->parent()->text(0).split(" - ")[0]); if (oldParent != msg->sender && oldParent) { qDebug() << "Changed parent of message. Trying to rehome it."; QTreeWidgetItem *newParent = nullptr; if (nodeToItem.contains(msg->sender)) newParent = nodeToItem.value(msg->sender); else //new node created within message editor. Create the item and then use it { QTreeWidgetItem *newNodeItem = new QTreeWidgetItem(); newNodeItem->setText(0, msg->sender->name); newNodeItem->setIcon(0, nodeIcon); newNodeItem->setData(0, Qt::UserRole, DBCItemTypes::NODE); ui->treeDBC->addTopLevelItem(newNodeItem); nodeToItem.insert(msg->sender, newNodeItem); itemToNode.insert(newNodeItem, msg->sender); newParent = newNodeItem; } QTreeWidgetItem *prevParent = nodeToItem.value(oldParent); prevParent->removeChild(item); newParent->addChild(item); ui->treeDBC->setCurrentItem(item); ui->treeDBC->sortItems(0, Qt::AscendingOrder); //resort because we just moved an item } } else qDebug() << "That mesage doesn't exist. That's a bug dude."; } void DBCMainEditor::updatedSignal(DBC_SIGNAL *sig) { if (signalToItem.contains(sig)) { QTreeWidgetItem *item = signalToItem.value(sig); QString sigInfo = createSignalText(sig); item->setText(0, sigInfo); if (sig->isMultiplexed) { 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]; else nameString = nameString.split(" ")[0]; DBC_SIGNAL *oldParent = sig->parentMessage->sigHandler->findSignalByName(nameString); if (oldParent && (oldParent != sig->multiplexParent)) { qDebug() << "You changed the signal's parent"; QTreeWidgetItem *newParent = nullptr; newParent = signalToItem.value(sig->multiplexParent); QTreeWidgetItem *prevParent = signalToItem.value(oldParent); prevParent->removeChild(item); newParent->addChild(item); ui->treeDBC->setCurrentItem(item); ui->treeDBC->sortItems(0, Qt::AscendingOrder); //resort because we just moved an item } } } } else qDebug() << "That signal doesn't exist. That's a bug dude."; } void DBCMainEditor::newNode(QString nodeName) { DBC_NODE node; DBC_NODE *nodePtr; 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, DBCItemTypes::NODE); nodeToItem.insert(nodePtr, nodeItem); itemToNode.insert(nodeItem, nodePtr); ui->treeDBC->addTopLevelItem(nodeItem); ui->treeDBC->setCurrentItem(nodeItem); 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.copyMultiplexValuesFromSignal(*sigSource); 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() { QTreeWidgetItem *nodeItem = nullptr; QTreeWidgetItem *msgItem = nullptr; nodeItem = ui->treeDBC->currentItem(); int typ = nodeItem->data(0, Qt::UserRole).toInt(); if (!nodeItem) return; //nothing selected! if (typ == DBCItemTypes::MESG) { msgItem = nodeItem; nodeItem = nodeItem->parent(); } 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]; DBC_NODE *node = dbcFile->findNodeByName(nodeName); if (!node) node = dbcFile->findNodeByIdx(0); DBC_MESSAGE msg; DBC_MESSAGE *msgPtr; if (msgItem) { QString idString = msgItem->text(0).split(" ")[0]; int msgID = static_cast(Utility::ParseStringToNum(idString)); DBC_MESSAGE *oldMsg = dbcFile->messageHandler->findMsgByID(msgID); if (oldMsg) { 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; msg.comment = oldMsg->comment; } else { msg.name = nodeName + "Msg" + QString::number(randGen.bounded(500)); msg.ID = 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 = 8; } msg.sender = node; 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(); } 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 == 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) { 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; sig.name = sig.name + QString::number(randGen.bounded(100)); } else { sig.name = msgItem->text(0).split(" ")[1] + "Sig" + QString::number(randGen.bounded(500)); } } else { sig.name = msgItem->text(0).split(" ")[1] + "Sig" + QString::number(randGen.bounded(500)); } sig.parentMessage = msg; if (!sig.receiver) sig.receiver = &dbcFile->dbc_nodes[0]; //if receiver not set then set it to... something. msg->sigHandler->addSignal(sig); sigPtr = msg->sigHandler->findSignalByIdx(msg->sigHandler->getCount() - 1); QTreeWidgetItem *newSigItem = new QTreeWidgetItem(); 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, DBCItemTypes::SIG); signalToItem.insert(sigPtr, newSigItem); itemToSignal.insert(newSigItem, sigPtr); parentItem->addChild(newSigItem); ui->treeDBC->setCurrentItem(newSigItem); dbcFile->setDirtyFlag(); } //gets confirmation before calling the real routines that delete things void DBCMainEditor::deleteCurrentTreeItem() { QTreeWidgetItem *currItem = ui->treeDBC->currentItem(); int typ = currItem->data(0, Qt::UserRole).toInt(); QString idString, columnText; int msgID; DBC_MESSAGE *msg; DBC_NODE *node; int numMsg = 0, numSig = 0; QMessageBox::StandardButton confirmDialog; switch (typ) { 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++) { if (dbcFile->messageHandler->findMsgByIdx(x)->sender == node) { numMsg++; numSig += dbcFile->messageHandler->findMsgByIdx(x)->sigHandler->getCount(); } } confirmDialog = QMessageBox::question(this, "Really?", "Are you sure you want to delete this node, its\n" + QString::number(numMsg) + " messages, and their " + QString::number(numSig) + " combined signals?", QMessageBox::Yes|QMessageBox::No); if (confirmDialog == QMessageBox::Yes) { if (itemToNode.contains(currItem)) { DBC_NODE *node = itemToNode.value(currItem); deleteNode(node); } else { qDebug() << "Could not find the node in the map. That should not happen."; } } break; 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); confirmDialog = QMessageBox::question(this, "Really?", "Are you sure you want to delete\nthis message and its " + QString::number(msg->sigHandler->getCount()) + " signals?", QMessageBox::Yes|QMessageBox::No); if (confirmDialog == QMessageBox::Yes) { QTreeWidgetItem *currItem = ui->treeDBC->currentItem(); if (itemToMessage.contains(currItem)) { DBC_MESSAGE *msg = itemToMessage.value(currItem); deleteMessage(msg); } else { qDebug() << "Could not find the message in the map. That should not happen."; } } break; 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) { QTreeWidgetItem *currItem = ui->treeDBC->currentItem(); if (itemToSignal.contains(currItem)) { DBC_SIGNAL *sig = itemToSignal.value(currItem); deleteSignal(sig); } else { qDebug() << "Could not find the signal in the map. That should not happen."; } } break; } } //these don't ask for permission. You call it, it disappears forever. void DBCMainEditor::deleteNode(DBC_NODE *node) { qDebug() << "Going through with it you mass deleter!"; if (!nodeToItem.contains(node)) return; QTreeWidgetItem *currItem = nodeToItem[node]; //don't actually store which messages are associated to which nodes so just iterate through the messages list and whack the ones //that claim to be associated to this node. int numItems = dbcFile->messageHandler->getCount(); for (int i = numItems - 1; i > -1; 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); itemToNode.remove(currItem); ui->treeDBC->removeItemWidget(currItem, 0); delete currItem; for (int j = 0; j < dbcFile->dbc_nodes.count(); j++) { if (dbcFile->dbc_nodes.at(j).name == node->name) { dbcFile->dbc_nodes.removeAt(j); break; } } dbcFile->setDirtyFlag(); } void DBCMainEditor::deleteMessage(DBC_MESSAGE *msg) { qDebug() << "Deleting the message and all signals. Bye bye!"; if (!messageToItem.contains(msg)) return; QTreeWidgetItem *currItem = messageToItem[msg]; int numItems = msg->sigHandler->getCount(); for (int i = numItems - 1; i > -1; i--) { deleteSignal(msg->sigHandler->findSignalByIdx(i)); } dbcFile->messageHandler->removeMessage(msg); itemToMessage.remove(currItem); messageToItem.remove(msg); ui->treeDBC->removeItemWidget(currItem, 0); delete currItem; dbcFile->setDirtyFlag(); } void DBCMainEditor::deleteSignal(DBC_SIGNAL *sig) { qDebug() << "Signal about to vanish."; if (!signalToItem.contains(sig)) return; QTreeWidgetItem *currItem = signalToItem[sig]; sig->parentMessage->sigHandler->removeSignal(sig); itemToSignal.remove(currItem); signalToItem.remove(sig); ui->treeDBC->removeItemWidget(currItem, 0); //delete currItem; //already removed by above remove call dbcFile->setDirtyFlag(); } savvycan-220/dbc/dbcmaineditor.h000066400000000000000000000055061500724750100167250ustar00rootroot00000000000000#ifndef DBCMAINEDITOR_H #define DBCMAINEDITOR_H #include #include #include #include #include #include "dbchandler.h" #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 public: explicit DBCMainEditor(const QVector *frames, QWidget *parent = 0); ~DBCMainEditor(); void setFileIdx(int idx); public slots: void updatedNode(DBC_NODE *node); void updatedMessage(DBC_MESSAGE *msg); void updatedSignal(DBC_SIGNAL *sig); private slots: void onTreeDoubleClicked(const QModelIndex &index); void onTreeContextMenu(const QPoint & pos); void currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev); void onCustomMenuTree(QPoint); void deleteCurrentTreeItem(); void deleteNode(DBC_NODE *node); void deleteMessage(DBC_MESSAGE *msg); void deleteSignal(DBC_SIGNAL *sig); 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 onRebaseMessages(); void onDuplicateNode(); private: Ui::DBCMainEditor *ui; DBCHandler *dbcHandler; const QVector *referenceFrames; DBCSignalEditor *sigEditor; DBCMessageEditor *msgEditor; DBCNodeEditor *nodeEditor; DBCNodeRebaseEditor *nodeRebaseEditor; DBCNodeDuplicateEditor *nodeDuplicateEditor; DBCFile *dbcFile; int fileIdx; QIcon nodeIcon; QIcon messageIcon; QIcon signalIcon; QIcon multiplexorSignalIcon; QIcon multiplexedSignalIcon; QList searchItems; int searchItemPos; //bidirectional mapping of QTreeWidget items back and forth to DBC objects QMap nodeToItem; QMap messageToItem; QMap signalToItem; QMap itemToNode; QMap itemToMessage; QMap itemToSignal; QRandomGenerator randGen; void showEvent(QShowEvent* event); void closeEvent(QCloseEvent *event); bool eventFilter(QObject *obj, QEvent *event); void readSettings(); void writeSettings(); void refreshTree(); void processSignalToTree(QTreeWidgetItem *parent, DBC_SIGNAL *sig); uint32_t getParentMessageID(QTreeWidgetItem *cell); QString createSignalText(DBC_SIGNAL *sig); }; #endif // DBCMAINEDITOR_H savvycan-220/dbc/dbcmessageeditor.cpp000066400000000000000000000206771500724750100177660ustar00rootroot00000000000000#include "dbcmessageeditor.h" #include "ui_dbcmessageeditor.h" #include #include #include #include "helpwindow.h" #include "utility.h" DBCMessageEditor::DBCMessageEditor(QWidget *parent) : QDialog(parent), ui(new Ui::DBCMessageEditor) { ui->setupUi(this); readSettings(); dbcHandler = DBCHandler::getReference(); dbcMessage = nullptr; suppressEditCallbacks = false; connect(ui->lineComment, &QLineEdit::editingFinished, [=]() { if (dbcMessage == nullptr) return; if (suppressEditCallbacks) return; if (dbcMessage->comment != ui->lineComment->text()) dbcFile->setDirtyFlag(); dbcMessage->comment = ui->lineComment->text(); emit updatedTreeInfo(dbcMessage); }); connect(ui->lineFrameID, &QLineEdit::editingFinished, [=]() { if (dbcMessage == nullptr) return; if (suppressEditCallbacks) return; if ((dbcMessage->ID & 0x1FFFFFFFul) != Utility::ParseStringToNum(ui->lineFrameID->text())) dbcFile->setDirtyFlag(); dbcMessage->ID = Utility::ParseStringToNum(ui->lineFrameID->text()); emit updatedTreeInfo(dbcMessage); }); connect(ui->lineMsgName, &QLineEdit::editingFinished, [=]() { if (dbcMessage == nullptr) return; if (suppressEditCallbacks) return; if (dbcMessage->name != ui->lineMsgName->text().simplified().replace(' ', '_')) dbcFile->setDirtyFlag(); dbcMessage->name = ui->lineMsgName->text().simplified().replace(' ', '_'); emit updatedTreeInfo(dbcMessage); }); connect(ui->lineFrameLen, &QLineEdit::editingFinished, [=]() { if (dbcMessage == nullptr) return; if (suppressEditCallbacks) return; if (dbcMessage->len != Utility::ParseStringToNum(ui->lineFrameLen->text())) dbcFile->setDirtyFlag(); dbcMessage->len = Utility::ParseStringToNum(ui->lineFrameLen->text()); }); connect(ui->comboSender, &QComboBox::currentTextChanged, [=](const QString newText) { if (dbcMessage == nullptr) return; if (suppressEditCallbacks) return; DBC_NODE *node = dbcFile->findNodeByName(newText); if (!node) return; if (node != dbcMessage->sender) dbcFile->setDirtyFlag(); dbcMessage->sender = node; emit updatedTreeInfo(dbcMessage); }); connect(ui->comboSender->lineEdit(), &QLineEdit::editingFinished, [=]() { if (dbcMessage == nullptr) return; if (suppressEditCallbacks) return; QString newText = ui->comboSender->currentText(); DBC_NODE *node = dbcFile->findNodeByName(newText); if (!node) { DBC_NODE newNode; newNode.name = newText; dbcFile->dbc_nodes.append(newNode); node = dbcFile->findNodeByName(newText); ui->comboSender->addItem(newText); } if (node != dbcMessage->sender) dbcFile->setDirtyFlag(); dbcMessage->sender = node; emit updatedTreeInfo(dbcMessage); }); connect(ui->btnTextColor, &QAbstractButton::clicked, [=]() { if (suppressEditCallbacks) return; QColor newColor = QColorDialog::getColor(dbcMessage->fgColor); if (dbcMessage->fgColor != newColor) dbcFile->setDirtyFlag(); dbcMessage->fgColor = newColor; DBC_ATTRIBUTE_VALUE *val = dbcMessage->findAttrValByName("GenMsgForegroundColor"); if (val) { val->value = newColor.name(); } else { DBC_ATTRIBUTE_VALUE newVal; newVal.attrName = "GenMsgForegroundColor"; newVal.value = newColor.name(); dbcMessage->attributes.append(newVal); } generateSampleText(); }); connect(ui->btnBackgroundColor, &QAbstractButton::clicked, [=]() { if (suppressEditCallbacks) return; QColor newColor = QColorDialog::getColor(dbcMessage->bgColor); if (dbcMessage->bgColor != newColor) dbcFile->setDirtyFlag(); dbcMessage->bgColor = newColor; DBC_ATTRIBUTE_VALUE *val = dbcMessage->findAttrValByName("GenMsgBackgroundColor"); if (val) { val->value = newColor.name(); } else { DBC_ATTRIBUTE_VALUE newVal; newVal.attrName = "GenMsgBackgroundColor"; newVal.value = newColor.name(); dbcMessage->attributes.append(newVal); } generateSampleText(); }); installEventFilter(this); } DBCMessageEditor::~DBCMessageEditor() { removeEventFilter(this); delete ui; } void DBCMessageEditor::closeEvent(QCloseEvent *event) { Q_UNUSED(event); writeSettings(); } bool DBCMessageEditor::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("messageeditor.md"); break; } return true; } else { // standard event processing return QObject::eventFilter(obj, event); } return false; } void DBCMessageEditor::setFileIdx(int idx) { if (idx < 0 || idx > dbcHandler->getFileCount() - 1) return; dbcFile = dbcHandler->getFileByIdx(idx); suppressEditCallbacks = true; ui->comboSender->clear(); for (int x = 0; x < dbcFile->dbc_nodes.count(); x++) { ui->comboSender->addItem(dbcFile->dbc_nodes[x].name); } suppressEditCallbacks = false; } void DBCMessageEditor::readSettings() { QSettings settings; if (settings.value("Main/SaveRestorePositions", false).toBool()) { resize(settings.value("DBCMessageEditor/WindowSize", QSize(340, 400)).toSize()); move(Utility::constrainedWindowPos(settings.value("DBCMessageEditor/WindowPos", QPoint(100, 100)).toPoint())); } } void DBCMessageEditor::writeSettings() { QSettings settings; if (settings.value("Main/SaveRestorePositions", false).toBool()) { settings.setValue("DBCMessageEditor/WindowSize", size()); settings.setValue("DBCMessageEditor/WindowPos", pos()); } } void DBCMessageEditor::setMessageRef(DBC_MESSAGE *msg) { dbcMessage = msg; } void DBCMessageEditor::showEvent(QShowEvent* event) { QDialog::showEvent(event); refreshView(); } void DBCMessageEditor::refreshView() { suppressEditCallbacks = true; ui->lineComment->setText(dbcMessage->comment); ui->lineFrameID->setText(Utility::formatCANID(dbcMessage->ID & 0x1FFFFFFFul)); ui->lineMsgName->setText(dbcMessage->name); ui->lineFrameLen->setText(QString::number(dbcMessage->len)); for (int i = 0; i < ui->comboSender->count(); i++) { if (ui->comboSender->itemText(i) == dbcMessage->sender->name) { ui->comboSender->setCurrentIndex(i); break; } } suppressEditCallbacks = false; generateSampleText(); } void DBCMessageEditor::generateSampleText() { QBrush fg, bg; if (dbcMessage->fgColor.isValid()) fg = QBrush(dbcMessage->fgColor); else fg = QBrush(QColor(dbcFile->findAttributeByName("GenMsgForegroundColor")->defaultValue.toString())); if (dbcMessage->bgColor.isValid()) bg = QBrush(dbcMessage->bgColor); else bg = QBrush(QColor(dbcFile->findAttributeByName("GenMsgBackgroundColor")->defaultValue.toString())); ui->listSample->clear(); QListWidgetItem *item = new QListWidgetItem("Test String"); item->setForeground(fg); item->setBackground(bg); ui->listSample->addItem(item); item = new QListWidgetItem("0x20F TestMsg"); item->setForeground(fg); item->setBackground(bg); ui->listSample->addItem(item); item = new QListWidgetItem("20 FF 10 A1 BB CC 4D"); item->setForeground(fg); item->setBackground(bg); ui->listSample->addItem(item); item = new QListWidgetItem("1024.3434"); item->setForeground(fg); item->setBackground(bg); ui->listSample->addItem(item); } savvycan-220/dbc/dbcmessageeditor.h000066400000000000000000000015301500724750100174160ustar00rootroot00000000000000#ifndef DBCMESSAGEEDITOR_H #define DBCMESSAGEEDITOR_H #include #include "dbc_classes.h" #include "dbchandler.h" namespace Ui { class DBCMessageEditor; } class DBCMessageEditor : public QDialog { Q_OBJECT public: explicit DBCMessageEditor(QWidget *parent = nullptr); ~DBCMessageEditor(); void showEvent(QShowEvent*); void setMessageRef(DBC_MESSAGE *msg); void setFileIdx(int idx); void refreshView(); signals: void updatedTreeInfo(DBC_MESSAGE *msg); private: Ui::DBCMessageEditor *ui; DBCHandler *dbcHandler; DBC_MESSAGE *dbcMessage; DBCFile *dbcFile; bool suppressEditCallbacks; void closeEvent(QCloseEvent *event); bool eventFilter(QObject *obj, QEvent *event); void readSettings(); void writeSettings(); void generateSampleText(); }; #endif // DBCMESSAGEEDITOR_H savvycan-220/dbc/dbcnodeduplicateeditor.cpp000066400000000000000000000133721500724750100211540ustar00rootroot00000000000000#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; } savvycan-220/dbc/dbcnodeduplicateeditor.h000066400000000000000000000017511500724750100206170ustar00rootroot00000000000000#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 savvycan-220/dbc/dbcnodeeditor.cpp000066400000000000000000000075261500724750100172650ustar00rootroot00000000000000#include "dbcnodeeditor.h" #include "ui_dbcnodeeditor.h" #include #include #include #include "helpwindow.h" #include "utility.h" DBCNodeEditor::DBCNodeEditor(QWidget *parent) : QDialog(parent), ui(new Ui::DBCNodeEditor) { ui->setupUi(this); readSettings(); dbcHandler = DBCHandler::getReference(); dbcNode = nullptr; connect(ui->lineComment, &QLineEdit::editingFinished, [=]() { if (dbcNode == nullptr) return; if (dbcNode->comment != ui->lineComment->text()) dbcFile->setDirtyFlag(); dbcNode->comment = ui->lineComment->text(); emit updatedTreeInfo(dbcNode); }); connect(ui->lineMsgName, &QLineEdit::editingFinished, [=]() { if (dbcNode == nullptr) return; if (dbcNode->name != ui->lineMsgName->text()) dbcFile->setDirtyFlag(); dbcNode->name = ui->lineMsgName->text(); emit updatedTreeInfo(dbcNode); }); installEventFilter(this); } DBCNodeEditor::~DBCNodeEditor() { removeEventFilter(this); delete ui; } void DBCNodeEditor::closeEvent(QCloseEvent *event) { Q_UNUSED(event); writeSettings(); } bool DBCNodeEditor::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 DBCNodeEditor::setFileIdx(int idx) { if (idx < 0 || idx > dbcHandler->getFileCount() - 1) return; dbcFile = dbcHandler->getFileByIdx(idx); } void DBCNodeEditor::readSettings() { QSettings settings; if (settings.value("Main/SaveRestorePositions", false).toBool()) { resize(settings.value("DBCNodeEditor/WindowSize", QSize(312, 128)).toSize()); move(Utility::constrainedWindowPos(settings.value("DBCNodeEditor/WindowPos", QPoint(100, 100)).toPoint())); } } void DBCNodeEditor::writeSettings() { QSettings settings; if (settings.value("Main/SaveRestorePositions", false).toBool()) { settings.setValue("DBCNodeEditor/WindowSize", size()); settings.setValue("DBCNodeEditor/WindowPos", pos()); } } void DBCNodeEditor::setNodeRef(DBC_NODE *node) { dbcNode = node; } void DBCNodeEditor::showEvent(QShowEvent* event) { QDialog::showEvent(event); refreshView(); } void DBCNodeEditor::refreshView() { if(dbcNode) { ui->lineComment->setText(dbcNode->comment); ui->lineMsgName->setText(dbcNode->name); } //generateSampleText(); } void DBCNodeEditor::generateSampleText() { /* QBrush fg, bg; if (dbcMessage->fgColor.isValid()) fg = QBrush(dbcMessage->fgColor); else fg = QBrush(QColor(dbcFile->findAttributeByName("GenMsgForegroundColor")->defaultValue.toString())); if (dbcMessage->bgColor.isValid()) bg = QBrush(dbcMessage->bgColor); else bg = QBrush(QColor(dbcFile->findAttributeByName("GenMsgBackgroundColor")->defaultValue.toString())); ui->listSample->clear(); QListWidgetItem *item = new QListWidgetItem("Test String"); item->setForeground(fg); item->setBackground(bg); ui->listSample->addItem(item); item = new QListWidgetItem("0x20F TestMsg"); item->setForeground(fg); item->setBackground(bg); ui->listSample->addItem(item); item = new QListWidgetItem("20 FF 10 A1 BB CC 4D"); item->setForeground(fg); item->setBackground(bg); ui->listSample->addItem(item); item = new QListWidgetItem("1024.3434"); item->setForeground(fg); item->setBackground(bg); ui->listSample->addItem(item); */ } savvycan-220/dbc/dbcnodeeditor.h000066400000000000000000000014231500724750100167200ustar00rootroot00000000000000#ifndef DBCNODEEDITOR_H #define DBCNODEEDITOR_H #include #include "dbc_classes.h" #include "dbchandler.h" namespace Ui { class DBCNodeEditor; } class DBCNodeEditor : public QDialog { Q_OBJECT public: explicit DBCNodeEditor(QWidget *parent = nullptr); ~DBCNodeEditor(); void showEvent(QShowEvent*); void setNodeRef(DBC_NODE *node); void setFileIdx(int idx); void refreshView(); signals: void updatedTreeInfo(DBC_NODE *node); private: Ui::DBCNodeEditor *ui; DBCHandler *dbcHandler; DBC_NODE *dbcNode; DBCFile *dbcFile; void closeEvent(QCloseEvent *event); bool eventFilter(QObject *obj, QEvent *event); void readSettings(); void writeSettings(); void generateSampleText(); }; #endif // DBCNODEEDITOR_H savvycan-220/dbc/dbcnoderebaseeditor.cpp000066400000000000000000000125141500724750100204400ustar00rootroot00000000000000#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; } savvycan-220/dbc/dbcnoderebaseeditor.h000066400000000000000000000014751500724750100201110ustar00rootroot00000000000000#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 savvycan-220/dbc/dbcsignaleditor.cpp000066400000000000000000000765631500724750100176240ustar00rootroot00000000000000#include "dbcsignaleditor.h" #include "ui_dbcsignaleditor.h" #include #include #include #include #include #include #include #include "helpwindow.h" DBCSignalEditor::DBCSignalEditor(QWidget *parent) : QDialog(parent), ui(new Ui::DBCSignalEditor) { ui->setupUi(this); readSettings(); dbcHandler = DBCHandler::getReference(); dbcMessage = nullptr; currentSignal = nullptr; inhibitMsgProc = false; QStringList headers2; headers2 << "Value" << "Text"; ui->valuesTable->setColumnCount(2); ui->valuesTable->setColumnWidth(0, 200); ui->valuesTable->setColumnWidth(1, 440); ui->valuesTable->setHorizontalHeaderLabels(headers2); ui->valuesTable->horizontalHeader()->setStretchLastSection(true); ui->comboType->addItem("UNSIGNED INTEGER"); ui->comboType->addItem("SIGNED INTEGER"); ui->comboType->addItem("SINGLE PRECISION"); ui->comboType->addItem("DOUBLE PRECISION"); ui->comboType->addItem("STRING"); ui->bitfield->setMode(GridMode::SIGNAL_VIEW); 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))); //now with 100% more lambda expressions just to make it interesting (and shorter, and easier...) connect(ui->cbIntelFormat, &QCheckBox::toggled, [=]() { if (currentSignal == nullptr) return; 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(); pushToUndoBuffer(); currentSignal->receiver = node; } }); connect(ui->comboType, &QComboBox::currentTextChanged, [=]() { if (currentSignal == nullptr) return; switch (ui->comboType->currentIndex()) { case 0: if (currentSignal->valType != UNSIGNED_INT) { pushToUndoBuffer(); currentSignal->valType = UNSIGNED_INT; dbcFile->setDirtyFlag(); fillSignalForm(currentSignal); } break; case 1: if (currentSignal->valType != SIGNED_INT) { pushToUndoBuffer(); currentSignal->valType = SIGNED_INT; dbcFile->setDirtyFlag(); fillSignalForm(currentSignal); } break; case 2: 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: 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: if (currentSignal->valType != STRING) { pushToUndoBuffer(); currentSignal->valType = STRING; dbcFile->setDirtyFlag(); fillSignalForm(currentSignal); } break; } }); connect(ui->txtBias, &QLineEdit::editingFinished, [=]() { if (currentSignal == nullptr) return; double temp; bool result; temp = ui->txtBias->text().toDouble(&result); if (result) { if (currentSignal->bias != temp) { pushToUndoBuffer(); dbcFile->setDirtyFlag(); currentSignal->bias = temp; } } }); connect(ui->txtMaxVal, &QLineEdit::editingFinished, [=]() { if (currentSignal == nullptr) return; double temp; bool result; temp = ui->txtMaxVal->text().toDouble(&result); if (result) { if (currentSignal->max != temp) { pushToUndoBuffer(); dbcFile->setDirtyFlag(); currentSignal->max = temp; } } }); connect(ui->txtMinVal, &QLineEdit::editingFinished, [=]() { if (currentSignal == nullptr) return; double temp; bool result; temp = ui->txtMinVal->text().toDouble(&result); if (result) { if (currentSignal->min != temp) { pushToUndoBuffer(); dbcFile->setDirtyFlag(); currentSignal->min = temp; } } }); connect(ui->txtScale, &QLineEdit::editingFinished, [=]() { if (currentSignal == nullptr) return; double temp; bool result; temp = ui->txtScale->text().toDouble(&result); if (result) { 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(' ','_')) { 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(' ','_')) { pushToUndoBuffer(); dbcFile->setDirtyFlag(); currentSignal->unitName = ui->txtUnitName->text().simplified().replace(' ', '_'); } }); connect(ui->txtBitLength, &QLineEdit::textChanged, [=]() { if (currentSignal == nullptr) return; int temp; temp = Utility::ParseStringToNum(ui->txtBitLength->text()); if (temp < 1) return; 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); refreshBitGrid(); } }); connect(ui->txtName, &QLineEdit::editingFinished, [=]() { if (currentSignal == nullptr) return; QString tempNameStr = ui->txtName->text().simplified().replace(' ', '_'); 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->txtMultiplexValues, &QLineEdit::editingFinished, [=]() { if (currentSignal == nullptr) return; int temp; if (currentSignal->multiplexDbcString(DBC_SIGNAL::MuxStringFormat_UI) != ui->txtMultiplexValues->text()) { //TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return QString errorString; if (!currentSignal->parseDbcMultiplexUiString(ui->txtMultiplexValues->text(), errorString)) { QMessageBox::critical(this, tr("Error"), tr("The multiplex values field contains errors:\n%1").arg(errorString)); } else { pushToUndoBuffer(); dbcFile->setDirtyFlag(); } } }); connect(ui->rbExtended, &QRadioButton::toggled, [=](bool state) { 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->txtMultiplexValues->setEnabled(currentSignal->isMultiplexed); ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed); fillSignalForm(currentSignal); } }); connect(ui->rbMultiplexed, &QRadioButton::toggled, [=](bool state) { 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->txtMultiplexValues->setEnabled(currentSignal->isMultiplexed); ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed); fillSignalForm(currentSignal); } }); connect(ui->rbMultiplexor, &QRadioButton::toggled, [=](bool state) { 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 = true; //we just set that this is the multiplexor so update the message to show that as well. dbcMessage->multiplexorSignal = currentSignal; ui->txtMultiplexValues->setEnabled(currentSignal->isMultiplexed); ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed); fillSignalForm(currentSignal); } }); connect(ui->rbNotMulti, &QRadioButton::toggled, [=](bool state) { 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->txtMultiplexValues->setEnabled(currentSignal->isMultiplexed); ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed); fillSignalForm(currentSignal); } }); connect(ui->cbMultiplexParent, &QComboBox::textActivated, [=]() { 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 && oldParent && (newSig != oldParent)) { pushToUndoBuffer(); dbcFile->setDirtyFlag(); oldParent->multiplexedChildren.removeOne(currentSignal); currentSignal->multiplexParent = newSig; newSig->multiplexedChildren.append(currentSignal); refreshBitGrid(); emit updatedTreeInfo(currentSignal); } }); installEventFilter(this); } DBCSignalEditor::~DBCSignalEditor() { removeEventFilter(this); delete ui; } void DBCSignalEditor::closeEvent(QCloseEvent *event) { Q_UNUSED(event); writeSettings(); } bool DBCSignalEditor::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("signaleditor.md"); break; case Qt::Key_Z: if (keyEvent->modifiers() == Qt::ControlModifier) { popFromUndoBuffer(); } break; } return true; } else { // standard event processing return QObject::eventFilter(obj, event); } return false; } void DBCSignalEditor::setFileIdx(int idx) { if (idx < 0 || idx > dbcHandler->getFileCount() - 1) return; dbcFile = dbcHandler->getFileByIdx(idx); for (int x = 0; x < dbcFile->dbc_nodes.count(); x++) { ui->comboReceiver->addItem(dbcFile->dbc_nodes[x].name); } } void DBCSignalEditor::readSettings() { QSettings settings; if (settings.value("Main/SaveRestorePositions", false).toBool()) { resize(settings.value("DBCSignalEditor/WindowSize", QSize(1000, 600)).toSize()); move(Utility::constrainedWindowPos(settings.value("DBCSignalEditor/WindowPos", QPoint(100, 100)).toPoint())); } } void DBCSignalEditor::writeSettings() { QSettings settings; if (settings.value("Main/SaveRestorePositions", false).toBool()) { settings.setValue("DBCSignalEditor/WindowSize", size()); settings.setValue("DBCSignalEditor/WindowPos", pos()); } } void DBCSignalEditor::setMessageRef(DBC_MESSAGE *msg) { dbcMessage = msg; } void DBCSignalEditor::setSignalRef(DBC_SIGNAL *sig) { currentSignal = sig; } void DBCSignalEditor::showEvent(QShowEvent* event) { QDialog::showEvent(event); fillSignalForm(currentSignal); fillValueTable(currentSignal); } void DBCSignalEditor::refreshView() { fillSignalForm(currentSignal); fillValueTable(currentSignal); } void DBCSignalEditor::onValuesCellChanged(int row,int col) { if (inhibitCellChanged) return; if (row == ui->valuesTable->rowCount() - 1) { DBC_VAL_ENUM_ENTRY newVal; newVal.value = 0; newVal.descript = "No Description"; currentSignal->valList.append(newVal); qDebug() << "Created new entry in value list"; //QTableWidgetItem *widgetVal = new QTableWidgetItem(QString::number(newVal.value)); //ui->valuesTable->setItem(row, 0, widgetVal); //QTableWidgetItem *widgetDesc = new QTableWidgetItem(newVal.descript); //ui->valuesTable->setItem(row, 1, widgetDesc); //add the blank at the end again ui->valuesTable->insertRow(ui->valuesTable->rowCount()); } if (col == 0) { currentSignal->valList[row].value = Utility::ParseStringToNum(ui->valuesTable->item(row, col)->text()); } else if (col == 1) { currentSignal->valList[row].descript = ui->valuesTable->item(row, col)->text().simplified().replace(' ', '_'); } } void DBCSignalEditor::onCustomMenuValues(QPoint point) { QMenu *menu = new QMenu(this); menu->setAttribute(Qt::WA_DeleteOnClose); menu->addAction(tr("Delete currently selected value"), this, SLOT(deleteCurrentValue())); menu->popup(ui->valuesTable->mapToGlobal(point)); } void DBCSignalEditor::deleteCurrentValue() { int currIdx = ui->valuesTable->currentRow(); if (currIdx > -1) { ui->valuesTable->removeRow(currIdx); currentSignal->valList.removeAt(currIdx); } } /* fillSignalForm also handles group "enabled" state */ /* WARNING: fillSignalForm can be called recursively since it is in the listener of cbIntelFormat */ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig) { //sanity checks if (!dbcMessage) return; if (!dbcMessage->sigHandler) return; inhibitMsgProc = true; if (sig == nullptr) { //ui->groupBox->setEnabled(false); ui->txtName->setText(""); ui->txtBias->setText(""); ui->txtBitLength->setText(""); ui->txtComment->setText(""); ui->txtMaxVal->setText(""); ui->txtMinVal->setText(""); ui->txtScale->setText(""); ui->txtUnitName->setText(""); ui->txtMultiplexValues->setText(""); ui->rbMultiplexed->setChecked(false); ui->rbMultiplexor->setChecked(false); ui->rbNotMulti->setChecked(true); ui->comboReceiver->setCurrentIndex(0); ui->comboType->setCurrentIndex(0); inhibitMsgProc = false; return; } /* we have a signal */ //ui->groupBox->setEnabled(true); ui->txtName->setText(sig->name); ui->txtBias->setText(QString::number(sig->bias)); ui->txtBitLength->setText(QString::number(sig->signalSize)); ui->txtMultiplexValues->setText(sig->multiplexDbcString(DBC_SIGNAL::MuxStringFormat_UI)); ui->txtComment->setText(sig->comment); ui->txtMaxVal->setText(QString::number(sig->max)); ui->txtMinVal->setText(QString::number(sig->min)); ui->txtScale->setText(QString::number(sig->factor)); ui->txtUnitName->setText(sig->unitName); if (sig->isMultiplexed && sig->isMultiplexor) { ui->rbMultiplexed->setChecked(false); ui->rbMultiplexor->setChecked(false); ui->rbNotMulti->setChecked(false); ui->rbExtended->setChecked(true); } else { ui->rbMultiplexed->setChecked(sig->isMultiplexed); ui->rbMultiplexor->setChecked(sig->isMultiplexor); ui->rbNotMulti->setChecked( !(sig->isMultiplexor | sig->isMultiplexed) ); } qDebug() << sig->isMultiplexor << "*" << sig->isMultiplexed; ui->cbMultiplexParent->clear(); int numSigs = dbcMessage->sigHandler->getCount(); for (int i = 0; i < numSigs; i++) { DBC_SIGNAL *sig_iter = dbcMessage->sigHandler->findSignalByIdx(i); if (sig_iter && sig_iter->isMultiplexor && (sig_iter != sig)) { //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); } } ui->txtMultiplexValues->setEnabled(sig->isMultiplexed); ui->cbMultiplexParent->setEnabled(sig->isMultiplexed); ui->cbIntelFormat->setChecked(sig->intelByteOrder); switch (sig->valType) { case UNSIGNED_INT: ui->comboType->setCurrentIndex(0); break; case SIGNED_INT: ui->comboType->setCurrentIndex(1); break; case SP_FLOAT: ui->comboType->setCurrentIndex(2); break; case DP_FLOAT: ui->comboType->setCurrentIndex(3); break; case STRING: ui->comboType->setCurrentIndex(4); break; } for (int i = 0; i < ui->comboReceiver->count(); i++) { if (ui->comboReceiver->itemText(i) == sig->receiver->name) { ui->comboReceiver->setCurrentIndex(i); break; } } 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->multiplexesIdenticalToSignal(currentSignal)) )) { 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) { int rowIdx; inhibitCellChanged = true; ui->valuesTable->clearContents(); ui->valuesTable->setRowCount(0); if (sig == nullptr) { ui->valuesTable->setEnabled(false); inhibitCellChanged = false; return; } ui->valuesTable->setEnabled(true); for (int i = 0; i < sig->valList.count(); i++) { QTableWidgetItem *val = new QTableWidgetItem(Utility::formatNumber((uint64_t)sig->valList[i].value)); QTableWidgetItem *desc = new QTableWidgetItem(sig->valList[i].descript); rowIdx = ui->valuesTable->rowCount(); ui->valuesTable->insertRow(rowIdx); ui->valuesTable->setItem(rowIdx, 0, val); ui->valuesTable->setItem(rowIdx, 1, desc); } //Add a blank row at the end to allow for adding records easily ui->valuesTable->insertRow(ui->valuesTable->rowCount()); inhibitCellChanged = false; } //Left clicking the grid sets the "starting bit" for the current signal void DBCSignalEditor::bitfieldLeftClicked(int bit) { if (currentSignal == nullptr) return; pushToUndoBuffer(); currentSignal->startBit = bit; if (currentSignal->valType == SP_FLOAT) { 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) { 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[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); //only pay attention to this signal if it's multiplexParent matches currentSignal or is null if (sig->multiplexParent) { if (sig->multiplexParent != currentSignal->multiplexParent) continue; //go thee away! if (!sig->multiplexesIdenticalToSignal(currentSignal)) continue; //buzz off } startBit = sig->startBit; if (sig->intelByteOrder) { endBit = startBit + sig->signalSize - 1; if (startBit < 0) startBit = 0; int maxBit = (dbcMessage->len * 8) - 1; if (endBit > maxBit) endBit = maxBit; for (int y = startBit; y <= endBit; y++) { int byt = y / 8; usedBits[byt] |= 1 << (y % 8); ui->bitfield->setUsedSignalNum(y, x); } } else //big endian / motorola format { //much more irritating than the intel version... int size = sig->signalSize; while (size > 0) { int byt = startBit / 8; usedBits[byt] |= 1 << (startBit % 8); ui->bitfield->setUsedSignalNum(startBit, x); size--; if ((startBit % 8) == 0) startBit += 15; else startBit--; 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); } savvycan-220/dbc/dbcsignaleditor.h000066400000000000000000000024761500724750100172610ustar00rootroot00000000000000#ifndef DBCSIGNALEDITOR_H #define DBCSIGNALEDITOR_H #include #include "dbchandler.h" #include "utility.h" namespace Ui { class DBCSignalEditor; } class DBCSignalEditor : public QDialog { Q_OBJECT public: explicit DBCSignalEditor(QWidget *parent = 0); void setMessageRef(DBC_MESSAGE *msg); void setFileIdx(int idx); void setSignalRef(DBC_SIGNAL *sig); void showEvent(QShowEvent*); ~DBCSignalEditor(); void refreshView(); signals: void updatedTreeInfo(DBC_SIGNAL *sig); private slots: void bitfieldLeftClicked(int bit); void bitfieldRightClicked(int bit); void onValuesCellChanged(int row,int col); void onCustomMenuValues(QPoint); void deleteCurrentValue(); private: Ui::DBCSignalEditor *ui; DBCHandler *dbcHandler; DBC_MESSAGE *dbcMessage; DBC_SIGNAL *currentSignal; QList undoBuffer; DBCFile *dbcFile; bool inhibitCellChanged; bool inhibitMsgProc; 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 savvycan-220/examples/000077500000000000000000000000001500724750100150305ustar00rootroot00000000000000savvycan-220/examples/BusMasterLog.log000066400000000000000000024640651500724750100201230ustar00rootroot00000000000000***BUSMASTER Ver 2.4.0*** ***PROTOCOL CAN*** ***NOTE: PLEASE DO NOT EDIT THIS DOCUMENT*** ***[START LOGGING SESSION]*** ***START DATE AND TIME 23:9:2015 22:17:28:928*** ***HEX*** ***SYSTEM MODE*** ***START CHANNEL BAUD RATE*** ***CHANNEL 1 - Kvaser - Kvaser Leaf Light HS #0 (Channel 0), Serial Number- 0, Firmware- 0x00000037 0x00020000 - 500000 bps*** ***END CHANNEL BAUD RATE*** ***START DATABASE FILES (DBF/DBC)*** ***END OF DATABASE FILES (DBF/DBC)*** ***