@@ -8,18 +8,18 @@ on:
|
||||
jobs:
|
||||
buildlinux:
|
||||
name: Linux x64
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
sudo apt-get install libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-randr0
|
||||
sudo apt-get install libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-randr0 libxkbcommon-x11-0
|
||||
|
||||
- name: Prepare Qt Libraries
|
||||
uses: jurplel/install-qt-action@v2
|
||||
uses: jurplel/install-qt-action@v3
|
||||
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Compile
|
||||
run: |
|
||||
@@ -29,18 +29,18 @@ jobs:
|
||||
- name: Package
|
||||
run: |
|
||||
make INSTALL_ROOT=appdir install
|
||||
wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/5/linuxdeployqt-5-x86_64.AppImage"
|
||||
chmod a+x linuxdeployqt-5-x86_64.AppImage
|
||||
./linuxdeployqt-5-x86_64.AppImage appdir/usr/share/applications/SavvyCAN.desktop -appimage -extra-plugins=iconengines,platformthemes/libqgtk3.so,canbus
|
||||
wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
|
||||
chmod a+x linuxdeployqt-continuous-x86_64.AppImage
|
||||
./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/share/applications/SavvyCAN.desktop -appimage -extra-plugins=iconengines,platformthemes/libqgtk3.so,canbus
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: SavvyCAN-Linux_x64.AppImage
|
||||
path: SavvyCAN-x86_64.AppImage
|
||||
path: SavvyCAN-*x86_64.AppImage
|
||||
|
||||
buildmacos:
|
||||
name: macOS x64
|
||||
runs-on: macos-10.15
|
||||
runs-on: macos-12
|
||||
|
||||
steps:
|
||||
- name: Prepare macOS Environment
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
brew link qt5 --force
|
||||
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Compile
|
||||
run: |
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
cp -R help/* SavvyCAN.app/Contents/MacOS/help
|
||||
macdeployqt SavvyCAN.app -dmg
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: SavvyCAN-macOS_x64.dmg
|
||||
path: SavvyCAN.dmg
|
||||
@@ -73,10 +73,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Prepare Qt Libraries
|
||||
uses: jurplel/install-qt-action@v2
|
||||
uses: jurplel/install-qt-action@v3
|
||||
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Compile
|
||||
shell: cmd
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
mkdir package/canbus
|
||||
copy "${Env:Qt5_Dir}/plugins/canbus/*.*" package/canbus/
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: SavvyCAN-Windows_x64
|
||||
path: package
|
||||
@@ -124,12 +124,12 @@ jobs:
|
||||
needs: [buildwindows, buildmacos, buildlinux]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v2
|
||||
- uses: actions/download-artifact@v3
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: zip -r SavvyCAN_CIBuild.zip SavvyCAN-Windows_x64
|
||||
run: zip -r SavvyCAN-Windows_x64_CIBuild.zip SavvyCAN-Windows_x64
|
||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
@@ -137,8 +137,8 @@ jobs:
|
||||
prerelease: true
|
||||
title: "Development Build"
|
||||
files: |
|
||||
SavvyCAN-Linux_x64.AppImage/SavvyCAN-x86_64.AppImage
|
||||
SavvyCAN_CIBuild.zip
|
||||
SavvyCAN-Linux_x64.AppImage/SavvyCAN-*x86_64.AppImage
|
||||
SavvyCAN-Windows_x64_CIBuild.zip
|
||||
SavvyCAN-macOS_x64.dmg/SavvyCAN.dmg
|
||||
|
||||
notify:
|
||||
@@ -155,6 +155,8 @@ jobs:
|
||||
)
|
||||
release_msg="${release_msg//$'\n'/'-'}"
|
||||
release_msg="${release_msg//$'\r'/'-'}"
|
||||
release_msg="${release_msg//$'('/'-'}"
|
||||
release_msg="${release_msg//$')'/'-'}"
|
||||
echo $release_msg
|
||||
echo "::set-output name=msg::$release_msg"
|
||||
|
||||
|
||||
@@ -30,9 +30,19 @@ build/
|
||||
*.out
|
||||
*.app
|
||||
*.user
|
||||
SavvyCAN
|
||||
|
||||
# Generated files
|
||||
**/moc_*
|
||||
**/ui_*.h
|
||||
**/qrc_*.cpp
|
||||
SavvyCAN.pro.user.*
|
||||
.qmake.stash
|
||||
Makefile
|
||||
|
||||
# Editor files
|
||||
*.code-workspace
|
||||
SavvyCAN.pro.qtds
|
||||
.xcode
|
||||
SavvyCAN.xcodeproj
|
||||
.vscode
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# SavvyCAN
|
||||
Qt based cross platform canbus tool
|
||||
(C) 2015-2021 EVTV and Collin Kidder
|
||||
Qt based cross platform canbus tool
|
||||
(C) 2015-2024 Collin Kidder
|
||||
|
||||
A Qt5 based cross platform tool which can be used to load, save, and capture canbus frames.
|
||||
This tool is designed to help with visualization, reverse engineering, debugging, and
|
||||
@@ -10,16 +10,13 @@ Please use the "Discussions" tab here on GitHub to ask questions and interact wi
|
||||
|
||||
Requires a resolution of at least 1024x768. Fully multi-monitor capable. Works on 4K monitors as well.
|
||||
|
||||
You are highly recommended to use the
|
||||
You are highly recommended to use the
|
||||
[CANDue board from EVTV](http://store.evtv.me/proddetail.php?prod=ArduinoDueCANBUS&cat=23).
|
||||
|
||||
The CANDue board must be running the GVRET firmware which can also be found
|
||||
within the collin80 repos.
|
||||
|
||||
It is now possible to use any Qt SerialBus driver (socketcan, Vector, PeakCAN, TinyCAN).
|
||||
There may, however, be some loss of some functionality as
|
||||
some functions of SavvyCAN are designed for use directly with the
|
||||
EVTVDue and CANDue 2.0 boards.
|
||||
|
||||
It should, however, be noted that use of a capture device is not required to make use
|
||||
of this program. It can load and save in several formats:
|
||||
@@ -43,15 +40,16 @@ from the Qt installer.
|
||||
|
||||
Uses QCustomPlot available at:
|
||||
|
||||
http://www.qcustomplot.com/
|
||||
http://www.qcustomplot.com/
|
||||
|
||||
However, this source code is integrated into the source for SavvyCAN and one isn't required
|
||||
However, this source code is integrated into the source for SavvyCAN and one isn't required
|
||||
to download it separately.
|
||||
|
||||
This project requires 5.14.0 or higher because of a dependency on QtSerialBus and other new additions to Qt.
|
||||
This project requires 5.14.0 or higher because of a dependency on QtSerialBus and other new additions to Qt.
|
||||
|
||||
NOTE: Qt6 currently lacks support for QtSerialBus and many other Qt sub-features. At this time you cannot
|
||||
use Qt6 to compile SavvyCAN. Support for Qt6 should be possible around Qt6.2.
|
||||
NOTE: As the code in this master branch sits, it does compile with QT6. Support for QT6 is approximately "beta" quality. Most all functions should work, please send issues if found.
|
||||
|
||||
It appears that the current binary build for MacOS requires at least MacOS 10.15
|
||||
|
||||
## Instructions for compiling:
|
||||
|
||||
@@ -75,6 +73,8 @@ Now run SavvyCAN
|
||||
./SavvyCAN
|
||||
```
|
||||
|
||||
On linux systems you can run `./install.sh` to create a desktop shortcut.
|
||||
|
||||
### Compiling in debug mode for additional information
|
||||
|
||||
```sh
|
||||
@@ -100,7 +100,7 @@ when you installed Qt.
|
||||
|
||||
### What to do if `qmake` fails with error `Project ERROR: Unknown module(s) in QT: qml serialbus help` on Ubuntu? :
|
||||
|
||||
`sudo apt install libqt5serialbus5-dev qtdeclarative5-dev qttools5-dev`
|
||||
`sudo apt install libqt5serialbus5-dev libqt5serialport5-dev qtdeclarative5-dev qttools5-dev`
|
||||
|
||||
### Used Items Requiring Attribution
|
||||
|
||||
@@ -115,4 +115,3 @@ signal by juli from the Noun Project
|
||||
signal by yudi from the Noun Project
|
||||
|
||||
Death by Adrien Coquet from the Noun Project
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ QT = core gui printsupport qml serialbus serialport widgets help network opengl
|
||||
|
||||
CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT
|
||||
|
||||
CONFIG += c++11
|
||||
CONFIG += c++17
|
||||
CONFIG += NO_UNIT_TESTS
|
||||
|
||||
DEFINES += QCUSTOMPLOT_USE_OPENGL
|
||||
@@ -20,7 +20,13 @@ QMAKE_INFO_PLIST = Info.plist.template
|
||||
ICON = icons/SavvyIcon.icns
|
||||
|
||||
SOURCES += main.cpp\
|
||||
canbridgewindow.cpp \
|
||||
connections/canlogserver.cpp \
|
||||
connections/canserver.cpp \
|
||||
connections/lawicel_serial.cpp \
|
||||
connections/mqtt_bus.cpp \
|
||||
dbc/dbcnodeduplicateeditor.cpp \
|
||||
framesenderobject.cpp \
|
||||
mqtt/qmqtt_client.cpp \
|
||||
mqtt/qmqtt_client_p.cpp \
|
||||
mqtt/qmqtt_frame.cpp \
|
||||
@@ -38,6 +44,7 @@ SOURCES += main.cpp\
|
||||
mainwindow.cpp \
|
||||
canframemodel.cpp \
|
||||
simplecrypt.cpp \
|
||||
triggerdialog.cpp \
|
||||
utility.cpp \
|
||||
qcustomplot.cpp \
|
||||
frameplaybackwindow.cpp \
|
||||
@@ -67,6 +74,7 @@ SOURCES += main.cpp\
|
||||
dbc/dbcmaineditor.cpp \
|
||||
dbc/dbcnodeeditor.cpp \
|
||||
dbc/dbcsignaleditor.cpp \
|
||||
dbc/dbcnoderebaseeditor.cpp \
|
||||
re/discretestatewindow.cpp \
|
||||
re/filecomparatorwindow.cpp \
|
||||
re/flowviewwindow.cpp \
|
||||
@@ -97,9 +105,16 @@ SOURCES += main.cpp\
|
||||
|
||||
HEADERS += mainwindow.h \
|
||||
can_structs.h \
|
||||
canbridgewindow.h \
|
||||
canframemodel.h \
|
||||
connections/canlogserver.h \
|
||||
connections/canserver.h \
|
||||
connections/lawicel_serial.h \
|
||||
connections/socketcand.h \
|
||||
connections/mqtt_bus.h \
|
||||
dbc/dbcnodeduplicateeditor.h \
|
||||
dbc/dbcnoderebaseeditor.h \
|
||||
framesenderobject.h \
|
||||
mqtt/qmqtt.h \
|
||||
mqtt/qmqtt_client.h \
|
||||
mqtt/qmqtt_client_p.h \
|
||||
@@ -122,6 +137,7 @@ HEADERS += mainwindow.h \
|
||||
qcpaxistickerhex.h \
|
||||
re/dbccomparatorwindow.h \
|
||||
simplecrypt.h \
|
||||
triggerdialog.h \
|
||||
utility.h \
|
||||
qcustomplot.h \
|
||||
frameplaybackwindow.h \
|
||||
@@ -183,11 +199,15 @@ HEADERS += mainwindow.h \
|
||||
pcaplite.h
|
||||
|
||||
FORMS += ui/candatagrid.ui \
|
||||
triggerdialog.ui \
|
||||
ui/canbridgewindow.ui \
|
||||
ui/dbcnodeduplicateeditor.ui \
|
||||
ui/dbccomparatorwindow.ui \
|
||||
ui/dbcmessageeditor.ui \
|
||||
ui/connectionwindow.ui \
|
||||
ui/dbcloadsavewindow.ui \
|
||||
ui/dbcmaineditor.ui \
|
||||
ui/dbcnoderebaseeditor.ui \
|
||||
ui/dbcsignaleditor.ui \
|
||||
ui/dbcnodeeditor.ui \
|
||||
ui/discretestatewindow.ui \
|
||||
@@ -210,7 +230,7 @@ FORMS += ui/candatagrid.ui \
|
||||
ui/udsscanwindow.ui \
|
||||
ui/bisectwindow.ui \
|
||||
ui/signalviewerwindow.ui \
|
||||
helpwindow.ui \
|
||||
ui/helpwindow.ui \
|
||||
ui/newconnectiondialog.ui \
|
||||
ui/temporalgraphwindow.ui
|
||||
|
||||
|
||||
@@ -25,7 +25,14 @@ BisectWindow::BisectWindow(const QVector<CANFrame> *frames, QWidget *parent) :
|
||||
connect(ui->slidePercentage, &QSlider::sliderReleased, this, &BisectWindow::updatePercentText);
|
||||
connect(ui->editFrameNumber, &QLineEdit::editingFinished, this, &BisectWindow::updateFrameNumSlider);
|
||||
connect(ui->editPercentage, &QLineEdit::editingFinished, this, &BisectWindow::updatePercentSlider);
|
||||
connect(ui->rbFrameNumber, &QRadioButton::toggled, this, &BisectWindow::updateSectionsText);
|
||||
connect(ui->rbPercentage, &QRadioButton::toggled, this, &BisectWindow::updateSectionsText);
|
||||
connect(ui->rbBusNum, &QRadioButton::toggled, this, &BisectWindow::updateSectionsText);
|
||||
connect(ui->rbIDRange, &QRadioButton::toggled, this, &BisectWindow::updateSectionsText);
|
||||
|
||||
installEventFilter(this);
|
||||
|
||||
updateSectionsText();
|
||||
}
|
||||
|
||||
BisectWindow::~BisectWindow()
|
||||
@@ -61,6 +68,31 @@ bool BisectWindow::eventFilter(QObject *obj, QEvent *event)
|
||||
return false;
|
||||
}
|
||||
|
||||
void BisectWindow::updateSectionsText()
|
||||
{
|
||||
if (ui->rbBusNum->isChecked())
|
||||
{
|
||||
ui->rbLowerSection->setText("Only this bus");
|
||||
ui->rbUpperSection->setText("Not this bus");
|
||||
}
|
||||
if (ui->rbFrameNumber->isChecked())
|
||||
{
|
||||
ui->rbLowerSection->setText("Up to this frame number");
|
||||
ui->rbUpperSection->setText("After this frame number");
|
||||
}
|
||||
if (ui->rbIDRange->isChecked())
|
||||
{
|
||||
ui->rbLowerSection->setText("Inside the ID range");
|
||||
ui->rbUpperSection->setText("Outside the ID range");
|
||||
}
|
||||
if (ui->rbPercentage->isChecked())
|
||||
{
|
||||
ui->rbLowerSection->setText("Up to this percentage into the file");
|
||||
ui->rbUpperSection->setText("After this percentage into the file");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void BisectWindow::refreshIDList()
|
||||
{
|
||||
int id;
|
||||
@@ -112,11 +144,12 @@ void BisectWindow::handleCalculateButton()
|
||||
{
|
||||
splitFrames.clear();
|
||||
bool saveLower = ui->rbLowerSection->isChecked();
|
||||
int targetFrameNum;
|
||||
int targetFrameNum = 0;
|
||||
if (ui->rbFrameNumber->isChecked() || ui->rbPercentage->isChecked())
|
||||
{
|
||||
if (ui->rbFrameNumber->isChecked()) targetFrameNum = ui->slideFrameNumber->value();
|
||||
else targetFrameNum = modelFrames->count() * ui->slidePercentage->value() / 10000;
|
||||
else targetFrameNum = modelFrames->count() * (ui->slidePercentage->value() / 10000.0);
|
||||
qDebug() << "Target frame num " << targetFrameNum;
|
||||
if (saveLower)
|
||||
{
|
||||
for (int i = 0; i < targetFrameNum; i++) splitFrames.append(modelFrames->at(i));
|
||||
@@ -132,7 +165,29 @@ void BisectWindow::handleCalculateButton()
|
||||
uint32_t upperID = Utility::ParseStringToNum2(ui->cbIDUpper->currentText());
|
||||
for (int i = 0; i < modelFrames->count(); i++)
|
||||
{
|
||||
if (modelFrames->at(i).frameId() >= lowerID && modelFrames->at(i).frameId() <= upperID) splitFrames.append(modelFrames->at(i));
|
||||
if (modelFrames->at(i).frameId() >= lowerID && modelFrames->at(i).frameId() <= upperID)
|
||||
{
|
||||
if (saveLower) splitFrames.append(modelFrames->at(i));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!saveLower) splitFrames.append(modelFrames->at(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ui->rbBusNum->isChecked())
|
||||
{
|
||||
int targetBus = Utility::ParseStringToNum(ui->editBusNum->text());
|
||||
for (int i = 0; i < modelFrames->count(); i++)
|
||||
{
|
||||
if (modelFrames->at(i).bus == targetBus)
|
||||
{
|
||||
if (saveLower) splitFrames.append(modelFrames->at(i));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!saveLower) splitFrames.append(modelFrames->at(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
refreshFrameNumbers();
|
||||
|
||||
@@ -30,6 +30,7 @@ private slots:
|
||||
void updatePercentSlider();
|
||||
void updateFrameNumText();
|
||||
void updatePercentText();
|
||||
void updateSectionsText();
|
||||
|
||||
private:
|
||||
Ui::BisectWindow *ui;
|
||||
|
||||
@@ -249,7 +249,7 @@ void ISOTP_HANDLER::processFrame(const CANFrame &frame)
|
||||
messageBuffer.insert(msg.frameId(), msg);
|
||||
//The sending ID is set to the last ID we used to send from this class which is
|
||||
//very likely to be correct. But, caution, there is a chance that it isn't. Beware.
|
||||
if (issueFlowMsgs && lastSenderID > 0)
|
||||
if (issueFlowMsgs && lastSenderID > 0 && lastSenderBus==static_cast<uint32_t>(frame.bus))
|
||||
{
|
||||
CANFrame outFrame;
|
||||
outFrame.bus = lastSenderBus;
|
||||
|
||||
@@ -4,6 +4,17 @@
|
||||
#include "can_structs.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QUuid>
|
||||
|
||||
enum TriggerMask
|
||||
{
|
||||
TRG_ID = 1,
|
||||
TRG_MS = 2,
|
||||
TRG_COUNT = 4,
|
||||
TRG_BUS = 8,
|
||||
TRG_SIGNAL = 16,
|
||||
TRG_SIGVAL = 32,
|
||||
};
|
||||
|
||||
//Stores a single trigger.
|
||||
class Trigger
|
||||
@@ -16,6 +27,10 @@ public:
|
||||
int maxCount; //max # of these frames to trigger for
|
||||
int currCount; //how many we have triggered for so far.
|
||||
int bus; //which bus to monitor (-1 if we aren't picky)
|
||||
QString sigName; //which signal to trigger on
|
||||
int64_t sigValueInt; //value to trigger on (integer)
|
||||
double sigValueDbl; //value to trigger on (floating point)
|
||||
uint32_t triggerMask;
|
||||
};
|
||||
|
||||
//referece for a source location for a single modifier.
|
||||
@@ -24,6 +39,7 @@ public:
|
||||
//If ID is -1 then this is the temporary storage register. This is a shadow
|
||||
//register used to accumulate the results of a multi operation modifier.
|
||||
//if ID is -2 then this is a look up of our own data bytes stored in the class data.
|
||||
//Of course, if the ID is positive then we grab bytes or signals from newest message with that ID
|
||||
class ModifierOperand
|
||||
{
|
||||
public:
|
||||
@@ -31,6 +47,7 @@ public:
|
||||
int bus;
|
||||
int databyte;
|
||||
bool notOper; //should a bitwise NOT be applied to this prior to doing the actual calculation?
|
||||
QString signalName; //if ID is positive and there is text in here then we'll look up the signal and use its value
|
||||
};
|
||||
|
||||
//list of operations that can be done between the two operands
|
||||
@@ -59,7 +76,8 @@ public:
|
||||
class Modifier
|
||||
{
|
||||
public:
|
||||
int destByte;
|
||||
int destByte; //if -1 then target one of this ID's signals instead
|
||||
QString signalName;
|
||||
QList<ModifierOp> operations;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
#include "canbridgewindow.h"
|
||||
#include "helpwindow.h"
|
||||
#include "qevent.h"
|
||||
#include "ui_canbridgewindow.h"
|
||||
#include "filterutility.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
CANBridgeWindow::CANBridgeWindow(const QVector<CANFrame> *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<QKeyEvent *>(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<int32_t>(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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef CANBRIDGEWINDOW_H
|
||||
#define CANBRIDGEWINDOW_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "connections/canconmanager.h"
|
||||
|
||||
namespace Ui {
|
||||
class CANBridgeWindow;
|
||||
}
|
||||
|
||||
class CANBridgeWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CANBridgeWindow(const QVector<CANFrame> *frames, QWidget *parent = nullptr);
|
||||
~CANBridgeWindow();
|
||||
void showEvent(QShowEvent*);
|
||||
|
||||
|
||||
private slots:
|
||||
void updatedFrames(int);
|
||||
void recalcSides();
|
||||
|
||||
private:
|
||||
Ui::CANBridgeWindow *ui;
|
||||
const QVector<CANFrame> *modelFrames;
|
||||
QMap<int, bool> foundIDSide1;
|
||||
QMap<int, bool> foundIDSide2;
|
||||
int side1BusNum;
|
||||
int side2BusNum;
|
||||
|
||||
void processIncomingFrame(CANFrame *frame);
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
|
||||
};
|
||||
|
||||
#endif // CANBRIDGEWINDOW_H
|
||||
@@ -6,6 +6,57 @@
|
||||
#include <QMouseEvent>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
//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<QColor> signalColors =
|
||||
{
|
||||
QColor(101, 104, 159),
|
||||
QColor(237, 35, 109),
|
||||
QColor(216, 53, 221),
|
||||
QColor(37, 252, 220),
|
||||
QColor(8, 160, 53),
|
||||
QColor(226, 111, 11),
|
||||
QColor(216, 59, 2),
|
||||
QColor(249, 246, 27),
|
||||
QColor(214, 21, 34),
|
||||
QColor(178, 219, 74),
|
||||
QColor(214, 55, 71),
|
||||
QColor(80, 198, 206),
|
||||
QColor(83, 214, 201),
|
||||
QColor(35, 210, 237),
|
||||
QColor(25, 170, 105),
|
||||
QColor(110, 221, 55),
|
||||
QColor(30, 38, 181),
|
||||
QColor(79, 124, 209),
|
||||
QColor(7, 91, 175),
|
||||
QColor(14, 234, 208),
|
||||
QColor(125, 45, 168),
|
||||
QColor(176, 211, 80),
|
||||
QColor(255, 206, 48),
|
||||
QColor(202, 226, 93),
|
||||
QColor(201, 99, 232),
|
||||
QColor(13, 72, 135),
|
||||
QColor(32, 45, 219),
|
||||
QColor(197, 26, 216),
|
||||
QColor(119, 15, 216),
|
||||
QColor(97, 18, 150),
|
||||
QColor(242, 16, 242),
|
||||
QColor(177, 5, 196),
|
||||
QColor(214, 10, 102),
|
||||
QColor(9, 50, 155),
|
||||
QColor(201, 38, 76),
|
||||
QColor(202, 234, 72),
|
||||
QColor(59, 191, 57),
|
||||
QColor(0, 25, 170),
|
||||
QColor(7, 186, 93),
|
||||
QColor(29, 211, 147)
|
||||
};
|
||||
|
||||
CANDataGrid::CANDataGrid(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::CANDataGrid)
|
||||
@@ -14,13 +65,17 @@ CANDataGrid::CANDataGrid(QWidget *parent) :
|
||||
|
||||
gridMode = GridMode::CHANGED_BITS;
|
||||
|
||||
memset(data, 0, 8);
|
||||
memset(refData, 0, 8);
|
||||
memset(usedData, 0, 8);
|
||||
memset(usedSignalNum, -1, 64);
|
||||
memset(data, 0, 64);
|
||||
memset(refData, 0, 64);
|
||||
memset(usedData, 0, 64);
|
||||
for (int j = 0; j < 512; j++) usedSignalNum[j] = -1;
|
||||
bytesToDraw = 8; //default to the old behavior
|
||||
for (int x = 0; x < 8; x++)
|
||||
for (int y = 0; y < 8; y++)
|
||||
textStates[x][y] = GridTextState::NORMAL;
|
||||
for (int y = 0; y < 64; y++)
|
||||
{
|
||||
textStates[y][x] = GridTextState::NORMAL;
|
||||
heatData[y * 8 + x] = 0;
|
||||
}
|
||||
|
||||
blackBrush = QBrush(Qt::black);
|
||||
whiteBrush = QBrush(Qt::white);
|
||||
@@ -29,6 +84,29 @@ CANDataGrid::CANDataGrid(QWidget *parent) :
|
||||
greenHashBrush = QBrush(QColor(0, 0xB6, 0), Qt::BDiagPattern);
|
||||
blackHashBrush = QBrush(QColor(0, 0, 0), Qt::FDiagPattern);
|
||||
grayBrush = QBrush(QColor(230,230,230));
|
||||
xOffset = 0;
|
||||
yOffset = 0;
|
||||
|
||||
//generate the palette
|
||||
|
||||
/* //this one looks sort of like a fire. It was my first idea but never really got to where I liked it
|
||||
for(int x = 1; x < 256; x++)
|
||||
{
|
||||
int lum = x * 2;
|
||||
if (lum < 40) lum = 40;
|
||||
if (lum > 250) lum = 250;
|
||||
fire[x] = QColor::fromHsl((int)(x / 3.5), 255 - ((256 - x) / 10), lum);
|
||||
}
|
||||
*/
|
||||
|
||||
for(int x = 1; x < 256; x++)
|
||||
{
|
||||
int hue = 256 - x;
|
||||
fire[x] = QColor::fromHsl(hue, 255, 127);
|
||||
}
|
||||
|
||||
//the 0th entry should always be black and really stand out from the rest
|
||||
fire[0] = Qt::black;
|
||||
}
|
||||
|
||||
CANDataGrid::~CANDataGrid()
|
||||
@@ -46,6 +124,12 @@ void CANDataGrid::setMode(GridMode mode)
|
||||
gridMode = mode;
|
||||
}
|
||||
|
||||
void CANDataGrid::setBytesToDraw(int num)
|
||||
{
|
||||
bytesToDraw = num;
|
||||
//this->update();
|
||||
}
|
||||
|
||||
void CANDataGrid::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
QPoint clickedPoint = event->pos();
|
||||
@@ -55,33 +139,58 @@ void CANDataGrid::mousePressEvent(QMouseEvent *event)
|
||||
clickedPoint -= upperLeft;
|
||||
if (clickedPoint.x() < 0 || clickedPoint.y() < 0)
|
||||
{
|
||||
//qDebug() << "Clicked outside the grid you wanker";
|
||||
//qDebug() << "Clicked outside the grid";
|
||||
return;
|
||||
}
|
||||
int x = clickedPoint.x() / gridSize.x();
|
||||
int y = clickedPoint.y() / gridSize.y();
|
||||
//qDebug() << "Grid square clicked " << x << " " << y;
|
||||
emit gridClicked(x,y);
|
||||
qDebug() << "Grid square clicked " << x << " " << y;
|
||||
int bitClicked = gridToBitPosition(x, y);
|
||||
//this control is the ultimate authority on which bit is at which grid so what we're going to do now
|
||||
//is return the actual bit so everyone else doesn't have to try to calculate it. When someone clicks
|
||||
//what the parent GUI really cares about is which bit that was.
|
||||
emit gridClicked(bitClicked);
|
||||
}
|
||||
if (event->button() == Qt::MiddleButton) //cycle through the various grid layouts for frame sizes
|
||||
{
|
||||
//qDebug() << "Middle Button";
|
||||
if (bytesToDraw < 9) bytesToDraw = 16;
|
||||
else if (bytesToDraw < 31) bytesToDraw = 32;
|
||||
else if (bytesToDraw < 63) bytesToDraw = 64;
|
||||
else if (bytesToDraw > 32) bytesToDraw = 8;
|
||||
this->update();
|
||||
}
|
||||
|
||||
//right click is exactly like left click but the emitted signal is different so that
|
||||
//external code can differentiate which button was pressed
|
||||
if (event->button() == Qt::RightButton)
|
||||
{
|
||||
clickedPoint -= upperLeft;
|
||||
if (clickedPoint.x() < 0 || clickedPoint.y() < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int x = clickedPoint.x() / gridSize.x();
|
||||
int y = clickedPoint.y() / gridSize.y();
|
||||
int bitClicked = gridToBitPosition(x, y);
|
||||
emit gridRightClicked(bitClicked);
|
||||
}
|
||||
}
|
||||
|
||||
void CANDataGrid::setCellTextState(int x, int y, GridTextState state)
|
||||
void CANDataGrid::setCellTextState(int bitPos, GridTextState state)
|
||||
{
|
||||
if (x < 0) return;
|
||||
if (x > 7) return;
|
||||
if (y < 0) return;
|
||||
if (y > 7) return;
|
||||
textStates[x][y] = state;
|
||||
//textStates has two dimensions but they are NOT X and Y and don't necessarily correspond to X and Y in the grid
|
||||
int byte = bitPos / 8;
|
||||
int bit = bitPos & 7;
|
||||
textStates[byte][bit] = state;
|
||||
this->update();
|
||||
}
|
||||
|
||||
GridTextState CANDataGrid::getCellTextState(int x, int y)
|
||||
GridTextState CANDataGrid::getCellTextState(int bitPos)
|
||||
{
|
||||
if (x < 0) return GridTextState::NORMAL;
|
||||
if (x > 7) return GridTextState::NORMAL;
|
||||
if (y < 0) return GridTextState::NORMAL;
|
||||
if (y > 7) return GridTextState::NORMAL;
|
||||
return textStates[x][y];
|
||||
int byte = bitPos / 8;
|
||||
int bit = bitPos & 7;
|
||||
return textStates[byte][bit];
|
||||
}
|
||||
|
||||
void CANDataGrid::setSignalNames(int sigIdx, const QString sigName)
|
||||
@@ -98,20 +207,19 @@ void CANDataGrid::clearSignalNames()
|
||||
{
|
||||
signalNames.clear();
|
||||
signalNames.resize(40);
|
||||
signalColors.clear();
|
||||
}
|
||||
|
||||
void CANDataGrid::setUsedSignalNum(int bit, int signal)
|
||||
{
|
||||
if (bit < 0) return;
|
||||
if (bit > 63) return;
|
||||
if (bit > 511) return;
|
||||
usedSignalNum[bit] = signal;
|
||||
}
|
||||
|
||||
int CANDataGrid::getUsedSignalNum(int bit)
|
||||
{
|
||||
if (bit < 0) return 0;
|
||||
if (bit > 63) return 0;
|
||||
if (bit > 511) return 0;
|
||||
return usedSignalNum[bit];
|
||||
}
|
||||
|
||||
@@ -120,11 +228,17 @@ void CANDataGrid::paintEvent(QPaintEvent *event)
|
||||
Q_UNUSED(event);
|
||||
|
||||
paintCommonBeginning();
|
||||
if (gridMode == GridMode::CHANGED_BITS) paintChangedBits();
|
||||
if (gridMode == GridMode::SIGNAL_VIEW) paintSignalView();
|
||||
paintGridCells();
|
||||
paintCommonEnding();
|
||||
}
|
||||
|
||||
/*
|
||||
* CAN-FD causes the need to support these sizes: 8, 12, 16, 20, 24, 32, 48, 64 bytes.
|
||||
* It's probably OK to ignore 12 and just go straight from 8 to 16 where each cell is subdivided in half along the X axis
|
||||
* Then 12 is just 16 minus some bits that never can get used. Then jump to 32 drawn cells from there. That would be also
|
||||
* subdividing along the Y axis. Obviously, as before, 24 is just 32 but with unusable bits. Lastly, subdivide X yet again
|
||||
* so now it's in quarters. This allows for 64 bits (48 is likewise just 64 with unusable bits)
|
||||
*/
|
||||
void CANDataGrid::paintCommonBeginning()
|
||||
{
|
||||
int x;
|
||||
@@ -132,29 +246,36 @@ void CANDataGrid::paintCommonBeginning()
|
||||
painter = new QPainter(this);
|
||||
viewport = painter->viewport();
|
||||
|
||||
xSpan = viewport.right() - viewport.left();
|
||||
ySpan = viewport.bottom() - viewport.top();
|
||||
neededXDivisions = 8;
|
||||
neededYDivisions = 8;
|
||||
|
||||
qDebug() << "XSpan" << xSpan << " YSpan " << ySpan;
|
||||
int textRestrict = qMax(neededXDivisions, neededYDivisions);
|
||||
|
||||
xSector = xSpan / 9;
|
||||
ySector = ySpan / 9;
|
||||
|
||||
//the whole thing is broken up into 81 chunks which are broken up
|
||||
//into the 8x8 grid of bits in the bottom right and the other parts
|
||||
//taken up by helper text
|
||||
|
||||
if (gridMode == GridMode::CHANGED_BITS)
|
||||
if (bytesToDraw > 8)
|
||||
{
|
||||
bigTextSize = qMin(xSector, ySector) * 0.6;
|
||||
smallTextSize = qMin(xSector, ySector) * 0.5;
|
||||
neededXDivisions = 16;
|
||||
}
|
||||
if (gridMode == GridMode::SIGNAL_VIEW)
|
||||
if (bytesToDraw > 16)
|
||||
{
|
||||
bigTextSize = qMin(xSector, ySector) * 0.5;
|
||||
smallTextSize = qMin(xSector, ySector) * 0.25;
|
||||
neededXDivisions = 16;
|
||||
neededYDivisions = 16;
|
||||
}
|
||||
sigNameTextSize = qMin(xSector, ySector) * 0.19;
|
||||
if (bytesToDraw > 32)
|
||||
{
|
||||
neededXDivisions = 32;
|
||||
}
|
||||
|
||||
if (gridMode != GridMode::SIGNAL_VIEW)
|
||||
{
|
||||
bigTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 3.25);
|
||||
smallTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 4.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
bigTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 3.50);
|
||||
smallTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 5.5);
|
||||
}
|
||||
sigNameTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 5.0);
|
||||
|
||||
painter->setPen(QPen(QApplication::palette().color(QPalette::Text)));
|
||||
mainFont.setPixelSize(bigTextSize);
|
||||
@@ -165,41 +286,60 @@ void CANDataGrid::paintCommonBeginning()
|
||||
sigNameFont.setPixelSize(sigNameTextSize);
|
||||
|
||||
smallMetric = new QFontMetrics(sigNameFont);
|
||||
largeMetric = new QFontMetrics(mainFont);
|
||||
|
||||
xOffset = smallMetric->maxWidth();
|
||||
yOffset = smallMetric->height();
|
||||
|
||||
xSpan = viewport.right() - viewport.left() - xOffset;
|
||||
ySpan = viewport.bottom() - viewport.top() - yOffset;
|
||||
|
||||
qDebug() << "XSpan" << xSpan << " YSpan " << ySpan;
|
||||
|
||||
xSector = xSpan / neededXDivisions;
|
||||
ySector = ySpan / neededYDivisions;
|
||||
|
||||
qDebug() << "XSector " << xSector << " YSector " << ySector;
|
||||
|
||||
nearX = viewport.left() + xOffset;
|
||||
nearY = viewport.top() + yOffset;
|
||||
farX = nearX + xSector * neededXDivisions;
|
||||
farY = nearY + ySector * neededYDivisions;
|
||||
|
||||
//painter->setFont(boldFont);
|
||||
painter->setFont(smallFont);
|
||||
painter->drawText(QRect(viewport.left(), viewport.top(), xSector, ySector), Qt::AlignCenter, "BITS ->");
|
||||
painter->setFont(boldFont);
|
||||
|
||||
for (x = 0; x < 8; x++)
|
||||
//draw grid by doing vertical and horizontal lines. This is not needed normally but helps when developing new code. Only uncomment for testing
|
||||
/*
|
||||
for (int y = 0; y <= neededYDivisions; y++)
|
||||
{
|
||||
painter->drawText(QRect(viewport.left() + (x+1) * xSector, viewport.top(), xSector, ySector), Qt::AlignCenter, QString::number(7-x));
|
||||
painter->drawLine(nearX, nearY + (y * ySector), farX, nearY + (y * ySector) );
|
||||
}
|
||||
//for (y = 0; y < 8; y++)
|
||||
//{
|
||||
// painter->drawText(QRect(viewport.left() + xSector, viewport.top() + ySector * (y + 2), xSector, ySector), Qt::AlignCenter, QString::number(y));
|
||||
//}
|
||||
|
||||
painter->drawText(viewport.left() + 4, viewport.top() + ySector * 3, "B");
|
||||
painter->drawText(viewport.left() + 4, viewport.top() + ySector * 4, "Y");
|
||||
painter->drawText(viewport.left() + 4, viewport.top() + ySector * 5, "T");
|
||||
painter->drawText(viewport.left() + 4, viewport.top() + ySector * 6, "E");
|
||||
painter->drawText(viewport.left() + 4, viewport.top() + ySector * 7, "S");
|
||||
for (int x = 0; x <= neededXDivisions; x++)
|
||||
{
|
||||
painter->drawLine(nearX + (x * xSector), nearY, nearX + (x * xSector), farY);
|
||||
}
|
||||
*/
|
||||
|
||||
for (x = 0; x < neededXDivisions; x++)
|
||||
{
|
||||
int num = (neededXDivisions - 1) - x;
|
||||
num = num & 7;
|
||||
painter->drawText(QRect(nearX + (x * xSector), viewport.top(), xSector, viewport.top() + smallMetric->height()), Qt::AlignCenter, QString::number(num));
|
||||
}
|
||||
|
||||
painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 1, xSector, ySector), Qt::AlignCenter, "0");
|
||||
painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 2, xSector, ySector), Qt::AlignCenter, "1");
|
||||
painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 3, xSector, ySector), Qt::AlignCenter, "2");
|
||||
painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 4, xSector, ySector), Qt::AlignCenter, "3");
|
||||
painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 5, xSector, ySector), Qt::AlignCenter, "4");
|
||||
painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 6, xSector, ySector), Qt::AlignCenter, "5");
|
||||
painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 7, xSector, ySector), Qt::AlignCenter, "6");
|
||||
painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 8, xSector, ySector), Qt::AlignCenter, "7");
|
||||
int skip = neededXDivisions / 8;
|
||||
for (int y = 0; y < neededYDivisions; y++)
|
||||
{
|
||||
painter->drawText(QRect(viewport.left() + 2, nearY + (ySector * y), xOffset, ySector), Qt::AlignCenter, QString::number(y * skip));
|
||||
}
|
||||
|
||||
painter->setPen(QPen(Qt::black));
|
||||
painter->setFont(mainFont);
|
||||
}
|
||||
|
||||
void CANDataGrid::paintChangedBits()
|
||||
void CANDataGrid::paintGridCells()
|
||||
{
|
||||
int x, y, bit;
|
||||
unsigned char prevByte, thisByte;
|
||||
@@ -212,50 +352,69 @@ void CANDataGrid::paintChangedBits()
|
||||
//bits that used to be set but now are unset are red, bits that used to be unset but now are set
|
||||
//are green
|
||||
|
||||
for (y = 0; y < 8; y++)
|
||||
for (y = 0; y < neededYDivisions; y++)
|
||||
{
|
||||
thisByte = data[y];
|
||||
prevByte = refData[y];
|
||||
for (x = 0; x < 8; x++)
|
||||
for (x = 0; x < neededXDivisions; x++)
|
||||
{
|
||||
bit = (y * 8) + (7 - x);
|
||||
int byteIdx = (y * (neededXDivisions / 8) + (x / 8));
|
||||
thisByte = data[byteIdx];
|
||||
prevByte = refData[byteIdx];
|
||||
int bitIdx = ((neededXDivisions - 1) - x) & 7;
|
||||
bit = (byteIdx * 8) + bitIdx;
|
||||
thisBit = false;
|
||||
prevBit = false;
|
||||
if ((thisByte & (1 << (7-x))) == (1 << (7-x))) thisBit = true;
|
||||
if ((prevByte & (1 << (7-x))) == (1 << (7-x))) prevBit = true;
|
||||
if ((thisByte & (1 << bitIdx)) == (1 << bitIdx)) thisBit = true;
|
||||
if ((prevByte & (1 << bitIdx)) == (1 << bitIdx)) prevBit = true;
|
||||
|
||||
if (thisBit)
|
||||
if (gridMode == GridMode::HEAT_VIEW)
|
||||
{
|
||||
if (prevBit)
|
||||
{
|
||||
painter->setBrush(blackBrush);
|
||||
}
|
||||
else
|
||||
{
|
||||
painter->setBrush(greenBrush);
|
||||
}
|
||||
painter->setBrush(QBrush(fire[heatData[bit]]));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (prevBit)
|
||||
if (thisBit)
|
||||
{
|
||||
painter->setBrush(redBrush);
|
||||
if (prevBit)
|
||||
{
|
||||
if ((signalColors.count() > 0) && (gridMode == GridMode::SIGNAL_VIEW)) painter->setBrush(blackHashBrush);
|
||||
else painter->setBrush(blackBrush);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((signalColors.count() > 0) && (gridMode == GridMode::SIGNAL_VIEW)) painter->setBrush(greenHashBrush);
|
||||
else painter->setBrush(greenBrush);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
usedSigNum = -1;
|
||||
if ((usedData[y] & (1 << (7-x))) == (1 << (7-x)))
|
||||
if (prevBit)
|
||||
{
|
||||
grayBrush = QBrush(QColor(0xB6, 0xB6, 0xB6), Qt::BDiagPattern);
|
||||
painter->setBrush(grayBrush);
|
||||
painter->setBrush(redBrush);
|
||||
}
|
||||
else
|
||||
{
|
||||
usedSigNum = -1;
|
||||
if ((usedData[byteIdx] & (1 << bitIdx)) == (1 << bitIdx))
|
||||
{
|
||||
if (gridMode == GridMode::SIGNAL_VIEW) usedSigNum = getUsedSignalNum(bit);
|
||||
if (usedSigNum == -1)
|
||||
{
|
||||
grayBrush = QBrush(QColor(0xB6, 0xB6, 0xB6), Qt::BDiagPattern);
|
||||
painter->setBrush(grayBrush);
|
||||
}
|
||||
else
|
||||
{
|
||||
int idx = usedSigNum % signalColors.length(); //can only use as many colors as have been defined
|
||||
painter->setBrush(QBrush(signalColors[idx]));
|
||||
}
|
||||
}
|
||||
else painter->setBrush(whiteBrush);
|
||||
}
|
||||
else painter->setBrush(whiteBrush);
|
||||
}
|
||||
}
|
||||
|
||||
//painter->fillRect(viewport.left() + (x+2) * xSector, viewport.top() + (y+2) * ySector, xSector, ySector, redBrush);
|
||||
painter->drawRect(viewport.left() + (x+1) * xSector, viewport.top() + (y+1) * ySector, xSector, ySector);
|
||||
switch (textStates[x][y])
|
||||
painter->drawRect(nearX + (x * xSector), nearY + (y * ySector), xSector, ySector);
|
||||
switch (textStates[byteIdx][bitIdx])
|
||||
{
|
||||
case GridTextState::NORMAL:
|
||||
if (thisBit && prevBit) painter->setPen(QPen(Qt::gray));
|
||||
@@ -275,151 +434,78 @@ void CANDataGrid::paintChangedBits()
|
||||
//change style of bit number output for current signal
|
||||
//if (thisBit) painter->setFont(boldFont);
|
||||
// else painter->setFont(mainFont);
|
||||
painter->setFont(smallFont);
|
||||
|
||||
painter->drawText(viewport.left() + (x+1) * xSector + (xSector / 3), viewport.top() + (y + 2) * ySector - (ySector * 0.4), QString::number(bit));
|
||||
|
||||
painter->setFont(mainFont);
|
||||
painter->setPen(QPen(Qt::black));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CANDataGrid::paintSignalView()
|
||||
{
|
||||
int x, y, bit;
|
||||
unsigned char prevByte, thisByte;
|
||||
bool thisBit, prevBit;
|
||||
int usedSigNum;
|
||||
QString prevSigName;
|
||||
|
||||
//if this is true then generate unique colors for each signal
|
||||
if ((signalColors.count() == 0) && (signalNames.count() > 0))
|
||||
{
|
||||
qDebug() << "Generating colors";
|
||||
signalColors.resize(signalNames.count());
|
||||
for (int i = 0; i < signalNames.count(); i++)
|
||||
{
|
||||
QColor newColor;
|
||||
while (newColor.saturation() < 40)
|
||||
newColor.setRgb(QRandomGenerator::global()->bounded(160) + 60,QRandomGenerator::global()->bounded(160) + 60, QRandomGenerator::global()->bounded(160) + 60);
|
||||
qDebug() << newColor;
|
||||
signalColors[i] = newColor;
|
||||
}
|
||||
}
|
||||
|
||||
for (y = 0; y < 8; y++)
|
||||
{
|
||||
thisByte = data[y];
|
||||
prevByte = refData[y];
|
||||
for (x = 0; x < 8; x++)
|
||||
{
|
||||
bit = (y * 8) + (7 - x);
|
||||
thisBit = false;
|
||||
prevBit = false;
|
||||
if ((thisByte & (1 << (7-x))) == (1 << (7-x))) thisBit = true;
|
||||
if ((prevByte & (1 << (7-x))) == (1 << (7-x))) prevBit = true;
|
||||
|
||||
if (thisBit)
|
||||
{
|
||||
if (prevBit)
|
||||
{
|
||||
if (signalColors.count() > 0) painter->setBrush(blackHashBrush);
|
||||
else painter->setBrush(blackBrush);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (signalColors.count() > 0) painter->setBrush(greenHashBrush);
|
||||
else painter->setBrush(greenBrush);
|
||||
}
|
||||
}
|
||||
//painter->setFont(smallFont);
|
||||
if (gridMode != GridMode::SIGNAL_VIEW)
|
||||
painter->drawText(QRect(nearX + (x * xSector), nearY + (y * ySector), xSector, ySector), Qt::AlignCenter, QString::number(bit)); //center center of grid
|
||||
else
|
||||
{
|
||||
if (prevBit)
|
||||
{
|
||||
painter->setBrush(redBrush);
|
||||
}
|
||||
else
|
||||
{
|
||||
usedSigNum = -1;
|
||||
if ((usedData[y] & (1 << (7-x))) == (1 << (7-x)))
|
||||
{
|
||||
usedSigNum = getUsedSignalNum(bit);
|
||||
if (usedSigNum == -1)
|
||||
{
|
||||
grayBrush = QBrush(QColor(0xB6, 0xB6, 0xB6), Qt::BDiagPattern);
|
||||
painter->setBrush(grayBrush);
|
||||
}
|
||||
else painter->setBrush(QBrush(signalColors[usedSigNum]));
|
||||
}
|
||||
else painter->setBrush(whiteBrush);
|
||||
}
|
||||
}
|
||||
|
||||
//painter->fillRect(viewport.left() + (x+2) * xSector, viewport.top() + (y+2) * ySector, xSector, ySector, redBrush);
|
||||
painter->drawRect(viewport.left() + (x+1) * xSector, viewport.top() + (y+1) * ySector, xSector, ySector);
|
||||
switch (textStates[x][y])
|
||||
{
|
||||
case GridTextState::NORMAL:
|
||||
//if (thisBit && prevBit) painter->setPen(QPen(Qt::gray));
|
||||
/*else*/ painter->setPen(QPen(Qt::black));
|
||||
painter->setFont(mainFont);
|
||||
break;
|
||||
case GridTextState::BOLD_BLUE:
|
||||
painter->setPen(QPen(Qt::blue));
|
||||
painter->setFont(boldFont);
|
||||
break;
|
||||
case GridTextState::INVERT:
|
||||
painter->setFont(mainFont);
|
||||
QColor brushColor = painter->brush().color();
|
||||
painter->setPen(QColor(255-brushColor.red(), 255-brushColor.green(), 255-brushColor.blue()));
|
||||
break;
|
||||
}
|
||||
//change style of bit number output for current signal
|
||||
//if (thisBit) painter->setFont(boldFont);
|
||||
// else painter->setFont(mainFont);
|
||||
painter->setFont(smallFont);
|
||||
|
||||
painter->drawText(viewport.left() + (x+1) * xSector + (xSector / 8), viewport.top() + (y + 2) * ySector - (ySector * 0.7), QString::number(bit));
|
||||
painter->drawText(QRect(nearX + (x * xSector), nearY + (y * ySector), xSector, ySector), Qt::AlignLeft, QString::number(bit)); //upper left of grid
|
||||
|
||||
painter->setFont(mainFont);
|
||||
painter->setPen(QPen(Qt::black));
|
||||
}
|
||||
}
|
||||
|
||||
//now if signal names are loaded we'll go through all the bits again and try to label over top of the grid
|
||||
if (signalNames.count() > 0)
|
||||
|
||||
/*
|
||||
* now if signal names are loaded we'll go through all the bits again and try to label over top of the grid
|
||||
* We already have a big bitmap that tells us which signals occupy which bits so every time there is a new
|
||||
* signal look ahead to see if there's room in the row to just run the signal name through as long as needed.
|
||||
*/
|
||||
if ( (signalNames.count() > 0) && (gridMode == GridMode::SIGNAL_VIEW) )
|
||||
{
|
||||
painter->setFont(sigNameFont);
|
||||
for (y = 0; y < 8; y++)
|
||||
|
||||
for (y = 0; y < neededYDivisions; y++)
|
||||
{
|
||||
for (x = 0; x < 8; x++)
|
||||
for (x = 0; x < neededXDivisions; x++)
|
||||
{
|
||||
bit = (y * 8) + (7 - x);
|
||||
int byteIdx = (y * (neededXDivisions / 8) + (x / 8));
|
||||
int bitIdx = ((neededXDivisions - 1) - x) & 7;
|
||||
bit = (byteIdx * 8) + bitIdx;
|
||||
usedSigNum = -1;
|
||||
if ((usedData[y] & (1 << (7-x))) == (1 << (7-x)))
|
||||
if ((usedData[byteIdx] & (1 << bitIdx)) == (1 << bitIdx))
|
||||
{
|
||||
usedSigNum = getUsedSignalNum(bit);
|
||||
if (prevSigName != signalNames[usedSigNum])
|
||||
if ((usedSigNum > -1) && (prevSigName != signalNames[usedSigNum]) )
|
||||
{
|
||||
prevSigName = signalNames[usedSigNum];
|
||||
|
||||
int textWidth = smallMetric->horizontalAdvance(prevSigName);
|
||||
|
||||
if (textWidth > xSector) //signal name is too long for a single cell. Try to wrap it
|
||||
int usableWidth = getSignalRowRun(usedSigNum, bit);
|
||||
qDebug() << "Width this row: " << usableWidth;
|
||||
usableWidth *= xSector;
|
||||
|
||||
if (textWidth > usableWidth) //signal name is too long for space we've got on this row. Try to wrap it
|
||||
{
|
||||
int numAvgChars = xSector / smallMetric->averageCharWidth();
|
||||
painter->drawText(viewport.left() + (x+1) * xSector, viewport.top() + (y + 2) * ySector - (ySector * 0.4), prevSigName.left(numAvgChars - 1));
|
||||
painter->drawText(nearX + x * xSector + 5, nearY + (y * ySector) + smallMetric->height() * 1.6, prevSigName.left(numAvgChars - 1));
|
||||
QString remainder = prevSigName.mid(numAvgChars - 1, -1);
|
||||
textWidth = smallMetric->horizontalAdvance(prevSigName);
|
||||
if (textWidth > xSector)
|
||||
{
|
||||
painter->drawText(viewport.left() + (x+1) * xSector, viewport.top() + (y + 2) * ySector - (ySector * 0.2), remainder.left(numAvgChars - 1));
|
||||
painter->drawText(nearX + x * xSector + 12, nearY + (y * ySector) + smallMetric->height() * 2.6, remainder.left(numAvgChars - 1));
|
||||
}
|
||||
else painter->drawText(nearX + x * xSector + 12, nearY + (y * ySector) + smallMetric->height() * 2.6, remainder);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//first see if we even have enough room to use a bigger font and use that if so. Otherwise stick with the normal font
|
||||
textWidth = largeMetric->horizontalAdvance(prevSigName);
|
||||
if (textWidth < usableWidth)
|
||||
{
|
||||
painter->setFont(mainFont);
|
||||
QSize size = QSize(usableWidth, ySector - smallMetric->height() * 1.0);
|
||||
painter->drawText(QRect(nearX + x * xSector, nearY + (y * ySector) + smallMetric->height() * 1.0, size.width(), size.height()), Qt::AlignCenter, prevSigName);
|
||||
painter->setFont(sigNameFont);
|
||||
}
|
||||
else
|
||||
{
|
||||
QSize size = QSize(usableWidth, ySector - smallMetric->height() * 1.0);
|
||||
painter->drawText(QRect(nearX + x * xSector, nearY + (y * ySector) + smallMetric->height() * 1.0, size.width(), size.height()), Qt::AlignCenter, prevSigName);
|
||||
}
|
||||
else painter->drawText(viewport.left() + (x+1) * xSector, viewport.top() + (y + 2) * ySector - (ySector * 0.2), remainder);
|
||||
}
|
||||
else painter->drawText(viewport.left() + (x+1) * xSector, viewport.top() + (y + 2) * ySector - (ySector * 0.4), prevSigName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -427,17 +513,62 @@ void CANDataGrid::paintSignalView()
|
||||
}
|
||||
}
|
||||
|
||||
//starting at the given coords, go right / increment X until either we hit the end of the signal or the end of the row, whichever is first.
|
||||
//return how many grid cells that was. The part that sucks is that bits aren't really in "bit" order so you can't just increment the bit number
|
||||
int CANDataGrid::getSignalRowRun(int sigNum, int startBit)
|
||||
{
|
||||
qDebug() << "SigNum: " << sigNum << " startBit " << startBit;
|
||||
int width = 0;
|
||||
QPoint gridPt = getGridPointFromBitPosition(startBit);
|
||||
int x = gridPt.x();
|
||||
int y = gridPt.y();
|
||||
for (int xx = x; xx < neededXDivisions; xx++)
|
||||
{
|
||||
int bit = gridToBitPosition(xx, y);
|
||||
if (usedSignalNum[bit] == sigNum) width++;
|
||||
else return width;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
void CANDataGrid::paintCommonEnding()
|
||||
{
|
||||
upperLeft.setX(viewport.left() + 1 * xSector);
|
||||
upperLeft.setY(viewport.top() + 1 * ySector);
|
||||
//these are used to make it easy to figure out which grid has been clicked on during mousedown events
|
||||
upperLeft.setX(nearX);
|
||||
upperLeft.setY(nearY);
|
||||
gridSize.setX(xSector);
|
||||
gridSize.setY(ySector);
|
||||
|
||||
//and we don't need these anymore after we're done drawing
|
||||
delete painter;
|
||||
delete smallMetric;
|
||||
}
|
||||
|
||||
//given a grid cell we return which bit position that is within the CAN frame.
|
||||
int CANDataGrid::gridToBitPosition(int x, int y)
|
||||
{
|
||||
int byteIdx = (y * (neededXDivisions / 8) + (x / 8));
|
||||
int bitIdx = ((neededXDivisions - 1) - x) & 7;
|
||||
int bit = (byteIdx * 8) + bitIdx;
|
||||
return bit;
|
||||
}
|
||||
|
||||
//inverse of above. Given a bit position we calculate where that would be in our grid
|
||||
QPoint CANDataGrid::getGridPointFromBitPosition(int bitPos)
|
||||
{
|
||||
int tempBit = bitPos;
|
||||
//neededXDivisions tells us how many bits are on a single line.
|
||||
//From there we can easily determine which Y row we're in with simple division
|
||||
int y = bitPos / neededXDivisions;
|
||||
//x is more complicated because the bits go in a sort of stairstep pattern 7654321076543210
|
||||
tempBit = (bitPos - (neededXDivisions * y));
|
||||
int x = tempBit & 0xF8; //get the byte offset in the line
|
||||
x = x + (7- (tempBit & 7)); //reverse the bits in the byte
|
||||
|
||||
return QPoint(x, y);
|
||||
}
|
||||
|
||||
void CANDataGrid::saveImage(QString filename, int width, int height)
|
||||
{
|
||||
Q_UNUSED(width); //currently unused but I want to use them in the future
|
||||
@@ -462,20 +593,38 @@ void CANDataGrid::saveImage(QString filename, int width, int height)
|
||||
pixmap.save(filename); //QT will automatically pick the file format given the extension
|
||||
}
|
||||
|
||||
//these next three functions will copy the needed number of bytes from the passed buffer but you'd better have a large enough buffer or they'll get junk
|
||||
//this probably won't crash the program but it would yield some really strange output. This is only really an issue for CAN-FD traffic. Make sure you
|
||||
//have large enough buffers!
|
||||
void CANDataGrid::setReference(unsigned char *newRef, bool bUpdate = true)
|
||||
{
|
||||
memcpy(refData, newRef, 8);
|
||||
int bytesToTransfer = (bytesToDraw + 7) & 0xF8; //force copying in 8 byte increments
|
||||
memcpy(refData, newRef, bytesToTransfer);
|
||||
//clear all data past that point just to be sure we don't have garbage left over
|
||||
if (bytesToTransfer < 64) memset(refData + bytesToTransfer, 0, 64 - bytesToTransfer);
|
||||
if (bUpdate) this->update();
|
||||
}
|
||||
|
||||
void CANDataGrid::updateData(unsigned char *newData, bool bUpdate = true)
|
||||
{
|
||||
memcpy(data, newData, 8); //on a 64 bit processor this is probably optimized to a single instruction
|
||||
int bytesToTransfer = (bytesToDraw + 7) & 0xF8; //force copying in 8 byte increments
|
||||
memcpy(data, newData, bytesToTransfer);
|
||||
//clear all data past that point just to be sure we don't have garbage left over
|
||||
if (bytesToTransfer < 64) memset(refData + bytesToTransfer, 0, 64 - bytesToTransfer);
|
||||
if (bUpdate) this->update();
|
||||
}
|
||||
|
||||
void CANDataGrid::setUsed(unsigned char *newData, bool bUpdate = false)
|
||||
{
|
||||
memcpy(usedData, newData, 8);
|
||||
int bytesToTransfer = (bytesToDraw + 7) & 0xF8; //force copying in 8 byte increments
|
||||
memcpy(usedData, newData, bytesToTransfer);
|
||||
//clear all data past that point just to be sure we don't have garbage left over
|
||||
if (bytesToTransfer < 64) memset(refData + bytesToTransfer, 0, 64 - bytesToTransfer);
|
||||
if (bUpdate) this->update();
|
||||
}
|
||||
|
||||
void CANDataGrid::setHeat(unsigned char *newData)
|
||||
{
|
||||
memcpy(heatData, newData, 512);
|
||||
this->update();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ class CANDataGrid;
|
||||
*
|
||||
* Now the control also tracks which signal is using which bit. The graphical representation does exist now but some tweaking is probably
|
||||
* still needed. Also, it seems like it is necessary to allow for a variety of modes.
|
||||
*
|
||||
* And now, CAN-FD added as the cherry on top! It's a mess but really, all this functionality is handy to have.
|
||||
*/
|
||||
|
||||
enum GridTextState
|
||||
@@ -33,10 +35,13 @@ enum GridTextState
|
||||
|
||||
enum GridMode
|
||||
{
|
||||
CHANGED_BITS,
|
||||
SIGNAL_VIEW
|
||||
CHANGED_BITS, //traditional view, bunch of bits we color to show what's set and how the bits have changed over time
|
||||
SIGNAL_VIEW, //special view for DBC window where we draw the signals in the bits they take up
|
||||
HEAT_VIEW //another special mode where each bit is given a heat level and we color each bit based on that. Use refData for storage of those values
|
||||
};
|
||||
|
||||
extern QVector<QColor> signalColors;
|
||||
|
||||
class CANDataGrid : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -44,35 +49,39 @@ class CANDataGrid : public QWidget
|
||||
public:
|
||||
explicit CANDataGrid(QWidget *parent = 0);
|
||||
~CANDataGrid();
|
||||
void paintEvent(QPaintEvent *event);
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void setReference(unsigned char *, bool);
|
||||
void updateData(unsigned char *, bool);
|
||||
void setUsed(unsigned char *, bool);
|
||||
void setHeat(unsigned char *);
|
||||
void saveImage(QString filename, int width, int height);
|
||||
void setCellTextState(int x, int y, GridTextState state);
|
||||
GridTextState getCellTextState(int x, int y);
|
||||
void setCellTextState(int bitPos, GridTextState state);
|
||||
GridTextState getCellTextState(int bitPos);
|
||||
void setUsedSignalNum(int bit, int signal);
|
||||
void setSignalNames(int sigIdx, const QString sigName);
|
||||
void clearSignalNames();
|
||||
int getUsedSignalNum(int bit);
|
||||
GridMode getMode();
|
||||
void setMode(GridMode mode);
|
||||
void setBytesToDraw(int num);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
|
||||
|
||||
signals:
|
||||
void gridClicked(int x,int y);
|
||||
void gridClicked(int bitClicked);
|
||||
void gridRightClicked(int bitClicked);
|
||||
|
||||
private:
|
||||
Ui::CANDataGrid *ui;
|
||||
unsigned char refData[8];
|
||||
unsigned char data[8];
|
||||
unsigned char usedData[8];
|
||||
int usedSignalNum[64]; //so we can specify which signal claims this bit
|
||||
int bytesToDraw;
|
||||
unsigned char refData[64];
|
||||
unsigned char data[64];
|
||||
unsigned char usedData[64];
|
||||
unsigned char heatData[512];
|
||||
int usedSignalNum[512]; //so we can specify which signal claims this bit
|
||||
QVector<QString> signalNames;
|
||||
QVector<QColor> signalColors;
|
||||
GridTextState textStates[8][8];
|
||||
GridTextState textStates[64][8]; //first dimension is bytes, second is bits
|
||||
QPoint upperLeft, gridSize;
|
||||
GridMode gridMode;
|
||||
QBrush blackBrush, whiteBrush, redBrush, greenBrush, grayBrush;
|
||||
@@ -84,16 +93,25 @@ private:
|
||||
int xSector;
|
||||
int ySector;
|
||||
double bigTextSize, smallTextSize, sigNameTextSize;
|
||||
int xOffset;
|
||||
int yOffset;
|
||||
int farX, farY, nearX, nearY;
|
||||
int neededXDivisions;
|
||||
int neededYDivisions;
|
||||
QFont mainFont;
|
||||
QFont smallFont;
|
||||
QFont boldFont;
|
||||
QFont sigNameFont;
|
||||
QFontMetrics *smallMetric;
|
||||
QFontMetrics *largeMetric;
|
||||
QColor fire[256];
|
||||
|
||||
void paintChangedBits();
|
||||
void paintSignalView();
|
||||
void paintGridCells();
|
||||
void paintCommonBeginning();
|
||||
void paintCommonEnding();
|
||||
void paintCommonEnding();
|
||||
int gridToBitPosition(int x, int y);
|
||||
QPoint getGridPointFromBitPosition(int bitPos);
|
||||
int getSignalRowRun(int sigNum, int startBit);
|
||||
};
|
||||
|
||||
#endif // CANDATAGRID_H
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <QApplication>
|
||||
#include <QPalette>
|
||||
#include <QDateTime>
|
||||
#include <QSettings>
|
||||
#include "utility.h"
|
||||
|
||||
CANFrameModel::~CANFrameModel()
|
||||
@@ -45,31 +46,48 @@ int CANFrameModel::columnCount(const QModelIndex &index) const
|
||||
CANFrameModel::CANFrameModel(QObject *parent)
|
||||
: QAbstractTableModel(parent)
|
||||
{
|
||||
|
||||
int maxFramesDefault;
|
||||
if (QSysInfo::WordSize > 32)
|
||||
{
|
||||
qDebug() << "64 bit OS detected. Requesting a large preallocation";
|
||||
preallocSize = 10000000;
|
||||
maxFramesDefault = 10000000;
|
||||
}
|
||||
else //if compiling for 32 bit you can't ask for gigabytes of preallocation so tone it down.
|
||||
{
|
||||
qDebug() << "32 bit OS detected. Requesting a much restricted prealloc";
|
||||
preallocSize = 2000000;
|
||||
maxFramesDefault = 2000000;
|
||||
}
|
||||
|
||||
QSettings settings;
|
||||
preallocSize = settings.value("Main/MaximumFrames", maxFramesDefault).toInt();
|
||||
|
||||
//Each CANFrame object takes up 56 bytes and we're allocating two arrays here so take the
|
||||
//# of pre-alloc frames and multiply by 112 to get the RAM usage. This is around 1GiB for the default.
|
||||
|
||||
//the goal is to prevent a reallocation from ever happening
|
||||
frames.reserve(preallocSize);
|
||||
filteredFrames.reserve(preallocSize); //the goal is to prevent a reallocation from ever happening
|
||||
//this is pretty wasteful. We're storing all frames twice. It may be better for filteredFrames to be a list of pointers.
|
||||
//Pointers take up 8 bytes instead of 56 so this is quite a savings for RAM usage. But, then filteredFrames would
|
||||
//work differently from frames above so the two could not be used interchangeably. Still think about what can be done.
|
||||
filteredFrames.reserve(preallocSize);
|
||||
|
||||
dbcHandler = DBCHandler::getReference();
|
||||
interpretFrames = false;
|
||||
overwriteDups = false;
|
||||
filtersPersistDuringClear = false;
|
||||
useHexMode = true;
|
||||
timeSeconds = false;
|
||||
timeStyle = TS_MICROS;
|
||||
timeOffset = 0;
|
||||
needFilterRefresh = false;
|
||||
lastUpdateNumFrames = 0;
|
||||
timeFormat = "MMM-dd HH:mm:ss.zzz";
|
||||
sortDirAsc = false;
|
||||
bytesPerLine = 8;
|
||||
}
|
||||
|
||||
void CANFrameModel::setBytesPerLine(int bpl)
|
||||
{
|
||||
bytesPerLine = bpl;
|
||||
}
|
||||
|
||||
void CANFrameModel::setHexMode(bool mode)
|
||||
@@ -83,32 +101,13 @@ void CANFrameModel::setHexMode(bool mode)
|
||||
}
|
||||
}
|
||||
|
||||
void CANFrameModel::setSecondsMode(bool mode)
|
||||
void CANFrameModel::setTimeStyle(TimeStyle newStyle)
|
||||
{
|
||||
if (Utility::secondsMode != mode)
|
||||
if (timeStyle != newStyle)
|
||||
{
|
||||
this->beginResetModel();
|
||||
Utility::secondsMode = mode;
|
||||
this->endResetModel();
|
||||
}
|
||||
}
|
||||
|
||||
void CANFrameModel::setSysTimeMode(bool mode)
|
||||
{
|
||||
if (Utility::sysTimeMode != mode)
|
||||
{
|
||||
this->beginResetModel();
|
||||
Utility::sysTimeMode = mode;
|
||||
this->endResetModel();
|
||||
}
|
||||
}
|
||||
|
||||
void CANFrameModel::setMillisMode(bool mode)
|
||||
{
|
||||
if (Utility::millisMode != mode)
|
||||
{
|
||||
this->beginResetModel();
|
||||
Utility::millisMode = mode;
|
||||
timeStyle = newStyle;
|
||||
Utility::timeStyle = newStyle;
|
||||
this->endResetModel();
|
||||
}
|
||||
}
|
||||
@@ -133,6 +132,7 @@ bool CANFrameModel::getInterpretMode()
|
||||
void CANFrameModel::setTimeFormat(QString format)
|
||||
{
|
||||
Utility::timeFormat = format;
|
||||
timeFormat = format;
|
||||
beginResetModel(); //reset model to show new time format
|
||||
endResetModel();
|
||||
}
|
||||
@@ -195,6 +195,11 @@ void CANFrameModel::setOverwriteMode(bool mode)
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void CANFrameModel::setClearMode(bool mode)
|
||||
{
|
||||
filtersPersistDuringClear = mode;
|
||||
}
|
||||
|
||||
void CANFrameModel::setFilterState(unsigned int ID, bool state)
|
||||
{
|
||||
if (!filters.contains(ID)) return;
|
||||
@@ -224,11 +229,11 @@ void CANFrameModel::setAllFilters(bool state)
|
||||
* quicksort on the columns and interpret the columns numerically. But, correct or not, this implementation is quite fast
|
||||
* and sorts the columns properly.
|
||||
*/
|
||||
uint64_t CANFrameModel::getCANFrameVal(int row, Column col)
|
||||
uint64_t CANFrameModel::getCANFrameVal(QVector<CANFrame> *frames, int row, Column col)
|
||||
{
|
||||
uint64_t temp = 0;
|
||||
if (row >= frames.count()) return 0;
|
||||
CANFrame frame = frames[row];
|
||||
if (row >= frames->count()) return 0;
|
||||
CANFrame frame = frames->at(row);
|
||||
switch (col)
|
||||
{
|
||||
case Column::TimeStamp:
|
||||
@@ -252,7 +257,7 @@ uint64_t CANFrameModel::getCANFrameVal(int row, Column col)
|
||||
return static_cast<uint64_t>(frame.payload().length());
|
||||
case Column::ASCII: //sort both the same for now
|
||||
case Column::Data:
|
||||
for (int i = 0; i < frame.payload().length(); i++) temp += (static_cast<uint64_t>(frame.payload()[i]) << (56 - (8 * i)));
|
||||
for (int i = 0; i < std::min(frame.payload().length(), 8); i++) temp += (static_cast<uint64_t>(frame.payload()[i]) << (56 - (8 * i)));
|
||||
//qDebug() << temp;
|
||||
return temp;
|
||||
case Column::NUM_COLUMN:
|
||||
@@ -267,18 +272,18 @@ void CANFrameModel::qSortCANFrameAsc(QVector<CANFrame> *frames, Column column, i
|
||||
qDebug() << "Lower " << lowerBound << " Upper" << upperBound;
|
||||
if (lowerBound < upperBound)
|
||||
{
|
||||
uint64_t piv = getCANFrameVal(lowerBound + (upperBound - lowerBound) / 2, column);
|
||||
uint64_t piv = getCANFrameVal(frames, lowerBound + (upperBound - lowerBound) / 2, column);
|
||||
i = lowerBound - 1;
|
||||
j = upperBound + 1;
|
||||
for (;;){
|
||||
do {
|
||||
i++;
|
||||
} while ((i < upperBound) && getCANFrameVal(i, column) < piv);
|
||||
} while ((i < upperBound) && getCANFrameVal(frames, i, column) < piv);
|
||||
|
||||
do
|
||||
{
|
||||
j--;
|
||||
} while ((j > lowerBound) && getCANFrameVal(j, column) > piv);
|
||||
} while ((j > lowerBound) && getCANFrameVal(frames, j, column) > piv);
|
||||
if (i < j) {
|
||||
CANFrame temp = frames->at(i);
|
||||
frames->replace(i, frames->at(j));
|
||||
@@ -298,18 +303,18 @@ void CANFrameModel::qSortCANFrameDesc(QVector<CANFrame> *frames, Column column,
|
||||
qDebug() << "Lower " << lowerBound << " Upper" << upperBound;
|
||||
if (lowerBound < upperBound)
|
||||
{
|
||||
uint64_t piv = getCANFrameVal(lowerBound + (upperBound - lowerBound) / 2, column);
|
||||
uint64_t piv = getCANFrameVal(frames, lowerBound + (upperBound - lowerBound) / 2, column);
|
||||
i = lowerBound - 1;
|
||||
j = upperBound + 1;
|
||||
for (;;){
|
||||
do {
|
||||
i++;
|
||||
} while ((i < upperBound) && getCANFrameVal(i, column) > piv);
|
||||
} while ((i < upperBound) && getCANFrameVal(frames, i, column) > piv);
|
||||
|
||||
do
|
||||
{
|
||||
j--;
|
||||
} while ((j > lowerBound) && getCANFrameVal(j, column) < piv);
|
||||
} while ((j > lowerBound) && getCANFrameVal(frames, j, column) < piv);
|
||||
if (i < j) {
|
||||
CANFrame temp = frames->at(i);
|
||||
frames->replace(i, frames->at(j));
|
||||
@@ -326,11 +331,13 @@ void CANFrameModel::qSortCANFrameDesc(QVector<CANFrame> *frames, Column column,
|
||||
void CANFrameModel::sortByColumn(int column)
|
||||
{
|
||||
sortDirAsc = !sortDirAsc;
|
||||
//beginResetModel();
|
||||
if (sortDirAsc) qSortCANFrameAsc(&frames, Column(column), 0, frames.count()-1);
|
||||
else qSortCANFrameDesc(&frames, Column(column), 0, frames.count()-1);
|
||||
//endResetModel();
|
||||
sendRefresh();
|
||||
if (sortDirAsc) qSortCANFrameAsc(&filteredFrames, Column(column), 0, filteredFrames.count()-1);
|
||||
else qSortCANFrameDesc(&filteredFrames, Column(column), 0, filteredFrames.count()-1);
|
||||
|
||||
mutex.lock();
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
//End of custom sorting code
|
||||
@@ -353,33 +360,38 @@ void CANFrameModel::recalcOverwrite()
|
||||
|
||||
idAugmented = frame.frameId();
|
||||
idAugmented = idAugmented + (frame.bus << 29ull);
|
||||
if (!overWriteFrames.contains(idAugmented))
|
||||
if (filters[frame.frameId()] && busFilters[frame.bus])
|
||||
{
|
||||
frame.timedelta = 0;
|
||||
frame.frameCount = 1;
|
||||
overWriteFrames.insert(idAugmented, frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.timedelta = frame.timeStamp().microSeconds() - overWriteFrames[idAugmented].timeStamp().microSeconds();
|
||||
frame.frameCount = overWriteFrames[idAugmented].frameCount + 1;
|
||||
overWriteFrames[idAugmented] = frame;
|
||||
if (!overWriteFrames.contains(idAugmented))
|
||||
{
|
||||
frame.timedelta = 0;
|
||||
frame.frameCount = 1;
|
||||
overWriteFrames.insert(idAugmented, frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.timedelta = frame.timeStamp().microSeconds() - overWriteFrames[idAugmented].timeStamp().microSeconds();
|
||||
frame.frameCount = overWriteFrames[idAugmented].frameCount + 1;
|
||||
overWriteFrames[idAugmented] = frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
//Then replace the old list of frames with just the unique list
|
||||
frames.clear();
|
||||
frames.append(overWriteFrames.values().toVector());
|
||||
//frames.clear();
|
||||
//frames.append(overWriteFrames.values().toVector());
|
||||
//frames.reserve(preallocSize);
|
||||
|
||||
filteredFrames.clear();
|
||||
filteredFrames.append(overWriteFrames.values().toVector());
|
||||
filteredFrames.reserve(preallocSize);
|
||||
|
||||
for (int i = 0; i < frames.count(); i++)
|
||||
/*for (int i = 0; i < frames.count(); i++)
|
||||
{
|
||||
if (filters[frames[i].frameId()] && busFilters[frames[i].bus])
|
||||
{
|
||||
filteredFrames.append(frames[i]);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
endResetModel();
|
||||
mutex.unlock();
|
||||
@@ -403,7 +415,7 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
|
||||
const unsigned char *data = reinterpret_cast<const unsigned char *>(thisFrame.payload().constData());
|
||||
int dataLen = thisFrame.payload().count();
|
||||
|
||||
if (role == Qt::BackgroundColorRole)
|
||||
if (role == Qt::BackgroundRole)
|
||||
{
|
||||
if (dbcHandler != nullptr && interpretFrames && !ignoreDBCColors)
|
||||
{
|
||||
@@ -436,7 +448,7 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
}
|
||||
|
||||
if (role == Qt::TextColorRole)
|
||||
if (role == Qt::ForegroundRole)
|
||||
{
|
||||
if (dbcHandler != nullptr && interpretFrames && !ignoreDBCColors)
|
||||
{
|
||||
@@ -456,7 +468,7 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
|
||||
//Reformatting the output a bit with custom code
|
||||
if (overwriteDups)
|
||||
{
|
||||
if (timeSeconds) return QString::number(thisFrame.timedelta / 1000000.0, 'f', 5);
|
||||
if (timeStyle == TS_SECONDS) return QString::number(thisFrame.timedelta / 1000000.0, 'f', 5);
|
||||
return QString::number(thisFrame.timedelta);
|
||||
}
|
||||
else ts = Utility::formatTimestamp(thisFrame.timeStamp().microSeconds());
|
||||
@@ -495,6 +507,7 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
|
||||
if (byt < 0x20) byt = 0x2E; //dot character
|
||||
if (byt > 0x7E) byt = 0x2E;
|
||||
tempString.append(QString::fromUtf8(&byt, 1));
|
||||
if (!((i+1) % bytesPerLine) && (i != (dataLen - 1))) tempString.append("\n");
|
||||
}
|
||||
}
|
||||
if (thisFrame.frameType() == QCanBusFrame::ErrorFrame)
|
||||
@@ -512,7 +525,8 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (useHexMode) tempString.append( QString::number(data[i], 16).toUpper().rightJustified(2, '0'));
|
||||
else tempString.append(QString::number(data[i], 10));
|
||||
tempString.append(" ");
|
||||
if (!((i+1) % bytesPerLine) && (i != (dataLen - 1))) tempString.append("\n");
|
||||
else tempString.append(" ");
|
||||
}
|
||||
if (thisFrame.frameType() == thisFrame.ErrorFrame)
|
||||
{
|
||||
@@ -672,32 +686,52 @@ void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false)
|
||||
|
||||
if (!overwriteDups)
|
||||
{
|
||||
frames.append(tempFrame);
|
||||
if (filters[tempFrame.frameId()] && busFilters[tempFrame.bus])
|
||||
try
|
||||
{
|
||||
if (autoRefresh) beginInsertRows(QModelIndex(), filteredFrames.count(), filteredFrames.count());
|
||||
tempFrame.frameCount = 1;
|
||||
filteredFrames.append(tempFrame);
|
||||
if (autoRefresh) endInsertRows();
|
||||
frames.append(tempFrame);
|
||||
|
||||
if (filters[tempFrame.frameId()] && busFilters[tempFrame.bus])
|
||||
{
|
||||
if (autoRefresh) beginInsertRows(QModelIndex(), filteredFrames.count(), filteredFrames.count());
|
||||
tempFrame.frameCount = 1;
|
||||
filteredFrames.append(tempFrame);
|
||||
if (autoRefresh) endInsertRows();
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
qDebug() << "addFrame failed to append. App is probably going to crash. frames.length(): " << frames.length() << " Exception: " << ex.what();
|
||||
}
|
||||
}
|
||||
else //yes, overwrite dups
|
||||
{
|
||||
bool found = false;
|
||||
for (int i = 0; i < frames.count(); i++)
|
||||
// for (int i = 0; i < frames.count(); i++)
|
||||
// {
|
||||
// if ( (frames[i].frameId() == tempFrame.frameId()) && (frames[i].bus == tempFrame.bus) )
|
||||
// {
|
||||
// tempFrame.frameCount = frames[i].frameCount + 1;
|
||||
// tempFrame.timedelta = tempFrame.timeStamp().microSeconds() - frames[i].timeStamp().microSeconds();
|
||||
// frames.replace(i, tempFrame);
|
||||
// found = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
for (int i = 0; i < filteredFrames.count(); i++)
|
||||
{
|
||||
if ( (frames[i].frameId() == tempFrame.frameId()) && (frames[i].bus == tempFrame.bus) )
|
||||
if ( (filteredFrames[i].frameId() == tempFrame.frameId()) && (filteredFrames[i].bus == tempFrame.bus) )
|
||||
{
|
||||
tempFrame.frameCount = frames[i].frameCount + 1;
|
||||
tempFrame.timedelta = tempFrame.timeStamp().microSeconds() - frames[i].timeStamp().microSeconds();
|
||||
frames.replace(i, tempFrame);
|
||||
tempFrame.frameCount = filteredFrames[i].frameCount + 1;
|
||||
tempFrame.timedelta = tempFrame.timeStamp().microSeconds() - filteredFrames[i].timeStamp().microSeconds();
|
||||
filteredFrames.replace(i, tempFrame);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
frames.append(tempFrame);
|
||||
if (!found)
|
||||
{
|
||||
frames.append(tempFrame);
|
||||
//frames.append(tempFrame);
|
||||
if (filters[tempFrame.frameId()] && busFilters[tempFrame.bus])
|
||||
{
|
||||
if (autoRefresh) beginInsertRows(QModelIndex(), filteredFrames.count(), filteredFrames.count());
|
||||
@@ -727,6 +761,24 @@ void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false)
|
||||
|
||||
void CANFrameModel::addFrames(const CANConnection*, const QVector<CANFrame>& pFrames)
|
||||
{
|
||||
if(frames.length() > frames.capacity() * 0.99)
|
||||
{
|
||||
mutex.lock();
|
||||
qDebug() << "Frames count: " << frames.length() << " of " << frames.capacity() << " capacity, removing first " << (int)(frames.capacity() * 0.05) << " frames";
|
||||
frames.remove(0, (int)(frames.capacity() * 0.05));
|
||||
qDebug() << "Frames removed, new count: " << frames.length();
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
if(filteredFrames.length() > filteredFrames.capacity() * 0.99)
|
||||
{
|
||||
mutex.lock();
|
||||
qDebug() << "filteredFrames count: " << filteredFrames.length() << " of " << filteredFrames.capacity() << " capacity, removing first " << (int)(filteredFrames.capacity() * 0.05) << " frames";
|
||||
filteredFrames.remove(0, (int)(filteredFrames.capacity() * 0.05));
|
||||
qDebug() << "filteredFrames removed, new count: " << filteredFrames.length();
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
foreach(const CANFrame& frame, pFrames)
|
||||
{
|
||||
addFrame(frame);
|
||||
@@ -740,25 +792,33 @@ void CANFrameModel::addFrames(const CANConnection*, const QVector<CANFrame>& pFr
|
||||
|
||||
void CANFrameModel::sendRefresh()
|
||||
{
|
||||
qDebug() << "Sending mass refresh";
|
||||
QVector<CANFrame> tempContainer;
|
||||
int count = frames.count();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (filters[frames[i].frameId()] && busFilters[frames[i].bus])
|
||||
{
|
||||
tempContainer.append(frames[i]);
|
||||
}
|
||||
}
|
||||
mutex.lock();
|
||||
beginResetModel();
|
||||
filteredFrames.clear();
|
||||
filteredFrames.reserve(preallocSize);
|
||||
filteredFrames.append(tempContainer);
|
||||
qDebug() << "Sending mass refresh";
|
||||
|
||||
lastUpdateNumFrames = 0;
|
||||
endResetModel();
|
||||
mutex.unlock();
|
||||
if(overwriteDups)
|
||||
{
|
||||
recalcOverwrite();
|
||||
}
|
||||
else
|
||||
{
|
||||
QVector<CANFrame> tempContainer;
|
||||
int count = frames.count();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (filters[frames[i].frameId()] && busFilters[frames[i].bus])
|
||||
{
|
||||
tempContainer.append(frames[i]);
|
||||
}
|
||||
}
|
||||
|
||||
mutex.lock();
|
||||
beginResetModel();
|
||||
filteredFrames.clear();
|
||||
filteredFrames.append(tempContainer);
|
||||
filteredFrames.reserve(preallocSize);
|
||||
lastUpdateNumFrames = 0;
|
||||
endResetModel();
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void CANFrameModel::sendRefresh(int pos)
|
||||
@@ -776,9 +836,9 @@ int CANFrameModel::sendBulkRefresh()
|
||||
if (lastUpdateNumFrames <= 0) return 0;
|
||||
|
||||
if (lastUpdateNumFrames == 0 && !overwriteDups) return 0;
|
||||
if (filteredFrames.count() == 0) return 0;
|
||||
//if (filteredFrames.count() == 0) return 0;
|
||||
|
||||
qDebug() << "Bulk refresh of " << lastUpdateNumFrames;
|
||||
//qDebug() << "Bulk refresh of " << lastUpdateNumFrames;
|
||||
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
@@ -795,8 +855,11 @@ void CANFrameModel::clearFrames()
|
||||
this->beginResetModel();
|
||||
frames.clear();
|
||||
filteredFrames.clear();
|
||||
filters.clear();
|
||||
busFilters.clear();
|
||||
if(filtersPersistDuringClear == false)
|
||||
{
|
||||
filters.clear();
|
||||
busFilters.clear();
|
||||
}
|
||||
frames.reserve(preallocSize);
|
||||
filteredFrames.reserve(preallocSize);
|
||||
this->endResetModel();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "can_structs.h"
|
||||
#include "dbc/dbchandler.h"
|
||||
#include "connections/canconnection.h"
|
||||
#include "utility.h"
|
||||
|
||||
enum class Column {
|
||||
TimeStamp = 0, ///< The timestamp when the frame was transmitted or received
|
||||
@@ -46,14 +47,14 @@ public:
|
||||
bool getInterpretMode();
|
||||
void setOverwriteMode(bool);
|
||||
void setHexMode(bool);
|
||||
void setSysTimeMode(bool);
|
||||
void setMillisMode(bool mode);
|
||||
void setClearMode(bool mode);
|
||||
void setTimeStyle(TimeStyle newStyle);
|
||||
void setIgnoreDBCColors(bool mode);
|
||||
void setFilterState(unsigned int ID, bool state);
|
||||
void setBusFilterState(unsigned int BusID, bool state);
|
||||
void setAllFilters(bool state);
|
||||
void setSecondsMode(bool);
|
||||
void setTimeFormat(QString);
|
||||
void setBytesPerLine(int bpl);
|
||||
void loadFilterFile(QString filename);
|
||||
void saveFilterFile(QString filename);
|
||||
void normalizeTiming();
|
||||
@@ -77,7 +78,7 @@ signals:
|
||||
private:
|
||||
void qSortCANFrameAsc(QVector<CANFrame>* frames, Column column, int lowerBound, int upperBound);
|
||||
void qSortCANFrameDesc(QVector<CANFrame>* frames, Column column, int lowerBound, int upperBound);
|
||||
uint64_t getCANFrameVal(int row, Column col);
|
||||
uint64_t getCANFrameVal(QVector<CANFrame> *frames, int row, Column col);
|
||||
bool any_filters_are_configured(void);
|
||||
bool any_busfilters_are_configured(void);
|
||||
|
||||
@@ -89,16 +90,17 @@ private:
|
||||
QMutex mutex;
|
||||
bool interpretFrames; //should we use the dbcHandler?
|
||||
bool overwriteDups; //should we display all frames or only the newest for each ID?
|
||||
bool filtersPersistDuringClear;
|
||||
QString timeFormat;
|
||||
TimeStyle timeStyle;
|
||||
bool useHexMode;
|
||||
bool timeSeconds;
|
||||
bool useSystemTime;
|
||||
bool needFilterRefresh;
|
||||
bool ignoreDBCColors;
|
||||
int64_t timeOffset;
|
||||
int lastUpdateNumFrames;
|
||||
uint32_t preallocSize;
|
||||
bool sortDirAsc;
|
||||
int bytesPerLine;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef CONFIG
|
||||
#define CONFIG
|
||||
|
||||
#define VERSION 207
|
||||
#define VERSION 213
|
||||
|
||||
//try to keep this in sync.
|
||||
//SavvyCAN will complain if you connect a GVRET board with a revision
|
||||
|
||||
@@ -4,25 +4,22 @@
|
||||
|
||||
CANBus::CANBus()
|
||||
{
|
||||
speed = 250000;
|
||||
speed = 500000;
|
||||
listenOnly = false;
|
||||
singleWire = false;
|
||||
active = false;
|
||||
canFD = false;
|
||||
dataRate = 2000000;
|
||||
}
|
||||
|
||||
|
||||
CANBus::CANBus(const CANBus& pBus) :
|
||||
speed(pBus.speed),
|
||||
listenOnly(pBus.listenOnly),
|
||||
singleWire(pBus.singleWire),
|
||||
active(pBus.active) {}
|
||||
|
||||
|
||||
bool CANBus::operator==(const CANBus& bus) const{
|
||||
return speed == bus.speed &&
|
||||
listenOnly == bus.listenOnly &&
|
||||
singleWire == bus.singleWire &&
|
||||
active == bus.active;
|
||||
active == bus.active &&
|
||||
canFD == bus.canFD &&
|
||||
dataRate == bus.dataRate;
|
||||
}
|
||||
|
||||
void CANBus::setSpeed(int newSpeed){
|
||||
@@ -45,29 +42,48 @@ void CANBus::setActive(bool mode){
|
||||
active = mode;
|
||||
}
|
||||
|
||||
int CANBus::getSpeed(){
|
||||
void CANBus::setCanFD(bool mode){
|
||||
//qDebug() << "CANBUS setCanFD = " << mode;
|
||||
canFD = mode;
|
||||
}
|
||||
|
||||
int CANBus::getSpeed() const {
|
||||
return speed;
|
||||
}
|
||||
|
||||
bool CANBus::isListenOnly(){
|
||||
int CANBus::getDataRate() const {
|
||||
return dataRate;
|
||||
}
|
||||
|
||||
void CANBus::setDataRate(int newSpeed){
|
||||
//qDebug() << "CANBUS SetSpeed = " << newSpeed;
|
||||
dataRate = newSpeed;
|
||||
}
|
||||
|
||||
bool CANBus::isListenOnly() const {
|
||||
return listenOnly;
|
||||
}
|
||||
|
||||
bool CANBus::isSingleWire(){
|
||||
bool CANBus::isSingleWire() const {
|
||||
return singleWire;
|
||||
}
|
||||
|
||||
bool CANBus::isActive(){
|
||||
bool CANBus::isActive() const {
|
||||
return active;
|
||||
}
|
||||
|
||||
bool CANBus::isCanFD() const {
|
||||
return canFD;
|
||||
}
|
||||
|
||||
QDataStream& operator<<( QDataStream & pStream, const CANBus& pCanBus )
|
||||
|
||||
QDataStream& operator<<(QDataStream & pStream, const CANBus& pCanBus)
|
||||
{
|
||||
pStream << pCanBus.speed;
|
||||
pStream << pCanBus.listenOnly;
|
||||
pStream << pCanBus.singleWire;
|
||||
pStream << pCanBus.active;
|
||||
// FIXME CANFD settings missing
|
||||
return pStream;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,37 +5,43 @@
|
||||
|
||||
class CANBus
|
||||
{
|
||||
public:
|
||||
CANBus();
|
||||
CANBus(const CANBus&);
|
||||
bool operator==(const CANBus&) const;
|
||||
CANBus& operator=(const CANBus& other) = default;
|
||||
//virtual ~CANBus(){}
|
||||
|
||||
int speed;
|
||||
bool listenOnly;
|
||||
bool singleWire;
|
||||
bool active; //is this bus turned on?
|
||||
bool canFD;
|
||||
int dataRate;
|
||||
|
||||
friend QDataStream& operator<<(QDataStream & pStream, const CANBus& pCanBus);
|
||||
friend QDataStream& operator>>(QDataStream & pStream, CANBus& pCanBus);
|
||||
public:
|
||||
CANBus();
|
||||
|
||||
bool operator==(const CANBus&) const;
|
||||
|
||||
void setSpeed(int); // new speed
|
||||
void setListenOnly(bool); //bool for whether to only listen
|
||||
void setSingleWire(bool); //bool for whether to use single wire mode
|
||||
void setActive(bool); //whether this bus should be enabled or not.
|
||||
int getSpeed();
|
||||
bool isListenOnly();
|
||||
bool isSingleWire();
|
||||
bool isActive();
|
||||
void setCanFD(bool); // enable or disable CANFD support
|
||||
void setDataRate(int newSpeed);
|
||||
|
||||
int getSpeed() const;
|
||||
int getDataRate() const;
|
||||
bool isListenOnly() const;
|
||||
bool isSingleWire() const;
|
||||
bool isActive() const;
|
||||
bool isCanFD() const;
|
||||
};
|
||||
|
||||
QDataStream& operator<<( QDataStream & pStream, const CANBus& pCanBus );
|
||||
QDataStream & operator>>(QDataStream & pStream, CANBus& pCanBus);
|
||||
QDataStream& operator<<(QDataStream & pStream, const CANBus& pCanBus);
|
||||
QDataStream& operator>>(QDataStream & pStream, CANBus& pCanBus);
|
||||
|
||||
Q_DECLARE_METATYPE(CANBus);
|
||||
|
||||
struct BusData {
|
||||
CANBus mBus;
|
||||
bool mConfigured;
|
||||
bool mConfigured = {};
|
||||
QVector<CANFltObserver> mTargettedFrames;
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ namespace CANCon {
|
||||
REMOTE,
|
||||
KAYAK,
|
||||
MQTT,
|
||||
LAWICEL,
|
||||
CANSERVER,
|
||||
CANLOGSERVER,
|
||||
NONE
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,22 +4,34 @@
|
||||
#include "gvretserial.h"
|
||||
#include "mqtt_bus.h"
|
||||
#include "socketcand.h"
|
||||
#include "lawicel_serial.h"
|
||||
#include "canserver.h"
|
||||
#include "canlogserver.h"
|
||||
|
||||
using namespace CANCon;
|
||||
|
||||
CANConnection* CanConFactory::create(type pType, QString pPortName, QString pDriverName)
|
||||
CANConnection* CanConFactory::create(type pType, QString pPortName, QString pDriverName, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate)
|
||||
{
|
||||
switch(pType) {
|
||||
case SERIALBUS:
|
||||
return new SerialBusConnection(pPortName, pDriverName);
|
||||
case GVRET_SERIAL:
|
||||
if(pPortName.contains(".") && !pPortName.contains("tty") && !pPortName.contains("serial"))
|
||||
return new GVRetSerial(pPortName, true);
|
||||
else
|
||||
return new GVRetSerial(pPortName, false);
|
||||
case REMOTE:
|
||||
return new GVRetSerial(pPortName, true); //it's a special case of GVRET connected over TCP/IP so it uses the same class
|
||||
case LAWICEL:
|
||||
return new LAWICELSerial(pPortName, pSerialSpeed, pBusSpeed, pCanFd, pDataRate);
|
||||
case KAYAK:
|
||||
return new SocketCANd(pPortName);
|
||||
case MQTT:
|
||||
return new MQTT_BUS(pPortName);
|
||||
case CANSERVER:
|
||||
return new CANserver(pPortName);
|
||||
case CANLOGSERVER:
|
||||
return new CanLogServer(pPortName);
|
||||
default: {}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
class CanConFactory
|
||||
{
|
||||
public:
|
||||
static CANConnection* create(CANCon::type, QString pPortName, QString pDriverName);
|
||||
static CANConnection* create(CANCon::type, QString pPortName, QString pDriverName, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate);
|
||||
};
|
||||
|
||||
#endif // CANCONFACTORY_H
|
||||
|
||||
@@ -69,7 +69,9 @@ void CANConManager::remove(CANConnection* pConn_p)
|
||||
|
||||
void CANConManager::replace(int idx, CANConnection* pConn_p)
|
||||
{
|
||||
CANConnection *original = mConns[idx];
|
||||
mConns.replace(idx, pConn_p);
|
||||
delete original; original = NULL;
|
||||
}
|
||||
|
||||
//Get total number of buses currently registered with the program
|
||||
@@ -204,7 +206,6 @@ bool CANConManager::sendFrame(const CANFrame& pFrame)
|
||||
{
|
||||
int busBase = 0;
|
||||
CANFrame workingFrame = pFrame;
|
||||
CANFrame *txFrame;
|
||||
|
||||
if (mConns.count() == 0)
|
||||
{
|
||||
@@ -228,10 +229,7 @@ bool CANConManager::sendFrame(const CANFrame& pFrame)
|
||||
workingFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, mElapsedTimer.nsecsElapsed() / 1000));
|
||||
//workingFrame.timestamp -= mTimestampBasis;
|
||||
}
|
||||
txFrame = conn->getQueue().get();
|
||||
QCoreApplication::processEvents();
|
||||
*txFrame = workingFrame;
|
||||
conn->getQueue().queue();
|
||||
|
||||
return conn->sendFrame(workingFrame);
|
||||
}
|
||||
busBase += conn->getNumBuses();
|
||||
|
||||
@@ -5,10 +5,15 @@
|
||||
CANConnection::CANConnection(QString pPort,
|
||||
QString pDriver,
|
||||
CANCon::type pType,
|
||||
int pSerialSpeed,
|
||||
int pBusSpeed,
|
||||
bool pCanFd,
|
||||
int pDataRate,
|
||||
int pNumBuses,
|
||||
int pQueueLen,
|
||||
bool pUseThread) :
|
||||
mNumBuses(pNumBuses),
|
||||
mSerialSpeed(pSerialSpeed),
|
||||
mQueue(),
|
||||
mPort(pPort),
|
||||
mDriver(pDriver),
|
||||
@@ -34,6 +39,10 @@ CANConnection::CANConnection(QString pPort,
|
||||
mBusData[i].mConfigured = false;
|
||||
}
|
||||
|
||||
if (pBusSpeed > 0) mBusData[0].mBus.setSpeed(pBusSpeed);
|
||||
mBusData[0].mBus.setCanFD(pCanFd);
|
||||
if (pDataRate > 0) mBusData[0].mBus.setDataRate(pDataRate);
|
||||
|
||||
/* if needed, create a thread and move ourself into it */
|
||||
if(pUseThread) {
|
||||
mThread_p = new QThread();
|
||||
@@ -168,6 +177,14 @@ bool CANConnection::sendFrame(const CANFrame& pFrame)
|
||||
return ret;
|
||||
}
|
||||
|
||||
CANFrame *txFrame;
|
||||
txFrame = getQueue().get();
|
||||
if (txFrame)
|
||||
{
|
||||
*txFrame = pFrame;
|
||||
}
|
||||
getQueue().queue();
|
||||
|
||||
return piSendFrame(pFrame);
|
||||
}
|
||||
|
||||
@@ -245,11 +262,11 @@ CANCon::type CANConnection::getType() {
|
||||
|
||||
|
||||
CANCon::status CANConnection::getStatus() {
|
||||
return (CANCon::status) mStatus.load();
|
||||
return (CANCon::status) mStatus.loadRelaxed();
|
||||
}
|
||||
|
||||
void CANConnection::setStatus(CANCon::status pStatus) {
|
||||
mStatus.store(pStatus);
|
||||
mStatus.storeRelaxed(pStatus);
|
||||
}
|
||||
|
||||
bool CANConnection::isCapSuspended() {
|
||||
@@ -288,7 +305,12 @@ bool CANConnection::addTargettedFrame(int pBusId, uint32_t ID, uint32_t mask, QO
|
||||
target.id = ID;
|
||||
target.mask = mask;
|
||||
target.observer = receiver;
|
||||
mBusData[pBusId].mTargettedFrames.append(target);
|
||||
if (pBusId > -1)
|
||||
mBusData[pBusId].mTargettedFrames.append(target);
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < mBusData.count(); i++) mBusData[i].mTargettedFrames.append(target);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -339,7 +361,10 @@ void CANConnection::checkTargettedFrame(CANFrame &frame)
|
||||
//qDebug() << "Got frame with ID " << frame.ID << " on bus " << frame.bus;
|
||||
if (mBusData.count() == 0) return;
|
||||
|
||||
if (mBusData[frame.bus].mTargettedFrames.length() == 0) return;
|
||||
int bus = frame.bus;
|
||||
if (bus > (mBusData.length() - 1)) bus = mBusData.length() - 1;
|
||||
|
||||
if (mBusData[bus].mTargettedFrames.length() == 0) return;
|
||||
foreach (const CANFltObserver filt, mBusData[frame.bus].mTargettedFrames)
|
||||
{
|
||||
//qDebug() << "Checking filter with id " << filt.id << " mask " << filt.mask;
|
||||
|
||||
@@ -21,13 +21,19 @@ protected:
|
||||
* @param pPort: string containing port name
|
||||
* @param pDriver: string containing driver name - Really only used for SerialBus connections
|
||||
* @param pType: the type of connection @ref CANCon::type
|
||||
* @param pSerialSpeed: for devices with variable serial speed this is that speed.
|
||||
* @param pBusSpeed: set an initial speed when opening this connection
|
||||
* @param pNumBuses: the number of buses the device has
|
||||
* @param pQueueLen: the length of the lock free queue to use
|
||||
* @param pUseThread: if set to true, object will be execute in a dedicated thread
|
||||
*/
|
||||
CANConnection(QString pPort,
|
||||
QString pDriver,
|
||||
CANCon::type pType,
|
||||
CANCon::type pType,
|
||||
int pSerialSpeed,
|
||||
int pBusSpeed,
|
||||
bool pCanFd,
|
||||
int pDataRate,
|
||||
int pNumBuses,
|
||||
int pQueueLen,
|
||||
bool pUseThread);
|
||||
@@ -201,6 +207,7 @@ protected:
|
||||
int mNumBuses; //protected to allow connected device to figure out how many buses are available
|
||||
QVector<BusData> 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);
|
||||
|
||||
@@ -86,6 +86,9 @@ QVariant CANConnectionModel::data(const QModelIndex &index, int role) const
|
||||
case CANCon::SERIALBUS: return "SerialBus";
|
||||
case CANCon::GVRET_SERIAL: return "GVRET";
|
||||
case CANCon::KAYAK: return "socketcand";
|
||||
case CANCon::LAWICEL: return "LAWICEL";
|
||||
case CANCon::CANSERVER: return "CANserver";
|
||||
case CANCon::CANLOGSERVER: return "CanLogServer";
|
||||
default: {}
|
||||
}
|
||||
else qDebug() << "Tried to show connection type but connection was nullptr";
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QCanBusFrame>
|
||||
#include <QSettings>
|
||||
#include <QStringBuilder>
|
||||
#include <QtNetwork>
|
||||
|
||||
#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)> <canID> <msgId#data>")
|
||||
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 (<id>#<payload>)
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef CANLOGSERVER_H
|
||||
#define CANLOGSERVER_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <QCanBusDevice>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QTcpSocket>
|
||||
|
||||
/*************/
|
||||
#include <QDateTime>
|
||||
/*************/
|
||||
|
||||
#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
|
||||
@@ -0,0 +1,238 @@
|
||||
//
|
||||
// canserver.cpp
|
||||
// SavvyCAN
|
||||
//
|
||||
// Created by Chris Whiteford on 2022-01-21.
|
||||
//
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QCanBusFrame>
|
||||
#include <QSettings>
|
||||
#include <QStringBuilder>
|
||||
#include <QtNetwork>
|
||||
|
||||
#include "utility.h"
|
||||
#include "canserver.h"
|
||||
|
||||
CANserver::CANserver(QString serverAddressString) :
|
||||
CANConnection(serverAddressString, "CANserver", CANCon::CANSERVER, 0, 0, false, 0, 3, 4000, true),
|
||||
_udpClient(new QUdpSocket(this)),
|
||||
_heartbeatTimer(new QTimer(this))
|
||||
{
|
||||
|
||||
qDebug() << "CANserver: " << "Constructing new Connection...";
|
||||
|
||||
CANBus bus_info;
|
||||
bus_info.setActive(true);
|
||||
bus_info.setListenOnly(true);
|
||||
bus_info.setSpeed(500000);
|
||||
|
||||
setBusConfig(0, bus_info);
|
||||
setBusConfig(1, bus_info);
|
||||
setBusConfig(2, bus_info);
|
||||
|
||||
// setup udp client
|
||||
this->_canserverAddress = QHostAddress(serverAddressString);
|
||||
connect(_udpClient, SIGNAL(readyRead()), this, SLOT(readNetworkData()));
|
||||
|
||||
// setup signal and slot
|
||||
connect(_heartbeatTimer, SIGNAL(timeout()), this, SLOT(heartbeatTimerSlot()));
|
||||
_heartbeatTimer->stop();
|
||||
}
|
||||
|
||||
|
||||
CANserver::~CANserver()
|
||||
{
|
||||
qDebug() << "CANserver: " << "Deconstructing Connection...";
|
||||
|
||||
stop();
|
||||
|
||||
delete _heartbeatTimer; _heartbeatTimer = NULL;
|
||||
delete _udpClient; _udpClient = NULL;
|
||||
}
|
||||
|
||||
void CANserver::piStarted()
|
||||
{
|
||||
this->connectToDevice();
|
||||
}
|
||||
|
||||
void CANserver::piSuspend(bool pSuspend)
|
||||
{
|
||||
/* update capSuspended */
|
||||
setCapSuspended(pSuspend);
|
||||
|
||||
/* flush queue if we are suspended */
|
||||
if(isCapSuspended())
|
||||
getQueue().flush();
|
||||
}
|
||||
|
||||
|
||||
void CANserver::piStop()
|
||||
{
|
||||
this->disconnectFromDevice();
|
||||
}
|
||||
|
||||
|
||||
bool CANserver::piGetBusSettings(int pBusIdx, CANBus& pBus)
|
||||
{
|
||||
return getBusConfig(pBusIdx, pBus);
|
||||
}
|
||||
|
||||
|
||||
void CANserver::piSetBusSettings(int pBusIdx, CANBus bus)
|
||||
{
|
||||
/* sanity checks */
|
||||
if( (pBusIdx < 0) || pBusIdx >= getNumBuses())
|
||||
return;
|
||||
|
||||
/* copy bus config */
|
||||
setBusConfig(pBusIdx, bus);
|
||||
}
|
||||
|
||||
|
||||
bool CANserver::piSendFrame(const CANFrame& )
|
||||
{
|
||||
//We don't support sending frames right now
|
||||
return true;
|
||||
}
|
||||
|
||||
void CANserver::connectToDevice()
|
||||
{
|
||||
qDebug() << "CANserver: " << "Establishing UDP connection to a CANserver device...";
|
||||
|
||||
bool bindResult = _udpClient->bind(1338);
|
||||
qDebug() << "CANserver: " << "UDP Bind result: " << bindResult;
|
||||
|
||||
heartbeat();
|
||||
|
||||
qDebug() << "CANserver: " << "Scheduling additional heartbeats...";
|
||||
_heartbeatTimer->start(3500);
|
||||
|
||||
setStatus(CANCon::CONNECTED);
|
||||
|
||||
CANConStatus stats;
|
||||
stats.conStatus = getStatus();
|
||||
stats.numHardwareBuses = 3;
|
||||
emit status(stats);
|
||||
}
|
||||
|
||||
void CANserver::disconnectFromDevice()
|
||||
{
|
||||
_heartbeatTimer->stop();
|
||||
|
||||
QByteArray Data;
|
||||
Data.append("bye");
|
||||
QNetworkDatagram datagram(Data, _canserverAddress, 1338);
|
||||
|
||||
qDebug() << "CANserver: " << "Sending bye...";
|
||||
_udpClient->writeDatagram(datagram);
|
||||
|
||||
|
||||
_udpClient->close();
|
||||
}
|
||||
|
||||
void CANserver::heartbeat()
|
||||
{
|
||||
//Send the PANDA hello packet (in this case we are using the v1 version of the protocol because we just want to get ALL the data)
|
||||
QByteArray Data;
|
||||
Data.append("hello");
|
||||
QNetworkDatagram datagram(Data, _canserverAddress, 1338);
|
||||
|
||||
qDebug() << "CANserver: " << "Sending a heartbeat...";
|
||||
_udpClient->writeDatagram(datagram);
|
||||
}
|
||||
|
||||
void CANserver::readNetworkData()
|
||||
{
|
||||
QByteArray datagram;
|
||||
do {
|
||||
datagram.resize(_udpClient->pendingDatagramSize());
|
||||
_udpClient->readDatagram(datagram.data(), datagram.size());
|
||||
} while (_udpClient->hasPendingDatagrams());
|
||||
|
||||
|
||||
// If capture is suspended bail out after reading the bytes from the network. No need to parse them
|
||||
if(isCapSuspended())
|
||||
return;
|
||||
|
||||
|
||||
uint16_t packetCount = datagram.length() / 16;
|
||||
//qDebug() << "Processing " << packetCount << " packets";
|
||||
|
||||
//Process this chunk of data
|
||||
for (int i = 0 ; i < packetCount; i++)
|
||||
{
|
||||
uint16_t headerByteLocation = (i*16);
|
||||
uint16_t dataByteLocation = (i*16) + 8;
|
||||
|
||||
|
||||
uint32_t headerData1Byte1 = datagram.at(headerByteLocation + 0) & 0xFF;
|
||||
uint32_t headerData1Byte2 = datagram.at(headerByteLocation + 1) & 0xFF;
|
||||
uint32_t headerData1Byte3 = datagram.at(headerByteLocation + 2) & 0xFF;
|
||||
uint32_t headerData1Byte4 = datagram.at(headerByteLocation + 3) & 0xFF;
|
||||
|
||||
uint32_t headerData2Byte1 = datagram.at(headerByteLocation + 4) & 0xFF;
|
||||
uint32_t headerData2Byte2 = datagram.at(headerByteLocation + 5) & 0xFF;
|
||||
uint32_t headerData2Byte3 = datagram.at(headerByteLocation + 6) & 0xFF;
|
||||
uint32_t headerData2Byte4 = datagram.at(headerByteLocation + 7) & 0xFF;
|
||||
|
||||
|
||||
//printf("HB1: %02X, %02X, %02X, %02X\n", headerData1Byte1, headerData1Byte2, headerData1Byte3, headerData1Byte4);
|
||||
//printf("HB2: %02X, %02X, %02X, %02X\n", headerData2Byte1, headerData2Byte2, headerData2Byte3, headerData2Byte4);
|
||||
|
||||
uint32_t headerData1 = headerData1Byte1;
|
||||
headerData1 += headerData1Byte2 << 8;
|
||||
headerData1 += headerData1Byte3 << 16;
|
||||
headerData1 += headerData1Byte4 << 24;
|
||||
|
||||
uint32_t headerData2 = headerData2Byte1;
|
||||
headerData2 += headerData2Byte2 << 8;
|
||||
headerData2 += headerData2Byte3 << 16;
|
||||
headerData2 += headerData2Byte4 << 24;
|
||||
|
||||
uint32_t frameId = headerData1 >> 21;
|
||||
uint32_t length = (headerData2 & 0x0F);
|
||||
uint32_t busId = (headerData2 >> 4);
|
||||
|
||||
//printf("frameId: %02X, busId: %d, length: %d\n", frameId, busId, length);
|
||||
|
||||
CANFrame* frame_p = getQueue().get();
|
||||
if(frame_p)
|
||||
{
|
||||
frame_p->setFrameId(frameId);
|
||||
frame_p->setExtendedFrameFormat(0);
|
||||
|
||||
// We need to change the bus id if it is the special CANserver bus id.
|
||||
// This keeps us from needing to define 15 busses just to get access to our special one
|
||||
if (busId == 15)
|
||||
{
|
||||
busId = 2;
|
||||
}
|
||||
frame_p->bus = busId;
|
||||
|
||||
frame_p->setFrameType(QCanBusFrame::DataFrame);
|
||||
frame_p->isReceived = true;
|
||||
|
||||
frame_p->setTimeStamp(QCanBusFrame::TimeStamp(0, QDateTime::currentMSecsSinceEpoch() * 1000ul));
|
||||
|
||||
frame_p->setPayload(datagram.mid(dataByteLocation, length));
|
||||
|
||||
checkTargettedFrame(*frame_p);
|
||||
|
||||
/* enqueue frame */
|
||||
getQueue().queue();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CANserver::heartbeatTimerSlot()
|
||||
{
|
||||
heartbeat();
|
||||
}
|
||||
|
||||
void CANserver::readSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// canserver.h
|
||||
// SavvyCAN
|
||||
//
|
||||
// Created by Chris Whiteford on 2022-01-21.
|
||||
//
|
||||
|
||||
#ifndef canserver_h
|
||||
#define canserver_h
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <QCanBusDevice>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QUdpSocket>
|
||||
|
||||
/*************/
|
||||
#include <QDateTime>
|
||||
/*************/
|
||||
|
||||
#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 */
|
||||
@@ -64,6 +64,7 @@ ConnectionWindow::ConnectionWindow(QWidget *parent) :
|
||||
|
||||
ui->cbBusSpeed->addItem("33333");
|
||||
ui->cbBusSpeed->addItem("50000");
|
||||
ui->cbBusSpeed->addItem("83333");
|
||||
ui->cbBusSpeed->addItem("100000");
|
||||
ui->cbBusSpeed->addItem("125000");
|
||||
ui->cbBusSpeed->addItem("250000");
|
||||
@@ -78,13 +79,12 @@ ConnectionWindow::ConnectionWindow(QWidget *parent) :
|
||||
//Need to make sure it tries to share the address in case there are
|
||||
//multiple instances of SavvyCAN running.
|
||||
rxBroadcastGVRET->bind(QHostAddress::AnyIPv4, 17222, QAbstractSocket::ShareAddress);
|
||||
|
||||
connect(rxBroadcastGVRET, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams()));
|
||||
connect(rxBroadcastGVRET, &QUdpSocket::readyRead, this, &ConnectionWindow::readPendingDatagrams);
|
||||
|
||||
//Doing the same for socketcand/kayak hosts:
|
||||
rxBroadcastKayak = new QUdpSocket(this);
|
||||
rxBroadcastKayak->bind(QHostAddress::AnyIPv4, 42000, QAbstractSocket::ShareAddress);
|
||||
connect(rxBroadcastKayak, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams()));
|
||||
connect(rxBroadcastKayak, &QUdpSocket::readyRead, this, &ConnectionWindow::readPendingDatagrams);
|
||||
|
||||
}
|
||||
|
||||
@@ -109,16 +109,16 @@ void ConnectionWindow::readPendingDatagrams()
|
||||
while(!CANBeaconXml.atEnd() && !CANBeaconXml.hasError())
|
||||
{
|
||||
CANBeaconXml.readNext();
|
||||
if(CANBeaconXml.name() == "CANBeacon" && !CANBeaconXml.isEndElement())
|
||||
if(CANBeaconXml.name() == QString("CANBeacon") && !CANBeaconXml.isEndElement())
|
||||
KayakHost.append(CANBeaconXml.attributes().value("name"));
|
||||
|
||||
if(CANBeaconXml.name() == "URL")
|
||||
if(CANBeaconXml.name() == QString("URL"))
|
||||
KayakHost.append(" (" + CANBeaconXml.readElementText() + ')');
|
||||
|
||||
//Kayak can theoretically send multiple busses over one ports
|
||||
//TODO: implement this case in socketcand.cpp
|
||||
if(CANBeaconXml.name() == "Bus" && !CANBeaconXml.isEndElement())
|
||||
KayakBus.append(CANBeaconXml.attributes().value("name") + ",");
|
||||
if(CANBeaconXml.name() == QString("Bus") && !CANBeaconXml.isEndElement())
|
||||
KayakBus.append(CANBeaconXml.attributes().value("name").toUtf8() + ",");
|
||||
|
||||
}
|
||||
KayakHost = KayakBus.left(KayakBus.length() - 1) + "@" + KayakHost;
|
||||
@@ -236,12 +236,12 @@ void ConnectionWindow::consoleEnableChanged(bool checked) {
|
||||
CANConnection* conn_p = connModel->getAtIdx(selIdx);
|
||||
|
||||
if (checked) { //enable console
|
||||
connect(conn_p, SIGNAL(debugOutput(QString)), this, SLOT(getDebugText(QString)));
|
||||
connect(this, SIGNAL(sendDebugData(QByteArray)), conn_p, SLOT(debugInput(QByteArray)));
|
||||
connect(conn_p, &CANConnection::debugOutput, this, &ConnectionWindow::getDebugText, Qt::UniqueConnection);
|
||||
connect(this, &ConnectionWindow::sendDebugData, conn_p, &CANConnection::debugInput, Qt::UniqueConnection);
|
||||
}
|
||||
else { //turn it off
|
||||
disconnect(conn_p, SIGNAL(debugOutput(QString)), nullptr, nullptr);
|
||||
disconnect(this, SIGNAL(sendDebugData(QByteArray)), conn_p, SLOT(debugInput(QByteArray)));
|
||||
disconnect(conn_p, &CANConnection::debugOutput, nullptr, nullptr);
|
||||
disconnect(this, &ConnectionWindow::sendDebugData, conn_p, &CANConnection::debugInput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,6 +251,10 @@ void ConnectionWindow::handleNewConn()
|
||||
CANCon::type newType;
|
||||
QString newPort;
|
||||
QString newDriver;
|
||||
int newSerialSpeed;
|
||||
int newBusSpeed;
|
||||
bool newCanFd;
|
||||
int newDataRate;
|
||||
CANConnection *conn;
|
||||
|
||||
if (thisDialog->exec() == QDialog::Accepted)
|
||||
@@ -258,7 +262,11 @@ void ConnectionWindow::handleNewConn()
|
||||
newType = thisDialog->getConnectionType();
|
||||
newPort = thisDialog->getPortName();
|
||||
newDriver = thisDialog->getDriverName();
|
||||
conn = create(newType, newPort, newDriver);
|
||||
newSerialSpeed = thisDialog->getSerialSpeed();
|
||||
newBusSpeed = thisDialog->getBusSpeed();
|
||||
newCanFd=thisDialog->isCanFd();
|
||||
newDataRate = thisDialog->getDataRate();
|
||||
conn = create(newType, newPort, newDriver, newSerialSpeed, newBusSpeed, newCanFd, newDataRate);
|
||||
if (conn)
|
||||
{
|
||||
connModel->add(conn);
|
||||
@@ -293,6 +301,8 @@ void ConnectionWindow::handleResetConn()
|
||||
{
|
||||
QString port, driver;
|
||||
CANCon::type type;
|
||||
int serSpeed, busSpeed, dataRate;
|
||||
bool canFd;
|
||||
|
||||
int selIdx = ui->tableConnections->selectionModel()->currentIndex().row();
|
||||
if (selIdx <0) return;
|
||||
@@ -305,13 +315,18 @@ void ConnectionWindow::handleResetConn()
|
||||
type = conn_p->getType();
|
||||
port = conn_p->getPort();
|
||||
driver = conn_p->getDriver();
|
||||
serSpeed = 0; //TODO: implement these
|
||||
busSpeed = 0;
|
||||
dataRate = 0;
|
||||
canFd = false;
|
||||
|
||||
|
||||
/* stop and delete connection */
|
||||
conn_p->stop();
|
||||
|
||||
conn_p = nullptr;
|
||||
|
||||
conn_p = create(type, port, driver);
|
||||
conn_p = create(type, port, driver, serSpeed, busSpeed,canFd,dataRate);
|
||||
if (conn_p) connModel->replace(selIdx, conn_p);
|
||||
}
|
||||
|
||||
@@ -358,6 +373,8 @@ void ConnectionWindow::saveBusSettings()
|
||||
bus.setSpeed(ui->cbBusSpeed->currentText().toInt());
|
||||
bus.setActive(ui->ckEnable->isChecked());
|
||||
bus.setListenOnly(ui->ckListenOnly->isChecked());
|
||||
bus.setCanFD(ui->canFDEnable->isChecked());
|
||||
bus.setDataRate(ui->cbDataRate->currentText().toInt());
|
||||
conn_p->setBusSettings(offset, bus);
|
||||
}
|
||||
}
|
||||
@@ -374,7 +391,10 @@ void ConnectionWindow::populateBusDetails(int offset)
|
||||
{
|
||||
//bool ret;
|
||||
//int numBuses;
|
||||
|
||||
ui->canFDEnable->setVisible(false);
|
||||
ui->canFDEnable_label->setVisible(false);
|
||||
ui->dataRate_label->setVisible(false);
|
||||
ui->cbDataRate->setVisible(false);
|
||||
CANConnection* conn_p = connModel->getAtIdx(selIdx);
|
||||
CANBus bus;
|
||||
if(!conn_p) return;
|
||||
@@ -389,6 +409,14 @@ void ConnectionWindow::populateBusDetails(int offset)
|
||||
//ui->lblBusNum->setText(QString::number(busBase + offset));
|
||||
ui->ckListenOnly->setChecked(bus.isListenOnly());
|
||||
ui->ckEnable->setChecked(bus.isActive());
|
||||
if (conn_p->getType() == CANCon::type::SERIALBUS || conn_p->getType() == CANCon::type::LAWICEL)
|
||||
{
|
||||
ui->canFDEnable->setVisible(true);
|
||||
ui->canFDEnable_label->setVisible(true);
|
||||
ui->canFDEnable->setChecked(bus.isCanFD());
|
||||
ui->cbDataRate->setVisible(true);
|
||||
ui->dataRate_label->setVisible(true);
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (int i = 0; i < ui->cbBusSpeed->count(); i++)
|
||||
@@ -400,7 +428,19 @@ void ConnectionWindow::populateBusDetails(int offset)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) ui->cbBusSpeed->addItem(QString::number(bus.getSpeed()));
|
||||
found = false;
|
||||
for (int i = 0; i < ui->cbDataRate->count(); i++)
|
||||
{
|
||||
if (bus.getDataRate() == ui->cbDataRate->itemText(i).toInt())
|
||||
{
|
||||
found = true;
|
||||
ui->cbDataRate->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) ui->cbDataRate->addItem(QString::number(bus.getDataRate()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,12 +451,11 @@ void ConnectionWindow::currentTabChanged(int newIdx)
|
||||
|
||||
void ConnectionWindow::currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous)
|
||||
{
|
||||
Q_UNUSED(previous);
|
||||
|
||||
int selIdx = current.row();
|
||||
|
||||
disconnect(connModel->getAtIdx(previous.row()), SIGNAL(debugOutput(QString)), 0, 0);
|
||||
disconnect(this, SIGNAL(sendDebugData(QByteArray)), connModel->getAtIdx(previous.row()), SLOT(debugInput(QByteArray)));
|
||||
CANConnection* prevConn = connModel->getAtIdx(previous.row());
|
||||
if(prevConn != nullptr)
|
||||
disconnect(prevConn, &CANConnection::debugOutput, nullptr, nullptr);
|
||||
disconnect(this, &ConnectionWindow::sendDebugData, nullptr, nullptr);
|
||||
|
||||
/* set parameters */
|
||||
if (selIdx == -1) {
|
||||
@@ -433,7 +472,7 @@ void ConnectionWindow::currentRowChanged(const QModelIndex ¤t, const QMode
|
||||
if(!conn_p) return;
|
||||
|
||||
//because this might have already been setup during the initial setup so tear that one down and then create the normal one.
|
||||
//disconnect(conn_p, SIGNAL(debugOutput(QString)), 0, 0);
|
||||
//disconnect(conn_p, &CANConnection::debugOutput, 0, 0);
|
||||
|
||||
numBuses = conn_p->getNumBuses();
|
||||
int numB = ui->tabBuses->count();
|
||||
@@ -446,8 +485,8 @@ void ConnectionWindow::currentRowChanged(const QModelIndex ¤t, const QMode
|
||||
populateBusDetails(0);
|
||||
if (ui->ckEnableConsole->isChecked())
|
||||
{
|
||||
connect(conn_p, SIGNAL(debugOutput(QString)), this, SLOT(getDebugText(QString)));
|
||||
connect(this, SIGNAL(sendDebugData(QByteArray)), conn_p, SLOT(debugInput(QByteArray)));
|
||||
connect(conn_p, &CANConnection::debugOutput, this, &ConnectionWindow::getDebugText, Qt::UniqueConnection);
|
||||
connect(this, &ConnectionWindow::sendDebugData, conn_p, &CANConnection::debugInput, Qt::UniqueConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -476,21 +515,20 @@ void ConnectionWindow::handleSendText() {
|
||||
emit sendDebugData(bytes);
|
||||
}
|
||||
|
||||
CANConnection* ConnectionWindow::create(CANCon::type pTye, QString pPortName, QString pDriver)
|
||||
CANConnection* ConnectionWindow::create(CANCon::type pTye, QString pPortName, QString pDriver, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate)
|
||||
{
|
||||
CANConnection* conn_p;
|
||||
|
||||
/* create connection */
|
||||
conn_p = CanConFactory::create(pTye, pPortName, pDriver);
|
||||
conn_p = CanConFactory::create(pTye, pPortName, pDriver, pSerialSpeed, pBusSpeed, pCanFd, pDataRate);
|
||||
if(conn_p)
|
||||
{
|
||||
/* connect signal */
|
||||
connect(conn_p, SIGNAL(status(CANConStatus)),
|
||||
this, SLOT(connectionStatus(CANConStatus)));
|
||||
connect(conn_p, &CANConnection::status, this, &ConnectionWindow::connectionStatus);
|
||||
if (ui->ckEnableConsole->isChecked())
|
||||
{
|
||||
{
|
||||
//set up the debug console to operate if we've selected it. Doing so here allows debugging right away during set up
|
||||
connect(conn_p, SIGNAL(debugOutput(QString)), this, SLOT(getDebugText(QString)));
|
||||
connect(conn_p, &CANConnection::debugOutput, this, &ConnectionWindow::getDebugText, Qt::UniqueConnection);
|
||||
}
|
||||
/*TODO add return value and checks */
|
||||
conn_p->start();
|
||||
@@ -501,8 +539,10 @@ CANConnection* ConnectionWindow::create(CANCon::type pTye, QString pPortName, QS
|
||||
|
||||
void ConnectionWindow::loadConnections()
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
qRegisterMetaTypeStreamOperators<CANBus>();
|
||||
qRegisterMetaTypeStreamOperators<QList<CANBus>>();
|
||||
#endif
|
||||
|
||||
QSettings settings;
|
||||
|
||||
@@ -516,7 +556,8 @@ void ConnectionWindow::loadConnections()
|
||||
|
||||
for(int i = 0 ; i < portNames.count() ; i++)
|
||||
{
|
||||
CANConnection* conn_p = create((CANCon::type)devTypes[i], portNames[i], driverNames[i]);
|
||||
//TODO: add serial speed and bus speed to this properly.
|
||||
CANConnection* conn_p = create((CANCon::type)devTypes[i], portNames[i], driverNames[i], 0, 0, false, 0);
|
||||
/* add connection to model */
|
||||
connModel->add(conn_p);
|
||||
}
|
||||
@@ -534,6 +575,8 @@ void ConnectionWindow::saveConnections()
|
||||
QVector<QString> portNames;
|
||||
QVector<int> devTypes;
|
||||
QVector<QString> driverNames;
|
||||
QVector<int> serialSpeeds;
|
||||
QVector<int> busSpeeds;
|
||||
|
||||
/* save connections */
|
||||
foreach(CANConnection* conn_p, conns)
|
||||
|
||||
@@ -65,7 +65,7 @@ private:
|
||||
QVector<QString> remoteDeviceIPGVRET;
|
||||
QVector<QString> remoteDeviceKayak;
|
||||
|
||||
CANConnection* create(CANCon::type pTye, QString pPortName, QString pDriver);
|
||||
CANConnection* create(CANCon::type pTye, QString pPortName, QString pDriver, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate);
|
||||
void populateBusDetails(int offset);
|
||||
void loadConnections();
|
||||
void saveConnections();
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "gvretserial.h"
|
||||
|
||||
GVRetSerial::GVRetSerial(QString portName, bool useTcp) :
|
||||
CANConnection(portName, "gvret", CANCon::GVRET_SERIAL, 3, 4000, true),
|
||||
CANConnection(portName, "gvret", CANCon::GVRET_SERIAL, 0, 0, false, 0, 3, 4000, true),
|
||||
mTimer(this), /*NB: set this as parent of timer to manage it from working thread */
|
||||
useTcp(useTcp)
|
||||
{
|
||||
@@ -485,6 +485,7 @@ void GVRetSerial::serialError(QSerialPort::SerialPortError err)
|
||||
killConnection = true;
|
||||
piStop();
|
||||
break;
|
||||
#if QT_VERSION <= QT_VERSION_CHECK( 6, 0, 0 )
|
||||
case QSerialPort::ParityError:
|
||||
errMessage = "Parity error on serial port";
|
||||
break;
|
||||
@@ -494,6 +495,7 @@ void GVRetSerial::serialError(QSerialPort::SerialPortError err)
|
||||
case QSerialPort::BreakConditionError:
|
||||
errMessage = "Break error on serial port";
|
||||
break;
|
||||
#endif
|
||||
case QSerialPort::WriteError:
|
||||
errMessage = "Write error on serial port";
|
||||
piStop();
|
||||
@@ -584,10 +586,11 @@ void GVRetSerial::readSerialData()
|
||||
{
|
||||
c = data.at(i);
|
||||
//qDebug() << c << " " << QString::number(c, 16) << " " << QString(c);
|
||||
debugBuild = debugBuild % QString::number(c, 16) % " ";
|
||||
debugBuild = debugBuild % QString::number(c, 16).rightJustified(2,'0') % " ";
|
||||
procRXChar(c);
|
||||
}
|
||||
debugOutput(debugBuild);
|
||||
//qDebug() << debugBuild;
|
||||
}
|
||||
|
||||
//Debugging data sent from connection window. Inject it into Comm traffic.
|
||||
@@ -640,7 +643,7 @@ void GVRetSerial::procRXChar(unsigned char c)
|
||||
break;
|
||||
case 9:
|
||||
validationCounter = 10;
|
||||
//qDebug() << "Got validated";
|
||||
qDebug() << "Got validated";
|
||||
rx_state = IDLE;
|
||||
break;
|
||||
case 12:
|
||||
@@ -653,6 +656,15 @@ void GVRetSerial::procRXChar(unsigned char c)
|
||||
qDebug() << "Got extended buses info reply";
|
||||
rx_step = 0;
|
||||
break;
|
||||
case 20:
|
||||
rx_state = BUILD_FD_FRAME;
|
||||
rx_step = 0;
|
||||
break;
|
||||
case 22:
|
||||
rx_state = GET_FD_SETTINGS;
|
||||
rx_step = 0;
|
||||
qDebug() << "Got FD settings reply";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case BUILD_CAN_FRAME:
|
||||
@@ -702,6 +714,93 @@ void GVRetSerial::procRXChar(unsigned char c)
|
||||
break;
|
||||
default:
|
||||
if (rx_step < buildData.length() + 9)
|
||||
{
|
||||
buildData[rx_step - 9] = c;
|
||||
if (rx_step == buildData.length() + 8) //it's the last data byte so immediately process the frame
|
||||
{
|
||||
rx_state = IDLE;
|
||||
rx_step = 0;
|
||||
buildFrame.isReceived = true;
|
||||
buildFrame.setPayload(buildData);
|
||||
buildFrame.setFrameType(QCanBusFrame::FrameType::DataFrame);
|
||||
if (!isCapSuspended())
|
||||
{
|
||||
/* get frame from queue */
|
||||
CANFrame* frame_p = getQueue().get();
|
||||
if(frame_p) {
|
||||
//qDebug() << "GVRET got frame on bus " << frame_p->bus;
|
||||
/* copy frame */
|
||||
*frame_p = buildFrame;
|
||||
checkTargettedFrame(buildFrame);
|
||||
/* enqueue frame */
|
||||
getQueue().queue();
|
||||
}
|
||||
else
|
||||
qDebug() << "can't get a frame, ERROR";
|
||||
|
||||
//take the time the frame came in and try to resync the time base.
|
||||
//if (continuousTimeSync) txTimestampBasis = QDateTime::currentMSecsSinceEpoch() - (buildFrame.timestamp / 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
else //should never get here! But, just in case, reset the comm
|
||||
{
|
||||
rx_state = IDLE;
|
||||
rx_step = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
rx_step++;
|
||||
break;
|
||||
case BUILD_FD_FRAME:
|
||||
switch (rx_step)
|
||||
{
|
||||
case 0:
|
||||
buildTimestamp = c;
|
||||
break;
|
||||
case 1:
|
||||
buildTimestamp |= (uint)(c << 8);
|
||||
break;
|
||||
case 2:
|
||||
buildTimestamp |= (uint)c << 16;
|
||||
break;
|
||||
case 3:
|
||||
buildTimestamp |= (uint)c << 24;
|
||||
|
||||
buildTimestamp += timeBasis;
|
||||
if (useSystemTime)
|
||||
{
|
||||
buildTimestamp = QDateTime::currentMSecsSinceEpoch() * 1000l;
|
||||
}
|
||||
buildFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, buildTimestamp));
|
||||
break;
|
||||
case 4:
|
||||
buildId = c;
|
||||
break;
|
||||
case 5:
|
||||
buildId |= c << 8;
|
||||
break;
|
||||
case 6:
|
||||
buildId |= c << 16;
|
||||
break;
|
||||
case 7:
|
||||
buildId |= c << 24;
|
||||
if ((buildId & 1 << 31) == 1u << 31)
|
||||
{
|
||||
buildId &= 0x7FFFFFFF;
|
||||
buildFrame.setExtendedFrameFormat(true);
|
||||
}
|
||||
else buildFrame.setExtendedFrameFormat(false);
|
||||
buildFrame.setFrameId(buildId);
|
||||
break;
|
||||
case 8:
|
||||
buildData.resize(c & 0x3F);
|
||||
break;
|
||||
case 9:
|
||||
buildFrame.bus = c;
|
||||
break;
|
||||
default:
|
||||
if (rx_step < buildData.length() + 10)
|
||||
{
|
||||
buildData[rx_step - 9] = c;
|
||||
}
|
||||
@@ -719,7 +818,7 @@ void GVRetSerial::procRXChar(unsigned char c)
|
||||
if(frame_p) {
|
||||
//qDebug() << "GVRET got frame on bus " << frame_p->bus;
|
||||
/* copy frame */
|
||||
*frame_p = buildFrame;
|
||||
*frame_p = buildFrame;
|
||||
checkTargettedFrame(buildFrame);
|
||||
/* enqueue frame */
|
||||
getQueue().queue();
|
||||
@@ -912,6 +1011,8 @@ void GVRetSerial::procRXChar(unsigned char c)
|
||||
|
||||
emit status(stats);
|
||||
break;
|
||||
case GET_FD_SETTINGS:
|
||||
break;
|
||||
case GET_EXT_BUSES:
|
||||
switch (rx_step)
|
||||
{
|
||||
|
||||
@@ -32,7 +32,9 @@ enum STATE
|
||||
GET_DEVICE_INFO,
|
||||
SET_SINGLEWIRE_MODE,
|
||||
GET_NUM_BUSES,
|
||||
GET_EXT_BUSES
|
||||
GET_EXT_BUSES,
|
||||
BUILD_FD_FRAME,
|
||||
GET_FD_SETTINGS
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,690 @@
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QCanBusFrame>
|
||||
#include <QSerialPortInfo>
|
||||
#include <QSettings>
|
||||
#include <QStringBuilder>
|
||||
#include <QtNetwork>
|
||||
|
||||
#include "lawicel_serial.h"
|
||||
#include "utility.h"
|
||||
|
||||
LAWICELSerial::LAWICELSerial(QString portName, int serialSpeed, int lawicelSpeed, bool canFd, int dataRate) :
|
||||
CANConnection(portName, "LAWICEL", CANCon::LAWICEL,serialSpeed, lawicelSpeed, canFd, dataRate, 3, 4000, true),
|
||||
mTimer(this) /*NB: set this as parent of timer to manage it from working thread */
|
||||
{
|
||||
sendDebug("LAWICELSerial()");
|
||||
|
||||
serial = nullptr;
|
||||
isAutoRestart = false;
|
||||
|
||||
readSettings();
|
||||
}
|
||||
|
||||
|
||||
LAWICELSerial::~LAWICELSerial()
|
||||
{
|
||||
stop();
|
||||
sendDebug("~LAWICELSerial()");
|
||||
}
|
||||
|
||||
void LAWICELSerial::sendDebug(const QString debugText)
|
||||
{
|
||||
qDebug() << debugText;
|
||||
debugOutput(debugText);
|
||||
}
|
||||
|
||||
void LAWICELSerial::sendToSerial(const QByteArray &bytes)
|
||||
{
|
||||
if (serial == nullptr)
|
||||
{
|
||||
sendDebug("Attempt to write to serial port when it has not been initialized!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (serial && !serial->isOpen())
|
||||
{
|
||||
sendDebug("Attempt to write to serial port when it is not open!");
|
||||
return;
|
||||
}
|
||||
|
||||
QString buildDebug;
|
||||
buildDebug = "Write to serial -> ";
|
||||
foreach (int byt, bytes) {
|
||||
byt = (unsigned char)byt;
|
||||
buildDebug = buildDebug % QString::number(byt, 16) % " ";
|
||||
}
|
||||
sendDebug(buildDebug);
|
||||
|
||||
if (serial) serial->write(bytes);
|
||||
}
|
||||
|
||||
void LAWICELSerial::piStarted()
|
||||
{
|
||||
connectDevice();
|
||||
}
|
||||
|
||||
|
||||
void LAWICELSerial::piSuspend(bool pSuspend)
|
||||
{
|
||||
/* update capSuspended */
|
||||
setCapSuspended(pSuspend);
|
||||
|
||||
/* flush queue if we are suspended */
|
||||
if(isCapSuspended())
|
||||
getQueue().flush();
|
||||
}
|
||||
|
||||
|
||||
void LAWICELSerial::piStop()
|
||||
{
|
||||
mTimer.stop();
|
||||
disconnectDevice();
|
||||
}
|
||||
|
||||
|
||||
bool LAWICELSerial::piGetBusSettings(int pBusIdx, CANBus& pBus)
|
||||
{
|
||||
return getBusConfig(pBusIdx, pBus);
|
||||
}
|
||||
|
||||
|
||||
void LAWICELSerial::piSetBusSettings(int pBusIdx, CANBus bus)
|
||||
{
|
||||
/* sanity checks */
|
||||
if( (pBusIdx < 0) || pBusIdx >= getNumBuses())
|
||||
return;
|
||||
|
||||
/* copy bus config */
|
||||
setBusConfig(pBusIdx, bus);
|
||||
/*
|
||||
qDebug() << "About to update bus " << pBusIdx << " on GVRET";
|
||||
if (pBusIdx == 0)
|
||||
{
|
||||
can0Baud = bus.getSpeed();
|
||||
can0Baud |= 0x80000000;
|
||||
if (bus.isActive())
|
||||
{
|
||||
can0Baud |= 0x40000000;
|
||||
can0Enabled = true;
|
||||
}
|
||||
else can0Enabled = false;
|
||||
|
||||
if (bus.isListenOnly())
|
||||
{
|
||||
can0Baud |= 0x20000000;
|
||||
can0ListenOnly = true;
|
||||
}
|
||||
else can0ListenOnly = false;
|
||||
}
|
||||
*/
|
||||
if (pBusIdx < 2) {
|
||||
/* update baud rates */
|
||||
QByteArray buffer;
|
||||
//sendDebug("Got signal to update bauds. 1: " + QString::number((can0Baud & 0xFFFFFFF)));
|
||||
buffer[0] = (char)0xF1; //start of a command over serial
|
||||
//sendToSerial(buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool LAWICELSerial::piSendFrame(const CANFrame& frame)
|
||||
{
|
||||
QByteArray buffer;
|
||||
int c;
|
||||
quint32 ID;
|
||||
|
||||
//qDebug() << "Sending out lawicel frame with id " << frame.ID << " on bus " << frame.bus;
|
||||
|
||||
framesRapid++;
|
||||
|
||||
if (serial == nullptr) return false;
|
||||
if (serial && !serial->isOpen()) return false;
|
||||
//if (!isConnected) return false;
|
||||
|
||||
// Doesn't make sense to send an error frame
|
||||
// to an adapter
|
||||
if (frame.frameId() & 0x20000000) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ID = frame.frameId();
|
||||
if (frame.hasExtendedFrameFormat()) ID |= 1u << 31;
|
||||
|
||||
int idx = 0;
|
||||
QString buildStr;
|
||||
if(frame.hasFlexibleDataRateFormat()){
|
||||
if (frame.hasExtendedFrameFormat())
|
||||
{
|
||||
if (frame.hasBitrateSwitch())
|
||||
buildStr = QString::asprintf("B%08X%u", ID, LAWICELSerial::bytes_to_dlc_code(frame.payload().length()));
|
||||
else
|
||||
buildStr = QString::asprintf("D%08X%u", ID, LAWICELSerial::bytes_to_dlc_code(frame.payload().length()));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (frame.hasBitrateSwitch())
|
||||
buildStr = QString::asprintf("b%03X%u", ID, LAWICELSerial::bytes_to_dlc_code(frame.payload().length()));
|
||||
else
|
||||
buildStr = QString::asprintf("d%03X%u", ID, LAWICELSerial::bytes_to_dlc_code(frame.payload().length()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (frame.hasExtendedFrameFormat())
|
||||
{
|
||||
buildStr = QString::asprintf("T%08X%u", ID, frame.payload().length());
|
||||
}
|
||||
else
|
||||
{
|
||||
buildStr = QString::asprintf("t%03X%u", ID, frame.payload().length());
|
||||
}
|
||||
}
|
||||
foreach (QChar chr, buildStr)
|
||||
{
|
||||
buffer[idx] = chr.toLatin1();
|
||||
idx++;
|
||||
}
|
||||
|
||||
for (c = 0; c < frame.payload().length(); c++)
|
||||
{
|
||||
QString byt = Utility::formatByteAsHex(frame.payload()[c]);
|
||||
buffer[idx + (c * 2)] = byt[0].toLatin1();
|
||||
buffer[idx + (c * 2) + 1] = byt[1].toLatin1();
|
||||
}
|
||||
buffer[idx + (frame.payload().length() * 2)] = 13; //CR
|
||||
|
||||
sendToSerial(buffer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/****************************************************************/
|
||||
|
||||
void LAWICELSerial::readSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
}
|
||||
|
||||
|
||||
void LAWICELSerial::connectDevice()
|
||||
{
|
||||
QSettings settings;
|
||||
|
||||
/* disconnect device */
|
||||
if(serial)
|
||||
disconnectDevice();
|
||||
|
||||
/* open new device */
|
||||
|
||||
qDebug() << "Serial port: " << getPort();
|
||||
|
||||
serial = new QSerialPort(QSerialPortInfo(getPort()));
|
||||
if(!serial) {
|
||||
sendDebug("can't open serial port " + getPort());
|
||||
return;
|
||||
}
|
||||
sendDebug("Created Serial Port Object");
|
||||
|
||||
/* connect reading event */
|
||||
connect(serial, SIGNAL(readyRead()), this, SLOT(readSerialData()));
|
||||
connect(serial, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(serialError(QSerialPort::SerialPortError)));
|
||||
|
||||
/* configure */
|
||||
serial->setBaudRate(mSerialSpeed);
|
||||
serial->setDataBits(serial->Data8);
|
||||
|
||||
serial->setFlowControl(serial->HardwareControl);
|
||||
//serial->setFlowControl(serial->NoFlowControl);
|
||||
if (!serial->open(QIODevice::ReadWrite))
|
||||
{
|
||||
//sendDebug("Error returned during port opening: " + serial->errorString());
|
||||
}
|
||||
else
|
||||
{
|
||||
//serial->setDataTerminalReady(true); //Seemingly these two lines used to be needed
|
||||
//serial->setRequestToSend(true); //But, really both ends should automatically handle these
|
||||
deviceConnected();
|
||||
}
|
||||
}
|
||||
|
||||
void LAWICELSerial::deviceConnected()
|
||||
{
|
||||
sendDebug("Connecting to LAWICEL Device!");
|
||||
|
||||
QByteArray output;
|
||||
|
||||
output.clear();
|
||||
output.append('C'); //close the bus in case it was already up
|
||||
output.append(13);
|
||||
sendToSerial(output);
|
||||
|
||||
output.clear();
|
||||
output.append('S'); //configure speed of bus
|
||||
switch (this->mBusData[0].mBus.getSpeed())
|
||||
{
|
||||
case 10000:
|
||||
output.append('0');
|
||||
break;
|
||||
case 20000:
|
||||
output.append('1');
|
||||
break;
|
||||
case 50000:
|
||||
output.append('2');
|
||||
break;
|
||||
case 100000:
|
||||
output.append('3');
|
||||
break;
|
||||
case 125000:
|
||||
output.append('4');
|
||||
break;
|
||||
case 250000:
|
||||
output.append('5');
|
||||
break;
|
||||
case 500000:
|
||||
output.append('6');
|
||||
break;
|
||||
case 800000:
|
||||
output.append('7');
|
||||
break;
|
||||
case 1000000:
|
||||
output.append('8');
|
||||
break;
|
||||
default:
|
||||
output.append('6');
|
||||
break;
|
||||
}
|
||||
|
||||
output.append('\x0D');
|
||||
|
||||
sendToSerial(output);
|
||||
|
||||
output.clear();
|
||||
|
||||
if (this->canFd){
|
||||
switch (this->dataRate)
|
||||
{
|
||||
case 1000000:
|
||||
output.append("Y1");
|
||||
break;
|
||||
case 2000000:
|
||||
output.append("Y2");
|
||||
break;
|
||||
case 4000000:
|
||||
output.append("Y4");
|
||||
break;
|
||||
case 5000000:
|
||||
output.append("Y5");
|
||||
break;
|
||||
default:
|
||||
output.append("Y2");
|
||||
break;
|
||||
}
|
||||
|
||||
output.append('\x0D');
|
||||
sendToSerial(output);
|
||||
output.clear();
|
||||
}
|
||||
|
||||
output.append('O'); //open bus now that we set the speed
|
||||
output.append(13);
|
||||
|
||||
sendToSerial(output);
|
||||
|
||||
mNumBuses = 1;
|
||||
setStatus(CANCon::CONNECTED);
|
||||
CANConStatus stats;
|
||||
stats.conStatus = getStatus();
|
||||
stats.numHardwareBuses = mNumBuses;
|
||||
mBusData[0].mConfigured = true;
|
||||
mBusData[0].mBus.setActive(true);
|
||||
//mBusData[0].mBus.setSpeed();
|
||||
emit status(stats);
|
||||
}
|
||||
|
||||
void LAWICELSerial::disconnectDevice() {
|
||||
if (serial != nullptr)
|
||||
{
|
||||
if (serial->isOpen())
|
||||
{
|
||||
//serial->clear();
|
||||
serial->close();
|
||||
|
||||
}
|
||||
serial->disconnect(); //disconnect all signals
|
||||
delete serial;
|
||||
serial = nullptr;
|
||||
}
|
||||
|
||||
setStatus(CANCon::NOT_CONNECTED);
|
||||
CANConStatus stats;
|
||||
stats.conStatus = getStatus();
|
||||
stats.numHardwareBuses = mNumBuses;
|
||||
emit status(stats);
|
||||
}
|
||||
|
||||
void LAWICELSerial::serialError(QSerialPort::SerialPortError err)
|
||||
{
|
||||
QString errMessage;
|
||||
bool killConnection = false;
|
||||
switch (err)
|
||||
{
|
||||
case QSerialPort::NoError:
|
||||
return;
|
||||
case QSerialPort::DeviceNotFoundError:
|
||||
errMessage = "Device not found error on serial";
|
||||
killConnection = true;
|
||||
piStop();
|
||||
break;
|
||||
case QSerialPort::PermissionError:
|
||||
errMessage = "Permission error on serial port";
|
||||
killConnection = true;
|
||||
piStop();
|
||||
break;
|
||||
case QSerialPort::OpenError:
|
||||
errMessage = "Open error on serial port";
|
||||
killConnection = true;
|
||||
piStop();
|
||||
break;
|
||||
case QSerialPort::ParityError:
|
||||
errMessage = "Parity error on serial port";
|
||||
break;
|
||||
case QSerialPort::FramingError:
|
||||
errMessage = "Framing error on serial port";
|
||||
break;
|
||||
case QSerialPort::BreakConditionError:
|
||||
errMessage = "Break error on serial port";
|
||||
break;
|
||||
case QSerialPort::WriteError:
|
||||
errMessage = "Write error on serial port";
|
||||
piStop();
|
||||
break;
|
||||
case QSerialPort::ReadError:
|
||||
errMessage = "Read error on serial port";
|
||||
piStop();
|
||||
break;
|
||||
case QSerialPort::ResourceError:
|
||||
errMessage = "Serial port seems to have disappeared.";
|
||||
killConnection = true;
|
||||
piStop();
|
||||
break;
|
||||
case QSerialPort::UnsupportedOperationError:
|
||||
errMessage = "Unsupported operation on serial port";
|
||||
killConnection = true;
|
||||
break;
|
||||
case QSerialPort::UnknownError:
|
||||
errMessage = "Beats me what happened to the serial port.";
|
||||
killConnection = true;
|
||||
piStop();
|
||||
break;
|
||||
case QSerialPort::TimeoutError:
|
||||
errMessage = "Timeout error on serial port";
|
||||
killConnection = true;
|
||||
break;
|
||||
case QSerialPort::NotOpenError:
|
||||
errMessage = "The serial port isn't open";
|
||||
killConnection = true;
|
||||
piStop();
|
||||
break;
|
||||
}
|
||||
/*
|
||||
if (serial)
|
||||
{
|
||||
serial->clearError();
|
||||
serial->flush();
|
||||
serial->close();
|
||||
}*/
|
||||
if (errMessage.length() > 1)
|
||||
{
|
||||
sendDebug(errMessage);
|
||||
}
|
||||
if (killConnection)
|
||||
{
|
||||
qDebug() << "Shooting the serial object in the head. It deserves it.";
|
||||
disconnectDevice();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LAWICELSerial::connectionTimeout()
|
||||
{
|
||||
//one second after trying to connect are we actually connected?
|
||||
if (CANCon::NOT_CONNECTED==getStatus()) //no?
|
||||
{
|
||||
//then emit the the failure signal and see if anyone cares
|
||||
sendDebug("Failed to connect to LAWICEL at that com port");
|
||||
|
||||
disconnectDevice();
|
||||
connectDevice();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* start timer */
|
||||
connect(&mTimer, SIGNAL(timeout()), this, SLOT(handleTick()));
|
||||
mTimer.setInterval(250); //tick four times per second
|
||||
mTimer.setSingleShot(false); //keep ticking
|
||||
mTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
void LAWICELSerial::readSerialData()
|
||||
{
|
||||
QByteArray data;
|
||||
unsigned char c;
|
||||
QString debugBuild;
|
||||
CANFrame buildFrame;
|
||||
QByteArray buildData;
|
||||
|
||||
if (serial) data = serial->readAll();
|
||||
|
||||
sendDebug("Got data from serial. Len = " % QString::number(data.length()));
|
||||
for (int i = 0; i < data.length(); i++)
|
||||
{
|
||||
c = data.at(i);
|
||||
//qDebug() << c << " " << QString::number(c, 16) << " " << QString(c);
|
||||
debugBuild = debugBuild % QString::number(c, 16).rightJustified(2,'0') % " ";
|
||||
//procRXChar(c);
|
||||
mBuildLine.append(c);
|
||||
if (c == 13) //all lawicel commands end in CR
|
||||
{
|
||||
qDebug() << "Got CR!";
|
||||
|
||||
buildFrame.setTimeStamp(QDateTime::currentMSecsSinceEpoch() * 1000l);
|
||||
switch (mBuildLine[0].toLatin1())
|
||||
{
|
||||
case 't': //standard frame
|
||||
//tIIILDD
|
||||
buildFrame.setFrameId(mBuildLine.mid(1, 3).toInt(nullptr, 16));
|
||||
buildFrame.isReceived = true;
|
||||
buildFrame.setFrameType(QCanBusFrame::FrameType::DataFrame);
|
||||
buildData.resize(mBuildLine.mid(4, 1).toInt());
|
||||
for (int c = 0; c < buildData.size(); c++)
|
||||
{
|
||||
buildData[c] = mBuildLine.mid(5 + (c*2), 2).toInt(nullptr, 16);
|
||||
}
|
||||
buildFrame.setPayload(buildData);
|
||||
if (!isCapSuspended())
|
||||
{
|
||||
/* get frame from queue */
|
||||
CANFrame* frame_p = getQueue().get();
|
||||
if(frame_p) {
|
||||
//qDebug() << "Lawicel got frame on bus " << frame_p->bus;
|
||||
/* copy frame */
|
||||
*frame_p = buildFrame;
|
||||
checkTargettedFrame(buildFrame);
|
||||
/* enqueue frame */
|
||||
getQueue().queue();
|
||||
}
|
||||
else
|
||||
qDebug() << "can't get a frame, ERROR";
|
||||
}
|
||||
break;
|
||||
case 'T': //extended frame
|
||||
//TIIIIIIIILDD.
|
||||
buildFrame.setFrameId(mBuildLine.mid(1, 8).toInt(nullptr, 16));
|
||||
buildFrame.isReceived = true;
|
||||
buildFrame.setFrameType(QCanBusFrame::FrameType::DataFrame);
|
||||
buildFrame.setExtendedFrameFormat(true);
|
||||
buildData.resize(mBuildLine.mid(9, 1).toInt());
|
||||
for (int c = 0; c < buildData.size(); c++)
|
||||
{
|
||||
buildData[c] = mBuildLine.mid(10 + (c*2), 2).toInt(nullptr, 16);
|
||||
}
|
||||
buildFrame.setPayload(buildData);
|
||||
if (!isCapSuspended())
|
||||
{
|
||||
/* get frame from queue */
|
||||
CANFrame* frame_p = getQueue().get();
|
||||
if(frame_p) {
|
||||
//qDebug() << "Lawicel got frame on bus " << frame_p->bus;
|
||||
/* copy frame */
|
||||
*frame_p = buildFrame;
|
||||
checkTargettedFrame(buildFrame);
|
||||
/* enqueue frame */
|
||||
getQueue().queue();
|
||||
}
|
||||
else
|
||||
qDebug() << "can't get a frame, ERROR";
|
||||
}
|
||||
break;
|
||||
case 'b':
|
||||
buildFrame.setBitrateSwitch(true); //BRS enabled
|
||||
[[fallthrough]];
|
||||
case 'd': //standard fd frame, BRS disabled
|
||||
//tIIILDD
|
||||
buildFrame.setFlexibleDataRateFormat(true);
|
||||
buildFrame.setFrameId(mBuildLine.mid(1, 3).toInt(nullptr, 16));
|
||||
buildFrame.isReceived = true;
|
||||
buildFrame.setFrameType(QCanBusFrame::FrameType::DataFrame);
|
||||
buildData.resize(LAWICELSerial::dlc_code_to_bytes(mBuildLine.mid(4, 1).toInt(nullptr, 16)));
|
||||
for (int c = 0; c < buildData.size(); c++)
|
||||
{
|
||||
buildData[c] = mBuildLine.mid(5 + (c*2), 2).toInt(nullptr, 16);
|
||||
}
|
||||
buildFrame.setPayload(buildData);
|
||||
if (!isCapSuspended())
|
||||
{
|
||||
/* get frame from queue */
|
||||
CANFrame* frame_p = getQueue().get();
|
||||
if(frame_p) {
|
||||
//qDebug() << "Lawicel got frame on bus " << frame_p->bus;
|
||||
/* copy frame */
|
||||
*frame_p = buildFrame;
|
||||
checkTargettedFrame(buildFrame);
|
||||
/* enqueue frame */
|
||||
getQueue().queue();
|
||||
}
|
||||
else
|
||||
qDebug() << "can't get a frame, ERROR";
|
||||
}
|
||||
break;
|
||||
case 'B':
|
||||
buildFrame.setBitrateSwitch(true); //BRS enabled
|
||||
[[fallthrough]];
|
||||
case 'D': //extended fd frame
|
||||
//TIIIIIIIILDD.
|
||||
buildFrame.setFlexibleDataRateFormat(true);
|
||||
buildFrame.setBitrateSwitch(true);
|
||||
buildFrame.setFrameId(mBuildLine.mid(1, 8).toInt(nullptr, 16));
|
||||
buildFrame.isReceived = true;
|
||||
buildFrame.setFrameType(QCanBusFrame::FrameType::DataFrame);
|
||||
buildFrame.setExtendedFrameFormat(true);
|
||||
buildData.resize(LAWICELSerial::dlc_code_to_bytes(mBuildLine.mid(4, 1).toInt(nullptr, 16)));
|
||||
for (int c = 0; c < buildData.size(); c++)
|
||||
{
|
||||
buildData[c] = mBuildLine.mid(10 + (c*2), 2).toInt(nullptr, 16);
|
||||
}
|
||||
buildFrame.setPayload(buildData);
|
||||
if (!isCapSuspended())
|
||||
{
|
||||
/* get frame from queue */
|
||||
CANFrame* frame_p = getQueue().get();
|
||||
if(frame_p) {
|
||||
//qDebug() << "Lawicel got frame on bus " << frame_p->bus;
|
||||
/* copy frame */
|
||||
*frame_p = buildFrame;
|
||||
checkTargettedFrame(buildFrame);
|
||||
/* enqueue frame */
|
||||
getQueue().queue();
|
||||
}
|
||||
else
|
||||
qDebug() << "can't get a frame, ERROR";
|
||||
}
|
||||
break;
|
||||
}
|
||||
mBuildLine.clear();
|
||||
}
|
||||
}
|
||||
debugOutput(debugBuild);
|
||||
//qDebug() << debugBuild;
|
||||
}
|
||||
|
||||
//Debugging data sent from connection window. Inject it into Comm traffic.
|
||||
void LAWICELSerial::debugInput(QByteArray bytes) {
|
||||
sendToSerial(bytes);
|
||||
}
|
||||
|
||||
void LAWICELSerial::handleTick()
|
||||
{
|
||||
//qDebug() << "Tick!";
|
||||
}
|
||||
|
||||
|
||||
// Convert a FDCAN_data_length_code to number of bytes in a message
|
||||
uint8_t LAWICELSerial::dlc_code_to_bytes(int dlc_code)
|
||||
{
|
||||
if (dlc_code<=8)
|
||||
return dlc_code;
|
||||
else{
|
||||
switch(dlc_code)
|
||||
{
|
||||
case 9:
|
||||
return 12;
|
||||
case 10:
|
||||
return 16;
|
||||
case 11:
|
||||
return 20;
|
||||
case 12:
|
||||
return 24;
|
||||
case 13:
|
||||
return 32;
|
||||
case 14:
|
||||
return 48;
|
||||
case 15:
|
||||
return 64;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t LAWICELSerial::bytes_to_dlc_code(uint8_t bytes)
|
||||
{
|
||||
if (bytes<=8)
|
||||
return bytes;
|
||||
else{
|
||||
switch(bytes)
|
||||
{
|
||||
case 12:
|
||||
return 9;
|
||||
case 16:
|
||||
return 10;
|
||||
case 20:
|
||||
return 11;
|
||||
case 24:
|
||||
return 12;
|
||||
case 32:
|
||||
return 13;
|
||||
case 48:
|
||||
return 14;
|
||||
case 64:
|
||||
return 15;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef LAWICELSERIAL_H
|
||||
#define LAWICELSERIAL_H
|
||||
|
||||
#include <QSerialPort>
|
||||
#include <QCanBusDevice>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
|
||||
/*************/
|
||||
#include <QDateTime>
|
||||
/*************/
|
||||
|
||||
#include "canframemodel.h"
|
||||
#include "canconnection.h"
|
||||
#include "canconmanager.h"
|
||||
|
||||
class LAWICELSerial : public CANConnection
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LAWICELSerial(QString portName, int serialSpeed, int lawicelSpeed, bool canFd, int dataRate);
|
||||
virtual ~LAWICELSerial();
|
||||
|
||||
protected:
|
||||
|
||||
virtual void piStarted();
|
||||
virtual void piStop();
|
||||
virtual void piSetBusSettings(int pBusIdx, CANBus pBus);
|
||||
virtual bool piGetBusSettings(int pBusIdx, CANBus& pBus);
|
||||
virtual void piSuspend(bool pSuspend);
|
||||
virtual bool piSendFrame(const CANFrame&) ;
|
||||
|
||||
void disconnectDevice();
|
||||
|
||||
public slots:
|
||||
void debugInput(QByteArray bytes);
|
||||
|
||||
private slots:
|
||||
void connectDevice();
|
||||
void connectionTimeout();
|
||||
void readSerialData();
|
||||
void serialError(QSerialPort::SerialPortError err);
|
||||
void deviceConnected();
|
||||
void handleTick();
|
||||
|
||||
private:
|
||||
void readSettings();
|
||||
void rebuildLocalTimeBasis();
|
||||
void sendToSerial(const QByteArray &bytes);
|
||||
void sendDebug(const QString debugText);
|
||||
uint8_t dlc_code_to_bytes(int dlc_code);
|
||||
uint8_t bytes_to_dlc_code(uint8_t bytes);
|
||||
|
||||
protected:
|
||||
QTimer mTimer;
|
||||
QThread mThread;
|
||||
QString mBuildLine;
|
||||
|
||||
bool isAutoRestart;
|
||||
QSerialPort *serial;
|
||||
int framesRapid;
|
||||
CANFrame buildFrame;
|
||||
bool can0Enabled;
|
||||
bool can0ListenOnly;
|
||||
bool canFd;
|
||||
int dataRate;
|
||||
};
|
||||
|
||||
#endif // LAWICELSERIAL_H
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "mqtt_bus.h"
|
||||
|
||||
MQTT_BUS::MQTT_BUS(QString topicName) :
|
||||
CANConnection(topicName, "mqtt_client", CANCon::MQTT, 1, 4000, true),
|
||||
CANConnection(topicName, "mqtt_client", CANCon::MQTT, 0, 0, false, 0, 1, 4000, true),
|
||||
mTimer(this) /*NB: set this as parent of timer to manage it from working thread */
|
||||
{
|
||||
|
||||
@@ -228,8 +228,8 @@ void MQTT_BUS::piSetBusSettings(int pBusIdx, CANBus bus)
|
||||
bool MQTT_BUS::piSendFrame(const CANFrame& frame)
|
||||
{
|
||||
QByteArray buffer;
|
||||
int c;
|
||||
quint32 ID;
|
||||
//int c;
|
||||
//quint32 ID;
|
||||
|
||||
//qDebug() << "Sending out GVRET frame with id " << frame.ID << " on bus " << frame.bus;
|
||||
|
||||
@@ -278,7 +278,7 @@ void MQTT_BUS::readSettings()
|
||||
|
||||
void MQTT_BUS::clientMessageReceived(const QMQTT::Message& message)
|
||||
{
|
||||
uint64_t timeBasis = CANConManager::getInstance()->getTimeBasis();
|
||||
//uint64_t timeBasis = CANConManager::getInstance()->getTimeBasis();
|
||||
|
||||
|
||||
/* drop frame if capture is suspended */
|
||||
@@ -289,9 +289,10 @@ void MQTT_BUS::clientMessageReceived(const QMQTT::Message& message)
|
||||
if(frame_p)
|
||||
{
|
||||
uint32_t frameID = message.topic().split("/")[1].toInt();
|
||||
uint64_t timeStamp = message.payload()[0] + (message.payload()[1] << 8) + (message.payload()[2] << 16) + (message.payload()[3] << 24)
|
||||
+ ((uint64_t)message.payload()[4] << 32ull) + ((uint64_t)message.payload()[5] << 40ull)
|
||||
+ ((uint64_t)message.payload()[6] << 48ull) + ((uint64_t)message.payload()[7] << 56ull);
|
||||
|
||||
QByteArray timeStampBytes = message.payload().left(8);
|
||||
uint64_t timeStamp = qFromLittleEndian<uint64_t>(timeStampBytes.data());
|
||||
|
||||
int flags = message.payload()[8];
|
||||
frame_p->setPayload(message.payload().right(message.payload().count() - 9));
|
||||
frame_p->bus = 0;
|
||||
|
||||
@@ -27,12 +27,23 @@ NewConnectionDialog::NewConnectionDialog(QVector<QString>* gvretips, QVector<QSt
|
||||
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<int>::of(&QComboBox::currentIndexChanged), this, &NewConnectionDialog::handleDeviceTypeChanged);
|
||||
connect(ui->btnOK, &QPushButton::clicked, this, &NewConnectionDialog::handleCreateButton);
|
||||
|
||||
ui->lblDeviceType->setHidden(true);
|
||||
ui->cbDeviceType->setHidden(true);
|
||||
ui->cbCANSpeed->setHidden(true);
|
||||
ui->cbSerialSpeed->setHidden(true);
|
||||
ui->lblCANSpeed->setHidden(true);
|
||||
ui->lblSerialSpeed->setHidden(true);
|
||||
ui->cbCanFd->setHidden(true);
|
||||
ui->cbDataRate->setHidden(true);
|
||||
ui->lblDataRate->setHidden(true);
|
||||
|
||||
selectSerial();
|
||||
|
||||
qDebug() << "Was passed " << remoteDeviceIPGVRET->count() << " remote GVRET IPs";
|
||||
@@ -53,9 +64,12 @@ void NewConnectionDialog::handleConnTypeChanged()
|
||||
{
|
||||
if (ui->rbGVRET->isChecked()) selectSerial();
|
||||
if (ui->rbSocketCAN->isChecked()) selectSocketCan();
|
||||
if (ui->rbLawicel->isChecked()) selectLawicel();
|
||||
if (ui->rbRemote->isChecked()) selectRemote();
|
||||
if (ui->rbKayak->isChecked()) selectKayak();
|
||||
if (ui->rbMQTT->isChecked()) selectMQTT();
|
||||
if (ui->rbCANserver->isChecked()) selectCANserver();
|
||||
if (ui->rbCanlogserver->isChecked()) selectCANlogserver();
|
||||
}
|
||||
|
||||
void NewConnectionDialog::handleDeviceTypeChanged()
|
||||
@@ -68,12 +82,72 @@ void NewConnectionDialog::handleDeviceTypeChanged()
|
||||
ui->cbPort->addItem(canDevices[i].name());
|
||||
}
|
||||
|
||||
void NewConnectionDialog::selectLawicel()
|
||||
{
|
||||
ui->lPort->setText("Serial Port:");
|
||||
|
||||
ui->lblDeviceType->setHidden(true);
|
||||
ui->cbDeviceType->setHidden(true);
|
||||
|
||||
ui->cbCANSpeed->setHidden(false);
|
||||
ui->cbSerialSpeed->setHidden(false);
|
||||
ui->lblCANSpeed->setHidden(false);
|
||||
ui->lblSerialSpeed->setHidden(false);
|
||||
ui->cbCanFd->setHidden(false);
|
||||
ui->cbDataRate->setHidden(false);
|
||||
ui->lblDataRate->setHidden(false);
|
||||
|
||||
ui->cbPort->clear();
|
||||
ports = QSerialPortInfo::availablePorts();
|
||||
|
||||
for (int i = 0; i < ports.count(); i++)
|
||||
ui->cbPort->addItem(ports[i].portName());
|
||||
|
||||
if (ui->cbCANSpeed->count() == 0)
|
||||
{
|
||||
ui->cbCANSpeed->addItem("10000");
|
||||
ui->cbCANSpeed->addItem("20000");
|
||||
ui->cbCANSpeed->addItem("50000");
|
||||
ui->cbCANSpeed->addItem("83333");
|
||||
ui->cbCANSpeed->addItem("100000");
|
||||
ui->cbCANSpeed->addItem("125000");
|
||||
ui->cbCANSpeed->addItem("250000");
|
||||
ui->cbCANSpeed->addItem("500000");
|
||||
ui->cbCANSpeed->addItem("1000000");
|
||||
}
|
||||
if (ui->cbDataRate->count() == 0)
|
||||
{
|
||||
ui->cbDataRate->addItem("1000000");
|
||||
ui->cbDataRate->addItem("2000000");
|
||||
ui->cbDataRate->addItem("4000000");
|
||||
ui->cbDataRate->addItem("5000000");
|
||||
}
|
||||
if (ui->cbSerialSpeed->count() == 0)
|
||||
{
|
||||
ui->cbSerialSpeed->addItem("115200");
|
||||
ui->cbSerialSpeed->addItem("150000");
|
||||
ui->cbSerialSpeed->addItem("250000");
|
||||
ui->cbSerialSpeed->addItem("500000");
|
||||
ui->cbSerialSpeed->addItem("1000000");
|
||||
ui->cbSerialSpeed->addItem("2000000");
|
||||
ui->cbSerialSpeed->addItem("3000000");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void NewConnectionDialog::selectSerial()
|
||||
{
|
||||
ui->lPort->setText("Serial Port:");
|
||||
|
||||
ui->lblDeviceType->setHidden(true);
|
||||
ui->cbDeviceType->setHidden(true);
|
||||
ui->cbCANSpeed->setHidden(true);
|
||||
ui->cbSerialSpeed->setHidden(true);
|
||||
ui->lblCANSpeed->setHidden(true);
|
||||
ui->lblSerialSpeed->setHidden(true);
|
||||
ui->cbCanFd->setHidden(true);
|
||||
ui->cbDataRate->setHidden(true);
|
||||
ui->lblDataRate->setHidden(true);
|
||||
|
||||
ui->cbPort->clear();
|
||||
ports = QSerialPortInfo::availablePorts();
|
||||
@@ -87,6 +161,13 @@ void NewConnectionDialog::selectSocketCan()
|
||||
ui->lPort->setText("Port:");
|
||||
ui->lblDeviceType->setHidden(false);
|
||||
ui->cbDeviceType->setHidden(false);
|
||||
ui->cbCANSpeed->setHidden(true);
|
||||
ui->cbSerialSpeed->setHidden(true);
|
||||
ui->lblCANSpeed->setHidden(true);
|
||||
ui->lblSerialSpeed->setHidden(true);
|
||||
ui->cbCanFd->setHidden(true);
|
||||
ui->cbDataRate->setHidden(true);
|
||||
ui->lblDataRate->setHidden(true);
|
||||
|
||||
ui->cbDeviceType->clear();
|
||||
QStringList plugins;
|
||||
@@ -99,8 +180,17 @@ void NewConnectionDialog::selectSocketCan()
|
||||
void NewConnectionDialog::selectRemote()
|
||||
{
|
||||
ui->lPort->setText("IP Address:");
|
||||
|
||||
ui->lblDeviceType->setHidden(true);
|
||||
ui->cbDeviceType->setHidden(true);
|
||||
ui->cbCANSpeed->setHidden(true);
|
||||
ui->cbSerialSpeed->setHidden(true);
|
||||
ui->lblCANSpeed->setHidden(true);
|
||||
ui->lblSerialSpeed->setHidden(true);
|
||||
ui->cbCanFd->setHidden(true);
|
||||
ui->cbDataRate->setHidden(true);
|
||||
ui->lblDataRate->setHidden(true);
|
||||
|
||||
ui->cbPort->clear();
|
||||
foreach(QString pName, *remoteDeviceIPGVRET)
|
||||
{
|
||||
@@ -111,8 +201,17 @@ void NewConnectionDialog::selectRemote()
|
||||
void NewConnectionDialog::selectKayak()
|
||||
{
|
||||
ui->lPort->setText("Available Bus(ses):");
|
||||
|
||||
ui->lblDeviceType->setHidden(true);
|
||||
ui->cbDeviceType->setHidden(true);
|
||||
ui->cbCANSpeed->setHidden(true);
|
||||
ui->cbSerialSpeed->setHidden(true);
|
||||
ui->lblCANSpeed->setHidden(true);
|
||||
ui->lblSerialSpeed->setHidden(true);
|
||||
ui->cbCanFd->setHidden(true);
|
||||
ui->cbDataRate->setHidden(true);
|
||||
ui->lblDataRate->setHidden(true);
|
||||
|
||||
ui->cbPort->clear();
|
||||
foreach(QString pName, *remoteBusKayak)
|
||||
{
|
||||
@@ -123,8 +222,51 @@ void NewConnectionDialog::selectKayak()
|
||||
void NewConnectionDialog::selectMQTT()
|
||||
{
|
||||
ui->lPort->setText("Topic Name:");
|
||||
|
||||
ui->lblDeviceType->setHidden(true);
|
||||
ui->cbDeviceType->setHidden(true);
|
||||
ui->cbCANSpeed->setHidden(true);
|
||||
ui->cbSerialSpeed->setHidden(true);
|
||||
ui->lblCANSpeed->setHidden(true);
|
||||
ui->lblSerialSpeed->setHidden(true);
|
||||
ui->cbCanFd->setHidden(true);
|
||||
ui->cbDataRate->setHidden(true);
|
||||
ui->lblDataRate->setHidden(true);
|
||||
|
||||
ui->cbPort->clear();
|
||||
}
|
||||
|
||||
void NewConnectionDialog::selectCANserver()
|
||||
{
|
||||
ui->lPort->setText("CANserver IP Address:");
|
||||
|
||||
ui->lblDeviceType->setHidden(true);
|
||||
ui->cbDeviceType->setHidden(true);
|
||||
ui->cbCANSpeed->setHidden(true);
|
||||
ui->cbSerialSpeed->setHidden(true);
|
||||
ui->lblCANSpeed->setHidden(true);
|
||||
ui->lblSerialSpeed->setHidden(true);
|
||||
ui->cbCanFd->setHidden(true);
|
||||
ui->cbDataRate->setHidden(true);
|
||||
ui->lblDataRate->setHidden(true);
|
||||
|
||||
ui->cbPort->clear();
|
||||
}
|
||||
|
||||
void NewConnectionDialog::selectCANlogserver()
|
||||
{
|
||||
ui->lPort->setText("CANlogserver IP Address:");
|
||||
|
||||
ui->lblDeviceType->setHidden(true);
|
||||
ui->cbDeviceType->setHidden(true);
|
||||
ui->cbCANSpeed->setHidden(true);
|
||||
ui->cbSerialSpeed->setHidden(true);
|
||||
ui->lblCANSpeed->setHidden(true);
|
||||
ui->lblSerialSpeed->setHidden(true);
|
||||
ui->cbCanFd->setHidden(true);
|
||||
ui->cbDataRate->setHidden(true);
|
||||
ui->lblDataRate->setHidden(true);
|
||||
|
||||
ui->cbPort->clear();
|
||||
}
|
||||
|
||||
@@ -148,6 +290,15 @@ void NewConnectionDialog::setPortName(CANCon::type pType, QString pPortName, QSt
|
||||
case CANCon::MQTT:
|
||||
ui->rbMQTT->setChecked(true);
|
||||
break;
|
||||
case CANCon::LAWICEL:
|
||||
ui->rbLawicel->setChecked(true);
|
||||
break;
|
||||
case CANCon::CANSERVER:
|
||||
ui->rbCANserver->setChecked(true);
|
||||
break;
|
||||
case CANCon::CANLOGSERVER:
|
||||
ui->rbCanlogserver->setChecked(true);
|
||||
break;
|
||||
default: {}
|
||||
}
|
||||
|
||||
@@ -157,6 +308,7 @@ void NewConnectionDialog::setPortName(CANCon::type pType, QString pPortName, QSt
|
||||
switch(pType)
|
||||
{
|
||||
case CANCon::GVRET_SERIAL:
|
||||
case CANCon::LAWICEL:
|
||||
{
|
||||
int idx = ui->cbPort->findText(pPortName);
|
||||
if( idx<0 ) idx=0;
|
||||
@@ -190,6 +342,12 @@ void NewConnectionDialog::setPortName(CANCon::type pType, QString pPortName, QSt
|
||||
case CANCon::MQTT:
|
||||
ui->cbPort->setCurrentText(pPortName);
|
||||
break;
|
||||
case CANCon::CANSERVER:
|
||||
case CANCon::CANLOGSERVER:
|
||||
{
|
||||
ui->cbPort->setCurrentText(pPortName);
|
||||
break;
|
||||
}
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
@@ -201,9 +359,14 @@ QString NewConnectionDialog::getPortName()
|
||||
case CANCon::SERIALBUS:
|
||||
case CANCon::REMOTE:
|
||||
case CANCon::MQTT:
|
||||
case CANCon::LAWICEL:
|
||||
return ui->cbPort->currentText();
|
||||
case CANCon::KAYAK:
|
||||
return ui->cbPort->currentText();
|
||||
case CANCon::CANSERVER:
|
||||
case CANCon::CANLOGSERVER:
|
||||
return ui->cbPort->currentText();
|
||||
|
||||
default:
|
||||
qDebug() << "getPortName: can't get port";
|
||||
}
|
||||
@@ -220,6 +383,24 @@ QString NewConnectionDialog::getDriverName()
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
int NewConnectionDialog::getSerialSpeed()
|
||||
{
|
||||
if (getConnectionType() == CANCon::LAWICEL)
|
||||
{
|
||||
return ui->cbSerialSpeed->currentText().toInt();
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
int NewConnectionDialog::getBusSpeed()
|
||||
{
|
||||
if (getConnectionType() == CANCon::LAWICEL)
|
||||
{
|
||||
return ui->cbCANSpeed->currentText().toInt();
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
CANCon::type NewConnectionDialog::getConnectionType()
|
||||
{
|
||||
if (ui->rbGVRET->isChecked()) return CANCon::GVRET_SERIAL;
|
||||
@@ -227,6 +408,9 @@ CANCon::type NewConnectionDialog::getConnectionType()
|
||||
if (ui->rbRemote->isChecked()) return CANCon::REMOTE;
|
||||
if (ui->rbKayak->isChecked()) return CANCon::KAYAK;
|
||||
if (ui->rbMQTT->isChecked()) return CANCon::MQTT;
|
||||
if (ui->rbLawicel->isChecked()) return CANCon::LAWICEL;
|
||||
if (ui->rbCANserver->isChecked()) return CANCon::CANSERVER;
|
||||
if (ui->rbCanlogserver->isChecked()) return CANCon::CANLOGSERVER;
|
||||
qDebug() << "getConnectionType: error";
|
||||
|
||||
return CANCon::NONE;
|
||||
@@ -237,3 +421,21 @@ bool NewConnectionDialog::isSerialBusAvailable()
|
||||
if (QCanBus::instance()->plugins().count() > 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int NewConnectionDialog::getDataRate()
|
||||
{
|
||||
if (getConnectionType() == CANCon::LAWICEL)
|
||||
{
|
||||
return ui->cbDataRate->currentText().toInt();
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
bool NewConnectionDialog::isCanFd()
|
||||
{
|
||||
if (getConnectionType() == CANCon::LAWICEL)
|
||||
{
|
||||
return ui->cbCanFd;
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ public:
|
||||
CANCon::type getConnectionType();
|
||||
QString getPortName();
|
||||
QString getDriverName();
|
||||
int getSerialSpeed();
|
||||
int getBusSpeed();
|
||||
bool isCanFd();
|
||||
int getDataRate();
|
||||
|
||||
public slots:
|
||||
void handleConnTypeChanged();
|
||||
@@ -43,6 +47,9 @@ private:
|
||||
void selectRemote();
|
||||
void selectKayak();
|
||||
void selectMQTT();
|
||||
void selectLawicel();
|
||||
void selectCANserver();
|
||||
void selectCANlogserver();
|
||||
bool isSerialBusAvailable();
|
||||
void setPortName(CANCon::type pType, QString pPortName, QString pDriver);
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/***********************************/
|
||||
|
||||
SerialBusConnection::SerialBusConnection(QString portName, QString driverName) :
|
||||
CANConnection(portName, driverName, CANCon::SERIALBUS, 1, 4000, true),
|
||||
CANConnection(portName, driverName, CANCon::SERIALBUS,0 ,0, false, 0 ,1, 4000, true),
|
||||
mTimer(this) /*NB: set connection as parent of timer to manage it from working thread */
|
||||
{
|
||||
}
|
||||
@@ -47,6 +47,7 @@ void SerialBusConnection::piStarted()
|
||||
mTimer.setSingleShot(false); //keep ticking
|
||||
mTimer.start();
|
||||
mBusData[0].mBus.setActive(true);
|
||||
mBusData[0].mBus.setCanFD(false);
|
||||
mBusData[0].mConfigured = true;
|
||||
}
|
||||
|
||||
@@ -77,6 +78,8 @@ bool SerialBusConnection::piGetBusSettings(int pBusIdx, CANBus& pBus)
|
||||
|
||||
void SerialBusConnection::piSetBusSettings(int pBusIdx, CANBus bus)
|
||||
{
|
||||
quint32 sbusconfig = 0;
|
||||
|
||||
//CANConStatus stats;
|
||||
/* sanity checks */
|
||||
if(0 != pBusIdx)
|
||||
@@ -91,7 +94,7 @@ void SerialBusConnection::piSetBusSettings(int pBusIdx, CANBus bus)
|
||||
setBusConfig(0, bus);
|
||||
|
||||
/* if bus is not active we are done */
|
||||
if(!bus.active)
|
||||
if(!bus.isActive())
|
||||
return;
|
||||
|
||||
/* set configuration */
|
||||
@@ -102,7 +105,12 @@ void SerialBusConnection::piSetBusSettings(int pBusIdx, CANBus bus)
|
||||
|
||||
//You cannot set the speed of a socketcan interface, it has to be set with console commands.
|
||||
//But, you can probabaly set the speed of many of the other serialbus devices so go ahead and try
|
||||
mDev_p->setConfigurationParameter(QCanBusDevice::BitRateKey, bus.speed);
|
||||
mDev_p->setConfigurationParameter(QCanBusDevice::BitRateKey, bus.getSpeed());
|
||||
mDev_p->setConfigurationParameter(QCanBusDevice::CanFdKey, bus.isCanFD());
|
||||
|
||||
if(bus.isListenOnly())
|
||||
sbusconfig |= EN_SILENT_MODE;
|
||||
mDev_p->setConfigurationParameter(QCanBusDevice::UserKey, sbusconfig);
|
||||
|
||||
/* connect device */
|
||||
if (!mDev_p->connectDevice()) {
|
||||
@@ -180,6 +188,7 @@ void SerialBusConnection::framesReceived()
|
||||
|
||||
/* check frame */
|
||||
//if (recFrame.payload().length() <= 8) {
|
||||
if (true) {
|
||||
CANFrame* frame_p = getQueue().get();
|
||||
if(frame_p) {
|
||||
frame_p->setPayload(recFrame.payload());
|
||||
@@ -198,7 +207,7 @@ void SerialBusConnection::framesReceived()
|
||||
frame_p->setTimeStamp(recFrame.timeStamp());
|
||||
frame_p->setFrameType(recFrame.frameType());
|
||||
frame_p->setError(recFrame.error());
|
||||
/* If recorded frame has a local echo, it is a Tx message, and thus should not be marked as Rx */
|
||||
/* If recorded frame has a local echo, it is a Tx message, and thus should not be marked as Rx */
|
||||
frame_p->isReceived = !recFrame.hasLocalEcho();
|
||||
|
||||
if (useSystemTime) {
|
||||
@@ -210,11 +219,9 @@ void SerialBusConnection::framesReceived()
|
||||
|
||||
/* enqueue frame */
|
||||
getQueue().queue();
|
||||
//}
|
||||
#if 0
|
||||
}
|
||||
else
|
||||
qDebug() << "can't get a frame, ERROR";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,21 @@
|
||||
#include <QCanBusDevice>
|
||||
#include <QTimer>
|
||||
|
||||
/*
|
||||
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
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "socketcand.h"
|
||||
|
||||
SocketCANd::SocketCANd(QString portName) :
|
||||
CANConnection(portName, "kayak", CANCon::KAYAK, 1, 4000, true),
|
||||
CANConnection(portName, "kayak", CANCon::KAYAK, 0, 0, false, 0, 1, 4000, true),
|
||||
mTimer(this) /*NB: set this as parent of timer to manage it from working thread */
|
||||
{
|
||||
|
||||
@@ -32,6 +32,7 @@ SocketCANd::SocketCANd(QString portName) :
|
||||
for (int i = 0; i < mNumBuses; i++)
|
||||
{
|
||||
rx_state.append(IDLE);
|
||||
unprocessedData.append("");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -63,7 +64,7 @@ void SocketCANd::sendBytesToTCP(const QByteArray &bytes, int busNum)
|
||||
byt = (unsigned char)byt;
|
||||
buildDebug = buildDebug % QString::number(byt, 16) % " ";
|
||||
}
|
||||
sendDebug(buildDebug);
|
||||
//sendDebug(buildDebug);
|
||||
|
||||
if (tcpClient[busNum]) tcpClient[busNum]->write(bytes);
|
||||
}
|
||||
@@ -76,9 +77,9 @@ void SocketCANd::sendStringToTCP(const char* data, int busNum)
|
||||
return;
|
||||
}
|
||||
|
||||
QString buildDebug;
|
||||
buildDebug = "Send data to " + hostIP.toString() + ":" + QString::number(hostPort) + " -> " + data;
|
||||
sendDebug(buildDebug);
|
||||
//QString buildDebug;
|
||||
//buildDebug = "Send data to " + hostIP.toString() + ":" + QString::number(hostPort) + " -> " + data;
|
||||
//sendDebug(buildDebug);
|
||||
//qInfo() << buildDebug;
|
||||
|
||||
if (tcpClient[busNum]) tcpClient[busNum]->write(data);
|
||||
@@ -218,8 +219,8 @@ void SocketCANd::connectDevice()
|
||||
void SocketCANd::deviceConnected(int busNum)
|
||||
{
|
||||
sendDebug("Opening CAN on Kayak Device!");
|
||||
const char* openCanCmd = ("< open " + hostCanIDs[busNum] + " >").toStdString().c_str();
|
||||
sendStringToTCP(openCanCmd, busNum);
|
||||
QString openCanCmd("< open " % hostCanIDs[busNum] % " >");
|
||||
sendStringToTCP(openCanCmd.toUtf8().data(), busNum);
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
@@ -249,62 +250,89 @@ void SocketCANd::switchToRawMode(int busNum)
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
void SocketCANd::decodeFrames(QString data, int busNum)
|
||||
QString SocketCANd::decodeFrames(QString data, int busNum)
|
||||
{
|
||||
if (data.indexOf("< frame ") == -1)
|
||||
if (data.indexOf("<") == -1)
|
||||
return "";
|
||||
else if(data.length() >= 8 && data.indexOf("< frame ") == -1)
|
||||
return "";
|
||||
|
||||
int firstIndex = data.indexOf("< frame ");
|
||||
if(firstIndex > 0)
|
||||
{
|
||||
qDebug() << "Received datagramm doesn't contain any frame: " << data;
|
||||
return;
|
||||
QString framePartial = data.left(firstIndex);
|
||||
qDebug() << "Received datagramm that starts with fragment (missing '< frame'), this should only occur on startup, removing...: " << framePartial;
|
||||
}
|
||||
else
|
||||
QString framePart = data.mid(firstIndex); //remove starting beginning of payload if not < frame >
|
||||
const QString frameStrConst = framePart.left(framePart.indexOf(">")+1);
|
||||
QString frameStr = frameStrConst;
|
||||
QStringList frameParsed = (frameStr.remove(QRegularExpression("^<")).remove(QRegularExpression(">$"))).simplified().split(' ');
|
||||
|
||||
if(frameParsed.length() < 3)
|
||||
{
|
||||
QString framePart = data.mid(data.indexOf("< frame "), data.length()); //remove starting beginning of payload if not < frame >
|
||||
const QString frameStrConst = framePart.left(framePart.indexOf(">")+1);
|
||||
QString frameStr = frameStrConst;
|
||||
QStringList frameParsed = (frameStr.remove(QRegExp("^<")).remove(QRegExp(">$"))).simplified().split(' ');
|
||||
//qDebug() << "Received datagramm is an incomplete frame: " << data;
|
||||
|
||||
buildFrame.setFrameId(frameParsed[1].toUInt(nullptr, 16));
|
||||
buildFrame.bus = busNum;
|
||||
//ok great, need to leave it in the buffer in case it can be combined with what comes next
|
||||
//but if there was a fragment that did not have a starting token then we don't want it so only return
|
||||
//known good data...again this should only happen on startup, but just in case we need to remove it
|
||||
//so the data buffer doesn't grow uncontrolled.
|
||||
return framePart;
|
||||
}
|
||||
|
||||
if (buildFrame.frameId() > 0x7FF) buildFrame.setExtendedFrameFormat(true);
|
||||
else buildFrame.setExtendedFrameFormat(false);
|
||||
buildFrame.setFrameId(frameParsed[1].toUInt(nullptr, 16));
|
||||
buildFrame.bus = busNum;
|
||||
|
||||
buildFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, frameParsed[2].toDouble() * 1000000l));
|
||||
//buildFrame.len = frameParsed[3].length() * 0.5;
|
||||
int framelength = frameParsed[3].length() * 0.5;
|
||||
if (buildFrame.frameId() > 0x7FF) buildFrame.setExtendedFrameFormat(true);
|
||||
else buildFrame.setExtendedFrameFormat(false);
|
||||
|
||||
buildData.resize(framelength);
|
||||
buildFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, frameParsed[2].toDouble() * 1000000l));
|
||||
//buildFrame.len = frameParsed[3].length() * 0.5;
|
||||
|
||||
int c;
|
||||
for (c = 0; c < framelength; c++)
|
||||
{
|
||||
bool ok;
|
||||
unsigned char byteVal = frameParsed[3].mid(c*2, 2).toUInt(&ok, 16);
|
||||
buildData[c] = byteVal;
|
||||
}
|
||||
buildFrame.setPayload(buildData);
|
||||
int framelength = 0;
|
||||
|
||||
if(frameParsed.length() == 4)
|
||||
{
|
||||
framelength = frameParsed[3].length() * 0.5;
|
||||
}
|
||||
|
||||
QByteArray buildData;
|
||||
buildData.resize(framelength);
|
||||
|
||||
int c;
|
||||
for (c = 0; c < framelength; c++)
|
||||
{
|
||||
bool ok;
|
||||
unsigned char byteVal = frameParsed[3].mid(c*2, 2).toUInt(&ok, 16);
|
||||
buildData[c] = byteVal;
|
||||
}
|
||||
buildFrame.setPayload(buildData);
|
||||
// buildFrame.isReceived = true;
|
||||
|
||||
if (!isCapSuspended())
|
||||
{
|
||||
/* get frame from queue */
|
||||
CANFrame* frame_p = getQueue().get();
|
||||
if(frame_p) {
|
||||
/* copy frame */
|
||||
*frame_p = buildFrame;
|
||||
//frame_p->remote = false;
|
||||
frame_p->setFrameType(QCanBusFrame::DataFrame);
|
||||
checkTargettedFrame(buildFrame);
|
||||
/* enqueue frame */
|
||||
getQueue().queue();
|
||||
}
|
||||
if (!isCapSuspended())
|
||||
{
|
||||
/* get frame from queue */
|
||||
CANFrame* frame_p = getQueue().get();
|
||||
if(frame_p) {
|
||||
/* copy frame */
|
||||
*frame_p = buildFrame;
|
||||
//frame_p->remote = false;
|
||||
frame_p->setFrameType(QCanBusFrame::DataFrame);
|
||||
checkTargettedFrame(buildFrame);
|
||||
/* enqueue frame */
|
||||
getQueue().queue();
|
||||
}
|
||||
else
|
||||
qDebug() << "can't get a frame, capture suspended";
|
||||
|
||||
if (framePart.length() > frameStrConst.length())
|
||||
decodeFrames(framePart.right(framePart.length() - frameStrConst.length()), busNum);
|
||||
}
|
||||
//else
|
||||
// qDebug() << "can't get a frame, capture suspended";
|
||||
|
||||
//take out the data that we just processed and anything that is in front of it
|
||||
//this should keep broken frames from accumulating at in the data buffer
|
||||
if (framePart.length() > frameStrConst.length())
|
||||
{
|
||||
return decodeFrames(framePart.right(framePart.length() - frameStrConst.length()), busNum);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void SocketCANd::disconnectDevice() {
|
||||
@@ -339,15 +367,15 @@ void SocketCANd::invokeReadTCPData()
|
||||
|
||||
void SocketCANd::readTCPData(int busNum)
|
||||
{
|
||||
QByteArray data;
|
||||
QString data;
|
||||
|
||||
if (tcpClient[busNum]) data = tcpClient[busNum]->readAll();
|
||||
if (QTcpSocket* socket = tcpClient.value(busNum))
|
||||
data = QString(socket->readAll());
|
||||
//sendDebug("Got data from TCP. Len = " % QString::number(data.length()));
|
||||
//qDebug() << "Received datagramm: " << data;
|
||||
procRXData(data, busNum);
|
||||
}
|
||||
|
||||
|
||||
void SocketCANd::procRXData(QString data, int busNum)
|
||||
{
|
||||
if (data != "")
|
||||
@@ -355,8 +383,6 @@ void SocketCANd::procRXData(QString data, int busNum)
|
||||
mTimer.stop();
|
||||
mTimer.start();
|
||||
}
|
||||
QByteArray output;
|
||||
|
||||
switch (rx_state.at(busNum))
|
||||
{
|
||||
case IDLE:
|
||||
@@ -374,6 +400,7 @@ void SocketCANd::procRXData(QString data, int busNum)
|
||||
{
|
||||
switchToRawMode(busNum);
|
||||
rx_state[busNum] = SWITCHING2RAW;
|
||||
unprocessedData[busNum].clear();
|
||||
}
|
||||
else qInfo() << hostCanIDs[busNum] << ": Could not open bus. Host did not respond with ""< ok >"": " << data;
|
||||
break;
|
||||
@@ -383,9 +410,30 @@ void SocketCANd::procRXData(QString data, int busNum)
|
||||
{
|
||||
rx_state[busNum] = RAWMODE;
|
||||
}
|
||||
else if(data.indexOf("< ok >", 0, Qt::CaseSensitivity::CaseInsensitive) == 0)
|
||||
{
|
||||
qDebug() << "Ok found at start of compound message, switching to RAW and decoding immediately";
|
||||
rx_state[busNum] = RAWMODE;
|
||||
unprocessedData[busNum] = decodeFrames(data, busNum);
|
||||
}
|
||||
else if(data.indexOf("< ok >", 0, Qt::CaseSensitivity::CaseInsensitive) > 0)
|
||||
{
|
||||
qDebug() << "Ok found at in middle of compound message, switching to RAW and decoding immediately";
|
||||
rx_state[busNum] = RAWMODE;
|
||||
unprocessedData[busNum] = decodeFrames(data, busNum);
|
||||
}
|
||||
break;
|
||||
case RAWMODE:
|
||||
decodeFrames(data, busNum);
|
||||
unprocessedData[busNum] = decodeFrames(unprocessedData[busNum] + data, busNum);
|
||||
|
||||
if(unprocessedData[busNum].length() > 128)
|
||||
{
|
||||
//the buffer has grown too much we need to clear it out, but what is good logic for that?
|
||||
//the decodeFrames function strips out datat that doesn't have a '< frame' starting token, and in its
|
||||
//recursive calling of itself it strips out data that preceedes valid frames, so this should never happen
|
||||
qDebug() << "busNum: " << busNum << "- " << unprocessedData[busNum].length() << " bytes in unprocessedData, something is wrong, clearing...";
|
||||
unprocessedData[busNum].clear();
|
||||
}
|
||||
break;
|
||||
case ISOTP:
|
||||
break;
|
||||
|
||||
@@ -57,7 +57,7 @@ private slots:
|
||||
void invokeReadTCPData();
|
||||
void deviceConnected(int busNum);
|
||||
void switchToRawMode(int busNum);
|
||||
void decodeFrames(QString, int busNum);
|
||||
QString decodeFrames(QString, int busNum);
|
||||
|
||||
private:
|
||||
void procRXData(QString, int busNum);
|
||||
@@ -73,9 +73,9 @@ protected:
|
||||
int hostPort;
|
||||
QList<QString> hostCanIDs;
|
||||
int framesRapid;
|
||||
QByteArray buildData;
|
||||
QVarLengthArray<MODE> rx_state;
|
||||
CANFrame buildFrame;
|
||||
QVarLengthArray<QString> unprocessedData;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -76,12 +76,16 @@ QString DBC_SIGNAL::processSignalTree(const CANFrame &frame)
|
||||
if (sig->processAsText(frame, sigString))
|
||||
{
|
||||
qDebug() << "Returned value: " << sigString;
|
||||
if (!build.isEmpty() && !sigString.isEmpty())
|
||||
build.append("\n");
|
||||
build.append(sigString);
|
||||
build.append("\n");
|
||||
if (sig->isMultiplexor)
|
||||
{
|
||||
qDebug() << "Spelunkin!";
|
||||
build.append(sig->processSignalTree(frame));
|
||||
auto subTreeString = sig->processSignalTree(frame);
|
||||
if (!build.isEmpty() && !subTreeString.isEmpty())
|
||||
build.append("\n");
|
||||
build.append(subTreeString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +122,7 @@ s 5 47 46 45 44 43 42 41 40
|
||||
So, the bits are 12, 11, 10, 9, 8, 23, 22, 21. Yes, that's confusing. They now go in reverse value order too.
|
||||
Bit 12 is worth 128, 11 is worth 64, etc until bit 21 is worth 1.
|
||||
*/
|
||||
bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString, bool outputName)
|
||||
bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString, bool outputName, bool outputUnit)
|
||||
{
|
||||
int64_t result = 0;
|
||||
bool isSigned = false;
|
||||
@@ -155,7 +159,7 @@ bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString, bool o
|
||||
//a 32 bit single precision float. That's evil incarnate but it is very fast and small
|
||||
//in terms of new code.
|
||||
result = Utility::processIntegerSignal(frame.payload(), startBit, 32, intelByteOrder, false);
|
||||
endResult = (*((float *)(&result)) * factor) + bias;
|
||||
endResult = (*((float *)(&result)) * factor) + bias; //look away! This is awful. I don't even know for sure if it works. Should test that.
|
||||
}
|
||||
else //double precision float
|
||||
{
|
||||
@@ -166,11 +170,11 @@ bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString, bool o
|
||||
}
|
||||
//like the above, this is rotten and evil and wrong in so many ways. Force
|
||||
//calculation of a 64 bit integer and then cast it into a double.
|
||||
result = Utility::processIntegerSignal(frame.payload(), 0, 64, intelByteOrder, false);
|
||||
result = Utility::processIntegerSignal(frame.payload(), startBit, 64, intelByteOrder, false);
|
||||
endResult = (*((double *)(&result)) * factor) + bias;
|
||||
}
|
||||
|
||||
outString = makePrettyOutput(endResult, result, outputName, isInteger);
|
||||
outString = makePrettyOutput(endResult, result, outputName, isInteger, outputUnit);
|
||||
cachedValue = endResult;
|
||||
return true;
|
||||
}
|
||||
@@ -191,7 +195,7 @@ bool DBC_SIGNAL::getValueString(int64_t intVal, QString &outString)
|
||||
return false;
|
||||
}
|
||||
|
||||
QString DBC_SIGNAL::makePrettyOutput(double floatVal, int64_t intVal, bool outputName, bool isInteger)
|
||||
QString DBC_SIGNAL::makePrettyOutput(double floatVal, int64_t intVal, bool outputName, bool isInteger, bool outputUnit)
|
||||
{
|
||||
QString outputString;
|
||||
|
||||
@@ -209,11 +213,13 @@ QString DBC_SIGNAL::makePrettyOutput(double floatVal, int64_t intVal, bool outpu
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundVal) outputString += QString::number(intVal) + unitName;
|
||||
if (!foundVal) outputString += QString::number(intVal);
|
||||
if (outputUnit) outputString += unitName;
|
||||
}
|
||||
else //otherwise display the actual number and unit (if it exists)
|
||||
{
|
||||
outputString += (isInteger ? QString::number(intVal) : QString::number(floatVal)) + unitName;
|
||||
outputString += (isInteger ? QString::number(intVal) : QString::number(floatVal));
|
||||
if (outputUnit) outputString += unitName;
|
||||
}
|
||||
return outputString;
|
||||
}
|
||||
@@ -306,7 +312,7 @@ bool DBC_SIGNAL::processAsDouble(const CANFrame &frame, double &outValue)
|
||||
}
|
||||
//like the above, this is rotten and evil and wrong in so many ways. Force
|
||||
//calculation of a 64 bit integer and then cast it into a double.
|
||||
result = Utility::processIntegerSignal(frame.payload(), 0, 64, false, false);
|
||||
result = Utility::processIntegerSignal(frame.payload(), startBit, 64, false, false);
|
||||
endResult = (*((double *)(&result)) * factor) + bias;
|
||||
}
|
||||
cachedValue = endResult;
|
||||
|
||||
@@ -71,6 +71,7 @@ class DBC_NODE
|
||||
public:
|
||||
QString name;
|
||||
QString comment;
|
||||
QString sourceFileName;
|
||||
QList<DBC_ATTRIBUTE_VALUE> attributes;
|
||||
|
||||
DBC_ATTRIBUTE_VALUE *findAttrValByName(QString name);
|
||||
@@ -110,13 +111,14 @@ public: //TODO: this is sloppy. It shouldn't all be public!
|
||||
QList<DBC_VAL_ENUM_ENTRY> valList;
|
||||
QList<DBC_SIGNAL *> multiplexedChildren;
|
||||
DBC_SIGNAL *multiplexParent;
|
||||
DBC_SIGNAL *self;
|
||||
|
||||
DBC_SIGNAL();
|
||||
bool processAsText(const CANFrame &frame, QString &outString, bool outputName = true);
|
||||
bool processAsText(const CANFrame &frame, QString &outString, bool outputName = true, bool outputUnit = true);
|
||||
bool processAsInt(const CANFrame &frame, int32_t &outValue);
|
||||
bool processAsDouble(const CANFrame &frame, double &outValue);
|
||||
bool getValueString(int64_t intVal, QString &outString);
|
||||
QString makePrettyOutput(double floatVal, int64_t intVal, bool outputName = true, bool isInteger = false);
|
||||
QString makePrettyOutput(double floatVal, int64_t intVal, bool outputName = true, bool isInteger = false, bool outputUnit = true);
|
||||
QString processSignalTree(const CANFrame &frame);
|
||||
DBC_ATTRIBUTE_VALUE *findAttrValByName(QString name);
|
||||
DBC_ATTRIBUTE_VALUE *findAttrValByIdx(int idx);
|
||||
|
||||
@@ -100,8 +100,15 @@ void DBCSignalHandler::sort()
|
||||
DBC_MESSAGE* DBCMessageHandler::findMsgByID(uint32_t id)
|
||||
{
|
||||
if (messages.count() == 0) return nullptr;
|
||||
DBC_MESSAGE *bestMatch = nullptr;
|
||||
|
||||
for (int i = 0; i < messages.count(); i++)
|
||||
{
|
||||
if ( messages[i].ID == id )
|
||||
{
|
||||
return &messages[i];
|
||||
}
|
||||
|
||||
if (matchingCriteria == J1939)
|
||||
{
|
||||
// include data page and extended data page in the pgn
|
||||
@@ -112,7 +119,7 @@ DBC_MESSAGE* DBCMessageHandler::findMsgByID(uint32_t id)
|
||||
pgn &= 0x3FF00;
|
||||
if ((messages[i].ID & 0x3FF0000) == (pgn << 8))
|
||||
{
|
||||
return &messages[i];
|
||||
bestMatch = &messages[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -120,7 +127,7 @@ DBC_MESSAGE* DBCMessageHandler::findMsgByID(uint32_t id)
|
||||
// PDU2 format
|
||||
if ((messages[i].ID & 0x3FFFF00) == (pgn << 8))
|
||||
{
|
||||
return &messages[i];
|
||||
bestMatch = &messages[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,17 +136,10 @@ DBC_MESSAGE* DBCMessageHandler::findMsgByID(uint32_t id)
|
||||
// Match the bits 14-26 (Arbitration Id) of GMLAN 29bit header
|
||||
uint32_t arbId = id &0x3FFE000;
|
||||
if ( (arbId != 0) && (messages[i].ID & 0x3FFE000) == arbId )
|
||||
return &messages[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( messages[i].ID == id )
|
||||
{
|
||||
return &messages[i];
|
||||
}
|
||||
bestMatch = &messages[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
DBC_MESSAGE* DBCMessageHandler::findMsgByIdx(int idx)
|
||||
@@ -177,6 +177,24 @@ DBC_MESSAGE* DBCMessageHandler::findMsgByPartialName(QString name)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QList<DBC_MESSAGE*> DBCMessageHandler::findMsgsByNode(DBC_NODE* node)
|
||||
{
|
||||
QList<DBC_MESSAGE*> messagesForNode;
|
||||
|
||||
if (messages.count() == 0)
|
||||
return messagesForNode;
|
||||
|
||||
for (int i = 0; i < messages.count(); i++)
|
||||
{
|
||||
if (messages[i].sender == node)
|
||||
{
|
||||
messagesForNode.append(&messages[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return messagesForNode;
|
||||
}
|
||||
|
||||
bool DBCMessageHandler::addMessage(DBC_MESSAGE &msg)
|
||||
{
|
||||
messages.append(msg);
|
||||
@@ -192,6 +210,7 @@ bool DBCMessageHandler::removeMessage(DBC_MESSAGE *msg)
|
||||
{
|
||||
messages.removeAt(i);
|
||||
qDebug() << "Removed message at idx " << i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -336,7 +355,26 @@ DBC_NODE* DBCFile::findNodeByName(QString name)
|
||||
if (dbc_nodes.length() == 0) return nullptr;
|
||||
for (int i = 0; i < dbc_nodes.length(); i++)
|
||||
{
|
||||
if (dbc_nodes[i].name.compare(name, Qt::CaseInsensitive) == 0)
|
||||
if (name.compare(dbc_nodes[i].name, Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
return &dbc_nodes[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DBC_NODE* DBCFile::findNodeByNameAndComment(QString fullname)
|
||||
{
|
||||
QString nameAndComment;
|
||||
if (dbc_nodes.length() == 0) return nullptr;
|
||||
for (int i = 0; i < dbc_nodes.length(); i++)
|
||||
{
|
||||
if(dbc_nodes[i].comment.isEmpty())
|
||||
nameAndComment = dbc_nodes[i].name;
|
||||
else
|
||||
nameAndComment = dbc_nodes[i].name + " - " + dbc_nodes[i].comment;
|
||||
|
||||
if (fullname.compare(nameAndComment, Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
return &dbc_nodes[i];
|
||||
}
|
||||
@@ -354,6 +392,11 @@ QString DBCFile::getFilename()
|
||||
return fileName;
|
||||
}
|
||||
|
||||
QString DBCFile::getFilenameNoExt()
|
||||
{
|
||||
return fileName.split(".dbc")[0];
|
||||
}
|
||||
|
||||
QString DBCFile::getPath()
|
||||
{
|
||||
return filePath;
|
||||
@@ -403,12 +446,21 @@ void DBCFile::findAttributesByType(DBC_ATTRIBUTE_TYPE typ, QList<DBC_ATTRIBUTE>
|
||||
}
|
||||
}
|
||||
|
||||
//there's no external way to clear the flag. It is only cleared when the file is saved by this object.
|
||||
void DBCFile::setDirtyFlag()
|
||||
{
|
||||
isDirty = true;
|
||||
}
|
||||
|
||||
//BE CAREFUL HERE. Do not clear the dirty flag unless you're absolutely sure nothing has changed.
|
||||
//Currently the signal editor clears this flag if the entire undo buffer is emptied but still
|
||||
//it's possible that signals or messages were deleted or added so this is potentially not that safe
|
||||
//It would be better if every node, message, and signal had a dirty flag. Then the DBCFile getDirtyFlag
|
||||
//function could traverse the tree and see if anything is dirty.
|
||||
void DBCFile::clearDirtyFlag()
|
||||
{
|
||||
isDirty = false;
|
||||
}
|
||||
|
||||
bool DBCFile::getDirtyFlag()
|
||||
{
|
||||
return isDirty;
|
||||
@@ -422,7 +474,7 @@ DBC_MESSAGE* DBCFile::parseMessageLine(QString line)
|
||||
DBC_MESSAGE *msgPtr;
|
||||
|
||||
qDebug() << "Found a BO line";
|
||||
regex.setPattern("^BO\\_ (\\w+) (\\w+) *: (\\w+) (\\w+)");
|
||||
regex.setPattern("^BO\\_ (\\w+) ([-\\w]+) *: (\\w+) ([-\\w]+)");
|
||||
match = regex.match(line);
|
||||
//captured 1 = the ID in decimal
|
||||
//captured 2 = The message name
|
||||
@@ -433,7 +485,7 @@ DBC_MESSAGE* DBCFile::parseMessageLine(QString line)
|
||||
DBC_MESSAGE msg;
|
||||
uint32_t ID = match.captured(1).toULong(); //the ID is always stored in decimal format
|
||||
msg.ID = ID & 0x1FFFFFFFul;
|
||||
msg.extendedID = (ID & 80000000ul) ? true : false;
|
||||
msg.extendedID = (ID & 0x80000000ul) ? true : false;
|
||||
msg.name = match.captured(2);
|
||||
msg.len = match.captured(3).toUInt();
|
||||
msg.sender = findNodeByName(match.captured(4));
|
||||
@@ -461,7 +513,7 @@ DBC_SIGNAL* DBCFile::parseSignalLine(QString line, DBC_MESSAGE *msg)
|
||||
sig.isMultiplexor = false;
|
||||
|
||||
qDebug() << "Found a SG line";
|
||||
regex.setPattern("^SG\\_ *(\\w+) +M *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)");
|
||||
regex.setPattern("^SG\\_ *([-\\w]+) +M *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)");
|
||||
|
||||
match = regex.match(line);
|
||||
if (match.hasMatch())
|
||||
@@ -472,7 +524,7 @@ DBC_SIGNAL* DBCFile::parseSignalLine(QString line, DBC_MESSAGE *msg)
|
||||
}
|
||||
else
|
||||
{
|
||||
regex.setPattern("^SG\\_ *(\\w+) +m(\\d+) *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)");
|
||||
regex.setPattern("^SG\\_ *([-\\w]+) +m(\\d+) *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)");
|
||||
match = regex.match(line);
|
||||
if (match.hasMatch())
|
||||
{
|
||||
@@ -485,7 +537,7 @@ DBC_SIGNAL* DBCFile::parseSignalLine(QString line, DBC_MESSAGE *msg)
|
||||
}
|
||||
else
|
||||
{
|
||||
regex.setPattern("^SG\\_ *(\\w+) +m(\\d+)M *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)");
|
||||
regex.setPattern("^SG\\_ *([-\\w]+) +m(\\d+)M *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)");
|
||||
match = regex.match(line);
|
||||
if (match.hasMatch())
|
||||
{
|
||||
@@ -499,7 +551,7 @@ DBC_SIGNAL* DBCFile::parseSignalLine(QString line, DBC_MESSAGE *msg)
|
||||
else
|
||||
{
|
||||
qDebug() << "standard signal";
|
||||
regex.setPattern("^SG\\_ *(\\w+) *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)");
|
||||
regex.setPattern("^SG\\_ *([-\\w]+) *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)");
|
||||
match = regex.match(line);
|
||||
sig.isMultiplexed = false;
|
||||
sig.isMultiplexor = false;
|
||||
@@ -591,7 +643,7 @@ bool DBCFile::parseSignalMultiplexValueLine(QString line)
|
||||
QRegularExpressionMatch match;
|
||||
|
||||
qDebug() << "Found a multiplex definition line";
|
||||
regex.setPattern("^SG\\_MUL\\_VAL\\_ (\\d+) (\\w+) (\\w+) (\\d+)\\-(\\d+);");
|
||||
regex.setPattern("^SG\\_MUL\\_VAL\\_ (\\d+) ([-\\w]+) ([-\\w]+) (\\d+)\\-(\\d+);");
|
||||
match = regex.match(line);
|
||||
//captured 1 is message ID
|
||||
//Captured 2 is signal name
|
||||
@@ -612,6 +664,8 @@ bool DBCFile::parseSignalMultiplexValueLine(QString line)
|
||||
//now need to add "thisSignal" to the children multiplexed signals of "parentSignal"
|
||||
parentSignal->multiplexedChildren.append(thisSignal);
|
||||
thisSignal->multiplexParent = parentSignal;
|
||||
thisSignal->multiplexLowValue = match.captured(4).toInt();
|
||||
thisSignal->multiplexHighValue = match.captured(5).toInt();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -620,13 +674,51 @@ bool DBCFile::parseSignalMultiplexValueLine(QString line)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBCFile::parseSignalValueTypeLine(QString line)
|
||||
{
|
||||
QRegularExpression regex;
|
||||
QRegularExpressionMatch match;
|
||||
qDebug() << "Found a signal valtype line";
|
||||
regex.setPattern("^SIG\\_VALTYPE\\_ *(\\d+) *([-\\w]+) *: *(\\d+);");
|
||||
match = regex.match(line);
|
||||
|
||||
// captured 1 is the message id
|
||||
// captured 2 is the signal name
|
||||
// captured 3 is the valtype
|
||||
if (!match.hasMatch()) { return false; }
|
||||
uint32_t id = match.captured(1).toULong() & 0x1FFFFFFFUL;
|
||||
|
||||
DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toULong() & 0x1FFFFFFFUL);
|
||||
if (msg == nullptr) { return false; }
|
||||
|
||||
DBC_SIGNAL *thisSignal = msg->sigHandler->findSignalByName(match.captured(2));
|
||||
if (thisSignal == nullptr) { return false; }
|
||||
int valType = match.captured(3).toInt();
|
||||
|
||||
switch (valType) {
|
||||
case 1: {
|
||||
thisSignal->valType = SP_FLOAT;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
thisSignal->valType = DP_FLOAT;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool DBCFile::parseValueLine(QString line)
|
||||
{
|
||||
QRegularExpression regex;
|
||||
QRegularExpressionMatch match;
|
||||
|
||||
qDebug() << "Found a value definition line";
|
||||
regex.setPattern("^VAL\\_ (\\w+) (\\w+) (.*);");
|
||||
regex.setPattern("^VAL\\_ (\\w+) ([-\\w]+) (.*);");
|
||||
match = regex.match(line);
|
||||
//captured 1 is the ID to match against
|
||||
//captured 2 is the signal name to match against
|
||||
@@ -671,7 +763,7 @@ bool DBCFile::parseAttributeLine(QString line)
|
||||
QRegularExpression regex;
|
||||
QRegularExpressionMatch match;
|
||||
|
||||
regex.setPattern("^BA\\_ \\\"*(\\w+)\\\"* BO\\_ (\\d+) \\\"*([#\\w]+)\\\"*");
|
||||
regex.setPattern("^BA\\_ \\\"*([-\\w]+)\\\"* BO\\_ (\\d+) \\\"*([#\\w]+)\\\"*");
|
||||
match = regex.match(line);
|
||||
//captured 1 is the attribute name
|
||||
//captured 2 is the message ID number (frame ID)
|
||||
@@ -682,7 +774,7 @@ bool DBCFile::parseAttributeLine(QString line)
|
||||
DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1));
|
||||
if (foundAttr)
|
||||
{
|
||||
qDebug() << "That attribute does exist";
|
||||
qDebug() << "That message attribute does exist";
|
||||
DBC_MESSAGE *foundMsg = messageHandler->findMsgByID(match.captured(2).toUInt() & 0x1FFFFFFFul);
|
||||
if (foundMsg)
|
||||
{
|
||||
@@ -702,7 +794,7 @@ bool DBCFile::parseAttributeLine(QString line)
|
||||
}
|
||||
}
|
||||
|
||||
regex.setPattern("^BA\\_ \\\"*(\\w+)\\\"* SG\\_ (\\d+) \\\"*(\\w+)\\\"* \\\"*([#\\w]+)\\\"*");
|
||||
regex.setPattern("^BA\\_ \\\"*([-\\w]+)\\\"* SG\\_ (\\d+) \\\"*([-\\w]+)\\\"* \\\"*([#\\w]+)\\\"*");
|
||||
match = regex.match(line);
|
||||
//captured 1 is the attribute name
|
||||
//captured 2 is the message ID number (frame ID)
|
||||
@@ -714,7 +806,7 @@ bool DBCFile::parseAttributeLine(QString line)
|
||||
DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1));
|
||||
if (foundAttr)
|
||||
{
|
||||
qDebug() << "That attribute does exist";
|
||||
qDebug() << "That signal attribute does exist";
|
||||
DBC_MESSAGE *foundMsg = messageHandler->findMsgByID(match.captured(2).toUInt() & 0x1FFFFFFFUL);
|
||||
if (foundMsg)
|
||||
{
|
||||
@@ -736,7 +828,7 @@ bool DBCFile::parseAttributeLine(QString line)
|
||||
}
|
||||
}
|
||||
|
||||
regex.setPattern("^BA\\_ \\\"*(\\w+)\\\"* BU\\_ \\\"*(\\w+)\\\"* \\\"*([#\\w]+)\\\"*");
|
||||
regex.setPattern("^BA\\_ \\\"*([-\\w]+)\\\"* BU\\_ \\\"*([-\\w]+)\\\"* \\\"*([#\\w]+)\\\"*");
|
||||
match = regex.match(line);
|
||||
//captured 1 is the attribute name
|
||||
//captured 2 is the name of the node
|
||||
@@ -747,7 +839,7 @@ bool DBCFile::parseAttributeLine(QString line)
|
||||
DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1));
|
||||
if (foundAttr)
|
||||
{
|
||||
qDebug() << "That attribute does exist";
|
||||
qDebug() << "That node attribute does exist";
|
||||
DBC_NODE *foundNode = findNodeByName(match.captured(2));
|
||||
if (foundNode)
|
||||
{
|
||||
@@ -774,7 +866,7 @@ bool DBCFile::parseDefaultAttrLine(QString line)
|
||||
QRegularExpression regex;
|
||||
QRegularExpressionMatch match;
|
||||
|
||||
regex.setPattern("^BA\\_DEF\\_DEF\\_ \\\"*(\\w+)\\\"* \\\"*([#\\w]*)\\\"*");
|
||||
regex.setPattern("^BA\\_DEF\\_DEF\\_ \\\"*([-\\w]+)\\\"* \\\"*([#\\w]*)\\\"*");
|
||||
match = regex.match(line);
|
||||
//captured 1 is the name of the attribute
|
||||
//captured 2 is the default value for that attribute
|
||||
@@ -824,6 +916,7 @@ bool DBCFile::loadFile(QString fileName)
|
||||
DBC_ATTRIBUTE attr;
|
||||
int numSigFaults = 0, numMsgFaults = 0;
|
||||
int linesSinceYield = 0;
|
||||
QString fileBaseName = QFileInfo(fileName).baseName();
|
||||
|
||||
bool inMultilineBU = false;
|
||||
|
||||
@@ -862,6 +955,7 @@ bool DBCFile::loadFile(QString fileName)
|
||||
if (rawLine.startsWith("\t") || rawLine.startsWith(" "))
|
||||
{
|
||||
DBC_NODE node;
|
||||
node.sourceFileName = fileBaseName;
|
||||
node.name = line;
|
||||
dbc_nodes.append(node);
|
||||
}
|
||||
@@ -885,6 +979,11 @@ bool DBCFile::loadFile(QString fileName)
|
||||
if (!parseSignalMultiplexValueLine(line)) numSigFaults++;
|
||||
}
|
||||
|
||||
if (line.startsWith("SIG_VALTYPE_ ")) //defines a signal value type
|
||||
{
|
||||
if (!parseSignalValueTypeLine(line)) numSigFaults++;
|
||||
}
|
||||
|
||||
if (line.startsWith("BU_:")) //line specifies the nodes on this canbus
|
||||
{
|
||||
qDebug() << "Found a BU line";
|
||||
@@ -901,6 +1000,7 @@ bool DBCFile::loadFile(QString fileName)
|
||||
if (nodeStrings[i].length() > 1)
|
||||
{
|
||||
DBC_NODE node;
|
||||
node.sourceFileName = fileBaseName;
|
||||
node.name = nodeStrings[i];
|
||||
dbc_nodes.append(node);
|
||||
}
|
||||
@@ -911,7 +1011,7 @@ bool DBCFile::loadFile(QString fileName)
|
||||
if (line.startsWith("CM_ SG_ "))
|
||||
{
|
||||
qDebug() << "Found an SG comment line";
|
||||
regex.setPattern("^CM\\_ SG\\_ *(\\w+) *(\\w+) *\\\"(.*)\\\";");
|
||||
regex.setPattern("^CM\\_ SG\\_ *(\\w+) *([-\\w]+) *\\\"(.*)\\\";");
|
||||
match = regex.match(line);
|
||||
//captured 1 is the ID to match against to get to the message
|
||||
//captured 2 is the signal name from that message
|
||||
@@ -950,7 +1050,7 @@ bool DBCFile::loadFile(QString fileName)
|
||||
if (line.startsWith("CM_ BU_ "))
|
||||
{
|
||||
qDebug() << "Found a BU comment line";
|
||||
regex.setPattern("^CM\\_ BU\\_ *(\\w+) *\\\"(.*)\\\";");
|
||||
regex.setPattern("^CM\\_ BU\\_ *([-\\w]+) *\\\"(.*)\\\";");
|
||||
match = regex.match(line);
|
||||
//captured 1 is the Node name
|
||||
//captured 2 is the comment itself
|
||||
@@ -1254,7 +1354,7 @@ bool DBCFile::saveFile(QString fileName)
|
||||
int msgNumber = 1;
|
||||
int sigNumber = 1;
|
||||
QFile *outFile = new QFile(fileName);
|
||||
QString nodesOutput, msgOutput, commentsOutput, valuesOutput;
|
||||
QString nodesOutput, msgOutput, commentsOutput, valuesOutput, extMultiplexOutput;
|
||||
QString defaultsOutput, attrValOutput;
|
||||
bool hasExtendedMultiplexing = false;
|
||||
|
||||
@@ -1323,7 +1423,7 @@ bool DBCFile::saveFile(QString fileName)
|
||||
attrValOutput.append("BA_ \"" + val.attrName + "\" BU_ ");
|
||||
switch (val.value.type())
|
||||
{
|
||||
case QMetaType::QString:
|
||||
case QVariant::Type::String:
|
||||
attrValOutput.append("\"" + val.value.toString() + "\";\n");
|
||||
break;
|
||||
default:
|
||||
@@ -1349,7 +1449,10 @@ bool DBCFile::saveFile(QString fileName)
|
||||
}
|
||||
|
||||
uint32_t ID = msg->ID;
|
||||
if (msg->ID > 0x7FF || msg->extendedID) msg->ID += 0x80000000ul; //set bit 31 if this ID is extended.
|
||||
if (msg->ID > 0x7FF || msg->extendedID)
|
||||
{
|
||||
ID += 0x80000000ul; //set bit 31 if this ID is extended.
|
||||
}
|
||||
|
||||
msgOutput.append("BO_ " + QString::number(ID) + " " + msg->name + ": " + QString::number(msg->len) +
|
||||
" " + msg->sender->name + "\n");
|
||||
@@ -1365,7 +1468,7 @@ bool DBCFile::saveFile(QString fileName)
|
||||
attrValOutput.append("BA_ \"" + val.attrName + "\" BO_ " + QString::number(ID) + " ");
|
||||
switch (val.value.type())
|
||||
{
|
||||
case QMetaType::QString:
|
||||
case QVariant::Type::String:
|
||||
attrValOutput.append("\"" + val.value.toString() + "\";\n");
|
||||
break;
|
||||
default:
|
||||
@@ -1442,7 +1545,7 @@ bool DBCFile::saveFile(QString fileName)
|
||||
attrValOutput.append("BA_ \"" + val.attrName + "\" SG_ " + QString::number(ID) + " " + sig->name + " ");
|
||||
switch (val.value.type())
|
||||
{
|
||||
case QMetaType::QString:
|
||||
case QVariant::Type::String:
|
||||
attrValOutput.append("\"" + val.value.toString() + "\";\n");
|
||||
break;
|
||||
default:
|
||||
@@ -1469,6 +1572,9 @@ bool DBCFile::saveFile(QString fileName)
|
||||
msgOutput.clear(); //got to reset it after writing
|
||||
}
|
||||
|
||||
outFile->write(commentsOutput.toUtf8());
|
||||
commentsOutput.clear();
|
||||
|
||||
//Now dump out all of the stored attributes
|
||||
for (int x = 0; x < dbc_attributes.count(); x++)
|
||||
{
|
||||
@@ -1486,6 +1592,8 @@ bool DBCFile::saveFile(QString fileName)
|
||||
case ATTR_TYPE_SIG:
|
||||
msgOutput.append("SG_ ");
|
||||
break;
|
||||
case ATTR_TYPE_ANY:
|
||||
break;
|
||||
}
|
||||
|
||||
msgOutput.append("\"" + dbc_attributes[x].name + "\" ");
|
||||
@@ -1528,6 +1636,8 @@ bool DBCFile::saveFile(QString fileName)
|
||||
defaultsOutput.append("\"" + dbc_attributes[x].enumVals[dbc_attributes[x].defaultValue.toInt()] + "\";\n");
|
||||
break;
|
||||
case ATTR_INT:
|
||||
defaultsOutput.append(QString::number(dbc_attributes[x].defaultValue.toLongLong()) + ";\n");
|
||||
break;
|
||||
case ATTR_FLOAT:
|
||||
defaultsOutput.append(dbc_attributes[x].defaultValue.toString() + ";\n");
|
||||
break;
|
||||
@@ -1545,7 +1655,10 @@ bool DBCFile::saveFile(QString fileName)
|
||||
DBC_MESSAGE *msg = messageHandler->findMsgByIdx(x);
|
||||
|
||||
uint32_t ID = msg->ID;
|
||||
if (msg->ID > 0x7FF || msg->extendedID) msg->ID += 0x80000000ul; //set bit 31 if this ID is extended.
|
||||
if (msg->ID > 0x7FF || msg->extendedID)
|
||||
{
|
||||
ID += 0x80000000ul; //set bit 31 if this ID is extended.
|
||||
}
|
||||
|
||||
for (int s = 0; s < msg->sigHandler->getCount(); s++)
|
||||
{
|
||||
@@ -1554,10 +1667,10 @@ bool DBCFile::saveFile(QString fileName)
|
||||
if (sig->isMultiplexed)
|
||||
{
|
||||
msgOutput.append("SG_MUL_VAL_ " + QString::number(ID) + " ");
|
||||
msgOutput.append(sig->name + " " + sig->parentMessage->name + " ");
|
||||
msgOutput.append(sig->name + " " + sig->multiplexParent->name + " ");
|
||||
msgOutput.append(QString::number(sig->multiplexLowValue) + "-" + QString::number(sig->multiplexHighValue) + ";");
|
||||
msgOutput.append("\n");
|
||||
outFile->write(msgOutput.toUtf8());
|
||||
extMultiplexOutput.append(msgOutput);
|
||||
msgOutput.clear(); //got to reset it after writing
|
||||
}
|
||||
}
|
||||
@@ -1565,15 +1678,15 @@ bool DBCFile::saveFile(QString fileName)
|
||||
}
|
||||
|
||||
//now write out all of the accumulated comments and value tables from above
|
||||
outFile->write(attrValOutput.toUtf8());
|
||||
outFile->write(defaultsOutput.toUtf8());
|
||||
outFile->write(commentsOutput.toUtf8());
|
||||
outFile->write(attrValOutput.toUtf8());
|
||||
outFile->write(valuesOutput.toUtf8());
|
||||
outFile->write(extMultiplexOutput.toUtf8());
|
||||
|
||||
attrValOutput.clear();
|
||||
defaultsOutput.clear();
|
||||
commentsOutput.clear();
|
||||
valuesOutput.clear();
|
||||
extMultiplexOutput.clear();
|
||||
|
||||
outFile->close();
|
||||
delete outFile;
|
||||
@@ -2140,6 +2253,8 @@ DBC_MESSAGE* DBCHandler::findMessageForFilter(uint32_t id, MatchingCriteria_t *
|
||||
|
||||
/*
|
||||
* As above, a real shortcut function that searches all files in order to try to find a message with the given name
|
||||
* This has pitfalls because the same message name can easily exist in multiple dbc files AND nodes in a single file
|
||||
* By DBC standards only the MSG ID is required to be unique
|
||||
*/
|
||||
DBC_MESSAGE* DBCHandler::findMessage(const QString msgName)
|
||||
{
|
||||
@@ -2153,6 +2268,43 @@ DBC_MESSAGE* DBCHandler::findMessage(const QString msgName)
|
||||
return nullptr; //no match, tough luck, return null
|
||||
}
|
||||
|
||||
DBC_MESSAGE* DBCHandler::findMessage(const QString msgName, const QString nodeName, const QString fileNameNoExt)
|
||||
{
|
||||
DBC_MESSAGE *msg = nullptr;
|
||||
for(int i = 0; i < loadedFiles.count(); i++)
|
||||
{
|
||||
DBCFile * file = getFileByIdx(i);
|
||||
if(file->getFilenameNoExt() == fileNameNoExt)
|
||||
{
|
||||
int msgCount = file->messageHandler->getCount();
|
||||
for(int f = 0; f < msgCount; f++)
|
||||
{
|
||||
msg = file->messageHandler->findMsgByIdx(f);
|
||||
if (msg && msg->name == msgName && msg->sender->name == nodeName)
|
||||
return msg; //if it's not null and the node name matches return it
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr; //no match, tough luck, return null
|
||||
}
|
||||
|
||||
DBC_MESSAGE* DBCHandler::findMessage(const QString msgName, const QString fullyQualifiedNodeName)
|
||||
{
|
||||
QStringList nodeNameParts = fullyQualifiedNodeName.split(Utility::fullyQualifiedNameSeperator);
|
||||
if(nodeNameParts.count() != 2)
|
||||
{
|
||||
qDebug() << "Error parsing fully qualified node name for message search";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString fileNameNoExt = nodeNameParts[0];
|
||||
QString nodeName = nodeNameParts[1];
|
||||
|
||||
DBC_MESSAGE *msg = nullptr;
|
||||
msg = findMessage(msgName, nodeName, fileNameNoExt);
|
||||
return msg;
|
||||
}
|
||||
|
||||
int DBCHandler::getFileCount()
|
||||
{
|
||||
return loadedFiles.count();
|
||||
@@ -2183,18 +2335,19 @@ DBCHandler::DBCHandler()
|
||||
{
|
||||
// Load previously saved DBC file settings
|
||||
QSettings settings;
|
||||
qDebug() <<"Settings file: " << settings.fileName();
|
||||
int filecount = settings.value("DBC/FileCount", 0).toInt();
|
||||
qDebug() << "Previously loaded DBC file count: " << filecount;
|
||||
for (int i=0; i<filecount; i++)
|
||||
{
|
||||
QString filename = settings.value("DBC/Filename_" + QString(i),"").toString();
|
||||
QString filename = settings.value("DBC/Filename_" + QString::number(i),"").toString();
|
||||
DBCFile * file = loadDBCFile(filename);
|
||||
if (file)
|
||||
{
|
||||
int bus = settings.value("DBC/AssocBus_" + QString(i),0).toInt();
|
||||
int bus = settings.value("DBC/AssocBus_" + QString::number(i),0).toInt();
|
||||
file->setAssocBus(bus);
|
||||
|
||||
MatchingCriteria_t matchingCriteria = (MatchingCriteria_t)settings.value("DBC/MatchingCriteria_" + QString(i),0).toInt();
|
||||
MatchingCriteria_t matchingCriteria = (MatchingCriteria_t)settings.value("DBC/MatchingCriteria_" + QString::number(i),0).toInt();
|
||||
|
||||
DBC_ATTRIBUTE attr;
|
||||
|
||||
@@ -2208,7 +2361,7 @@ DBCHandler::DBCHandler()
|
||||
file->dbc_attributes.append(attr);
|
||||
file->messageHandler->setMatchingCriteria(matchingCriteria);
|
||||
|
||||
bool filterLabeling = settings.value("DBC/FilterLabeling_" + QString(i),0).toBool();
|
||||
int filterLabeling = settings.value("DBC/FilterLabeling_" + QString::number(i),0).toInt();
|
||||
attr.attrType = ATTR_TYPE_MESSAGE;
|
||||
attr.defaultValue = filterLabeling;
|
||||
attr.enumVals.clear();
|
||||
|
||||
@@ -43,6 +43,7 @@ public:
|
||||
DBC_MESSAGE *findMsgByIdx(int idx);
|
||||
DBC_MESSAGE *findMsgByName(QString name);
|
||||
DBC_MESSAGE *findMsgByPartialName(QString name);
|
||||
QList<DBC_MESSAGE*> findMsgsByNode(DBC_NODE *node);
|
||||
bool addMessage(DBC_MESSAGE &msg);
|
||||
bool removeMessage(DBC_MESSAGE *msg);
|
||||
bool removeMessageByIndex(int idx);
|
||||
@@ -72,6 +73,7 @@ public:
|
||||
DBCFile(const DBCFile& cpy);
|
||||
DBCFile& operator=(const DBCFile& cpy);
|
||||
DBC_NODE *findNodeByName(QString name);
|
||||
DBC_NODE *findNodeByNameAndComment(QString fullname);
|
||||
DBC_NODE *findNodeByIdx(int idx);
|
||||
DBC_ATTRIBUTE *findAttributeByName(QString name, DBC_ATTRIBUTE_TYPE type = ATTR_TYPE_ANY);
|
||||
DBC_ATTRIBUTE *findAttributeByIdx(int idx);
|
||||
@@ -80,11 +82,13 @@ public:
|
||||
bool loadFile(QString);
|
||||
QString getFullFilename();
|
||||
QString getFilename();
|
||||
QString getFilenameNoExt();
|
||||
QString getPath();
|
||||
int getAssocBus();
|
||||
void setAssocBus(int bus);
|
||||
void setDirtyFlag();
|
||||
bool getDirtyFlag();
|
||||
void clearDirtyFlag();
|
||||
void sort();
|
||||
|
||||
DBCMessageHandler *messageHandler;
|
||||
@@ -102,6 +106,7 @@ private:
|
||||
bool parseSignalMultiplexValueLine(QString line);
|
||||
DBC_MESSAGE* parseMessageLine(QString line);
|
||||
bool parseValueLine(QString line);
|
||||
bool parseSignalValueTypeLine(QString line);
|
||||
bool parseAttributeLine(QString line);
|
||||
bool parseDefaultAttrLine(QString line);
|
||||
};
|
||||
@@ -118,6 +123,8 @@ public:
|
||||
void swapFiles(int pos1, int pos2);
|
||||
DBC_MESSAGE* findMessage(const CANFrame &frame);
|
||||
DBC_MESSAGE* findMessage(const QString msgName);
|
||||
DBC_MESSAGE* findMessage(const QString msgName, const QString fullyQualifiedNodeName);
|
||||
DBC_MESSAGE* findMessage(const QString msgName, const QString nodeName, const QString fileNameNoExt);
|
||||
DBC_MESSAGE* findMessage(uint32_t id);
|
||||
DBC_MESSAGE* findMessageForFilter(uint32_t id, MatchingCriteria_t * matchingCriteria);
|
||||
int getFileCount();
|
||||
|
||||
@@ -108,10 +108,10 @@ void DBCLoadSaveWindow::updateSettings()
|
||||
qDebug() << "Save DBC settings #" << i << " File: " << file->getFullFilename()
|
||||
<< "Bus: " << file->getAssocBus() << "MC: " << file->messageHandler->getMatchingCriteria()
|
||||
<< "Filter Labeling: " << (file->messageHandler->filterLabeling() ? "enabled" : "disabled");
|
||||
settings.setValue("DBC/Filename_" + QString(i), file->getFullFilename());
|
||||
settings.setValue("DBC/AssocBus_" + QString(i), file->getAssocBus());
|
||||
settings.setValue("DBC/MatchingCriteria_" + QString(i), file->messageHandler->getMatchingCriteria());
|
||||
settings.setValue("DBC/FilterLabeling_" + QString(i), file->messageHandler->filterLabeling());
|
||||
settings.setValue("DBC/Filename_" + QString::number(i), file->getFullFilename());
|
||||
settings.setValue("DBC/AssocBus_" + QString::number(i), file->getAssocBus());
|
||||
settings.setValue("DBC/MatchingCriteria_" + QString::number(i), file->messageHandler->getMatchingCriteria());
|
||||
settings.setValue("DBC/FilterLabeling_" + QString::number(i), file->messageHandler->filterLabeling() ? 1 : 0);
|
||||
}
|
||||
}
|
||||
emit updatedDBCSettings();
|
||||
@@ -200,7 +200,7 @@ void DBCLoadSaveWindow::loadFile()
|
||||
|
||||
DBC_ATTRIBUTE *attr = file->findAttributeByName("matchingcriteria");
|
||||
QComboBox * mc_item = addMatchingCriteriaCombobox(idx);
|
||||
if (attr && attr->defaultValue > 0)
|
||||
if (attr && attr->defaultValue.toInt() > 0)
|
||||
{
|
||||
mc_item->setCurrentIndex(attr->defaultValue.toInt());
|
||||
}
|
||||
@@ -208,7 +208,7 @@ void DBCLoadSaveWindow::loadFile()
|
||||
attr = file->findAttributeByName("filterlabeling");
|
||||
QTableWidgetItem *item = new QTableWidgetItem("");
|
||||
ui->tableFiles->setItem(idx, 3, item);
|
||||
if (attr && attr->defaultValue > 0)
|
||||
if (attr && attr->defaultValue.toInt() > 0)
|
||||
{
|
||||
item->setCheckState(Qt::Checked);
|
||||
}
|
||||
|
||||
@@ -21,25 +21,35 @@ DBCMainEditor::DBCMainEditor( const QVector<CANFrame> *frames, QWidget *parent)
|
||||
dbcHandler = DBCHandler::getReference();
|
||||
referenceFrames = frames;
|
||||
|
||||
ui->treeDBC->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
connect(ui->btnSearch, &QAbstractButton::clicked, this, &DBCMainEditor::handleSearch);
|
||||
connect(ui->lineSearch, &QLineEdit::returnPressed, this, &DBCMainEditor::handleSearch);
|
||||
connect(ui->btnSearchNext, &QAbstractButton::clicked, this, &DBCMainEditor::handleSearchForward);
|
||||
connect(ui->btnSearchPrev, &QAbstractButton::clicked, this, &DBCMainEditor::handleSearchBackward);
|
||||
connect(ui->treeDBC, &QTreeWidget::doubleClicked, this, &DBCMainEditor::onTreeDoubleClicked);
|
||||
connect(ui->treeDBC, &QTreeWidget::customContextMenuRequested, this, &DBCMainEditor::onTreeContextMenu);
|
||||
connect(ui->treeDBC, &QTreeWidget::currentItemChanged, this, &DBCMainEditor::currentItemChanged);
|
||||
connect(ui->btnDelete, &QAbstractButton::clicked, this, &DBCMainEditor::deleteCurrentTreeItem);
|
||||
connect(ui->btnNewNode, &QAbstractButton::clicked, this, &DBCMainEditor::newNode);
|
||||
connect(ui->btnNewNode, &QAbstractButton::clicked, this, QOverload<>::of(&DBCMainEditor::newNode));
|
||||
connect(ui->btnNewMessage, &QAbstractButton::clicked, this, &DBCMainEditor::newMessage);
|
||||
connect(ui->btnNewSignal, &QAbstractButton::clicked, this, &DBCMainEditor::newSignal);
|
||||
|
||||
sigEditor = new DBCSignalEditor(this);
|
||||
msgEditor = new DBCMessageEditor(this);
|
||||
nodeEditor = new DBCNodeEditor(this);
|
||||
nodeRebaseEditor = new DBCNodeRebaseEditor(this);
|
||||
nodeDuplicateEditor = new DBCNodeDuplicateEditor(this);
|
||||
|
||||
//all three might potentially change the data stored and force the tree to be updated
|
||||
connect(sigEditor, &DBCSignalEditor::updatedTreeInfo, this, &DBCMainEditor::updatedSignal);
|
||||
connect(msgEditor, &DBCMessageEditor::updatedTreeInfo, this, &DBCMainEditor::updatedMessage);
|
||||
connect(nodeEditor, &DBCNodeEditor::updatedTreeInfo, this, &DBCMainEditor::updatedNode);
|
||||
connect(nodeRebaseEditor, &DBCNodeRebaseEditor::updatedTreeInfo, this, &DBCMainEditor::updatedMessage);
|
||||
connect(nodeDuplicateEditor, &DBCNodeDuplicateEditor::updatedTreeInfo, this, &DBCMainEditor::updatedMessage);
|
||||
connect(nodeDuplicateEditor, &DBCNodeDuplicateEditor::createNode, this, QOverload<QString>::of(&DBCMainEditor::newNode));
|
||||
connect(nodeDuplicateEditor, &DBCNodeDuplicateEditor::cloneMessageToNode, this, &DBCMainEditor::copyMessageToNode);
|
||||
connect(nodeDuplicateEditor, &DBCNodeDuplicateEditor::nodeAdded, this, &DBCMainEditor::refreshTree);
|
||||
|
||||
nodeIcon = QIcon(":/icons/images/node.png");
|
||||
messageIcon = QIcon(":/icons/images/message.png");
|
||||
@@ -150,6 +160,7 @@ void DBCMainEditor::writeSettings()
|
||||
|
||||
void DBCMainEditor::onCustomMenuTree(QPoint point)
|
||||
{
|
||||
Q_UNUSED(point);
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
@@ -205,19 +216,19 @@ void DBCMainEditor::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem
|
||||
}
|
||||
switch (current->data(0, Qt::UserRole).toInt())
|
||||
{
|
||||
case 1: //node
|
||||
case DBCItemTypes::NODE: //node
|
||||
ui->btnNewNode->setEnabled(true);
|
||||
ui->btnNewMessage->setEnabled(true);
|
||||
ui->btnNewSignal->setEnabled(false);
|
||||
ui->btnDelete->setEnabled(true);
|
||||
break;
|
||||
case 2: //message
|
||||
case DBCItemTypes::MESG: //message
|
||||
ui->btnNewNode->setEnabled(true);
|
||||
ui->btnNewMessage->setEnabled(true);
|
||||
ui->btnNewSignal->setEnabled(true);
|
||||
ui->btnDelete->setEnabled(true);
|
||||
break;
|
||||
case 3: //signal
|
||||
case DBCItemTypes::SIG: //signal
|
||||
ui->btnNewNode->setEnabled(true);
|
||||
ui->btnNewMessage->setEnabled(true);
|
||||
ui->btnNewSignal->setEnabled(true);
|
||||
@@ -232,7 +243,7 @@ void DBCMainEditor::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem
|
||||
|
||||
uint32_t DBCMainEditor::getParentMessageID(QTreeWidgetItem *cell)
|
||||
{
|
||||
if (cell->data(0, Qt::UserRole) == 2)
|
||||
if (cell->data(0, Qt::UserRole) == DBCItemTypes::MESG)
|
||||
{
|
||||
return static_cast<uint32_t>(Utility::ParseStringToNum(cell->text(0).split(" ")[0]));
|
||||
}
|
||||
@@ -249,7 +260,7 @@ void DBCMainEditor::onTreeDoubleClicked(const QModelIndex &index)
|
||||
Q_UNUSED(index)
|
||||
|
||||
QTreeWidgetItem* firstCol = ui->treeDBC->currentItem();
|
||||
bool ret = false;
|
||||
//bool ret = false;
|
||||
DBC_MESSAGE *msg;
|
||||
DBC_SIGNAL *sig;
|
||||
DBC_NODE *node;
|
||||
@@ -260,7 +271,7 @@ void DBCMainEditor::onTreeDoubleClicked(const QModelIndex &index)
|
||||
|
||||
switch (firstCol->data(0, Qt::UserRole).toInt())
|
||||
{
|
||||
case 1: //a node
|
||||
case DBCItemTypes::NODE: //a node
|
||||
idString = firstCol->text(0).split(" ")[0];
|
||||
node = dbcFile->findNodeByName(idString);
|
||||
nodeEditor->setFileIdx(fileIdx);
|
||||
@@ -268,7 +279,7 @@ void DBCMainEditor::onTreeDoubleClicked(const QModelIndex &index)
|
||||
nodeEditor->refreshView();
|
||||
nodeEditor->show();
|
||||
break;
|
||||
case 2: //a message
|
||||
case DBCItemTypes::MESG: //a message
|
||||
idString = firstCol->text(0).split(" ")[0];
|
||||
msgID = static_cast<uint32_t>(Utility::ParseStringToNum(idString));
|
||||
msg = dbcFile->messageHandler->findMsgByID(msgID);
|
||||
@@ -278,7 +289,7 @@ void DBCMainEditor::onTreeDoubleClicked(const QModelIndex &index)
|
||||
msgEditor->refreshView();
|
||||
msgEditor->show(); //show allows the rest of the forms to keep going
|
||||
break;
|
||||
case 3: //a signal
|
||||
case DBCItemTypes::SIG: //a signal
|
||||
msgID = getParentMessageID(firstCol);
|
||||
msg = dbcFile->messageHandler->findMsgByID(msgID);
|
||||
QString nameString = firstCol->text(0);
|
||||
@@ -298,6 +309,74 @@ void DBCMainEditor::onTreeDoubleClicked(const QModelIndex &index)
|
||||
}
|
||||
}
|
||||
|
||||
void DBCMainEditor::onTreeContextMenu(const QPoint & pos)
|
||||
{
|
||||
QTreeWidgetItem* firstCol = ui->treeDBC->currentItem();
|
||||
QString idString;
|
||||
|
||||
qDebug() << firstCol->data(0, Qt::UserRole) << " - " << firstCol->text(0);
|
||||
|
||||
switch (firstCol->data(0, Qt::UserRole).toInt())
|
||||
{
|
||||
case DBCItemTypes::NODE: //a node
|
||||
idString = firstCol->text(0).split(" ")[0];
|
||||
//node = dbcFile->findNodeByName(idString);
|
||||
|
||||
QAction *actionRebase = new QAction(QIcon(":/Resource/warning32.ico"), tr("Rebase all messages"), this);
|
||||
actionRebase->setStatusTip(tr("Rebase all messages in node"));
|
||||
connect(actionRebase, SIGNAL(triggered()), this, SLOT(onRebaseMessages()));
|
||||
|
||||
QAction *actionDupe = new QAction(QIcon(":/Resource/warning32.ico"), tr("Duplicate node"), this);
|
||||
actionDupe->setStatusTip(tr("Duplicate node and messages"));
|
||||
connect(actionDupe, SIGNAL(triggered()), this, SLOT(onDuplicateNode()));
|
||||
|
||||
QMenu menu(this);
|
||||
menu.addAction(actionRebase);
|
||||
menu.addAction(actionDupe);
|
||||
|
||||
//QPoint pt(pos);
|
||||
menu.exec( ui->treeDBC->mapToGlobal(pos) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DBCMainEditor::onRebaseMessages()
|
||||
{
|
||||
QTreeWidgetItem* firstCol = ui->treeDBC->currentItem();
|
||||
DBC_NODE *node;
|
||||
QString idString;
|
||||
|
||||
idString = firstCol->text(0).split(" ")[0];
|
||||
node = dbcFile->findNodeByName(idString);
|
||||
nodeRebaseEditor->setFileIdx(fileIdx);
|
||||
nodeRebaseEditor->setNodeRef(node);
|
||||
if(nodeRebaseEditor->refreshView())
|
||||
{
|
||||
nodeRebaseEditor->setModal(true);
|
||||
nodeRebaseEditor->show();
|
||||
}
|
||||
}
|
||||
|
||||
void DBCMainEditor::onDuplicateNode()
|
||||
{
|
||||
QTreeWidgetItem* firstCol = ui->treeDBC->currentItem();
|
||||
//bool ret = false;
|
||||
//DBC_MESSAGE *msg;
|
||||
//DBC_SIGNAL *sig;
|
||||
//uint32_t msgID;
|
||||
DBC_NODE *node;
|
||||
QString idString;
|
||||
|
||||
idString = firstCol->text(0).split(" ")[0];
|
||||
node = dbcFile->findNodeByName(idString);
|
||||
nodeDuplicateEditor->setFileIdx(fileIdx);
|
||||
nodeDuplicateEditor->setNodeRef(node);
|
||||
if(nodeDuplicateEditor->refreshView())
|
||||
{
|
||||
nodeDuplicateEditor->setModal(true);
|
||||
nodeDuplicateEditor->show();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Recreate the whole tree with pretty icons and custom user roles that give the rest of code an easy way to figure out whether a given tree node
|
||||
@@ -329,7 +408,7 @@ void DBCMainEditor::refreshTree()
|
||||
if (node->comment.count() > 0) nodeInfo.append(" - ").append(node->comment);
|
||||
nodeItem->setText(0, nodeInfo);
|
||||
nodeItem->setIcon(0, nodeIcon);
|
||||
nodeItem->setData(0, Qt::UserRole, 1);
|
||||
nodeItem->setData(0, Qt::UserRole, DBCItemTypes::NODE);
|
||||
nodeToItem.insert(node, nodeItem);
|
||||
itemToNode.insert(nodeItem, node);
|
||||
for (int x = 0; x < dbcFile->messageHandler->getCount(); x++)
|
||||
@@ -342,7 +421,7 @@ void DBCMainEditor::refreshTree()
|
||||
if (msg->comment.count() > 0) msgInfo.append(" - ").append(msg->comment);
|
||||
msgItem->setText(0, msgInfo);
|
||||
msgItem->setIcon(0, messageIcon);
|
||||
msgItem->setData(0, Qt::UserRole, 2);
|
||||
msgItem->setData(0, Qt::UserRole, DBCItemTypes::MESG);
|
||||
messageToItem.insert(msg, msgItem);
|
||||
itemToMessage.insert(msgItem, msg);
|
||||
for (int i = 0; i < msg->sigHandler->getCount(); i++)
|
||||
@@ -387,7 +466,7 @@ void DBCMainEditor::processSignalToTree(QTreeWidgetItem *parent, DBC_SIGNAL *sig
|
||||
if (sig->isMultiplexor) sigItem->setIcon(0, multiplexorSignalIcon);
|
||||
else if (sig->isMultiplexed) sigItem->setIcon(0, multiplexedSignalIcon);
|
||||
else sigItem->setIcon(0, signalIcon);
|
||||
sigItem->setData(0, Qt::UserRole, 3);
|
||||
sigItem->setData(0, Qt::UserRole, DBCItemTypes::SIG);
|
||||
signalToItem.insert(sig, sigItem);
|
||||
itemToSignal.insert(sigItem, sig);
|
||||
if (sig->multiplexedChildren.count() > 0)
|
||||
@@ -432,7 +511,7 @@ void DBCMainEditor::updatedMessage(DBC_MESSAGE *msg)
|
||||
QTreeWidgetItem *newNodeItem = new QTreeWidgetItem();
|
||||
newNodeItem->setText(0, msg->sender->name);
|
||||
newNodeItem->setIcon(0, nodeIcon);
|
||||
newNodeItem->setData(0, Qt::UserRole, 1);
|
||||
newNodeItem->setData(0, Qt::UserRole, DBCItemTypes::NODE);
|
||||
ui->treeDBC->addTopLevelItem(newNodeItem);
|
||||
nodeToItem.insert(msg->sender, newNodeItem);
|
||||
itemToNode.insert(newNodeItem, msg->sender);
|
||||
@@ -458,7 +537,7 @@ void DBCMainEditor::updatedSignal(DBC_SIGNAL *sig)
|
||||
item->setText(0, sigInfo);
|
||||
if (sig->isMultiplexed)
|
||||
{
|
||||
if (item->parent()->data(0, Qt::UserRole).toInt() == 3) //if our parent is another signal
|
||||
if (item->parent()->data(0, Qt::UserRole).toInt() == DBCItemTypes::SIG) //if our parent is another signal
|
||||
{
|
||||
QString nameString = item->parent()->text(0);
|
||||
if (nameString.contains("(")) nameString = nameString.split(" ")[1];
|
||||
@@ -481,17 +560,24 @@ void DBCMainEditor::updatedSignal(DBC_SIGNAL *sig)
|
||||
else qDebug() << "That signal doesn't exist. That's a bug dude.";
|
||||
}
|
||||
|
||||
void DBCMainEditor::newNode()
|
||||
void DBCMainEditor::newNode(QString nodeName)
|
||||
{
|
||||
DBC_NODE node;
|
||||
DBC_NODE *nodePtr;
|
||||
node.name = "Unnamed" + QString::number(randGen.bounded(50000));
|
||||
if(nodeName.isEmpty())
|
||||
{
|
||||
node.name = "Unnamed" + QString::number(randGen.bounded(50000));
|
||||
}
|
||||
else
|
||||
{
|
||||
node.name = nodeName;
|
||||
}
|
||||
dbcFile->dbc_nodes.append(node);
|
||||
nodePtr = dbcFile->findNodeByName(node.name);
|
||||
QTreeWidgetItem *nodeItem = new QTreeWidgetItem();
|
||||
nodeItem->setText(0, node.name);
|
||||
nodeItem->setIcon(0, nodeIcon);
|
||||
nodeItem->setData(0, Qt::UserRole, 1);
|
||||
nodeItem->setData(0, Qt::UserRole, DBCItemTypes::NODE);
|
||||
nodeToItem.insert(nodePtr, nodeItem);
|
||||
itemToNode.insert(nodeItem, nodePtr);
|
||||
ui->treeDBC->addTopLevelItem(nodeItem);
|
||||
@@ -499,6 +585,77 @@ void DBCMainEditor::newNode()
|
||||
dbcFile->setDirtyFlag();
|
||||
}
|
||||
|
||||
void DBCMainEditor::newNode()
|
||||
{
|
||||
newNode(QString());
|
||||
}
|
||||
|
||||
void DBCMainEditor::copyMessageToNode(DBC_NODE *parentNode, DBC_MESSAGE *source, uint newMsgId)
|
||||
{
|
||||
DBC_NODE *node = parentNode;
|
||||
if (!node) node = dbcFile->findNodeByIdx(0);
|
||||
QTreeWidgetItem *nodeItem = nullptr;
|
||||
DBC_MESSAGE msg;
|
||||
DBC_MESSAGE *msgPtr;
|
||||
|
||||
nodeItem = ui->treeDBC->currentItem();
|
||||
|
||||
msg.name = source->name;
|
||||
msg.ID = newMsgId;
|
||||
msg.len = source->len;
|
||||
msg.bgColor = source->bgColor;
|
||||
msg.fgColor = source->fgColor;
|
||||
msg.comment = source->comment;
|
||||
msg.sender = node;
|
||||
|
||||
DBC_SIGNAL *sigSource;
|
||||
int sigCount = source->sigHandler->getCount();
|
||||
|
||||
for(int i=0; i<sigCount; i++)
|
||||
{
|
||||
sigSource = source->sigHandler->findSignalByIdx(i);
|
||||
|
||||
DBC_SIGNAL sig;
|
||||
|
||||
//Does not properly handle multiplexed signals, for now
|
||||
sig.name = sigSource->name;
|
||||
sig.bias = sigSource->bias;
|
||||
sig.isMultiplexed = false; //sigSource->isMultiplexed;
|
||||
sig.isMultiplexor = false; //sigSource->isMultiplexor;
|
||||
sig.max = sigSource->max;
|
||||
sig.min = sigSource->min;
|
||||
sig.multiplexLowValue = sigSource->multiplexLowValue;
|
||||
sig.multiplexHighValue = sigSource->multiplexHighValue;
|
||||
sig.factor = sigSource->factor;
|
||||
sig.intelByteOrder = sigSource->intelByteOrder;
|
||||
sig.parentMessage = &msg;
|
||||
sig.multiplexParent = nullptr; //need to learn about multiplexed signals and track them when copying
|
||||
sig.receiver = node;
|
||||
sig.signalSize = sigSource->signalSize;
|
||||
sig.startBit = sigSource->startBit;
|
||||
sig.valType = sigSource->valType;
|
||||
|
||||
|
||||
sig.parentMessage = &msg;
|
||||
msg.sigHandler->addSignal(sig);
|
||||
}
|
||||
|
||||
msg.sigHandler->sort();
|
||||
|
||||
dbcFile->messageHandler->addMessage(msg);
|
||||
msgPtr = dbcFile->messageHandler->findMsgByIdx(dbcFile->messageHandler->getCount() - 1);
|
||||
QTreeWidgetItem *newMsgItem = new QTreeWidgetItem();
|
||||
QString msgInfo = Utility::formatCANID(msg.ID) + " " + msg.name;
|
||||
if (msg.comment.count() > 0) msgInfo.append(" - ").append(msg.comment);
|
||||
newMsgItem->setText(0, msgInfo);
|
||||
newMsgItem->setIcon(0, messageIcon);
|
||||
newMsgItem->setData(0, Qt::UserRole, DBCItemTypes::MESG);
|
||||
messageToItem.insert(msgPtr, newMsgItem);
|
||||
itemToMessage.insert(newMsgItem, msgPtr);
|
||||
nodeItem->addChild(newMsgItem);
|
||||
//ui->treeDBC->setCurrentItem(newMsgItem);
|
||||
dbcFile->setDirtyFlag();
|
||||
}
|
||||
|
||||
//create a new message with it's parent being the node we're currently within
|
||||
void DBCMainEditor::newMessage()
|
||||
@@ -508,16 +665,19 @@ void DBCMainEditor::newMessage()
|
||||
nodeItem = ui->treeDBC->currentItem();
|
||||
int typ = nodeItem->data(0, Qt::UserRole).toInt();
|
||||
if (!nodeItem) return; //nothing selected!
|
||||
if (typ == 2)
|
||||
if (typ == DBCItemTypes::MESG)
|
||||
{
|
||||
msgItem = nodeItem;
|
||||
nodeItem = nodeItem->parent();
|
||||
}
|
||||
if (typ == 3)
|
||||
if (typ == DBCItemTypes::SIG)
|
||||
{
|
||||
msgItem = nodeItem->parent();
|
||||
nodeItem = msgItem->parent();
|
||||
}
|
||||
if (typ == DBCItemTypes::NODE){
|
||||
msgItem = nodeItem;
|
||||
}
|
||||
|
||||
//if there was a comment this will find the location of the comment and snip it out.
|
||||
QString nodeName = nodeItem->data(0, Qt::DisplayRole).toString().split(" - ")[0];
|
||||
@@ -533,8 +693,14 @@ void DBCMainEditor::newMessage()
|
||||
DBC_MESSAGE *oldMsg = dbcFile->messageHandler->findMsgByID(msgID);
|
||||
if (oldMsg)
|
||||
{
|
||||
msg.name = oldMsg->name + QString::number(randGen.bounded(1000));
|
||||
msg.name = "Msg" + QString::number(randGen.bounded(500));
|
||||
msg.ID = oldMsg->ID + 1;
|
||||
DBC_MESSAGE *overlappedMsg = dbcFile->messageHandler->findMsgByID(msg.ID);
|
||||
while (overlappedMsg)
|
||||
{
|
||||
msg.ID++;
|
||||
overlappedMsg = dbcFile->messageHandler->findMsgByID(msg.ID);
|
||||
}
|
||||
msg.len = oldMsg->len;
|
||||
msg.bgColor = oldMsg->bgColor;
|
||||
msg.fgColor = oldMsg->fgColor;
|
||||
@@ -544,14 +710,15 @@ void DBCMainEditor::newMessage()
|
||||
{
|
||||
msg.name = nodeName + "Msg" + QString::number(randGen.bounded(500));
|
||||
msg.ID = 0;
|
||||
msg.len = 0;
|
||||
msg.len = 8;
|
||||
msg.bgColor = QApplication::palette().color(QPalette::Base);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.name = nodeName + "Msg" + QString::number(randGen.bounded(500));
|
||||
msg.ID = 0;
|
||||
msg.len = 0;
|
||||
msg.len = 8;
|
||||
}
|
||||
msg.sender = node;
|
||||
|
||||
@@ -562,7 +729,7 @@ void DBCMainEditor::newMessage()
|
||||
if (msg.comment.count() > 0) msgInfo.append(" - ").append(msg.comment);
|
||||
newMsgItem->setText(0, msgInfo);
|
||||
newMsgItem->setIcon(0, messageIcon);
|
||||
newMsgItem->setData(0, Qt::UserRole, 2);
|
||||
newMsgItem->setData(0, Qt::UserRole, DBCItemTypes::MESG);
|
||||
messageToItem.insert(msgPtr, newMsgItem);
|
||||
itemToMessage.insert(newMsgItem, msgPtr);
|
||||
nodeItem->addChild(newMsgItem);
|
||||
@@ -574,27 +741,37 @@ void DBCMainEditor::newSignal()
|
||||
{
|
||||
QTreeWidgetItem *msgItem = nullptr;
|
||||
QTreeWidgetItem *sigItem = nullptr;
|
||||
QTreeWidgetItem *parentItem = nullptr;
|
||||
msgItem = ui->treeDBC->currentItem();
|
||||
parentItem = msgItem;
|
||||
if (!msgItem) return; //nothing selected!
|
||||
int typ = msgItem->data(0, Qt::UserRole).toInt();
|
||||
if (typ == 1) return; //can't add signals to a node!
|
||||
if (typ == 3)
|
||||
if (typ == DBCItemTypes::NODE) return; //can't add signals to a node!
|
||||
if (typ == DBCItemTypes::SIG)
|
||||
{
|
||||
sigItem = msgItem;
|
||||
msgItem = msgItem->parent();
|
||||
parentItem = msgItem;
|
||||
//walk up the tree to find the parent msg
|
||||
while (msgItem && msgItem->data(0, Qt::UserRole).toInt() != DBCItemTypes::MESG) msgItem = msgItem->parent();
|
||||
if (!msgItem) return; //something bad happened. abort.
|
||||
}
|
||||
|
||||
QString idString = msgItem->text(0).split(" ")[0];
|
||||
int msgID = static_cast<uint32_t>(Utility::ParseStringToNum(idString));
|
||||
DBC_MESSAGE *msg = dbcFile->messageHandler->findMsgByID(msgID);
|
||||
if (!msg) return; //null pointers are a bummer. Do not follow them.
|
||||
DBC_SIGNAL sig;
|
||||
DBC_SIGNAL *sigPtr;
|
||||
if (sigItem)
|
||||
{
|
||||
DBC_SIGNAL *oldSig = msg->sigHandler->findSignalByName(sigItem->text(0).split(" ")[0]);
|
||||
QString txt = sigItem->text(0);
|
||||
if (txt.startsWith('(')) txt = txt.split(" ")[1]; //if it was a multiplexed signal we need to ignore that part and still grab sig name
|
||||
else txt = txt.split(" ")[0];
|
||||
DBC_SIGNAL *oldSig = msg->sigHandler->findSignalByName(txt);
|
||||
if (oldSig)
|
||||
{
|
||||
sig = *oldSig; //copy it all over
|
||||
sig = *oldSig;
|
||||
sig.name = sig.name + QString::number(randGen.bounded(100));
|
||||
}
|
||||
else
|
||||
@@ -612,16 +789,15 @@ void DBCMainEditor::newSignal()
|
||||
msg->sigHandler->addSignal(sig);
|
||||
sigPtr = msg->sigHandler->findSignalByIdx(msg->sigHandler->getCount() - 1);
|
||||
QTreeWidgetItem *newSigItem = new QTreeWidgetItem();
|
||||
QString sigInfo = sig.name;
|
||||
if (sig.comment.count() > 0) sigInfo.append(" - ").append(sig.comment);
|
||||
QString sigInfo = createSignalText(&sig);
|
||||
newSigItem->setText(0, sigInfo);
|
||||
if (sig.isMultiplexed) newSigItem->setIcon(0, multiplexedSignalIcon);
|
||||
else if (sig.isMultiplexor) newSigItem->setIcon(0, multiplexorSignalIcon);
|
||||
else newSigItem->setIcon(0, signalIcon);
|
||||
newSigItem->setData(0, Qt::UserRole, 3);
|
||||
newSigItem->setData(0, Qt::UserRole, DBCItemTypes::SIG);
|
||||
signalToItem.insert(sigPtr, newSigItem);
|
||||
itemToSignal.insert(newSigItem, sigPtr);
|
||||
msgItem->addChild(newSigItem);
|
||||
parentItem->addChild(newSigItem);
|
||||
ui->treeDBC->setCurrentItem(newSigItem);
|
||||
dbcFile->setDirtyFlag();
|
||||
}
|
||||
@@ -631,7 +807,7 @@ void DBCMainEditor::deleteCurrentTreeItem()
|
||||
{
|
||||
QTreeWidgetItem *currItem = ui->treeDBC->currentItem();
|
||||
int typ = currItem->data(0, Qt::UserRole).toInt();
|
||||
QString idString;
|
||||
QString idString, columnText;
|
||||
int msgID;
|
||||
DBC_MESSAGE *msg;
|
||||
DBC_NODE *node;
|
||||
@@ -641,8 +817,9 @@ void DBCMainEditor::deleteCurrentTreeItem()
|
||||
|
||||
switch (typ)
|
||||
{
|
||||
case 1: //deleting a node cascades deletion down to messages and signals
|
||||
node = dbcFile->findNodeByName(currItem->text(0));
|
||||
case DBCItemTypes::NODE: //deleting a node cascades deletion down to messages and signals
|
||||
columnText = currItem->text(0);
|
||||
node = dbcFile->findNodeByNameAndComment(columnText);
|
||||
if (!node) return;
|
||||
for (int x = 0; x < dbcFile->messageHandler->getCount(); x++)
|
||||
{
|
||||
@@ -668,7 +845,7 @@ void DBCMainEditor::deleteCurrentTreeItem()
|
||||
}
|
||||
|
||||
break;
|
||||
case 2: //cascades to removing all signals too.
|
||||
case DBCItemTypes::MESG: //cascades to removing all signals too.
|
||||
idString = currItem->text(0).split(" ")[0];
|
||||
msgID = static_cast<uint32_t>(Utility::ParseStringToNum(idString));
|
||||
msg = dbcFile->messageHandler->findMsgByID(msgID);
|
||||
@@ -689,7 +866,7 @@ void DBCMainEditor::deleteCurrentTreeItem()
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3: //no cascade, just this one signal.
|
||||
case DBCItemTypes::SIG: //no cascade, just this one signal.
|
||||
confirmDialog = QMessageBox::question(this, "Really?", "Are you sure you want to delete this signal?",
|
||||
QMessageBox::Yes|QMessageBox::No);
|
||||
if (confirmDialog == QMessageBox::Yes)
|
||||
@@ -702,7 +879,7 @@ void DBCMainEditor::deleteCurrentTreeItem()
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "WTF, could not find the signal in the map. That should not happen.";
|
||||
qDebug() << "Could not find the signal in the map. That should not happen.";
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -720,7 +897,21 @@ void DBCMainEditor::deleteNode(DBC_NODE *node)
|
||||
int numItems = dbcFile->messageHandler->getCount();
|
||||
for (int i = numItems - 1; i > -1; i--)
|
||||
{
|
||||
if (dbcFile->messageHandler->findMsgByIdx(i)->sender == node) deleteMessage(dbcFile->messageHandler->findMsgByIdx(i));
|
||||
DBC_MESSAGE *msg = dbcFile->messageHandler->findMsgByIdx(i);
|
||||
if (msg->sender == node) deleteMessage(dbcFile->messageHandler->findMsgByIdx(i));
|
||||
//also, each signal has a receiver field that references the nodes. It was probably stupid to store
|
||||
//pointers to node structures in signals but that's how it is currently. Need to iterate over all
|
||||
//signals in all messages and set the receiver field to Vector__XXX if the old receiver node was this one.
|
||||
else //still check for signals with receiver set to this node
|
||||
{
|
||||
DBC_NODE *unset_node = dbcFile->findNodeByName("Vector__XXX");
|
||||
int numSigs = msg->sigHandler->getCount();
|
||||
for (int j = numSigs - 1; j > -1; j--)
|
||||
{
|
||||
DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(j);
|
||||
if (sig->receiver == node) sig->receiver = unset_node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodeToItem.remove(node);
|
||||
@@ -771,6 +962,6 @@ void DBCMainEditor::deleteSignal(DBC_SIGNAL *sig)
|
||||
itemToSignal.remove(currItem);
|
||||
signalToItem.remove(sig);
|
||||
ui->treeDBC->removeItemWidget(currItem, 0);
|
||||
delete currItem;
|
||||
//delete currItem; //already removed by above remove call
|
||||
dbcFile->setDirtyFlag();
|
||||
}
|
||||
|
||||
@@ -10,12 +10,21 @@
|
||||
#include "dbcsignaleditor.h"
|
||||
#include "dbcmessageeditor.h"
|
||||
#include "dbcnodeeditor.h"
|
||||
#include "dbcnoderebaseeditor.h"
|
||||
#include "dbcnodeduplicateeditor.h"
|
||||
#include "utility.h"
|
||||
|
||||
namespace Ui {
|
||||
class DBCMainEditor;
|
||||
}
|
||||
|
||||
enum DBCItemTypes
|
||||
{
|
||||
NODE = 1,
|
||||
MESG = 2,
|
||||
SIG = 3
|
||||
};
|
||||
|
||||
class DBCMainEditor : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -32,6 +41,7 @@ public slots:
|
||||
|
||||
private slots:
|
||||
void onTreeDoubleClicked(const QModelIndex &index);
|
||||
void onTreeContextMenu(const QPoint & pos);
|
||||
void currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev);
|
||||
void onCustomMenuTree(QPoint);
|
||||
void deleteCurrentTreeItem();
|
||||
@@ -41,9 +51,13 @@ private slots:
|
||||
void handleSearch();
|
||||
void handleSearchForward();
|
||||
void handleSearchBackward();
|
||||
void newNode(QString nodeName);
|
||||
void newNode();
|
||||
void copyMessageToNode(DBC_NODE *node, DBC_MESSAGE *source, uint newMsgId);
|
||||
void newMessage();
|
||||
void newSignal();
|
||||
void newSignal();
|
||||
void onRebaseMessages();
|
||||
void onDuplicateNode();
|
||||
|
||||
private:
|
||||
Ui::DBCMainEditor *ui;
|
||||
@@ -52,6 +66,8 @@ private:
|
||||
DBCSignalEditor *sigEditor;
|
||||
DBCMessageEditor *msgEditor;
|
||||
DBCNodeEditor *nodeEditor;
|
||||
DBCNodeRebaseEditor *nodeRebaseEditor;
|
||||
DBCNodeDuplicateEditor *nodeDuplicateEditor;
|
||||
DBCFile *dbcFile;
|
||||
int fileIdx;
|
||||
QIcon nodeIcon;
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
#include "dbcnodeduplicateeditor.h"
|
||||
#include "ui_dbcnodeduplicateeditor.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QKeyEvent>
|
||||
#include <QColorDialog>
|
||||
#include <QMessageBox>
|
||||
#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<DBC_MESSAGE*> 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<QKeyEvent *>(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<DBC_MESSAGE*> messagesForNode = dbcFile->messageHandler->findMsgsByNode(dbcNode);
|
||||
lowestMsgId = 0xFFFFFFFF;
|
||||
|
||||
if(messagesForNode.count() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i=0; i<messagesForNode.count(); i++)
|
||||
{
|
||||
if(messagesForNode[i]->ID < lowestMsgId)
|
||||
lowestMsgId = messagesForNode[i]->ID;
|
||||
}
|
||||
|
||||
ui->lineOriginalBaseId->setText(Utility::formatCANID(lowestMsgId & 0x1FFFFFFFul));
|
||||
ui->lineNodeName->setText(dbcNode->name + QString("_Copy"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef DBCNODEDUPLICATEEDITOR_H
|
||||
#define DBCNODEDUPLICATEEDITOR_H
|
||||
|
||||
#include <QDialog>
|
||||
#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
|
||||
@@ -111,8 +111,11 @@ void DBCNodeEditor::showEvent(QShowEvent* event)
|
||||
|
||||
void DBCNodeEditor::refreshView()
|
||||
{
|
||||
ui->lineComment->setText(dbcNode->comment);
|
||||
ui->lineMsgName->setText(dbcNode->name);
|
||||
if(dbcNode)
|
||||
{
|
||||
ui->lineComment->setText(dbcNode->comment);
|
||||
ui->lineMsgName->setText(dbcNode->name);
|
||||
}
|
||||
|
||||
//generateSampleText();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
#include "dbcnoderebaseeditor.h"
|
||||
#include "ui_dbcnoderebaseeditor.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QKeyEvent>
|
||||
#include <QColorDialog>
|
||||
#include <QMessageBox>
|
||||
#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<DBC_MESSAGE*> 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<QKeyEvent *>(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<DBC_MESSAGE*> messagesForNode = dbcFile->messageHandler->findMsgsByNode(dbcNode);
|
||||
lowestMsgId = 0xFFFFFFFF;
|
||||
|
||||
if(messagesForNode.count() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i=0; i<messagesForNode.count(); i++)
|
||||
{
|
||||
if(messagesForNode[i]->ID < lowestMsgId)
|
||||
lowestMsgId = messagesForNode[i]->ID;
|
||||
}
|
||||
|
||||
ui->lineOriginalBaseId->setText(Utility::formatCANID(lowestMsgId & 0x1FFFFFFFul));
|
||||
ui->lineNodeName->setText(dbcNode->name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef DBCNODEREBASEEDITOR_H
|
||||
#define DBCNODEREBASEEDITOR_H
|
||||
|
||||
#include <QDialog>
|
||||
#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
|
||||
@@ -37,7 +37,9 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
|
||||
ui->bitfield->setMode(GridMode::SIGNAL_VIEW);
|
||||
|
||||
connect(ui->bitfield, SIGNAL(gridClicked(int,int)), this, SLOT(bitfieldClicked(int,int)));
|
||||
connect(ui->bitfield, SIGNAL(gridClicked(int)), this, SLOT(bitfieldLeftClicked(int)));
|
||||
connect(ui->bitfield, SIGNAL(gridRightClicked(int)), this, SLOT(bitfieldRightClicked(int)));
|
||||
|
||||
connect(ui->valuesTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomMenuValues(QPoint)));
|
||||
ui->valuesTable->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->valuesTable, SIGNAL(cellChanged(int,int)), this, SLOT(onValuesCellChanged(int,int)));
|
||||
@@ -47,18 +49,29 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == nullptr) return;
|
||||
if (currentSignal->intelByteOrder != ui->cbIntelFormat->isChecked()) dbcFile->setDirtyFlag();
|
||||
currentSignal->intelByteOrder = ui->cbIntelFormat->isChecked();
|
||||
fillSignalForm(currentSignal);
|
||||
if (currentSignal->intelByteOrder != ui->cbIntelFormat->isChecked())
|
||||
{
|
||||
dbcFile->setDirtyFlag();
|
||||
pushToUndoBuffer();
|
||||
currentSignal->intelByteOrder = ui->cbIntelFormat->isChecked();
|
||||
//fillSignalForm(currentSignal);
|
||||
refreshBitGrid();
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->comboReceiver, &QComboBox::currentTextChanged,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == nullptr) return;
|
||||
if (inhibitMsgProc) return;
|
||||
|
||||
DBC_NODE *node = dbcFile->findNodeByName(ui->comboReceiver->currentText());
|
||||
if (currentSignal->receiver != node) dbcFile->setDirtyFlag();
|
||||
currentSignal->receiver = node;
|
||||
if (currentSignal->receiver != node)
|
||||
{
|
||||
dbcFile->setDirtyFlag();
|
||||
pushToUndoBuffer();
|
||||
currentSignal->receiver = node;
|
||||
}
|
||||
});
|
||||
connect(ui->comboType, &QComboBox::currentTextChanged,
|
||||
[=]()
|
||||
@@ -67,27 +80,66 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
switch (ui->comboType->currentIndex())
|
||||
{
|
||||
case 0:
|
||||
currentSignal->valType = UNSIGNED_INT;
|
||||
if (currentSignal->valType != UNSIGNED_INT)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
currentSignal->valType = UNSIGNED_INT;
|
||||
dbcFile->setDirtyFlag();
|
||||
fillSignalForm(currentSignal);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
currentSignal->valType = SIGNED_INT;
|
||||
if (currentSignal->valType != SIGNED_INT)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
currentSignal->valType = SIGNED_INT;
|
||||
dbcFile->setDirtyFlag();
|
||||
fillSignalForm(currentSignal);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
currentSignal->valType = SP_FLOAT;
|
||||
if (currentSignal->startBit > 39) currentSignal->startBit = 39;
|
||||
currentSignal->signalSize = 32;
|
||||
if (currentSignal->valType != SP_FLOAT)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
currentSignal->valType = SP_FLOAT;
|
||||
dbcFile->setDirtyFlag();
|
||||
if (dbcMessage) //if we have a good msg reference we can use it to get the # of bytes expected.
|
||||
{
|
||||
int maxBit = ((dbcMessage->len * 8) - 32 + 7);
|
||||
if (maxBit < 0) maxBit = 0;
|
||||
if (currentSignal->startBit > maxBit) currentSignal->startBit = maxBit;
|
||||
}
|
||||
else if (currentSignal->startBit > 39) currentSignal->startBit = 39;
|
||||
currentSignal->signalSize = 32;
|
||||
fillSignalForm(currentSignal);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
currentSignal->valType = DP_FLOAT;
|
||||
currentSignal->startBit = 7; //has to be!
|
||||
currentSignal->signalSize = 64;
|
||||
if (currentSignal->valType != DP_FLOAT)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
currentSignal->valType = DP_FLOAT;
|
||||
dbcFile->setDirtyFlag();
|
||||
if (dbcMessage)
|
||||
{
|
||||
int maxBit = ((dbcMessage->len * 8) - 64 + 7);
|
||||
if (currentSignal->startBit > maxBit) currentSignal->startBit = maxBit;
|
||||
}
|
||||
else currentSignal->startBit = 7; //has to be!
|
||||
currentSignal->signalSize = 64;
|
||||
fillSignalForm(currentSignal);
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
currentSignal->valType = STRING;
|
||||
break;
|
||||
if (currentSignal->valType != STRING)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
currentSignal->valType = STRING;
|
||||
dbcFile->setDirtyFlag();
|
||||
fillSignalForm(currentSignal);
|
||||
}
|
||||
break;
|
||||
}
|
||||
dbcFile->setDirtyFlag();
|
||||
fillSignalForm(currentSignal);
|
||||
});
|
||||
connect(ui->txtBias, &QLineEdit::editingFinished,
|
||||
[=]()
|
||||
@@ -98,8 +150,12 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
temp = ui->txtBias->text().toDouble(&result);
|
||||
if (result)
|
||||
{
|
||||
if (currentSignal->bias != temp) dbcFile->setDirtyFlag();
|
||||
currentSignal->bias = temp;
|
||||
if (currentSignal->bias != temp)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
currentSignal->bias = temp;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -112,8 +168,12 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
temp = ui->txtMaxVal->text().toDouble(&result);
|
||||
if (result)
|
||||
{
|
||||
if (currentSignal->max != temp) dbcFile->setDirtyFlag();
|
||||
currentSignal->max = temp;
|
||||
if (currentSignal->max != temp)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
currentSignal->max = temp;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -126,10 +186,15 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
temp = ui->txtMinVal->text().toDouble(&result);
|
||||
if (result)
|
||||
{
|
||||
if (currentSignal->min != temp) dbcFile->setDirtyFlag();
|
||||
currentSignal->min = temp;
|
||||
if (currentSignal->min != temp)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
currentSignal->min = temp;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->txtScale, &QLineEdit::editingFinished,
|
||||
[=]()
|
||||
{
|
||||
@@ -139,26 +204,40 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
temp = ui->txtScale->text().toDouble(&result);
|
||||
if (result)
|
||||
{
|
||||
if (currentSignal->factor != temp) dbcFile->setDirtyFlag();
|
||||
currentSignal->factor = temp;
|
||||
if (currentSignal->factor != temp)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
currentSignal->factor = temp;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->txtComment, &QLineEdit::editingFinished,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == nullptr) return;
|
||||
if (currentSignal->comment != ui->txtComment->text().simplified().replace(' ','_')) dbcFile->setDirtyFlag();
|
||||
currentSignal->comment = ui->txtComment->text().simplified().replace(' ', '_');
|
||||
emit updatedTreeInfo(currentSignal);
|
||||
if (currentSignal->comment != ui->txtComment->text().simplified().replace(' ','_'))
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
currentSignal->comment = ui->txtComment->text().simplified().replace(' ', '_');
|
||||
emit updatedTreeInfo(currentSignal);
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->txtUnitName, &QLineEdit::editingFinished,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == nullptr) return;
|
||||
if (currentSignal->unitName != ui->txtUnitName->text().simplified().replace(' ','_')) dbcFile->setDirtyFlag();
|
||||
currentSignal->unitName = ui->txtUnitName->text().simplified().replace(' ', '_');
|
||||
if (currentSignal->unitName != ui->txtUnitName->text().simplified().replace(' ','_'))
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
currentSignal->unitName = ui->txtUnitName->text().simplified().replace(' ', '_');
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->txtBitLength, &QLineEdit::textChanged,
|
||||
[=]()
|
||||
{
|
||||
@@ -166,21 +245,40 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
int temp;
|
||||
temp = Utility::ParseStringToNum(ui->txtBitLength->text());
|
||||
if (temp < 1) return;
|
||||
if (temp > 64) return;
|
||||
if (currentSignal->signalSize != temp) dbcFile->setDirtyFlag();
|
||||
if (currentSignal->valType != SP_FLOAT && currentSignal->valType != DP_FLOAT)
|
||||
if (dbcMessage)
|
||||
{
|
||||
if (temp > (int)(dbcMessage->len * 8)) return;
|
||||
}
|
||||
else if (temp > 64) return;
|
||||
|
||||
if (currentSignal->valType == SP_FLOAT) temp = 32;
|
||||
if (currentSignal->valType == DP_FLOAT) temp = 64;
|
||||
|
||||
if (currentSignal->signalSize != temp)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
currentSignal->signalSize = temp;
|
||||
fillSignalForm(currentSignal);
|
||||
//fillSignalForm(currentSignal);
|
||||
refreshBitGrid();
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->txtName, &QLineEdit::editingFinished,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == nullptr) return;
|
||||
QString tempNameStr = ui->txtName->text().simplified().replace(' ', '_');
|
||||
if (currentSignal->name != tempNameStr) dbcFile->setDirtyFlag();
|
||||
if (tempNameStr.length() > 0) currentSignal->name = tempNameStr;
|
||||
//need to update the tree too.
|
||||
emit updatedTreeInfo(currentSignal);
|
||||
if (tempNameStr.length() == 0) return; //can't do that!
|
||||
if (currentSignal->name != tempNameStr)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
currentSignal->name = tempNameStr;
|
||||
refreshBitGrid();
|
||||
//need to update the tree too.
|
||||
emit updatedTreeInfo(currentSignal);
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->txtMultiplexLow, &QLineEdit::editingFinished,
|
||||
@@ -189,85 +287,113 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
if (currentSignal == nullptr) return;
|
||||
int temp;
|
||||
temp = Utility::ParseStringToNum(ui->txtMultiplexLow->text());
|
||||
if (currentSignal->multiplexLowValue != temp) dbcFile->setDirtyFlag();
|
||||
//TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return
|
||||
currentSignal->multiplexLowValue = temp;
|
||||
if (currentSignal->multiplexLowValue != temp)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
//TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return
|
||||
currentSignal->multiplexLowValue = temp;
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->txtMultiplexHigh, &QLineEdit::editingFinished,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == nullptr) return;
|
||||
if (!currentSignal) return;
|
||||
int temp;
|
||||
temp = Utility::ParseStringToNum(ui->txtMultiplexHigh->text());
|
||||
if (currentSignal->multiplexHighValue != temp) dbcFile->setDirtyFlag();
|
||||
//TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return
|
||||
currentSignal->multiplexHighValue = temp;
|
||||
if (currentSignal->multiplexHighValue != temp)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
//TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return
|
||||
currentSignal->multiplexHighValue = temp;
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->rbExtended, &QRadioButton::toggled,
|
||||
[=](bool state)
|
||||
{
|
||||
if (state && currentSignal) //signal is now set as an extended multiplex/multiplexor
|
||||
if (!currentSignal) return;
|
||||
if (!state) return; //we only need to handle the case where it is true
|
||||
|
||||
//only do anything if this is different from the current state. It should be because we're in a toggle event but let's be sure
|
||||
if (!currentSignal->isMultiplexed || !currentSignal->isMultiplexor)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
currentSignal->isMultiplexed = true;
|
||||
currentSignal->isMultiplexor = true;
|
||||
//an extended multi signal cannot be the root multiplexor for a message so make sure to remove it if it was.
|
||||
if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = nullptr;
|
||||
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
|
||||
fillSignalForm(currentSignal);
|
||||
}
|
||||
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
|
||||
dbcFile->setDirtyFlag();
|
||||
});
|
||||
|
||||
connect(ui->rbMultiplexed, &QRadioButton::toggled,
|
||||
[=](bool state)
|
||||
{
|
||||
if (state && currentSignal) //signal is now set as a multiplexed signal
|
||||
if (!currentSignal) return;
|
||||
if (!state) return; //we only need to handle the case where it is true
|
||||
|
||||
//only do anything if this is different from the current state. It should be because we're in a toggle event but let's be sure
|
||||
if (!currentSignal->isMultiplexed || currentSignal->isMultiplexor)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
currentSignal->isMultiplexed = true;
|
||||
currentSignal->isMultiplexor = false;
|
||||
//if the set multiplexor for the message was this signal then clear it
|
||||
if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = nullptr;
|
||||
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
|
||||
fillSignalForm(currentSignal);
|
||||
}
|
||||
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
|
||||
dbcFile->setDirtyFlag();
|
||||
});
|
||||
|
||||
connect(ui->rbMultiplexor, &QRadioButton::toggled,
|
||||
[=](bool state)
|
||||
{
|
||||
if (state && currentSignal) //signal is now set as a multiplexed signal
|
||||
if (!currentSignal) return;
|
||||
if (!state) return; //we only need to handle the case where it is true
|
||||
|
||||
if (currentSignal->isMultiplexed || !currentSignal->isMultiplexor)
|
||||
{
|
||||
//don't allow this signal to be a multiplexor if there is already one for this message.
|
||||
//if (dbcMessage->multiplexorSignal != currentSignal && dbcMessage->multiplexorSignal != nullptr) return; //I spoke too soon above...
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
currentSignal->isMultiplexed = false;
|
||||
currentSignal->isMultiplexor = true;
|
||||
//we just set that this is the multiplexor so update the message to show that as well.
|
||||
dbcMessage->multiplexorSignal = currentSignal;
|
||||
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
|
||||
fillSignalForm(currentSignal);
|
||||
}
|
||||
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
|
||||
dbcFile->setDirtyFlag();
|
||||
});
|
||||
|
||||
connect(ui->rbNotMulti, &QRadioButton::toggled,
|
||||
[=](bool state)
|
||||
{
|
||||
if (state && currentSignal) //signal is now set as a multiplexed signal
|
||||
if (!currentSignal) return;
|
||||
if (!state) return; //we only need to handle the case where it is true
|
||||
|
||||
if (currentSignal->isMultiplexed || currentSignal->isMultiplexor)
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
currentSignal->isMultiplexed = false;
|
||||
currentSignal->isMultiplexor = false;
|
||||
if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = nullptr;
|
||||
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
|
||||
fillSignalForm(currentSignal);
|
||||
}
|
||||
ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed);
|
||||
ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed);
|
||||
dbcFile->setDirtyFlag();
|
||||
});
|
||||
|
||||
connect(ui->cbMultiplexParent, &QComboBox::textActivated,
|
||||
@@ -275,17 +401,20 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
{
|
||||
if (currentSignal == nullptr) return;
|
||||
if (inhibitMsgProc) return;
|
||||
|
||||
//qDebug() << "Curr text: :" << ui->cbMultiplexParent->currentText();
|
||||
//try to look up the signal that we're set to now, remove this signal from existing children list
|
||||
//add it to this one, update this signal's parent multiplexor
|
||||
DBC_SIGNAL *newSig = dbcMessage->sigHandler->findSignalByName(ui->cbMultiplexParent->currentText());
|
||||
DBC_SIGNAL *oldParent = currentSignal->multiplexParent;
|
||||
if (newSig)
|
||||
if (newSig && oldParent && (newSig != oldParent))
|
||||
{
|
||||
pushToUndoBuffer();
|
||||
dbcFile->setDirtyFlag();
|
||||
oldParent->multiplexedChildren.removeOne(currentSignal);
|
||||
currentSignal->multiplexParent = newSig;
|
||||
newSig->multiplexedChildren.append(currentSignal);
|
||||
dbcFile->setDirtyFlag();
|
||||
refreshBitGrid();
|
||||
emit updatedTreeInfo(currentSignal);
|
||||
}
|
||||
});
|
||||
@@ -314,6 +443,12 @@ bool DBCSignalEditor::eventFilter(QObject *obj, QEvent *event)
|
||||
case Qt::Key_F1:
|
||||
HelpWindow::getRef()->showHelp("signaleditor.md");
|
||||
break;
|
||||
case Qt::Key_Z:
|
||||
if (keyEvent->modifiers() == Qt::ControlModifier)
|
||||
{
|
||||
popFromUndoBuffer();
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
@@ -373,18 +508,6 @@ void DBCSignalEditor::showEvent(QShowEvent* event)
|
||||
|
||||
fillSignalForm(currentSignal);
|
||||
fillValueTable(currentSignal);
|
||||
|
||||
ui->bitfield->clearSignalNames();
|
||||
for (int x = 0; x < dbcMessage->sigHandler->getCount(); x++)
|
||||
{
|
||||
DBC_SIGNAL *sig = dbcMessage->sigHandler->findSignalByIdx(x);
|
||||
//only set a signal name for signals which match multiplexparent with our currentsignal
|
||||
if (!sig->multiplexParent || ((sig->multiplexParent == currentSignal->multiplexParent) && (sig->multiplexHighValue == currentSignal->multiplexHighValue)) )
|
||||
{
|
||||
ui->bitfield->setSignalNames(x, sig->name);
|
||||
qDebug() << sig->name << sig->multiplexParent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DBCSignalEditor::refreshView()
|
||||
@@ -448,12 +571,14 @@ void DBCSignalEditor::deleteCurrentValue()
|
||||
/* WARNING: fillSignalForm can be called recursively since it is in the listener of cbIntelFormat */
|
||||
void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig)
|
||||
{
|
||||
unsigned char bitpattern[8];
|
||||
//sanity checks
|
||||
if (!dbcMessage) return;
|
||||
if (!dbcMessage->sigHandler) return;
|
||||
|
||||
inhibitMsgProc = true;
|
||||
|
||||
if (sig == nullptr) {
|
||||
ui->groupBox->setEnabled(false);
|
||||
//ui->groupBox->setEnabled(false);
|
||||
ui->txtName->setText("");
|
||||
ui->txtBias->setText("");
|
||||
ui->txtBitLength->setText("");
|
||||
@@ -467,9 +592,6 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig)
|
||||
ui->rbMultiplexed->setChecked(false);
|
||||
ui->rbMultiplexor->setChecked(false);
|
||||
ui->rbNotMulti->setChecked(true);
|
||||
memset(bitpattern, 0, 8); //clear it out
|
||||
ui->bitfield->setReference(bitpattern, false);
|
||||
ui->bitfield->updateData(bitpattern, true);
|
||||
ui->comboReceiver->setCurrentIndex(0);
|
||||
ui->comboType->setCurrentIndex(0);
|
||||
inhibitMsgProc = false;
|
||||
@@ -477,9 +599,8 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig)
|
||||
}
|
||||
|
||||
/* we have a signal */
|
||||
ui->groupBox->setEnabled(true);
|
||||
//ui->groupBox->setEnabled(true);
|
||||
|
||||
generateUsedBits();
|
||||
ui->txtName->setText(sig->name);
|
||||
ui->txtBias->setText(QString::number(sig->bias));
|
||||
ui->txtBitLength->setText(QString::number(sig->signalSize));
|
||||
@@ -513,7 +634,9 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig)
|
||||
DBC_SIGNAL *sig_iter = dbcMessage->sigHandler->findSignalByIdx(i);
|
||||
if (sig_iter && sig_iter->isMultiplexor && (sig_iter != sig))
|
||||
{
|
||||
ui->cbMultiplexParent->addItem(sig_iter->name);
|
||||
//only add this entry if there are no other entries with that name yet
|
||||
if (ui->cbMultiplexParent->findText(sig_iter->name) == -1)
|
||||
ui->cbMultiplexParent->addItem(sig_iter->name);
|
||||
if (sig->multiplexParent == sig_iter) ui->cbMultiplexParent->setCurrentIndex(ui->cbMultiplexParent->count() - 1);
|
||||
}
|
||||
}
|
||||
@@ -522,42 +645,6 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig)
|
||||
ui->txtMultiplexHigh->setEnabled(sig->isMultiplexed);
|
||||
ui->cbMultiplexParent->setEnabled(sig->isMultiplexed);
|
||||
|
||||
memset(bitpattern, 0, 8); //clear it out first.
|
||||
|
||||
int startBit, endBit;
|
||||
|
||||
startBit = sig->startBit;
|
||||
|
||||
bitpattern[startBit / 8] |= 1 << (startBit % 8); //make the start bit a different color to set it apart
|
||||
ui->bitfield->setReference(bitpattern, false);
|
||||
|
||||
if (sig->intelByteOrder)
|
||||
{
|
||||
endBit = startBit + sig->signalSize - 1;
|
||||
if (startBit < 0) startBit = 0;
|
||||
if (endBit > 63) endBit = 63;
|
||||
for (int y = startBit; y <= endBit; y++)
|
||||
{
|
||||
int byt = y / 8;
|
||||
bitpattern[byt] |= 1 << (y % 8);
|
||||
}
|
||||
}
|
||||
else //big endian / motorola format
|
||||
{
|
||||
//much more irritating than the intel version...
|
||||
int size = sig->signalSize;
|
||||
while (size > 0)
|
||||
{
|
||||
int byt = startBit / 8;
|
||||
bitpattern[byt] |= 1 << (startBit % 8);
|
||||
size--;
|
||||
if ((startBit % 8) == 0) startBit += 15;
|
||||
else startBit--;
|
||||
if (startBit > 63) startBit = 63;
|
||||
}
|
||||
}
|
||||
|
||||
ui->bitfield->updateData(bitpattern, true);
|
||||
ui->cbIntelFormat->setChecked(sig->intelByteOrder);
|
||||
|
||||
switch (sig->valType)
|
||||
@@ -588,9 +675,71 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig)
|
||||
}
|
||||
}
|
||||
|
||||
refreshBitGrid();
|
||||
|
||||
inhibitMsgProc = false;
|
||||
}
|
||||
|
||||
void DBCSignalEditor::refreshBitGrid()
|
||||
{
|
||||
unsigned char bitpattern[64];
|
||||
|
||||
memset(bitpattern, 0, 64); //clear it out
|
||||
ui->bitfield->setReference(bitpattern, false);
|
||||
ui->bitfield->updateData(bitpattern, true);
|
||||
|
||||
ui->bitfield->clearSignalNames();
|
||||
for (int x = 0; x < dbcMessage->sigHandler->getCount(); x++)
|
||||
{
|
||||
DBC_SIGNAL *sig = dbcMessage->sigHandler->findSignalByIdx(x);
|
||||
//only set a signal name for signals which match multiplexparent with our currentsignal
|
||||
if (!sig->multiplexParent || ((sig->multiplexParent == currentSignal->multiplexParent) && (sig->multiplexHighValue == currentSignal->multiplexHighValue)) )
|
||||
{
|
||||
ui->bitfield->setSignalNames(x, sig->name);
|
||||
//qDebug() << sig->name << sig->multiplexParent;
|
||||
}
|
||||
}
|
||||
|
||||
generateUsedBits();
|
||||
memset(bitpattern, 0, 64); //clear it out first.
|
||||
|
||||
int startBit, endBit;
|
||||
|
||||
startBit = currentSignal->startBit;
|
||||
|
||||
bitpattern[startBit / 8] |= 1 << (startBit % 8); //make the start bit a different color to set it apart
|
||||
ui->bitfield->setReference(bitpattern, false);
|
||||
|
||||
if (currentSignal->intelByteOrder)
|
||||
{
|
||||
endBit = startBit + currentSignal->signalSize - 1;
|
||||
if (startBit < 0) startBit = 0;
|
||||
if (endBit > 511) endBit = 511;
|
||||
for (int y = startBit; y <= endBit; y++)
|
||||
{
|
||||
int byt = y / 8;
|
||||
bitpattern[byt] |= 1 << (y % 8);
|
||||
}
|
||||
}
|
||||
else //big endian / motorola format
|
||||
{
|
||||
//much more irritating than the intel version...
|
||||
int size = currentSignal->signalSize;
|
||||
while (size > 0)
|
||||
{
|
||||
int byt = startBit / 8;
|
||||
bitpattern[byt] |= 1 << (startBit % 8);
|
||||
size--;
|
||||
if ((startBit % 8) == 0) startBit += 15;
|
||||
else startBit--;
|
||||
if (startBit > 511) startBit = 511;
|
||||
}
|
||||
}
|
||||
|
||||
ui->bitfield->updateData(bitpattern, true);
|
||||
|
||||
}
|
||||
|
||||
/* fillValueTable also handles "enabled" state */
|
||||
void DBCSignalEditor::fillValueTable(DBC_SIGNAL *sig)
|
||||
{
|
||||
@@ -625,26 +774,68 @@ void DBCSignalEditor::fillValueTable(DBC_SIGNAL *sig)
|
||||
inhibitCellChanged = false;
|
||||
}
|
||||
|
||||
void DBCSignalEditor::bitfieldClicked(int x, int y)
|
||||
//Left clicking the grid sets the "starting bit" for the current signal
|
||||
void DBCSignalEditor::bitfieldLeftClicked(int bit)
|
||||
{
|
||||
int bit = (7 - x) + (y * 8);
|
||||
if (currentSignal == nullptr) return;
|
||||
|
||||
pushToUndoBuffer();
|
||||
currentSignal->startBit = bit;
|
||||
if (currentSignal->valType == SP_FLOAT)
|
||||
{
|
||||
if (currentSignal->startBit > 31) currentSignal->startBit = 39;
|
||||
if (dbcMessage)
|
||||
{
|
||||
int maxBit = ((dbcMessage->len * 8) - 32 + 7);
|
||||
if (maxBit < 0) maxBit = 0;
|
||||
if (currentSignal->startBit > maxBit) currentSignal->startBit = maxBit;
|
||||
}
|
||||
else if (currentSignal->startBit > 31) currentSignal->startBit = 39;
|
||||
}
|
||||
|
||||
if (currentSignal->valType == DP_FLOAT)
|
||||
currentSignal->startBit = 7;
|
||||
{
|
||||
if (dbcMessage)
|
||||
{
|
||||
int maxBit = ((dbcMessage->len * 8) - 64 + 7);
|
||||
if (maxBit < 0) maxBit = 0;
|
||||
if (currentSignal->startBit > maxBit) currentSignal->startBit = maxBit;
|
||||
}
|
||||
else currentSignal->startBit = 7;
|
||||
}
|
||||
fillSignalForm(currentSignal);
|
||||
}
|
||||
|
||||
//Right clicking the grid starts editing on whichever signal currently "owns" that bit.
|
||||
//If there is no other signal then nothing happens (right now).
|
||||
//Would be possible to create a new signal in that case
|
||||
void DBCSignalEditor::bitfieldRightClicked(int bit)
|
||||
{
|
||||
//will return -1 if there is no signal there. Otherwise, returns signal number
|
||||
//which is quite luckily also the index into the signal handler table
|
||||
int sigNum = ui->bitfield->getUsedSignalNum(bit);
|
||||
if (sigNum < 0) return;
|
||||
|
||||
pushToUndoBuffer(); // undo to resume editing the previous signal
|
||||
|
||||
currentSignal = dbcMessage->sigHandler->findSignalByIdx(sigNum);
|
||||
|
||||
if (currentSignal)
|
||||
{
|
||||
fillSignalForm(currentSignal);
|
||||
fillValueTable(currentSignal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DBCSignalEditor::generateUsedBits()
|
||||
{
|
||||
uint8_t usedBits[8] = {0,0,0,0,0,0,0,0};
|
||||
uint8_t usedBits[64];
|
||||
int startBit, endBit;
|
||||
|
||||
memset(usedBits, 0, 64);
|
||||
|
||||
if (!dbcMessage || !dbcMessage->sigHandler) return;
|
||||
|
||||
for (int x = 0; x < dbcMessage->sigHandler->getCount(); x++)
|
||||
{
|
||||
DBC_SIGNAL *sig = dbcMessage->sigHandler->findSignalByIdx(x);
|
||||
@@ -663,7 +854,8 @@ void DBCSignalEditor::generateUsedBits()
|
||||
{
|
||||
endBit = startBit + sig->signalSize - 1;
|
||||
if (startBit < 0) startBit = 0;
|
||||
if (endBit > 63) endBit = 63;
|
||||
int maxBit = (dbcMessage->len * 8) - 1;
|
||||
if (endBit > maxBit) endBit = maxBit;
|
||||
for (int y = startBit; y <= endBit; y++)
|
||||
{
|
||||
int byt = y / 8;
|
||||
@@ -683,9 +875,41 @@ void DBCSignalEditor::generateUsedBits()
|
||||
size--;
|
||||
if ((startBit % 8) == 0) startBit += 15;
|
||||
else startBit--;
|
||||
if (startBit > 63) startBit = 63;
|
||||
int maxBit = (dbcMessage->len * 8) - 1;
|
||||
if (startBit > maxBit) startBit = maxBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
ui->bitfield->setUsed(usedBits, false);
|
||||
ui->bitfield->setBytesToDraw(dbcMessage->len);
|
||||
}
|
||||
|
||||
//Copy the current signal in its entirety to the undo buffer. Just for safe keeping
|
||||
//Called before an edit is done to save the state so we can revert if necessary
|
||||
void DBCSignalEditor::pushToUndoBuffer()
|
||||
{
|
||||
if (!currentSignal) return;
|
||||
//store a copy of the pointer so that if we need to pop we can pop to the proper place
|
||||
currentSignal->self = currentSignal;
|
||||
undoBuffer.append(*currentSignal); //save the whole thing
|
||||
qDebug() << "Pushing to undo buffer";
|
||||
}
|
||||
|
||||
//Pop the last copy of a signal from the stack and begin editing it
|
||||
void DBCSignalEditor::popFromUndoBuffer()
|
||||
{
|
||||
if (undoBuffer.empty())
|
||||
{
|
||||
dbcFile->clearDirtyFlag(); //TODO: Don't do this. Implement per-item dirty flags.
|
||||
qDebug() << "Undo buffer empty";
|
||||
return; //can't pop if there are no stored entries!
|
||||
}
|
||||
qDebug() << "Popping undo buffer";
|
||||
DBC_SIGNAL sig = undoBuffer.back();
|
||||
undoBuffer.pop_back();
|
||||
currentSignal = sig.self; //restore the pointer
|
||||
*currentSignal = sig; //write the contents into the memory pointed to
|
||||
|
||||
fillSignalForm(currentSignal);
|
||||
fillValueTable(currentSignal);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ signals:
|
||||
void updatedTreeInfo(DBC_SIGNAL *sig);
|
||||
|
||||
private slots:
|
||||
void bitfieldClicked(int x, int y);
|
||||
void bitfieldLeftClicked(int bit);
|
||||
void bitfieldRightClicked(int bit);
|
||||
void onValuesCellChanged(int row,int col);
|
||||
void onCustomMenuValues(QPoint);
|
||||
void deleteCurrentValue();
|
||||
@@ -36,6 +37,7 @@ private:
|
||||
DBCHandler *dbcHandler;
|
||||
DBC_MESSAGE *dbcMessage;
|
||||
DBC_SIGNAL *currentSignal;
|
||||
QList<DBC_SIGNAL> undoBuffer;
|
||||
DBCFile *dbcFile;
|
||||
bool inhibitCellChanged;
|
||||
bool inhibitMsgProc;
|
||||
@@ -43,11 +45,14 @@ private:
|
||||
void fillSignalForm(DBC_SIGNAL *sig);
|
||||
void fillValueTable(DBC_SIGNAL *sig);
|
||||
void generateUsedBits();
|
||||
void refreshBitGrid();
|
||||
|
||||
void closeEvent(QCloseEvent *event);
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
void readSettings();
|
||||
void writeSettings();
|
||||
void pushToUndoBuffer();
|
||||
void popFromUndoBuffer();
|
||||
};
|
||||
|
||||
#endif // DBCSIGNALEDITOR_H
|
||||
|
||||
@@ -133,38 +133,38 @@ void FirmwareUploaderWindow::timerElapsed()
|
||||
|
||||
void FirmwareUploaderWindow::sendFirmwareChunk()
|
||||
{
|
||||
CANFrame *output = new CANFrame;
|
||||
CANFrame output;
|
||||
int firmwareLocation = currentSendingPosition * 4;
|
||||
int xorByte = 0;
|
||||
output->setExtendedFrameFormat(false);
|
||||
output.setExtendedFrameFormat(false);
|
||||
QByteArray bytes(7,0);
|
||||
output->bus = bus;
|
||||
output->setFrameId(baseAddress + 0x16);
|
||||
output->payload()[0] = currentSendingPosition & 0xFF;
|
||||
output->payload()[1] = (currentSendingPosition >> 8) & 0xFF;
|
||||
output->payload()[2] = firmwareData[firmwareLocation++];
|
||||
output->payload()[3] = firmwareData[firmwareLocation++];
|
||||
output->payload()[4] = firmwareData[firmwareLocation++];
|
||||
output->payload()[5] = firmwareData[firmwareLocation++];
|
||||
for (int i = 0; i < 6; i++) xorByte = xorByte ^ static_cast<unsigned char>(output->payload()[i]);
|
||||
output->payload()[6] = xorByte;
|
||||
output->setPayload(bytes);
|
||||
CANConManager::getInstance()->sendFrame(*output);
|
||||
output.bus = bus;
|
||||
output.setFrameId(baseAddress + 0x16);
|
||||
bytes[0] = currentSendingPosition & 0xFF;
|
||||
bytes[1] = (currentSendingPosition >> 8) & 0xFF;
|
||||
bytes[2] = firmwareData[firmwareLocation++];
|
||||
bytes[3] = firmwareData[firmwareLocation++];
|
||||
bytes[4] = firmwareData[firmwareLocation++];
|
||||
bytes[5] = firmwareData[firmwareLocation++];
|
||||
for (int i = 0; i < 6; i++) xorByte ^= static_cast<unsigned char>(bytes[i]);
|
||||
bytes[6] = xorByte;
|
||||
output.setPayload(bytes);
|
||||
CANConManager::getInstance()->sendFrame(output);
|
||||
timer->start();
|
||||
}
|
||||
|
||||
void FirmwareUploaderWindow::sendFirmwareEnding()
|
||||
{
|
||||
CANFrame *output = new CANFrame;
|
||||
output->setExtendedFrameFormat(false);
|
||||
output->bus = bus;
|
||||
CANFrame output;
|
||||
output.setExtendedFrameFormat(false);
|
||||
output.bus = bus;
|
||||
QByteArray bytes(4,0);
|
||||
output->setFrameId(baseAddress + 0x30);
|
||||
output->payload()[3] = 0xC0;
|
||||
output->payload()[2] = 0xDE;
|
||||
output->payload()[1] = 0xFA;
|
||||
output->payload()[0] = 0xDE;
|
||||
output->setPayload(bytes);
|
||||
output.setFrameId(baseAddress + 0x30);
|
||||
bytes[3] = (char)0xC0;
|
||||
bytes[2] = (char)0xDE;
|
||||
bytes[1] = (char)0xFA;
|
||||
bytes[0] = (char)0xDE;
|
||||
output.setPayload(bytes);
|
||||
//sendCANFrame(output, bus);
|
||||
}
|
||||
|
||||
@@ -182,23 +182,23 @@ void FirmwareUploaderWindow::handleStartStopTransfer()
|
||||
qDebug() << "Base address: " + QString::number(baseAddress);
|
||||
CANConManager::getInstance()->addTargettedFrame(bus, baseAddress + 0x10, 0x7FF, this);
|
||||
CANConManager::getInstance()->addTargettedFrame(bus, baseAddress + 0x20, 0x7FF, this);
|
||||
CANFrame *output = new CANFrame;
|
||||
output->setExtendedFrameFormat(false);
|
||||
CANFrame output;
|
||||
output.setExtendedFrameFormat(false);
|
||||
QByteArray bytes(8,0);
|
||||
output->bus = bus;
|
||||
output->setFrameId(baseAddress);
|
||||
output->setFrameType(QCanBusFrame::DataFrame);
|
||||
output.bus = bus;
|
||||
output.setFrameId(baseAddress);
|
||||
output.setFrameType(QCanBusFrame::DataFrame);
|
||||
|
||||
bytes[0] = 0xEF;
|
||||
bytes[1] = 0xBE;
|
||||
bytes[2] = 0xAD;
|
||||
bytes[3] = 0xDE;
|
||||
bytes[0] = (char)0xEF;
|
||||
bytes[1] = (char)0xBE;
|
||||
bytes[2] = (char)0xAD;
|
||||
bytes[3] = (char)0xDE;
|
||||
bytes[4] = token & 0xFF;
|
||||
bytes[5] = (token >> 8) & 0xFF;
|
||||
bytes[6] = (token >> 16) & 0xFF;
|
||||
bytes[7] = (token >> 24) & 0xFF;
|
||||
output->setPayload(bytes);
|
||||
CANConManager::getInstance()->sendFrame(*output);
|
||||
output.setPayload(bytes);
|
||||
CANConManager::getInstance()->sendFrame(output);
|
||||
}
|
||||
else //stop anything in process
|
||||
{
|
||||
@@ -209,7 +209,6 @@ void FirmwareUploaderWindow::handleStartStopTransfer()
|
||||
|
||||
void FirmwareUploaderWindow::handleLoadFile()
|
||||
{
|
||||
QString filename;
|
||||
QFileDialog dialog;
|
||||
|
||||
QStringList filters;
|
||||
@@ -221,7 +220,7 @@ void FirmwareUploaderWindow::handleLoadFile()
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
filename = dialog.selectedFiles()[0];
|
||||
QString filename = dialog.selectedFiles().constFirst();
|
||||
|
||||
loadBinaryFile(filename);
|
||||
}
|
||||
@@ -232,22 +231,20 @@ void FirmwareUploaderWindow::loadBinaryFile(QString filename)
|
||||
|
||||
if (transferInProgress) handleStartStopTransfer();
|
||||
|
||||
QFile *inFile = new QFile(filename);
|
||||
QFile inFile(filename);
|
||||
|
||||
if (!inFile->open(QIODevice::ReadOnly))
|
||||
if (!inFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
delete inFile;
|
||||
return;
|
||||
}
|
||||
|
||||
firmwareData = inFile->readAll();
|
||||
firmwareData = inFile.readAll();
|
||||
|
||||
currentSendingPosition = 0;
|
||||
firmwareSize = firmwareData.length();
|
||||
|
||||
updateProgress();
|
||||
|
||||
inFile->close();
|
||||
delete inFile;
|
||||
inFile.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -369,16 +369,6 @@ bool FrameFileIO::autoDetectLoadFile(QString filename, QVector<CANFrame>* frames
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Attempting lawicel";
|
||||
if (isLawicelFile(filename))
|
||||
{
|
||||
if (loadLawicelFile(filename, frames))
|
||||
{
|
||||
qDebug() << "Loaded as lawicel successfully!";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Attempting 'CARBUS Analyzer'";
|
||||
if (isCARBUSAnalyzerFile(filename))
|
||||
{
|
||||
@@ -494,6 +484,16 @@ bool FrameFileIO::autoDetectLoadFile(QString filename, QVector<CANFrame>* frames
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Attempting lawicel";
|
||||
if (isLawicelFile(filename))
|
||||
{
|
||||
if (loadLawicelFile(filename, frames))
|
||||
{
|
||||
qDebug() << "Loaded as lawicel successfully!";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Attempting generic CSV";
|
||||
if (isGenericCSVFile(filename))
|
||||
{
|
||||
@@ -867,7 +867,7 @@ bool FrameFileIO::loadCARBUSAnalyzerFile(QString filename, QVector<CANFrame>* fr
|
||||
line = txt.readLine().simplified();
|
||||
if (line.length() > 2)
|
||||
{
|
||||
QList<QString> tokens = line.split(QRegExp("\\s+"));
|
||||
QList<QString> tokens = line.split(QRegularExpression("\\s+"));
|
||||
if (tokens.length() > 3)
|
||||
{
|
||||
QString time = tokens[0].replace(",", "");
|
||||
@@ -1054,7 +1054,7 @@ bool FrameFileIO::isCANHackerFile(QString filename)
|
||||
|
||||
// CANHacker trace format
|
||||
// Time ID DLC Data Comment
|
||||
// 00.000 00004000 8 36 47 19 43 01 00 00 80
|
||||
// 00[.|,]000 00004000 8 36 47 19 43 01 00 00 80
|
||||
bool FrameFileIO::loadCANHackerFile(QString filename, QVector<CANFrame>* frames)
|
||||
{
|
||||
QFile *inFile = new QFile(filename);
|
||||
@@ -1062,6 +1062,9 @@ bool FrameFileIO::loadCANHackerFile(QString filename, QVector<CANFrame>* frames)
|
||||
QByteArray line;
|
||||
int lineCounter = 0;
|
||||
bool foundErrors = false;
|
||||
int addendumTime = 0;
|
||||
double previousTime = 0;
|
||||
|
||||
|
||||
if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
@@ -1086,6 +1089,11 @@ bool FrameFileIO::loadCANHackerFile(QString filename, QVector<CANFrame>* frames)
|
||||
if (tokens.length() > 3)
|
||||
{
|
||||
int idxOfDecimal = tokens[0].indexOf('.');
|
||||
// If no dot is found, try comma
|
||||
if(idxOfDecimal == -1)
|
||||
{
|
||||
idxOfDecimal = tokens[0].indexOf(',');
|
||||
}
|
||||
if (idxOfDecimal > -1) {
|
||||
//int decimalPlaces = tokens[0].length() - tokens[0].indexOf('.') - 1;
|
||||
//the result of the above is the # of digits after the decimal.
|
||||
@@ -1097,7 +1105,11 @@ bool FrameFileIO::loadCANHackerFile(QString filename, QVector<CANFrame>* frames)
|
||||
multiplier = 1; //special case. Assume no decimal means microseconds
|
||||
}
|
||||
//qDebug() << "decimal places " << decimalPlaces;
|
||||
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast<uint64_t>(tokens[0].toDouble() * multiplier)));
|
||||
if (previousTime > tokens[0].toDouble()) {
|
||||
addendumTime += 60;
|
||||
}
|
||||
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast<uint64_t>((tokens[0].toDouble() + addendumTime) * multiplier)));
|
||||
previousTime = tokens[0].toDouble();
|
||||
thisFrame.setFrameId( static_cast<uint32_t>(tokens[1].toInt(nullptr, 16)) );
|
||||
thisFrame.setExtendedFrameFormat((thisFrame.frameId() > 0x7FF));
|
||||
thisFrame.isReceived = true;
|
||||
@@ -1369,8 +1381,17 @@ bool FrameFileIO::isPCANFile(QString filename)
|
||||
;---+-- ------+------ +- --+----- +- +- +- +- -- -- -- -- -- -- --
|
||||
0-6 8-20 22-23 25-32 34-35 37-38 40-? two chars each + space
|
||||
|
||||
Both versions are supported now but rather lazily. Very many aspects of the file are ignored.
|
||||
Version 2.1
|
||||
; Message Time Type ID Rx/Tx
|
||||
; Number Offset | Bus [hex] | Reserved
|
||||
; | [ms] | | | | | Data Length Code
|
||||
; | | | | | | | | Data [hex] ...
|
||||
; | | | | | | | | |
|
||||
;---+--- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --
|
||||
0 1 2 3 4 5 6 7 8 +
|
||||
|
||||
It seems as if Version 2 files might be able to store other protocols like ISO-TP or J1939
|
||||
|
||||
*/
|
||||
bool FrameFileIO::loadPCANFile(QString filename, QVector<CANFrame>* frames)
|
||||
{
|
||||
@@ -1395,10 +1416,14 @@ bool FrameFileIO::loadPCANFile(QString filename, QVector<CANFrame>* frames)
|
||||
lineCounter = 0;
|
||||
}
|
||||
line = inFile->readLine();
|
||||
if (line.startsWith(";$FILEVERSION=2.0")) fileVersion = 20;
|
||||
if (line.startsWith(";$FILEVERSION=1.3")) fileVersion = 13;
|
||||
if (line.startsWith(";$FILEVERSION=1.1")) fileVersion = 11;
|
||||
if (line.startsWith(';')) continue;
|
||||
if (line.startsWith(';'))
|
||||
{
|
||||
if (line.contains("$FILEVERSION=2.1")) fileVersion = 21;
|
||||
if (line.contains("$FILEVERSION=2.0")) fileVersion = 20;
|
||||
if (line.contains("$FILEVERSION=1.3")) fileVersion = 13;
|
||||
if (line.contains("$FILEVERSION=1.1")) fileVersion = 11;
|
||||
continue;
|
||||
}
|
||||
if (line.length() > 41)
|
||||
{
|
||||
line = line.simplified();
|
||||
@@ -1459,7 +1484,7 @@ bool FrameFileIO::loadPCANFile(QString filename, QVector<CANFrame>* frames)
|
||||
if (tokens.length() > 6)
|
||||
{
|
||||
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast<uint64_t>(tokens[1].toDouble() * 1000.0)));
|
||||
thisFrame.setFrameId(tokens[3].toUInt(nullptr, 16));
|
||||
thisFrame.setFrameId(tokens[4].toUInt(nullptr, 16));
|
||||
if (thisFrame.frameId() < 0x1FFFFFFF)
|
||||
{
|
||||
int numBytes = tokens[6].toInt();
|
||||
@@ -1538,6 +1563,58 @@ bool FrameFileIO::loadPCANFile(QString filename, QVector<CANFrame>* frames)
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
Version 2.1
|
||||
; Message Time Type ID Rx/Tx
|
||||
; Number Offset | Bus [hex] | Reserved
|
||||
; | [ms] | | | | | Data Length Code
|
||||
; | | | | | | | | Data [hex] ...
|
||||
; | | | | | | | | |
|
||||
;---+--- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- --
|
||||
0 1 2 3 4 5 6 7 8 +
|
||||
*/
|
||||
else if (fileVersion == 21)
|
||||
{
|
||||
if (tokens.length() > 7)
|
||||
{
|
||||
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast<uint64_t>(tokens[1].toDouble() * 1000.0)));
|
||||
thisFrame.setFrameId(tokens[4].toUInt(nullptr, 16));
|
||||
if (thisFrame.frameId() < 0x1FFFFFFF)
|
||||
{
|
||||
int numBytes = tokens[7].toInt();
|
||||
QByteArray bytes(numBytes, 0);
|
||||
//qDebug() << thisFrame.payload().length();
|
||||
thisFrame.isReceived = true;
|
||||
thisFrame.bus = tokens[3].toInt();
|
||||
if (thisFrame.frameId() > 0x10000000)
|
||||
{
|
||||
thisFrame.setExtendedFrameFormat(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
thisFrame.setExtendedFrameFormat(false);
|
||||
}
|
||||
if (tokens[2] == "R")
|
||||
{
|
||||
thisFrame.setFrameType(QCanBusFrame::RemoteRequestFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
thisFrame.setFrameType(QCanBusFrame::DataFrame);
|
||||
for (int d = 0; d < numBytes; d++)
|
||||
{
|
||||
if (tokens[d + 8] != "")
|
||||
{
|
||||
bytes[d] = static_cast<char>(tokens[d + 8].toInt(nullptr, 16));
|
||||
}
|
||||
else bytes[d] = 0;
|
||||
}
|
||||
}
|
||||
thisFrame.setPayload(bytes);
|
||||
frames->append(thisFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//else foundErrors = true;
|
||||
}
|
||||
@@ -1551,9 +1628,9 @@ bool FrameFileIO::isCanalyzerASC(QString filename)
|
||||
{
|
||||
QFile *inFile = new QFile(filename);
|
||||
QByteArray line;
|
||||
int lineCounter = 0;
|
||||
//int lineCounter = 0;
|
||||
//bool inHeader = true;
|
||||
bool isMatch = true;
|
||||
bool inHeader = true;
|
||||
QList<QByteArray> tokens;
|
||||
|
||||
if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
@@ -1596,15 +1673,23 @@ bool FrameFileIO::isCanalyzerASC(QString filename)
|
||||
//There tends to be four lines of header first. The last of which starts with // so first burn off lines
|
||||
//until a line starting with // is seen, then the rest are formatted like this:
|
||||
//Version 8.0:
|
||||
// 47.971842 2 248 Rx d 8 FF FF FF FF FF FF FF FF
|
||||
//version 8.1
|
||||
//47244640.194244 CANFD 1 Rx 122 0 0 6 6 00 00 18 12 D2 00 0 0 200000 0 0 0 0 0
|
||||
//That can be simplified to look like this:
|
||||
//47.971842 2 248 Rx d 8 FF FF FF FF FF FF FF FF
|
||||
//version 8.1
|
||||
//47244640.194244 CANFD 1 Rx 122 0 0 6 6 00 00 18 12 D2 00 0 0 200000 0 0 0 0 0
|
||||
//Which is then easy to parse by splitting on the spaces
|
||||
//Time bus id dir ? len databytes (Ver 8.0)
|
||||
//Time type bus dir ID ? ? length length bytes then many values of unknown type (ver 8.1)
|
||||
//Version 9.0
|
||||
//0.001371 CANFD 1 Rx 171 A0066_Unknown 0 0 8 8 00 00 07 76 23 00 00 00 230199 119 220000 62e7 46500250 460a0250 20011736 20010205
|
||||
//Version 16.0.0
|
||||
//0.008600 1 358 Rx d 8 04 03 50 01 F0 40 54 2C Length = 233910 BitCount = 121 ID = 856
|
||||
//Version 17.3.0
|
||||
//0.001684 CANFD 1 Rx 65b 1 0 d 32 f1 01 00 00 50 00 00 00 00 00 00 00 00 00 00 40 00 88 f5 77 17 78 00 00 c0 03 00 00 00 00 00 00 213797 356 303000 11729 46500250 4b140250 20011736 2001040d
|
||||
|
||||
//Time bus id dir ? len databytes (Ver 8.0)
|
||||
//Time bus id dir ? len databytes (addl info) (ver 16.0)
|
||||
//Time type bus dir ID ? ? length length (bytes) (many values of unknown type) (ver 8.1)
|
||||
//Time type bus dir ID SignalName ? ? length length bytes (then many values of unknown type)(ver 9.0)
|
||||
//Time Type Bus Dir ID ? ? (length) (Real Length) (bytes) (many values of unknown type) (Ver 17.3)
|
||||
//0 1 2 3 4 5 6 7 8 9 10
|
||||
//This seems like a rather eclectic mix. It's almost arbitrary!
|
||||
bool FrameFileIO::loadCanalyzerASC(QString filename, QVector<CANFrame>* frames)
|
||||
{
|
||||
QFile *inFile = new QFile(filename);
|
||||
@@ -1638,7 +1723,6 @@ bool FrameFileIO::loadCanalyzerASC(QString filename, QVector<CANFrame>* frames)
|
||||
{
|
||||
if (line.startsWith("//"))
|
||||
{
|
||||
// version 8.1.0
|
||||
line = line.right(line.length() - 11);
|
||||
QList<QByteArray> versionTokens = line.split('.');
|
||||
if (versionTokens.length() > 2)
|
||||
@@ -1646,6 +1730,7 @@ bool FrameFileIO::loadCanalyzerASC(QString filename, QVector<CANFrame>* frames)
|
||||
verMajor = versionTokens[0].toInt();
|
||||
verMinor = versionTokens[1].toInt();
|
||||
verRevision = versionTokens[2].toInt();
|
||||
qDebug() << "Major: " << verMajor << " Minor:" << verMinor << " Rev:" << verRevision;
|
||||
}
|
||||
}
|
||||
if (line.startsWith("//") || lineCounter > 4)
|
||||
@@ -1655,115 +1740,135 @@ bool FrameFileIO::loadCanalyzerASC(QString filename, QVector<CANFrame>* frames)
|
||||
}
|
||||
}
|
||||
if (inHeader) continue;
|
||||
if (line.length() > 2)
|
||||
if (line.length() > 2 && !line.startsWith("//"))
|
||||
{
|
||||
tokens = line.simplified().split(' ');
|
||||
if (verMajor == 8 && verMinor == 0)
|
||||
|
||||
if (tokens[0].contains("Begin")) continue; //probably begin triggerblock but we're ignoring that.
|
||||
|
||||
//try to do some investigating to see if this line is a CAN frame or not. The file format has many other potential line types it seems...
|
||||
if (tokens.length() > 5)
|
||||
{
|
||||
if (tokens.length() > 5)
|
||||
if (tokens[3].toUpper().startsWith("RX") || tokens[3].toUpper().startsWith("TX"))
|
||||
{
|
||||
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast<uint64_t>(tokens[0].toDouble() * 1000000.0)));
|
||||
if (tokens[2].endsWith('x'))
|
||||
if (tokens[1].contains("CAN")) //the different format I haven't seen a whole lot of, seems to support CANFD in this format
|
||||
{
|
||||
QByteArray copied_id = tokens[2];
|
||||
copied_id.chop(1);
|
||||
thisFrame.setFrameId(copied_id.toUInt(nullptr, 16));
|
||||
thisFrame.setExtendedFrameFormat(true);
|
||||
if (tokens[4].endsWith('x'))
|
||||
{
|
||||
QByteArray copied_id = tokens[4];
|
||||
copied_id.chop(1);
|
||||
thisFrame.setFrameId(copied_id.toUInt(nullptr, 16));
|
||||
thisFrame.setExtendedFrameFormat(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
thisFrame.setFrameId(tokens[4].toUInt(nullptr, 16));
|
||||
thisFrame.setExtendedFrameFormat(thisFrame.frameId() > 0x7FF); //some .asc files have extended IDs without 'x'
|
||||
}
|
||||
|
||||
int payloadLen = tokens[8].toInt();
|
||||
qDebug() << "PayloadLen: " << payloadLen << " Tokens: " << tokens;
|
||||
QByteArray bytes(payloadLen, 0);
|
||||
if (payloadLen > 64)
|
||||
{
|
||||
qDebug() << "Payload length too long. Original line: " << line;
|
||||
return false;
|
||||
}
|
||||
if (payloadLen < 0)
|
||||
{
|
||||
qDebug() << "Payload length negative! Original line: " << line;
|
||||
return false;
|
||||
}
|
||||
thisFrame.isReceived = tokens[3].toUpper().contains("RX");
|
||||
thisFrame.bus = tokens[2].toInt();
|
||||
|
||||
if (tokens[5].at(0) >= '0' && tokens[5].at(0) <= '9')
|
||||
{
|
||||
for (int d = 9; d < (9 + payloadLen); d++)
|
||||
{
|
||||
if (tokens.count() > d)
|
||||
{
|
||||
bytes[d - 9] = static_cast<char>(tokens[d].toInt(nullptr, 16));
|
||||
}
|
||||
else //expected byte wasn't there to read. Set it zero and set error flag
|
||||
{
|
||||
bytes[d - 9] = 0;
|
||||
foundErrors = true;
|
||||
qDebug() << "D:" << d << " Count:" << tokens.count();
|
||||
qDebug() << "Expected byte missing! Original line: " << line;
|
||||
}
|
||||
}
|
||||
thisFrame.setPayload(bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int d = 10; d < (10 + payloadLen); d++)
|
||||
{
|
||||
if (tokens.count() > d)
|
||||
{
|
||||
bytes[d - 10] = static_cast<char>(tokens[d].toInt(nullptr, 16));
|
||||
}
|
||||
else //expected byte wasn't there to read. Set it zero and set error flag
|
||||
{
|
||||
bytes[d - 10] = 0;
|
||||
foundErrors = true;
|
||||
qDebug() << "D:" << d << " Count:" << tokens.count();
|
||||
qDebug() << "Expected byte missing! Original line: " << line;
|
||||
}
|
||||
}
|
||||
thisFrame.setPayload(bytes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
thisFrame.setFrameId(tokens[2].toUInt(nullptr, 16));
|
||||
thisFrame.setExtendedFrameFormat(thisFrame.frameId() > 0x7FF); //some .asc files have extended IDs without 'x'
|
||||
}
|
||||
|
||||
int payloadLen = tokens[5].toInt();
|
||||
QByteArray bytes(payloadLen, 0);
|
||||
|
||||
if (payloadLen > 8)
|
||||
{
|
||||
qDebug() << "Payload length too long. Original line: " << line;
|
||||
return false;
|
||||
}
|
||||
if (payloadLen < 0)
|
||||
{
|
||||
qDebug() << "Payload length negative! Original line: " << line;
|
||||
return false;
|
||||
}
|
||||
thisFrame.isReceived = tokens[3].toUpper().contains("RX");
|
||||
thisFrame.bus = tokens[1].toInt();
|
||||
if (tokens[4] == "r") thisFrame.setFrameType(QCanBusFrame::RemoteRequestFrame);
|
||||
for (int d = 6; d < (6 + payloadLen); d++)
|
||||
{
|
||||
if (tokens.count() > d)
|
||||
int payloadLen = tokens[5].toInt();
|
||||
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast<uint64_t>(tokens[0].toDouble() * 1000000.0)));
|
||||
if (tokens[2].endsWith('x'))
|
||||
{
|
||||
bytes[d - 6] = static_cast<char>(tokens[d].toInt(nullptr, 16));
|
||||
QByteArray copied_id = tokens[2];
|
||||
copied_id.chop(1);
|
||||
thisFrame.setFrameId(copied_id.toUInt(nullptr, 16));
|
||||
thisFrame.setExtendedFrameFormat(true);
|
||||
}
|
||||
else //expected byte wasn't there to read. Set it zero and set error flag
|
||||
else
|
||||
{
|
||||
bytes[d - 6] = 0;
|
||||
foundErrors = true;
|
||||
qDebug() << "D:" << d << " Count:" << tokens.count();
|
||||
qDebug() << "Expected byte missing! Original line: " << line;
|
||||
thisFrame.setFrameId(tokens[2].toUInt(nullptr, 16));
|
||||
thisFrame.setExtendedFrameFormat(thisFrame.frameId() > 0x7FF); //some .asc files have extended IDs without 'x'
|
||||
}
|
||||
QByteArray bytes(payloadLen, 0);
|
||||
|
||||
if (payloadLen > 8)
|
||||
{
|
||||
qDebug() << "Payload length too long. Original line: " << line;
|
||||
return false;
|
||||
}
|
||||
if (payloadLen < 0)
|
||||
{
|
||||
qDebug() << "Payload length negative! Original line: " << line;
|
||||
return false;
|
||||
}
|
||||
thisFrame.isReceived = tokens[3].toUpper().contains("RX");
|
||||
thisFrame.bus = tokens[1].toInt();
|
||||
if (tokens[4] == "r") thisFrame.setFrameType(QCanBusFrame::RemoteRequestFrame);
|
||||
for (int d = 6; d < (6 + payloadLen); d++)
|
||||
{
|
||||
if (tokens.count() > d)
|
||||
{
|
||||
bytes[d - 6] = static_cast<char>(tokens[d].toInt(nullptr, 16));
|
||||
}
|
||||
else //expected byte wasn't there to read. Set it zero and set error flag
|
||||
{
|
||||
bytes[d - 6] = 0;
|
||||
foundErrors = true;
|
||||
qDebug() << "D:" << d << " Count:" << tokens.count();
|
||||
qDebug() << "Expected byte missing! Original line: " << line;
|
||||
}
|
||||
}
|
||||
thisFrame.setPayload(bytes);
|
||||
}
|
||||
thisFrame.setPayload(bytes);
|
||||
frames->append(thisFrame);
|
||||
}
|
||||
frames->append(thisFrame);
|
||||
}
|
||||
if (verMajor == 8 && verMinor == 1)
|
||||
{
|
||||
if (tokens.length() > 5)
|
||||
{
|
||||
//47244640.194244 CANFD 1 Rx 122 0 0 6 6 00 00 18 12 D2 00 0 0 200000 0 0 0 0 0
|
||||
//Time type bus dir ID ? ? length length bytes then many values of unknown type (ver 8.1)
|
||||
// 0 1 2 3 4 5 6 7 8 9-
|
||||
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast<uint64_t>(tokens[0].toDouble() * 1000000.0)));
|
||||
if (tokens[4].endsWith('x'))
|
||||
{
|
||||
QByteArray copied_id = tokens[4];
|
||||
copied_id.chop(1);
|
||||
thisFrame.setFrameId(copied_id.toUInt(nullptr, 16));
|
||||
thisFrame.setExtendedFrameFormat(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
thisFrame.setFrameId(tokens[4].toUInt(nullptr, 16));
|
||||
thisFrame.setExtendedFrameFormat(thisFrame.frameId() > 0x7FF); //some .asc files have extended IDs without 'x'
|
||||
}
|
||||
|
||||
int payloadLen = tokens[8].toInt();
|
||||
QByteArray bytes(payloadLen, 0);
|
||||
|
||||
if (payloadLen > 8)
|
||||
{
|
||||
qDebug() << "Payload length too long. Original line: " << line;
|
||||
return false;
|
||||
}
|
||||
if (payloadLen < 0)
|
||||
{
|
||||
qDebug() << "Payload length negative! Original line: " << line;
|
||||
return false;
|
||||
}
|
||||
thisFrame.isReceived = tokens[3].toUpper().contains("RX");
|
||||
thisFrame.bus = tokens[2].toInt();
|
||||
if (tokens[5] == "r") thisFrame.setFrameType(QCanBusFrame::RemoteRequestFrame);
|
||||
for (int d = 9; d < (9 + payloadLen); d++)
|
||||
{
|
||||
if (tokens.count() > d)
|
||||
{
|
||||
bytes[d - 9] = static_cast<char>(tokens[d].toInt(nullptr, 16));
|
||||
}
|
||||
else //expected byte wasn't there to read. Set it zero and set error flag
|
||||
{
|
||||
bytes[d - 9] = 0;
|
||||
foundErrors = true;
|
||||
qDebug() << "D:" << d << " Count:" << tokens.count();
|
||||
qDebug() << "Expected byte missing! Original line: " << line;
|
||||
}
|
||||
}
|
||||
thisFrame.setPayload(bytes);
|
||||
}
|
||||
frames->append(thisFrame);
|
||||
}
|
||||
}
|
||||
//else foundErrors = true;
|
||||
@@ -2000,6 +2105,9 @@ bool FrameFileIO::loadNativeCSVFile(QString filename, QVector<CANFrame>* frames)
|
||||
if (tokens[2].toUpper().contains("TRUE")) thisFrame.setExtendedFrameFormat(true);
|
||||
else thisFrame.setExtendedFrameFormat(false);
|
||||
|
||||
//fix for faulty files that fail to set the extended flag when they should
|
||||
if (thisFrame.frameId() > 0x7FF) thisFrame.setExtendedFrameFormat(true);
|
||||
|
||||
thisFrame.setFrameType(QCanBusFrame::DataFrame);
|
||||
|
||||
if (fileVersion == 1)
|
||||
@@ -2294,11 +2402,16 @@ bool FrameFileIO::loadGenericCSVFile(QString filename, QVector<CANFrame>* frames
|
||||
else thisFrame.setExtendedFrameFormat(false);
|
||||
thisFrame.bus = 0;
|
||||
thisFrame.setFrameType(QCanBusFrame::DataFrame);
|
||||
QList<QByteArray> dataTok = tokens[1].split(' ');
|
||||
QByteArray bytes(dataTok.length(), 0);
|
||||
for (int d = 0; d < dataTok.length(); d++) bytes[d] = static_cast<char>(dataTok[d].toInt(nullptr, 16));
|
||||
thisFrame.setPayload(bytes);
|
||||
frames->append(thisFrame);
|
||||
if (tokens.length() > 1)
|
||||
{
|
||||
QList<QByteArray> dataTok = tokens[1].split(' ');
|
||||
int dLen = dataTok.length();
|
||||
if (dLen > 8) dLen = 8;
|
||||
QByteArray bytes(dLen, 0);
|
||||
for (int d = 0; d < dLen; d++) bytes[d] = static_cast<char>(dataTok[d].toInt(nullptr, 16));
|
||||
thisFrame.setPayload(bytes);
|
||||
frames->append(thisFrame);
|
||||
}
|
||||
}
|
||||
else foundErrors = true;
|
||||
}
|
||||
@@ -3468,9 +3581,9 @@ bool FrameFileIO::isCanDumpFile(QString filename)
|
||||
QFile *inFile = new QFile(filename);
|
||||
QByteArray line;
|
||||
QList<QByteArray> tokens;
|
||||
QRegExp timeExp("^\\((\\S+)\\)$");
|
||||
QRegExp IdValExp("^(\\S+)#(\\S+)$");
|
||||
QRegExp valExp("(\\S{2})");
|
||||
QRegularExpression timeExp(QRegularExpression::anchoredPattern("^\\((\\S+)\\)$")); //anchored pattern causes exact match
|
||||
QRegularExpression IdValExp(QRegularExpression::anchoredPattern("^(\\S+)#(\\S+)$"));
|
||||
QRegularExpression valExp("(\\S{2})");
|
||||
int lineCounter = 0;
|
||||
int pos = 0;
|
||||
bool isMatch = true;
|
||||
@@ -3495,11 +3608,13 @@ bool FrameFileIO::isCanDumpFile(QString filename)
|
||||
tokens = line.simplified().split(' ');
|
||||
if(tokens.count() < 3) isMatch = false;
|
||||
|
||||
/* timestamp */
|
||||
ret = timeExp.exactMatch(tokens[0]);
|
||||
if(!ret) isMatch = false;
|
||||
/* timestamp */
|
||||
QRegularExpressionMatch timeExpMatched = timeExp.match(tokens[0]);
|
||||
if(!timeExpMatched.hasMatch()) {
|
||||
isMatch = false;
|
||||
}
|
||||
|
||||
/*uint64_t timestamp = (uint64_t)*/(timeExp.cap(1).toDouble(&ret) /** (double)1000000.0*/);
|
||||
/*uint64_t timestamp = (uint64_t)*/(timeExpMatched.captured(1).toDouble(&ret) /** (double)1000000.0*/);
|
||||
if(!ret) isMatch = false;
|
||||
|
||||
if (line.contains('[')) //the expanded format
|
||||
@@ -3529,17 +3644,19 @@ bool FrameFileIO::isCanDumpFile(QString filename)
|
||||
isMatch = false;
|
||||
continue;
|
||||
}
|
||||
ret = IdValExp.exactMatch(tokens[2]);
|
||||
if(!ret)
|
||||
|
||||
|
||||
QRegularExpressionMatch IdValExpMatched = IdValExp.match(tokens[2]);
|
||||
if(!IdValExpMatched.hasMatch())
|
||||
{
|
||||
isMatch = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ID */
|
||||
/*int ID = */IdValExp.cap(1).toInt(&ret, 16);
|
||||
/*int ID = */IdValExpMatched.captured(1).toInt(&ret, 16);
|
||||
|
||||
QString val= IdValExp.cap(2);
|
||||
QString val= IdValExpMatched.captured(2);
|
||||
|
||||
pos = 0;
|
||||
int len = 0;
|
||||
@@ -3553,22 +3670,23 @@ bool FrameFileIO::isCanDumpFile(QString filename)
|
||||
} else {
|
||||
/* val byte per byte */
|
||||
int lng = 0;
|
||||
while ((pos = valExp.indexIn(val, pos)) != -1)
|
||||
{
|
||||
QRegularExpressionMatch valExpMatch;
|
||||
QRegularExpressionMatchIterator i = valExp.globalMatch(val);
|
||||
while (i.hasNext()) {
|
||||
valExpMatch = i.next();
|
||||
lng++;
|
||||
if (lng > 8)
|
||||
{
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
/*int data = */valExp.cap(1).toInt(&ret, 16);
|
||||
/*int data = */valExpMatch.captured(1).toInt(&ret, 16);
|
||||
if(!ret)
|
||||
{
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
|
||||
pos += valExp.matchedLength();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3595,11 +3713,10 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector<CANFrame>* frames)
|
||||
CANFrame thisFrame;
|
||||
QByteArray line;
|
||||
QList<QByteArray> tokens;
|
||||
QRegExp timeExp("^\\((\\S+)\\)$");
|
||||
QRegExp IdValExp("^(\\S+)#(\\S+)$");
|
||||
QRegExp valExp("(\\S{2})");
|
||||
QRegularExpression timeExp(QRegularExpression::anchoredPattern("^\\((\\S+)\\)$")); //anchored pattern causes exact match
|
||||
QRegularExpression IdValExp(QRegularExpression::anchoredPattern("^(\\S+)#(\\S+)$"));
|
||||
QRegularExpression valExp("(\\S{2})");
|
||||
int lineCounter = 0;
|
||||
int pos = 0;
|
||||
bool ret;
|
||||
|
||||
if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
@@ -3625,8 +3742,8 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector<CANFrame>* frames)
|
||||
if(tokens.count()<3) continue;
|
||||
|
||||
/* timestamp */
|
||||
ret = timeExp.exactMatch(tokens[0]);
|
||||
if(!ret) continue;
|
||||
QRegularExpressionMatch timeExpMatched = timeExp.match(tokens[0]);
|
||||
if(!timeExpMatched.hasMatch()) continue;
|
||||
|
||||
//Sort out the bus
|
||||
std::string busString = tokens[1].toStdString();
|
||||
@@ -3647,7 +3764,7 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector<CANFrame>* frames)
|
||||
|
||||
thisFrame.bus = busNum;
|
||||
|
||||
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, (uint64_t)(timeExp.cap(1).toDouble(&ret) * (double)1000000.0)));
|
||||
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, (uint64_t)(timeExpMatched.captured(1).toDouble(&ret) * (double)1000000.0)));
|
||||
if(!ret) continue;
|
||||
|
||||
if (line.contains('[')) //the expanded format (second one from the above list)
|
||||
@@ -3670,24 +3787,26 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector<CANFrame>* frames)
|
||||
{
|
||||
/* ID & value */
|
||||
//qDebug() << tokens[2];
|
||||
ret = IdValExp.exactMatch(tokens[2]);
|
||||
if(!ret)
|
||||
QRegularExpressionMatch IdValExpMatched = IdValExp.match(tokens[2]);
|
||||
if(!IdValExpMatched.hasMatch())
|
||||
{
|
||||
qDebug() << "ID regex didn't match!";
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ID */
|
||||
thisFrame.setFrameId(static_cast<uint32_t>(IdValExp.cap(1).toInt(&ret, 16)));
|
||||
if (IdValExp.cap(1).length() > 3) {
|
||||
thisFrame.setFrameId(static_cast<uint32_t>(IdValExpMatched.captured(1).toInt(&ret, 16)));
|
||||
if (IdValExpMatched.captured(1).length() > 3)
|
||||
{
|
||||
thisFrame.setExtendedFrameFormat(true);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
thisFrame.setExtendedFrameFormat(false);
|
||||
}
|
||||
|
||||
QString val= IdValExp.cap(2);
|
||||
QString val = IdValExpMatched.captured(2);
|
||||
|
||||
pos = 0;
|
||||
QByteArray bytes;
|
||||
if (val.startsWith("R") && val.at(1).isDigit()) {
|
||||
thisFrame.payload().resize( val.at(1).toLatin1() - '0' );
|
||||
@@ -3695,12 +3814,13 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector<CANFrame>* frames)
|
||||
} else {
|
||||
thisFrame.setFrameType(QCanBusFrame::DataFrame);
|
||||
/* val byte per byte */
|
||||
while ((pos = valExp.indexIn(val, pos)) != -1)
|
||||
QRegularExpressionMatch valExpMatch;
|
||||
QRegularExpressionMatchIterator i = valExp.globalMatch(val);
|
||||
while (i.hasNext())
|
||||
{
|
||||
bytes.append((char)valExp.cap(1).toInt(&ret, 16));
|
||||
valExpMatch = i.next();
|
||||
bytes.append((char)valExpMatch.captured(1).toInt(&ret, 16));
|
||||
if(!ret) continue;
|
||||
|
||||
pos += valExp.matchedLength();
|
||||
}
|
||||
}
|
||||
thisFrame.setPayload(bytes);
|
||||
@@ -4159,8 +4279,8 @@ bool FrameFileIO::loadTeslaAPFile(QString filename, QVector<CANFrame>* frames)
|
||||
CANFrame thisFrame;
|
||||
int lineCounter = 0;
|
||||
QByteArray data;
|
||||
int timeOffset = 0;
|
||||
int64_t lastTimeStamp = 0;
|
||||
//int timeOffset = 0;
|
||||
//int64_t lastTimeStamp = 0;
|
||||
bool foundErrors = false;
|
||||
thisFrame.setFrameType(QCanBusFrame::DataFrame);
|
||||
TeslaAPCANRecord record;
|
||||
@@ -4217,7 +4337,7 @@ bool FrameFileIO::isCLX000File(QString filename) {
|
||||
}
|
||||
|
||||
QTextStream fileStream(inFile.get());
|
||||
bool foundErrors = false;
|
||||
//bool foundErrors = false;
|
||||
|
||||
// Contains 16 lines of header prior to (potential) data.
|
||||
QString headerLine;
|
||||
@@ -4269,7 +4389,7 @@ bool FrameFileIO::isCLX000File(QString filename) {
|
||||
qDebug() << "Could not decode time format" << headerLine << "using pattern" << timeFormatPattern;
|
||||
return false;
|
||||
}
|
||||
auto timeFormat = matchTimeFormat.captured("timeFormat").front().digitValue();
|
||||
//auto timeFormat = matchTimeFormat.captured("timeFormat").front().digitValue();
|
||||
|
||||
headerLine = fileStream.readLine();
|
||||
QString const timeSeparatorPattern = "# Time separator: \"(?<timeSeparator>[" + validSeparators + "]?)\"";
|
||||
@@ -4279,7 +4399,7 @@ bool FrameFileIO::isCLX000File(QString filename) {
|
||||
qDebug() << "Could not decode time format" << headerLine << "using pattern" << timeSeparatorPattern;
|
||||
return false;
|
||||
}
|
||||
QChar timeSeparator = matchTimeSeparator.captured("timeSeparator").front();
|
||||
//QChar timeSeparator = matchTimeSeparator.captured("timeSeparator").front();
|
||||
|
||||
headerLine = fileStream.readLine();
|
||||
QString const timeSeparatorMsPattern = "# Time separator ms: \"(?<timeSeparatorMs>[" + validSeparators + "]?)\"";
|
||||
@@ -4289,7 +4409,7 @@ bool FrameFileIO::isCLX000File(QString filename) {
|
||||
qDebug() << "Could not decode time format ms" << headerLine << "using pattern" << timeSeparatorMsPattern;
|
||||
return false;
|
||||
}
|
||||
QChar timeSeparatorMs = matchTimeSeparatorMs.captured("timeSeparatorMs").front();
|
||||
//QChar timeSeparatorMs = matchTimeSeparatorMs.captured("timeSeparatorMs").front();
|
||||
|
||||
headerLine = fileStream.readLine();
|
||||
QString const dateSeparatorPattern = "# Date separator: \"(?<dateSeparator>[" + validSeparators + "]?)\"";
|
||||
@@ -4299,7 +4419,7 @@ bool FrameFileIO::isCLX000File(QString filename) {
|
||||
qDebug() << "Could not decode time format ms" << headerLine << "using pattern" << dateSeparatorPattern;
|
||||
return false;
|
||||
}
|
||||
QChar dateSeparator = matchDateSeparator.captured("dateSeparator").front();
|
||||
//QChar dateSeparator = matchDateSeparator.captured("dateSeparator").front();
|
||||
|
||||
headerLine = fileStream.readLine();
|
||||
QString const timeDateSeparatorPattern = "# Time and date separator: \"(?<timeDateSeparator>[" + validSeparators + "]?)\"";
|
||||
@@ -4309,7 +4429,7 @@ bool FrameFileIO::isCLX000File(QString filename) {
|
||||
qDebug() << "Could not decode time format ms" << headerLine << "using pattern" << timeDateSeparatorPattern;
|
||||
return false;
|
||||
}
|
||||
QChar timeDateSeparator = matchTimeDateSeparator.captured("timeDateSeparator").front();
|
||||
//QChar timeDateSeparator = matchTimeDateSeparator.captured("timeDateSeparator").front();
|
||||
|
||||
// Skip remaining header lines.
|
||||
fileStream.readLine();
|
||||
@@ -4373,7 +4493,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector<CANFrame>* frames) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString const validSeparators(" -~");
|
||||
QString const validSeparators(" -~;");
|
||||
|
||||
// Decode separators and format for later decoding.
|
||||
headerLine = fileStream.readLine();
|
||||
@@ -4480,6 +4600,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector<CANFrame>* frames) {
|
||||
}
|
||||
|
||||
// Fallthrough
|
||||
[[fallthrough]];
|
||||
}
|
||||
case 5: {
|
||||
// Handle month.
|
||||
@@ -4490,6 +4611,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector<CANFrame>* frames) {
|
||||
}
|
||||
|
||||
// Fallthrough
|
||||
[[fallthrough]];
|
||||
}
|
||||
case 4: {
|
||||
// Handle day.
|
||||
@@ -4500,6 +4622,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector<CANFrame>* frames) {
|
||||
}
|
||||
|
||||
// Fallthrough
|
||||
[[fallthrough]];
|
||||
}
|
||||
case 3: {
|
||||
// Handle hours.
|
||||
@@ -4510,6 +4633,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector<CANFrame>* frames) {
|
||||
}
|
||||
|
||||
// Fallthrough
|
||||
[[fallthrough]];
|
||||
}
|
||||
case 2: {
|
||||
// Handle minutes.
|
||||
@@ -4520,6 +4644,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector<CANFrame>* frames) {
|
||||
}
|
||||
|
||||
// Fallthrough
|
||||
[[fallthrough]];
|
||||
}
|
||||
case 1: {
|
||||
// Handle seconds.
|
||||
@@ -4530,6 +4655,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector<CANFrame>* frames) {
|
||||
}
|
||||
|
||||
// Fallthrough
|
||||
[[fallthrough]];
|
||||
}
|
||||
case 0: {
|
||||
// Handle milliseconds.
|
||||
@@ -4576,17 +4702,34 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector<CANFrame>* frames) {
|
||||
currentFrame.setFrameType(QCanBusFrame::DataFrame);
|
||||
currentFrame.bus = 0;
|
||||
|
||||
currentTime = currentTime.addYears(res.captured("year").toUInt());
|
||||
currentTime = currentTime.addMonths(res.captured("month").toUInt());
|
||||
currentTime = currentTime.addDays(res.captured("day").toUInt());
|
||||
currentTime = currentTime.addSecs(
|
||||
res.captured("hours").toUInt() * 60 * 60 +
|
||||
res.captured("minutes").toUInt() * 60 +
|
||||
res.captured("seconds").toUInt()
|
||||
);
|
||||
currentTime = currentTime.addMSecs(res.captured("ms").toUInt());
|
||||
//currentTime is already initialized with startDate so it already has year,month,etc filled out.
|
||||
//For each timestamp on the line we need to figure out how it differs from the already set time
|
||||
//and move the timestamp accordingly.
|
||||
if (res.captured("year").length() > 0)
|
||||
{
|
||||
//for all of these, only add the difference not the whole value!
|
||||
currentTime = currentTime.addYears(res.captured("year").toUInt() - startDate.date().year());
|
||||
}
|
||||
|
||||
currentFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, currentTime.toMSecsSinceEpoch()));
|
||||
if (res.captured("month").length() > 0)
|
||||
{
|
||||
currentTime = currentTime.addMonths(res.captured("month").toUInt() - startDate.date().month());
|
||||
}
|
||||
|
||||
if (res.captured("day").length() > 0)
|
||||
{
|
||||
currentTime = currentTime.addMonths(res.captured("day").toUInt() - startDate.date().day());
|
||||
}
|
||||
|
||||
unsigned int seconds = res.captured("hours").toUInt() * 60 * 60 + res.captured("minutes").toUInt() * 60 +
|
||||
res.captured("seconds").toUInt() - (startDate.time().msecsSinceStartOfDay() / 1000);
|
||||
|
||||
currentTime = currentTime.addSecs(seconds);
|
||||
currentTime = currentTime.addMSecs(res.captured("ms").toUInt() - (startDate.time().msecsSinceStartOfDay() % 1000));
|
||||
|
||||
//time is stored in microseconds so we need to get the timestamp as milliseconds since the epoch
|
||||
//then multiplyby 1000 to get microseconds since the epoch.
|
||||
currentFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, currentTime.toMSecsSinceEpoch() * 1000));
|
||||
|
||||
if(res.captured("type").length() != 0) {
|
||||
auto frameType = res.captured("type").toUInt();
|
||||
@@ -4652,7 +4795,7 @@ bool FrameFileIO::isCANServerFile(QString filename)
|
||||
{
|
||||
//qDebug() << "header: " << headerData;
|
||||
//We have enough bytes to make up our header. Lets check if it matches
|
||||
if (headerData == "CANSERVER_v2_CANSERVER")
|
||||
if (headerData == "CANSERVER_v2_CANSERVER" || headerData == "CANSERVER_v3_CANSERVER")
|
||||
{
|
||||
isMatch = true;
|
||||
}
|
||||
@@ -4694,6 +4837,7 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector<CANFrame>* frames)
|
||||
//Skip the header
|
||||
|
||||
bool fileIsGood = false;
|
||||
uint8_t logVersion = 1;
|
||||
QByteArray headerData = inFile->read(22);
|
||||
if (headerData.length() == 22)
|
||||
{
|
||||
@@ -4701,13 +4845,19 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector<CANFrame>* frames)
|
||||
//We have enough bytes to make up our header. Lets check if it matches
|
||||
if (headerData == "CANSERVER_v2_CANSERVER")
|
||||
{
|
||||
logVersion = 1;
|
||||
fileIsGood = true;
|
||||
}
|
||||
else if (headerData == "CANSERVER_v3_CANSERVER")
|
||||
{
|
||||
logVersion = 2;
|
||||
fileIsGood = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (fileIsGood)
|
||||
{
|
||||
uint64_t lastFrameTime = 0;
|
||||
//uint64_t lastFrameTime = 0;
|
||||
|
||||
uint8_t data[1];
|
||||
while (!inFile->atEnd())
|
||||
@@ -4725,7 +4875,13 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector<CANFrame>* frames)
|
||||
{
|
||||
if (possibleHeaderData == "ANSERVER_v2_CANSERVER" )
|
||||
{
|
||||
//We found another header. Lets just ignore things and move on
|
||||
//We found another header (in this case a version 1 format). Lets just ignore things and move on
|
||||
logVersion = 1;
|
||||
}
|
||||
else if (possibleHeaderData == "ANSERVER_v3_CANSERVER" )
|
||||
{
|
||||
//We found another header (in this case a version 2 format). Lets just ignore things and move on
|
||||
logVersion = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4734,10 +4890,10 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector<CANFrame>* frames)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (data[0] == 0xCD)
|
||||
else if ((logVersion == 1 && data[0] == 0xCD) || (logVersion == 2 && data[0] == 0xD0))
|
||||
{
|
||||
//Log mark message
|
||||
|
||||
//Log mark message (the same format in v1 or v2. Just a different identifier byte)
|
||||
|
||||
//Read in the size of the mark
|
||||
uint8_t markSize[1];
|
||||
inFile->read((char*)&markSize, 1);
|
||||
@@ -4758,9 +4914,9 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector<CANFrame>* frames)
|
||||
frames->append(markFrame);
|
||||
*/
|
||||
}
|
||||
else if (data[0] == 0xCE)
|
||||
else if ((logVersion == 1 && data[0] == 0xCE) || (logVersion == 2 && data[0] == 0xA0))
|
||||
{
|
||||
//Timesync message
|
||||
//Timesync message (v1 log format)
|
||||
framedata_U timesyncvalue;
|
||||
::memset(timesyncvalue.data, 0, sizeof(timesyncvalue.data));
|
||||
inFile->read(timesyncvalue.data, 8);
|
||||
@@ -4769,39 +4925,54 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector<CANFrame>* frames)
|
||||
//qDebug() << "Found a time sync message: " << timesyncvalue.u64;
|
||||
timeStampBase = timesyncvalue.u64;
|
||||
}
|
||||
else if (data[0] == 0xCF)
|
||||
else if ((logVersion == 1 && data[0] == 0xCF) || (logVersion == 2 && (data[0] & 0xF0) == 0xB0))
|
||||
{
|
||||
if (frameCounter++ > 100)
|
||||
// Can frame format (either a v1 or a v2)
|
||||
if (frameCounter++ > 10000)
|
||||
{
|
||||
qApp->processEvents();
|
||||
frameCounter = 0;
|
||||
}
|
||||
|
||||
//CAN Frame
|
||||
|
||||
uint8_t frameheaderdata[5];
|
||||
inFile->read((char*)frameheaderdata, 5);
|
||||
|
||||
//The frame time offset is how long since the last time sync message (now in ms) this frame is
|
||||
uint8_t byte1 = frameheaderdata[0];
|
||||
uint8_t byte2 = frameheaderdata[1];
|
||||
uint16_t frametimeoffset = ((uint16_t)byte2 << 8) | byte1;
|
||||
uint32_t frametimeoffset = 0;
|
||||
if (logVersion == 1)
|
||||
{
|
||||
//The frame time offset is how long since the last time sync message (now in ms) this frame is
|
||||
frametimeoffset = ((uint16_t)frameheaderdata[1] << 8) | frameheaderdata[0];
|
||||
frametimeoffset *= 1000;
|
||||
}
|
||||
else if (logVersion == 2)
|
||||
{
|
||||
frametimeoffset = (data[0] & 0x0F) + (frameheaderdata[0] << 4) + (frameheaderdata[1] << 12) + (((frameheaderdata[2] & 0xF8) >> 3) << 20);
|
||||
}
|
||||
|
||||
byte1 = frameheaderdata[2];
|
||||
byte2 = frameheaderdata[3];
|
||||
uint16_t messageId = ((uint16_t)byte2 << 8) | byte1;
|
||||
|
||||
uint8_t framelength = frameheaderdata[4] & 0x0F;
|
||||
uint8_t busid = frameheaderdata[4] >> 4;
|
||||
uint16_t messageId = 0;
|
||||
if (logVersion == 1)
|
||||
{
|
||||
messageId = ((uint16_t)frameheaderdata[3] << 8) | frameheaderdata[2];
|
||||
}
|
||||
else if (logVersion == 2)
|
||||
{
|
||||
messageId = ((frameheaderdata[2] & 0x07) << 8) + (frameheaderdata[3] & 0xFF);
|
||||
}
|
||||
|
||||
uint8_t framelength = (frameheaderdata[4] & 0x0F);
|
||||
uint8_t busid = (frameheaderdata[4] & 0xF0) >> 4;
|
||||
|
||||
framelength = framelength > 8 ? 8 : framelength;
|
||||
|
||||
|
||||
//Now that we have a length lets read those bytes
|
||||
::memset(framedata.data, 0, sizeof(framedata.data));
|
||||
inFile->read(framedata.data, framelength);
|
||||
|
||||
if (framelength > 0)
|
||||
{
|
||||
inFile->read(framedata.data, framelength);
|
||||
}
|
||||
|
||||
//Setup the frame object and populate it
|
||||
|
||||
CANFrame thisFrame;
|
||||
thisFrame.setFrameType(QCanBusFrame::DataFrame);
|
||||
|
||||
@@ -4811,11 +4982,8 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector<CANFrame>* frames)
|
||||
|
||||
uint64_t frameTime = timeStampBase;
|
||||
uint64_t frameoffset = frametimeoffset;
|
||||
frameoffset *= 1000;
|
||||
|
||||
frameTime += frameoffset;
|
||||
lastFrameTime = frameTime;
|
||||
|
||||
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, frameTime));
|
||||
|
||||
QByteArray bytes(framelength,0);
|
||||
|
||||
@@ -32,6 +32,33 @@ quint64 FramePlaybackObject::updatePosition(bool forward)
|
||||
currentPosition = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//only send frame out if its ID is checked in the list. Otherwise discard it.
|
||||
CANFrame *thisFrame = ¤tSeqItem->data[currentPosition];
|
||||
uint32_t originalBus = thisFrame->bus;
|
||||
if (currentSeqItem->idFilters.find(thisFrame->frameId()).value())
|
||||
{
|
||||
if (whichBusSend > -1)
|
||||
{
|
||||
thisFrame->bus = whichBusSend;
|
||||
sendingBuffer.append(*thisFrame);
|
||||
}
|
||||
else if (whichBusSend == -1)
|
||||
{
|
||||
for (int c = 0; c < numBuses; c++)
|
||||
{
|
||||
thisFrame->bus = c;
|
||||
sendingBuffer.append(*thisFrame);
|
||||
}
|
||||
}
|
||||
else //from file so retain original bus and send as-is
|
||||
{
|
||||
sendingBuffer.append(*thisFrame);
|
||||
}
|
||||
|
||||
thisFrame->bus = originalBus;
|
||||
}
|
||||
|
||||
if (forward)
|
||||
{
|
||||
if (currentPosition < (currentSeqItem->data.count() - 1)) currentPosition++; //still in same file so keep going
|
||||
@@ -65,31 +92,6 @@ quint64 FramePlaybackObject::updatePosition(bool forward)
|
||||
}
|
||||
}
|
||||
|
||||
//only send frame out if its ID is checked in the list. Otherwise discard it.
|
||||
CANFrame *thisFrame = ¤tSeqItem->data[currentPosition];
|
||||
uint32_t originalBus = thisFrame->bus;
|
||||
if (currentSeqItem->idFilters.find(thisFrame->frameId()).value())
|
||||
{
|
||||
if (whichBusSend > -1)
|
||||
{
|
||||
thisFrame->bus = whichBusSend;
|
||||
sendingBuffer.append(*thisFrame);
|
||||
}
|
||||
else if (whichBusSend == -1)
|
||||
{
|
||||
for (int c = 0; c < numBuses; c++)
|
||||
{
|
||||
thisFrame->bus = c;
|
||||
sendingBuffer.append(*thisFrame);
|
||||
}
|
||||
}
|
||||
else //from file so retain original bus and send as-is
|
||||
{
|
||||
sendingBuffer.append(*thisFrame);
|
||||
}
|
||||
|
||||
thisFrame->bus = originalBus;
|
||||
}
|
||||
return thisFrame->timeStamp().microSeconds();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "frameplaybackwindow.h"
|
||||
#include "mainwindow.h"
|
||||
#include "ui_frameplaybackwindow.h"
|
||||
#include <QDebug>
|
||||
#include <QFileDialog>
|
||||
@@ -48,6 +49,8 @@ FramePlaybackWindow::FramePlaybackWindow(const QVector<CANFrame> *frames, QWidge
|
||||
currentPosition = 0;
|
||||
forward = true;
|
||||
isPlaying = false;
|
||||
wantPlaying = false;
|
||||
haveIncomingTraffic = false;
|
||||
|
||||
updateFrameLabel();
|
||||
|
||||
@@ -72,6 +75,7 @@ FramePlaybackWindow::FramePlaybackWindow(const QVector<CANFrame> *frames, QWidge
|
||||
connect(ui->btnLoadFilters, &QAbstractButton::clicked, this, &FramePlaybackWindow::loadFilters);
|
||||
connect(ui->btnSaveFilters, &QAbstractButton::clicked, this, &FramePlaybackWindow::saveFilters);
|
||||
connect(ui->cbOriginalTiming, &QCheckBox::toggled, this, &FramePlaybackWindow::useOrigTimingClicked);
|
||||
connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int)));
|
||||
|
||||
connect(&playbackObject, &FramePlaybackObject::EndOfFrameCache, this, &FramePlaybackWindow::EndOfFrameCache);
|
||||
connect(&playbackObject, &FramePlaybackObject::statusUpdate, this, &FramePlaybackWindow::getStatusUpdate);
|
||||
@@ -101,6 +105,8 @@ void FramePlaybackWindow::showEvent(QShowEvent *)
|
||||
{
|
||||
readSettings();
|
||||
installEventFilter(this);
|
||||
|
||||
//any time the view is shown we'll go see how many buses are registered and redo the combobox to match
|
||||
int numBuses = CANConManager::getInstance()->getNumBuses();
|
||||
ui->comboCANBus->clear();
|
||||
for (int n = 0; n < numBuses; n++) ui->comboCANBus->addItem(QString::number(n));
|
||||
@@ -279,6 +285,32 @@ void FramePlaybackWindow::loadFilters()
|
||||
}
|
||||
}
|
||||
|
||||
void FramePlaybackWindow::updatedFrames(int numFrames)
|
||||
{
|
||||
CANFrame thisFrame;
|
||||
|
||||
if (numFrames == -1) //all frames deleted. Don't care
|
||||
{
|
||||
}
|
||||
else if (numFrames == -2) //all new set of frames. Don't care
|
||||
{
|
||||
}
|
||||
else //just got some new frames.
|
||||
{
|
||||
if (numFrames > 0)
|
||||
{
|
||||
haveIncomingTraffic = true;
|
||||
if (wantPlaying && !isPlaying)
|
||||
{
|
||||
isPlaying = true;
|
||||
updateFrameLabel();
|
||||
if (forward) playbackObject.startPlaybackForward();
|
||||
else playbackObject.startPlaybackBackward();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FramePlaybackWindow::refreshIDList()
|
||||
{
|
||||
if (currentSeqNum < 0 || currentSeqItem == nullptr)
|
||||
@@ -338,6 +370,8 @@ void FramePlaybackWindow::EndOfFrameCache()
|
||||
else //not looping so stop playback entirely
|
||||
{
|
||||
isPlaying = false;
|
||||
wantPlaying = false;
|
||||
haveIncomingTraffic = false;
|
||||
playbackObject.stopPlayback();
|
||||
}
|
||||
}
|
||||
@@ -361,6 +395,8 @@ void FramePlaybackWindow::EndOfFrameCache()
|
||||
else //not looping so stop playback entirely
|
||||
{
|
||||
isPlaying = false;
|
||||
wantPlaying = false;
|
||||
haveIncomingTraffic = false;
|
||||
playbackObject.stopPlayback();
|
||||
}
|
||||
}
|
||||
@@ -395,7 +431,10 @@ void FramePlaybackWindow::updateFrameLabel()
|
||||
|
||||
ui->lblCurrPlayback->setText(currentSeqItem->filename);
|
||||
|
||||
ui->lblPosition->setText(QString::number(currentPosition) + tr(" of ") + QString::number(seqItems[row].data.count()));
|
||||
if (wantPlaying && !isPlaying)
|
||||
ui->lblPosition->setText(QString::number(currentPosition) + tr(" of ") + QString::number(seqItems[row].data.count()) + " (WAITING)");
|
||||
else
|
||||
ui->lblPosition->setText(QString::number(currentPosition) + tr(" of ") + QString::number(seqItems[row].data.count()));
|
||||
}
|
||||
|
||||
void FramePlaybackWindow::seqTableCellClicked(int row, int col)
|
||||
@@ -537,28 +576,40 @@ void FramePlaybackWindow::btnLoadLive()
|
||||
|
||||
void FramePlaybackWindow::btnBackOneClick()
|
||||
{
|
||||
if (!checkNoSeqLoaded()) return;
|
||||
forward = false;
|
||||
isPlaying = false;
|
||||
wantPlaying = false;
|
||||
playbackObject.stepPlaybackBackward();
|
||||
updateFrameLabel();
|
||||
}
|
||||
|
||||
void FramePlaybackWindow::btnPauseClick()
|
||||
{
|
||||
isPlaying = false;
|
||||
wantPlaying = false;
|
||||
playbackObject.pausePlayback();
|
||||
updateFrameLabel();
|
||||
}
|
||||
|
||||
void FramePlaybackWindow::btnReverseClick()
|
||||
{
|
||||
if (!checkNoSeqLoaded()) return;
|
||||
forward = false;
|
||||
isPlaying = true;
|
||||
playbackObject.startPlaybackBackward();
|
||||
wantPlaying = true;
|
||||
if (!ui->ckWaitForTraffic->isChecked())
|
||||
{
|
||||
playbackObject.startPlaybackBackward();
|
||||
isPlaying = true;
|
||||
}
|
||||
updateFrameLabel();
|
||||
}
|
||||
|
||||
void FramePlaybackWindow::btnStopClick()
|
||||
{
|
||||
isPlaying = false;
|
||||
wantPlaying = false;
|
||||
haveIncomingTraffic = false;
|
||||
playbackObject.stopPlayback();
|
||||
if (seqItems.count() > 0)
|
||||
{
|
||||
@@ -574,20 +625,40 @@ void FramePlaybackWindow::btnStopClick()
|
||||
ui->tblSequence->setCurrentCell(0, 0);
|
||||
refreshIDList();
|
||||
}
|
||||
updateFrameLabel();
|
||||
}
|
||||
|
||||
void FramePlaybackWindow::btnPlayClick()
|
||||
{
|
||||
if (!checkNoSeqLoaded()) return;
|
||||
forward = true;
|
||||
isPlaying = true;
|
||||
playbackObject.startPlaybackForward();
|
||||
wantPlaying = true;
|
||||
if (!ui->ckWaitForTraffic->isChecked())
|
||||
{
|
||||
playbackObject.startPlaybackForward();
|
||||
isPlaying = true;
|
||||
}
|
||||
updateFrameLabel();
|
||||
}
|
||||
|
||||
void FramePlaybackWindow::btnFwdOneClick()
|
||||
{
|
||||
if (!checkNoSeqLoaded()) return;
|
||||
forward = true;
|
||||
isPlaying = false;
|
||||
wantPlaying = false;
|
||||
playbackObject.stepPlaybackForward();
|
||||
updateFrameLabel();
|
||||
}
|
||||
|
||||
bool FramePlaybackWindow::checkNoSeqLoaded()
|
||||
{
|
||||
if (seqItems.count() == 0)
|
||||
{
|
||||
QMessageBox::warning(this, "Warning", "Cannot begin playback until at\nleast one playback source is loaded.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void FramePlaybackWindow::changePlaybackSpeed(int newSpeed)
|
||||
|
||||
@@ -44,6 +44,7 @@ private slots:
|
||||
void useOrigTimingClicked();
|
||||
void getStatusUpdate(int frameNum);
|
||||
void EndOfFrameCache();
|
||||
void updatedFrames(int);
|
||||
|
||||
private:
|
||||
Ui::FramePlaybackWindow *ui;
|
||||
@@ -55,8 +56,10 @@ private:
|
||||
int currentSeqNum;
|
||||
FramePlaybackObject playbackObject;
|
||||
bool forward;
|
||||
bool wantPlaying;
|
||||
bool isPlaying;
|
||||
int currentPosition;
|
||||
bool haveIncomingTraffic = false;
|
||||
|
||||
void refreshIDList();
|
||||
void updateFrameLabel();
|
||||
@@ -66,6 +69,7 @@ private:
|
||||
void readSettings();
|
||||
void writeSettings();
|
||||
void calculateWhichBus();
|
||||
bool checkNoSeqLoaded();
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
#include "framesenderobject.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
FrameSenderObject::FrameSenderObject(const QVector<CANFrame> *frames)
|
||||
{
|
||||
mThread_p = new QThread();
|
||||
|
||||
statusCounter = 0;
|
||||
modelFrames = frames;
|
||||
dbcHandler = DBCHandler::getReference();
|
||||
}
|
||||
|
||||
FrameSenderObject::~FrameSenderObject()
|
||||
{
|
||||
mThread_p->quit();
|
||||
mThread_p->wait();
|
||||
delete mThread_p;
|
||||
}
|
||||
|
||||
void FrameSenderObject::piStart()
|
||||
{
|
||||
sendingTimer = new QTimer();
|
||||
sendingTimer->setTimerType(Qt::PreciseTimer);
|
||||
sendingTimer->setInterval(1);
|
||||
|
||||
connect(sendingTimer, &QTimer::timeout, this, &FrameSenderObject::timerTriggered);
|
||||
}
|
||||
|
||||
void FrameSenderObject::piStop()
|
||||
{
|
||||
sendingTimer->stop();
|
||||
delete sendingTimer;
|
||||
}
|
||||
|
||||
void FrameSenderObject::initialize()
|
||||
{
|
||||
if( mThread_p && (mThread_p != QThread::currentThread()) )
|
||||
{
|
||||
/* move ourself to the thread */
|
||||
moveToThread(mThread_p); /*TODO handle errors */
|
||||
/* connect started() */
|
||||
connect(mThread_p, SIGNAL(started()), this, SLOT(initialize()));
|
||||
/* start the thread */
|
||||
mThread_p->start(QThread::HighPriority);
|
||||
return;
|
||||
}
|
||||
|
||||
/* set started flag */
|
||||
//mStarted = true;
|
||||
|
||||
connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int)));
|
||||
|
||||
/* in multithread case, this will be called before entering thread event loop */
|
||||
return piStart();
|
||||
|
||||
}
|
||||
|
||||
void FrameSenderObject::finalize()
|
||||
{
|
||||
/* 1) execute in mThread_p context */
|
||||
if( mThread_p && (mThread_p != QThread::currentThread()) )
|
||||
{
|
||||
/* if thread is finished, it means we call this function for the second time so we can leave */
|
||||
if( !mThread_p->isFinished() )
|
||||
{
|
||||
/* we need to call piStop() */
|
||||
QMetaObject::invokeMethod(this, "finalize",
|
||||
Qt::BlockingQueuedConnection);
|
||||
/* 3) stop thread */
|
||||
mThread_p->quit();
|
||||
if(!mThread_p->wait()) {
|
||||
qDebug() << "can't stop thread";
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* 2) call piStop in mThread context */
|
||||
return piStop();
|
||||
}
|
||||
|
||||
void FrameSenderObject::startSending()
|
||||
{
|
||||
/* make sure we execute in mThread context */
|
||||
if( mThread_p && (mThread_p != QThread::currentThread()) ) {
|
||||
QMetaObject::invokeMethod(this, "startSending",
|
||||
Qt::BlockingQueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
sendingElapsed.start();
|
||||
sendingTimer->start();
|
||||
}
|
||||
|
||||
void FrameSenderObject::stopSending()
|
||||
{
|
||||
/* make sure we execute in mThread context */
|
||||
if( mThread_p && (mThread_p != QThread::currentThread()) ) {
|
||||
QMetaObject::invokeMethod(this, "stopPlayback",
|
||||
Qt::BlockingQueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
sendingTimer->stop(); //pushing this button halts automatic playback
|
||||
//emit statusUpdate(currentPosition);
|
||||
}
|
||||
|
||||
void FrameSenderObject::addSendRecord(FrameSendData record)
|
||||
{
|
||||
/* make sure we execute in mThread context */
|
||||
if( mThread_p && (mThread_p != QThread::currentThread()) ) {
|
||||
QMetaObject::invokeMethod(this, "addSendRecord",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_ARG(FrameSendData, record));
|
||||
return;
|
||||
}
|
||||
sendingData.append(record);
|
||||
}
|
||||
|
||||
void FrameSenderObject::removeSendRecord(int idx)
|
||||
{
|
||||
/* make sure we execute in mThread context */
|
||||
if( mThread_p && (mThread_p != QThread::currentThread()) ) {
|
||||
QMetaObject::invokeMethod(this, "removeSendRecord",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_ARG(int, idx));
|
||||
return;
|
||||
}
|
||||
sendingData.removeAt(idx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: Getting a reference to an item in a QList is a dangerous thing. If anyone
|
||||
* or anything happens to reallocate the list you're screwed. So, call this, do the
|
||||
* work, and drop the reference as soon as possible.
|
||||
*/
|
||||
FrameSendData *FrameSenderObject::getSendRecordRef(int idx)
|
||||
{
|
||||
if (idx < 0 || idx >= sendingData.count()) return nullptr;
|
||||
return &sendingData[idx];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every millisecond to set the system update figures and send frames if necessary.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="timerEventArgs"></param>
|
||||
///
|
||||
void FrameSenderObject::timerTriggered()
|
||||
{
|
||||
FrameSendData *sendData;
|
||||
Trigger *trigger;
|
||||
|
||||
sendingList.clear();
|
||||
if(mutex.tryLock())
|
||||
{
|
||||
/*
|
||||
* Requested tick interval was 1ms but the actual interval could be wildly different. So, we track
|
||||
* by counting microseconds and accumulating. This creates decent stability even for long intervals
|
||||
*/
|
||||
quint64 elapsed = sendingElapsed.nsecsElapsed() / 1000ul;
|
||||
if (elapsed == 0) elapsed = 1;
|
||||
sendingElapsed.start();
|
||||
sendingLastTimeStamp += elapsed;
|
||||
//qDebug() << sendingLastTimeStamp;
|
||||
//qDebug() << "El: " << elapsed;
|
||||
statusCounter++;
|
||||
for (int i = 0; i < sendingData.count(); i++)
|
||||
{
|
||||
sendData = &sendingData[i];
|
||||
if (!sendData->enabled)
|
||||
{
|
||||
if (sendData->triggers.count() > 0)
|
||||
{
|
||||
for (int j = 0; j < sendData->triggers.count(); j++) //resetting currCount when line is disabled
|
||||
{
|
||||
sendData->triggers[j].currCount = 0;
|
||||
}
|
||||
}
|
||||
continue; //abort any processing on this if it is not enabled.
|
||||
}
|
||||
if (sendData->triggers.count() == 0)
|
||||
{
|
||||
//qDebug() << "No triggers to process";
|
||||
break;
|
||||
}
|
||||
for (int j = 0; j < sendData->triggers.count(); j++)
|
||||
{
|
||||
trigger = &sendData->triggers[j];
|
||||
//if ( (trigger->currCount >= trigger->maxCount) || (trigger->maxCount == -1) ) continue; //don't process if we've sent max frames we were supposed to
|
||||
if (!trigger->readyCount) continue; //don't tick if not ready to tick
|
||||
//is it time to fire?
|
||||
trigger->msCounter += elapsed; //gives proper tracking even if timer doesn't fire as fast as it should
|
||||
//qDebug() << trigger->msCounter;
|
||||
if (trigger->msCounter >= (trigger->milliseconds * 1000))
|
||||
{
|
||||
trigger->msCounter -= (trigger->milliseconds * 1000);
|
||||
sendData->count++;
|
||||
trigger->currCount++;
|
||||
doModifiers(i);
|
||||
//updateGridRow(i);
|
||||
//qDebug() << "About to try to send a frame";
|
||||
//CANConManager::getInstance()->sendFrame(sendingData[i]);
|
||||
sendingList.append(sendingData[i]); //queue it instead of immediate sending
|
||||
if (trigger->ID > 0) trigger->readyCount = false; //reset flag if this is a timed ID trigger
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if we have any frames to send after the above then send as a batch
|
||||
if (sendingList.count() > 0) CANConManager::getInstance()->sendFrames(sendingList);
|
||||
mutex.unlock();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "framesenderobject::handleTick() couldn't get mutex, elapsed is: " << sendingElapsed.elapsed();
|
||||
}
|
||||
}
|
||||
|
||||
void FrameSenderObject::buildFrameCache()
|
||||
{
|
||||
CANFrame thisFrame;
|
||||
frameCache.clear();
|
||||
for (int i = 0; i < modelFrames->count(); i++)
|
||||
{
|
||||
thisFrame = modelFrames->at(i);
|
||||
if (!frameCache.contains(thisFrame.frameId()))
|
||||
{
|
||||
frameCache.insert(thisFrame.frameId(), thisFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
frameCache[thisFrame.frameId()] = thisFrame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//remember, negative numbers are special -1 = all frames deleted, -2 = totally new set of frames.
|
||||
void FrameSenderObject::updatedFrames(int numFrames)
|
||||
{
|
||||
CANFrame thisFrame;
|
||||
if (numFrames == -1) //all frames deleted.
|
||||
{
|
||||
}
|
||||
else if (numFrames == -2) //all new set of frames.
|
||||
{
|
||||
buildFrameCache();
|
||||
}
|
||||
else //just got some new frames. See if they are relevant.
|
||||
{
|
||||
if (numFrames > modelFrames->count()) return;
|
||||
qDebug() << "New frames in sender window";
|
||||
//run through the supposedly new frames in order
|
||||
for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++)
|
||||
{
|
||||
thisFrame = modelFrames->at(i);
|
||||
if (!frameCache.contains(thisFrame.frameId()))
|
||||
{
|
||||
frameCache.insert(thisFrame.frameId(), thisFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
frameCache[thisFrame.frameId()] = thisFrame;
|
||||
}
|
||||
processIncomingFrame(&thisFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FrameSenderObject::processIncomingFrame(CANFrame *frame)
|
||||
{
|
||||
for (int sd = 0; sd < sendingData.count(); sd++)
|
||||
{
|
||||
if (sendingData[sd].triggers.count() == 0) continue;
|
||||
bool passedChecks = true;
|
||||
for (int trig = 0; trig < sendingData[sd].triggers.count(); trig++)
|
||||
{
|
||||
Trigger *thisTrigger = &sendingData[sd].triggers[trig];
|
||||
//need to ensure that this trigger is actually related to frames incoming.
|
||||
//Otherwise ignore it here. Only frames that have BUS and/or ID set as trigger will work here.
|
||||
if (!(thisTrigger->triggerMask & (TriggerMask::TRG_BUS | TriggerMask::TRG_ID) ) ) continue;
|
||||
|
||||
passedChecks = true;
|
||||
//qDebug() << "Trigger ID: " << thisTrigger->ID;
|
||||
//qDebug() << "Frame ID: " << frame->frameId();
|
||||
|
||||
//Check to see if we have a bus trigger condition and if so does it match
|
||||
if (thisTrigger->bus != frame->bus && (thisTrigger->triggerMask & TriggerMask::TRG_BUS) )
|
||||
passedChecks = false;
|
||||
|
||||
//check to see if we have an ID trigger condition and if so does it match
|
||||
if ((thisTrigger->triggerMask & TriggerMask::TRG_ID) && (uint32_t)thisTrigger->ID != frame->frameId() )
|
||||
passedChecks = false;
|
||||
|
||||
//check to see if we're limiting the trigger by max count and have we reached that count?
|
||||
if ( (thisTrigger->triggerMask & TriggerMask::TRG_COUNT) && (thisTrigger->currCount >= thisTrigger->maxCount) )
|
||||
passedChecks = false;
|
||||
|
||||
//if the above passed then are we triggering not only on ID but also signal?
|
||||
if (passedChecks && (thisTrigger->triggerMask & (TriggerMask::TRG_SIGNAL | TriggerMask::TRG_ID) ) )
|
||||
{
|
||||
bool sigCheckPassed = false;
|
||||
DBC_MESSAGE *msg = dbcHandler->findMessage(thisTrigger->ID);
|
||||
if (msg)
|
||||
{
|
||||
DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(thisTrigger->sigName);
|
||||
if (sig)
|
||||
{
|
||||
//first of all, is this signal really in this message we got?
|
||||
if (sig->isSignalInMessage(*frame)) sigCheckPassed = true;
|
||||
//if it was and we're also filtering on value then try that next
|
||||
if (sigCheckPassed && (thisTrigger->triggerMask & TriggerMask::TRG_SIGVAL))
|
||||
{
|
||||
double sigval = 0.0;
|
||||
if (sig->processAsDouble(*frame, sigval))
|
||||
{
|
||||
if (abs(sigval - thisTrigger->sigValueDbl) > 0.001)
|
||||
{
|
||||
sigCheckPassed = false;
|
||||
}
|
||||
}
|
||||
else sigCheckPassed = false;
|
||||
}
|
||||
}
|
||||
else sigCheckPassed = false;
|
||||
}
|
||||
else sigCheckPassed = false;
|
||||
passedChecks &= sigCheckPassed; //passedChecks can only be true if both are
|
||||
}
|
||||
|
||||
//if all the above says it's OK then we'll go ahead and send that message.
|
||||
//If a message that comes through here has a MS value then we use it as a delay
|
||||
//after the check passes. This allows for delaying the sending of the frame if that
|
||||
//is required. Otherwise, just send it immediately.
|
||||
if (passedChecks)
|
||||
{
|
||||
if (thisTrigger->milliseconds == 0) //immediate reply
|
||||
{
|
||||
thisTrigger->currCount++;
|
||||
sendingData[sd].count++;
|
||||
doModifiers(sd);
|
||||
//updateGridRow(sd);
|
||||
CANConManager::getInstance()->sendFrame(sendingData[sd]);
|
||||
}
|
||||
else //delayed sending frame
|
||||
{
|
||||
thisTrigger->readyCount = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// given an index into the sendingData list we run the modifiers that it has set up
|
||||
/// </summary>
|
||||
/// <param name="idx">The index into the sendingData list</param>
|
||||
void FrameSenderObject::doModifiers(int idx)
|
||||
{
|
||||
int shadowReg = 0; //shadow register we use to accumulate results
|
||||
int first=0, second=0;
|
||||
|
||||
FrameSendData *sendData = &sendingData[idx];
|
||||
Modifier *mod;
|
||||
ModifierOp op;
|
||||
|
||||
if (sendData->modifiers.count() == 0) return; //if no modifiers just leave right now
|
||||
|
||||
//qDebug() << "Executing mods";
|
||||
|
||||
for (int i = 0; i < sendData->modifiers.count(); i++)
|
||||
{
|
||||
mod = &sendData->modifiers[i];
|
||||
for (int j = 0; j < mod->operations.count(); j++)
|
||||
{
|
||||
op = mod->operations.at(j);
|
||||
if (op.first.ID == -1)
|
||||
{
|
||||
first = shadowReg;
|
||||
}
|
||||
else first = fetchOperand(idx, op.first);
|
||||
second = fetchOperand(idx, op.second);
|
||||
switch (op.operation)
|
||||
{
|
||||
case ADDITION:
|
||||
shadowReg = first + second;
|
||||
break;
|
||||
case AND:
|
||||
shadowReg = first & second;
|
||||
break;
|
||||
case DIVISION:
|
||||
shadowReg = first / second;
|
||||
break;
|
||||
case MULTIPLICATION:
|
||||
shadowReg = first * second;
|
||||
break;
|
||||
case OR:
|
||||
shadowReg = first | second;
|
||||
break;
|
||||
case SUBTRACTION:
|
||||
shadowReg = first - second;
|
||||
break;
|
||||
case XOR:
|
||||
shadowReg = first ^ second;
|
||||
break;
|
||||
case MOD:
|
||||
shadowReg = first % second;
|
||||
}
|
||||
}
|
||||
//Finally, drop the result into the proper data byte
|
||||
QByteArray newArr(sendData->payload());
|
||||
newArr[mod->destByte] = (char) shadowReg;
|
||||
sendData->setPayload(newArr);
|
||||
}
|
||||
}
|
||||
|
||||
int FrameSenderObject::fetchOperand(int idx, ModifierOperand op)
|
||||
{
|
||||
CANFrame *tempFrame = nullptr;
|
||||
if (op.ID == 0) //numeric constant
|
||||
{
|
||||
if (op.notOper) return ~op.databyte;
|
||||
else return op.databyte;
|
||||
}
|
||||
else if (op.ID == -2) //fetch data from a data byte within the output frame
|
||||
{
|
||||
if (op.notOper) return ~((unsigned char)sendingData.at(idx).payload()[op.databyte]);
|
||||
else return (unsigned char)sendingData.at(idx).payload()[op.databyte];
|
||||
}
|
||||
else //look up external data byte
|
||||
{
|
||||
tempFrame = lookupFrame(op.ID, op.bus);
|
||||
if (tempFrame != nullptr)
|
||||
{
|
||||
if (op.notOper) return ~((unsigned char)tempFrame->payload()[op.databyte]);
|
||||
else return (unsigned char)tempFrame->payload()[op.databyte];
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to find the most recent frame given the input criteria
|
||||
/// </summary>
|
||||
/// <param name="ID">The ID to find</param>
|
||||
/// <param name="bus">Which bus to look on (-1 if you don't care)</param>
|
||||
/// <returns></returns>
|
||||
CANFrame* FrameSenderObject::lookupFrame(int ID, int bus)
|
||||
{
|
||||
if (!frameCache.contains(ID)) return nullptr;
|
||||
|
||||
if (bus == -1 || frameCache[ID].bus == bus) return &frameCache[ID];
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#ifndef FRAMESENDEROBJECT_H
|
||||
#define FRAMESENDEROBJECT_H
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QTimer>
|
||||
#include <QHash>
|
||||
#include <QThread>
|
||||
#include <QDebug>
|
||||
#include <QMutex>
|
||||
#include "can_structs.h"
|
||||
#include "connections/canconmanager.h"
|
||||
#include "can_trigger_structs.h"
|
||||
#include "dbc/dbchandler.h"
|
||||
|
||||
class FrameSenderObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FrameSenderObject(const QVector<CANFrame> *frames);
|
||||
~FrameSenderObject();
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief start the device. This calls piStarted
|
||||
* @note starts the working thread if required (piStart is called in the working thread context)
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
/**
|
||||
* @brief stop the device. This calls piStop
|
||||
* @note if a working thread is used, piStop is called before exiting the working thread
|
||||
*/
|
||||
void finalize();
|
||||
|
||||
void startSending();
|
||||
void stopSending();
|
||||
|
||||
void addSendRecord(FrameSendData record);
|
||||
void removeSendRecord(int idx);
|
||||
FrameSendData *getSendRecordRef(int idx);
|
||||
|
||||
signals:
|
||||
|
||||
private slots:
|
||||
void timerTriggered();
|
||||
void updatedFrames(int);
|
||||
|
||||
private:
|
||||
QList<CANFrame> sendingList;
|
||||
int currentPosition;
|
||||
QTimer *sendingTimer;
|
||||
QElapsedTimer sendingElapsed;
|
||||
quint64 sendingLastTimeStamp;
|
||||
int statusCounter;
|
||||
QList<FrameSendData> sendingData;
|
||||
QThread* mThread_p;
|
||||
QHash<int, CANFrame> frameCache; //hash with frame ID as the key and the most recent frame as the value
|
||||
const QVector<CANFrame> *modelFrames;
|
||||
bool inhibitChanged = false;
|
||||
QMutex mutex;
|
||||
DBCHandler *dbcHandler;
|
||||
|
||||
void doModifiers(int);
|
||||
int fetchOperand(int, ModifierOperand);
|
||||
CANFrame* lookupFrame(int, int);
|
||||
void buildFrameCache();
|
||||
void processIncomingFrame(CANFrame *frame);
|
||||
|
||||
/**
|
||||
* @brief starts the device
|
||||
*/
|
||||
void piStart();
|
||||
|
||||
/**
|
||||
* @brief stops the device
|
||||
*/
|
||||
void piStop();
|
||||
};
|
||||
|
||||
#endif // FRAMESENDEROBJECT_H
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "mainwindow.h"
|
||||
#include "helpwindow.h"
|
||||
#include "connections/canconmanager.h"
|
||||
#include "triggerdialog.h"
|
||||
|
||||
/*
|
||||
* notes: need to ensure that you grab pointers when modifying data structures and dont
|
||||
@@ -24,6 +25,8 @@ FrameSenderWindow::FrameSenderWindow(const QVector<CANFrame> *frames, QWidget *p
|
||||
|
||||
modelFrames = frames;
|
||||
|
||||
dbcHandler = DBCHandler::getReference();
|
||||
|
||||
intervalTimer = new QTimer();
|
||||
intervalTimer->setTimerType(Qt::PreciseTimer);
|
||||
intervalTimer->setInterval(1);
|
||||
@@ -32,6 +35,7 @@ FrameSenderWindow::FrameSenderWindow(const QVector<CANFrame> *frames, QWidget *p
|
||||
createBlankRow();
|
||||
|
||||
connect(ui->tableSender, SIGNAL(cellChanged(int,int)), this, SLOT(onCellChanged(int,int)));
|
||||
connect(ui->tableSender, SIGNAL(cellDoubleClicked(int, int)), SLOT(onCellDoubleTap(int, int)));
|
||||
connect(intervalTimer, SIGNAL(timeout()), this, SLOT(handleTick()));
|
||||
connect(ui->btnClearGrid, SIGNAL(clicked(bool)), this, SLOT(clearGrid()));
|
||||
connect(ui->btnDisableAll, SIGNAL(clicked(bool)), this, SLOT(disableAll()));
|
||||
@@ -48,19 +52,20 @@ FrameSenderWindow::FrameSenderWindow(const QVector<CANFrame> *frames, QWidget *p
|
||||
void FrameSenderWindow::setupGrid()
|
||||
{
|
||||
QStringList headers;
|
||||
headers << "En" << "Bus" << "ID" << "Len" << "Ext" << "Rem" << "Data"
|
||||
headers << "En" << "Bus" << "ID" << "MsgName" << "Len" << "Ext" << "Rem" << "Data"
|
||||
<< "Trigger" << "Modifications" << "Count";
|
||||
ui->tableSender->setColumnCount(10);
|
||||
ui->tableSender->setColumnWidth(0, 50);
|
||||
ui->tableSender->setColumnWidth(1, 50);
|
||||
ui->tableSender->setColumnWidth(2, 50);
|
||||
ui->tableSender->setColumnWidth(3, 50);
|
||||
ui->tableSender->setColumnWidth(4, 50);
|
||||
ui->tableSender->setColumnWidth(5, 50);
|
||||
ui->tableSender->setColumnWidth(6, 220);
|
||||
ui->tableSender->setColumnWidth(7, 220);
|
||||
ui->tableSender->setColumnWidth(8, 220);
|
||||
ui->tableSender->setColumnWidth(9, 80);
|
||||
ui->tableSender->setColumnCount(11);
|
||||
ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_EN, 50);
|
||||
ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_BUS, 50);
|
||||
ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_ID, 50);
|
||||
ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_MSGNAME, 150);
|
||||
ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_LEN, 50);
|
||||
ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_EXT, 50);
|
||||
ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_REM, 50);
|
||||
ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_DATA, 220);
|
||||
ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_TRIGGER, 270);
|
||||
ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_MODS, 270);
|
||||
ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_COUNT, 80);
|
||||
ui->tableSender->setHorizontalHeaderLabels(headers);
|
||||
}
|
||||
|
||||
@@ -100,22 +105,26 @@ void FrameSenderWindow::createBlankRow()
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
inhibitChanged = true;
|
||||
ui->tableSender->setItem(row, 0, item);
|
||||
ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_EN, item);
|
||||
|
||||
item = new QTableWidgetItem();
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
ui->tableSender->setItem(row, 4, item);
|
||||
ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_EXT, item);
|
||||
|
||||
item = new QTableWidgetItem();
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
ui->tableSender->setItem(row, 5, item);
|
||||
ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_REM, item);
|
||||
|
||||
for (int i = 1; i < 10; i++)
|
||||
for (int i = 1; i <= ST_COLS::SENDTAB_COL_COUNT; i++)
|
||||
{
|
||||
if (i != 4 && i != 5) {
|
||||
if (i != ST_COLS::SENDTAB_COL_EXT && i != ST_COLS::SENDTAB_COL_REM) {
|
||||
item = new QTableWidgetItem("");
|
||||
//msgname is looked up via DBC interface and not able to be edited.
|
||||
//Though, it might be perhaps interesting to allow renaming it to
|
||||
//a shorter name in some cases? Jury is still out on this one...
|
||||
if (i == ST_COLS::SENDTAB_COL_MSGNAME) item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
||||
ui->tableSender->setItem(row, i, item);
|
||||
}
|
||||
}
|
||||
@@ -178,30 +187,79 @@ void FrameSenderWindow::processIncomingFrame(CANFrame *frame)
|
||||
for (int sd = 0; sd < sendingData.count(); sd++)
|
||||
{
|
||||
if (sendingData[sd].triggers.count() == 0) continue;
|
||||
bool passedChecks = true;
|
||||
for (int trig = 0; trig < sendingData[sd].triggers.count(); trig++)
|
||||
{
|
||||
Trigger *thisTrigger = &sendingData[sd].triggers[trig];
|
||||
qDebug() << "Trigger ID: " << thisTrigger->ID;
|
||||
qDebug() << "Frame ID: " << frame->frameId();
|
||||
if (thisTrigger->ID > 0 && (uint32_t)thisTrigger->ID == frame->frameId())
|
||||
//need to ensure that this trigger is actually related to frames incoming.
|
||||
//Otherwise ignore it here. Only frames that have BUS and/or ID set as trigger will work here.
|
||||
if (!(thisTrigger->triggerMask & (TriggerMask::TRG_BUS | TriggerMask::TRG_ID) ) ) continue;
|
||||
|
||||
passedChecks = true;
|
||||
//qDebug() << "Trigger ID: " << thisTrigger->ID;
|
||||
//qDebug() << "Frame ID: " << frame->frameId();
|
||||
|
||||
//Check to see if we have a bus trigger condition and if so does it match
|
||||
if (thisTrigger->bus != frame->bus && (thisTrigger->triggerMask & TriggerMask::TRG_BUS) )
|
||||
passedChecks = false;
|
||||
|
||||
//check to see if we have an ID trigger condition and if so does it match
|
||||
if ((thisTrigger->triggerMask & TriggerMask::TRG_ID) && (uint32_t)thisTrigger->ID != frame->frameId() )
|
||||
passedChecks = false;
|
||||
|
||||
//check to see if we're limiting the trigger by max count and have we reached that count?
|
||||
if ( (thisTrigger->triggerMask & TriggerMask::TRG_COUNT) && (thisTrigger->currCount >= thisTrigger->maxCount) )
|
||||
passedChecks = false;
|
||||
|
||||
//if the above passed then are we triggering not only on ID but also signal?
|
||||
if (passedChecks && (thisTrigger->triggerMask & (TriggerMask::TRG_SIGNAL | TriggerMask::TRG_ID) ) )
|
||||
{
|
||||
if (thisTrigger->bus == frame->bus || thisTrigger->bus == -1)
|
||||
bool sigCheckPassed = false;
|
||||
DBC_MESSAGE *msg = dbcHandler->findMessage(thisTrigger->ID);
|
||||
if (msg)
|
||||
{
|
||||
if (thisTrigger->currCount < thisTrigger->maxCount)
|
||||
DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(thisTrigger->sigName);
|
||||
if (sig)
|
||||
{
|
||||
if (thisTrigger->milliseconds == 0) //immediate reply
|
||||
//first of all, is this signal really in this message we got?
|
||||
if (sig->isSignalInMessage(*frame)) sigCheckPassed = true;
|
||||
//if it was and we're also filtering on value then try that next
|
||||
if (sigCheckPassed && (thisTrigger->triggerMask & TriggerMask::TRG_SIGVAL))
|
||||
{
|
||||
thisTrigger->currCount++;
|
||||
sendingData[sd].count++;
|
||||
doModifiers(sd);
|
||||
updateGridRow(sd);
|
||||
CANConManager::getInstance()->sendFrame(sendingData[sd]);
|
||||
}
|
||||
else //delayed sending frame
|
||||
{
|
||||
thisTrigger->readyCount = true;
|
||||
double sigval = 0.0;
|
||||
if (sig->processAsDouble(*frame, sigval))
|
||||
{
|
||||
if (abs(sigval - thisTrigger->sigValueDbl) > 0.001)
|
||||
{
|
||||
sigCheckPassed = false;
|
||||
}
|
||||
}
|
||||
else sigCheckPassed = false;
|
||||
}
|
||||
}
|
||||
else sigCheckPassed = false;
|
||||
}
|
||||
else sigCheckPassed = false;
|
||||
passedChecks &= sigCheckPassed; //passedChecks can only be true if both are
|
||||
}
|
||||
|
||||
//if all the above says it's OK then we'll go ahead and send that message.
|
||||
//If a message that comes through here has a MS value then we use it as a delay
|
||||
//after the check passes. This allows for delaying the sending of the frame if that
|
||||
//is required. Otherwise, just send it immediately.
|
||||
if (passedChecks)
|
||||
{
|
||||
if (thisTrigger->milliseconds == 0) //immediate reply
|
||||
{
|
||||
thisTrigger->currCount++;
|
||||
sendingData[sd].count++;
|
||||
doModifiers(sd);
|
||||
updateGridRow(sd);
|
||||
CANConManager::getInstance()->sendFrame(sendingData[sd]);
|
||||
}
|
||||
else //delayed sending frame
|
||||
{
|
||||
thisTrigger->readyCount = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,7 +270,7 @@ void FrameSenderWindow::enableAll()
|
||||
{
|
||||
for (int i = 0; i < ui->tableSender->rowCount() - 1; i++)
|
||||
{
|
||||
ui->tableSender->item(i, 0)->setCheckState(Qt::Checked);
|
||||
ui->tableSender->item(i, ST_COLS::SENDTAB_COL_EN)->setCheckState(Qt::Checked);
|
||||
sendingData[i].enabled = true;
|
||||
}
|
||||
}
|
||||
@@ -221,7 +279,7 @@ void FrameSenderWindow::disableAll()
|
||||
{
|
||||
for (int i = 0; i < ui->tableSender->rowCount() - 1; i++)
|
||||
{
|
||||
ui->tableSender->item(i, 0)->setCheckState(Qt::Unchecked);
|
||||
ui->tableSender->item(i, ST_COLS::SENDTAB_COL_EN)->setCheckState(Qt::Unchecked);
|
||||
sendingData[i].enabled = false;
|
||||
}
|
||||
}
|
||||
@@ -290,6 +348,8 @@ void FrameSenderWindow::loadGrid()
|
||||
}
|
||||
}
|
||||
|
||||
createBlankRow();
|
||||
|
||||
setupGrid();
|
||||
}
|
||||
|
||||
@@ -307,14 +367,14 @@ void FrameSenderWindow::saveSenderFile(QString filename)
|
||||
for (int c = 0; c < sendingData.count(); c++)
|
||||
{
|
||||
outString.clear();
|
||||
if (ui->tableSender->item(c, 0)->checkState() == Qt::Checked)
|
||||
if (ui->tableSender->item(c, ST_COLS::SENDTAB_COL_EN)->checkState() == Qt::Checked)
|
||||
{
|
||||
outString = "T#";
|
||||
}
|
||||
else outString = "F#";
|
||||
for (int i = 1; i < 9; i++)
|
||||
for (int i = 1; i < ST_COLS::SENDTAB_COL_COUNT; i++)
|
||||
{
|
||||
if (i == 4 || i == 5) {
|
||||
if (i == ST_COLS::SENDTAB_COL_EXT || i == ST_COLS::SENDTAB_COL_REM) {
|
||||
if (ui->tableSender->item(c, i)->checkState() == Qt::Checked) {
|
||||
outString.append("T");
|
||||
} else {
|
||||
@@ -357,28 +417,28 @@ void FrameSenderWindow::loadSenderFile(QString filename)
|
||||
int row = ui->tableSender->rowCount();
|
||||
ui->tableSender->insertRow(row);
|
||||
QTableWidgetItem *item = new QTableWidgetItem();
|
||||
ui->tableSender->setItem(row, 0, item);
|
||||
ui->tableSender->item(row, 0)->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_EN, item);
|
||||
ui->tableSender->item(row, ST_COLS::SENDTAB_COL_EN)->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
|
||||
item = new QTableWidgetItem();
|
||||
ui->tableSender->setItem(row, 4, item);
|
||||
ui->tableSender->item(row, 4)->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_EXT, item);
|
||||
ui->tableSender->item(row, ST_COLS::SENDTAB_COL_EN)->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
|
||||
item = new QTableWidgetItem();
|
||||
ui->tableSender->setItem(row, 5, item);
|
||||
ui->tableSender->item(row, 5)->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_REM, item);
|
||||
ui->tableSender->item(row, ST_COLS::SENDTAB_COL_REM)->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
|
||||
if (tokens[0] == "T")
|
||||
{
|
||||
ui->tableSender->item(row, 0)->setCheckState(Qt::Checked);
|
||||
ui->tableSender->item(row, ST_COLS::SENDTAB_COL_EN)->setCheckState(Qt::Checked);
|
||||
}
|
||||
else ui->tableSender->item(row, 0)->setCheckState(Qt::Unchecked);
|
||||
else ui->tableSender->item(row, ST_COLS::SENDTAB_COL_EN)->setCheckState(Qt::Unchecked);
|
||||
if (tokens.length() >= 9) {
|
||||
for (int i = 1; i < 9; i++)
|
||||
for (int i = 1; i < ST_COLS::SENDTAB_COL_COUNT; i++)
|
||||
{
|
||||
if (i != 4 && i != 5) {
|
||||
if (i != ST_COLS::SENDTAB_COL_EXT && i != ST_COLS::SENDTAB_COL_REM) {
|
||||
ui->tableSender->setItem(row, i, new QTableWidgetItem(QString(tokens[i])));
|
||||
} else {
|
||||
if (tokens[i] == "T") {
|
||||
@@ -387,15 +447,15 @@ void FrameSenderWindow::loadSenderFile(QString filename)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ui->tableSender->setItem(row, 1, new QTableWidgetItem(QString(tokens[1])));
|
||||
ui->tableSender->setItem(row, 2, new QTableWidgetItem(QString(tokens[2])));
|
||||
ui->tableSender->setItem(row, 3, new QTableWidgetItem(QString(tokens[3])));
|
||||
ui->tableSender->setItem(row, 6, new QTableWidgetItem(QString(tokens[4])));
|
||||
ui->tableSender->setItem(row, 7, new QTableWidgetItem(QString(tokens[5])));
|
||||
ui->tableSender->setItem(row, 8, new QTableWidgetItem(QString(tokens[6])));
|
||||
ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_BUS, new QTableWidgetItem(QString(tokens[1])));
|
||||
ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_ID, new QTableWidgetItem(QString(tokens[2])));
|
||||
ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_LEN, new QTableWidgetItem(QString(tokens[3])));
|
||||
ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_DATA, new QTableWidgetItem(QString(tokens[4])));
|
||||
ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_TRIGGER, new QTableWidgetItem(QString(tokens[5])));
|
||||
ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_MODS, new QTableWidgetItem(QString(tokens[6])));
|
||||
}
|
||||
inhibitChanged = false;
|
||||
for (int k = 0; k < 9; k++) processCellChange(row, k);
|
||||
for (int k = 0; k < ST_COLS::SENDTAB_COL_COUNT; k++) processCellChange(row, k);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -403,6 +463,45 @@ void FrameSenderWindow::loadSenderFile(QString filename)
|
||||
delete inFile;
|
||||
}
|
||||
|
||||
void FrameSenderWindow::onCellDoubleTap(int row, int column)
|
||||
{
|
||||
if (column == ST_COLS::SENDTAB_COL_TRIGGER)
|
||||
{
|
||||
if (row == ui->tableSender->rowCount() - 1)
|
||||
{
|
||||
createBlankRow();
|
||||
}
|
||||
if (row >= sendingData.count())
|
||||
{
|
||||
FrameSendData tempData;
|
||||
tempData.enabled = false;
|
||||
tempData.setFrameType(QCanBusFrame::DataFrame);
|
||||
tempData.setExtendedFrameFormat(false);
|
||||
sendingData.append(tempData);
|
||||
}
|
||||
sendData = nullptr;
|
||||
sendData = &sendingData[row];
|
||||
td = new TriggerDialog(sendData->triggers);
|
||||
if (td->exec() == QDialog::Accepted)
|
||||
{
|
||||
sendData->triggers.clear();
|
||||
sendData->triggers.append(td->getUpdatedTriggers());
|
||||
//now have to generate the actual trigger text
|
||||
QString output;
|
||||
foreach (Trigger trig, sendData->triggers)
|
||||
{
|
||||
output += td->buildEntry(trig) + ",";
|
||||
}
|
||||
output.chop(1); //don't want the trailing ,
|
||||
inhibitChanged = true;
|
||||
ui->tableSender->item(row, ST_COLS::SENDTAB_COL_TRIGGER)->setText(output);
|
||||
inhibitChanged = false;
|
||||
}
|
||||
delete td;
|
||||
td = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void FrameSenderWindow::onCellChanged(int row, int col)
|
||||
{
|
||||
if (inhibitChanged) return;
|
||||
@@ -424,44 +523,54 @@ void FrameSenderWindow::handleTick()
|
||||
{
|
||||
FrameSendData *sendData;
|
||||
Trigger *trigger;
|
||||
int elapsed = elapsedTimer.restart();
|
||||
if (elapsed == 0) elapsed = 1;
|
||||
//Modifier modifier;
|
||||
for (int i = 0; i < sendingData.count(); i++)
|
||||
if(mutex.tryLock())
|
||||
{
|
||||
sendData = &sendingData[i];
|
||||
if (!sendData->enabled)
|
||||
int elapsed = elapsedTimer.restart();
|
||||
if (elapsed == 0) elapsed = 1;
|
||||
//Modifier modifier;
|
||||
for (int i = 0; i < sendingData.count(); i++)
|
||||
{
|
||||
if (sendData->triggers.count() > 0)
|
||||
sendData = &sendingData[i];
|
||||
if (!sendData->enabled)
|
||||
{
|
||||
for (int j = 0; j < sendData->triggers.count(); j++) //resetting currCount when line is disabled
|
||||
if (sendData->triggers.count() > 0)
|
||||
{
|
||||
sendData->triggers[j].currCount = 0;
|
||||
for (int j = 0; j < sendData->triggers.count(); j++) //resetting currCount when line is disabled
|
||||
{
|
||||
sendData->triggers[j].currCount = 0;
|
||||
}
|
||||
}
|
||||
continue; //abort any processing on this if it is not enabled.
|
||||
}
|
||||
if (sendData->triggers.count() == 0) break;
|
||||
for (int j = 0; j < sendData->triggers.count(); j++)
|
||||
{
|
||||
trigger = &sendData->triggers[j];
|
||||
if (trigger->currCount >= trigger->maxCount) continue; //don't process if we've sent max frames we were supposed to
|
||||
if (!trigger->readyCount) continue; //don't tick if not ready to tick
|
||||
//is it time to fire?
|
||||
trigger->msCounter += elapsed; //gives proper tracking even if timer doesn't fire as fast as it should
|
||||
if (trigger->msCounter >= trigger->milliseconds)
|
||||
{
|
||||
trigger->msCounter = 0;
|
||||
sendData->count++;
|
||||
trigger->currCount++;
|
||||
doModifiers(i);
|
||||
updateGridRow(i);
|
||||
//qDebug() << "About to try to send a frame";
|
||||
CANConManager::getInstance()->sendFrame(sendingData[i]);
|
||||
if (trigger->ID > 0) trigger->readyCount = false; //reset flag if this is a timed ID trigger
|
||||
}
|
||||
}
|
||||
continue; //abort any processing on this if it is not enabled.
|
||||
}
|
||||
if (sendData->triggers.count() == 0) return;
|
||||
for (int j = 0; j < sendData->triggers.count(); j++)
|
||||
{
|
||||
trigger = &sendData->triggers[j];
|
||||
if (trigger->currCount >= trigger->maxCount) continue; //don't process if we've sent max frames we were supposed to
|
||||
if (!trigger->readyCount) continue; //don't tick if not ready to tick
|
||||
//is it time to fire?
|
||||
trigger->msCounter += elapsed; //gives proper tracking even if timer doesn't fire as fast as it should
|
||||
if (trigger->msCounter >= trigger->milliseconds)
|
||||
{
|
||||
trigger->msCounter = 0;
|
||||
sendData->count++;
|
||||
trigger->currCount++;
|
||||
doModifiers(i);
|
||||
updateGridRow(i);
|
||||
qDebug() << "About to try to send a frame";
|
||||
CANConManager::getInstance()->sendFrame(sendingData[i]);
|
||||
if (trigger->ID > 0) trigger->readyCount = false; //reset flag if this is a timed ID trigger
|
||||
}
|
||||
}
|
||||
|
||||
mutex.unlock();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "framesenderwindow::handleTick() couldn't get mutex, elapsed is: " << elapsedTimer.elapsed();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -479,7 +588,7 @@ void FrameSenderWindow::doModifiers(int idx)
|
||||
|
||||
if (sendData->modifiers.count() == 0) return; //if no modifiers just leave right now
|
||||
|
||||
qDebug() << "Executing mods";
|
||||
//qDebug() << "Executing mods";
|
||||
|
||||
for (int i = 0; i < sendData->modifiers.count(); i++)
|
||||
{
|
||||
@@ -584,6 +693,12 @@ void FrameSenderWindow::processModifierText(int line)
|
||||
//Example line:
|
||||
//d0 = D0 + 1,d1 = id:0x200:d3 + id:0x200:d4 AND 0xF0 - Original version
|
||||
//D0=D0+1,D1=ID:0x200:D3+ID:0x200:D4&0xF0
|
||||
|
||||
//[BMS_TargetVoltage]=45 would set the value of signal BMS_TargetVoltage to 45
|
||||
|
||||
//[BMS_TargetVoltage]=[0x234:BMS_CurrentVoltage] + 4 would instead grab the value
|
||||
//of BMS_CurrentVoltage from ID 0x234, add 4 to it, and set BMS_TargetVoltage to that value.
|
||||
|
||||
//This is certainly much harder to parse than the trigger definitions.
|
||||
//the left side of the = has to be D0 to D7. After that there is a string of
|
||||
//data. Spaces used to be required but no longer are. This makes parsing harder but data entry easier
|
||||
@@ -601,22 +716,33 @@ void FrameSenderWindow::processModifierText(int line)
|
||||
Modifier thisMod;
|
||||
thisMod.destByte = 0;
|
||||
|
||||
QString leftSide = Utility::grabAlphaNumeric(mods[i]);
|
||||
if (leftSide.startsWith("D") && leftSide.length() == 2)
|
||||
QRegularExpression regex;
|
||||
QRegularExpressionMatch match;
|
||||
|
||||
regex.setPattern("^\\[(\\w+)]=(\\w+)");
|
||||
match = regex.match(mods[i]);
|
||||
if (match.hasMatch())
|
||||
{
|
||||
thisMod.destByte = leftSide.right(1).toInt();
|
||||
thisMod.destByte = -1;
|
||||
thisMod.signalName = match.captured(1);
|
||||
mods[i] = match.captured(2);
|
||||
thisMod.operations.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Something wrong with lefthand val";
|
||||
continue;
|
||||
}
|
||||
if (!(Utility::grabOperation(mods[i]) == "="))
|
||||
{
|
||||
qDebug() << "Err: No = after lefthand val";
|
||||
continue;
|
||||
QString leftSide = Utility::grabAlphaNumeric(mods[i]);
|
||||
if (leftSide.startsWith("D") && leftSide.length() == 2)
|
||||
{
|
||||
thisMod.destByte = leftSide.right(1).toInt();
|
||||
thisMod.operations.clear();
|
||||
if (!(Utility::grabOperation(mods[i]) == "="))
|
||||
{
|
||||
qDebug() << "Err: No = after lefthand val";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abort = false;
|
||||
|
||||
token = Utility::grabAlphaNumeric(mods[i]);
|
||||
@@ -648,10 +774,10 @@ void FrameSenderWindow::processModifierText(int line)
|
||||
{
|
||||
thisOp.operation = parseOperation(operation);
|
||||
QString secondOp = Utility::grabAlphaNumeric(mods[i]);
|
||||
if (mods[i][0] == '~')
|
||||
if (secondOp.length() > 0 && secondOp[0] == '~')
|
||||
{
|
||||
thisOp.second.notOper = true;
|
||||
mods[i] = mods[i].remove(0, 1); //remove the ~ character
|
||||
secondOp = secondOp.remove(0, 1); //remove the ~ character
|
||||
}
|
||||
else thisOp.second.notOper = false;
|
||||
thisOp.second.bus = sendingData[line].bus;
|
||||
@@ -676,11 +802,10 @@ void FrameSenderWindow::processTriggerText(int line)
|
||||
QString trigger;
|
||||
|
||||
//Example line:
|
||||
//id0x200 5ms 10x bus0,1000ms
|
||||
//id0x200 5ms 10x bus0,1000ms,ID0x202 SIG[BMS_maxChargeCurrent;300]
|
||||
//trigger has two levels of syntactic parsing. First you split by comma to get each
|
||||
//actual trigger. Then you split by spaces to get the tokens within each trigger
|
||||
//trigger = ui->tableSender->item(line, 5)->text().toUpper().trimmed().replace(" ", "");
|
||||
trigger = ui->tableSender->item(line, 7)->text().toUpper();
|
||||
trigger = ui->tableSender->item(line, ST_COLS::SENDTAB_COL_TRIGGER)->text().toUpper();
|
||||
if (trigger != "")
|
||||
{
|
||||
QStringList triggers = trigger.split(',');
|
||||
@@ -696,6 +821,7 @@ void FrameSenderWindow::processTriggerText(int line)
|
||||
thisTrigger.milliseconds = -1;
|
||||
thisTrigger.currCount = 0;
|
||||
thisTrigger.msCounter = 0;
|
||||
thisTrigger.triggerMask = 0;
|
||||
thisTrigger.readyCount = true;
|
||||
|
||||
QStringList trigToks = triggers[k].split(' ');
|
||||
@@ -705,32 +831,59 @@ void FrameSenderWindow::processTriggerText(int line)
|
||||
if (tok.left(2) == "ID")
|
||||
{
|
||||
thisTrigger.ID = Utility::ParseStringToNum(tok.right(tok.length() - 2));
|
||||
if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 10000000;
|
||||
//if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 0;
|
||||
|
||||
if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 0; //by default don't count, just send it upon trigger
|
||||
thisTrigger.readyCount = false; //won't try counting until trigger hits
|
||||
thisTrigger.triggerMask |= TriggerMask::TRG_ID;
|
||||
}
|
||||
else if (tok.endsWith("MS"))
|
||||
{
|
||||
thisTrigger.milliseconds = Utility::ParseStringToNum(tok.left(tok.length()-2));
|
||||
if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 10000000;
|
||||
if (thisTrigger.ID == -1) thisTrigger.ID = 0;
|
||||
thisTrigger.triggerMask |= TriggerMask::TRG_MS;
|
||||
//if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 0;
|
||||
//if (thisTrigger.ID == -1) thisTrigger.ID = 0;
|
||||
}
|
||||
else if (tok.endsWith("X"))
|
||||
{
|
||||
thisTrigger.maxCount = Utility::ParseStringToNum(tok.left(tok.length() - 1));
|
||||
if (thisTrigger.ID == -1) thisTrigger.ID = 0;
|
||||
if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 10;
|
||||
thisTrigger.triggerMask |= TriggerMask::TRG_COUNT;
|
||||
//if (thisTrigger.ID == -1) thisTrigger.ID = 0;
|
||||
if (thisTrigger.milliseconds == -1)
|
||||
{
|
||||
thisTrigger.milliseconds = 10;
|
||||
thisTrigger.triggerMask |= TriggerMask::TRG_ID;
|
||||
}
|
||||
}
|
||||
else if (tok.startsWith("BUS"))
|
||||
{
|
||||
thisTrigger.bus = Utility::ParseStringToNum(tok.right(tok.length() - 3));
|
||||
thisTrigger.triggerMask |= TriggerMask::TRG_BUS;
|
||||
}
|
||||
else if (tok.startsWith("SIG["))
|
||||
{
|
||||
//remove the SIG[ header
|
||||
tok = tok.right(tok.size() - 4);
|
||||
int semi = tok.indexOf(';');
|
||||
if (semi == -1) semi = tok.indexOf(']');
|
||||
QString signame = tok.left(semi);
|
||||
thisTrigger.sigName = signame;
|
||||
thisTrigger.triggerMask |= TriggerMask::TRG_SIGNAL;
|
||||
semi = tok.indexOf(';');
|
||||
if (semi > -1) //have a signal value to match too
|
||||
{
|
||||
thisTrigger.triggerMask |= TriggerMask::TRG_SIGVAL;
|
||||
int end = tok.indexOf(']');
|
||||
thisTrigger.sigValueDbl =
|
||||
tok.mid(semi + 1, end - semi - 1)
|
||||
.toDouble();
|
||||
}
|
||||
}
|
||||
}
|
||||
//now, find anything that wasn't set and set it to defaults
|
||||
if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 10000000;
|
||||
if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 100;
|
||||
if (thisTrigger.ID == -1) thisTrigger.ID = 0;
|
||||
//if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 0;
|
||||
//if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 100;
|
||||
//if (thisTrigger.ID == -1) thisTrigger.ID = 0;
|
||||
sendingData[line].triggers.append(thisTrigger);
|
||||
}
|
||||
}
|
||||
@@ -741,6 +894,7 @@ void FrameSenderWindow::processTriggerText(int line)
|
||||
thisTrigger.ID = 0;
|
||||
thisTrigger.maxCount = 1;
|
||||
thisTrigger.milliseconds = 10;
|
||||
thisTrigger.triggerMask = TriggerMask::TRG_MS;
|
||||
sendingData[line].triggers.append(thisTrigger);
|
||||
}
|
||||
}
|
||||
@@ -797,27 +951,39 @@ ModifierOperationType FrameSenderWindow::parseOperation(QString op)
|
||||
/// <param name="idx"></param>
|
||||
void FrameSenderWindow::updateGridRow(int idx)
|
||||
{
|
||||
qDebug() << "updateGridRow";
|
||||
//qDebug() << "updateGridRow";
|
||||
|
||||
inhibitChanged = true;
|
||||
FrameSendData *temp = &sendingData[idx];
|
||||
int gridLine = idx;
|
||||
QString dataString;
|
||||
QTableWidgetItem *item = ui->tableSender->item(gridLine, 9);
|
||||
QTableWidgetItem *item = ui->tableSender->item(gridLine, ST_COLS::SENDTAB_COL_COUNT);
|
||||
const unsigned char *data = reinterpret_cast<const unsigned char *>(temp->payload().constData());
|
||||
int dataLen = temp->payload().length();
|
||||
|
||||
if (item == nullptr) item = new QTableWidgetItem();
|
||||
item->setText(QString::number(temp->count));
|
||||
if (temp->frameType() != QCanBusFrame::RemoteRequestFrame) {
|
||||
if (item == nullptr)
|
||||
{
|
||||
item = new QTableWidgetItem();
|
||||
item->setText(QString::number(temp->count));
|
||||
ui->tableSender->setItem(gridLine, ST_COLS::SENDTAB_COL_COUNT, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
item->setText(QString::number(temp->count));
|
||||
}
|
||||
|
||||
if (temp->frameType() != QCanBusFrame::RemoteRequestFrame)
|
||||
{
|
||||
for (int i = 0; i < dataLen; i++)
|
||||
{
|
||||
dataString.append(Utility::formatNumber(data[i]));
|
||||
dataString.append(" ");
|
||||
}
|
||||
ui->tableSender->item(gridLine, 6)->setText(dataString);
|
||||
} else {
|
||||
ui->tableSender->item(gridLine, 6)->setText("");
|
||||
ui->tableSender->item(gridLine, ST_COLS::SENDTAB_COL_DATA)->setText(dataString);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->tableSender->item(gridLine, ST_COLS::SENDTAB_COL_DATA)->setText("");
|
||||
}
|
||||
inhibitChanged = false;
|
||||
}
|
||||
@@ -828,6 +994,7 @@ void FrameSenderWindow::processCellChange(int line, int col)
|
||||
FrameSendData tempData;
|
||||
QStringList tokens;
|
||||
int tempVal;
|
||||
DBC_MESSAGE *msg;
|
||||
|
||||
//if this is a new line then create the base object for the line
|
||||
if (line >= sendingData.count())
|
||||
@@ -846,7 +1013,7 @@ void FrameSenderWindow::processCellChange(int line, int col)
|
||||
|
||||
switch (col)
|
||||
{
|
||||
case 0: //Enable check box
|
||||
case ST_COLS::SENDTAB_COL_EN: //Enable check box
|
||||
if (ui->tableSender->item(line, 0)->checkState() == Qt::Checked)
|
||||
{
|
||||
sendingData[line].enabled = true;
|
||||
@@ -854,51 +1021,61 @@ void FrameSenderWindow::processCellChange(int line, int col)
|
||||
else sendingData[line].enabled = false;
|
||||
qDebug() << "Setting enabled to " << sendingData[line].enabled;
|
||||
break;
|
||||
case 1: //Bus designation
|
||||
tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, 1)->text());
|
||||
case ST_COLS::SENDTAB_COL_BUS: //Bus designation
|
||||
tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, ST_COLS::SENDTAB_COL_BUS)->text());
|
||||
if (tempVal < -1) tempVal = -1;
|
||||
if (tempVal >= numBuses) tempVal = numBuses - 1;
|
||||
sendingData[line].bus = tempVal;
|
||||
qDebug() << "Setting bus to " << tempVal;
|
||||
break;
|
||||
case 2: //ID field
|
||||
tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, 2)->text());
|
||||
case ST_COLS::SENDTAB_COL_ID: //ID field
|
||||
tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, ST_COLS::SENDTAB_COL_ID)->text());
|
||||
if (tempVal < 0) tempVal = 0;
|
||||
if (tempVal > 0x7FFFFFFF) tempVal = 0x7FFFFFFF;
|
||||
sendingData[line].setFrameId(tempVal);
|
||||
if (sendingData[line].frameId() > 0x7FF) {
|
||||
sendingData[line].setExtendedFrameFormat(true);
|
||||
ui->tableSender->blockSignals(true);
|
||||
ui->tableSender->item(line, 4)->setCheckState(Qt::Checked);
|
||||
ui->tableSender->item(line, ST_COLS::SENDTAB_COL_EXT)->setCheckState(Qt::Checked);
|
||||
ui->tableSender->blockSignals(false);
|
||||
}
|
||||
msg = dbcHandler->findMessage(sendingData[line].frameId());
|
||||
if (msg)
|
||||
{
|
||||
ui->tableSender->blockSignals(true);
|
||||
ui->tableSender->item(line, ST_COLS::SENDTAB_COL_MSGNAME)->setText(msg->name);
|
||||
ui->tableSender->item(line, ST_COLS::SENDTAB_COL_LEN)->setText(QString::number(msg->len));
|
||||
ui->tableSender->blockSignals(false);
|
||||
}
|
||||
qDebug() << "setting ID to " << tempVal;
|
||||
break;
|
||||
case 3: //length field
|
||||
case ST_COLS::SENDTAB_COL_LEN:
|
||||
tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, 3)->text());
|
||||
if (tempVal < 0) tempVal = 0;
|
||||
if (tempVal > 8) tempVal = 8;
|
||||
arr.resize(tempVal);
|
||||
sendingData[line].setPayload(arr);
|
||||
break;
|
||||
case 4: // Ext
|
||||
case ST_COLS::SENDTAB_COL_EXT:
|
||||
if (ui->tableSender->item(line, 4)->checkState() == Qt::Checked) {
|
||||
sendingData[line].setExtendedFrameFormat(true);
|
||||
} else {
|
||||
sendingData[line].setExtendedFrameFormat(false);
|
||||
}
|
||||
break;
|
||||
case 5: // Rem
|
||||
case ST_COLS::SENDTAB_COL_REM:
|
||||
if (ui->tableSender->item(line, 5)->checkState() == Qt::Checked) {
|
||||
sendingData[line].setFrameType(QCanBusFrame::RemoteRequestFrame);
|
||||
} else {
|
||||
sendingData[line].setFrameType(QCanBusFrame::DataFrame);
|
||||
}
|
||||
break;
|
||||
case 6: //Data bytes
|
||||
for (int i = 0; i < 8; i++) sendingData[line].payload().data()[i] = 0;
|
||||
|
||||
tokens = ui->tableSender->item(line, 6)->text().split(" ");
|
||||
case ST_COLS::SENDTAB_COL_DATA: //Data bytes
|
||||
#if QT_VERSION >= QT_VERSION_CHECK( 5, 14, 0 )
|
||||
tokens = ui->tableSender->item(line, ST_COLS::SENDTAB_COL_DATA)->text().split(" ", Qt::SkipEmptyParts);
|
||||
#else
|
||||
tokens = ui->tableSender->item(line, ST_COLS::SENDTAB_COL_DATA)->text().split(" ", QString::SkipEmptyParts);
|
||||
#endif
|
||||
arr.clear();
|
||||
arr.reserve(tokens.count());
|
||||
for (int j = 0; j < tokens.count(); j++)
|
||||
@@ -907,10 +1084,10 @@ void FrameSenderWindow::processCellChange(int line, int col)
|
||||
}
|
||||
sendingData[line].setPayload(arr);
|
||||
break;
|
||||
case 7: //triggers
|
||||
case ST_COLS::SENDTAB_COL_TRIGGER: //triggers
|
||||
processTriggerText(line);
|
||||
break;
|
||||
case 8: //modifiers
|
||||
case ST_COLS::SENDTAB_COL_MODS: //modifiers
|
||||
processModifierText(line);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5,13 +5,31 @@
|
||||
#include <QTimer>
|
||||
#include <QElapsedTimer>
|
||||
#include <QTime>
|
||||
#include <QMutex>
|
||||
#include "can_structs.h"
|
||||
#include "can_trigger_structs.h"
|
||||
#include "dbc/dbchandler.h"
|
||||
#include "triggerdialog.h"
|
||||
|
||||
namespace Ui {
|
||||
class FrameSenderWindow;
|
||||
}
|
||||
|
||||
enum ST_COLS
|
||||
{
|
||||
SENDTAB_COL_EN = 0,
|
||||
SENDTAB_COL_BUS = 1,
|
||||
SENDTAB_COL_ID = 2,
|
||||
SENDTAB_COL_MSGNAME = 3,
|
||||
SENDTAB_COL_LEN = 4,
|
||||
SENDTAB_COL_EXT = 5,
|
||||
SENDTAB_COL_REM = 6,
|
||||
SENDTAB_COL_DATA = 7,
|
||||
SENDTAB_COL_TRIGGER = 8,
|
||||
SENDTAB_COL_MODS = 9,
|
||||
SENDTAB_COL_COUNT = 10,
|
||||
};
|
||||
|
||||
class FrameSenderWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -22,6 +40,7 @@ public:
|
||||
|
||||
private slots:
|
||||
void onCellChanged(int, int);
|
||||
void onCellDoubleTap(int, int);
|
||||
void handleTick();
|
||||
void enableAll();
|
||||
void disableAll();
|
||||
@@ -38,6 +57,10 @@ private:
|
||||
QTimer *intervalTimer;
|
||||
QElapsedTimer elapsedTimer;
|
||||
bool inhibitChanged = false;
|
||||
QMutex mutex;
|
||||
DBCHandler *dbcHandler;
|
||||
TriggerDialog *td;
|
||||
FrameSendData *sendData;
|
||||
|
||||
void createBlankRow();
|
||||
void doModifiers(int);
|
||||
@@ -55,6 +78,7 @@ private:
|
||||
void processIncomingFrame(CANFrame *frame);
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
void setupGrid();
|
||||
void finishedTriggerDialog(int result);
|
||||
};
|
||||
|
||||
#endif // FRAMESENDERWINDOW_H
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Bisector Window
|
||||
=================
|
||||
|
||||

|
||||

|
||||
|
||||
Using the Bisector Window
|
||||
==========================
|
||||
@@ -14,8 +14,10 @@ Frame Number - You can split right at a given frame number. All frames up to tha
|
||||
|
||||
Percentage - Pretty much just like the Frame Number option but in percentage instead in case that is more convenient.
|
||||
|
||||
Bus Number - You can also split the capture to include or exclude a given bus number. This can be helpful to allow breaking up the file into per-bus files.
|
||||
|
||||
In all cases, you have the option of which side of the split you want to save. Click "Calculate Split" to process the split. You will see above the buttons a reference of how many frames there were in total and how many you would be saving after the split. From here you *should* be able to do one of two things:
|
||||
|
||||
"Save split frames to a new file" - Save the new list of frames (after the split) to a file. You can save to any file format that SavvyCAN supports elsewhere.
|
||||
|
||||
"Replace main list with split frames" - Were this to work it would replace the frames loaded in the rest of the application with the split list. I'm sure this would be a really nice thing to be able to do. Well, you can't right now. It's not implemented yet. So, just imagine you can do this. In the mean time you can save to a file then load that file. Sorry...
|
||||
"Replace main list with split frames" - Erases all messages on the main window and replaces them with the results of the bisection. You will lose all discarded frames if you haven't saved them elsewhere.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
CAN Bridge Window
|
||||
=================
|
||||
|
||||

|
||||
|
||||
Using the Bridge Window
|
||||
==========================
|
||||
|
||||
The purpose of this window is simple, to allow one to forward traffic from one bus to another. Any two buses you have connected could be bridged in this way, even if they originate on different hardware adapters. By default neither side will forward to the other. You must select different buses for Side 1 and Side 2 then enable forwarding from one to the other. You can enable bi-directional forwarding. However, this can be problematic. If you are not careful you can create an infinite loop where traffic from one side gets forwarded to the other side which then forwards to the first side, and so on.
|
||||
|
||||
If you'd like to only forward some traffic then uncheck the boxes next to IDs you do not want to forward.
|
||||
|
||||
At the moment this window is in the preliminary stages and, as mentioned above, could be prone to allowing infinite loops in some circumstances.
|
||||
@@ -1,17 +1,31 @@
|
||||
DBC Message Editor
|
||||
===================
|
||||
|
||||

|
||||

|
||||
|
||||
This new interface places all nodes, messages, and signals into a tree structure. Each node has a list of messages that are sent by that node. Each message, in turn, has a list of signals contained within. Multiplexed and multi-level multiplexed messages are supported. Double clicking a node, message, or signal will bring up the relevant editor.
|
||||
|
||||
Quick Cheat Sheet
|
||||
==================
|
||||
* F3 = Go to the previous item while searching
|
||||
* F4 = Go to the next item while searching
|
||||
* F5 = Create a new node
|
||||
* F6 = Create a new message
|
||||
* F7 = Create a new signal
|
||||
* DEL = Delete the currently selected item (node, message, or signal)
|
||||
* You can right click on nodes to get some special operations
|
||||
* If you have a message selected and you create a new message it will be a clone of the selected message. Likewise for signals.
|
||||
* If you have a message selected and create a signal then that signal will have the message as its parent.
|
||||
* All items have icons to help you quickly determine what they are. Nodes have triangle icons, messages have envelope icons, signals have a few different icons depending on whether they're normal signals, multiplexor, or multiplexed.
|
||||
|
||||
Working with Nodes
|
||||
===================
|
||||
|
||||
In DBC files a node is a device on the CAN bus. For instance, the engine control unit (ECU) would be a node as would a motor controller, a battery charger, or any other device that is connected to the CAN bus. DBC files let you define nodes that are
|
||||
set as either the sender or receiver of a message. This allows messages to be organized for more easy retrieval. To add
|
||||
a new node click on the empty row beneath the last defined node and type a new name and optionally a comment. The comment is not used by SavvyCAN but can be filled out for your own reference. When a node is selected in the top list you will then see
|
||||
in the bottom a listing of every message it sends.
|
||||
In DBC files a node is a device on the CAN bus. For instance, the engine control unit (ECU) would be a node as would a motor controller, a battery charger, or any other device that is connected to the CAN bus. DBC files let you define nodes that are set as either the sender or receiver of a message. This allows messages to be organized for more easy retrieval. To add a new node click on the "New Node" button at the top or press F5 and type a new name and optionally a comment. The comment is not used by SavvyCAN but can be filled out for your own reference. The arrow next to a node can be pressed to get a list of all messages contained within.
|
||||
|
||||
Working with Messages
|
||||
=====================
|
||||
|
||||
The bottom list is all of the messages that are sent by the selected node. A message is defined based on its message ID. The message ID is the CAN id used for this message. For normal DBC files this creates a one to one correspondence of ID to a given CAN ID. For J1939 messages special masking is done and so more than one actual CAN id will map to the given message ID but still only one specific J1939 PGN will come through. Once a message ID is entered in for a new message it will attempt to auto populate the Data Len column with the number of data bytes that message has. This is only for your information, it is not used by SavvyCAN. You can give the DBC message a meaningful name. The "Fg" and "Bg" columns can be clicked on to set a color. This will set the foreground and background color to use for this message. These colors will be used in the main frame view on the main screen when you click "Interpret Frames." Clicking on the "Signals" column will bring up the signals editor for that message so that you can edit, add, or remove signals from the message. The "Comments" column is once again not used by SavvyCAN and only for your viewing reference.
|
||||

|
||||
|
||||
Within each node are zero or more messages. Messages are defined based on their frame ID. For normal DBC files this creates a one to one correspondence of ID to a given CAN ID. For J1939 and GMLAN messages special masking is done and so more than one actual CAN id will map to the given message ID but still only one specific J1939 PGN will come through. Once a message ID is entered in for a new message it will attempt to auto populate the Data Len column with the number of data bytes that message has. You won't be able to set signals into bits that are past the message length so care should be taken to make this value accurate. You can give the DBC message a meaningful name. You can set the text color and background color for each message. This information will be used on the main screen when you click "Interpret Frames" but can quickly back fire. If you set black on black you will have a bad time. And, making your window look like a circus might not be ideal either. However, the choice is yours!
|
||||
|
||||
@@ -6,7 +6,7 @@ DBC File Manager
|
||||
Working with DBC Files
|
||||
=======================
|
||||
|
||||
This screen allows you to load and save DBC files. SavvyCAN supports loading more than one DBC file at a time. It can even use more than one DBC file per bus. But, "Associated Bus" can be used to associate a given DBC file to only one bus. If you don't need to associate to any specific bus then set this value to -1 which means "any bus." The J1939 button causes SavvyCAN to mask out J1939 message IDs to conform to J1939 signaling. You can create a brand new DBC file by clicking "Create new DBC" button. It will be automatically named a unique name for you. You probably don't want that name though. Any time you save a DBC file its name will automatically update in the list. The "Load", "Save", "Remove", "Edit" buttons are all straight forward. You can also edit a DBC file by double clicking it in the list.
|
||||
This screen allows you to load and save DBC files. SavvyCAN supports loading more than one DBC file at a time. It can even use more than one DBC file per bus. But, "Associated Bus" can be used to associate a given DBC file to only one bus. If you don't need to associate to any specific bus then set this value to -1 which means "any bus." Matching criteria is used to select J1939 or GMLAN if necessary. These two systems have special ways of interpreting the frame ID. You can create a brand new DBC file by clicking "Create new DBC" button. It will be automatically named a unique name for you. You probably don't want that name though. Any time you save a DBC file its name will automatically update in the list. The "Load", "Save", "Remove", "Edit" buttons are all straight forward. You can edit a DBC file by double clicking it in the list.
|
||||
|
||||
|
||||
DBC File Ordering
|
||||
|
||||
@@ -16,6 +16,10 @@ If you have a DBC file loaded which matches the ID you've selected then you will
|
||||
|
||||
The top right graph is a histogram of all the bits and the number of times each bit was set. This can be used to quickly visually see where data has changed.
|
||||
|
||||
The bottom right graph is of each individual byte as its value varies over time. All textual information can be saved to a text file for later analysis.
|
||||
The bottom right has 8 graphs, one for each possible byte in a standard CAN frame (CAN-FD support is coming... eventually) Double clicking one of these graphs will size it up and remove the other 7 graphs. Double clicking again brings the 8 byte view back.
|
||||
|
||||
The interval histogram is in logarithmic scale and shows a listing of what intervals were seen between frames. This can be used to visually see the frame timing. Some frames get sent very regularly. They will show a very pronouced bell curve. Other frames might get sent on demand. These frames will have peaks at odd places and not conform to a nice distribution. For instance, in the picture you can see that the frame shows many frames around 15-20ms timing but some around 40, 60, and 80 as well. This might indicate the frame is not always sent or it might show that the frame is getting lost. After all, those timings are all around multiples of 20ms.
|
||||
As in other windows, it is possible to use the mouse wheel to scale all the graphs on this form and it is also possible to pan around by clicking and dragging. Selecting one axis will also let you scale / pan just that one axis.
|
||||
|
||||
In the middle there is a bitfield view. This is color coded based upon how often each bit changes. It is called the "heatmap" for this reason. Bits that don't change often are cold and colored blue. Bits that are hot get increasingly red. In the picture you can see distinct areas where the bits are blue, light blue, green, orange. This is highly indicative of a counter. When a counter is found you will find that the lower bit changes basically every frame, the next bit up every other frame, the next bit every fourth frame, etc. This produces a very distinct pattern in the heatmap. Bits that never change are black. This view thus allows one to see where changing data is found within a frame. Chances are you can ignore all the black parts and focus only on places where some change has happened.
|
||||
|
||||
The interval histogram is in logarithmic scale and shows a listing of what intervals were seen between frames. This can be used to visually see the frame timing. Some frames get sent very regularly. They will show a very pronouced bell curve. Other frames might get sent on demand. These frames will have peaks at odd places and not conform to a nice distribution. For instance, in the picture you can see that the frame shows quite a few messages around 100ms but messages extend out to around 600ms as well. Still, the logrithmic scale means that the faster interval is, by far, the most common.
|
||||
|
||||
@@ -39,6 +39,8 @@ Once you've set both Data Len and Little Endian you can set the bits to use. The
|
||||
|
||||
"Stride" is not often used but will cause only every "x" values to actually be graphed. This can be used to graph a very dense set of data with less points to speed things up.
|
||||
|
||||
"Associated Bus" defaults to -1 which means any bus. If you know you want to only graph messages that arrived on a particular bus you can enter the number here. It is rare for two buses to have the same frame ID but with different actual messages but not unheard of. If you find that your graphs appear to randomly jitter back and forth between two distinct sets of values this may be the cause.
|
||||
|
||||
"Only Points" will graph using disconnected points instead of lines.
|
||||
|
||||
"Point Style" has a list of every style you can use for points. The default is to not show the points and instead draw a line through where the points are at. But, there are many other options.
|
||||
@@ -48,3 +50,5 @@ Once you've set both Data Len and Little Endian you can set the bits to use. The
|
||||
"Line Color" changes the color of the graphed line. It is automatically randomly set for new graphs but if you don't like the random color you can click the color and select a better one.
|
||||
|
||||
"Fill Color" If you're feeling fancy you can also specify a fill color. The default fill color has an alpha value of 0 meaning it is completely transparent / no used. If you set a fill color you will need to set the alpha channel as well. An alpha of even 40-60 is usually plenty.
|
||||
|
||||
DO NOTE -> all the options starting at "Point Style" will drastically affect performance. If you try to graph a million points with a special point style, large line thickness, and a fill color it WILL be slow. For maximum speed just graph colored lines with no points.
|
||||
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 50 KiB |
@@ -12,17 +12,18 @@ The Main Frame List
|
||||
|
||||
The main frame list takes up the majority of the main screen. This list consists of the following sections:
|
||||
|
||||
- Timestamp: The timestamp is either in microseconds or seconds. This is a setting in preferences. Either way, the timestamp can have microsecond resolution. The difference is just whether there is a decimal point or not. GVRET has the ability to maintain full microsecond resolution for timestamping purposes. There is a third timing mode where the timestamp can be customized and is based upon the actual "clock" time.
|
||||
- Timestamp: The timestamp is either in microseconds or seconds. This is a setting in preferences. Either way, the timestamp can have microsecond resolution. The difference is just whether there is a decimal point or not. Many of the CAN capture devices have the ability to maintain full microsecond resolution for timestamping purposes. There is a third timing mode where the timestamp can be customized and is based upon the actual "clock" time.
|
||||
- ID: The ID is specified either in hexadecimal or decimal (a preference you can set). This is the message identifier sent over the CAN bus.
|
||||
- RTR: 0 = Standard Message 1 = Remote transmit request. An RTR frame merely asks a node to send a message, it has no payload of its own.
|
||||
- Ext: 0 = Standard message (11 bit ID). 1 = Extended message (29 bit ID)
|
||||
- Dir: Either "Rx" or "Tx" to show whether SavvyCAN has received or sent this message.
|
||||
- Bus: SavvyCAN supports a variety of capture hardware. GVRET compatible devices can support more than one bus. The bus a frame came in on
|
||||
is specified here. Many file formats do not specify bus and thus all frames will be loaded as bus 0.
|
||||
- Len: The number of data bytes that were sent with this frame. It can range from 0 to 8.
|
||||
- Len: The number of data bytes that were sent with this frame. It can range from 0 to 8 for standard CAN and 0 to 64 for CAN-FD.
|
||||
- ASCII: A character based view of the CAN bytes in ASCII characters. Many systems that send serial numbers or VIN numbers will send them in ASCII and these will thus be visible here.
|
||||
- Data: All of the data bytes separated by spaces. Can be in either hexadecimal or decimal (preference). If "Interpret Frames" is checked you will
|
||||
also see extra data at the end of any frames that have DBC data. To see the rest of this data click upon the frame in the list. It will automatically expand to show all signals attached to that frame.
|
||||
There is also a setting to limit the number of displayed bytes per line. This is especially useful for CAN-FD traffic.
|
||||
|
||||
|
||||
The Bottom Statusbar
|
||||
@@ -38,31 +39,33 @@ At the very bottom of the main screen is a status bar with three sections.
|
||||
The Rest of the Main Window
|
||||
===========================
|
||||
|
||||
To the right of the main frames list is an area that shows the total number of captured frames and the frames per second. Total frames might not match the number of shown frames. If you've deselected any IDs in the filter list then fewer frames will be shown. Frames per second is calculated as an average and so will wind up or down when there is a sudden change.
|
||||
*To the right of the main frames list is an area that shows the total number of captured frames and the frames per second. Total frames might not match the number of shown frames. If you've deselected any IDs in the filter list then fewer frames will be shown. Frames per second is calculated as an average and so will wind up or down when there is a sudden change.
|
||||
|
||||
Suspend Capturing / Resume Capturing is a button that will temporarily disable frame capture or re-enable it. This can be used to keep everything connected without capturing traffic for a short time. This can help to not capture traffic in between tests.
|
||||
*Suspend Capturing / Resume Capturing is a button that will temporarily disable frame capture or re-enable it. This can be used to keep everything connected without capturing traffic for a short time. This can help to not capture traffic in between tests.
|
||||
|
||||
The "Normalize Frame Timing" button is used to reset the lowest timestamp to "0" and offset all other timestamps accordingly. This is useful to remove the starting offset when you start up a device long before actual traffic starts. SavvyCAN is designed such that this doesn't really matter most of the time but normalizing the timing might be useful to help correlate the timing between two different captures.
|
||||
*The "Normalize Frame Timing" button is used to reset the lowest timestamp to "0" and offset all other timestamps accordingly. This is useful to remove the starting offset when you start up a device long before actual traffic starts. SavvyCAN is designed such that this doesn't really matter most of the time but normalizing the timing might be useful to help correlate the timing between two different captures.
|
||||
|
||||
The "Clear Frames" button will erase all captured messages. They will be irreversibly erased and all memory will be freed.
|
||||
*The "Clear Frames" button will erase all captured messages. They will be irreversibly erased and all memory will be freed.
|
||||
|
||||
The "Auto Scroll Window" checkbox will cause the main frame list to hunt toward the bottom of the list as frames come in. It will normally not be quite
|
||||
*"Keep Filters While Clearing" will keep all the filters intact if you push the "Clear Frames" button. Otherwise all filters will also be cleared.
|
||||
|
||||
*The "Auto Scroll Window" checkbox will cause the main frame list to hunt toward the bottom of the list as frames come in. It will normally not be quite
|
||||
at the very bottom as, for performance reasons, the program runs at quarter second updates to things like the auto scroll. Thus, the main list will be
|
||||
scrolled to the bottom four times per second.
|
||||
|
||||
The "Interpret Frames" checkbox is used to specify whether the loaded DBC file should be used to interpret all available messages and signals. One might want
|
||||
*The "Interpret Frames" checkbox is used to specify whether the loaded DBC file should be used to interpret all available messages and signals. One might want
|
||||
this off for performance reasons (interpreting takes some extra processor power and RAM) or to declutter the main frame list.
|
||||
|
||||
The "Overwrite Mode" checkbox is used to ensure that only the newest frame for each message ID is shown. That is, if 100 messages with ID 0x105 come in you
|
||||
*The "Overwrite Mode" checkbox is used to ensure that only the newest frame for each message ID is shown. That is, if 100 messages with ID 0x105 come in you
|
||||
will see only the newest one. This is generally used alongside "Interpret Frames" to interpret frames and always see the up-to-date information.
|
||||
|
||||
"Expand All Rows" will expand all the rows to show every signal in every message. This will take a **VERY** long time if there are many messages loaded. Because of this, you may receive a warning if the program determines that this will take an excessive amount of time to complete. You can make it work faster by filtering away any unneeded messages.
|
||||
*"Expand All Rows" will expand all the rows to show every signal in every message. This will take a **VERY** long time if there are many messages loaded. Because of this, you may receive a warning if the program determines that this will take an excessive amount of time to complete. You can make it work faster by filtering away any unneeded messages.
|
||||
|
||||
"Collapse All Rows" will drop all rows back to taking up only one line. This can also take a while to run and will also warn if the operation seems like it will take a very long time to complete.
|
||||
*"Collapse All Rows" will drop all rows back to taking up only one line. This can also take a while to run and will also warn if the operation seems like it will take a very long time to complete.
|
||||
|
||||
"Bus Filtering" allows for messages to be shown or hidden based on which bus they came in on.
|
||||
*"Bus Filtering" allows for messages to be shown or hidden based on which bus they came in on.
|
||||
|
||||
"Frame Filtering" provides a list of all the frame IDs seen so far. Any ID which is checked will be shown in the main list. Any ID which is unchecked will not.
|
||||
*"Frame Filtering" provides a list of all the frame IDs seen so far. Any ID which is checked will be shown in the main list. Any ID which is unchecked will not.
|
||||
This can be used to hone in on frames of importance while hiding frames that are currently of no interest. The filtered list can be saved as well.
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Playback Window
|
||||
===============
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
Preparing Frames for Playback
|
||||
@@ -18,7 +18,9 @@ The playback window can send frames on a specific bus, all buses (be careful wit
|
||||
|
||||
The next order of business is frame timing. There are two approaches possible here. If you click "Use original frame timing from captured frames" then frames will be sent out in approximately the same timing as they came in with. The word approximately is used because it is difficult to get 1ms timing precision on a desktop OS. Frames that come in rapidly might have a 2-3ms jitter. In practice this is almost always irrelevant. This setting is suitable for nearly all uses.
|
||||
|
||||
Alternatively, it is also possible to send on a set schedule. With the "Use original" checkbox not checked you can set a playback speed in milliseconds and a burst rate. Burst means that it'll send that many frames every tick. So, if you have a burst of 5 and a timing of 10ms then every 10ms 5 frames will be sent. This mode can provide for a predictable number of frames per second and could be useful to test how quickly a device really requires traffic without faulting. But, it will potentially drastically alter the timing of frames compared to their timing when they were captured.
|
||||
Alternatively, it is also possible to send on a set schedule. With the "Use original" checkbox not checked you can set a playback speed in milliseconds and a burst rate. Burst means that it'll send that many frames every tick. So, if you have a burst of 5 and a timing of 10ms then every 10ms 5 frames will be sent. This mode can provide for a predictable number of frames per second and could be useful to test how quickly a device really requires traffic without faulting. But, it will potentially drastically alter the timing of frames compared to their timing when they were captured. You can set a burst rate as well. Burst Rate is the number of frames sent every "tick." This can speed up how fast you can send traffic.
|
||||
|
||||
There is also now a checkbox that allows for waiting for traffic before sending CAN frames when you click play forward or backward. If this checkbox is checked you will see (WAITING) to the left of the number of frames below "Current frame." Once any CAN traffic starts to come in your frames will begin to playback automatically. Why would you want to do this? Well, the most likely reason is that you want to play back a CAN capture but you only want to do so once the vehicle has been powered on. And, you want to wait until the CAN buses are active so that you don't fault by sending traffic into nowhere. Lastly, this allows your playback to happen very rapidly after start up which might otherwise be tougher to pull off accurately.
|
||||
|
||||
The top of the window has a series of 6 icons all in a row:
|
||||
|
||||
|
||||
@@ -5,53 +5,71 @@ Preference Window
|
||||
|
||||
There are a variety of preferences that can be set in the program and persisted for future sessions.
|
||||
|
||||
Main Form Settings
|
||||
===================
|
||||
* Autoscroll main frame winow by default - When capturing frames should the main window track the newest incoming messages or stay where it was? This option defaults it to follow the incoming frames by default. The main window has a toggle for auto scroll. This setting just sets the default value of that toggle.
|
||||
* Maximum data bytes per line - Really must helpful for CAN-FD traffic. Setting this to 8, 16, or 32 can help to be able to see all the bytes without the window having to be extremely wide.
|
||||
|
||||
General Settings
|
||||
====================
|
||||
|
||||
"Save/Restore window positions and sizes": If this is set then the size and placement of the various windows in this application will be saved when the program is closed and loaded when each window is brought back up in the future. This can be used to create your own preferred layout. If you'd rather things come up in their default state every time then you can uncheck this box.
|
||||
* "Save/Restore window positions and sizes": If this is set then the size and placement of the various windows in this application will be saved when the program is closed and loaded when each window is brought back up in the future. This can be used to create your own preferred layout. If you'd rather things come up in their default state every time then you can uncheck this box.
|
||||
|
||||
"Save/Restore CAN bus connections": This will cause the program to remember the devices you were connected to last time you ran the program and attempt to reconnect to them upon start up.
|
||||
* "Save/Restore CAN bus connections": This will cause the program to remember the devices you were connected to last time you ran the program and attempt to reconnect to them upon start up.
|
||||
|
||||
"Display values as hexadecimal": A lot of the time people who are doing CAN reverse engineering like to see values in hexadecimal (base 16) instead of the more familiar decimal (base 10) system. Checking this box will cause most of the values in the application to show up in hex. This applies to CAN ids and data bytes. Unchecking this causes values to default to decimal instead. The reason for using hex is that each hex digit is 4 bits. Integers on a computer tend to be in multiples of 8 - 8, 16, 32, 64. So, hex digits have a direct mapping to the underlying binary. Decimal does not have this correspondence AT ALL. But, the choice is yours.
|
||||
* "Display values as hexadecimal": A lot of the time people who are doing CAN reverse engineering like to see values in hexadecimal (base 16) instead of the more familiar decimal (base 10) system. Checking this box will cause most of the values in the application to show up in hex. This applies to CAN ids and data bytes. Unchecking this causes values to default to decimal instead. The reason for using hex is that each hex digit is 4 bits. Integers on a computer tend to be in multiples of 8 - 8, 16, 32, 64. So, hex digits have a direct mapping to the underlying binary. Decimal does not have this correspondence AT ALL. But, the choice is yours.
|
||||
|
||||
"Require validation of GVRET connection": GVRET style devices run over a serial connection. Serial connections can be finicky sometimes and so the connection can be validated to prove that everything is really still operating and talking. There probably isn't any reason to turn this off except while debugging to see if it changes anything. Mostly just don't touch this.
|
||||
* "Require validation of GVRET connection": GVRET style devices run over a serial connection. Serial connections can be finicky sometimes and so the connection can be validated to prove that everything is really still operating and talking. There probably isn't any reason to turn this off except while debugging to see if it changes anything. Mostly just don't touch this.
|
||||
|
||||
"Label filters using messages from DBC files": Part of a two part system. If this is selected and a given DBC file also has its checkbox checked then the IDs list on the main screen (lower right) used for filtering will have not only the IDs but also the message name from the DBC file. This will allow for more easily determining what a given message means. Sometimes IDs can be hard to remember. Do note that this checkbox must be checked or the checkboxes for each loaded DBC file won't do anything. This is the master on/off switch.
|
||||
* "Use filtered frames in sub-windows": The main window has a filtering interface where you can uncheck IDs to hide them. Ordinarily when you bring up one of the other windows it will still use the main unfiltered list. Sometimes you really do want to deal with the filtered list of frames even in the other windows. If this is checked then the other windows will see the filtered list and not the unfiltered actual list of frames that have been captured.
|
||||
|
||||
"Time Keeping": There are a variety of ways one could timestamp CAN frames as they come into the program. Selecting "Seconds" will cause the timestamp to be expressed as seconds since the frame list was last cleared. This tends to be an easy choice to work with. "Microseconds" will express the timestamp as millionths of a second since the last time the frame list was cleared. This is exactly like "Seconds" mode but without any decimal point. You might find this to be a bit hard to conceptualize. The last option is "System Clock" this will timestamp frames with the current system time when the frame came in. This is still very precise but now you'll get an absolute time stamp with the full date and time. The display of this mode can be changed by editing the "Time Format String" value. It defaults to an output that looks like "JAN-10 12:34:53.234" But you can set it to other values. Look here to find a reference for how you can create new format strings: http://doc.qt.io/qt-4.8/qdatetime.html#toString
|
||||
* "OpenGL Accelerated AntiAliased Graphing": Checking this will cause all of the graphs to use OpenGL 3D acceleration. Most modern machines have some form of 3D acceleration so this option should be OK to use. If you check this your graphs will look a lot better and on good hardware should also be faster. In the future other options are likely to be added to the graphing screen that will likely only be enabled if OpenGL mode is also enabled. Try enabling this and see if performance is still good. It's safe to leave it off if in doubt.
|
||||
|
||||
"Use filtered frames in sub-windows": The main window has a filtering interface where you can uncheck IDs to hide them. Ordinarily when you bring up one of the other windows it will still use the main unfiltered list. Sometimes you really do want to deal with the filtered list of frames even in the other windows. If this is checked then the other windows will see the filtered list and not the unfiltered actual list of frames that have been captured.
|
||||
* "CAN Frame Pre-allocation Size" - This requires a bit of explanation and caution. When SavvyCAN starts it pre-allocates a giant buffer for incoming CAN traffic. Otherwise as traffic comes in the program would have a limited amount of space allocated to receive the traffic. If this reserved space runs out then the program would have to go ask the operating system for more and copy all existing frames to the newer, bigger buffer. This is a slow process. So, instead a giant buffer is allocated up front (by default 10 million frames worth!). You aren't likely to exceed this value and so it never has to ask for more memory and things run smoothly. 10M frames is about 1/2 of a gigabyte. This is a lot of memory but very doable for most modern PCs. But, if you are running on a Raspberry Pi it may be a good idea to turn this down to, say, 1M instead. You may be tempted to make this value really large so that, no matter what, it never has to reallocate. But, setting this 100x bigger would try to allocate 50GB of RAM. You probably don't have that much RAM to spare. So, be cautious if you raise this value. 10M should be enough for most anyone. Even if you did happen to exceed the value the program won't crash, it will just pause for a long time as it creates a larger buffer and moves everything over.
|
||||
|
||||
"OpenGL Accelerated AntiAliased Graphing": Checking this will cause all of the graphs to use OpenGL 3D acceleration. Most modern machines have some form of 3D acceleration so this option should be OK to use. If you check this your graphs will look a lot better and on good hardware should also be faster. In the future other options are likely to be added to the graphing screen that will likely only be enabled if OpenGL mode is also enabled. Try enabling this and see if performance is still good. It's safe to leave it off if in doubt.
|
||||
* "Time Keeping": There are a variety of ways one could timestamp CAN frames as they come into the program. Selecting "Seconds" will cause the timestamp to be expressed as seconds since the frame list was last cleared. This tends to be an easy choice to work with. "Microseconds" will express the timestamp as millionths of a second since the last time the frame list was cleared. This is exactly like "Seconds" mode but without any decimal point. You might find this to be a bit hard to conceptualize. The last option is "System Clock" this will timestamp frames with the current system time when the frame came in. This is still very precise but now you'll get an absolute time stamp with the full date and time. The display of this mode can be changed by editing the "Time Format String" value. It defaults to an output that looks like "JAN-10 12:34:53.234" But you can set it to other values. Look here to find a reference for how you can create new format strings: http://doc.qt.io/qt-4.8/qdatetime.html#toString
|
||||
|
||||
"Autoscroll main frame window by default": If checked it will automatically check the relevant checkbox on the main screen when the program starts. This will cause the view to automatically scroll to the bottom by default. This is merely a convenience option if you'd prefer it to be the default.
|
||||
Font Settings
|
||||
==============
|
||||
* "Use fixed-width font in tables" - The default is to use the normal system font for everything in the program. The problem is, most default fonts are not fixed width. Why would you want fixed width? This makes things line up better between lines. With fixed width fonts you know that the third byte of one line is directly over top of the third byte for the line one down. This can make it easier to scan through multiple lines.
|
||||
|
||||
* Size - You can set the default font size used in the application. Setting this up or down a bit may help things to look better to you. Getting to absurd is likely to make the program look really bad. This setting may be required if you use a 4k monitor and your operating system doesn't want to cooperate.
|
||||
|
||||
Flow View Settings
|
||||
==================
|
||||
"Use timestamp mode by default": This is another convenience option. With this checked the Flow view will default to using time stamps on the graphing area instead of frame numbers.
|
||||
* "Use timestamp mode by default": This is another convenience option. With this checked the Flow view will default to using time stamps on the graphing area instead of frame numbers.
|
||||
|
||||
"Set Auto Reference by default": The Flow view can either use static referencing or dynamic referencing (see the Flow view documentation for more info). If you'd like to use dynamic referencing and automatically set the reference by default then check this option.
|
||||
* "Set Auto Reference by default": The Flow view can either use static referencing or dynamic referencing (see the Flow view documentation for more info). If you'd like to use dynamic referencing and automatically set the reference by default then check this option.
|
||||
|
||||
* "Hexadecimal Graph Y Axis" - Use 00-FF instead of 0-255 for the Y axis.
|
||||
|
||||
|
||||
Playback Window Settings
|
||||
========================
|
||||
"Loop by default": Set the playback window to default to looping infinitely by default.
|
||||
* "Loop by default": Set the playback window to default to looping infinitely by default.
|
||||
|
||||
"Default playback speed (ms)": Set the default timing for playback
|
||||
* "Default playback speed (ms)": Set the default timing for playback
|
||||
|
||||
"Default Sending Bus": Set a default for which bus to send frames on.
|
||||
* "Default Sending Bus": Set a default for which bus to send frames on.
|
||||
|
||||
|
||||
DBC Settings
|
||||
=============
|
||||
* "Label DBC Messages by default" - Normally the filter section of the main screen just shows the frame IDs. If you label DBC signals you will actually see the name of the DBC message next to the ID.
|
||||
|
||||
* "Ignore DBC message colors" - It's possible to make DBC messages have rainbow colors. Each message can have a different background and foreground color. Sometimes they can get set to some really silly values. To save your sanity you have the option to completely ignore all colors set in the DBC file and just use the default colors for everything.
|
||||
|
||||
File Info And Comparator Window Settings
|
||||
========================================
|
||||
"Auto expand all nodes": Both of the referenced windows have tree views that potentially have a large number of nodes. It's more neat not to expand them all by default but it also then requires more clicks if you have to expand them to view the information. So, you can set whether you'd like to expand them all by default or not.
|
||||
* "Auto expand all nodes": Both of the referenced windows have tree views that potentially have a large number of nodes. It's more neat not to expand them all by default but it also then requires more clicks if you have to expand them to view the information. So, you can set whether you'd like to expand them all by default or not.
|
||||
|
||||
* "Hexadecimal Graph Y Axis" - As with the Flowview, it is possible to change the Y axis to hexadecimal instead of decimal.
|
||||
|
||||
|
||||
MQTT Settings
|
||||
=============
|
||||
|
||||
MQTT allows SavvyCAN to connec to a broker for CAN transmission over the internet. You will need to set up the host, port, username, and password. These things are determined by your broker. You can run your own on Linux with Mosquitto.
|
||||
* MQTT allows SavvyCAN to connect to a broker for CAN transmission over the internet. You will need to set up the host, port, username, and password. These things are determined by your broker. You can run your own on Linux with Mosquitto. Yes, api.savvycan.com really does exist but, no, you can't actually connect as Anonymous and use it. Sorry...
|
||||
|
||||
|
||||
Final Word of Warning
|
||||
|
||||
@@ -3,12 +3,25 @@ DBC Signal Editor
|
||||
|
||||

|
||||
|
||||
Not So Secret Things
|
||||
=====================
|
||||
First of all, here are the things that might otherwise pass under the radar but are functions of this form:
|
||||
|
||||
* Pretty much everything on this form now has secret undo functionality. If you make a mistake you can press CTRL-Z to revert it. This will revert practically anything you've done on this form.
|
||||
|
||||
* The 8x8 data grid is not locked that way. CAN-FD signals are supported and so up to 64 bytes can be shown in the bitfield. The bitfield will automatically adjust for the number of bytes in a given message. However, you can also switch the views yourself by middle clicking in the bitfield.
|
||||
|
||||
* If you right click a different signal in the bitfield you will then be editing that signal instead. This lets you quickly move between signals in the same message. However, this only works for other signals that are either not multiplexed (if you started with a non-multiplexed signal when editing) or otherwise only other signals that have the same multiplexor value.
|
||||
|
||||
|
||||
Defining and Editing Signals
|
||||
============================
|
||||
|
||||
On the right side you can rename the signal and you'll see that it is renamed in the DBC window as well.
|
||||
At the top you can rename the signal and you'll see that it is renamed in the DBC window as well.
|
||||
|
||||
"Bit Length" - This sets how many bits the signal uses. Once you do this you'll see that that many bits are now highlighted in the 8x8 data grid above. The black bit is the "start" bit, green bits are the other bits in the signal. Gray bits are already used by another signal. You can still use them for the current signal too but doing so would be quite unusual unless you have multiplexed signals.
|
||||
The majority of the view is taken up by the bitfield grid. This view shows all the signals contained within this message (that have the same multiplexor value as this one). They're all labeled as well. Bonus fun fact - SavvyCAN technically does support CAN-FD DBC files! That's right, you can load DBC files with CAN-FD signals and they will work. The bitfield will adjust to show enough bits to show the whole message.
|
||||
|
||||
"Bit Length" - This sets how many bits the signal uses. Once you do this you'll see that that many bits are now highlighted in the data grid. The black bit with diagonal stripes is the "start" bit, green bits (with stripes the other way) are the other bits in the signal. Bits used by other signals are colored according to a secret list of colors. They also have the signal name. You should not overlap onto other signals. This could be possible with multiplexed signals but you will NOT see signals on this grid that are not part of the same multiplexor value as this signal so you should NOT overlap here.
|
||||
|
||||
"Byte Order" - The way that the green bits are filled out is changed by this checkbox. Checking it selects little endian mode whereas deselecting chooses big endian mode. This will have an effect on any signal that crosses byte boundaries. The simplest explanation is that little endian signals start at the start bit and then go "down" in bit numbers while big endian mode goes "up."
|
||||
|
||||
|
||||
@@ -12,13 +12,15 @@ This is essentially another CAN fuzzing window but a very special one. This wind
|
||||
Using the UDS Scan Window
|
||||
==========================
|
||||
|
||||
This window allows one to set a list of tests to perform. They will be done in order. You are free to change the parameters of each separately. For instance, one test can scan 0x7E0 through 0x7E7 and the next test can can only 0x600.
|
||||
|
||||
UDS queries are sent out on the bus from "Starting ID" to "Ending ID". Usually UDS compliant ECUs will respond to 0x7E0 through 0x7E7 which is why those are the defaults. Some vehicles use UDS "like" protocols on other IDs. Usually UDS nodes reply with an ID 8 higher than the request ID. This is thus the default in the program. However, some nodes cheat and do not do this. It is quite common for responses to come from an address 16 higher instead. Sometimes the reply address has no resemblance to the listening address. To deal with this situation there is a checkbox "Allow adaptive reply offset." If this is checked then replies will be accepted no matter what address they come from. Deselecting this will cause only replies of the proper offset to be accepted. The offset defaults to 8 but can be changed with the "Reply Offset" selector. Additionally, you can select which bus to scan and set how long you want to wait for replies.
|
||||
|
||||
"Show tests with no Replies" - This checkbox does what it says. It is a personal preference whether you'd like to see an entry in the list for scans that returned no results. Sometimes an ECU will just plain ignore messages it doesn't like. In that case you have the option to see an entry in the list telling you that the message was ignored or whether you'd prefer to reduce clutter and just skip anything that had no reply.
|
||||
"Show 'No Reply'" - This checkbox does what it says. It is a personal preference whether you'd like to see an entry in the list for scans that returned no results. Sometimes an ECU will just plain ignore messages it doesn't like. In that case you have the option to see an entry in the list telling you that the message was ignored or whether you'd prefer to reduce clutter and just skip anything that had no reply.
|
||||
|
||||
You need to also set a type of scan to do. You can select more than one type but if you don't set any then you aren't going to see any results. Keep that in mind as none are checked by default.
|
||||
You need to also set a type of scan to do. Each test you register will do one type of scan but you are free to create as many tests as you like.
|
||||
|
||||
"Read By ID" - UDS allows one to read data from the ECU by an ID number. These are not defined anywhere and are custom to each ECU. But, you can use this to scan a range of IDs to see if you get a response to any of them. There is no real standard for how many bytes the ID will be. It could be 1, it could be 2, 3, 4. It's likely to be around 2. You can set the number of subfunction bytes which will set the size of the ID. Then you can set the upper and lower bound to scan.
|
||||
"Read By ID" - UDS allows one to read data from the ECU by an ID number. Many of these are not defined anywhere and are custom to each ECU. But, you can use this to scan a range of IDs to see if you get a response to any of them. There is usually no real standard for how many bytes the ID will be. It could be 1, it could be 2, 3, 4. It's likely to be around 2. You can set the number of subfunction bytes which will set the size of the ID. Then you can set the upper and lower bound to scan.
|
||||
|
||||
"Read By Addr" - You can also read data by address. Like IDs the address could be different sizes depending on the hardware you are querying. This works the same as reading by ID but by address instead.
|
||||
|
||||
@@ -26,8 +28,10 @@ You need to also set a type of scan to do. You can select more than one type but
|
||||
|
||||
"ECU Reset" - The ECU might also support being reset by a UDS message. This scan type will try the various reset types and see which are supported.
|
||||
|
||||
"Security Access" - When a device is first started and normally operating it will generally not allow you to do potentially dangerous or sensitive operations such as downloading firmware or changing parameters. To do these things you need to enter a different security level. The security access mechanism is used for this. There are a few different security levels that are likely to be supported. This scan type will attempt to find security levels and see if they are protected or not. That is, unfortunately for the casual cracker, most of the time the elevate security levels will be protected by a challenge/response system. The ECU will send you a challenge in the form of one or more random looking bytes. You are tasked with returning the proper response for those challenge bytes. You generally don't have very many guesses before your hands are slapped. Sometimes the C/R is actually fixed and you can just capture valid traffic once and then use the same response forever. Sometimes the C/R is stupidly easy or there are only a couple of different possible answers. Sometimes the ECU developer actually took more than 2 seconds to implement these features and your work will be cut out for you. This scan type will find which levels the ECU seems to support but is unlikely to actually unlock them.
|
||||
"Security Access" - When a device is first started and normally operating it will generally not allow you to do potentially dangerous or sensitive operations such as downloading firmware or changing parameters. To do these things you need to enter a different security level. The security access mechanism is used for this. There are a few different security levels that are likely to be supported. This scan type will attempt to find security levels and see if they are protected or not. That is, unfortunately for the casual cracker, most of the time the elevated security levels will be protected by a challenge/response system. The ECU will send you a challenge in the form of one or more random looking bytes. You are tasked with returning the proper response for those challenge bytes. You generally don't have very many guesses before your hands are slapped. Sometimes the C/R is actually fixed and you can just capture valid traffic once and then use the same response forever. Sometimes the C/R is stupidly easy or there are only a couple of different possible answers. Sometimes the ECU developer actually took more than 2 seconds to implement these features and your work will be cut out for you. This scan type will find which levels the ECU seems to support but is unlikely to actually unlock them.
|
||||
|
||||
"Tester Present" - A scan that tries to see if tester present is supported. It almost certainly is. This scan can be used to sweep a wide range of addresses just to narrow down the list of addresses to scan more thoroughly. Normally tester present is sent periodically by a connected device just to let the ECU know that someone is still there.
|
||||
|
||||
"Wildcard" - Allows for you to set a lower and upper range for the service byte as well as the number of subfunction bytes and the range there as well. This allows for UDS fuzzing by shooting the moon and trying a huge range of traffic just to see what is supported and what isn't. This test can take a VERY long time if you aren't careful but will thoroughly determine what the ECU will support and what it won't.
|
||||
|
||||
"Run test in session type" is supported for some of the above scan types. This will cause the program to attempt to put the ECU into the chosen session type before doing the test. This can be useful as some things will only be supported in diagnostics mode or programming mode. Unfortunately, entering programming mode is likely to require one to elevate the security level which this program is not set up to do (that being a proprietary process.)
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
CURDIR=`pwd`
|
||||
APPDIR="$(dirname -- "$(readlink -f -- "${0}")" )"
|
||||
|
||||
cd "$APPDIR"
|
||||
|
||||
# Set absolute path work around
|
||||
sed -e "s,FULLPATH,$PWD,g" SavvyCAN.desktop > SavvyCAN.desktop.temp
|
||||
# Set absolute path
|
||||
sed -e "s,<DIR>,$CURDIR,g" SavvyCAN.desktop > SavvyCAN.desktop.temp
|
||||
|
||||
cp SavvyCAN.desktop.temp ~/.local/share/applications/SavvyCAN.desktop
|
||||
cp SavvyCAN.desktop.temp ~/Desktop/SavvyCAN.desktop
|
||||
rm SavvyCAN.desktop.temp
|
||||
|
||||
echo "Installed SavvyCAN icons on menu and desktop !"
|
||||
|
||||
|
||||
@@ -481,12 +481,12 @@ void JSHighlighter::mark(const QString &str, Qt::CaseSensitivity caseSensitivity
|
||||
|
||||
QStringList JSHighlighter::keywords() const
|
||||
{
|
||||
return m_keywords.toList();
|
||||
return m_keywords.values();
|
||||
}
|
||||
|
||||
void JSHighlighter::setKeywords(const QStringList &keywords)
|
||||
{
|
||||
m_keywords = QSet<QString>::fromList(keywords);
|
||||
m_keywords = QSet<QString>(keywords.begin(), keywords.end());
|
||||
rehighlight();
|
||||
}
|
||||
|
||||
@@ -931,7 +931,8 @@ void JSEdit::resizeEvent(QResizeEvent *e)
|
||||
void JSEdit::wheelEvent(QWheelEvent *e)
|
||||
{
|
||||
if (e->modifiers() == Qt::ControlModifier) {
|
||||
int steps = e->delta() / 20;
|
||||
QPoint numDegrees = e->angleDelta();
|
||||
int steps = numDegrees.y() / 20;
|
||||
steps = qBound(-3, steps, 3);
|
||||
QFont textFont = font();
|
||||
int pointSize = textFont.pointSize() + steps;
|
||||
|
||||
@@ -24,6 +24,12 @@ public:
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
#ifdef QT_DEBUG
|
||||
//uncomment for verbose debug data in application output
|
||||
//qputenv("QT_FATAL_WARNINGS", "1");
|
||||
//qSetMessagePattern("Type: %{type}\nProduct Name: %{appname}\nFile: %{file}\nLine: %{line}\nMethod: %{function}\nThreadID: %{threadid}\nThreadPtr: %{qthreadptr}\nMessage: %{message}");
|
||||
#endif
|
||||
|
||||
SavvyCANApplication a(argc, argv);
|
||||
|
||||
//Add a local path for Qt extensions, to allow for per-application extensions.
|
||||
|
||||
@@ -21,7 +21,7 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) :
|
||||
ui->comboSendingBus->addItem(tr("None"));
|
||||
ui->comboSendingBus->addItem(tr("0"));
|
||||
ui->comboSendingBus->addItem(tr("1"));
|
||||
ui->comboSendingBus->addItem(tr("Both"));
|
||||
ui->comboSendingBus->addItem(tr("All"));
|
||||
ui->comboSendingBus->addItem(tr("From File"));
|
||||
|
||||
//update the GUI with all the settings we have stored giving things
|
||||
@@ -48,6 +48,7 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) :
|
||||
ui->cbLoadConnections->setChecked(settings.value("Main/SaveRestoreConnections", false).toBool());
|
||||
|
||||
ui->spinFontSize->setValue(settings.value("Main/FontSize", ui->cbDisplayHex->font().pointSize()).toUInt());
|
||||
ui->cbFontFixedWidth->setChecked(settings.value("Main/FontFixedWidth", false).toBool());
|
||||
|
||||
bool secondsMode = settings.value("Main/TimeSeconds", false).toBool();
|
||||
bool clockMode = settings.value("Main/TimeClock", false).toBool();
|
||||
@@ -84,12 +85,28 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) :
|
||||
}
|
||||
}
|
||||
|
||||
ui->cbCSVAbsTime->setChecked(settings.value("Main/CSVAbsTime", false).toBool());
|
||||
ui->comboSendingBus->setCurrentIndex(settings.value("Playback/SendingBus", 4).toInt());
|
||||
ui->cbUseFiltered->setChecked(settings.value("Main/UseFiltered", false).toBool());
|
||||
ui->cbUseOpenGL->setChecked(settings.value("Main/UseOpenGL", false).toBool());
|
||||
ui->cbFilterLabeling->setChecked(settings.value("Main/FilterLabeling", true).toBool());
|
||||
ui->cbIgnoreDBCColors->setChecked(settings.value("Main/IgnoreDBCColors", false).toBool());
|
||||
|
||||
int maxFramesDefault;
|
||||
if (QSysInfo::WordSize > 32)
|
||||
{
|
||||
qDebug() << "64 bit OS detected. Requesting a large preallocation";
|
||||
maxFramesDefault = 10000000;
|
||||
}
|
||||
else //if compiling for 32 bit you can't ask for gigabytes of preallocation so tone it down.
|
||||
{
|
||||
qDebug() << "32 bit OS detected. Requesting a much restricted prealloc";
|
||||
maxFramesDefault = 2000000;
|
||||
}
|
||||
|
||||
ui->spinMaximumFrames->setValue(settings.value("Main/MaximumFrames", maxFramesDefault).toInt());
|
||||
ui->spinBytesPerLine->setValue(settings.value("Main/BytesPerLine", 8).toInt());
|
||||
|
||||
//just for simplicity they all call the same function and that function updates all settings at once
|
||||
connect(ui->cbDisplayHex, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
||||
connect(ui->cbFlowAutoRef, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
||||
@@ -104,6 +121,7 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) :
|
||||
connect(ui->rbMicros, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
||||
connect(ui->rbSysClock, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
||||
connect(ui->rbMillis, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
||||
connect(ui->cbCSVAbsTime, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
||||
connect(ui->comboSendingBus, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSettings()));
|
||||
connect(ui->cbUseFiltered, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
||||
connect(ui->lineClockFormat, SIGNAL(editingFinished()), this, SLOT(updateSettings()));
|
||||
@@ -117,6 +135,9 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) :
|
||||
connect(ui->cbHexGraphFlow, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
||||
connect(ui->cbHexGraphInfo, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
||||
connect(ui->cbIgnoreDBCColors, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
||||
connect(ui->spinMaximumFrames, SIGNAL(valueChanged(int)), this, SLOT(updateSettings()));
|
||||
connect(ui->cbFontFixedWidth, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
||||
connect(ui->spinBytesPerLine, SIGNAL(valueChanged(int)), this, SLOT(updateSettings()));
|
||||
|
||||
installEventFilter(this);
|
||||
}
|
||||
@@ -171,6 +192,7 @@ void MainSettingsDialog::updateSettings()
|
||||
settings.setValue("Main/TimeSeconds", ui->rbSeconds->isChecked());
|
||||
settings.setValue("Main/TimeMillis", ui->rbMillis->isChecked());
|
||||
settings.setValue("Main/TimeClock", ui->rbSysClock->isChecked());
|
||||
settings.setValue("Main/CSVAbsTime", ui->cbCSVAbsTime->isChecked());
|
||||
settings.setValue("Playback/SendingBus", ui->comboSendingBus->currentIndex());
|
||||
settings.setValue("Main/UseFiltered", ui->cbUseFiltered->isChecked());
|
||||
settings.setValue("Main/UseOpenGL", ui->cbUseOpenGL->isChecked());
|
||||
@@ -183,6 +205,9 @@ void MainSettingsDialog::updateSettings()
|
||||
settings.setValue("Remote/Pass", encPass);
|
||||
settings.setValue("Main/FilterLabeling", ui->cbFilterLabeling->isChecked());
|
||||
settings.setValue("Main/IgnoreDBCColors", ui->cbIgnoreDBCColors->isChecked());
|
||||
settings.setValue("Main/MaximumFrames", ui->spinMaximumFrames->value());
|
||||
settings.setValue("Main/BytesPerLine", ui->spinBytesPerLine->value());
|
||||
settings.setValue("Main/FontFixedWidth", ui->cbFontFixedWidth->isChecked());
|
||||
|
||||
settings.sync();
|
||||
emit updatedSettings();
|
||||
|
||||
@@ -30,8 +30,10 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
ui(new Ui::MainWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
qRegisterMetaTypeStreamOperators<QVector<QString>>();
|
||||
qRegisterMetaTypeStreamOperators<QVector<int>>();
|
||||
#endif
|
||||
|
||||
useHex = true;
|
||||
|
||||
@@ -55,12 +57,18 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
verticalHeader->setSectionResizeMode(QHeaderView::Fixed);
|
||||
QSettings settings;
|
||||
int fontSize = settings.value("Main/FontSize", 9).toUInt();
|
||||
QFont sysFont = QFont(); //get default font
|
||||
QFont sysFont;
|
||||
if(settings.value("Main/FontFixedWidth", false).toBool())
|
||||
sysFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); //get default fixed width font
|
||||
else
|
||||
sysFont = QFont(); //get default font
|
||||
sysFont.setPointSize(fontSize);
|
||||
verticalHeader->setDefaultSectionSize(sysFont.pixelSize());
|
||||
verticalHeader->setFont(QFont());
|
||||
ui->canFramesView->setFont(sysFont);
|
||||
|
||||
QHeaderView *HorzHdr = ui->canFramesView->horizontalHeader();
|
||||
HorzHdr->setFont(QFont());
|
||||
HorzHdr->setStretchLastSection(true); //causes the data column to automatically fill the tableview
|
||||
connect(HorzHdr, SIGNAL(sectionClicked(int)), this, SLOT(headerClicked(int)));
|
||||
|
||||
@@ -87,6 +95,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
signalViewerWindow = nullptr;
|
||||
temporalGraphWindow = nullptr;
|
||||
dbcComparatorWindow = nullptr;
|
||||
canBridgeWindow = nullptr;
|
||||
dbcHandler = DBCHandler::getReference();
|
||||
bDirty = false;
|
||||
inhibitFilterUpdate = false;
|
||||
@@ -111,6 +120,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
connect(ui->actionFuzzy_Scope, &QAction::triggered, this, &MainWindow::showFuzzyScopeWindow);
|
||||
connect(ui->actionRange_State_2, &QAction::triggered, this, &MainWindow::showRangeWindow);
|
||||
connect(ui->actionSave_Decoded_Frames, &QAction::triggered, this, &MainWindow::handleSaveDecoded);
|
||||
connect(ui->actionSave_Decoded_Frames_CSV, &QAction::triggered, this, &MainWindow::handleSaveDecodedCsv);
|
||||
connect(ui->actionSingle_Multi_State_2, &QAction::triggered, this, &MainWindow::showSingleMultiWindow);
|
||||
connect(ui->actionFile_Comparison, &QAction::triggered, this, &MainWindow::showComparisonWindow);
|
||||
connect(ui->actionDBC_Comparison, &QAction::triggered, this, &MainWindow::showDBCComparisonWindow);
|
||||
@@ -127,6 +137,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
connect(ui->actionSignal_Viewer, &QAction::triggered, this, &MainWindow::showSignalViewer);
|
||||
connect(ui->actionSave_Continuous_Logfile, &QAction::triggered, this, &MainWindow::handleContinousLogging);
|
||||
connect(ui->actionTemporal_Graph, &QAction::triggered, this, &MainWindow::showTemporalGraphWindow);
|
||||
connect(ui->actionCAN_Bridge, &QAction::triggered, this, &MainWindow::showCANBridgeWindow);
|
||||
|
||||
//handlers fror interactions with the main can frame view table
|
||||
connect(ui->canFramesView, &QAbstractItemView::clicked, this, &MainWindow::gridClicked);
|
||||
@@ -141,6 +152,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
|
||||
connect(ui->cbInterpret, &QAbstractButton::toggled, this, &MainWindow::interpretToggled);
|
||||
connect(ui->cbOverwrite, &QAbstractButton::toggled, this, &MainWindow::overwriteToggled);
|
||||
connect(ui->cbPersistentFilters, &QAbstractButton::toggled, this, &MainWindow::presistentFiltersToggled);
|
||||
connect(ui->listFilters, &QListWidget::itemChanged, this, &MainWindow::filterListItemChanged);
|
||||
connect(ui->listBusFilters, &QListWidget::itemChanged, this, &MainWindow::busFilterListItemChanged);
|
||||
|
||||
@@ -152,6 +164,8 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
connect(ui->btnExpandAll, &QAbstractButton::clicked, this, &MainWindow::expandAllRows);
|
||||
connect(ui->btnCollapseAll, &QAbstractButton::clicked, this, &MainWindow::collapseAllRows);
|
||||
|
||||
connect(ui->tableSimpleSender, SIGNAL(cellChanged(int,int)), this, SLOT(onSenderCellChanged(int,int)));
|
||||
|
||||
lbStatusConnected.setText(tr("Connected to 0 buses"));
|
||||
lbHelp.setText(tr("Press F1 on any screen for help"));
|
||||
lbHelp.setAlignment(Qt::AlignCenter);
|
||||
@@ -177,7 +191,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
updateTimer.setInterval(250);
|
||||
updateTimer.start();
|
||||
|
||||
elapsedTime = new QTime;
|
||||
elapsedTime = new QElapsedTimer;
|
||||
elapsedTime->start();
|
||||
|
||||
isConnected = false;
|
||||
@@ -192,6 +206,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
temp.isReceived = true;
|
||||
temp.setTimeStamp(QCanBusFrame::TimeStamp(0, 100000000));
|
||||
model->addFrame(temp, true);
|
||||
ui->canFramesView->resizeRowToContents(0); // Resize the row to fit the contents so we get a proper height value
|
||||
qApp->processEvents();
|
||||
tickGUIUpdate(); //force a GUI refresh so that the row exists to measure
|
||||
normalRowHeight = ui->canFramesView->rowHeight(0);
|
||||
@@ -199,6 +214,8 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
qDebug() << "normal row height = " << normalRowHeight;
|
||||
model->clearFrames();
|
||||
|
||||
ui->canFramesView->verticalHeader()->setDefaultSectionSize(normalRowHeight); // Set the default height for all rows to the height that was calculated
|
||||
|
||||
//connect(CANConManager::getInstance(), CANConManager::connectionStatusUpdated, this, MainWindow::connectionStatusUpdated);
|
||||
connect(CANConManager::getInstance(), SIGNAL(connectionStatusUpdated(int)), this, SLOT(connectionStatusUpdated(int)));
|
||||
|
||||
@@ -212,12 +229,34 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
ui->actionMotorControlConfig->setVisible(false);
|
||||
ui->actionSingle_Multi_State_2->setVisible(false);
|
||||
|
||||
QStringList headers;
|
||||
headers << "En" << "Bus" << "ID" << "Ext" << "Rem" << "Data"
|
||||
<< "Interval" << "Count";
|
||||
ui->tableSimpleSender->setColumnCount(8);
|
||||
ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_EN, 70);
|
||||
ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_BUS, 70);
|
||||
ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_ID, 70);
|
||||
ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_EXT, 70);
|
||||
ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_REM, 70);
|
||||
ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_DATA, 300);
|
||||
ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_INTERVAL, 100);
|
||||
ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_COUNT, 100);
|
||||
ui->tableSimpleSender->setHorizontalHeaderLabels(headers);
|
||||
|
||||
createSenderRow();
|
||||
|
||||
frameSender = new FrameSenderObject(model->getListReference());
|
||||
|
||||
frameSender->initialize(); //creates the thread and sets things up
|
||||
frameSender->startSending(); //start the timer in the object so enabled things can send
|
||||
|
||||
installEventFilter(this);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
updateTimer.stop();
|
||||
frameSender->stopSending();
|
||||
killEmAll(); //Ride the lightning
|
||||
delete ui;
|
||||
delete model;
|
||||
@@ -252,8 +291,11 @@ void MainWindow::killEmAll()
|
||||
killWindow(firmwareUploaderWindow);
|
||||
killWindow(motorctrlConfigWindow);
|
||||
killWindow(signalViewerWindow);
|
||||
killWindow(connectionWindow);
|
||||
killWindow(temporalGraphWindow);
|
||||
killWindow(canBridgeWindow);
|
||||
|
||||
//trying to kill this window can cause a fault to happen. It's closed last just in case.
|
||||
killWindow(connectionWindow);
|
||||
}
|
||||
|
||||
//forcefully close the window, kill it, and salt the earth
|
||||
@@ -372,16 +414,25 @@ void MainWindow::readUpdateableSettings()
|
||||
useHex = settings.value("Main/UseHex", true).toBool();
|
||||
model->setHexMode(useHex);
|
||||
Utility::decimalMode = !useHex;
|
||||
secondsMode = settings.value("Main/TimeSeconds", false).toBool();
|
||||
model->setSecondsMode(secondsMode);
|
||||
useSystemClock = settings.value("Main/TimeClock", false).toBool();
|
||||
model->setSysTimeMode(useSystemClock);
|
||||
millisMode = settings.value("Main/TimeMillis", false).toBool();
|
||||
model->setMillisMode(millisMode);
|
||||
|
||||
bool tempBool;
|
||||
TimeStyle ts = TS_MICROS;
|
||||
tempBool = settings.value("Main/TimeSeconds", false).toBool();
|
||||
if (tempBool) ts = TS_SECONDS;
|
||||
tempBool = settings.value("Main/TimeClock", false).toBool();
|
||||
if (tempBool) ts = TS_CLOCK;
|
||||
tempBool = settings.value("Main/TimeMillis", false).toBool();
|
||||
if (tempBool) ts = TS_MILLIS;
|
||||
model->setTimeStyle(ts);
|
||||
|
||||
useFiltered = settings.value("Main/UseFiltered", false).toBool();
|
||||
model->setTimeFormat(settings.value("Main/TimeFormat", "MMM-dd HH:mm:ss.zzz").toString());
|
||||
ignoreDBCColors = settings.value("Main/IgnoreDBCColors", false).toBool();
|
||||
model->setIgnoreDBCColors(ignoreDBCColors);
|
||||
int bpl = settings.value("Main/BytesPerLine", 8).toInt();
|
||||
model->setBytesPerLine(bpl);
|
||||
|
||||
CSVAbsTime = settings.value("Main/CSVAbsTime", false).toBool();
|
||||
|
||||
if (settings.value("Main/FilterLabeling", false).toBool())
|
||||
ui->listFilters->setMaximumWidth(250);
|
||||
@@ -411,6 +462,171 @@ void MainWindow::writeSettings()
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onSenderCellChanged(int row, int col)
|
||||
{
|
||||
if (inhibitSenderChanged) return;
|
||||
qDebug() << "onCellChanged";
|
||||
if (row == ui->tableSimpleSender->rowCount() - 1)
|
||||
{
|
||||
createSenderRow();
|
||||
}
|
||||
|
||||
processSenderCellChange(row, col);
|
||||
}
|
||||
|
||||
void MainWindow::processSenderCellChange(int line, int col)
|
||||
{
|
||||
qDebug() << "processSenderCellChange";
|
||||
FrameSendData *tempData;
|
||||
QStringList tokens;
|
||||
int tempVal;
|
||||
|
||||
int numBuses = CANConManager::getInstance()->getNumBuses();
|
||||
QByteArray arr;
|
||||
|
||||
tempData = frameSender->getSendRecordRef(line);
|
||||
|
||||
if (!tempData)
|
||||
{
|
||||
qDebug() << "Need to set up a new entry in senders";
|
||||
FrameSendData dat;
|
||||
dat.enabled = false;
|
||||
dat.count = 0;
|
||||
dat.frameCount = 0;
|
||||
dat.bus = 0;
|
||||
frameSender->addSendRecord(dat);
|
||||
tempData = frameSender->getSendRecordRef(line);
|
||||
}
|
||||
|
||||
if (!tempData)
|
||||
{
|
||||
qDebug() << "No data to modify in processSenderCellChange. This is a bug!";
|
||||
return;
|
||||
}
|
||||
|
||||
switch (col)
|
||||
{
|
||||
case SIMP_COL::SC_COL_EN: //Enable check box
|
||||
if (ui->tableSimpleSender->item(line, 0)->checkState() == Qt::Checked)
|
||||
{
|
||||
tempData->enabled = true;
|
||||
}
|
||||
else tempData->enabled = false;
|
||||
qDebug() << "Setting enabled to " << tempData->enabled;
|
||||
break;
|
||||
case SIMP_COL::SC_COL_BUS: //Bus designation
|
||||
tempVal = Utility::ParseStringToNum(ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_BUS)->text());
|
||||
if (tempVal < -1) tempVal = -1;
|
||||
if (tempVal >= numBuses) tempVal = numBuses - 1;
|
||||
tempData->bus = tempVal;
|
||||
qDebug() << "Setting bus to " << tempVal;
|
||||
break;
|
||||
case SIMP_COL::SC_COL_ID: //ID field
|
||||
tempVal = Utility::ParseStringToNum(ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_ID)->text());
|
||||
if (tempVal < 0) tempVal = 0;
|
||||
if (tempVal > 0x7FFFFFFF) tempVal = 0x7FFFFFFF;
|
||||
tempData->setFrameId(tempVal);
|
||||
if (tempData->frameId() > 0x7FF) {
|
||||
tempData->setExtendedFrameFormat(true);
|
||||
ui->tableSimpleSender->blockSignals(true);
|
||||
ui->tableSimpleSender->item(line, ST_COLS::SENDTAB_COL_EXT)->setCheckState(Qt::Checked);
|
||||
ui->tableSimpleSender->blockSignals(false);
|
||||
}
|
||||
qDebug() << "setting ID to " << tempVal;
|
||||
break;
|
||||
case SIMP_COL::SC_COL_EXT:
|
||||
if (ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_EXT)->checkState() == Qt::Checked) {
|
||||
tempData->setExtendedFrameFormat(true);
|
||||
} else {
|
||||
tempData->setExtendedFrameFormat(false);
|
||||
}
|
||||
break;
|
||||
case SIMP_COL::SC_COL_REM:
|
||||
if (ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_REM)->checkState() == Qt::Checked) {
|
||||
tempData->setFrameType(QCanBusFrame::RemoteRequestFrame);
|
||||
} else {
|
||||
tempData->setFrameType(QCanBusFrame::DataFrame);
|
||||
}
|
||||
break;
|
||||
case SIMP_COL::SC_COL_DATA: //Data bytes
|
||||
for (int i = 0; i < 8; i++) tempData->payload().data()[i] = 0;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK( 5, 14, 0 )
|
||||
tokens = ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_DATA)->text().split(" ", Qt::SkipEmptyParts);
|
||||
#else
|
||||
tokens = ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_DATA)->text().split(" ", QString::SkipEmptyParts);
|
||||
#endif
|
||||
arr.clear();
|
||||
arr.reserve(tokens.count());
|
||||
for (int j = 0; j < tokens.count(); j++)
|
||||
{
|
||||
arr.append((uint8_t)Utility::ParseStringToNum(tokens[j]));
|
||||
}
|
||||
tempData->setPayload(arr);
|
||||
break;
|
||||
case SIMP_COL::SC_COL_INTERVAL: //interval in ms
|
||||
|
||||
QString trigger = ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_INTERVAL)->text().toUpper();
|
||||
|
||||
Trigger thisTrigger;
|
||||
thisTrigger.bus = -1; //-1 means we don't care which
|
||||
thisTrigger.ID = -1; //the rest of these being -1 means nothing has changed it
|
||||
thisTrigger.maxCount = -1;
|
||||
thisTrigger.milliseconds = -1;
|
||||
thisTrigger.currCount = 0;
|
||||
thisTrigger.msCounter = 0;
|
||||
thisTrigger.triggerMask = 0;
|
||||
thisTrigger.readyCount = true;
|
||||
|
||||
tempData->triggers.clear();
|
||||
tempData->triggers.reserve(1);
|
||||
|
||||
if (trigger != "")
|
||||
{
|
||||
thisTrigger.milliseconds = Utility::ParseStringToNum(trigger);
|
||||
thisTrigger.triggerMask |= TriggerMask::TRG_MS;
|
||||
}
|
||||
|
||||
if (thisTrigger.milliseconds < 1) thisTrigger.milliseconds = 1;
|
||||
|
||||
tempData->triggers.append(thisTrigger);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::createSenderRow()
|
||||
{
|
||||
int row = ui->tableSimpleSender->rowCount();
|
||||
ui->tableSimpleSender->insertRow(row);
|
||||
|
||||
QTableWidgetItem *item = new QTableWidgetItem();
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
inhibitSenderChanged = true;
|
||||
ui->tableSimpleSender->setItem(row, SIMP_COL::SC_COL_EN, item);
|
||||
|
||||
item = new QTableWidgetItem();
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
ui->tableSimpleSender->setItem(row, SIMP_COL::SC_COL_EXT, item);
|
||||
|
||||
item = new QTableWidgetItem();
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
ui->tableSimpleSender->setItem(row, SIMP_COL::SC_COL_REM, item);
|
||||
|
||||
for (int i = 1; i <= SIMP_COL::SC_COL_COUNT; i++)
|
||||
{
|
||||
if (i != SIMP_COL::SC_COL_EXT && i != SIMP_COL::SC_COL_REM) {
|
||||
item = new QTableWidgetItem("");
|
||||
ui->tableSimpleSender->setItem(row, i, item);
|
||||
}
|
||||
}
|
||||
|
||||
inhibitSenderChanged = false;
|
||||
}
|
||||
|
||||
void MainWindow::updateConnectionSettings(QString connectionType, QString port, int speed0, int speed1)
|
||||
{
|
||||
Q_UNUSED(connectionType);
|
||||
@@ -432,6 +648,8 @@ void MainWindow::headerClicked(int logicalIndex)
|
||||
{
|
||||
//ui->canFramesView->sortByColumn(logicalIndex);
|
||||
model->sortByColumn(logicalIndex);
|
||||
|
||||
manageRowExpansion();
|
||||
}
|
||||
|
||||
void MainWindow::expandAllRows()
|
||||
@@ -452,9 +670,30 @@ void MainWindow::expandAllRows()
|
||||
if (goAhead)
|
||||
{
|
||||
ui->canFramesView->resizeRowsToContents();
|
||||
|
||||
rowExpansionActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::manageRowExpansion()
|
||||
{
|
||||
int numRows = ui->canFramesView->model()->rowCount();
|
||||
if(numRows < 20000)
|
||||
{
|
||||
if(rowExpansionActive && model->getInterpretMode())
|
||||
ui->canFramesView->resizeRowsToContents();
|
||||
}
|
||||
else
|
||||
{
|
||||
disableAutoRowExpansion();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::disableAutoRowExpansion()
|
||||
{
|
||||
rowExpansionActive = false;
|
||||
}
|
||||
|
||||
void MainWindow::collapseAllRows()
|
||||
{
|
||||
bool goAhead = false;
|
||||
@@ -473,6 +712,8 @@ void MainWindow::collapseAllRows()
|
||||
if (goAhead)
|
||||
{
|
||||
for (int i = 0; i < numRows; i++) ui->canFramesView->setRowHeight(i, normalRowHeight);
|
||||
|
||||
rowExpansionActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,7 +792,7 @@ void MainWindow::setupSendToLatestGraphWindow()
|
||||
GraphParams param;
|
||||
QString signalName = getSignalNameFromPosition(contextMenuPosition);
|
||||
param.ID = getMessageIDFromPosition(contextMenuPosition);
|
||||
DBC_MESSAGE *msg = dbcHandler->findMessageForFilter(param.ID, nullptr);
|
||||
DBC_MESSAGE *msg = dbcHandler->findMessage(param.ID);
|
||||
if(msg)
|
||||
{
|
||||
DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(signalName);
|
||||
@@ -612,16 +853,29 @@ void MainWindow::overwriteToggled(bool state)
|
||||
}
|
||||
else
|
||||
{
|
||||
rowExpansionActive = false;
|
||||
model->setOverwriteMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::presistentFiltersToggled(bool state)
|
||||
{
|
||||
if (state)
|
||||
{
|
||||
model->setClearMode(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
model->setClearMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::updateFilterList()
|
||||
{
|
||||
if (model == nullptr) return;
|
||||
const QMap<int, bool> *filters = model->getFiltersReference();
|
||||
const QMap<int, bool> *busFilters = model->getBusFiltersReference();
|
||||
if (filters == nullptr && busFilters == nullptr) return;
|
||||
if (filters == nullptr || busFilters == nullptr) return;
|
||||
|
||||
qDebug() << "updateFilterList called on MainWindow";
|
||||
|
||||
@@ -642,7 +896,7 @@ void MainWindow::updateFilterList()
|
||||
|
||||
for (filterIter = busFilters->begin(); filterIter != busFilters->end(); ++filterIter)
|
||||
{
|
||||
QListWidgetItem *thisItem = FilterUtility::createCheckableBusFilterItem(filterIter.key(), filterIter.value(), ui->listBusFilters);
|
||||
/*QListWidgetItem *thisItem = */ FilterUtility::createCheckableBusFilterItem(filterIter.key(), filterIter.value(), ui->listBusFilters);
|
||||
}
|
||||
inhibitFilterUpdate = false;
|
||||
}
|
||||
@@ -658,6 +912,8 @@ void MainWindow::filterListItemChanged(QListWidgetItem *item)
|
||||
if (item->checkState() == Qt::Checked) isSet = true;
|
||||
|
||||
model->setFilterState(ID, isSet);
|
||||
|
||||
manageRowExpansion();
|
||||
}
|
||||
|
||||
void MainWindow::busFilterListItemChanged(QListWidgetItem *item)
|
||||
@@ -671,6 +927,8 @@ void MainWindow::busFilterListItemChanged(QListWidgetItem *item)
|
||||
if (item->checkState() == Qt::Checked) isSet = true;
|
||||
|
||||
model->setBusFilterState(ID, isSet);
|
||||
|
||||
manageRowExpansion();
|
||||
}
|
||||
|
||||
void MainWindow::filterSetAll()
|
||||
@@ -682,6 +940,8 @@ void MainWindow::filterSetAll()
|
||||
}
|
||||
inhibitFilterUpdate = false;
|
||||
model->setAllFilters(true);
|
||||
|
||||
manageRowExpansion();
|
||||
}
|
||||
|
||||
void MainWindow::filterClearAll()
|
||||
@@ -697,6 +957,7 @@ void MainWindow::filterClearAll()
|
||||
|
||||
void MainWindow::logReceivedFrame(CANConnection* conn, QVector<CANFrame> frames)
|
||||
{
|
||||
Q_UNUSED(conn);
|
||||
if (continuousLogging)
|
||||
{
|
||||
FrameFileIO::writeContinuousNative(&frames, 0);
|
||||
@@ -717,12 +978,14 @@ void MainWindow::tickGUIUpdate()
|
||||
framesPerSec = 0;
|
||||
|
||||
ui->lbNumFrames->setText(QString::number(model->rowCount()));
|
||||
if (rxFrames > 0 && /*allowCapture && */ ui->cbAutoScroll->isChecked()) ui->canFramesView->scrollToBottom();
|
||||
if (rxFrames > 0 && /*allowCapture && */ ui->cbAutoScroll->isChecked())
|
||||
ui->canFramesView->scrollToBottom();
|
||||
ui->lbFPS->setText(QString::number(framesPerSec));
|
||||
if (rxFrames > 0)
|
||||
{
|
||||
bDirty = true;
|
||||
emit framesUpdated(rxFrames); //anyone care that frames were updated?
|
||||
manageRowExpansion();
|
||||
}
|
||||
|
||||
if (model->needsFilterRefresh()) updateFilterList();
|
||||
@@ -751,6 +1014,18 @@ void MainWindow::tickGUIUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
//refresh the count for all the frame senders
|
||||
FrameSendData *tempData;
|
||||
int numRows = ui->tableSimpleSender->rowCount();
|
||||
for (int i = 0; i < numRows; i++)
|
||||
{
|
||||
tempData = frameSender->getSendRecordRef(i);
|
||||
if (tempData)
|
||||
{
|
||||
ui->tableSimpleSender->item(i, SIMP_COL::SC_COL_COUNT)->setText(QString::number( tempData->count ));
|
||||
}
|
||||
}
|
||||
|
||||
rxFrames = 0;
|
||||
//}
|
||||
}
|
||||
@@ -773,7 +1048,7 @@ void MainWindow::addFrameToDisplay(CANFrame &frame, bool autoRefresh = false)
|
||||
|
||||
//A sub-window is sending us a center on timestamp and ID signal
|
||||
//try to find the relevant frame in the list and focus on it.
|
||||
void MainWindow::gotCenterTimeID(int32_t ID, double timestamp)
|
||||
void MainWindow::gotCenterTimeID(uint32_t ID, double timestamp)
|
||||
{
|
||||
int idx = model->getIndexFromTimeID(ID, timestamp);
|
||||
if (idx > -1)
|
||||
@@ -823,6 +1098,7 @@ void MainWindow::handleLoadFile()
|
||||
|
||||
if (loadResult)
|
||||
{
|
||||
disableAutoRowExpansion();
|
||||
ui->canFramesView->scrollToTop();
|
||||
model->clearFrames();
|
||||
model->insertFrames(tempFrames);
|
||||
@@ -866,6 +1142,7 @@ void MainWindow::handleDroppedFile(const QString &filename)
|
||||
|
||||
if (loadResult)
|
||||
{
|
||||
disableAutoRowExpansion();
|
||||
ui->canFramesView->scrollToTop();
|
||||
model->clearFrames();
|
||||
model->insertFrames(loadedFrames);
|
||||
@@ -967,13 +1244,24 @@ void MainWindow::handleLoadFilters()
|
||||
}
|
||||
|
||||
void MainWindow::handleSaveDecoded()
|
||||
{
|
||||
handleSaveDecodedMethod(false);
|
||||
}
|
||||
|
||||
void MainWindow::handleSaveDecodedCsv()
|
||||
{
|
||||
handleSaveDecodedMethod(true);
|
||||
}
|
||||
|
||||
void MainWindow::handleSaveDecodedMethod(bool csv)
|
||||
{
|
||||
QString filename;
|
||||
QFileDialog dialog(this);
|
||||
QSettings settings;
|
||||
|
||||
QStringList filters;
|
||||
filters.append(QString(tr("Text File (*.txt)")));
|
||||
if (!csv) filters.append(QString(tr("Text File (*.txt *.TXT)")));
|
||||
else filters.append(QString(tr("CSV File (*.csv *.CSV)")));
|
||||
|
||||
dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString());
|
||||
dialog.setFileMode(QFileDialog::AnyFile);
|
||||
@@ -984,12 +1272,196 @@ void MainWindow::handleSaveDecoded()
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
filename = dialog.selectedFiles()[0];
|
||||
if (!filename.contains('.')) filename += ".txt";
|
||||
saveDecodedTextFile(filename);
|
||||
if (!filename.contains('.'))
|
||||
{
|
||||
if (!csv) filename += ".txt";
|
||||
else filename += ".csv";
|
||||
}
|
||||
|
||||
if(csv)
|
||||
saveDecodedTextFileAsColumns(filename);
|
||||
else
|
||||
saveDecodedTextFile(filename);
|
||||
|
||||
settings.setValue("FileIO/LoadSaveDirectory", dialog.directory().path());
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::saveDecodedTextFileAsColumns(QString filename)
|
||||
{
|
||||
QFile *outFile = new QFile(filename);
|
||||
const QVector<CANFrame> *frames = model->getFilteredListReference();
|
||||
|
||||
//const unsigned char *data;
|
||||
int dataLen;
|
||||
const CANFrame *frame;
|
||||
|
||||
if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
return;
|
||||
/*
|
||||
Time: 205.173000 ID: 0x20E Std Bus: 0 Len: 8
|
||||
Data Bytes: 88 10 00 13 BB 00 06 00
|
||||
SignalName Value
|
||||
*/
|
||||
QList<QPair<uint32_t, int>> msgsAndColumns;
|
||||
int columnsAdded = 0;
|
||||
int dataStartCol = 0;
|
||||
|
||||
QString builderString;
|
||||
if (CSVAbsTime)
|
||||
{
|
||||
builderString += tr("Year") + "," + tr("Month") + "," + tr("Day") + "," + tr("Hour") + "," + tr("Minute") + "," + tr("Second") + "," + tr("Ms") + ",";
|
||||
dataStartCol += 7;
|
||||
}
|
||||
else
|
||||
{
|
||||
//time
|
||||
builderString += tr("Time") + ",";
|
||||
dataStartCol++;
|
||||
}
|
||||
//id
|
||||
builderString += tr("ID") + ",";
|
||||
dataStartCol++;
|
||||
//if (frame->hasExtendedFrameFormat()) builderString += tr(" Ext ");
|
||||
//else builderString += tr(" Std ");
|
||||
//bus
|
||||
builderString += tr("Bus") + ",";
|
||||
dataStartCol++;
|
||||
//len
|
||||
builderString += tr("DataLen") + ",";
|
||||
dataStartCol++;
|
||||
|
||||
columnsAdded = dataStartCol;
|
||||
|
||||
//loop through all the frames and the message data therein
|
||||
for (int c = 0; c < frames->count(); c++)
|
||||
{
|
||||
frame = &frames->at(c);
|
||||
//data = reinterpret_cast<const unsigned char *>(frame->payload().constData());
|
||||
dataLen = frame->payload().count();
|
||||
|
||||
//add all column names
|
||||
if (dbcHandler != nullptr)
|
||||
{
|
||||
DBC_MESSAGE *msg = dbcHandler->findMessage(*frame);
|
||||
if (msg != nullptr)
|
||||
{
|
||||
bool found = false;
|
||||
for (int j = 0; j < msg->sigHandler->getCount(); j++)
|
||||
{
|
||||
if(j==0)
|
||||
{
|
||||
for(int m=0; m<msgsAndColumns.count(); m++)
|
||||
{
|
||||
if(msgsAndColumns[m].first == msg->ID)
|
||||
found = true;
|
||||
}
|
||||
if(found == false)
|
||||
msgsAndColumns.append(QPair<uint32_t,int>(msg->ID, columnsAdded));
|
||||
}
|
||||
|
||||
if(found == false)
|
||||
{
|
||||
QString temp;
|
||||
if (msg->sigHandler->findSignalByIdx(j)->processAsText(*frame, temp))
|
||||
{
|
||||
builderString.append(msg->sigHandler->findSignalByIdx(j)->name);
|
||||
builderString.append(",");
|
||||
columnsAdded++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//add EOL
|
||||
builderString += "\n";
|
||||
//write out the header row
|
||||
outFile->write(builderString.toUtf8());
|
||||
|
||||
//builderString = tr("Data Bytes: ");
|
||||
//for (int temp = 0; temp < dataLen; temp++)
|
||||
//{
|
||||
// builderString += Utility::formatNumber(data[temp]) + " ";
|
||||
//}
|
||||
//builderString += "\n";
|
||||
//outFile->write(builderString.toUtf8());
|
||||
|
||||
int dataColumnsAdded = 0;
|
||||
builderString = "";
|
||||
for (int c = 0; c < frames->count(); c++)
|
||||
{
|
||||
dataColumnsAdded = 0;
|
||||
frame = &frames->at(c);
|
||||
//data = reinterpret_cast<const unsigned char *>(frame->payload().constData());
|
||||
dataLen = frame->payload().count();
|
||||
|
||||
QString builderString;
|
||||
if (CSVAbsTime)
|
||||
{
|
||||
QDateTime dt = QDateTime::fromMSecsSinceEpoch(frame->timeStamp().microSeconds() / 1000);
|
||||
builderString += QString::number(dt.date().year()) + "," + QString::number(dt.date().month()) + ",";
|
||||
builderString += QString::number(dt.date().day()) + "," + QString::number(dt.time().hour()) + ",";
|
||||
builderString += QString::number(dt.time().minute()) + "," + QString::number(dt.time().second()) + ",";
|
||||
builderString += QString::number(dt.time().msec()) + ",";
|
||||
dataColumnsAdded += 7;
|
||||
}
|
||||
else {
|
||||
builderString += QString::number((frame->timeStamp().microSeconds() / 1000000.0), 'f', 6) + ",";
|
||||
dataColumnsAdded++;
|
||||
}
|
||||
//id
|
||||
builderString += Utility::formatCANID(frame->frameId(), frame->hasExtendedFrameFormat()) + ",";
|
||||
dataColumnsAdded++;
|
||||
//if (frame->hasExtendedFrameFormat()) builderString += tr(" Ext ");
|
||||
//else builderString += tr(" Std ");
|
||||
//bus
|
||||
builderString += QString::number(frame->bus) + ",";
|
||||
dataColumnsAdded++;
|
||||
//len
|
||||
builderString += QString::number(dataLen) + ",";
|
||||
dataColumnsAdded++;
|
||||
|
||||
if (dbcHandler != nullptr)
|
||||
{
|
||||
DBC_MESSAGE *msg = dbcHandler->findMessage(*frame);
|
||||
if (msg != nullptr)
|
||||
{
|
||||
for (int j = 0; j < msg->sigHandler->getCount(); j++)
|
||||
{
|
||||
if(j==0)
|
||||
{
|
||||
for(int i = 0; i<msgsAndColumns.count(); i++)
|
||||
{
|
||||
if(msgsAndColumns[i].first == msg->ID)
|
||||
{
|
||||
int startCol = msgsAndColumns[i].second;
|
||||
while(dataColumnsAdded < startCol)
|
||||
{
|
||||
builderString += ",";
|
||||
dataColumnsAdded++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString temp;
|
||||
if (msg->sigHandler->findSignalByIdx(j)->processAsText(*frame, temp, false, false))
|
||||
{
|
||||
builderString.append(temp);
|
||||
builderString.append(",");
|
||||
dataColumnsAdded++;
|
||||
}
|
||||
}
|
||||
}
|
||||
builderString.append("\n");
|
||||
outFile->write(builderString.toUtf8());
|
||||
}
|
||||
}
|
||||
outFile->close();
|
||||
}
|
||||
|
||||
void MainWindow::saveDecodedTextFile(QString filename)
|
||||
{
|
||||
QFile *outFile = new QFile(filename);
|
||||
@@ -1100,6 +1572,11 @@ CANFrameModel* MainWindow::getCANFrameModel()
|
||||
return model;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* All functions past this point set up the various other windows that can be opened
|
||||
*/
|
||||
|
||||
void MainWindow::showSettingsDialog()
|
||||
{
|
||||
if (!settingsDialog)
|
||||
@@ -1129,13 +1606,13 @@ void MainWindow::showGraphingWindow()
|
||||
lastGraphingWindow = new GraphingWindow(model->getListReference());
|
||||
graphWindows.append(lastGraphingWindow);
|
||||
|
||||
connect(lastGraphingWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), this, SLOT(gotCenterTimeID(int32_t,double)));
|
||||
connect(this, SIGNAL(sendCenterTimeID(uint32_t,double)), lastGraphingWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
||||
connect(lastGraphingWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), this, SLOT(gotCenterTimeID(uint32_t,double)));
|
||||
connect(this, SIGNAL(sendCenterTimeID(uint32_t,double)), lastGraphingWindow, SLOT(gotCenterTimeID(uint32_t,double)));
|
||||
|
||||
if (flowViewWindow) //connect the two external windows together
|
||||
{
|
||||
connect(lastGraphingWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), flowViewWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
||||
connect(flowViewWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), lastGraphingWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
||||
connect(lastGraphingWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), flowViewWindow, SLOT(gotCenterTimeID(uint32_t,double)));
|
||||
connect(flowViewWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), lastGraphingWindow, SLOT(gotCenterTimeID(uint32_t,double)));
|
||||
}
|
||||
|
||||
lastGraphingWindow->show();
|
||||
@@ -1146,11 +1623,26 @@ void MainWindow::showTemporalGraphWindow()
|
||||
//only create an instance of the object if we dont have one. Otherwise just display the existing one.
|
||||
if (!temporalGraphWindow)
|
||||
{
|
||||
const QVector<CANFrame> *frames;
|
||||
if (!useFiltered)
|
||||
temporalGraphWindow = new TemporalGraphWindow(model->getListReference());
|
||||
frames = model->getListReference();
|
||||
else
|
||||
temporalGraphWindow = new TemporalGraphWindow(model->getFilteredListReference());
|
||||
frames = model->getFilteredListReference();
|
||||
|
||||
if(frames->count() > 2000)
|
||||
{
|
||||
QMessageBox::StandardButton confirmDialog;
|
||||
confirmDialog = QMessageBox::question(this, "Danger Will Robinson", "There are a lot of frames (>2000) to plot, this may take a while or crash the app. Crash likely with more than 10k frames. Continue?",
|
||||
QMessageBox::Yes|QMessageBox::No);
|
||||
if (confirmDialog == QMessageBox::No)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
temporalGraphWindow = new TemporalGraphWindow(frames);
|
||||
}
|
||||
|
||||
temporalGraphWindow->show();
|
||||
}
|
||||
|
||||
@@ -1195,6 +1687,15 @@ void MainWindow::showBisectWindow()
|
||||
bisectWindow->show();
|
||||
}
|
||||
|
||||
void MainWindow::showCANBridgeWindow()
|
||||
{
|
||||
if (!canBridgeWindow)
|
||||
{
|
||||
canBridgeWindow = new CANBridgeWindow(model->getListReference());
|
||||
}
|
||||
canBridgeWindow->show();
|
||||
}
|
||||
|
||||
void MainWindow::showFrameSenderWindow()
|
||||
{
|
||||
if (!frameSenderWindow)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "framefileio.h"
|
||||
#include "dbc/dbchandler.h"
|
||||
#include "bus_protocols/isotp_handler.h"
|
||||
|
||||
#include "framesenderobject.h"
|
||||
#include "re/graphingwindow.h"
|
||||
#include "re/frameinfowindow.h"
|
||||
#include "frameplaybackwindow.h"
|
||||
@@ -33,12 +33,25 @@
|
||||
#include "signalviewerwindow.h"
|
||||
#include "re/temporalgraphwindow.h"
|
||||
#include "re/dbccomparatorwindow.h"
|
||||
#include "canbridgewindow.h"
|
||||
|
||||
class CANConnection;
|
||||
class ConnectionWindow;
|
||||
class ISOTP_InterpreterWindow;
|
||||
class ScriptingWindow;
|
||||
|
||||
enum SIMP_COL
|
||||
{
|
||||
SC_COL_EN = 0,
|
||||
SC_COL_BUS = 1,
|
||||
SC_COL_ID = 2,
|
||||
SC_COL_EXT = 3,
|
||||
SC_COL_REM = 4,
|
||||
SC_COL_DATA = 5,
|
||||
SC_COL_INTERVAL = 6,
|
||||
SC_COL_COUNT = 7,
|
||||
};
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
}
|
||||
@@ -89,8 +102,10 @@ private slots:
|
||||
void showSignalViewer();
|
||||
void showTemporalGraphWindow();
|
||||
void showDBCComparisonWindow();
|
||||
void showCANBridgeWindow();
|
||||
void exitApp();
|
||||
void handleSaveDecoded();
|
||||
void handleSaveDecodedCsv();
|
||||
void connectionStatusUpdated(int conns);
|
||||
void gridClicked(QModelIndex);
|
||||
void gridDoubleClicked(QModelIndex);
|
||||
@@ -99,6 +114,7 @@ private slots:
|
||||
void setupSendToLatestGraphWindow();
|
||||
void interpretToggled(bool);
|
||||
void overwriteToggled(bool);
|
||||
void presistentFiltersToggled(bool state);
|
||||
void logReceivedFrame(CANConnection*, QVector<CANFrame>);
|
||||
void tickGUIUpdate();
|
||||
void toggleCapture();
|
||||
@@ -110,12 +126,13 @@ private slots:
|
||||
void filterClearAll();
|
||||
void headerClicked (int logicalIndex);
|
||||
void DBCSettingsUpdated();
|
||||
void onSenderCellChanged(int, int);
|
||||
|
||||
public slots:
|
||||
void gotFrames(int);
|
||||
void updateSettings();
|
||||
void readUpdateableSettings();
|
||||
void gotCenterTimeID(int32_t ID, double timestamp);
|
||||
void gotCenterTimeID(uint32_t ID, double timestamp);
|
||||
void updateConnectionSettings(QString connectionType, QString port, int speed0, int speed1);
|
||||
|
||||
signals:
|
||||
@@ -137,18 +154,18 @@ private:
|
||||
DBCHandler *dbcHandler;
|
||||
QByteArray inputBuffer;
|
||||
QTimer updateTimer;
|
||||
QTime *elapsedTime;
|
||||
QElapsedTimer *elapsedTime;
|
||||
FrameSenderObject *frameSender;
|
||||
int framesPerSec;
|
||||
int rxFrames;
|
||||
bool inhibitFilterUpdate;
|
||||
bool useHex;
|
||||
bool allowCapture;
|
||||
bool secondsMode;
|
||||
bool millisMode;
|
||||
bool useSystemClock;
|
||||
bool ignoreDBCColors;
|
||||
bool CSVAbsTime;
|
||||
bool bDirty; //have frames been added or subtracted since the last save/load?
|
||||
bool useFiltered; //should sub-windows use the unfiltered or filtered frames list?
|
||||
bool inhibitSenderChanged;
|
||||
|
||||
bool continuousLogging;
|
||||
int continuousLogFlushCounter;
|
||||
@@ -181,6 +198,7 @@ private:
|
||||
SignalViewerWindow *signalViewerWindow;
|
||||
TemporalGraphWindow *temporalGraphWindow;
|
||||
DBCComparatorWindow *dbcComparatorWindow;
|
||||
CANBridgeWindow *canBridgeWindow;
|
||||
|
||||
//various private storage
|
||||
QLabel lbStatusConnected;
|
||||
@@ -190,11 +208,14 @@ private:
|
||||
int normalRowHeight;
|
||||
bool isConnected;
|
||||
QPoint contextMenuPosition;
|
||||
bool rowExpansionActive = false;
|
||||
|
||||
//private methods
|
||||
QString getSignalNameFromPosition(QPoint pos);
|
||||
uint32_t getMessageIDFromPosition(QPoint pos);
|
||||
void handleSaveDecodedMethod(bool csv);
|
||||
void saveDecodedTextFile(QString);
|
||||
void saveDecodedTextFileAsColumns(QString);
|
||||
void addFrameToDisplay(CANFrame &, bool);
|
||||
void updateFileStatus();
|
||||
void closeEvent(QCloseEvent *event);
|
||||
@@ -203,6 +224,10 @@ private:
|
||||
void readSettings();
|
||||
void writeSettings();
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
void manageRowExpansion();
|
||||
void disableAutoRowExpansion();
|
||||
void createSenderRow();
|
||||
void processSenderCellChange(int line, int col);
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
@@ -129,7 +129,7 @@ class Q_MQTT_EXPORT Client : public QObject
|
||||
Q_PROPERTY(quint8 _willQos READ willQos WRITE setWillQos)
|
||||
Q_PROPERTY(bool _willRetain READ willRetain WRITE setWillRetain)
|
||||
Q_PROPERTY(QByteArray _willMessage READ willMessage WRITE setWillMessage)
|
||||
Q_PROPERTY(QString _connectionState READ connectionState)
|
||||
Q_PROPERTY(ConnectionState _connectionState READ connectionState)
|
||||
#ifndef QT_NO_SSL
|
||||
Q_PROPERTY(QSslConfiguration _sslConfiguration READ sslConfiguration WRITE setSslConfiguration)
|
||||
#endif // QT_NO_SSL
|
||||
|
||||
@@ -41,9 +41,9 @@ QMQTT::Socket::Socket(QObject* parent)
|
||||
connect(_socket.data(), &QTcpSocket::connected, this, &SocketInterface::connected);
|
||||
connect(_socket.data(), &QTcpSocket::disconnected, this, &SocketInterface::disconnected);
|
||||
connect(_socket.data(),
|
||||
static_cast<void (QTcpSocket::*)(QAbstractSocket::SocketError)>(&QTcpSocket::error),
|
||||
SIGNAL(error(QTcpSocket::error)),
|
||||
this,
|
||||
static_cast<void (SocketInterface::*)(QAbstractSocket::SocketError)>(&SocketInterface::error));
|
||||
SLOT(errorHandler(SocketInterface::error)));
|
||||
}
|
||||
|
||||
QMQTT::Socket::~Socket()
|
||||
|
||||
@@ -46,9 +46,9 @@ QMQTT::SslSocket::SslSocket(const QSslConfiguration& config, QObject* parent)
|
||||
connect(_socket.data(), &QSslSocket::encrypted, this, &SocketInterface::connected);
|
||||
connect(_socket.data(), &QSslSocket::disconnected, this, &SocketInterface::disconnected);
|
||||
connect(_socket.data(),
|
||||
static_cast<void (QSslSocket::*)(QAbstractSocket::SocketError)>(&QSslSocket::error),
|
||||
SIGNAL(error(QSslSocket::error)),
|
||||
this,
|
||||
static_cast<void (SocketInterface::*)(QAbstractSocket::SocketError)>(&SocketInterface::error));
|
||||
SLOT(errorHandler(SocketInterface::error)));
|
||||
connect(_socket.data(),
|
||||
static_cast<void (QSslSocket::*)(const QList<QSslError>&)>(&QSslSocket::sslErrors),
|
||||
this,
|
||||
|
||||
@@ -113,7 +113,7 @@ const unsigned char *pcap_next_ng(pcap_t *p, struct pcap_pkthdr *h) {
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
// calcualte option length aligned on 4 byte boundary
|
||||
// calculate option length aligned on 4 byte boundary
|
||||
unsigned int option_file_length = 4*(oh.option_length/4) + ((oh.option_length%4) ? 4:0);
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define PCAPLITE_H
|
||||
|
||||
#include <stdio.h>
|
||||
#if defined(unix)
|
||||
#if defined(unix) || defined __APPLE__
|
||||
#include <sys/time.h>
|
||||
#else
|
||||
#include <winsock.h>
|
||||
|
||||