├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── DataLoadCAN ├── dataload_can.cpp └── dataload_can.h ├── DataStreamCAN ├── connectdialog.cpp ├── connectdialog.h ├── connectdialog.ui ├── datastream_can.cpp └── datastream_can.h ├── LICENSE ├── PluginsCommonCAN ├── CanFrameProcessor.cpp ├── CanFrameProcessor.h ├── N2kMsg │ ├── GenericFastPacket.c │ ├── GenericFastPacket.h │ ├── N2kMsgBase.h │ ├── N2kMsgFast.h │ ├── N2kMsgInterface.h │ └── N2kMsgStandard.h ├── select_can_database.cpp ├── select_can_database.h └── select_can_database.ui ├── README.md ├── cmake └── FindPlotJuggler.cmake ├── datasamples ├── README.md ├── test_rav4h.dbc └── test_rav4h.log └── docs ├── CanPluginInclude.png ├── DataLoadCAN.png └── DatabaseLoaded.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | **/build 35 | **/.flatpak-builder 36 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/dbcppp"] 2 | path = 3rdparty/dbcppp 3 | url = https://github.com/ozzdemir/dbcppp.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | 3 | if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR ) 4 | # Standalone build, call project and look for PlotJuggler 5 | project(plotjuggler_can_plugins) 6 | 7 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 8 | find_package(PlotJuggler REQUIRED) 9 | else() 10 | # Included from project (assuming from PlotJuggler) 11 | # Set PlotJuggler_LIBRARY manually since find_package fails in this configuration. 12 | set(PlotJuggler_LIBRARY plotjuggler_base) 13 | endif() 14 | 15 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 16 | set(CMAKE_AUTOMOC ON) 17 | SET(CMAKE_AUTOUIC ON) 18 | 19 | set(CMAKE_CXX_STANDARD 17) 20 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 21 | 22 | if(APPLE AND EXISTS /usr/local/opt/qt5) 23 | # Homebrew installs Qt5 (up to at least 5.9.1) in 24 | # /usr/local/qt5, ensure it can be found by CMake since 25 | # it is not in the default /usr/local prefix. 26 | # source: https://github.com/Homebrew/homebrew-core/issues/8392#issuecomment-325226494 27 | list(APPEND CMAKE_PREFIX_PATH "/usr/local/opt/qt5") 28 | set(CMAKE_MACOSX_RPATH 1) 29 | endif() 30 | 31 | find_package(Qt5 REQUIRED COMPONENTS 32 | Core 33 | Widgets 34 | Concurrent 35 | Xml 36 | Svg 37 | ) 38 | find_package(Qt5SerialBus OPTIONAL_COMPONENTS 39 | SerialBus 40 | ) 41 | 42 | include_directories( 43 | ${Qt5Core_INCLUDE_DIRS} 44 | ${Qt5Widgets_INCLUDE_DIRS} 45 | ${Qt5Concurrent_INCLUDE_DIRS} 46 | ${Qt5Xml_INCLUDE_DIRS} 47 | ${PlotJuggler_INCLUDE_DIRS} 48 | ${Qt5Svg_INCLUDE_DIRS} 49 | 3rdparty/dbcppp/include 50 | ) 51 | 52 | set(PJ_LIBRARIES 53 | Qt5::Core 54 | Qt5::Widgets 55 | Qt5::Xml 56 | Qt5::Concurrent 57 | Qt5::Svg 58 | ${PlotJuggler_LIBRARY} 59 | ) 60 | 61 | add_definitions(${QT_DEFINITIONS}) 62 | add_definitions(-DQT_PLUGIN) 63 | 64 | add_subdirectory(3rdparty/dbcppp) 65 | 66 | add_library(CanFrameProcessor STATIC 67 | PluginsCommonCAN/CanFrameProcessor.cpp 68 | PluginsCommonCAN/N2kMsg/GenericFastPacket.c 69 | PluginsCommonCAN/select_can_database.h 70 | PluginsCommonCAN/select_can_database.cpp 71 | ) 72 | target_link_libraries(CanFrameProcessor 73 | ${PJ_LIBRARIES} 74 | libdbcppp 75 | ) 76 | set_property(TARGET CanFrameProcessor PROPERTY POSITION_INDEPENDENT_CODE ON) 77 | 78 | add_library(DataLoadCAN SHARED 79 | DataLoadCAN/dataload_can.h 80 | DataLoadCAN/dataload_can.cpp) 81 | 82 | target_link_libraries(DataLoadCAN 83 | ${PJ_LIBRARIES} 84 | CanFrameProcessor 85 | ) 86 | install(TARGETS DataLoadCAN DESTINATION bin) 87 | 88 | # Build&Install DataStreamCAN only if SerialBus found. 89 | if(${Qt5SerialBus_FOUND}) 90 | message("-- Found Qt5::SerialBus, building DataStreamCAN.") 91 | add_library(DataStreamCAN SHARED 92 | DataStreamCAN/datastream_can.h 93 | DataStreamCAN/datastream_can.cpp 94 | DataStreamCAN/connectdialog.h 95 | DataStreamCAN/connectdialog.cpp) 96 | target_link_libraries(DataStreamCAN 97 | ${PJ_LIBRARIES} 98 | CanFrameProcessor 99 | Qt5::SerialBus 100 | ) 101 | install(TARGETS DataStreamCAN DESTINATION bin) 102 | else() 103 | message("-- Since Qt5::SerialBus not found, cannot build DataStreamCAN.") 104 | endif() 105 | -------------------------------------------------------------------------------- /DataLoadCAN/dataload_can.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include "dataload_can.h" 14 | #include "../PluginsCommonCAN/select_can_database.h" 15 | 16 | // Regular expression for log files created by candump -L 17 | // Captured groups: time, channel, frame_id, payload 18 | const QRegularExpression canlog_rgx("\\((\\d*\\.\\d*)\\)\\s*([\\S]*)\\s*([0-9a-fA-F]{3,8})\\#([0-9a-fA-F]*)"); 19 | 20 | DataLoadCAN::DataLoadCAN() 21 | { 22 | extensions_.push_back("log"); 23 | } 24 | 25 | const std::vector& DataLoadCAN::compatibleFileExtensions() const 26 | { 27 | return extensions_; 28 | } 29 | 30 | bool DataLoadCAN::loadCANDatabase(PlotDataMapRef& plot_data_map, std::string dbc_file_location, 31 | CanFrameProcessor::CanProtocol protocol) 32 | { 33 | std::ifstream dbc_file{ dbc_file_location }; 34 | frame_processor_ = std::make_unique(dbc_file, plot_data_map, protocol); 35 | return true; 36 | } 37 | 38 | QSize DataLoadCAN::inspectFile(QFile* file) 39 | { 40 | QTextStream inA(file); 41 | int linecount = 0; 42 | 43 | while (!inA.atEnd()) 44 | { 45 | inA.readLine(); 46 | linecount++; 47 | } 48 | 49 | QSize table_size; 50 | table_size.setWidth(4); 51 | table_size.setHeight(linecount); 52 | 53 | return table_size; 54 | } 55 | 56 | bool DataLoadCAN::readDataFromFile(FileLoadInfo* fileload_info, PlotDataMapRef& plot_data_map) 57 | { 58 | bool use_provided_configuration = false; 59 | 60 | if (fileload_info->plugin_config.hasChildNodes()) 61 | { 62 | use_provided_configuration = true; 63 | xmlLoadState(fileload_info->plugin_config.firstChildElement()); 64 | } 65 | 66 | const int TIME_INDEX_NOT_DEFINED = -2; 67 | 68 | int time_index = TIME_INDEX_NOT_DEFINED; 69 | 70 | QFile file(fileload_info->filename); 71 | file.open(QFile::ReadOnly); 72 | 73 | std::vector column_names; 74 | 75 | const QSize table_size = inspectFile(&file); 76 | const int tot_lines = table_size.height() - 1; 77 | const int columncount = table_size.width(); 78 | file.close(); 79 | 80 | DialogSelectCanDatabase* dialog = new DialogSelectCanDatabase(); 81 | 82 | if (dialog->exec() != static_cast(QDialog::Accepted)) 83 | { 84 | return false; 85 | } 86 | loadCANDatabase(plot_data_map, dialog->GetDatabaseLocation().toStdString(), dialog->GetCanProtocol()); 87 | 88 | file.open(QFile::ReadOnly); 89 | QTextStream inB(&file); 90 | 91 | bool interrupted = false; 92 | 93 | int linecount = 0; 94 | 95 | QProgressDialog progress_dialog; 96 | progress_dialog.setLabelText("Loading... please wait"); 97 | progress_dialog.setWindowModality(Qt::ApplicationModal); 98 | progress_dialog.setRange(0, tot_lines); 99 | progress_dialog.setAutoClose(true); 100 | progress_dialog.setAutoReset(true); 101 | progress_dialog.show(); 102 | 103 | bool monotonic_warning = false; 104 | // To have . as decimal seperator, save current locale and change it. 105 | const auto oldLocale = std::setlocale(LC_NUMERIC, nullptr); 106 | std::setlocale(LC_NUMERIC, "C"); 107 | while (!inB.atEnd()) 108 | { 109 | QString line = inB.readLine(); 110 | static QRegularExpressionMatchIterator rxIterator; 111 | rxIterator = canlog_rgx.globalMatch(line); 112 | if (!rxIterator.hasNext()) 113 | { 114 | continue; // skip invalid lines 115 | } 116 | QRegularExpressionMatch canFrame = rxIterator.next(); 117 | uint64_t frameId = std::stoul(canFrame.captured(3).toStdString(), 0, 16); 118 | double frameTime = std::stod(canFrame.captured(1).toStdString()); 119 | 120 | int dlc = canFrame.capturedLength(4) / 2; 121 | std::string frameDataString; 122 | // When dlc is less than 8, right padding is required 123 | if (dlc < 8) 124 | { 125 | std::string padding = std::string(2 * (8 - dlc), '0'); 126 | frameDataString = canFrame.captured(4).toStdString().append(padding); 127 | } 128 | else 129 | { 130 | frameDataString = canFrame.captured(4).toStdString(); 131 | } 132 | 133 | uint64_t frameData = std::stoul(frameDataString, 0, 16); 134 | uint8_t frameDataBytes[8]; 135 | std::memcpy(frameDataBytes, &frameData, 8); 136 | std::reverse(frameDataBytes, frameDataBytes + 8); 137 | frame_processor_->ProcessCanFrame(frameId, frameDataBytes, 8, frameTime); 138 | //------ progress dialog -------------- 139 | if (linecount++ % 100 == 0) 140 | { 141 | progress_dialog.setValue(linecount); 142 | QApplication::processEvents(); 143 | 144 | if (progress_dialog.wasCanceled()) 145 | { 146 | return false; 147 | } 148 | } 149 | } 150 | // Restore locale setting 151 | std::setlocale(LC_NUMERIC, oldLocale); 152 | file.close(); 153 | 154 | if (interrupted) 155 | { 156 | progress_dialog.cancel(); 157 | plot_data_map.numeric.clear(); 158 | } 159 | 160 | if (monotonic_warning) 161 | { 162 | QString message = 163 | "Two consecutive samples had the same X value (i.e. time).\n" 164 | "Since PlotJuggler makes the assumption that timeseries are strictly monotonic, you " 165 | "might experience undefined behaviours.\n\n" 166 | "You have been warned..."; 167 | QMessageBox::warning(0, tr("Warning"), message); 168 | } 169 | 170 | return true; 171 | } 172 | 173 | DataLoadCAN::~DataLoadCAN() 174 | { 175 | } 176 | 177 | bool DataLoadCAN::xmlSaveState(QDomDocument& doc, QDomElement& parent_element) const 178 | { 179 | QDomElement elem = doc.createElement("default"); 180 | elem.setAttribute("time_axis", default_time_axis_.c_str()); 181 | 182 | parent_element.appendChild(elem); 183 | return true; 184 | } 185 | 186 | bool DataLoadCAN::xmlLoadState(const QDomElement& parent_element) 187 | { 188 | QDomElement elem = parent_element.firstChildElement("default"); 189 | if (!elem.isNull()) 190 | { 191 | if (elem.hasAttribute("time_axis")) 192 | { 193 | default_time_axis_ = elem.attribute("time_axis").toStdString(); 194 | return true; 195 | } 196 | } 197 | return false; 198 | } 199 | -------------------------------------------------------------------------------- /DataLoadCAN/dataload_can.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "../PluginsCommonCAN/CanFrameProcessor.h" 7 | 8 | using namespace PJ; 9 | 10 | class DataLoadCAN : public DataLoader 11 | { 12 | Q_OBJECT 13 | Q_PLUGIN_METADATA(IID "facontidavide.PlotJuggler3.DataLoader") 14 | Q_INTERFACES(PJ::DataLoader) 15 | 16 | public: 17 | DataLoadCAN(); 18 | virtual const std::vector &compatibleFileExtensions() const override; 19 | virtual QSize inspectFile(QFile *file); 20 | virtual bool readDataFromFile(FileLoadInfo *fileload_info, PlotDataMapRef &plot_data_map) override; 21 | bool loadCANDatabase(PlotDataMapRef &plot_data_map, std::string dbc_file_location, CanFrameProcessor::CanProtocol protocol); 22 | virtual ~DataLoadCAN(); 23 | 24 | virtual const char *name() const override 25 | { 26 | return "DataLoad CAN"; 27 | } 28 | 29 | virtual bool xmlSaveState(QDomDocument &doc, QDomElement &parent_element) const override; 30 | virtual bool xmlLoadState(const QDomElement &parent_element) override; 31 | 32 | private: 33 | std::vector extensions_; 34 | std::string default_time_axis_; 35 | std::unique_ptr frame_processor_; 36 | }; 37 | -------------------------------------------------------------------------------- /DataStreamCAN/connectdialog.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2017 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the QtSerialBus module. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #include "connectdialog.h" 52 | #include "ui_connectdialog.h" 53 | #include "../PluginsCommonCAN/select_can_database.h" 54 | 55 | #include 56 | 57 | ConnectDialog::ConnectDialog(QWidget *parent) : QDialog(parent), 58 | m_ui(new Ui::ConnectDialog) 59 | { 60 | m_ui->setupUi(this); 61 | 62 | m_ui->errorFilterEdit->setValidator(new QIntValidator(0, 0x1FFFFFFFU, this)); 63 | 64 | m_ui->loopbackBox->addItem(tr("unspecified"), QVariant()); 65 | m_ui->loopbackBox->addItem(tr("false"), QVariant(false)); 66 | m_ui->loopbackBox->addItem(tr("true"), QVariant(true)); 67 | m_ui->loopbackBox->setCurrentIndex(1); 68 | 69 | m_ui->receiveOwnBox->addItem(tr("unspecified"), QVariant()); 70 | m_ui->receiveOwnBox->addItem(tr("false"), QVariant(false)); 71 | m_ui->receiveOwnBox->addItem(tr("true"), QVariant(true)); 72 | m_ui->receiveOwnBox->setCurrentIndex(1); 73 | 74 | m_ui->bitrateBox->addItem(tr("125000"), QVariant(true)); 75 | m_ui->bitrateBox->addItem(tr("250000"), QVariant(true)); 76 | m_ui->bitrateBox->addItem(tr("500000"), QVariant(true)); 77 | m_ui->bitrateBox->addItem(tr("1000000"), QVariant(true)); 78 | m_ui->bitrateBox->setCurrentIndex(2); 79 | 80 | m_ui->okButton->setEnabled(false); 81 | 82 | connect(m_ui->okButton, &QPushButton::clicked, this, &ConnectDialog::ok); 83 | connect(m_ui->cancelButton, &QPushButton::clicked, this, &ConnectDialog::cancel); 84 | connect(m_ui->backendListBox, &QComboBox::currentTextChanged, 85 | this, &ConnectDialog::backendChanged); 86 | connect(m_ui->interfaceListBox, &QComboBox::currentTextChanged, 87 | this, &ConnectDialog::interfaceChanged); 88 | connect(m_ui->loadDatabaseButton, &QPushButton::clicked, 89 | this, &ConnectDialog::importDatabaseLocation); 90 | m_ui->rawFilterEdit->hide(); 91 | m_ui->rawFilterLabel->hide(); 92 | 93 | m_ui->backendListBox->addItems(QCanBus::instance()->plugins()); 94 | 95 | updateSettings(); 96 | } 97 | 98 | ConnectDialog::~ConnectDialog() 99 | { 100 | delete m_ui; 101 | } 102 | 103 | ConnectDialog::Settings ConnectDialog::settings() const 104 | { 105 | return m_currentSettings; 106 | } 107 | 108 | void ConnectDialog::backendChanged(const QString &backend) 109 | { 110 | m_ui->interfaceListBox->clear(); 111 | m_interfaces = QCanBus::instance()->availableDevices(backend); 112 | for (const QCanBusDeviceInfo &info : qAsConst(m_interfaces)) 113 | m_ui->interfaceListBox->addItem(info.name()); 114 | } 115 | 116 | void ConnectDialog::interfaceChanged(const QString &interface) 117 | { 118 | 119 | for (const QCanBusDeviceInfo &info : qAsConst(m_interfaces)) 120 | { 121 | if (info.name() == interface) 122 | { 123 | break; 124 | } 125 | } 126 | } 127 | 128 | void ConnectDialog::ok() 129 | { 130 | updateSettings(); 131 | accept(); 132 | } 133 | 134 | void ConnectDialog::cancel() 135 | { 136 | revertSettings(); 137 | reject(); 138 | } 139 | 140 | QString ConnectDialog::configurationValue(QCanBusDevice::ConfigurationKey key) 141 | { 142 | QVariant result; 143 | 144 | for (const ConfigurationItem &item : qAsConst(m_currentSettings.configurations)) 145 | { 146 | if (item.first == key) 147 | { 148 | result = item.second; 149 | break; 150 | } 151 | } 152 | 153 | if (result.isNull() && (key == QCanBusDevice::LoopbackKey || 154 | key == QCanBusDevice::ReceiveOwnKey)) 155 | { 156 | return tr("unspecified"); 157 | } 158 | 159 | return result.toString(); 160 | } 161 | 162 | void ConnectDialog::revertSettings() 163 | { 164 | m_ui->backendListBox->setCurrentText(m_currentSettings.backendName); 165 | m_ui->interfaceListBox->setCurrentText(m_currentSettings.deviceInterfaceName); 166 | 167 | QString value = configurationValue(QCanBusDevice::LoopbackKey); 168 | m_ui->loopbackBox->setCurrentText(value); 169 | 170 | value = configurationValue(QCanBusDevice::ReceiveOwnKey); 171 | m_ui->receiveOwnBox->setCurrentText(value); 172 | 173 | value = configurationValue(QCanBusDevice::ErrorFilterKey); 174 | m_ui->errorFilterEdit->setText(value); 175 | 176 | value = configurationValue(QCanBusDevice::BitRateKey); 177 | m_ui->bitrateBox->setCurrentText(value); 178 | } 179 | 180 | void ConnectDialog::updateSettings() 181 | { 182 | m_currentSettings.backendName = m_ui->backendListBox->currentText(); 183 | m_currentSettings.deviceInterfaceName = m_ui->interfaceListBox->currentText(); 184 | 185 | if (m_currentSettings.useConfigurationEnabled) 186 | { 187 | m_currentSettings.configurations.clear(); 188 | // process LoopBack 189 | if (m_ui->loopbackBox->currentIndex() != 0) 190 | { 191 | ConfigurationItem item; 192 | item.first = QCanBusDevice::LoopbackKey; 193 | item.second = m_ui->loopbackBox->currentData(); 194 | m_currentSettings.configurations.append(item); 195 | } 196 | 197 | // process ReceiveOwnKey 198 | if (m_ui->receiveOwnBox->currentIndex() != 0) 199 | { 200 | ConfigurationItem item; 201 | item.first = QCanBusDevice::ReceiveOwnKey; 202 | item.second = m_ui->receiveOwnBox->currentData(); 203 | m_currentSettings.configurations.append(item); 204 | } 205 | 206 | // process error filter 207 | if (!m_ui->errorFilterEdit->text().isEmpty()) 208 | { 209 | QString value = m_ui->errorFilterEdit->text(); 210 | bool ok = false; 211 | int dec = value.toInt(&ok); 212 | if (ok) 213 | { 214 | ConfigurationItem item; 215 | item.first = QCanBusDevice::ErrorFilterKey; 216 | item.second = QVariant::fromValue(QCanBusFrame::FrameErrors(dec)); 217 | m_currentSettings.configurations.append(item); 218 | } 219 | } 220 | 221 | // process raw filter list 222 | if (!m_ui->rawFilterEdit->text().isEmpty()) 223 | { 224 | //TODO current ui not sfficient to reflect this param 225 | } 226 | 227 | // process bitrate 228 | const int bitrate = m_ui->bitrateBox->currentText().toInt(); 229 | if (bitrate > 0) 230 | { 231 | const ConfigurationItem item(QCanBusDevice::BitRateKey, QVariant(bitrate)); 232 | m_currentSettings.configurations.append(item); 233 | } 234 | } 235 | } 236 | 237 | void ConnectDialog::importDatabaseLocation() 238 | { 239 | DialogSelectCanDatabase* dialog = new DialogSelectCanDatabase(); 240 | if (dialog->exec() != static_cast(QDialog::Accepted)) 241 | { 242 | ConnectDialog::cancel(); 243 | return; 244 | } 245 | 246 | m_currentSettings.canDatabaseLocation = dialog->GetDatabaseLocation(); 247 | m_currentSettings.protocol = dialog->GetCanProtocol(); 248 | // Since file is gotten, enable ok button. 249 | m_ui->okButton->setEnabled(true); 250 | } 251 | -------------------------------------------------------------------------------- /DataStreamCAN/connectdialog.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2017 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the QtSerialBus module. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #ifndef CONNECTDIALOG_H 52 | #define CONNECTDIALOG_H 53 | 54 | #include 55 | #include 56 | 57 | #include 58 | #include 59 | #include 60 | 61 | #include "../PluginsCommonCAN/CanFrameProcessor.h" 62 | 63 | 64 | QT_BEGIN_NAMESPACE 65 | 66 | namespace Ui { 67 | class ConnectDialog; 68 | } 69 | 70 | QT_END_NAMESPACE 71 | 72 | class ConnectDialog : public QDialog 73 | { 74 | Q_OBJECT 75 | 76 | public: 77 | typedef QPair ConfigurationItem; 78 | 79 | struct Settings { 80 | QString backendName; 81 | QString deviceInterfaceName; 82 | QString canDatabaseLocation; 83 | QList configurations; 84 | bool useConfigurationEnabled = false; 85 | CanFrameProcessor::CanProtocol protocol; 86 | }; 87 | 88 | explicit ConnectDialog(QWidget *parent = nullptr); 89 | ~ConnectDialog(); 90 | 91 | Settings settings() const; 92 | 93 | private slots: 94 | void backendChanged(const QString &backend); 95 | void interfaceChanged(const QString &interface); 96 | void ok(); 97 | void cancel(); 98 | 99 | private: 100 | QString configurationValue(QCanBusDevice::ConfigurationKey key); 101 | void revertSettings(); 102 | void updateSettings(); 103 | void importDatabaseLocation(); 104 | 105 | Ui::ConnectDialog *m_ui = nullptr; 106 | Settings m_currentSettings; 107 | QList m_interfaces; 108 | }; 109 | 110 | #endif // CONNECTDIALOG_H 111 | -------------------------------------------------------------------------------- /DataStreamCAN/connectdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ConnectDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 446 10 | 257 11 | 12 | 13 | 14 | Connect 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Qt::Horizontal 23 | 24 | 25 | 26 | 96 27 | 20 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Cancel 36 | 37 | 38 | false 39 | 40 | 41 | 42 | 43 | 44 | 45 | OK 46 | 47 | 48 | false 49 | 50 | 51 | true 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Specify CAN interface name 61 | 62 | 63 | 64 | 65 | 66 | true 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | Select CAN backend 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | Load CAN Database 89 | 90 | 91 | 92 | 93 | 94 | 95 | true 96 | 97 | 98 | Specify Configuration 99 | 100 | 101 | 102 | 103 | 104 | Bitrate 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | Loopback 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | Error Filter 128 | 129 | 130 | 131 | 132 | 133 | 134 | RAW Filter 135 | 136 | 137 | 138 | 139 | 140 | 141 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 142 | 143 | 144 | FrameError bits 145 | 146 | 147 | 148 | 149 | 150 | 151 | Receive Own 152 | 153 | 154 | 155 | 156 | 157 | 158 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /DataStreamCAN/datastream_can.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "datastream_can.h" 16 | 17 | using namespace PJ; 18 | 19 | DataStreamCAN::DataStreamCAN() : connect_dialog_{ new ConnectDialog() } 20 | { 21 | connect(connect_dialog_, &QDialog::accepted, this, &DataStreamCAN::connectCanInterface); 22 | } 23 | 24 | void DataStreamCAN::connectCanInterface() 25 | { 26 | const ConnectDialog::Settings p = connect_dialog_->settings(); 27 | 28 | QString errorString; 29 | can_interface_ = QCanBus::instance()->createDevice(p.backendName, p.deviceInterfaceName, &errorString); 30 | if (!can_interface_) 31 | { 32 | qDebug() << tr("Error creating device '%1', reason: '%2'").arg(p.backendName).arg(errorString); 33 | return; 34 | } 35 | 36 | if (p.useConfigurationEnabled) 37 | { 38 | for (const ConnectDialog::ConfigurationItem& item : p.configurations) 39 | can_interface_->setConfigurationParameter(item.first, item.second); 40 | } 41 | 42 | if (!can_interface_->connectDevice()) 43 | { 44 | qDebug() << tr("Connection error: %1").arg(can_interface_->errorString()); 45 | 46 | delete can_interface_; 47 | can_interface_ = nullptr; 48 | } 49 | else 50 | { 51 | std::ifstream dbc_file{ p.canDatabaseLocation.toStdString() }; 52 | frame_processor_ = std::make_unique(dbc_file, dataMap(), p.protocol); 53 | 54 | QVariant bitRate = can_interface_->configurationParameter(QCanBusDevice::BitRateKey); 55 | if (bitRate.isValid()) 56 | { 57 | qDebug() << tr("Backend: %1, connected to %2 at %3 kBit/s") 58 | .arg(p.backendName) 59 | .arg(p.deviceInterfaceName) 60 | .arg(bitRate.toInt() / 1000); 61 | } 62 | else 63 | { 64 | qDebug() << tr("Backend: %1, connected to %2").arg(p.backendName).arg(p.deviceInterfaceName); 65 | } 66 | } 67 | } 68 | 69 | bool DataStreamCAN::start(QStringList*) 70 | { 71 | if (running_) { 72 | return running_; 73 | } 74 | connect_dialog_->show(); 75 | int res = connect_dialog_->exec(); 76 | if (res != QDialog::Accepted) 77 | { 78 | return false; 79 | } 80 | thread_ = std::thread([this]() { this->loop(); }); 81 | return true; 82 | } 83 | 84 | void DataStreamCAN::shutdown() 85 | { 86 | running_ = false; 87 | if (thread_.joinable()) 88 | thread_.join(); 89 | } 90 | 91 | bool DataStreamCAN::isRunning() const 92 | { 93 | return running_; 94 | } 95 | 96 | DataStreamCAN::~DataStreamCAN() 97 | { 98 | shutdown(); 99 | } 100 | 101 | bool DataStreamCAN::xmlSaveState(QDomDocument& doc, QDomElement& parent_element) const 102 | { 103 | return true; 104 | } 105 | 106 | bool DataStreamCAN::xmlLoadState(const QDomElement& parent_element) 107 | { 108 | return true; 109 | } 110 | 111 | void DataStreamCAN::pushSingleCycle() 112 | { 113 | std::lock_guard lock(mutex()); 114 | 115 | // Since readAllFrames is introduced in Qt5.12, reading using for 116 | auto n_frames = can_interface_->framesAvailable(); 117 | for (int i = 0; i < n_frames; i++) 118 | { 119 | auto frame = can_interface_->readFrame(); 120 | double timestamp = frame.timeStamp().seconds() + frame.timeStamp().microSeconds() * 1e-6; 121 | frame_processor_->ProcessCanFrame(frame.frameId(), (const uint8_t*)frame.payload().data(), 8, timestamp); 122 | } 123 | } 124 | 125 | void DataStreamCAN::loop() 126 | { 127 | // Block until both are initalized 128 | while (can_interface_ == nullptr || frame_processor_ == nullptr) 129 | { 130 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 131 | } 132 | running_ = true; 133 | while (running_) 134 | { 135 | auto prev = std::chrono::high_resolution_clock::now(); 136 | pushSingleCycle(); 137 | emit dataReceived(); 138 | std::this_thread::sleep_until(prev + std::chrono::milliseconds(20)); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /DataStreamCAN/datastream_can.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "connectdialog.h" 11 | #include "../PluginsCommonCAN/CanFrameProcessor.h" 12 | 13 | 14 | class DataStreamCAN : public PJ::DataStreamer 15 | { 16 | Q_OBJECT 17 | Q_PLUGIN_METADATA(IID "facontidavide.PlotJuggler3.DataStreamer") 18 | Q_INTERFACES(PJ::DataStreamer) 19 | 20 | public: 21 | DataStreamCAN(); 22 | bool start(QStringList*) override; 23 | void shutdown() override; 24 | bool isRunning() const override; 25 | ~DataStreamCAN() override; 26 | 27 | const char* name() const override 28 | { 29 | return "CAN Streamer"; 30 | } 31 | 32 | bool isDebugPlugin() override 33 | { 34 | return false; 35 | } 36 | 37 | bool xmlSaveState(QDomDocument& doc, QDomElement& parent_element) const override; 38 | bool xmlLoadState(const QDomElement& parent_element) override; 39 | 40 | private slots: 41 | void connectCanInterface(); 42 | 43 | private: 44 | ConnectDialog *connect_dialog_; 45 | QCanBusDevice *can_interface_ = nullptr; 46 | std::unique_ptr frame_processor_; 47 | 48 | std::thread thread_; 49 | bool running_; 50 | void loop(); 51 | void pushSingleCycle(); 52 | }; 53 | 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Davide Faconti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PluginsCommonCAN/CanFrameProcessor.cpp: -------------------------------------------------------------------------------- 1 | #include "CanFrameProcessor.h" 2 | #include "N2kMsg/GenericFastPacket.h" 3 | 4 | CanFrameProcessor::CanFrameProcessor(std::ifstream& dbc_file, PJ::PlotDataMapRef& data_map, CanProtocol protocol) 5 | : protocol_{ protocol }, data_map_{ data_map } 6 | { 7 | can_network_ = dbcppp::INetwork::LoadDBCFromIs(dbc_file); 8 | messages_.clear(); 9 | for (const dbcppp::IMessage& msg : can_network_->Messages()) 10 | { 11 | if (protocol_ == CanProtocol::RAW) { 12 | // When protocol is raw, use can_id from the dbc as the key for the messages 13 | messages_.insert(std::make_pair(msg.Id(), &msg)); 14 | } 15 | else { 16 | // When protocol is not raw, use PGN as the key for the messages_ 17 | messages_.insert(std::make_pair(PGN_FROM_FRAME_ID(msg.Id()), &msg)); 18 | // For N2kMsgFast, MessageSize is certainly larger than 8 bytes 19 | if (protocol_ == CanProtocol::NMEA2K) 20 | { 21 | if (msg.MessageSize() > 8) 22 | { 23 | fast_packet_pgns_set_.insert(PGN_FROM_FRAME_ID(msg.Id())); 24 | } 25 | } 26 | } 27 | } 28 | } 29 | 30 | bool CanFrameProcessor::ProcessCanFrame(const uint32_t frame_id, const uint8_t* payload_ptr, const size_t data_len, 31 | double timestamp_secs) 32 | { 33 | if (can_network_) 34 | { 35 | switch (protocol_) 36 | { 37 | case CanProtocol::RAW: 38 | { 39 | return ProcessCanFrameRaw(frame_id, payload_ptr, data_len, timestamp_secs); 40 | } 41 | case CanProtocol::NMEA2K: 42 | { 43 | return ProcessCanFrameN2k(frame_id, payload_ptr, data_len, timestamp_secs); 44 | } 45 | case CanProtocol::J1939: 46 | { 47 | return ProcessCanFrameJ1939(frame_id, payload_ptr, data_len, timestamp_secs); 48 | } 49 | default: 50 | return false; 51 | } 52 | } 53 | else 54 | { 55 | return false; 56 | } 57 | } 58 | 59 | bool CanFrameProcessor::ProcessCanFrameRaw(const uint32_t frame_id, const uint8_t* data_ptr, const size_t data_len, 60 | const double timestamp_secs) 61 | { 62 | auto messages_iter = messages_.find(frame_id); 63 | if (messages_iter != messages_.end()) 64 | { 65 | const dbcppp::IMessage* msg = messages_iter->second; 66 | for (const dbcppp::ISignal& sig : msg->Signals()) 67 | { 68 | const dbcppp::ISignal* mux_sig = msg->MuxSignal(); 69 | if (sig.MultiplexerIndicator() != dbcppp::ISignal::EMultiplexer::MuxValue || 70 | (mux_sig && (mux_sig->Decode(data_ptr) == sig.MultiplexerSwitchValue()))) 71 | { 72 | double decoded_val = sig.RawToPhys(sig.Decode(data_ptr)); 73 | auto str = QString("can_frames/%1/%2") 74 | .arg(QString::number(msg->Id()), QString::fromStdString(sig.Name())) 75 | .toStdString(); 76 | // qCritical() << str.c_str(); 77 | auto it = data_map_.numeric.find(str); 78 | if (it != data_map_.numeric.end()) 79 | { 80 | auto& plot = it->second; 81 | plot.pushBack({ timestamp_secs, decoded_val }); 82 | } 83 | else 84 | { 85 | auto& plot = data_map_.addNumeric(str)->second; 86 | plot.pushBack({ timestamp_secs, decoded_val }); 87 | } 88 | } 89 | } 90 | return true; 91 | } 92 | else 93 | { 94 | return false; 95 | } 96 | } 97 | bool CanFrameProcessor::ProcessCanFrameN2k(const uint32_t frame_id, const uint8_t* data_ptr, const size_t data_len, 98 | const double timestamp_secs) 99 | { 100 | N2kMsgStandard n2k_msg(frame_id, data_ptr, data_len, timestamp_secs); 101 | 102 | if (fast_packet_pgns_set_.count(n2k_msg.GetPgn())) 103 | { 104 | fp_generic_fast_packet_t fp_unpacked; 105 | fp_generic_fast_packet_unpack(&fp_unpacked, n2k_msg.GetDataPtr(), FP_GENERIC_FAST_PACKET_LENGTH); 106 | 107 | // Find the current fast packet, return null one if it does not exists. 108 | auto current_fp_it = fast_packets_map_.find(n2k_msg.GetFrameId()); 109 | auto& current_fp = current_fp_it != fast_packets_map_.end() ? current_fp_it->second : null_n2k_fast_ptr_; 110 | 111 | if (fp_unpacked.chunk_id == FP_GENERIC_FAST_PACKET_CHUNK_ID_FIRST_CHUNK_CHOICE) 112 | { 113 | // First chunk's data is only 6 bytes 114 | fast_packets_map_[n2k_msg.GetFrameId()] = std::make_unique( 115 | n2k_msg.GetFrameId(), n2k_msg.GetDataPtr() + 2, 6ul, timestamp_secs, fp_unpacked.len_bytes); 116 | } 117 | else 118 | { 119 | if (current_fp) 120 | { 121 | current_fp->AppendData(n2k_msg.GetDataPtr() + 1, 7ul); 122 | } 123 | } 124 | if (current_fp && current_fp->IsComplete()) 125 | { 126 | ForwardN2kSignalsToPlot(*current_fp); 127 | current_fp.release(); 128 | return true; 129 | } 130 | } 131 | else 132 | { 133 | ForwardN2kSignalsToPlot(n2k_msg); 134 | return true; 135 | } 136 | return false; 137 | } 138 | bool CanFrameProcessor::ProcessCanFrameJ1939(const uint32_t frame_id, const uint8_t* data_ptr, const size_t data_len, 139 | const double timestamp_secs) 140 | { 141 | N2kMsgStandard n2k_msg(frame_id, data_ptr, data_len, timestamp_secs); 142 | ForwardN2kSignalsToPlot(n2k_msg); 143 | return true; 144 | } 145 | 146 | void CanFrameProcessor::ForwardN2kSignalsToPlot(const N2kMsgInterface& n2k_msg) 147 | { 148 | auto protocol_prefix = protocol_ == CanProtocol::NMEA2K ? QString("nmea2k_msg") : QString("j1939_msg"); 149 | // qCritical() << "frame_id:" << QString::number(dbc_id) << "\tcan_id:" << QString::number(n2k_msg.GetFrameId()); 150 | auto messages_iter = messages_.find(n2k_msg.GetPgn()); 151 | if (messages_iter != messages_.end()) 152 | { 153 | const dbcppp::IMessage* msg = messages_iter->second; 154 | // qCritical() << "msg_name:" << QString::fromStdString(msg->Name()); 155 | for (const dbcppp::ISignal& sig : msg->Signals()) 156 | { 157 | const dbcppp::ISignal* mux_sig = msg->MuxSignal(); 158 | if (sig.MultiplexerIndicator() != dbcppp::ISignal::EMultiplexer::MuxValue || 159 | (mux_sig && (mux_sig->Decode(n2k_msg.GetDataPtr()) == sig.MultiplexerSwitchValue()))) 160 | { 161 | double decoded_val = sig.RawToPhys(sig.Decode(n2k_msg.GetDataPtr())); 162 | std::string ts_name; 163 | if (n2k_msg.GetPduFormat() < 240) 164 | { 165 | auto destination_qstr = QString("%1").arg(n2k_msg.GetPduSpecific(), 2, 16, QLatin1Char('0')).toUpper(); 166 | ts_name = QString("%1/PDUF1/%2 (0x%3)/0x%4/0x%5/%6") 167 | .arg(protocol_prefix, 168 | QString::fromStdString(msg->Name()), 169 | QString("%1").arg(n2k_msg.GetPgn(), 4, 16, QLatin1Char('0')).toUpper(), 170 | QString("%1").arg(n2k_msg.GetSourceAddr(), 2, 16, QLatin1Char('0')).toUpper(), 171 | destination_qstr, QString::fromStdString(sig.Name())) 172 | .toStdString(); 173 | } 174 | else 175 | { 176 | ts_name = QString("%1/PDUF2/%2 (0x%3)/0x%4/%5") 177 | .arg(protocol_prefix, 178 | QString::fromStdString(msg->Name()), 179 | QString("%1").arg(n2k_msg.GetPgn(), 5, 16, QLatin1Char('0')).toUpper(), 180 | QString("%1").arg(n2k_msg.GetSourceAddr(), 2, 16, QLatin1Char('0')).toUpper(), 181 | QString::fromStdString(sig.Name())) 182 | .toStdString(); 183 | } 184 | // qCritical() << str.c_str(); 185 | auto it = data_map_.numeric.find(ts_name); 186 | if (it != data_map_.numeric.end()) 187 | { 188 | auto& plot = it->second; 189 | plot.pushBack({ n2k_msg.GetTimeStamp(), decoded_val }); 190 | } 191 | else 192 | { 193 | auto& plot = data_map_.addNumeric(ts_name)->second; 194 | plot.pushBack({ n2k_msg.GetTimeStamp(), decoded_val }); 195 | } 196 | } 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /PluginsCommonCAN/CanFrameProcessor.h: -------------------------------------------------------------------------------- 1 | #ifndef CAN_FRAME_PROCESSOR_H_ 2 | #define CAN_FRAME_PROCESSOR_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "N2kMsg/N2kMsgStandard.h" 10 | #include "N2kMsg/N2kMsgFast.h" 11 | 12 | class CanFrameProcessor 13 | { 14 | public: 15 | enum CanProtocol 16 | { 17 | RAW, 18 | NMEA2K, 19 | J1939 20 | }; 21 | CanFrameProcessor(std::ifstream& dbc_file, PJ::PlotDataMapRef& data_map, CanProtocol protocol); 22 | 23 | bool ProcessCanFrame(const uint32_t frame_id, const uint8_t* data_ptr, const size_t data_len, 24 | const double timestamp_secs); 25 | 26 | private: 27 | bool ProcessCanFrameRaw(const uint32_t frame_id, const uint8_t* data_ptr, const size_t data_len, 28 | const double timestamp_secs); 29 | bool ProcessCanFrameN2k(const uint32_t frame_id, const uint8_t* data_ptr, const size_t data_len, 30 | const double timestamp_secs); 31 | bool ProcessCanFrameJ1939(const uint32_t frame_id, const uint8_t* data_ptr, const size_t data_len, 32 | const double timestamp_secs); 33 | void ForwardN2kSignalsToPlot(const N2kMsgInterface& n2k_msg); 34 | 35 | // Common 36 | CanProtocol protocol_; 37 | 38 | // Database 39 | std::unique_ptr can_network_ = nullptr; 40 | std::unordered_map messages_; // key of the map is dbc_id 41 | 42 | // PJ 43 | PJ::PlotDataMapRef& data_map_; 44 | 45 | // N2k specialization 46 | std::set fast_packet_pgns_set_; 47 | std::unordered_map> fast_packets_map_; // key of the map is the frame_id 48 | std::unique_ptr null_n2k_fast_ptr_ = nullptr; 49 | }; 50 | #endif // CAN_FRAME_PROCESSOR_H_ 51 | -------------------------------------------------------------------------------- /PluginsCommonCAN/N2kMsg/GenericFastPacket.c: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2018-2019 Erik Moqvist 5 | * 6 | * Permission is hereby granted, free of charge, to any person 7 | * obtaining a copy of this software and associated documentation 8 | * files (the "Software"), to deal in the Software without 9 | * restriction, including without limitation the rights to use, copy, 10 | * modify, merge, publish, distribute, sublicense, and/or sell copies 11 | * of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | /** 28 | * This file was generated by cantools version 36.2.0 Thu Jan 19 18:00:48 2023. 29 | */ 30 | 31 | #include 32 | 33 | #include "GenericFastPacket.h" 34 | 35 | static inline uint8_t pack_left_shift_u8( 36 | uint8_t value, 37 | uint8_t shift, 38 | uint8_t mask) 39 | { 40 | return (uint8_t)((uint8_t)(value << shift) & mask); 41 | } 42 | 43 | static inline uint8_t pack_left_shift_u64( 44 | uint64_t value, 45 | uint8_t shift, 46 | uint8_t mask) 47 | { 48 | return (uint8_t)((uint8_t)(value << shift) & mask); 49 | } 50 | 51 | static inline uint8_t pack_right_shift_u64( 52 | uint64_t value, 53 | uint8_t shift, 54 | uint8_t mask) 55 | { 56 | return (uint8_t)((uint8_t)(value >> shift) & mask); 57 | } 58 | 59 | static inline uint64_t unpack_left_shift_u64( 60 | uint8_t value, 61 | uint8_t shift, 62 | uint8_t mask) 63 | { 64 | return (uint64_t)((uint64_t)(value & mask) << shift); 65 | } 66 | 67 | static inline uint8_t unpack_right_shift_u8( 68 | uint8_t value, 69 | uint8_t shift, 70 | uint8_t mask) 71 | { 72 | return (uint8_t)((uint8_t)(value & mask) >> shift); 73 | } 74 | 75 | static inline uint64_t unpack_right_shift_u64( 76 | uint8_t value, 77 | uint8_t shift, 78 | uint8_t mask) 79 | { 80 | return (uint64_t)((uint64_t)(value & mask) >> shift); 81 | } 82 | 83 | int fp_generic_fast_packet_pack( 84 | uint8_t *dst_p, 85 | const struct fp_generic_fast_packet_t *src_p, 86 | size_t size) 87 | { 88 | if (size < 8u) { 89 | return (-EINVAL); 90 | } 91 | 92 | memset(&dst_p[0], 0, 8); 93 | 94 | dst_p[0] |= pack_left_shift_u8(src_p->chunk_id, 0u, 0x1fu); 95 | dst_p[0] |= pack_left_shift_u8(src_p->sequence_counter, 5u, 0xe0u); 96 | 97 | switch (src_p->chunk_id) { 98 | 99 | case 0: 100 | dst_p[1] |= pack_left_shift_u8(src_p->len_bytes, 0u, 0xffu); 101 | dst_p[2] |= pack_left_shift_u64(src_p->data_first_chunk, 0u, 0xffu); 102 | dst_p[3] |= pack_right_shift_u64(src_p->data_first_chunk, 8u, 0xffu); 103 | dst_p[4] |= pack_right_shift_u64(src_p->data_first_chunk, 16u, 0xffu); 104 | dst_p[5] |= pack_right_shift_u64(src_p->data_first_chunk, 24u, 0xffu); 105 | dst_p[6] |= pack_right_shift_u64(src_p->data_first_chunk, 32u, 0xffu); 106 | dst_p[7] |= pack_right_shift_u64(src_p->data_first_chunk, 40u, 0xffu); 107 | break; 108 | 109 | case 1: 110 | dst_p[1] |= pack_left_shift_u64(src_p->data, 0u, 0xffu); 111 | dst_p[2] |= pack_right_shift_u64(src_p->data, 8u, 0xffu); 112 | dst_p[3] |= pack_right_shift_u64(src_p->data, 16u, 0xffu); 113 | dst_p[4] |= pack_right_shift_u64(src_p->data, 24u, 0xffu); 114 | dst_p[5] |= pack_right_shift_u64(src_p->data, 32u, 0xffu); 115 | dst_p[6] |= pack_right_shift_u64(src_p->data, 40u, 0xffu); 116 | dst_p[7] |= pack_right_shift_u64(src_p->data, 48u, 0xffu); 117 | break; 118 | 119 | default: 120 | break; 121 | } 122 | 123 | return (8); 124 | } 125 | 126 | int fp_generic_fast_packet_unpack( 127 | struct fp_generic_fast_packet_t *dst_p, 128 | const uint8_t *src_p, 129 | size_t size) 130 | { 131 | if (size < 8u) { 132 | return (-EINVAL); 133 | } 134 | 135 | dst_p->chunk_id = unpack_right_shift_u8(src_p[0], 0u, 0x1fu); 136 | dst_p->sequence_counter = unpack_right_shift_u8(src_p[0], 5u, 0xe0u); 137 | 138 | switch (dst_p->chunk_id) { 139 | 140 | case 0: 141 | dst_p->len_bytes = unpack_right_shift_u8(src_p[1], 0u, 0xffu); 142 | dst_p->data_first_chunk = unpack_right_shift_u64(src_p[2], 0u, 0xffu); 143 | dst_p->data_first_chunk |= unpack_left_shift_u64(src_p[3], 8u, 0xffu); 144 | dst_p->data_first_chunk |= unpack_left_shift_u64(src_p[4], 16u, 0xffu); 145 | dst_p->data_first_chunk |= unpack_left_shift_u64(src_p[5], 24u, 0xffu); 146 | dst_p->data_first_chunk |= unpack_left_shift_u64(src_p[6], 32u, 0xffu); 147 | dst_p->data_first_chunk |= unpack_left_shift_u64(src_p[7], 40u, 0xffu); 148 | break; 149 | 150 | case 1: 151 | dst_p->data = unpack_right_shift_u64(src_p[1], 0u, 0xffu); 152 | dst_p->data |= unpack_left_shift_u64(src_p[2], 8u, 0xffu); 153 | dst_p->data |= unpack_left_shift_u64(src_p[3], 16u, 0xffu); 154 | dst_p->data |= unpack_left_shift_u64(src_p[4], 24u, 0xffu); 155 | dst_p->data |= unpack_left_shift_u64(src_p[5], 32u, 0xffu); 156 | dst_p->data |= unpack_left_shift_u64(src_p[6], 40u, 0xffu); 157 | dst_p->data |= unpack_left_shift_u64(src_p[7], 48u, 0xffu); 158 | break; 159 | 160 | default: 161 | break; 162 | } 163 | 164 | return (0); 165 | } 166 | 167 | uint8_t fp_generic_fast_packet_chunk_id_encode(double value) 168 | { 169 | return (uint8_t)(value); 170 | } 171 | 172 | double fp_generic_fast_packet_chunk_id_decode(uint8_t value) 173 | { 174 | return ((double)value); 175 | } 176 | 177 | bool fp_generic_fast_packet_chunk_id_is_in_range(uint8_t value) 178 | { 179 | return (value <= 31u); 180 | } 181 | 182 | uint8_t fp_generic_fast_packet_sequence_counter_encode(double value) 183 | { 184 | return (uint8_t)(value); 185 | } 186 | 187 | double fp_generic_fast_packet_sequence_counter_decode(uint8_t value) 188 | { 189 | return ((double)value); 190 | } 191 | 192 | bool fp_generic_fast_packet_sequence_counter_is_in_range(uint8_t value) 193 | { 194 | return (value <= 7u); 195 | } 196 | 197 | uint8_t fp_generic_fast_packet_len_bytes_encode(double value) 198 | { 199 | return (uint8_t)(value); 200 | } 201 | 202 | double fp_generic_fast_packet_len_bytes_decode(uint8_t value) 203 | { 204 | return ((double)value); 205 | } 206 | 207 | bool fp_generic_fast_packet_len_bytes_is_in_range(uint8_t value) 208 | { 209 | return (value <= 223u); 210 | } 211 | 212 | uint64_t fp_generic_fast_packet_data_encode(double value) 213 | { 214 | return (uint64_t)(value); 215 | } 216 | 217 | double fp_generic_fast_packet_data_decode(uint64_t value) 218 | { 219 | return ((double)value); 220 | } 221 | 222 | bool fp_generic_fast_packet_data_is_in_range(uint64_t value) 223 | { 224 | return (value <= 72057594037927935ull); 225 | } 226 | 227 | uint64_t fp_generic_fast_packet_data_first_chunk_encode(double value) 228 | { 229 | return (uint64_t)(value); 230 | } 231 | 232 | double fp_generic_fast_packet_data_first_chunk_decode(uint64_t value) 233 | { 234 | return ((double)value); 235 | } 236 | 237 | bool fp_generic_fast_packet_data_first_chunk_is_in_range(uint64_t value) 238 | { 239 | return (value <= 281474976710655ull); 240 | } 241 | -------------------------------------------------------------------------------- /PluginsCommonCAN/N2kMsg/GenericFastPacket.h: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2018-2019 Erik Moqvist 5 | * 6 | * Permission is hereby granted, free of charge, to any person 7 | * obtaining a copy of this software and associated documentation 8 | * files (the "Software"), to deal in the Software without 9 | * restriction, including without limitation the rights to use, copy, 10 | * modify, merge, publish, distribute, sublicense, and/or sell copies 11 | * of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | /** 28 | * This file was generated by cantools version 36.2.0 Thu Jan 19 18:00:48 2023. 29 | */ 30 | 31 | #ifndef GENERIC_FAST_PACKET_H 32 | #define GENERIC_FAST_PACKET_H 33 | 34 | #ifdef __cplusplus 35 | extern "C" { 36 | #endif 37 | 38 | #include 39 | #include 40 | #include 41 | 42 | #ifndef EINVAL 43 | # define EINVAL 22 44 | #endif 45 | 46 | /* Frame ids. */ 47 | #define FP_GENERIC_FAST_PACKET_FRAME_ID (0x19f000feu) 48 | 49 | /* Frame lengths in bytes. */ 50 | #define FP_GENERIC_FAST_PACKET_LENGTH (8u) 51 | 52 | /* Extended or standard frame types. */ 53 | #define FP_GENERIC_FAST_PACKET_IS_EXTENDED (1) 54 | 55 | /* Frame cycle times in milliseconds. */ 56 | 57 | 58 | /* Signal choices. */ 59 | #define FP_GENERIC_FAST_PACKET_CHUNK_ID_FIRST_CHUNK_CHOICE (0u) 60 | 61 | /** 62 | * Signals in message GenericFastPacket. 63 | * 64 | * All signal values are as on the CAN bus. 65 | */ 66 | struct fp_generic_fast_packet_t { 67 | /** 68 | * Range: - 69 | * Scale: 1 70 | * Offset: 0 71 | */ 72 | uint8_t chunk_id; 73 | 74 | /** 75 | * Range: - 76 | * Scale: 1 77 | * Offset: 0 78 | */ 79 | uint8_t sequence_counter; 80 | 81 | /** 82 | * Range: 0..223 (0..223 -) 83 | * Scale: 1 84 | * Offset: 0 85 | */ 86 | uint8_t len_bytes; 87 | 88 | /** 89 | * Range: - 90 | * Scale: 1 91 | * Offset: 0 92 | */ 93 | uint64_t data; 94 | 95 | /** 96 | * Range: - 97 | * Scale: 1 98 | * Offset: 0 99 | */ 100 | uint64_t data_first_chunk; 101 | }; 102 | 103 | /** 104 | * Pack message GenericFastPacket. 105 | * 106 | * @param[out] dst_p Buffer to pack the message into. 107 | * @param[in] src_p Data to pack. 108 | * @param[in] size Size of dst_p. 109 | * 110 | * @return Size of packed data, or negative error code. 111 | */ 112 | int fp_generic_fast_packet_pack( 113 | uint8_t *dst_p, 114 | const struct fp_generic_fast_packet_t *src_p, 115 | size_t size); 116 | 117 | /** 118 | * Unpack message GenericFastPacket. 119 | * 120 | * @param[out] dst_p Object to unpack the message into. 121 | * @param[in] src_p Message to unpack. 122 | * @param[in] size Size of src_p. 123 | * 124 | * @return zero(0) or negative error code. 125 | */ 126 | int fp_generic_fast_packet_unpack( 127 | struct fp_generic_fast_packet_t *dst_p, 128 | const uint8_t *src_p, 129 | size_t size); 130 | 131 | /** 132 | * Encode given signal by applying scaling and offset. 133 | * 134 | * @param[in] value Signal to encode. 135 | * 136 | * @return Encoded signal. 137 | */ 138 | uint8_t fp_generic_fast_packet_chunk_id_encode(double value); 139 | 140 | /** 141 | * Decode given signal by applying scaling and offset. 142 | * 143 | * @param[in] value Signal to decode. 144 | * 145 | * @return Decoded signal. 146 | */ 147 | double fp_generic_fast_packet_chunk_id_decode(uint8_t value); 148 | 149 | /** 150 | * Check that given signal is in allowed range. 151 | * 152 | * @param[in] value Signal to check. 153 | * 154 | * @return true if in range, false otherwise. 155 | */ 156 | bool fp_generic_fast_packet_chunk_id_is_in_range(uint8_t value); 157 | 158 | /** 159 | * Encode given signal by applying scaling and offset. 160 | * 161 | * @param[in] value Signal to encode. 162 | * 163 | * @return Encoded signal. 164 | */ 165 | uint8_t fp_generic_fast_packet_sequence_counter_encode(double value); 166 | 167 | /** 168 | * Decode given signal by applying scaling and offset. 169 | * 170 | * @param[in] value Signal to decode. 171 | * 172 | * @return Decoded signal. 173 | */ 174 | double fp_generic_fast_packet_sequence_counter_decode(uint8_t value); 175 | 176 | /** 177 | * Check that given signal is in allowed range. 178 | * 179 | * @param[in] value Signal to check. 180 | * 181 | * @return true if in range, false otherwise. 182 | */ 183 | bool fp_generic_fast_packet_sequence_counter_is_in_range(uint8_t value); 184 | 185 | /** 186 | * Encode given signal by applying scaling and offset. 187 | * 188 | * @param[in] value Signal to encode. 189 | * 190 | * @return Encoded signal. 191 | */ 192 | uint8_t fp_generic_fast_packet_len_bytes_encode(double value); 193 | 194 | /** 195 | * Decode given signal by applying scaling and offset. 196 | * 197 | * @param[in] value Signal to decode. 198 | * 199 | * @return Decoded signal. 200 | */ 201 | double fp_generic_fast_packet_len_bytes_decode(uint8_t value); 202 | 203 | /** 204 | * Check that given signal is in allowed range. 205 | * 206 | * @param[in] value Signal to check. 207 | * 208 | * @return true if in range, false otherwise. 209 | */ 210 | bool fp_generic_fast_packet_len_bytes_is_in_range(uint8_t value); 211 | 212 | /** 213 | * Encode given signal by applying scaling and offset. 214 | * 215 | * @param[in] value Signal to encode. 216 | * 217 | * @return Encoded signal. 218 | */ 219 | uint64_t fp_generic_fast_packet_data_encode(double value); 220 | 221 | /** 222 | * Decode given signal by applying scaling and offset. 223 | * 224 | * @param[in] value Signal to decode. 225 | * 226 | * @return Decoded signal. 227 | */ 228 | double fp_generic_fast_packet_data_decode(uint64_t value); 229 | 230 | /** 231 | * Check that given signal is in allowed range. 232 | * 233 | * @param[in] value Signal to check. 234 | * 235 | * @return true if in range, false otherwise. 236 | */ 237 | bool fp_generic_fast_packet_data_is_in_range(uint64_t value); 238 | 239 | /** 240 | * Encode given signal by applying scaling and offset. 241 | * 242 | * @param[in] value Signal to encode. 243 | * 244 | * @return Encoded signal. 245 | */ 246 | uint64_t fp_generic_fast_packet_data_first_chunk_encode(double value); 247 | 248 | /** 249 | * Decode given signal by applying scaling and offset. 250 | * 251 | * @param[in] value Signal to decode. 252 | * 253 | * @return Decoded signal. 254 | */ 255 | double fp_generic_fast_packet_data_first_chunk_decode(uint64_t value); 256 | 257 | /** 258 | * Check that given signal is in allowed range. 259 | * 260 | * @param[in] value Signal to check. 261 | * 262 | * @return true if in range, false otherwise. 263 | */ 264 | bool fp_generic_fast_packet_data_first_chunk_is_in_range(uint64_t value); 265 | 266 | 267 | #ifdef __cplusplus 268 | } 269 | #endif 270 | 271 | #endif 272 | -------------------------------------------------------------------------------- /PluginsCommonCAN/N2kMsg/N2kMsgBase.h: -------------------------------------------------------------------------------- 1 | #ifndef N2K_MSG_BASE_H_ 2 | #define N2K_MSG_BASE_H_ 3 | 4 | #include "N2kMsgInterface.h" 5 | 6 | struct N2kMsgBase : public N2kMsgInterface 7 | { 8 | N2kMsgBase(const uint32_t frame_id, const double timestamp_secs) 9 | : frame_id_{ frame_id } 10 | , data_len_{ 0 } 11 | , timestamp_secs_{ timestamp_secs } 12 | { 13 | } 14 | uint32_t GetFrameId() const override 15 | { 16 | return frame_id_; 17 | } 18 | uint32_t GetPgn() const override 19 | { 20 | return PGN_FROM_FRAME_ID(frame_id_); 21 | } 22 | size_t GetDataLen() const override 23 | { 24 | return data_len_; 25 | } 26 | uint32_t GetSourceAddr() const override 27 | { 28 | return (frame_id_ & 0xFF); 29 | } 30 | uint32_t GetPduFormat() const override 31 | { 32 | return { (frame_id_ >> 16) & 0xFF }; 33 | }; 34 | uint32_t GetPduSpecific() const override 35 | { 36 | return { (frame_id_ >> 8) & 0xFF }; 37 | } 38 | double GetTimeStamp() const override 39 | { 40 | return timestamp_secs_; 41 | } 42 | 43 | protected: 44 | uint32_t frame_id_; 45 | size_t data_len_; 46 | double timestamp_secs_; 47 | }; 48 | 49 | #endif // N2K_MSG_BASE_H_ 50 | -------------------------------------------------------------------------------- /PluginsCommonCAN/N2kMsg/N2kMsgFast.h: -------------------------------------------------------------------------------- 1 | #ifndef N2K_MSG_FAST_H_ 2 | #define N2K_MSG_FAST_H_ 3 | 4 | #include 5 | #include "N2kMsgBase.h" 6 | 7 | struct N2kMsgFast : public N2kMsgBase 8 | { 9 | N2kMsgFast(const uint32_t frame_id, const uint8_t* data_ptr, const size_t data_len, const double timestamp_secs, 10 | const uint8_t fast_packet_data_len) 11 | : N2kMsgBase(frame_id, timestamp_secs), fast_packet_data_len_{ fast_packet_data_len } 12 | { 13 | size_t data_len_clipped = data_len > max_data_len_ ? max_data_len_ : data_len; 14 | memset(data_, 0xFF, max_data_len_); 15 | memcpy(&data_, data_ptr, data_len_clipped); 16 | data_len_ = data_len_clipped; 17 | } 18 | N2kMsgFast(const N2kMsgFast&) = delete; 19 | const uint8_t* GetDataPtr() const override 20 | { 21 | return data_; 22 | } 23 | bool IsComplete() const 24 | { 25 | return fast_packet_data_len_ == data_len_; 26 | } 27 | bool AppendData(const uint8_t* src_data_ptr, size_t src_data_len) override 28 | { 29 | if (data_len_ < fast_packet_data_len_) 30 | { 31 | if (data_len_ + src_data_len > fast_packet_data_len_) 32 | { 33 | src_data_len = fast_packet_data_len_ - data_len_; 34 | } 35 | } 36 | else 37 | { 38 | src_data_len = 0; 39 | } 40 | if (src_data_len > 0) 41 | { 42 | memcpy(data_ + data_len_, src_data_ptr, src_data_len); 43 | data_len_ += src_data_len; 44 | return true; 45 | } 46 | else 47 | { 48 | return false; 49 | } 50 | }; 51 | 52 | private: 53 | const static uint8_t max_data_len_{ 223 }; 54 | uint8_t fast_packet_data_len_; 55 | uint8_t data_[max_data_len_]; 56 | }; 57 | 58 | #endif // N2K_MSG_FAST_H_ 59 | -------------------------------------------------------------------------------- /PluginsCommonCAN/N2kMsg/N2kMsgInterface.h: -------------------------------------------------------------------------------- 1 | #ifndef N2K_MSG_INTERFACE_H_ 2 | #define N2K_MSG_INTERFACE_H_ 3 | 4 | #include 5 | #include 6 | 7 | // When PDUF1, clear Destination addr 8 | #define PGN_FROM_FRAME_ID(frame_id) uint32_t( ((frame_id >> 16) & 0xFF) >= 240 ? ((frame_id >> 8) & 0x03FFFF) : ((frame_id >> 8) & 0x03FF00) ) 9 | 10 | struct N2kMsgInterface 11 | { 12 | virtual uint32_t GetFrameId() const = 0; 13 | virtual uint32_t GetPgn() const = 0; 14 | virtual size_t GetDataLen() const = 0; 15 | virtual uint32_t GetSourceAddr() const = 0; 16 | virtual uint32_t GetPduFormat() const = 0; 17 | virtual uint32_t GetPduSpecific() const = 0; 18 | virtual const uint8_t* GetDataPtr() const = 0; 19 | virtual double GetTimeStamp() const = 0; 20 | 21 | virtual bool IsComplete() const = 0; 22 | virtual bool AppendData(const uint8_t* src_data_ptr, size_t src_data_len) = 0; 23 | }; 24 | 25 | #endif // N2K_MSG_INTERFACE_H_ 26 | -------------------------------------------------------------------------------- /PluginsCommonCAN/N2kMsg/N2kMsgStandard.h: -------------------------------------------------------------------------------- 1 | #ifndef N2K_MSG_STANDARD_H_ 2 | #define N2K_MSG_STANDARD_H_ 3 | 4 | #include 5 | #include "N2kMsgBase.h" 6 | 7 | struct N2kMsgStandard : public N2kMsgBase 8 | { 9 | N2kMsgStandard(const uint32_t frame_id, const uint8_t* data_ptr, const size_t data_len, const double timestamp_secs) 10 | : N2kMsgBase(frame_id, timestamp_secs) 11 | { 12 | size_t data_len_clipped = data_len > max_data_len_ ? max_data_len_ : data_len; 13 | memset(data_, 0xFF, max_data_len_); 14 | memcpy(&data_, data_ptr, data_len_clipped); 15 | data_len_ = data_len_clipped; 16 | } 17 | N2kMsgStandard(const N2kMsgStandard&) = delete; 18 | const uint8_t* GetDataPtr() const override 19 | { 20 | return data_; 21 | } 22 | bool IsComplete() const 23 | { 24 | return true; 25 | } 26 | bool AppendData(const uint8_t* src_data_ptr, size_t src_data_len) override 27 | { 28 | // Standard msg consist of 8 byte max, so returning false on append data. 29 | return false; 30 | }; 31 | 32 | private: 33 | const static uint8_t max_data_len_{ 8 }; 34 | uint8_t data_[max_data_len_]; 35 | }; 36 | 37 | #endif // N2K_MSG_STANDARD_H_ 38 | -------------------------------------------------------------------------------- /PluginsCommonCAN/select_can_database.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "select_can_database.h" 4 | #include "ui_select_can_database.h" 5 | 6 | DialogSelectCanDatabase::DialogSelectCanDatabase(QWidget* parent) 7 | : QDialog(parent), ui_(new Ui::DialogSelectCanDatabase), database_location_{}, protocol_{} 8 | { 9 | ui_->setupUi(this); 10 | ui_->protocolListBox->addItem(tr("RAW"), QVariant(true)); 11 | ui_->protocolListBox->addItem(tr("NMEA2K"), QVariant(true)); 12 | ui_->protocolListBox->addItem(tr("J1939"), QVariant(true)); 13 | ui_->protocolListBox->setCurrentIndex(0); 14 | ui_->okButton->setEnabled(false); 15 | 16 | connect(ui_->okButton, &QPushButton::clicked, this, &DialogSelectCanDatabase::Ok); 17 | connect(ui_->cancelButton, &QPushButton::clicked, this, &DialogSelectCanDatabase::Cancel); 18 | connect(ui_->loadDatabaseButton, &QPushButton::clicked, this, &DialogSelectCanDatabase::ImportDatabaseLocation); 19 | } 20 | QString DialogSelectCanDatabase::GetDatabaseLocation() const 21 | { 22 | return database_location_; 23 | } 24 | CanFrameProcessor::CanProtocol DialogSelectCanDatabase::GetCanProtocol() const 25 | { 26 | return protocol_; 27 | } 28 | 29 | DialogSelectCanDatabase::~DialogSelectCanDatabase() 30 | { 31 | delete ui_; 32 | } 33 | 34 | void DialogSelectCanDatabase::Ok() 35 | { 36 | auto protocol_text = ui_->protocolListBox->currentText().toStdString(); 37 | if (protocol_text == "RAW") 38 | { 39 | protocol_ = CanFrameProcessor::CanProtocol::RAW; 40 | } 41 | else if (protocol_text == "NMEA2K") 42 | { 43 | protocol_ = CanFrameProcessor::CanProtocol::NMEA2K; 44 | } 45 | else if (protocol_text == "J1939") 46 | { 47 | protocol_ = CanFrameProcessor::CanProtocol::J1939; 48 | } 49 | accept(); 50 | } 51 | 52 | void DialogSelectCanDatabase::Cancel() 53 | { 54 | reject(); 55 | } 56 | 57 | void DialogSelectCanDatabase::ImportDatabaseLocation() 58 | { 59 | database_location_ = 60 | QFileDialog::getOpenFileUrl(Q_NULLPTR, tr("Select CAN database"), QUrl(), tr("CAN database (*.dbc)")) 61 | .toLocalFile(); 62 | // Since file is gotten, enable ok button. 63 | ui_->okButton->setEnabled(true); 64 | } 65 | -------------------------------------------------------------------------------- /PluginsCommonCAN/select_can_database.h: -------------------------------------------------------------------------------- 1 | #ifndef DIALOG_SELECT_CAN_DATABASE_H 2 | #define DIALOG_SELECT_CAN_DATABASE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "../PluginsCommonCAN/CanFrameProcessor.h" 12 | 13 | QT_BEGIN_NAMESPACE 14 | namespace Ui 15 | { 16 | class DialogSelectCanDatabase; 17 | } 18 | QT_END_NAMESPACE 19 | 20 | 21 | class DialogSelectCanDatabase : public QDialog 22 | { 23 | Q_OBJECT 24 | 25 | public: 26 | explicit DialogSelectCanDatabase(QWidget* parent = nullptr); 27 | QString GetDatabaseLocation() const; 28 | CanFrameProcessor::CanProtocol GetCanProtocol() const; 29 | 30 | ~DialogSelectCanDatabase() override; 31 | 32 | private slots: 33 | void Ok(); 34 | void Cancel(); 35 | 36 | private: 37 | Ui::DialogSelectCanDatabase* ui_; 38 | QString database_location_; 39 | CanFrameProcessor::CanProtocol protocol_; 40 | 41 | void ImportDatabaseLocation(); 42 | }; 43 | 44 | #endif // DIALOG_SELECT_CAN_DATABASE_H 45 | -------------------------------------------------------------------------------- /PluginsCommonCAN/select_can_database.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | DialogSelectCanDatabase 4 | 5 | 6 | 7 | 0 8 | 0 9 | 446 10 | 127 11 | 12 | 13 | 14 | Connect 15 | 16 | 17 | 18 | 19 | 20 | Load CAN Database 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Qt::Horizontal 30 | 31 | 32 | 33 | 96 34 | 20 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | Cancel 43 | 44 | 45 | false 46 | 47 | 48 | 49 | 50 | 51 | 52 | OK 53 | 54 | 55 | false 56 | 57 | 58 | true 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Select Protocol 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # plotjuggler-CAN-dbs 2 | Plugins to visualize CAN .dbs files and streams in PlotJuggler. 3 | 4 | Using the plugins, One can; 5 | * Visualize CAN logs created by `candump -L` in PlotJuggler using a can database file (for ex. .dbc) 6 | * Visualize CAN streams 7 | 8 | # Prerequisites 9 | * PlotJuggler (3.2 or later) 10 | * Qt5 SerialBus (Optional, required for DataStreamCAN) 11 | 12 | # Building the CAN plugins 13 | 14 | To build any plugin, PlotJuggler must be installed in your system. 15 | 16 | For instance, in Linux, you should perform a full compilation and installation: 17 | 18 | ```BASH 19 | git clone --recurse-submodules https://github.com/facontidavide/PlotJuggler.git 20 | cd PlotJuggler 21 | mkdir build; cd build 22 | cmake .. 23 | make -j 24 | sudo make install 25 | ``` 26 | After successfull installation of PlotJuggler, one can build DataLoadCAN and DataStreamCAN plugins as follows. 27 | ```BASH 28 | git clone --recurse-submodules https://github.com/PlotJuggler/plotjuggler-CAN-dbs.git 29 | cd plotjuggler-CAN-dbs 30 | mkdir build; cd build 31 | cmake .. 32 | make 33 | 34 | ``` 35 | 36 | An alternative approach is including this project in a local copy of PlotJuggler. This approach might be preferable on Windows or AppImage builds 37 | ```BASH 38 | git clone --recurse-submodules https://github.com/facontidavide/PlotJuggler.git 39 | cd PlotJuggler/plotjuggler_plugins 40 | git clone --recurse-submodules https://github.com/PlotJuggler/plotjuggler-CAN-dbs.git 41 | # Add the following line in the CMakeList.txt of the PlotJuggler, just after other plugin include lines 42 | # add_subdirectory( plotjuggler_plugins/plotjuggler-CAN-dbs ) 43 | cd ../; mkdir build; cd build # Create a build folder in the root of PlotJuggler 44 | cmake .. 45 | make -j 46 | sudo make install # In this approach, system-wide install is optional, you can diretly use the PJ inside the bin with CAN plugins included 47 | ``` 48 | 49 | # Using the plugins 50 | 51 | After building the plugins, one needs to include build directory to the plugins directory of the PlotJuggler. 52 | 53 | App -> Preferences -> Plugins 54 | 55 | ![CanPluginInclude](docs/CanPluginInclude.png "CanPluginInclude snapshot") 56 | 57 | ## DataLoadCAN 58 | 59 | If DataLoadCAN plugin is loaded, you will be able to import `.log` files. When a `.log` file is choosen, another dialog will be opened for selecting the database (`.dbc`) and the protocol (`RAW`, `NMEA2K` or `J1939`). 60 | 61 | ![DataLoadCAN](docs/DataLoadCAN.png "DataLoadCAN snapshot") 62 | 63 | ## DataStreamCAN 64 | 65 | CAN Streamer plugin is only built if Qt5 SerialBus plugin is installed in your machine (surprisingly, it is not possible to install via apt on Ubuntu 18.04). 66 | If you want to use CAN Streamer plugin (and your machine does not have the plugin), you can check [this gist](https://gist.github.com/awesomebytes/ed90785324757b03c8f01e3ffa36d436) for instructions on how to install Qt5 Serialbus. 67 | 68 | When you start CAN Streamer plugin, a connect dialog will be opened as in the figures below. After choosing the correct backend and interface, one need to click `Load CAN Database`, which opens another dialog for selecting the database (`.dbc` only for now) and the protocol (`RAW`, `NMEA2K` or `J1939`). Only after the database dialog is accepted, OK button will be enabled for you to start the streamer. 69 | 70 | ![DataStreamCAN](docs/DatabaseLoaded.png "DataStreamCAN connect, database loaded.") 71 | 72 | # Details about the plugins 73 | 74 | RAW CAN signals are added to the plot in the following format: 75 | 76 | `can_frames//` 77 | 78 | NMEA2K signals are added to the plot in the following formats: 79 | 80 | * When the received PGN is PDU Format 1: 81 | * `nmea2k_msg/PDUF1/ ()///` 82 | * When the received PGN is PDU Format 2 (i.e. broadcast type): 83 | * `nmea2k_msg/PDUF2/ ()//` 84 | 85 | J1939 signals are added to the plot just like the NMEA2K ones, with only difference being the the use of prefix `j1939_msg` instead of `nmea2k_msg`. 86 | -------------------------------------------------------------------------------- /cmake/FindPlotJuggler.cmake: -------------------------------------------------------------------------------- 1 | # - try to find PlotJuggler library 2 | # 3 | # Cache Variables: (probably not for direct use in your scripts) 4 | # PlotJuggler_INCLUDE_DIR 5 | # PlotJuggler_LIBRARY 6 | # 7 | # Non-cache variables you might use in your CMakeLists.txt: 8 | # PlotJuggler_FOUND 9 | # PlotJuggler_INCLUDE_DIRS 10 | # PlotJuggler_LIBRARIES 11 | # PlotJuggler_RUNTIME_LIBRARIES - aka the dll for installing 12 | # PlotJuggler_RUNTIME_LIBRARY_DIRS 13 | # 14 | # Requires these CMake modules: 15 | # FindPackageHandleStandardArgs (known included with CMake >=2.6.2) 16 | # 17 | 18 | set(PlotJuggler_ROOT_DIR 19 | "${PlotJuggler_ROOT_DIR}" 20 | CACHE 21 | PATH 22 | "Directory to search") 23 | 24 | if(CMAKE_SIZEOF_VOID_P MATCHES "8") 25 | set(_LIBSUFFIXES /lib64 /lib) 26 | else() 27 | set(_LIBSUFFIXES /lib) 28 | endif() 29 | 30 | find_library(PlotJuggler_LIBRARY 31 | NAMES 32 | plotjuggler_base 33 | PATHS 34 | "${PlotJuggler_ROOT_DIR}" 35 | PATH_SUFFIXES 36 | "${_LIBSUFFIXES}") 37 | 38 | # Might want to look close to the library first for the includes. 39 | get_filename_component(_libdir "${PlotJuggler_LIBRARY}" PATH) 40 | 41 | find_path(PlotJuggler_INCLUDE_DIR 42 | NAMES 43 | pj_plugin.h 44 | plotdata.h 45 | HINTS 46 | "${_libdir}" # the library I based this on was sometimes bundled right next to its include 47 | "${_libdir}/.." 48 | PATHS 49 | "${PlotJuggler_ROOT_DIR}" 50 | PATH_SUFFIXES 51 | include/PlotJuggler) 52 | 53 | include(FindPackageHandleStandardArgs) 54 | find_package_handle_standard_args(PlotJuggler 55 | DEFAULT_MSG 56 | PlotJuggler_LIBRARY 57 | PlotJuggler_INCLUDE_DIR 58 | ${_deps_check}) 59 | 60 | if(PlotJuggler_FOUND) 61 | set(PlotJuggler_LIBRARIES "${PlotJuggler_LIBRARY}") 62 | set(PlotJuggler_INCLUDE_DIRS "${PlotJuggler_INCLUDE_DIR}/..") 63 | mark_as_advanced(PlotJuggler_ROOT_DIR) 64 | endif() 65 | 66 | mark_as_advanced(PlotJuggler_INCLUDE_DIR 67 | PlotJuggler_LIBRARY 68 | PlotJuggler_RUNTIME_LIBRARY) 69 | 70 | -------------------------------------------------------------------------------- /datasamples/README.md: -------------------------------------------------------------------------------- 1 | # Datasamples 2 | 3 | Included dbc file gotten from [opendbc](https://github.com/commaai/opendbc) but clipped/corrected since their dbc files does not strictly follow the conventions. -------------------------------------------------------------------------------- /datasamples/test_rav4h.dbc: -------------------------------------------------------------------------------- 1 | VERSION "" 2 | 3 | NS_ : 4 | NS_DESC_ 5 | CM_ 6 | BA_DEF_ 7 | BA_ 8 | VAL_ 9 | CAT_DEF_ 10 | CAT_ 11 | FILTER 12 | BA_DEF_DEF_ 13 | EV_DATA_ 14 | ENVVAR_DATA_ 15 | SGTYPE_ 16 | SGTYPE_VAL_ 17 | BA_DEF_SGTYPE_ 18 | BA_SGTYPE_ 19 | SIG_TYPE_REF_ 20 | VAL_TABLE_ 21 | SIG_GROUP_ 22 | SIG_VALTYPE_ 23 | SIGTYPE_VALTYPE_ 24 | BO_TX_BU_ 25 | BA_DEF_REL_ 26 | BA_REL_ 27 | BA_DEF_DEF_REL_ 28 | BU_SG_REL_ 29 | BU_EV_REL_ 30 | BU_BO_REL_ 31 | SG_MUL_VAL_ 32 | 33 | BS_: 34 | 35 | BU_: XXX DSU HCU EPS IPAS CGW 36 | 37 | BO_ 36 KINEMATICS: 8 XXX 38 | SG_ ACCEL_Y : 33|10@0+ (0.03589,-18.375) [0|65535] "m/s^2" XXX 39 | SG_ YAW_RATE : 1|10@0+ (0.244,-125) [0|65535] "deg/sec" XXX 40 | SG_ STEERING_TORQUE : 17|10@0+ (1,-512) [0|65535] "" XXX 41 | 42 | BO_ 37 STEER_ANGLE_SENSOR: 8 XXX 43 | SG_ STEER_ANGLE : 3|12@0- (1.5,0) [-500|500] "deg" XXX 44 | SG_ STEER_FRACTION : 39|4@0- (0.1,0) [-0.7|0.7] "deg" XXX 45 | SG_ STEER_RATE : 35|12@0- (1,0) [-2000|2000] "deg/s" XXX 46 | 47 | BO_ 166 BRAKE: 8 XXX 48 | SG_ BRAKE_AMOUNT : 7|8@0+ (1,0) [0|255] "" XXX 49 | SG_ BRAKE_PEDAL : 23|8@0+ (1,0) [0|255] "" XXX 50 | 51 | BO_ 170 WHEEL_SPEEDS: 8 XXX 52 | SG_ WHEEL_SPEED_FR : 7|16@0+ (0.01,-67.67) [0|250] "kph" XXX 53 | SG_ WHEEL_SPEED_FL : 23|16@0+ (0.01,-67.67) [0|250] "kph" XXX 54 | SG_ WHEEL_SPEED_RR : 39|16@0+ (0.01,-67.67) [0|250] "kph" XXX 55 | SG_ WHEEL_SPEED_RL : 55|16@0+ (0.01,-67.67) [0|250] "kph" XXX 56 | 57 | BO_ 180 SPEED: 8 XXX 58 | SG_ ENCODER : 39|8@0+ (1,0) [0|255] "" XXX 59 | SG_ SPEED : 47|16@0+ (0.01,0) [0|250] "kph" XXX 60 | SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX 61 | 62 | BO_ 353 DSU_SPEED: 8 XXX 63 | SG_ FORWARD_SPEED : 15|16@0- (0.00390625,-30) [0|255] "kph" XXX 64 | 65 | BO_ 452 ENGINE_RPM: 8 CGW 66 | SG_ RPM : 7|16@0- (0.78125,0) [0|0] "rpm" SCS 67 | 68 | BO_ 552 ACCELEROMETER: 4 XXX 69 | SG_ ACCEL_Z : 22|15@0- (1,0) [0|32767] "" XXX 70 | SG_ ACCEL_X : 6|15@0- (0.001,0) [-20|20] "m/s2" XXX 71 | 72 | BO_ 1014 BSM: 8 XXX 73 | SG_ L_ADJACENT : 0|1@0+ (1,0) [0|1] "" XXX 74 | SG_ L_APPROACHING : 8|1@0+ (1,0) [0|1] "" XXX 75 | SG_ R_ADJACENT : 1|1@0+ (1,0) [0|1] "" XXX 76 | SG_ R_APPROACHING : 10|1@0+ (1,0) [0|1] "" XXX 77 | SG_ ADJACENT_ENABLED : 7|1@0+ (1,0) [0|1] "" XXX 78 | SG_ APPROACHING_ENABLED : 15|1@0+ (1,0) [0|1] "" XXX 79 | 80 | BO_ 295 GEAR_PACKET: 8 XXX 81 | SG_ CAR_MOVEMENT : 39|8@0- (1,0) [0|255] "" XXX 82 | SG_ COUNTER : 55|8@0+ (1,0) [0|255] "" XXX 83 | SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX 84 | SG_ GEAR : 47|4@0+ (1,0) [0|15] "" XXX 85 | 86 | BO_ 608 STEER_TORQUE_SENSOR: 8 XXX 87 | SG_ STEER_TORQUE_EPS : 47|16@0- (0.5475,0) [-20000|20000] "" XXX 88 | SG_ STEER_TORQUE_DRIVER : 15|16@0- (1,0) [-32768|32767] "" XXX 89 | SG_ STEER_OVERRIDE : 0|1@0+ (1,0) [0|1] "" XXX 90 | SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX 91 | SG_ STEER_ANGLE : 31|16@0- (0.0573,0) [-500|500] "" XXX 92 | -------------------------------------------------------------------------------- /docs/CanPluginInclude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotJuggler/plotjuggler-CAN-dbs/8f8d62fba7d5b9ad771cb1557ef883764f0d8036/docs/CanPluginInclude.png -------------------------------------------------------------------------------- /docs/DataLoadCAN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotJuggler/plotjuggler-CAN-dbs/8f8d62fba7d5b9ad771cb1557ef883764f0d8036/docs/DataLoadCAN.png -------------------------------------------------------------------------------- /docs/DatabaseLoaded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotJuggler/plotjuggler-CAN-dbs/8f8d62fba7d5b9ad771cb1557ef883764f0d8036/docs/DatabaseLoaded.png --------------------------------------------------------------------------------