Merge pull request #130 from collin80/WIP

Merge WIP into Master
This commit is contained in:
Collin Kidder
2018-04-23 20:39:11 -04:00
committed by GitHub
124 changed files with 6872 additions and 4223 deletions
+9 -4
View File
@@ -4,7 +4,7 @@
#
#-------------------------------------------------
QT = core gui printsupport qml serialbus serialport widgets
QT = core gui printsupport qml serialbus serialport widgets help
CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT
@@ -63,7 +63,9 @@ SOURCES += main.cpp\
bus_protocols/j1939_handler.cpp \
bus_protocols/uds_handler.cpp \
jsedit.cpp \
frameplaybackobject.cpp
frameplaybackobject.cpp \
helpwindow.cpp \
blfhandler.cpp
HEADERS += mainwindow.h \
can_structs.h \
@@ -117,7 +119,9 @@ HEADERS += mainwindow.h \
bus_protocols/uds_handler.h \
bus_protocols/isotp_message.h \
jsedit.h \
frameplaybackobject.h
frameplaybackobject.h \
helpwindow.h \
blfhandler.h
FORMS += ui/candatagrid.ui \
ui/connectionwindow.ui \
@@ -143,7 +147,8 @@ FORMS += ui/candatagrid.ui \
ui/snifferwindow.ui \
ui/udsscanwindow.ui \
ui/bisectwindow.ui \
ui/signalviewerwindow.ui
ui/signalviewerwindow.ui \
helpwindow.ui
DISTFILES +=
+21
View File
@@ -3,6 +3,7 @@
#include "mainwindow.h"
#include "framefileio.h"
#include "helpwindow.h"
#include <QDebug>
#include <algorithm>
@@ -23,10 +24,12 @@ 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);
installEventFilter(this);
}
BisectWindow::~BisectWindow()
{
removeEventFilter(this);
delete ui;
}
@@ -39,6 +42,24 @@ void BisectWindow::showEvent(QShowEvent* event)
updatePercentText();
}
bool BisectWindow::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("bisector.html");
break;
}
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
return false;
}
void BisectWindow::refreshIDList()
{
int id;
+1
View File
@@ -39,6 +39,7 @@ private:
void refreshIDList();
void refreshFrameNumbers();
bool eventFilter(QObject *obj, QEvent *event);
};
#endif // BISECTWINDOW_H
+91
View File
@@ -0,0 +1,91 @@
#include "blfhandler.h"
#include <QDebug>
#include <QFile>
#include <QString>
#include <QtEndian>
BLFHandler::BLFHandler()
{
}
/*
Written while peeking at source code here:
https://python-can.readthedocs.io/en/latest/_modules/can/io/blf.html
All the code actually below is freshly written though.
*/
bool BLFHandler::loadBLF(QString filename, QVector<CANFrame>* frames)
{
BLF_OBJ_HEADER objHeader;
QByteArray fileData;
QByteArray uncompressedData;
QByteArray junk;
BLF_OBJECT obj;
uint32_t pos;
BLF_CAN_OBJ canObject;
QFile *inFile = new QFile(filename);
if (!inFile->open(QIODevice::ReadOnly))
{
delete inFile;
return false;
}
inFile->read((char *)&header, sizeof(header));
if (qFromLittleEndian(header.sig) == 0x47474F4C)
{
}
else return false;
while (!inFile->atEnd())
{
inFile->read((char *)&objHeader, sizeof(objHeader));
if (qFromLittleEndian(objHeader.sig) == 0x4A424F4C)
{
int readSize = objHeader.objSize - sizeof(BLF_OBJ_HEADER);
fileData = inFile->read(readSize);
junk = inFile->read(readSize % 4); //file is padded so sizes must always end up on even multiple of 4
if (objHeader.objType == BLF_CONTAINER)
{
fileData.prepend(objHeader.uncompSize & 0xFF);
fileData.prepend((objHeader.uncompSize >> 8) & 0xFF);
fileData.prepend((objHeader.uncompSize >> 16) & 0xFF);
fileData.prepend((objHeader.uncompSize >> 24) & 0xFF);
uncompressedData += qUncompress(fileData);
pos = 0;
while (pos + sizeof(BLF_OBJ_HEADER) < uncompressedData.count())
{
memcpy(&obj.header, uncompressedData.mid(pos, sizeof(BLF_OBJ_HEADER)).constData(), sizeof(BLF_OBJ_HEADER));
if (qFromLittleEndian(objHeader.sig) == 0x4A424F4C)
{
fileData = uncompressedData.mid(pos + sizeof(BLF_OBJ_HEADER), obj.header.objSize - sizeof(BLF_OBJ_HEADER));
if (obj.header.objType == BLF_CAN_MSG)
{
memcpy(&canObject, fileData.constData(), sizeof(BLF_CAN_OBJ));
CANFrame frame;
frame.bus = canObject.channel;
frame.extended = (canObject.id & 0x80000000ull)?true:false;
frame.ID = canObject.id & 0x1FFFFFFFull;
frame.isReceived = true;
frame.len = canObject.dlc;
frame.timestamp = obj.header.uncompSize / 1000000.0; //uncompsize field also used for timestamp oddly enough
for (int i = 0; i < 8; i++) frame.data[i] = canObject.data[i];
frames->append(frame);
}
pos += obj.header.objSize + (obj.header.objSize % 4);
}
}
uncompressedData.remove(0, pos);
}
}
else return false;
}
return true;
}
bool BLFHandler::saveBLF(QString filename, QVector<CANFrame> *frames)
{
return false;
}
+79
View File
@@ -0,0 +1,79 @@
#ifndef BLFHANDLER_H
#define BLFHANDLER_H
#include <Qt>
#include <QByteArray>
#include <QList>
#include "can_structs.h"
enum
{
BLF_CAN_MSG = 1,
BLF_CAN_ERR = 2,
BLF_CONTAINER = 10,
BLF_GLOBAL_MARKER = 96,
};
struct BLF_FILE_HEADER
{
uint32_t sig;
uint32_t headerSize;
uint8_t appID;
uint8_t appVerMajor;
uint8_t appVerMinor;
uint8_t appVerBuild;
uint8_t binLogVerMajor;
uint8_t binLogVerMinor;
uint8_t binLogVerBuild;
uint8_t binLogVerPatch;
uint64_t fileSize;
uint64_t uncompressedFileSize;
uint32_t countObjs;
uint32_t countObjsRead;
uint8_t startTime[16];
uint8_t stopTime[16];
uint8_t unused[72];
}; //144 bytes
struct BLF_OBJ_HEADER
{
uint32_t sig;
uint16_t headerSize;
uint16_t headerVersion;
uint32_t objSize;
uint32_t objType;
uint32_t flags;
uint16_t nothing;
uint16_t objVer;
uint64_t uncompSize;
}; //32 bytes
struct BLF_OBJECT
{
BLF_OBJ_HEADER header;
QByteArray data;
};
struct BLF_CAN_OBJ
{
uint16_t channel;
uint8_t flags;
uint8_t dlc;
uint32_t id;
uint8_t data[8];
};
class BLFHandler
{
public:
BLFHandler();
bool loadBLF(QString filename, QVector<CANFrame>* frames);
bool saveBLF(QString filename, QVector<CANFrame>* frames);
private:
BLF_FILE_HEADER header;
QList<BLF_OBJECT> objects;
};
#endif // BLFHANDLER_H
+7 -1
View File
@@ -9,6 +9,7 @@ ISOTP_HANDLER::ISOTP_HANDLER()
processAll = false;
lastSenderBus = 0;
lastSenderID = 0;
issuedMultiFrame = false;
modelFrames = MainWindow::getReference()->getCANFrameModel()->getListReference();
@@ -60,6 +61,7 @@ void ISOTP_HANDLER::sendISOTPFrame(int bus, int ID, QVector<unsigned char> data)
if (data.length() < 8)
{
issuedMultiFrame = false;
frame.bus = bus;
frame.extended = false;
frame.ID = ID;
@@ -71,6 +73,7 @@ void ISOTP_HANDLER::sendISOTPFrame(int bus, int ID, QVector<unsigned char> data)
}
else //need to send a multi-part ISO_TP message - Respects timing and frame number based flow control
{
issuedMultiFrame = true;
frame.bus = bus;
frame.ID = ID;
frame.extended = false;
@@ -200,6 +203,7 @@ void ISOTP_HANDLER::processFrame(const CANFrame &frame)
msg.ID = ID;
msg.timestamp = frame.timestamp;
msg.isReceived = frame.isReceived;
issuedMultiFrame = true;
frameLen = frameLen << 8;
if (useExtendedAddressing)
{
@@ -220,7 +224,7 @@ void ISOTP_HANDLER::processFrame(const CANFrame &frame)
messageBuffer.append(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 && issuedMultiFrame)
{
CANFrame outFrame;
outFrame.bus = lastSenderBus;
@@ -260,6 +264,7 @@ void ISOTP_HANDLER::processFrame(const CANFrame &frame)
if (pMsg->len <= pMsg->data.count())
{
//qDebug() << "Emitting multiframe ISOTP message";
issuedMultiFrame = false;
emit newISOMessage(*pMsg);
}
break;
@@ -300,6 +305,7 @@ void ISOTP_HANDLER::checkNeedFlush(uint64_t ID)
//used to pass by reference but now newISOMessage should pass by value which makes it easier to use cross thread
//qDebug() << "Flushing a partial frame";
emit newISOMessage(messageBuffer[i]);
issuedMultiFrame = false;
messageBuffer.removeAt(i);
return;
}
+1
View File
@@ -46,6 +46,7 @@ private:
int framesUntilFlow;
bool processAll;
bool issueFlowMsgs;
bool issuedMultiFrame;
QTimer frameTimer;
uint32_t lastSenderID;
uint32_t lastSenderBus;
+27 -4
View File
@@ -40,7 +40,7 @@ int CANFrameModel::totalFrameCount()
int CANFrameModel::columnCount(const QModelIndex &index) const
{
Q_UNUSED(index);
return 7;
return 8;
}
CANFrameModel::CANFrameModel(QObject *parent)
@@ -222,7 +222,8 @@ enum class Column {
Direction = 3, ///< Whether the frame was transmitted or received
Bus = 4, ///< The bus where the frame was transmitted or received
Length = 5, ///< The frames payload data length
Data = 6 ///< The frames payload data
ASCII = 6, ///< The payload interpreted as ASCII characters
Data = 7 ///< The frames payload data
};
QVariant CANFrameModel::data(const QModelIndex &index, int role) const
@@ -282,13 +283,33 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
return QString::number(thisFrame.bus);
case Column::Length:
return QString::number(thisFrame.len);
case Column::Data:
case Column::ASCII:
if (thisFrame.ID >= 0x7FFFFFF0ull)
{
tempString.append("MARK ");
tempString.append(QString::number(thisFrame.ID & 0x7));
return tempString;
}
dLen = thisFrame.len;
if (dLen < 0) dLen = 0;
if (dLen > 8) dLen = 8;
for (int i = 0; i < dLen; i++)
{
tempString.append(Utility::formatNumber(thisFrame.data[i]));
quint8 byt = thisFrame.data[i];
if (byt < 0x20) byt = 0x2E; //A dot
if (byt > 0x7E) byt = 0x2E;
tempString.append(QString::fromUtf8((char *)&byt, 1));
}
return tempString;
case Column::Data:
dLen = thisFrame.len;
if (dLen < 0) dLen = 0;
if (dLen > 8) dLen = 8;
//if (useHexMode) tempString.append("0x ");
for (int i = 0; i < dLen; i++)
{
if (useHexMode) tempString.append( QString::number(thisFrame.data[i], 16).toUpper().rightJustified(2, '0'));
else tempString.append(QString::number(thisFrame.data[i], 10));
tempString.append(" ");
}
//now, if we're supposed to interpret the data and the DBC handler is loaded then use it
@@ -339,6 +360,8 @@ QVariant CANFrameModel::headerData(int section, Qt::Orientation orientation,
return QString(tr("Bus"));
case Column::Length:
return QString(tr("Len"));
case Column::ASCII:
return QString(tr("ASCII"));
case Column::Data:
return QString(tr("Data"));
}
+1 -1
View File
@@ -1,7 +1,7 @@
#ifndef CONFIG
#define CONFIG
#define VERSION 181
#define VERSION 184
//try to keep this in sync.
//SavvyCAN will complain if you connect a GVRET board with a revision
+1
View File
@@ -17,6 +17,7 @@ namespace CANCon {
GVRET_SERIAL,
KVASER,
SOCKETCAN,
REMOTE,
NONE
};
}
+7 -5
View File
@@ -8,11 +8,13 @@ using namespace CANCon;
CANConnection* CanConFactory::create(type pType, QString pPortName)
{
switch(pType) {
case SOCKETCAN:
return new SerialBusConnection(pPortName);
case GVRET_SERIAL:
return new GVRetSerial(pPortName);
default: {}
case SOCKETCAN:
return new SerialBusConnection(pPortName);
case GVRET_SERIAL:
return new GVRetSerial(pPortName);
case REMOTE:
return new GVRetSerial(pPortName); //it's a special case of GVRET connected over TCP/IP so it uses the same class
default: {}
}
return NULL;
+59 -13
View File
@@ -3,13 +3,12 @@
#include "connectionwindow.h"
#include "mainwindow.h"
#include "helpwindow.h"
#include "ui_connectionwindow.h"
#include "connections/canconfactory.h"
#include "connections/canconmanager.h"
#include "canbus.h"
ConnectionWindow::ConnectionWindow(QWidget *parent) :
QDialog(parent),
ui(new Ui::ConnectionWindow)
@@ -53,9 +52,11 @@ ConnectionWindow::ConnectionWindow(QWidget *parent) :
connect(ui->rbGVRET, &QAbstractButton::clicked, this, &ConnectionWindow::handleConnTypeChanged);
connect(ui->rbKvaser, &QAbstractButton::clicked, this, &ConnectionWindow::handleConnTypeChanged);
connect(ui->rbSocketCAN, &QAbstractButton::clicked, this, &ConnectionWindow::handleConnTypeChanged);
connect(ui->rbRemote, &QAbstractButton::clicked, this, &ConnectionWindow::handleConnTypeChanged);
connect(ui->tableConnections->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &ConnectionWindow::currentRowChanged);
connect(ui->btnActivateAll, &QPushButton::clicked, this, &ConnectionWindow::handleEnableAll);
connect(ui->btnDeactivateAll, &QPushButton::clicked, this, &ConnectionWindow::handleDisableAll);
connect(ui->btnReconnect, &QPushButton::clicked, this, &ConnectionWindow::handleReconnect);
connect(ui->btnRemoveBus, &QPushButton::clicked, this, &ConnectionWindow::handleRemoveConn);
connect(ui->btnClearDebug, &QPushButton::clicked, this, &ConnectionWindow::handleClearDebugText);
connect(ui->btnSendHex, &QPushButton::clicked, this, &ConnectionWindow::handleSendHex);
@@ -87,6 +88,7 @@ void ConnectionWindow::showEvent(QShowEvent* event)
{
QDialog::showEvent(event);
qDebug() << "Show connectionwindow";
installEventFilter(this);
readSettings();
ui->tableConnections->selectRow(0);
currentRowChanged(ui->tableConnections->currentIndex(), ui->tableConnections->currentIndex());
@@ -96,9 +98,28 @@ void ConnectionWindow::showEvent(QShowEvent* event)
void ConnectionWindow::closeEvent(QCloseEvent *event)
{
Q_UNUSED(event);
removeEventFilter(this);
writeSettings();
}
bool ConnectionWindow::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyRelease) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
switch (keyEvent->key())
{
case Qt::Key_F1:
HelpWindow::getRef()->showHelp("connectionwindow.html");
break;
}
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
return false;
}
void ConnectionWindow::readSettings()
{
QSettings settings;
@@ -149,26 +170,38 @@ void ConnectionWindow::setActiveAll(bool pActive)
connModel->refresh();
}
void ConnectionWindow::consoleEnableChanged(bool checked) {
void ConnectionWindow::handleReconnect()
{
int selIdx = ui->tableConnections->selectionModel()->currentIndex().row();
if (selIdx <0) return;
int busId;
CANConnection* conn_p = connModel->getAtIdx(selIdx, busId);
if(!conn_p) return;
CANConnection* conn_p = connModel->getAtIdx(ui->tableConnections->currentIndex().row(), busId);
conn_p->stop();
conn_p->start();
}
void ConnectionWindow::consoleEnableChanged(bool checked) {
ui->textConsole->setEnabled(checked);
ui->btnClearDebug->setEnabled(checked);
ui->btnSendHex->setEnabled(checked);
ui->btnSendText->setEnabled(checked);
ui->lineSend->setEnabled(checked);
if(!conn_p) return;
QList<CANConnection*>& conns = CANConManager::getInstance()->getConnections();
if (checked) { //enable console
connect(conn_p, SIGNAL(debugOutput(QString)), this, SLOT(getDebugText(QString)));
connect(this, SIGNAL(sendDebugData(QByteArray)), conn_p, SLOT(debugInput(QByteArray)));
}
else { //turn it off
disconnect(conn_p, SIGNAL(debugOutput(QString)), 0, 0);
disconnect(this, SIGNAL(sendDebugData(QByteArray)), conn_p, SLOT(debugInput(QByteArray)));
foreach(CANConnection* conn_p, conns)
{
if (checked) { //enable console
connect(conn_p, SIGNAL(debugOutput(QString)), this, SLOT(getDebugText(QString)));
connect(this, SIGNAL(sendDebugData(QByteArray)), conn_p, SLOT(debugInput(QByteArray)));
}
else { //turn it off
disconnect(conn_p, SIGNAL(debugOutput(QString)), 0, 0);
disconnect(this, SIGNAL(sendDebugData(QByteArray)), conn_p, SLOT(debugInput(QByteArray)));
}
}
}
@@ -194,6 +227,7 @@ void ConnectionWindow::handleConnTypeChanged()
if (ui->rbGVRET->isChecked()) selectSerial();
if (ui->rbKvaser->isChecked()) selectKvaser();
if (ui->rbSocketCAN->isChecked()) selectSocketCan();
if (ui->rbRemote->isChecked()) selectRemote();
}
@@ -219,6 +253,7 @@ void ConnectionWindow::handleOKButton()
return;
/* add connection to model */
connModel->add(conn_p);
consoleEnableChanged(ui->ckEnableConsole->isChecked());
}
}
@@ -294,6 +329,7 @@ void ConnectionWindow::handleSendText() {
void ConnectionWindow::selectSerial()
{
ui->lPort->setText("Port:");
/* set combobox page visible */
ui->stPort->setCurrentWidget(ui->cbPage);
@@ -306,16 +342,24 @@ void ConnectionWindow::selectSerial()
void ConnectionWindow::selectKvaser()
{
ui->lPort->setText("Port:");
/* set combobox page visible */
ui->stPort->setCurrentWidget(ui->cbPage);
}
void ConnectionWindow::selectSocketCan()
{
ui->lPort->setText("Port:");
/* set edit text page visible */
ui->stPort->setCurrentWidget(ui->etPage);
}
void ConnectionWindow::selectRemote()
{
ui->lPort->setText("IP Address:");
ui->stPort->setCurrentWidget(ui->etPage);
}
void ConnectionWindow::setSpeed(int speed0)
{
}
@@ -350,6 +394,7 @@ void ConnectionWindow::setPortName(CANCon::type pType, QString pPortName)
break;
}
case CANCon::SOCKETCAN:
case CANCon::REMOTE:
{
ui->lePort->setText(pPortName);
break;
@@ -372,6 +417,7 @@ QString ConnectionWindow::getPortName()
case CANCon::KVASER:
return ui->cbPort->currentText();
case CANCon::SOCKETCAN:
case CANCon::REMOTE:
return ui->lePort->text();
default:
qDebug() << "getPortName: can't get port";
@@ -385,7 +431,7 @@ CANCon::type ConnectionWindow::getConnectionType()
if (ui->rbGVRET->isChecked()) return CANCon::GVRET_SERIAL;
if (ui->rbKvaser->isChecked()) return CANCon::KVASER;
if (ui->rbSocketCAN->isChecked()) return CANCon::SOCKETCAN;
if (ui->rbRemote->isChecked()) return CANCon::REMOTE;
qDebug() << "getConnectionType: error";
return CANCon::NONE;
}
+3
View File
@@ -52,6 +52,7 @@ private slots:
void handleRemoveConn();
void handleEnableAll();
void handleDisableAll();
void handleReconnect();
void handleRevert();
void handleNewConn();
void handleClearDebugText();
@@ -68,6 +69,7 @@ private:
void selectSerial();
void selectKvaser();
void selectSocketCan();
void selectRemote();
bool isSocketCanAvailable();
int getSpeed();
QString getPortName();
@@ -79,6 +81,7 @@ private:
void saveConnections();
void showEvent(QShowEvent *);
void closeEvent(QCloseEvent *event);
bool eventFilter(QObject *obj, QEvent *event);
void readSettings();
void writeSettings();
};
+201 -67
View File
@@ -4,6 +4,7 @@
#include <QSerialPortInfo>
#include <QSettings>
#include <QStringBuilder>
#include <QtNetwork>
#include "gvretserial.h"
@@ -15,6 +16,7 @@ GVRetSerial::GVRetSerial(QString portName) :
debugOutput("GVRetSerial()");
serial = NULL;
tcpClient = NULL;
rx_state = IDLE;
rx_step = 0;
gotValidated = true;
@@ -35,9 +37,40 @@ GVRetSerial::~GVRetSerial()
debugOutput("~GVRetSerial()");
}
void GVRetSerial::sendToSerial(const QByteArray &bytes)
{
if (serial == NULL && tcpClient == NULL)
{
debugOutput("Attempt to write to serial port when it has not been initialized!");
return;
}
if (serial && !serial->isOpen())
{
debugOutput("Attempt to write to serial port when it is not open!");
return;
}
if (tcpClient && !tcpClient->isOpen())
{
debugOutput("Attempt to write to TCP/IP 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) % " ";
}
debugOutput(buildDebug);
if (serial) serial->write(bytes);
if (tcpClient) tcpClient->write(bytes);
}
void GVRetSerial::piStarted()
{
{
connectDevice();
/* start timer */
@@ -161,9 +194,7 @@ void GVRetSerial::piSetBusSettings(int pBusIdx, CANBus bus)
buffer[8] = (unsigned char)(can1Baud >> 16);
buffer[9] = (unsigned char)(can1Baud >> 24);
buffer[10] = 0;
if (serial == NULL) return;
if (!serial->isOpen()) return;
serial->write(buffer);
sendToSerial(buffer);
}
else
{
@@ -186,9 +217,7 @@ void GVRetSerial::piSetBusSettings(int pBusIdx, CANBus bus)
buffer[12] = (unsigned char)(lin2Baud >> 16);
buffer[13] = (unsigned char)(lin2Baud >> 24);
buffer[14] = 0;
if (serial == NULL) return;
if (!serial->isOpen()) return;
serial->write(buffer);
sendToSerial(buffer);
}
}
@@ -210,7 +239,7 @@ bool GVRetSerial::piSendFrame(const CANFrame& frame)
ID = frame.ID;
if (frame.extended) ID |= 1 << 31;
buffer[0] = (char)0xF1; //start of a command over serial
buffer[0] = (unsigned char)0xF1; //start of a command over serial
buffer[1] = 0; //command ID for sending a CANBUS frame
buffer[2] = (unsigned char)(ID & 0xFF); //four bytes of ID LSB first
buffer[3] = (unsigned char)(ID >> 8);
@@ -224,9 +253,7 @@ bool GVRetSerial::piSendFrame(const CANFrame& frame)
}
buffer[8 + frame.len] = 0;
//qDebug() << "writing " << buffer.length() << " bytes to serial port";
debugOutput("writing " + QString::number(buffer.length()) + " bytes to serial port");
serial->write(buffer);
sendToSerial(buffer);
return true;
}
@@ -254,39 +281,65 @@ void GVRetSerial::connectDevice()
/* disconnect device */
if(serial)
disconnectDevice();
if(tcpClient)
disconnectDevice();
/* open new device */
serial = new QSerialPort(QSerialPortInfo(getPort()));
if(!serial) {
qDebug() << "can't open serial port " << getPort();
debugOutput("can't open serial port " + getPort());
return;
}
/* configure */
serial->setDataBits(serial->Data8);
serial->setFlowControl(serial->HardwareControl); //this is important though
if (!serial->open(QIODevice::ReadWrite))
if (getPort().contains('.')) //TCP/IP mode then since it looks like an IP address
{
qDebug() << serial->errorString();
tcpClient = new QTcpSocket();
tcpClient->connectToHost(getPort(), 23);
connect(tcpClient, SIGNAL(readyRead()), this, SLOT(readSerialData()));
connect(tcpClient, SIGNAL(connected()), this, SLOT(tcpConnected()));
}
serial->setDataTerminalReady(true); //you do need to set these or the fan gets dirty
serial->setRequestToSend(true);
else {
serial = new QSerialPort(QSerialPortInfo(getPort()));
if(!serial) {
qDebug() << "can't open serial port " << getPort();
debugOutput("can't open serial port " + getPort());
return;
}
debugOutput("Created Serial Port Object");
/* configure */
serial->setDataBits(serial->Data8);
serial->setFlowControl(serial->HardwareControl); //this is important though
//serial->setFlowControl(serial->NoFlowControl);
serial->setBaudRate(1000000);
if (!serial->open(QIODevice::ReadWrite))
{
qDebug() << serial->errorString();
}
//serial->setDataTerminalReady(false); //you do need to set these or the fan gets dirty
//serial->setRequestToSend(false);
serial->setDataTerminalReady(true); //you do need to set these or the fan gets dirty
serial->setRequestToSend(true);
debugOutput("Opened Serial Port");
/* connect reading event */
connect(serial, SIGNAL(readyRead()), this, SLOT(readSerialData()));
connect(serial, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(serialError(QSerialPort::SerialPortError)));
}
if (serial) tcpConnected(); //well, not a proper function name I guess...
}
void GVRetSerial::tcpConnected()
{
qDebug() << "Connected to GVRET Device!";
QByteArray output;
output.append((char)0xE7); //this puts the device into binary comm mode
output.append((char)0xE7);
output.append((unsigned char)0xE7); //this puts the device into binary comm mode
output.append((unsigned char)0xE7);
output.append((char)0xF1);
output.append((char)0x0C); //get number of actually implemented buses. Not implemented except on M2RET
output.append((unsigned char)0xF1);
output.append((unsigned char)0x0C); //get number of actually implemented buses. Not implemented except on M2RET
mNumBuses = 2; //the proper number if C/12 is not implemented
output.append((char)0xF1); //signal we want to issue a command
output.append((char)0x06); //request canbus stats from the board
output.append((unsigned char)0xF1); //signal we want to issue a command
output.append((unsigned char)0x06); //request canbus stats from the board
output.append((char)0xF1); //another command to the GVRET
output.append((char)0x07); //request device information
output.append((unsigned char)0xF1); //another command to the GVRET
output.append((unsigned char)0x07); //request device information
/*output.append((char)0xF1);
output.append((char)0x08); //setting singlewire mode
@@ -299,21 +352,15 @@ void GVRetSerial::connectDevice()
output.append((char)0xFF); //signal we don't want single wire mode
}*/
output.append((char)0xF1); //and another command
output.append((char)0x01); //Time Sync - Not implemented until 333 but we can try
output.append((unsigned char)0xF1); //and another command
output.append((unsigned char)0x01); //Time Sync - Not implemented until 333 but we can try
output.append((char)0xF1); //yet another command
output.append((char)0x09); //comm validation command
output.append((unsigned char)0xF1); //yet another command
output.append((unsigned char)0x09); //comm validation command
continuousTimeSync = true;
serial->write(output);
QString buildDebug;
buildDebug = "Write to serial -> ";
foreach (int byt, output) {
buildDebug = buildDebug % QString::number(byt, 16) % " ";
}
debugOutput(buildDebug);
sendToSerial(output);
if(doValidation) {
QTimer::singleShot(1000, this, SLOT(connectionTimeout()));
@@ -325,12 +372,8 @@ void GVRetSerial::connectDevice()
stats.numHardwareBuses = mNumBuses;
emit status(stats);
}
/* connect reading event */
connect(serial, SIGNAL(readyRead()), this, SLOT(readSerialData()));
}
void GVRetSerial::disconnectDevice() {
if (serial != NULL)
{
@@ -344,6 +387,92 @@ void GVRetSerial::disconnectDevice() {
delete serial;
serial = NULL;
}
if (tcpClient != NULL)
{
if (tcpClient->isOpen())
{
tcpClient->close();
}
tcpClient->disconnect();
delete tcpClient;
tcpClient = NULL;
}
setStatus(CANCon::NOT_CONNECTED);
CANConStatus stats;
stats.conStatus = getStatus();
stats.numHardwareBuses = mNumBuses;
emit status(stats);
}
void GVRetSerial::serialError(QSerialPort::SerialPortError err)
{
QString errMessage;
bool killConnection = false;
switch (err)
{
case QSerialPort::NoError:
return;
case QSerialPort::DeviceNotFoundError:
errMessage = "Device not found error on serial";
killConnection = true;
break;
case QSerialPort::PermissionError:
errMessage = "Permission error on serial port";
killConnection = true;
break;
case QSerialPort::OpenError:
errMessage = "Open error on serial port";
killConnection = true;
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";
break;
case QSerialPort::ReadError:
errMessage = "Read error on serial port";
break;
case QSerialPort::ResourceError:
errMessage = "Serial port seems to have disappeared.";
killConnection = true;
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;
break;
case QSerialPort::TimeoutError:
errMessage = "Timeout error on serial port";
killConnection = true;
break;
case QSerialPort::NotOpenError:
errMessage = "The serial port isn't open dummy";
killConnection = true;
break;
}
serial->clearError();
serial->flush();
serial->close();
if (errMessage.length() > 1)
{
qDebug() << errMessage;
debugOutput(errMessage);
}
if (killConnection)
{
qDebug() << "Shooting the serial object in the head. It deserves it.";
disconnectDevice();
}
}
@@ -363,9 +492,13 @@ void GVRetSerial::connectionTimeout()
void GVRetSerial::readSerialData()
{
QByteArray data = serial->readAll();
QByteArray data;
unsigned char c;
QString debugBuild;
if (serial) data = serial->readAll();
if (tcpClient) data = tcpClient->readAll();
debugOutput("Got data from serial. Len = " % QString::number(data.length()));
//qDebug() << (tr("Got data from serial. Len = %0").arg(data.length()));
for (int i = 0; i < data.length(); i++)
@@ -380,7 +513,7 @@ void GVRetSerial::readSerialData()
//Debugging data sent from connection window. Inject it into Comm traffic.
void GVRetSerial::debugInput(QByteArray bytes) {
serial->write(bytes);
sendToSerial(bytes);
}
void GVRetSerial::procRXChar(unsigned char c)
@@ -689,9 +822,9 @@ void GVRetSerial::procRXChar(unsigned char c)
}
}
output.append((char)0xF1); //start a new command
output.append((char)13); //get extended buses
serial->write(output);
output.append((unsigned char)0xF1); //start a new command
output.append((unsigned char)13); //get extended buses
sendToSerial(output);
emit status(stats);
break;
@@ -746,8 +879,11 @@ void GVRetSerial::procRXChar(unsigned char c)
qDebug() << "SWCAN Baud = " << swcanBaud;
qDebug() << "LIN1 Baud = " << lin1Baud;
qDebug() << "LIN2 Baud = " << lin2Baud;
mBusData[2].mBus.setSpeed(swcanBaud);
mBusData[2].mBus.setEnabled(swcanEnabled);
if (getNumBuses() > 2)
{
mBusData[2].mBus.setSpeed(swcanBaud);
mBusData[2].mBus.setEnabled(swcanEnabled);
}
setStatus(CANCon::CONNECTED);
stats.conStatus = getStatus();
@@ -781,28 +917,29 @@ void GVRetSerial::handleTick()
{
if (lastSystemTimeBasis != CANConManager::getInstance()->getTimeBasis()) rebuildLocalTimeBasis();
//qDebug() << "Tick!";
/*
if( CANCon::CONNECTED == getStatus() )
{
if (!gotValidated && doValidation)
{
if (serial == NULL) return;
if (serial->isOpen()) //if it's still false we have a problem...
if (serial == NULL && tcpClient == NULL) return;
if ( (serial && serial->isOpen()) || (tcpClient && tcpClient->isOpen())) //if it's still false we have a problem...
{
qDebug() << "Comm validation failed. ";
setStatus(CANCon::NOT_CONNECTED);
emit status(getStatus());
//emit status(getStatus());
disconnectDevice(); //start by stopping everything.
//Then wait 500ms and restart the connection automatically
QTimer::singleShot(500, this, SLOT(connectDevice()));
//QTimer::singleShot(500, this, SLOT(connectDevice()));
return;
}
}
else if (doValidation); //qDebug() << "Comm connection validated";
}
*/
if (doValidation && serial && serial->isOpen()) sendCommValidation();
if (doValidation && tcpClient && tcpClient->isOpen()) sendCommValidation();
}
@@ -811,13 +948,10 @@ void GVRetSerial::sendCommValidation()
QByteArray output;
gotValidated = false;
output.append((char)0xF1); //another command to the GVRET
output.append((char)0x09); //request a reply to get validation
//send it twice for good measure.
output.append((char)0xF1); //another command to the GVRET
output.append((char)0x09); //request a reply to get validation
output.append((unsigned char)0xF1); //another command to the GVRET
output.append((unsigned char)0x09); //request a reply to get validation
serial->write(output);
sendToSerial(output);
}
+5
View File
@@ -5,6 +5,7 @@
#include <QCanBusDevice>
#include <QThread>
#include <QTimer>
#include <QTcpSocket>
/*************/
#include <QDateTime>
@@ -62,6 +63,8 @@ private slots:
void connectDevice();
void connectionTimeout();
void readSerialData();
void serialError(QSerialPort::SerialPortError err);
void tcpConnected();
void handleTick();
private:
@@ -69,6 +72,7 @@ private:
void procRXChar(unsigned char);
void sendCommValidation();
void rebuildLocalTimeBasis();
void sendToSerial(const QByteArray &bytes);
protected:
QTimer mTimer;
@@ -79,6 +83,7 @@ protected:
bool isAutoRestart;
bool continuousTimeSync;
QSerialPort *serial;
QTcpSocket *tcpClient;
int framesRapid;
STATE rx_state;
uint32_t rx_step;
+449 -380
View File
@@ -326,16 +326,348 @@ void DBCFile::findAttributesByType(DBC_ATTRIBUTE_TYPE typ, QList<DBC_ATTRIBUTE>
}
}
DBC_MESSAGE* DBCFile::parseMessageLine(QString line)
{
QRegularExpression regex;
QRegularExpressionMatch match;
DBC_MESSAGE *msgPtr;
qDebug() << "Found a BO line";
regex.setPattern("^BO\\_ (\\w+) (\\w+) *: (\\w+) (\\w+)");
match = regex.match(line);
//captured 1 = the ID in decimal
//captured 2 = The message name
//captured 3 = the message length
//captured 4 = the NODE responsible for this message
if (match.hasMatch())
{
DBC_MESSAGE msg;
msg.ID = match.captured(1).toULong() & 0x7FFFFFFFul; //the ID is always stored in decimal format
msg.name = match.captured(2);
msg.len = match.captured(3).toInt();
msg.sender = findNodeByName(match.captured(4));
messageHandler->addMessage(msg);
msgPtr = messageHandler->findMsgByID(msg.ID);
}
else msgPtr = nullptr;
return msgPtr;
}
DBC_SIGNAL* DBCFile::parseSignalLine(QString line, DBC_MESSAGE *msg)
{
QRegularExpression regex;
QRegularExpressionMatch match;
int offset = 0;
bool isMultiplexor = false;
//bool isMultiplexed = false;
DBC_SIGNAL sig;
sig.multiplexValue = 0;
sig.isMultiplexed = false;
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]+)\\] \\\"(.*)\\\" (.*)");
match = regex.match(line);
if (match.hasMatch())
{
qDebug() << "Multiplexor signal";
isMultiplexor = true;
sig.isMultiplexor = true;
}
else
{
regex.setPattern("^SG\\_ *(\\w+) +m(\\d+) *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)");
match = regex.match(line);
if (match.hasMatch())
{
qDebug() << "Multiplexed signal";
//isMultiplexed = true;
sig.isMultiplexed = true;
sig.multiplexValue = match.captured(2).toInt();
offset = 1;
}
else
{
qDebug() << "standard signal";
regex.setPattern("^SG\\_ *(\\w+) *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)");
match = regex.match(line);
sig.isMultiplexed = false;
sig.isMultiplexor = false;
}
}
//captured 1 is the signal name
//captured 2 would be multiplex value if this is a multiplex signal. Then offset the rest of these by 1
//captured 2 is the starting bit
//captured 3 is the length in bits
//captured 4 is the byte order / value type
//captured 5 specifies signed/unsigned for ints
//captured 6 is the scaling factor
//captured 7 is the offset
//captured 8 is the minimum value
//captured 9 is the maximum value
//captured 10 is the unit
//captured 11 is the receiving node
if (match.hasMatch())
{
sig.name = match.captured(1);
sig.startBit = match.captured(2 + offset).toInt();
sig.signalSize = match.captured(3 + offset).toInt();
int val = match.captured(4 + offset).toInt();
if (val < 2)
{
if (match.captured(5 + offset) == "+") sig.valType = UNSIGNED_INT;
else sig.valType = SIGNED_INT;
}
switch (val)
{
case 0: //big endian mode
sig.intelByteOrder = false;
break;
case 1: //little endian mode
sig.intelByteOrder = true;
break;
case 2:
sig.valType = SP_FLOAT;
break;
case 3:
sig.valType = DP_FLOAT;
break;
case 4:
sig.valType = STRING;
break;
}
sig.factor = match.captured(6 + offset).toDouble();
sig.bias = match.captured(7 + offset).toDouble();
sig.min = match.captured(8 + offset).toDouble();
sig.max = match.captured(9 + offset).toDouble();
sig.unitName = match.captured(10 + offset);
if (match.captured(11 + offset).contains(','))
{
QString tmp = match.captured(11 + offset).split(',')[0];
sig.receiver = findNodeByName(tmp);
}
else sig.receiver = findNodeByName(match.captured(11 + offset));
sig.parentMessage = msg;
msg->sigHandler->addSignal(sig);
if (isMultiplexor) msg->multiplexorSignal = msg->sigHandler->findSignalByName(sig.name);
return msg->sigHandler->findSignalByName(sig.name);
}
return nullptr;
}
bool DBCFile::parseValueLine(QString line)
{
QRegularExpression regex;
QRegularExpressionMatch match;
qDebug() << "Found a value definition line";
regex.setPattern("^VAL\\_ (\\w+) (\\w+) (.*);");
match = regex.match(line);
//captured 1 is the ID to match against
//captured 2 is the signal name to match against
//captured 3 is a series of values in the form (number "text") that is, all sep'd by spaces
if (match.hasMatch())
{
//qDebug() << "Data was: " << match.captured(3);
DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toInt());
if (msg != NULL)
{
DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(match.captured(2));
if (sig != NULL)
{
QString tokenString = match.captured(3);
DBC_VAL_ENUM_ENTRY val;
while (tokenString.length() > 2)
{
regex.setPattern("(\\d+) \\\"(.*?)\\\"(.*)");
match = regex.match(tokenString);
if (match.hasMatch())
{
val.value = match.captured(1).toInt();
val.descript = match.captured(2);
//qDebug() << "sig val " << val.value << " desc " <<val.descript;
sig->valList.append(val);
int rightSize = tokenString.length() - match.captured(1).length() - match.captured(2).length() - 4;
if (rightSize > 0) tokenString = tokenString.right(rightSize);
else tokenString = "";
//qDebug() << "New token string: " << tokenString;
}
else tokenString = "";
}
return true;
}
}
}
return false;
}
bool DBCFile::parseAttributeLine(QString line)
{
QRegularExpression regex;
QRegularExpressionMatch match;
regex.setPattern("^BA\\_ \\\"*(\\w+)\\\"* BO\\_ (\\d+) \\\"*([#\\w]+)\\\"*");
match = regex.match(line);
//captured 1 is the attribute name
//captured 2 is the message ID number (frame ID)
//captured 3 is the attribute value
if (match.hasMatch())
{
qDebug() << "Found an attribute setting line for a message";
DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1));
if (foundAttr)
{
qDebug() << "That attribute does exist";
DBC_MESSAGE *foundMsg = messageHandler->findMsgByID(match.captured(2).toInt());
if (foundMsg)
{
qDebug() << "It references a valid, registered message";
DBC_ATTRIBUTE_VALUE *foundAttrVal = foundMsg->findAttrValByName(match.captured(1));
if (foundAttrVal) {
foundAttrVal->value = processAttributeVal(match.captured(3), foundAttr->valType);
}
else
{
DBC_ATTRIBUTE_VALUE val;
val.attrName = match.captured(1);
val.value = processAttributeVal(match.captured(3), foundAttr->valType);
foundMsg->attributes.append(val);
}
}
}
}
regex.setPattern("^BA\\_ \\\"*(\\w+)\\\"* SG\\_ (\\d+) \\\"*(\\w+)\\\"* \\\"*([#\\w]+)\\\"*");
match = regex.match(line);
//captured 1 is the attribute name
//captured 2 is the message ID number (frame ID)
//captured 3 is the signal name to bind to
//captured 4 is the attribute value
if (match.hasMatch())
{
qDebug() << "Found an attribute setting line for a signal";
DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1));
if (foundAttr)
{
qDebug() << "That attribute does exist";
DBC_MESSAGE *foundMsg = messageHandler->findMsgByID(match.captured(2).toInt());
if (foundMsg)
{
qDebug() << "It references a valid, registered message";
DBC_SIGNAL *foundSig = foundMsg->sigHandler->findSignalByName(match.captured(3));
if (foundSig)
{
DBC_ATTRIBUTE_VALUE *foundAttrVal = foundSig->findAttrValByName(match.captured(1));
if (foundAttrVal) foundAttrVal->value = processAttributeVal(match.captured(3), foundAttr->valType);
else
{
DBC_ATTRIBUTE_VALUE val;
val.attrName = match.captured(1);
val.value = processAttributeVal(match.captured(3), foundAttr->valType);
foundSig->attributes.append(val);
}
}
}
}
}
regex.setPattern("^BA\\_ \\\"*(\\w+)\\\"* BU\\_ \\\"*(\\w+)\\\"* \\\"*([#\\w]+)\\\"*");
match = regex.match(line);
//captured 1 is the attribute name
//captured 2 is the name of the node
//captured 3 is the attribute value
if (match.hasMatch())
{
qDebug() << "Found an attribute setting line for a node";
DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1));
if (foundAttr)
{
qDebug() << "That attribute does exist";
DBC_NODE *foundNode = findNodeByName(match.captured(2));
if (foundNode)
{
qDebug() << "References a valid node name";
DBC_ATTRIBUTE_VALUE *foundAttrVal = foundNode->findAttrValByName(match.captured(1));
if (foundAttrVal) foundAttrVal->value = processAttributeVal(match.captured(3), foundAttr->valType);
else
{
DBC_ATTRIBUTE_VALUE val;
val.attrName = match.captured(1);
val.value = processAttributeVal(match.captured(3), foundAttr->valType);
foundNode->attributes.append(val);
}
}
return true;
}
}
return false;
}
bool DBCFile::parseDefaultAttrLine(QString line)
{
QRegularExpression regex;
QRegularExpressionMatch match;
regex.setPattern("^BA\\_DEF\\_DEF\\_ \\\"*(\\w+)\\\"* \\\"*([#\\w]*)\\\"*");
match = regex.match(line);
//captured 1 is the name of the attribute
//captured 2 is the default value for that attribute
if (match.hasMatch())
{
qDebug() << "Found an attribute default value line, searching for an attribute named " << match.captured(1) << "with data " << match.captured(2);
DBC_ATTRIBUTE *found = findAttributeByName(match.captured(1));
if (found)
{
switch (found->valType)
{
case QSTRING:
found->defaultValue = match.captured(2);
break;
case QFLOAT:
found->defaultValue = match.captured(2).toFloat();
break;
case QINT:
found->defaultValue = match.captured(2).toInt();
break;
case ENUM:
QString temp = match.captured(2);
found->defaultValue = 0;
for (int x = 0; x < found->enumVals.count(); x++)
{
if (!found->enumVals[x].compare(temp, Qt::CaseInsensitive))
{
found->defaultValue = x;
break;
}
}
}
qDebug() << "Matched an attribute. Setting default value to " << found->defaultValue;
return true;
}
}
return false;
}
void DBCFile::loadFile(QString fileName)
{
QFile *inFile = new QFile(fileName);
QString line;
QString line, rawLine;
QRegularExpression regex;
QRegularExpressionMatch match;
DBC_MESSAGE *currentMessage = NULL;
DBC_ATTRIBUTE attr;
int numSigFaults = 0, numMsgFaults = 0;
bool inMultilineBU = false;
qDebug() << "DBC File: " << fileName;
if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text))
@@ -355,420 +687,159 @@ void DBCFile::loadFile(QString fileName)
dbc_nodes.append(falseNode);
while (!inFile->atEnd()) {
line = QString(inFile->readLine().simplified());
if (line.startsWith("BO_ ")) //defines a message
rawLine = QString(inFile->readLine());
line = rawLine.simplified();
if (inMultilineBU)
{
qDebug() << "Found a BO line";
regex.setPattern("^BO\\_ (\\w+) (\\w+) *: (\\w+) (\\w+)");
match = regex.match(line);
//captured 1 = the ID in decimal
//captured 2 = The message name
//captured 3 = the message length
//captured 4 = the NODE responsible for this message
if (match.hasMatch())
if (rawLine.startsWith("\t") || rawLine.startsWith(" "))
{
DBC_MESSAGE msg;
msg.ID = match.captured(1).toULong() & 0x7FFFFFFFul; //the ID is always stored in decimal format
msg.name = match.captured(2);
msg.len = match.captured(3).toInt();
msg.sender = findNodeByName(match.captured(4));
messageHandler->addMessage(msg);
currentMessage = messageHandler->findMsgByID(msg.ID);
DBC_NODE node;
node.name = line;
dbc_nodes.append(node);
}
else numMsgFaults++;
else inMultilineBU = false;
}
if (line.startsWith("SG_ ")) //defines a signal
if (!inMultilineBU)
{
int offset = 0;
bool isMultiplexor = false;
//bool isMultiplexed = false;
DBC_SIGNAL sig;
sig.multiplexValue = 0;
sig.isMultiplexed = false;
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]+)\\] \\\"(.*)\\\" (.*)");
match = regex.match(line);
if (match.hasMatch())
if (line.startsWith("BO_ ")) //defines a message
{
qDebug() << "Multiplexor signal";
isMultiplexor = true;
sig.isMultiplexor = true;
currentMessage = parseMessageLine(line);
if (currentMessage == nullptr) numMsgFaults++;
}
else
if (line.startsWith("SG_ ")) //defines a signal
{
regex.setPattern("^SG\\_ *(\\w+) +m(\\d+) *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)");
if (!parseSignalLine(line, currentMessage)) numSigFaults++;
}
if (line.startsWith("BU_:")) //line specifies the nodes on this canbus
{
qDebug() << "Found a BU line";
regex.setPattern("^BU\\_\\:(.*)");
match = regex.match(line);
//captured 1 = a list of node names separated by spaces. No idea how many yet
if (match.hasMatch())
{
qDebug() << "Multiplexed signal";
//isMultiplexed = true;
sig.isMultiplexed = true;
sig.multiplexValue = match.captured(2).toInt();
offset = 1;
}
else
{
qDebug() << "standard signal";
regex.setPattern("^SG\\_ *(\\w+) *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)");
match = regex.match(line);
sig.isMultiplexed = false;
sig.isMultiplexor = false;
}
}
//captured 1 is the signal name
//captured 2 would be multiplex value if this is a multiplex signal. Then offset the rest of these by 1
//captured 2 is the starting bit
//captured 3 is the length in bits
//captured 4 is the byte order / value type
//captured 5 specifies signed/unsigned for ints
//captured 6 is the scaling factor
//captured 7 is the offset
//captured 8 is the minimum value
//captured 9 is the maximum value
//captured 10 is the unit
//captured 11 is the receiving node
if (match.hasMatch())
{
sig.name = match.captured(1);
sig.startBit = match.captured(2 + offset).toInt();
sig.signalSize = match.captured(3 + offset).toInt();
int val = match.captured(4 + offset).toInt();
if (val < 2)
{
if (match.captured(5 + offset) == "+") sig.valType = UNSIGNED_INT;
else sig.valType = SIGNED_INT;
}
switch (val)
{
case 0: //big endian mode
sig.intelByteOrder = false;
break;
case 1: //little endian mode
sig.intelByteOrder = true;
break;
case 2:
sig.valType = SP_FLOAT;
break;
case 3:
sig.valType = DP_FLOAT;
break;
case 4:
sig.valType = STRING;
break;
}
sig.factor = match.captured(6 + offset).toDouble();
sig.bias = match.captured(7 + offset).toDouble();
sig.min = match.captured(8 + offset).toDouble();
sig.max = match.captured(9 + offset).toDouble();
sig.unitName = match.captured(10 + offset);
if (match.captured(11 + offset).contains(','))
{
QString tmp = match.captured(11 + offset).split(',')[0];
sig.receiver = findNodeByName(tmp);
}
else sig.receiver = findNodeByName(match.captured(11 + offset));
sig.parentMessage = currentMessage;
currentMessage->sigHandler->addSignal(sig);
if (isMultiplexor) currentMessage->multiplexorSignal = currentMessage->sigHandler->findSignalByName(sig.name);
}
else numSigFaults++;
}
if (line.startsWith("BU_:")) //line specifies the nodes on this canbus
{
qDebug() << "Found a BU line";
regex.setPattern("^BU\\_\\:(.*)");
match = regex.match(line);
//captured 1 = a list of node names separated by spaces. No idea how many yet
if (match.hasMatch())
{
QStringList nodeStrings = match.captured(1).split(' ');
qDebug() << "Found " << nodeStrings.count() << " node names";
for (int i = 0; i < nodeStrings.count(); i++)
{
//qDebug() << nodeStrings[i];
if (nodeStrings[i].length() > 1)
QStringList nodeStrings = match.captured(1).split(' ');
qDebug() << "Found " << nodeStrings.count() << " node names";
for (int i = 0; i < nodeStrings.count(); i++)
{
DBC_NODE node;
node.name = nodeStrings[i];
dbc_nodes.append(node);
}
}
}
}
if (line.startsWith("CM_ SG_ "))
{
qDebug() << "Found an SG comment line";
regex.setPattern("^CM\\_ SG\\_ *(\\w+) *(\\w+) *\\\"(.*)\\\";");
match = regex.match(line);
//captured 1 is the ID to match against to get to the message
//captured 2 is the signal name from that message
//captured 3 is the comment itself
if (match.hasMatch())
{
//qDebug() << "Comment was: " << match.captured(3);
DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toInt());
if (msg != NULL)
{
DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(match.captured(2));
if (sig != NULL)
{
sig->comment = match.captured(3);
}
}
}
}
if (line.startsWith("CM_ BO_ "))
{
qDebug() << "Found a BO comment line";
regex.setPattern("^CM\\_ BO\\_ *(\\w+) *\\\"(.*)\\\";");
match = regex.match(line);
//captured 1 is the ID to match against to get to the message
//captured 2 is the comment itself
if (match.hasMatch())
{
//qDebug() << "Comment was: " << match.captured(2);
DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toInt());
if (msg != NULL)
{
msg->comment = match.captured(2);
}
}
}
if (line.startsWith("CM_ BU_ "))
{
qDebug() << "Found a BU comment line";
regex.setPattern("^CM\\_ BU\\_ *(\\w+) *\\\"(.*)\\\";");
match = regex.match(line);
//captured 1 is the Node name
//captured 2 is the comment itself
if (match.hasMatch())
{
//qDebug() << "Comment was: " << match.captured(2);
DBC_NODE *node = findNodeByName(match.captured(1));
if (node != NULL)
{
node->comment = match.captured(2);
}
}
}
//VAL_ (1090) (VCUPresentParkLightOC) (1 "Error present" 0 "Error not present") ;
if (line.startsWith("VAL_ "))
{
qDebug() << "Found a value definition line";
regex.setPattern("^VAL\\_ (\\w+) (\\w+) (.*);");
match = regex.match(line);
//captured 1 is the ID to match against
//captured 2 is the signal name to match against
//captured 3 is a series of values in the form (number "text") that is, all sep'd by spaces
if (match.hasMatch())
{
//qDebug() << "Data was: " << match.captured(3);
DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toInt());
if (msg != NULL)
{
DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(match.captured(2));
if (sig != NULL)
{
QString tokenString = match.captured(3);
DBC_VAL_ENUM_ENTRY val;
while (tokenString.length() > 2)
//qDebug() << nodeStrings[i];
if (nodeStrings[i].length() > 1)
{
regex.setPattern("(\\d+) \\\"(.*?)\\\"(.*)");
match = regex.match(tokenString);
if (match.hasMatch())
{
val.value = match.captured(1).toInt();
val.descript = match.captured(2);
//qDebug() << "sig val " << val.value << " desc " <<val.descript;
sig->valList.append(val);
int rightSize = tokenString.length() - match.captured(1).length() - match.captured(2).length() - 4;
if (rightSize > 0) tokenString = tokenString.right(rightSize);
else tokenString = "";
//qDebug() << "New token string: " << tokenString;
}
else tokenString = "";
DBC_NODE node;
node.name = nodeStrings[i];
dbc_nodes.append(node);
}
}
inMultilineBU = true; //we might be... Need to check next line.
}
}
}
if (line.startsWith("BA_DEF_ SG_ "))
{
qDebug() << "Found a SG attribute line";
if (parseAttribute(line.right(line.length() - 12), attr))
if (line.startsWith("CM_ SG_ "))
{
qDebug() << "Success";
attr.attrType = SIG;
dbc_attributes.append(attr);
}
}
if (line.startsWith("BA_DEF_ BO_ ")) //definition of a message attribute
{
qDebug() << "Found a BO attribute line";
if (parseAttribute(line.right(line.length() - 12), attr))
{
qDebug() << "Success";
attr.attrType = MESSAGE;
dbc_attributes.append(attr);
}
}
if (line.startsWith("BA_DEF_ BU_ ")) //definition of a node attribute
{
qDebug() << "Found a BU attribute line";
if (parseAttribute(line.right(line.length() - 12), attr))
{
qDebug() << "Success";
attr.attrType = NODE;
dbc_attributes.append(attr);
}
}
if (line.startsWith("BA_DEF_DEF_ ")) //definition of default value for an attribute
{
regex.setPattern("^BA\\_DEF\\_DEF\\_ \\\"*(\\w+)\\\"* \\\"*([#\\w]*)\\\"*");
match = regex.match(line);
//captured 1 is the name of the attribute
//captured 2 is the default value for that attribute
if (match.hasMatch())
{
qDebug() << "Found an attribute default value line, searching for an attribute named " << match.captured(1) << "with data " << match.captured(2);
DBC_ATTRIBUTE *found = findAttributeByName(match.captured(1));
if (found)
qDebug() << "Found an SG comment line";
regex.setPattern("^CM\\_ SG\\_ *(\\w+) *(\\w+) *\\\"(.*)\\\";");
match = regex.match(line);
//captured 1 is the ID to match against to get to the message
//captured 2 is the signal name from that message
//captured 3 is the comment itself
if (match.hasMatch())
{
switch (found->valType)
//qDebug() << "Comment was: " << match.captured(3);
DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toInt());
if (msg != NULL)
{
case QSTRING:
found->defaultValue = match.captured(2);
break;
case QFLOAT:
found->defaultValue = match.captured(2).toFloat();
break;
case QINT:
found->defaultValue = match.captured(2).toInt();
break;
case ENUM:
QString temp = match.captured(2);
found->defaultValue = 0;
for (int x = 0; x < found->enumVals.count(); x++)
DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(match.captured(2));
if (sig != NULL)
{
if (!found->enumVals[x].compare(temp, Qt::CaseInsensitive))
{
found->defaultValue = x;
break;
}
}
}
qDebug() << "Matched an attribute. Setting default value to " << found->defaultValue;
}
}
}
//BA_ "GenMsgCycleTime" BO_ 101 100;
if (line.startsWith("BA_ ")) //set value of attribute
{
regex.setPattern("^BA\\_ \\\"*(\\w+)\\\"* BO\\_ (\\d+) \\\"*([#\\w]+)\\\"*");
match = regex.match(line);
//captured 1 is the attribute name
//captured 2 is the message ID number (frame ID)
//captured 3 is the attribute value
if (match.hasMatch())
{
qDebug() << "Found an attribute setting line for a message";
DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1));
if (foundAttr)
{
qDebug() << "That attribute does exist";
DBC_MESSAGE *foundMsg = messageHandler->findMsgByID(match.captured(2).toInt());
if (foundMsg)
{
qDebug() << "It references a valid, registered message";
DBC_ATTRIBUTE_VALUE *foundAttrVal = foundMsg->findAttrValByName(match.captured(1));
if (foundAttrVal) {
foundAttrVal->value = processAttributeVal(match.captured(3), foundAttr->valType);
}
else
{
DBC_ATTRIBUTE_VALUE val;
val.attrName = match.captured(1);
val.value = processAttributeVal(match.captured(3), foundAttr->valType);
foundMsg->attributes.append(val);
sig->comment = match.captured(3);
}
}
}
}
regex.setPattern("^BA\\_ \\\"*(\\w+)\\\"* SG\\_ (\\d+) \\\"*(\\w+)\\\"* \\\"*([#\\w]+)\\\"*");
match = regex.match(line);
//captured 1 is the attribute name
//captured 2 is the message ID number (frame ID)
//captured 3 is the signal name to bind to
//captured 4 is the attribute value
if (match.hasMatch())
if (line.startsWith("CM_ BO_ "))
{
qDebug() << "Found an attribute setting line for a signal";
DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1));
if (foundAttr)
qDebug() << "Found a BO comment line";
regex.setPattern("^CM\\_ BO\\_ *(\\w+) *\\\"(.*)\\\";");
match = regex.match(line);
//captured 1 is the ID to match against to get to the message
//captured 2 is the comment itself
if (match.hasMatch())
{
qDebug() << "That attribute does exist";
DBC_MESSAGE *foundMsg = messageHandler->findMsgByID(match.captured(2).toInt());
if (foundMsg)
//qDebug() << "Comment was: " << match.captured(2);
DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toInt());
if (msg != NULL)
{
qDebug() << "It references a valid, registered message";
DBC_SIGNAL *foundSig = foundMsg->sigHandler->findSignalByName(match.captured(3));
if (foundSig)
{
DBC_ATTRIBUTE_VALUE *foundAttrVal = foundSig->findAttrValByName(match.captured(1));
if (foundAttrVal) foundAttrVal->value = processAttributeVal(match.captured(3), foundAttr->valType);
else
{
DBC_ATTRIBUTE_VALUE val;
val.attrName = match.captured(1);
val.value = processAttributeVal(match.captured(3), foundAttr->valType);
foundSig->attributes.append(val);
}
}
msg->comment = match.captured(2);
}
}
}
regex.setPattern("^BA\\_ \\\"*(\\w+)\\\"* BU\\_ \\\"*(\\w+)\\\"* \\\"*([#\\w]+)\\\"*");
match = regex.match(line);
//captured 1 is the attribute name
//captured 2 is the name of the node
//captured 3 is the attribute value
if (match.hasMatch())
if (line.startsWith("CM_ BU_ "))
{
qDebug() << "Found an attribute setting line for a node";
DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1));
if (foundAttr)
qDebug() << "Found a BU comment line";
regex.setPattern("^CM\\_ BU\\_ *(\\w+) *\\\"(.*)\\\";");
match = regex.match(line);
//captured 1 is the Node name
//captured 2 is the comment itself
if (match.hasMatch())
{
qDebug() << "That attribute does exist";
DBC_NODE *foundNode = findNodeByName(match.captured(2));
if (foundNode)
//qDebug() << "Comment was: " << match.captured(2);
DBC_NODE *node = findNodeByName(match.captured(1));
if (node != NULL)
{
qDebug() << "References a valid node name";
DBC_ATTRIBUTE_VALUE *foundAttrVal = foundNode->findAttrValByName(match.captured(1));
if (foundAttrVal) foundAttrVal->value = processAttributeVal(match.captured(3), foundAttr->valType);
else
{
DBC_ATTRIBUTE_VALUE val;
val.attrName = match.captured(1);
val.value = processAttributeVal(match.captured(3), foundAttr->valType);
foundNode->attributes.append(val);
}
node->comment = match.captured(2);
}
}
}
//VAL_ (1090) (VCUPresentParkLightOC) (1 "Error present" 0 "Error not present") ;
if (line.startsWith("VAL_ "))
{
parseValueLine(line);
}
if (line.startsWith("BA_DEF_ SG_ "))
{
qDebug() << "Found a SG attribute line";
if (parseAttribute(line.right(line.length() - 12), attr))
{
qDebug() << "Success";
attr.attrType = SIG;
dbc_attributes.append(attr);
}
}
if (line.startsWith("BA_DEF_ BO_ ")) //definition of a message attribute
{
qDebug() << "Found a BO attribute line";
if (parseAttribute(line.right(line.length() - 12), attr))
{
qDebug() << "Success";
attr.attrType = MESSAGE;
dbc_attributes.append(attr);
}
}
if (line.startsWith("BA_DEF_ BU_ ")) //definition of a node attribute
{
qDebug() << "Found a BU attribute line";
if (parseAttribute(line.right(line.length() - 12), attr))
{
qDebug() << "Success";
attr.attrType = NODE;
dbc_attributes.append(attr);
}
}
if (line.startsWith("BA_DEF_DEF_ ")) //definition of default value for an attribute
{
parseDefaultAttrLine(line);
}
//BA_ "GenMsgCycleTime" BO_ 101 100;
if (line.startsWith("BA_ ")) //set value of attribute
{
parseAttributeLine(line);
}
}
}
@@ -818,8 +889,6 @@ void DBCFile::loadFile(QString fileName)
DBC_ATTRIBUTE_VALUE *thisBG;
DBC_ATTRIBUTE_VALUE *thisFG;
for (int x = 0; x < messageHandler->getCount(); x++)
{
DBC_MESSAGE *msg = messageHandler->findMsgByIdx(x);
+5
View File
@@ -79,6 +79,11 @@ private:
bool parseAttribute(QString inpString, DBC_ATTRIBUTE &attr);
QVariant processAttributeVal(QString input, DBC_ATTRIBUTE_VAL_TYPE typ);
DBC_SIGNAL* parseSignalLine(QString line, DBC_MESSAGE *msg);
DBC_MESSAGE* parseMessageLine(QString line);
bool parseValueLine(QString line);
bool parseAttributeLine(QString line);
bool parseDefaultAttrLine(QString line);
};
class DBCHandler: public QObject
+22
View File
@@ -1,6 +1,7 @@
#include "dbcloadsavewindow.h"
#include "ui_dbcloadsavewindow.h"
#include <QCheckBox>
#include "helpwindow.h"
DBCLoadSaveWindow::DBCLoadSaveWindow(const QVector<CANFrame> *frames, QWidget *parent) :
QDialog(parent),
@@ -32,13 +33,34 @@ DBCLoadSaveWindow::DBCLoadSaveWindow(const QVector<CANFrame> *frames, QWidget *p
connect(ui->tableFiles, &QTableWidget::cellDoubleClicked, this, &DBCLoadSaveWindow::cellDoubleClicked);
editorWindow = new DBCMainEditor(frames);
installEventFilter(this);
}
DBCLoadSaveWindow::~DBCLoadSaveWindow()
{
removeEventFilter(this);
delete ui;
}
bool DBCLoadSaveWindow::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("dbc_manager.html");
break;
}
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
return false;
}
void DBCLoadSaveWindow::newFile()
{
int idx = dbcHandler->createBlankFile();
+1
View File
@@ -38,6 +38,7 @@ private:
void swapTableRows(bool up);
QList<QTableWidgetItem*> takeRow(int row);
void setRow(int row, const QList<QTableWidgetItem*>& rowItems);
bool eventFilter(QObject *obj, QEvent *event);
};
#endif // DBCLOADSAVEWINDOW_H
+23
View File
@@ -5,6 +5,7 @@
#include <QMessageBox>
#include <QSettings>
#include <QColorDialog>
#include "helpwindow.h"
DBCMainEditor::DBCMainEditor( const QVector<CANFrame> *frames, QWidget *parent) :
QDialog(parent),
@@ -49,6 +50,8 @@ DBCMainEditor::DBCMainEditor( const QVector<CANFrame> *frames, QWidget *parent)
ui->MessagesTable->setContextMenuPolicy(Qt::CustomContextMenu);
sigEditor = new DBCSignalEditor();
installEventFilter(this);
}
void DBCMainEditor::showEvent(QShowEvent* event)
@@ -66,10 +69,29 @@ void DBCMainEditor::showEvent(QShowEvent* event)
DBCMainEditor::~DBCMainEditor()
{
removeEventFilter(this);
delete ui;
delete sigEditor;
}
bool DBCMainEditor::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("dbc_editor.html");
break;
}
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
return false;
}
void DBCMainEditor::setFileIdx(int idx)
{
if (idx < 0 || idx > dbcHandler->getFileCount() - 1) return;
@@ -374,6 +396,7 @@ void DBCMainEditor::onCellClickedMessage(int row, int col)
DBC_MESSAGE *message = dbcFile->messageHandler->findMsgByID(Utility::ParseStringToNum(idString));
sigEditor->setMessageRef(message);
sigEditor->setFileIdx(fileIdx);
sigEditor->setWindowModality(Qt::WindowModal);
sigEditor->exec(); //blocks this window from being active until we're done
//now update the displayed # of signals
inhibitCellChanged = true;
+1
View File
@@ -44,6 +44,7 @@ private:
void refreshMessagesTable(const DBC_NODE *node);
void showEvent(QShowEvent* event);
void closeEvent(QCloseEvent *event);
bool eventFilter(QObject *obj, QEvent *event);
void readSettings();
void writeSettings();
void insertBlankRow();
+22
View File
@@ -4,6 +4,7 @@
#include <QDebug>
#include <QMenu>
#include <QSettings>
#include "helpwindow.h"
DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
QDialog(parent),
@@ -205,10 +206,13 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = NULL;
}
});
installEventFilter(this);
}
DBCSignalEditor::~DBCSignalEditor()
{
removeEventFilter(this);
delete ui;
}
@@ -218,6 +222,24 @@ void DBCSignalEditor::closeEvent(QCloseEvent *event)
writeSettings();
}
bool DBCSignalEditor::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("signaleditor.html");
break;
}
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
return false;
}
void DBCSignalEditor::setFileIdx(int idx)
{
if (idx < 0 || idx > dbcHandler->getFileCount() - 1) return;
+1
View File
@@ -44,6 +44,7 @@ private:
void generateUsedBits();
void closeEvent(QCloseEvent *event);
bool eventFilter(QObject *obj, QEvent *event);
void readSettings();
void writeSettings();
};
Executable
+4
View File
@@ -0,0 +1,4 @@
make qthelp
~/Qt/5.9.1/gcc_64/bin/qcollectiongenerator build/qthelp/SavvyCAN.qhcp
~/Qt/5.9.1/gcc_64/bin/assistant -collectionFile build/qthelp/SavvyCAN.qhc
+24
View File
@@ -0,0 +1,24 @@
Bisector Window
=================
.
.. image:: ./images/Bisector.png
Using the Bisector Window
==========================
The purpose of this window is obviously to split the list of frames into two pieces. You have many options for how to do this. You can split by:
ID Range - Grab a set of frames between two values. Note that you cannot pick and choose frame IDs here but rather just use a list. If you want to pick and choose then use the filter interface on the main form and then save the filtered list from the File menu.
Frame Number - You can split right at a given frame number. All frames up to that number will be on one side with all the rest on the other.
Percentage - Pretty much just like the Frame Number option but in percentage instead in case that is more convenient.
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...
+2 -2
View File
@@ -55,9 +55,9 @@ author = u'Collin Kidder'
# built documents.
#
# The short X.Y version.
version = '165'
version = '181'
# The full version, including alpha/beta/rc tags.
release = '165'
release = '181'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
+36
View File
@@ -0,0 +1,36 @@
Connection Window
==============================
.
.. image:: ./images/ConnectionWindow.png
The connection window is used to add, remove, and modify connections. At the moment it is possible to use any SocketCAN compatible device (in LINUX) and any GVRET compatible device in any of the supported operating systems.
At this time GVRET compatible devices are: EVTVDue, EVTV CANDue (1.3/2/2.1/2.2), Teensy 3.1-3.6, Macchina M2.
Connecting To A Dongle
==============================
SavvyCAN is able to connect to GVRET compatible devices to capture new traffic. These devices will present as serial ports on the connected PC.
To connect to a dongle select the proper serial port and click "Create New Connection". If a valid device is found on that serial port the first
statusbar section will update and the currently set canbus speeds will show in the table at the left of the window. These speeds can then
be changed by clicking on the speed (or otherwise selecting the cell in the table) and typing in a new value. Leaving the cell will update the speed to the new value. GVRET devices also support
setting "listen only" on each bus. This mode causes the device to not acknowledge any traffic or try to modify the bus at all. It is as it says, a mode where you can only listen to whatever traffic
is found on the bus. Some older GVRET devices supported a mode where you could change the second bus between single wire CAN and normal CAN. This is deprecated. However, newer GVRET devices have
dedicated single wire CAN buses and the relevant bus will show the checkbox.
SavvyCAN can also connect to SocketCAN devices in LINUX. Select "SocketCAN" as the connection type and then type in the device name. This should be just the same as you would provide to the can-utils
programs. That is, do not enter /dev/can0 but rather can0. Then push "Create New Connection" and you should see the new connection in the table on the left of the window. Note that SocketCAN devices don't
support changing the baud rate within a program. You must do this when you set up the connection via console commands. This is outside the scope of this documentation. Consult the SocketCAN documentation
for details on configuring such devices.
Debugging Connection Problems
==============================
GVRET devices present as serial ports and have significant configuration options. However, the ability to configure so many things and the ability to compile the firmware yourself both come as a
double edged sword. They present many opportunities for things to go wrong. Because of this there is a debugging console present on the connection window. Click a bus in the table then click "Enable Console"
to cause it to start logging serial traffic. From this console you can see what is going on. It shows what SavvyCAN is sending and what it is getting back. It has extended status messages that might help to narrow down
what is going wrong. Additionally, if you're feeling adventurous you can send traffic to the serial device from the Send line. "Send Hex" accepts a set of hex values separated by spaces. "Send Text" will send the raw
text you type on the line. GVRET traffic is ordinarily binary so "Send Text" won't work very well for that. But, there is also a text console possible on GVRET devices. If you connect to them with a serial program you can
configure things via a text console. Type ? and follow it up with some form of line ending (Cr, Lf, CrLf, any will work).
+4 -4
View File
@@ -1,13 +1,13 @@
Custom Sender Window
====================
=====================
**SavvyCAN Custom Frame Sender**
.
.. image:: ./images/CustomSender.png
General Overview
=================
====================
This window allows you to create custom frames that will be sent out on one of the can buses. The uses are endless.
It can be used to generate valid traffic to control connected hardware. It can be used to test out various ideas
@@ -16,7 +16,7 @@ modifications.
Layout of the View
===================
=====================
This screen is laid out as a data grid.
+19
View File
@@ -0,0 +1,19 @@
DBC Message Editor
===================
.
.. image:: ./images/DBCEditor.png
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.
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 correspondance 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.
+17
View File
@@ -0,0 +1,17 @@
DBC File Manager
=================
.
.. image:: ./images/DBCManager.png
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 signalling. 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.
DBC File Ordering
===================
The "Move Up" and "Move Down" buttons can be used to change the order of DBC files. Why would you care? DBC files are accessed in the order they are in the list. When a frame is interpreted the system goes through the DBC files in order. It selects the first DBC file that is associated to the bus the message came in on and that imprements the correct message ID. So, if you have multiple DBC files it is possible that the order might matter.
+28 -2
View File
@@ -1,6 +1,32 @@
File Comparison Window
======================
========================
How to compare one file against one or more other files.
.
.. image:: ./images/FileComparator.png
:align: left
The Purpose of the File Comparator
==================================
This screen can be used to figure out what is different between a set of files. On one side you have a single file. This is called the "File of interest". On the other side you have any list of files. They're not listed any longer as actual files. The program can load frames from any number of files and dump them all into the same "bucket" of frames. You can thus load up a batch of files and compare them against the one "File of interest." The purpose of this is to figure out what is different. Are there IDs found only on one side? For IDs found on both sides are there bits set only on one side and not the other? This can be used to find stubborn data that you are having trouble locating. One use is to capture a large amount of traffic to use as "background noise" of sorts. Perhaps drive around for a long time or let the vehicle idle for some time but never do the thing you need to find. Then do another capture and do the thing you're missing a few times. Perhaps you're looking for a gear shift signal. You could capture a large batch of frames while idling. Then, in a second capture shift several times. Now, compare the two. Somewhere in the differences should be the gear selection you couldn't find. The list ought to be much more narrow than just "shooting in the dark" so to speak.
The layout of the differences list
==================================
In the differences list you'll find three main tree nodes:
1. IDs found only in <file of interest>
* In sub nodes you'll find every ID found only in the file of interest and not in any of the reference files.
2. IDs found only in reference frames
* In theory this should be a fairly small list. Here are any IDs never seen in the file of interest
3. IDs found in both
* Here is where the interesting information lies. The sub nodes here are found in both places. A list of all differences will be shown sub nodes of each ID node. Here you can see bits set only in one side or the other. You can also find values only found on one side or the other. These might be candidates for your mystery signal.
-4
View File
@@ -1,4 +0,0 @@
Firmware Uploader Window
========================
Uploading new firmware to CANBus connected devices
+45 -2
View File
@@ -1,5 +1,48 @@
Flow View Window
================
==================
Go with the flow
.
.. image:: ./images/FlowView.png
The Purpose of Flow View
=========================
Flow view is meant to show a view of what "happens to" the data for a given ID as time goes on. The flow is a flow of time or messages going by. The goal is to visualize what is going on as time passes. The screen is very busy but everything has the same goal - to show the data bytes across the list of all frames with that ID.
Selecting an ID to Flow
=======================
Along the left most side of the window is a list of every frame ID captured so far. You can select any of these IDs. Once you select an ID you'll see the number of frames with that ID just to the right of the IDs list (in the bottom of the Playback Control box).
Viewing the Flow
=================
There are a variety of ways that the data bytes for the current frame are displayed. Toward the middle of the window there are 8 text boxes that show the current value of each data byte. At the right of the window are two visualizations. On the top there is an 8x8 grid that visualizes every possible bit in a CAN message. Bits which are white are currently 0 or off. Bits which are black are 1 or on. Bits that are green are newly on in this frame. They were 0/off in the reference. Bits which are red are newly off in this frame. Those bits were 1/on in the reference.
Below the 8x8 grid view is a graph based view. The "+" in the center marks the "current" time. You can thus see both into the future and the past (relative to the current frame) in this view. All 8 data bytes are graphed according to their value. You can thus see in the graph what the value is doing as time ticks by.
Between these three views you should have a very good idea of how the data bytes change over time.
Controlling the Flow
=====================
The screen has many ways to control the flow. The "Playback Control" box has 6 icons in a 3x2 grid. The upper icons mean (from left to right): Go back one frame, Stop, Go forward one frame. The bottom icons mean: Play backward, pause, Play forward. Playback speed is used to control how quickly the flow happens. You can raise and lower this value even as the flow is playing back and it will change the flow speed in realtime. You can select "Loop Playback" to keep looping over and over.
Additional control options
===========================
Below the "Playback Control" box is a set of three check boxes. "Live Mode" will cause the flow view to keep the current frame at the latest received frame from the capture hardware. This is thus a "real time" option where the flow will happen in real time as frames come in. "Synchronize windows" causes the flowing in this window to update the position in the main window so that the main window is always at the current position in flow playback. "Graph by timestamp" causes the "Time Axis" at the bottom of the graph view to be in seconds or microseconds. Deselecting this will cause that axis to instead be in frame number.
Reference Values
=================
Previously it was mentioned that bits in the 8x8 grid are colored based on whether they are
newly on/off or steady state compared to the reference. What is the reference? Well, look to the left of the 8x8 grid. The reference values here are used for reference with the 8x8 grid. You can set these values to something static and then see how bits compare to that static reference. Or, above the reference values is a checkbox called "Auto Reference" if this is clicked then the last frame is used as a reference for the current one. There are advantages to both options. A static reference can be used to see how things change compared to, say, the very first frame. Auto reference shows how the data changed from frame to frame. Either might yield interesting findings.
Seeking to Specific Values
===========================
Lastly, it is possible to seek to specific values. "Data Seek Values" normally default to -1. But, if a value other than -1 is found in one of these boxes then it will be used as a seek value. Any time the flow is playing back (either forward or backward) it will compare to the seek values. If there is a match then playback will automatically stop. This can be used to quickly play things back until you get to a specific value. One use would be to seek to a known value that precedes an important section you want to analyze in more depth.
+8 -2
View File
@@ -1,5 +1,11 @@
Frame Details Window
====================
======================
Get all the juicy details about your frames
.
.. image:: ./images/FrameInfoWindow.png
The Purpose of Frame Details Window
===================================
This window is used to get detailed statistics about frames. It provides information about a given frame ID across all frames with that ID. You can get such information as the number of frames, the number of data bytes that frame ID has, the average interval between frames with that ID, and the minimum and maximum interval. Also listed are detailed statistics for each data byte in that frame. Each byte has listed which bits changed, the range of values found, and a histogram both graphically (at the bottom of the window) and textually. The textual representation shows the number of times a specific value occurred. Also listed at the bottom is a histogram of each bit and how many times it was set. The 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. All information can be saved to a text file for later analysis.
+44
View File
@@ -0,0 +1,44 @@
Fuzzing Window
===============
.
.. image:: ./images/FuzzingWindow.png
The Purpose of Fuzzing
======================
Some people are big fans of fuzzing, some people have no use for it. There usually isn't much in between. So, what is fuzzing and why would you want to do it? Fuzzing is intentionally sending random information to see what happens. It's pretty much the "shotgun" solution - you fire birdshot into the air and see if it hits anything. Does that sound a bit dangerous? It kind of is. So, why do it? It might help you to find frame IDs that control things. It might help you to find a data byte that controls something. Used carefully it can be used to figure out how different values affect things.
Fuzzing is Dangerous
====================
You have now been warned. Sending random garbage over the CAN bus to see what happens could mess something up. It just might put your vehicle into gear and cause you to drive over a box full of kittens. Be careful! Never fuzz a car unless you're right there and you can stop it. Even better, don't do it unless your car is up on a hoist or something and can't drive over anyone. Even still, there is a small chance you could cause an adverse effect to your car.
Controlling the Fuzzy Beast
===========================
So, you want to give it a try? Let's do it! First of all, you can set the delay between frames and the burst rate. The burst rate can cause the program to send one than one frame each interval. This is useful as a CAN bus could potentially support 2000 to 8000 frames per second. Then you can set the number of bytes to send. Ordinarily this would be the full 8 but you can experiment with smaller frames. You can set to send on a specific bus. That's all the simple settings. It gets a bit more complicated now.
The "ID Scanning" box has two radio buttons:
1. Sequential will go from "Start ID" to "End ID" then reset back to Start over and over.
2. Random will pick IDs at random in the range between Start and End
But, perhaps you don't want to fuzz IDs like that? The next box is "ID Selection" and the choices are:
1. Range of IDs - this uses the aforementioned Start and End IDs
2. Filter list - This causes it to pick frame IDs from the list below either sequentially or randomly according to the radio boxes under "ID Scanning"
The last box is "Bit Scanning"
1. Sequential causes it to scan bits in logically sequential order. That is, the first available fuzzing bit is set then the just the second, then the first two, etc. This causes all of the fuzzed bits to sequentially set in order.
2. Sweep causes the system to set the first one, then unset that one and set the second bit, then unset that, etc. Thus the fuzzed bit sweeps and only one fuzzed bit is set at once.
3. Random will randomly pick whether each fuzzed bit is set or not.
In order to fuzz bits you need to set which bits to fuzz and which not to. As listed at the bottom of the window, there is a color code to the 8x8 grid. Clicking cells in the grid will toggle them between their various values. White bits are never set, black bits are always set no matter what, green bits follow the fuzzing pattern you specified in "Bit Scanning"
Pulling the Trigger
===================
Once you've configured everything click "Start Fuzzing" to give it a shot. You will see the number of frames sent so far listed below the button. You can stop the fuzzing by pushing the button again.
+44
View File
@@ -0,0 +1,44 @@
Graph Setup
============
.
.. image:: ./images/GraphSetup.png
Setting up a Graph
==================
This same screen is used whether you are creating a new graph or editing an existing graph. If you are creating a new graph then you have two options.
Graphing a DBC Signal
=====================
If you want to graph a signal from a DBC file that you have loaded then pick the Message from the combo box. Picking a message will then allow you to pick a signal within that message. After selecting both the message and the signal then click the "Copy Signal Parameters" button. This will fill out the left hand side with the proper values for you automatically.
Manual Signal Graphing (Or Editing)
===================================
If you want to manually create a signal or edit an existing signal then you will find the relevant fields on the left hand size of the window.
"Name" is for your reference and will be shown on the Graphing Window.
"ID" is the frame ID to use for this graph. You can enter in either decimal or hex. Hex values are preceded by 0x
"Data Len" is used to specify how many bits are in the signal to graph. You can graph odd bit lengths such as 9, 11, or 15 bits. These are not as uncommon as you might think.
"Little Endian" switches between little and big endian mode. This will change how the signal grabs bits from the frame.
Once you've set both Data Len and Little Endian you can set the bits to use. The 8x8 grid above is used for this. Click on the start bit and the rest of the bits will automatically be selected for you based on Data Len and Little Endian.
"Signed" can be used to select between signed and unsigned mode.
"Mask" will apply a mask to the value before using it.
"Bias" allows for setting an offset to the values to bring them to a different resting point
"Scale" is used as a multiplier to change the scaling of the values
"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.
"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.
+42 -1
View File
@@ -1,4 +1,45 @@
Graphing Window
===============
Graphing the data
.
.. image:: ./images/GraphingView.png
Creating a new Graph
=====================
Right click in the graph window to bring up a popup menu. The last entry in the first section is "Add new graph". This brings up the Graph Setup Window. From there you can create a new graph.
Selecting a Graph
=================
You can select a graph either by clicking on it or by clicking its name in the list of graphs up that the top right.
Editing a Graph
=================
You can either double click its name in the list of graphs at the top right or select the graph, right click, and select "Edit Selected Graph"
Deleting Graphs
================
There are two options. You can select a graph, right click, and select "Remove selected graph" or you can right click and select "Remove All Graphs"
Moving Around
==============
Left clicking and dragging in the graphing area will allow you to pan around. The mouse wheel (if you have one) will let you zoom in and out. If you select the numbers on either the X or Y axis then you'll be able to pan and zoom on just that axis leaving the other alone. This is useful in order to expand or shrink the time axis or to rescale the vertical axis to better fit the data in view. The "+" key will zoom in, the "-" key zooms out. You can also zoom in and out from the pop up window that appears when you right click. If you've messed up your view and can't figure out how to fix it then right click and select "Reset View" to get a nice view of all the data again.
Loading and Saving Graphs
=========================
It can be beneficial to create a set of graphs that can be used over and over. You can save the currently setup graphs to a file and then load it later. Right click on the graphing window and use "Save graph defintions to file" and "Load graph definitions from file" to do this. You can also save a picture of the graphing window. PDF, PNG, and JPG are supported. Lastly, you can save a spreadsheet of all the graphed points.
Real Time Graphing
===================
Sometimes it is useful to graph while data are still being captured. When this is done it is additionally handy if the view follows the new data. In the right click pop up menu you will find "Follow end of graph". It is a checkbox. When it is on the current view will follow the end of the data captured. The "window" you've set up will scroll. That is, the zoom and vertical calibration will stay the same and only the "Time Axis" will scroll such that the end of the capture is always at the right hand side of the graphing window.
Hidden Tricks
==============
If you double click on a graphed line the main window will scroll and the frame that produced the nearest data point to where you clicked will be selected. Additionally, if you selected "Synchronize Windows" in Flow view then the graphing view will synchronize to Flow view as well.
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

+18 -12
View File
@@ -1,27 +1,33 @@
Welcome to SavvyCAN's documentation!
====================================
Welcome to SavvyCAN V181 documentation!
=========================================
Contents:
.. toctree::
:maxdepth: 2
:maxdepth: 1
:titlesonly:
:numbered:
mainscreen
connectionwindow
preferences
flowview
dbc_manager
dbc_editor
signaleditor
graphwindow
graphsetup
flowview
bisector
sniffer
framedetails
filecomparison
fuzzingwindow
isotp_decoder
rangestate
uds_scanner
playbackwindow
customsender
firmwareuploader
scriptingwindow
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
+19
View File
@@ -0,0 +1,19 @@
ISO-TP Decoder
===============
.
.. image:: ./images/ISOTPDecoder.png
Using the ISO-TP Decoder
==========================
This window scans the existing captured frames and newly captured frames to see if it can find CAN traffic that seems to conform to the ISO-TP standard. ISO-TP is used to send multi-frame messages and as an encoding standard that forms the base for other protocols such as UDS and ODBII (which is essentially itself a subset of UDS).
The main list at the top shows any messages that seem to conform to ISO-TP. There will very likely be messages here which aren't really ISO-TP. You can deselect IDs that seem to generate false positives so that they quit showing up in this list. As you can see in the picture only the ids 0x7E0 through 0x7EA were selected. These IDs are standard for UDS communication. If you want to immediately recalculate the results to exclude the deselected IDs then push "Interpret Previously Captured Frames" to regenerate the whole list. Otherwise the effect of changing the ID selections will only happen for newly captured frames.
The "Show incomplete and/or corrupted messages" checkbox will cause a lot of false positives and should only be used as a last resort if you suspect that you might have some dropped traffic.
"Use extended addressing" will cause the decoder to assume that extended addressing is being used on this CAN bus. Extended addressing adds an additional byte of addressing that is found in the data bytes of the frame. This isn't that commonly used but is used on some vehicles and ISO-TP decoding won't work properly unless this setting is correct. If you find that decoding seems to have failed you might try toggling this setting to see if it helps. Remember to click "Interpret Previously Captured Frames" to recalculate things for previously captured traffic.
Once you have messages in the table at the top of the window you can click on a message to get more details about it in the text box in the lower left. In the picture you can see that 0x7F 0x10 0x12 was interpreted as a UDS error response saying that the ECU does not support the requested sub-function passed to the diagnostic session control service. This is much easier than trying to remember what all those bytes mean off the top of your head!
+19 -32
View File
@@ -1,13 +1,10 @@
Main / Start Up Screen
======================
**SavvyCAN main screen**
.. image:: ./images/MainScreen.png
This screen embodies the core of the program. Here you will find the master list of all frames. Also, here you can navigate to the other aspects
of the program. You can have multiple sub-windows open at once - in fact, quite often this is very beneficial. This screen is also used to
connect to a GVRET compatible device. But, one thing at a time.
of the program. You can have multiple sub-windows open at once - in fact, quite often this is very beneficial.
The Main Frame List
@@ -17,10 +14,11 @@ The main frame list takes up the majority of the main screen. This list consists
- 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.
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.
- Ext: 0 = Standard message (11 bit ID). 1 = Extended message (29 bit ID)
- Bus: SavvyCAN was meant for use with GVRET compatible capture devices. GVRET supports two buses labeled 0 and 1. The bus a frame came in on
- 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.
- Data: All of the data bytes separated by spaces. Can be in either hexadecimal or decimal (preference). If "Interpet Frames" is checked you will
@@ -31,28 +29,16 @@ The main frame list takes up the majority of the main screen. This list consists
The Bottom Statusbar
====================
At the very bottom of the main screen is a status bar with three sections.
At the very bottom of the main screen is a status bar with two sections.
* The first section shows the connection status. If you are connected to a GVRET compatible dongle you will see the
version of the connected device here.
* The first section shows the connection status. You will see the number of currently connected buses here.
* The second second section shows which file is currently loaded. This is updated by loading or saving.
* The third section shows whether a DBC file is currently loaded and which one. DBC files are used to interpret messages.
Connecting To A Dongle
======================
SavvyCAN is able to connect to GVRET compatible devices to capture new traffic. These devices will present as serial ports on the connected PC.
To connect to a dongle select the proper serial port and click "Connect to GVRET". If a valid device is found on that serial port the first
statusbar section will update and the currently set canbus speeds will show in the drop down lists below the button. These speeds can then
be changed. Changes to canbus speed will only take effect when you click "Set CANBUS Speeds". The program will let you know if the connection
was not successful.
The Rest of the Main Window
===========================
Below the dongle configuration is an area that shows the total number of captured frames and the frames per second. Total frames might not match
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.
@@ -75,25 +61,27 @@ this off for performance reasons (interpreting takes some extra processor power
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 along side "Interpret Frames" to interpret frames and always see the up-to-date information.
"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.
Loading And Saving Frames
=========================
What CANBus analysis tool would be complete without an easy way to load and save frames?
SavvyCAN can load and save in several formats:
SavvyCAN can load and save in several formats (a few of which are listed below):
- CRTD: This format was made by Mark Webb-Johnson for OVMS (open vehicle monitoring system) and other related tools. It is a reasonably
readable and compact format. GVRET defaults to saving in this format. One reason one might not want to use CRTD is if knowing which bus
a frame originated on is important. CRTD does not save that information.
- GVRET: This is the native format for GVRET and SavvyCAN. One might then ask why CRTD is the default. Well, for compatibility with
OVMS tools. Otherwise, the GVRET format saves more information such as the bus a frame originated on. This format is in CSV
readable and compact format. One reason one might not want to use CRTD is if knowing which bus a frame originated on is important. CRTD does not save that information.
- GVRET: This is the native format for GVRET and SavvyCAN. The GVRET format saves more information such as the bus a frame originated on. This format is in CSV
(comma delimited) format and as such can easily be loaded into your favorite spreadsheet program as well.
- Generic ID/DATA - Another CSV format. This is a very cut down format with limited information.
- BusMaster - This is the format output by the BusMaster CANBus program. BusMaster is an open source Windows-only somewhat clone
of CANAlyzer (the 800lb gorilla in the analysis space). The ability to load and save in this format makes SavvyCAN fully capable
of swapping data with BusMaster should you need to do so.
- Microchip - Format output by Microchip CANBus tools. Perhaps you have logs that were
captured with a $100 Microchip dongle? You can load them in SavvyCAN.
- Microchip - Format output by Microchip CANBus tools. Perhaps you have logs that were captured with a $100 Microchip dongle? You can load them in SavvyCAN.
There are many other formats supported. Some are only supported for writing, some only for reading. The list of supported formats is expanded every so often.
Filters
@@ -126,7 +114,6 @@ to figure out the actual details of that signal.
How DBC interacts with the main screen:
=======================================
First of all, one can load and save DBC files from the File menu. Also, one can edit the currently loaded DBC file (or start working on
one if one is not loaded). It is also possible to save the currently loaded frames but with DBC decoding. This is somewhat like the normal
saving functionality with a two differences: there is only one output format and that format has all signals contained in each message listed
and decoded.
First of all, one can load and save DBC files from the "DBC File Manager" found in the File menu. Also in the File menu it is possible to save the currently
loaded frames but with DBC decoding. This is somewhat like the normal saving functionality with a two differences: there is only one output format
and that format has all signals contained in each message listed and decoded.
+33 -1
View File
@@ -1,5 +1,37 @@
Playback Window
===============
Play back that which was once captured
.
.. image:: ./images/Playback.png
Preparing Frames for Playback
=============================
The first order of business is to load some CAN frames that you'd like to play back onto a CAN bus. In the lower left is a section titled "Playback Sequence". It is so named because this playback interface can play a chain of different CAN captures very configurably. It consists of a list of captures to playback along with how many times to play each sequence item. For instance, you could play a file twice then go to the next, then play a third one four times. A playback item can either come from a file (Load File) or from the current list of captured frames on the main window (Load Captured Data). If you load the currently captured frames it truly means "currently". That is, if more traffic comes in it will not play that new traffic back. A snapshot is taken at the time you push the button. Each sequence item has its own list of ID filters. In this way you can send only some of the frame IDs from the capture and this list can be different for each file or capture you load. The list of ID filters can be saved and loaded to make the process faster in the future.
Once you've set up a sequence of frames to playback you can also decide whether you'd like to loop that sequence forever or not. Up above the Playback Sequence and ID Filtering sections is the "Loop Sequence" checkbox.
Playing Back Frames
====================
The playback window can send frames on a specific bus, all buses (be careful with that!) or "From File." Some file formats store which bus each frame came in on. Also, the main window stores that info. So, captures that stored the bus properly could be used to send frames out multiple buses always to the proper bus for the frame in question. But, if you load a capture without this info it will default to bus 0 so bear that in mind.
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.
The top of the window has a series of 6 icons all in a row:
1. White Left Arrow - Play the last frame (just one frame)
2. Pause sign - Pause playback
3. Green Left Arrow - Play frames backward
4. Blue Stop Button - Stop playback and return to the first frame in the first capture in the sequence
5. Green Right Arrow - Play frames forward
6. White Right Arrow - Play the next frame (just one frame)
Playback Status
================
Below the number spinners for Playback Speed and Burst Rate is text that displays the currently playing sequence item along with the current frame within that capture.
+35 -1
View File
@@ -1,5 +1,39 @@
Preference Window
=================
So, what do you prefer?
.
.. image:: ./images/Preferences.png
Setting Preferences
====================
There are a variety of preferences that can be set in the program and persisted for future sessions.
"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.
"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 familar 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 correspondance 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.
"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
"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.
"OpenGL Accelerated AntiAliased Graphing": A personal favorite of mine. 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.
"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.
"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.
"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 Sending Bus": Set a default for which bus to send frames on.
"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.
After changing preferences it is the best practice to close and reopen the application to ensure that all settings have taken effect.
+23
View File
@@ -0,0 +1,23 @@
Range State Window
===================
.
.. image:: ./images/RangeState.png
Using the Range State Window
============================
The purpose of this window is to search for signals that look like they might be important. That's a rather nebulous description. But, the idea is to find signals that seem to vary somewhat coherently. As you can see in the picture in this help, the displayed signal has some jumps but they look like they might "be something." That's the idea. It *will* generate a lot of false positives but it will still provide some decent ideas for where to look for signals. It is up to you to figure out if the signal is anything important or anything you can use.
To use it first set up the follow things:
1. The ID Filter list - Deselect any IDs you don't want to search.
2. Sensitivity - This is a subjective measurement. It doesn't affect the results a lot but changing this value back and forth can alter what the program thinks is a relevant signal.
3. Min Signal Size - The smallest signal you want to search for. You can search for signals all of the way down to 1 bit if you're feeling adventurous.
4. Max Signal Size - The largest signal you want to search for. The range between Min and Max changes how long the process will take. If you set Min to 8 and Max to 16 then it will search for signals with 8, 9, 10, 11, 12, 13, 14, 15, 16 bits. You will get duplicates as the same basic set of bits will be used for all of the sizes.
5. Granularity - Sets the bit jump width. What is bit jump width? The number of bits that we move over each time we search for a new signal. If you have a granularity of 1 and a minimum signal width of 8 then it will search starting at bit 0 for an 8 bit message then starting at bit 1 then bit 2, etc. If you set a granularity of 8 then it will search for an 8 bit signal at bit 0 then search at bit 8, then 16, etc. So this sets how finely we will search for signals. A lot of times signals will be found on byte edges so a granularity of 8 will cause all signal searches to start on byte boundaries and this will be OK for many things. But, some designers are more devious and place signals at uneven boundaries. You might need finer granularity to find such signals.
6. Signal Mode - For signals over 8 bits there is a choice to make. Signals over 8 bits can be either in big or little endian mode. This relates to whether bit 0 of a signal is the highest or lowest value. You can search for only big endian signals, only little endian, or try it both ways. *Usually* the developer of a CAN device will stick to one or the other but not always.
7. Signed Mode - Likewise, any signal over 1 bit could be either unsigned or signed. Signed signals have their highest bit as 1 for negative numbers and 0 for positive numbers. You can search for only unsigned signals, only signed, or try it both ways. There really isn't any rhyme or reason for when a signal would be signed or unsigned. It could easily be both ways so unless you're sure it's probably safest to allow the program to try it both ways and you can pick which looks best.
Once you've got it all set up click "Recalculate Candidate Signals." Be prepared to wait depending on what options you selected. Once it is done processing you'll get a list of candidates in the upper list labeled "Candidate Signals." Here you can see all of the signals it found. You get the ID, the starting bit (remember, bits start at 0 and go through 63), the length, and whether it was signed/unsigned and big/little endian. If you click on or otherwise select a signal in this list then a graphical view of it will appear in the graphing area beneath. You might try the arrow keys Up and Down to move through the list. You can even hold down the arrow key and let it rapidly scroll. As it scrolls through the signals you can look at the graph and stop when you see a signal that catches your eye. This is useful as you can have hundreds of candidates and it is tedious to view them explicitly one at a time.
+136
View File
@@ -0,0 +1,136 @@
Scripting Interface
====================
.
.. image:: ./images/ScriptingWindow.png
Purpose of the Scripting Interface
==================================
To give you an open ended place where you can write JavaScript files that do whatever you need them to do. You can load several files at once and they'll all work at the same time. Scripts can expose parameters that can then be edited by you while the program is running. They also update 4 times per second so they can be used to give feedback of the current state of the script as well. But, it is safer to give status in the "Log Window" instead. However, for values that might need to be seen and frequently updated it might be easiest to show them as "Public Variables".
Managing Scripts
=================
In the bottom left is the "Loaded Scripts" list. You can create a new script by clicking the "New" button below the list. The new script will have a random name until you save it. The "Del" button will delete the currently selected script but it will ask you first. The "Load" button will load a new script from a file. The "Save" button is above the main script view which is in the upper right of the window. "Revert" will revert the script to the last version that was compiled. Any changes you've made since compiling well be reverted. "Recompile" is used to compile the script and begin running the new version.
Getting Script Status
======================
There are two places you can look for the status of a script. The "Log Window" is directly below the script source code editor. This window is shared between all scripts and shows the status of compiling scripts as well as log messages from each script. The script name that sent the log message is prepended. The number before the script name is the amount of time the scripting window had been open for when the message was sent. This unified interface can be used to keep an eye on all of the running scripts and to debug issues when a script is compiled. Any compile errors will show up in the log window. You can set the checkbox on "Auto Scroll Log Window" to make it continue to stay at the bottom of the log. You can clear the log at any time as well.
The other way to see script status is to use the "Public Variables" interface. Here you will find variables that were registered by the current script. Each script has its own list so information that needs to be updated frequently and/or specific to a script and easily accessible should be registered here. But, public variables can have their value set by you, the user, as well. So, care should be taken not to edit variables used for script feedback and scripts shouldn't try to change the value of variables used for input to the program.
Writing Scripts
================
You are more or less free to write JavaScript scripts but, of course, you aren't in a web browser so browser specific functions are just not there. In their place are a couple of JS objects that allow the script to interface with the CAN buses connected to SavvyCAN. Also, certain functions can be created to automatically register callbacks.
Callback Functions
===================
These functions can be created in your scripts to enable certain functionality:
setup () - If you create a function named setup then it will be called as soon as the script starts. Yes, you probably could just dump code into no function at all right into the file but that's bad form!
tick () - If you registered to receive a periodic tick within your setup function then the script interface will call this function for every tick. You can do whatever you need to periodically do here. But, you get only one tick handler so if you need multiple tick rates you'll have to create a fast tick here and dispatch from this function at different rates yourself.
gotCANFrame (bus, id, len, data) - A callback that will be called whenever a CAN frame comes in that you've registered for. You did register for frames in your setup function didn't you? Well, if you use one of the below callbacks you might not need this one.
gotISOTPMessage (bus, id, len, data) - If you are instead looking for ISO-TP messages (which could have been multiple CAN frames in length) then you can create this function and it will automatically be registered with the system. But, you still will need to set which ISO-TP message IDs you want to receive. That is covered later on.
gotUDSMessage (bus, id, service, subfunc, len, data) - UDS messages are transmitted over ISO-TP but with additional structure. If you're looking to interface directly at the UDS level then you can create this function to have it automatically registered. As with raw CAN and ISO-TP you still need to specify which messages IDs you are interested in.
The host Object
================
The first object you can use is "host" This object handles setup of the tick timer as well as logging output and registration of public variables.
host.setTickInterval(interval) - If the interval is more than 0 then your tick callback will be called every "interval" milliseconds. If a value of 0 is passed then the tick timer will be stopped.
host.log(text) - Send text to the log window. It will be timestamped, marked according to which script sent it, and placed into the log window.
host.addParameter("variablename") - Add the named variable to the list of public variables. From then on any changes that you make in the GUI will immediately show up in the script and any changes the script makes to a value will reflect in the GUI within 250ms. Remember to use quotes around the variable name. You want to pass the variable name, not its value.
The can Object
===============
This object is your interface to raw CAN. It has the following functions:
can.setFilter(id, mask, bus) - register to receive messages based on an ID, Mask, and Bus. It works like this. First the bus is compared. If it doesn't match the frame is not delivered to you. Then, the incoming frame has its ID ANDed with your mask. Let's say your mask is 0x7F0 and the incoming frame has an ID of 0x235. 0x235 AND 0x7F0 is 0x230. This value is compared to the ID you passed. So, if your filter ID is 0x230 then the frame is accepted and you will get a callback with the frame. Otherwise the frame is not delivered to you. This masking setup is very common in CAN bus interfaces. Basically, the mask allows a single filter to accept a range of IDs. 0x7F0 would accept 16 different IDs (0x230 through 0x23F in this case). 0x700 accepts 256 different IDs, etc.
can.clearFilters() - remove all filters and revert to a clean state. You will no longer receive any CAN callbacks unless you create more filters with setFilter.
can.sendFrame(bus, id, length, data) - Send a CAN frame out the given bus. The CAN id will be what you set as will the length. The length can thus be different from the actual length of "data" which should be a valid javascript array. The length can not exceed 8. The frame will be sent as soon as possible so long as that bus is connected and not in listen only mode.
The isotp Object
================
isotp.setFilter(id, mask, bus) - Exactly like the raw CAN version in the can object. Allows you to register a filter so that you can receive ISO-TP traffic from the filtered addresses. It should be noted that you'll essentially only get traffic that seems to be able to be turned into ISO-TP traffic. Any CAN frames obviously not ISO-TP will be rejected.
isotp.clearFilters() - Clear all ISO-TP filters and no longer receive ISO-TP traffic.
isotp.sendISOTP(bus, id, length, data) - As in the can version. The difference here is that ISO-TP messages can be longer than 8 bytes and so might get turned into a multi-frame set of messages with flow control. This is handled for you by SavvyCAN so you needn't handle of the details of the exchange.
The uds Object
===============
uds.setFilter(id, mask, bus) - Exactly like the other two setFilter functions. Register for a set of IDs to be interpreted as UDS (if possible) and sent through to your callback. Any obviously not UDS traffic will be discarded.
uds.clearFilter() - Remove all filters and quit receiving UDS traffic.
uds.sendUDS(bus, id, service, sublen, subfunc, length, data) - Sends a UDS message out from the script. service must be between 0 and 255, subfunc can be larger than one byte if needed. data is only needed for extended payloads as the actual UDS protocol is handled by the service and subfunc parameters.
A full example script
=====================
::
var newID = 0; //set this to the ID you want your RLEC to become
function setup ()
{
host.log("RLEC ID Changer");
can.setFilter(0x0, 0x0F, 0);
can.sendFrame(0, 0x7E0, 8, [0x0d, 1, 0, 0, 0, 0, 0, 0]);
}
function gotCANFrame (bus, id, len, data)
{
var dataBytes = [];
if (len == 8)
{
if (data[0] == 0xd && data[1] == 1 && data[2] == 0xAA)
{
host.log("Got challenge: 0x" + data[3].toString(16) + data[4].toString(16));
var notData3 = ~data[3];
var notData4 = ~data[4];
dataBytes[0] = 0xD;
dataBytes[1] = 2;
dataBytes[2] = ((notData4 & 0xF) << 4) + ((notData3 >> 4) & 0xF);
dataBytes[3] = ((notData4 >> 4) & 0xF) + ((notData3 & 0xF) << 4);
dataBytes[4] = 0;
dataBytes[5] = 0;
dataBytes[6] = 0;
dataBytes[7] = 0;
can.sendFrame(0, 0x7E0, 8, dataBytes);
}
if (data[0] == 0xd && data[1] == 2 && data[2] == 0xAA)
{
host.log("Passed security Check!");
dataBytes[0] = 4;
dataBytes[1] = 0x15;
dataBytes[2] = newID;
can.sendFrame(0, 0x7E0, 8, dataBytes);
}
if (data[0] ==4 && data[1] == 0x15 && data[2] == 0xAA)
{
host.log("ID Reprogramming Successful!");
}
}
}
+44
View File
@@ -0,0 +1,44 @@
DBC Signal Editor
=================
.
.. image:: ./images/SignalEditor.png
Defining and Editing Signals
============================
If you're starting from scratch or otherwise adding a signal then you will need to right click in the list box in the upper left of the window. From there select "Add a New Signal." This will create a randomly named signal that you can then edit.
Otherwise, select a signal to edit. The details of it will fill out the rest of the window.
On the right side you can rename the signal and you'll see that it is renamed in the list 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.
"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.
"Type" can be:
1. UNSIGNED INTEGER - No sign bit, only positive numbers
2. SIGNED INTEGER - The top bit is used as a sign bit
3. SINGLE PRECISION - Floating point number (should be a 16 bit signal)
4. DOUBLE PRECISION - Floating point number (should be a 32 bit signal)
5. STRING - Directly turn the CAN bytes into a string
"Scale" is used to multiply the signal by the value to scale it appropriately
"Bias" is added to each value generated by the signal to set it at a different bias point
"Min Value" is purely informational. It is just a reference to anyone else viewing the DBC information as to what you expect the lowest value to be.
"Max Value" is likely informational.
"Units Name" is displayed after the value when you interpret a signal on the main frames list. For instance, you could set the units name to V so that a voltage value reads something like "12.34V" when it is displayed.
"Receiving Node" This is informational at the moment. You can set which node (out of all your defined nodes) is the one that receives this signal.
"Multiplexing" This is a somewhat involved topic. DBC signals can be multiplexed which means that a given frame might have a range of data that is not always found in every frame. There is a key of sorts that specifies which piece of data this particular frame is sending. This leads to the concept of multiplexed signals and multiplexors. Multiplexors are the key. They provide a value that specifies which multiplexed data item is being sent. A multiplexed signal is then connected to a specific value of the multiplexor. Thus, a multiplexed signal requires that a multiplexor also exists. You would normally set all of this to "Not multiplexed" and skip all this complication. But, multiplexed signals do exist. In that case the message would have one multiplexor and one or more multiplexed signals. So, you'd set up a multiplexor for the message and then create additional multiplexed signals that are marked as "Multiplexed" and have filled out the "Multiplex Value" with something unique.
"Comment" is purely informational.
Signals that use either "UNSIGNED INTEGER" or "SIGNED INTEGER" as their type can define a Value Table. This table allows text strings to be substituted in place of integer values. For instance, if you know that a value of 0x10 means PARK then you can go to the next empty entry in the list, type 0x10 for the value and PARK for the Text. This will make the interpreted value read PARK any time the signal has a value of 0x10. This is used to make a more human friendly presentation. It should be noted that if the signal has a value not in the list then it will still be shown as it's integer value. But, known good values can be entered in the Value Table to make them easier to work with.
+18
View File
@@ -0,0 +1,18 @@
Sniffer Window
=================
.
.. image:: ./images/Sniffer.png
Using the Sniffer Window
=========================
This window is essentially a graphical version of the linux can_utils. The general idea here is to display a list of frames such that you only see frames that are actively updating. If a given ID has not been seen in 4 seconds the ID portion will turn RED and then disappear from the list. In this way only frames that are updating are in the list. They are ordered by last update time. Bytes that have deincremented will be red and bytes that have incremented will be green. You can use the "Filters" area to mask away some IDs so that they never show up. This can help to declutter the list.
Notching and Unnotching
========================
Honestly, I don't know. It seems that notching means to store the current value of the data bytes for each frame and then use that notch data for the comparison for increment / deincrement to color the bytes. Unnotching would then be clearing that out so that it uses the previous value from the last time the frame ID was seen. But, don't quote me on that. This should be figured out definitively and corrected.
+35
View File
@@ -0,0 +1,35 @@
UDS Scan Window
=================
.
.. image:: ./images/UDS_Scanner.png
Purpose of the UDS Scan Window
===============================
This is essentially another CAN fuzzing window but a very special one. This window is meant to search for UDS compliant (or nearly compliant) nodes on the CAN bus. It can also be used to do a blanket search for services, sub functions, and data items on a known UDS node.
Using the UDS Scan Window
==========================
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. 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.
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.
"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 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.
"Session Ctrl" - UDS has the concept of session type. Some types are "normal" "extended diagnostics" "programmming". Scanning for session type will try them all and let you know which the device supports. However, note that you might get a response that indicates that conditions are not correct or that access was denied. This probably means that the mode is actually supported but you'd have to do the right things first. This is still useful information. This scan window is too simple to use for further cracking of such things but the scripting interface is the perfect thing to use instead.
"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 secutity 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.
"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.
+30
View File
@@ -0,0 +1,30 @@
function setup ()
{
host.log("Example script 1.0");
host.setTickInterval(20000);
can.sendFrame(0, 0x200, 4, [1,30,20,10]);
uds.sendUDS(0, 0x604, 0x3E, 1, 0, 0, 0);
can.setFilter(0x100, 0x700, 0);
uds.setFilter(0x600, 0x7F0, 0);
}
function gotCANFrame (bus, id, len, data)
{
host.log("Bus: " + bus + " id: " + id.toString(16));
}
function gotISOTPMessage(bus, id, len, data)
{
host.log("ISOTP bus " + bus + " ID: " + id.toString(16));
}
function gotUDSMessage(bus, id, service, subFunc, len, data)
{
host.log("UDS Bus: " + bus + " ID: " + id.toString(16) + " Sv: " + service.toString(16));
}
function tick()
{
host.log("TICK!");
}
+17
View File
@@ -0,0 +1,17 @@
function setup ()
{
host.log("Example script 1.0");
host.setTickInterval(20000);
can.sendFrame(0, 0x7E0, 8, [0x0d, 1, 0, 0, 0, 0, 0, 0]);
can.setFilter(0x100, 0x700, 0);
}
function gotCANFrame (bus, id, len, data)
{
host.log("Bus: " + bus + " id: " + id.toString(16));
}
function tick()
{
host.log("TICK!");
}
+44
View File
@@ -0,0 +1,44 @@
var newID = 0;
function setup ()
{
host.log("RLEC ID Changer");
can.setFilter(0x0, 0x0F, 0);
can.sendFrame(0, 0x7E0, 8, [0x0d, 1, 0, 0, 0, 0, 0, 0]);
}
function gotCANFrame (bus, id, len, data)
{
var dataBytes = [];
if (len == 8)
{
if (data[0] == 0xd && data[1] == 1 && data[2] == 0xAA)
{
host.log("Got challenge: 0x" + data[3].toString(16) + data[4].toString(16));
var notData3 = ~data[3];
var notData4 = ~data[4];
dataBytes[0] = 0xD;
dataBytes[1] = 2;
dataBytes[2] = ((notData4 & 0xF) << 4) + ((notData3 >> 4) & 0xF);
dataBytes[3] = ((notData4 >> 4) & 0xF) + ((notData3 & 0xF) << 4);
dataBytes[4] = 0;
dataBytes[5] = 0;
dataBytes[6] = 0;
dataBytes[7] = 0;
can.sendFrame(0, 0x7E0, 8, dataBytes);
}
if (data[0] == 0xd && data[1] == 2 && data[2] == 0xAA)
{
host.log("Passed security Check!");
dataBytes[0] = 4;
dataBytes[1] = 0x15;
dataBytes[2] = newID;
can.sendFrame(0, 0x7E0, 8, dataBytes);
}
if (data[0] ==4 && data[1] == 0x15 && data[2] == 0xAA)
{
host.log("ID Reprogramming Successful!");
}
}
}
+12
View File
@@ -0,0 +1,12 @@
var testingVar = 10;
function setup()
{
host.addParameter("testingVar");
host.setTickInterval(2000);
}
function tick()
{
testingVar++;
}
+204 -16
View File
@@ -6,6 +6,7 @@
#include <iostream>
#include "utility.h"
#include "blfhandler.h"
QFile FrameFileIO::continuousFile;
@@ -137,6 +138,9 @@ bool FrameFileIO::loadFrameFile(QString &fileName, QVector<CANFrame>* frameCache
filters.append(QString(tr("PCAN Viewer (*.trc *.TRC)")));
filters.append(QString(tr("Kvaser Log Decimal (*.txt *.TXT)")));
filters.append(QString(tr("Kvaser Log Hex (*.txt *.TXT)")));
filters.append(QString(tr("CANalyzer Ascii Log (*.asc *.ASC)")));
filters.append(QString(tr("CANalyzer Binary Log Files (*.blf *.BLF)")));
filters.append(QString(tr("CANHacker Trace Files (*.trc *.TRC)")));
dialog.setFileMode(QFileDialog::ExistingFile);
dialog.setNameFilters(filters);
@@ -169,6 +173,9 @@ bool FrameFileIO::loadFrameFile(QString &fileName, QVector<CANFrame>* frameCache
if (dialog.selectedNameFilter() == filters[10]) result = loadPCANFile(filename, frameCache);
if (dialog.selectedNameFilter() == filters[11]) result = loadKvaserFile(filename, frameCache, false);
if (dialog.selectedNameFilter() == filters[12]) result = loadKvaserFile(filename, frameCache, true);
if (dialog.selectedNameFilter() == filters[13]) result = loadCanalyzerASC(filename, frameCache);
if (dialog.selectedNameFilter() == filters[14]) result = loadCanalyzerBLF(filename, frameCache);
if (dialog.selectedNameFilter() == filters[15]) result = loadCANHackerFile(filename, frameCache);
progress.cancel();
@@ -290,7 +297,6 @@ tokens:
2 = ID
3-x = The data bytes
*/
bool FrameFileIO::loadCRTDFile(QString filename, QVector<CANFrame>* frames)
{
QFile *inFile = new QFile(filename);
@@ -363,6 +369,75 @@ bool FrameFileIO::loadCRTDFile(QString filename, QVector<CANFrame>* frames)
return !foundErrors;
}
// CANHacker trace format
// Time ID DLC Data Comment
// 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);
CANFrame thisFrame;
QByteArray line;
int lineCounter = 0;
bool foundErrors = false;
if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text))
{
delete inFile;
return false;
}
line = inFile->readLine().toUpper(); //read out the header first and discard it.
while (!inFile->atEnd()) {
lineCounter++;
if (lineCounter > 100)
{
qApp->processEvents();
lineCounter = 0;
}
line = inFile->readLine().simplified();
if (line.length() > 2)
{
QList<QByteArray> tokens = line.split(' ');
int multiplier;
if (tokens.length() > 3)
{
int 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.
//This program deals in microsecond so turn the value into microseconds
multiplier = 1000000; //turn the decimal into full microseconds
}
else
{
multiplier = 1; //special case. Assume no decimal means microseconds
}
//qDebug() << "decimal places " << decimalPlaces;
thisFrame.timestamp = (int64_t)(tokens[0].toDouble() * multiplier);
thisFrame.ID = tokens[1].toInt(NULL, 16);
thisFrame.extended = (thisFrame.ID > 0x7FF);
thisFrame.isReceived = true;
thisFrame.bus = 0;
thisFrame.len = tokens[2].toInt(NULL, 16);
for (unsigned int d = 0; d < thisFrame.len; d++)
{
if (tokens[d + 3] != "")
{
thisFrame.data[d] = tokens[d + 3].toInt(NULL, 16);
}
else thisFrame.data[d] = 0;
}
frames->append(thisFrame);
}
else foundErrors = true;
}
}
inFile->close();
delete inFile;
return !foundErrors;
}
bool FrameFileIO::saveCRTDFile(QString filename, const QVector<CANFrame>* frames)
{
QFile *outFile = new QFile(filename);
@@ -416,6 +491,8 @@ bool FrameFileIO::saveCRTDFile(QString filename, const QVector<CANFrame>* frames
}
/*Fixed length lines
Version 1.1
//; Message Number
//; | Time Offset (ms)
//; | | Type
@@ -424,14 +501,27 @@ bool FrameFileIO::saveCRTDFile(QString filename, const QVector<CANFrame>* frames
//; | | | | | Data Bytes (hex) ...
//; | | | | | |
//;---+-- ----+---- --+-- ----+--- + -+ -- -- -- -- -- -- --
// 0-6 10-18 21-25 28-35 38 41-?
//Fixed length lines
// 0-6 10-18 21-25 28-35 38 41-? two chars each + space
Version 2.0
; Message Time Type ID Rx/Tx
; Number Offset | [hex] | Data Length
; | [ms] | | | | Data [hex] ...
; | | | | | | |
;---+-- ------+------ +- --+----- +- +- +- +- -- -- -- -- -- -- --
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.
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)
{
QFile *inFile = new QFile(filename);
CANFrame thisFrame;
QByteArray line;
int lineCounter = 0;
int fileVersion = 1;
bool foundErrors = false;
if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text))
@@ -448,27 +538,54 @@ bool FrameFileIO::loadPCANFile(QString filename, QVector<CANFrame>* frames)
lineCounter = 0;
}
line = inFile->readLine();
if (line.startsWith(";$FILEVERSION=2.0")) fileVersion = 2;
if (line.startsWith(';')) continue;
if (line.length() > 2)
{
thisFrame.timestamp = line.mid(10, 8).simplified().toFloat() * 1000ull;
thisFrame.ID = line.mid(28, 8).simplified().toUInt(NULL, 16);
if (thisFrame.ID < 0x1FFFFFFF)
if (fileVersion == 1)
{
thisFrame.len = line.mid(38,1).toInt();
thisFrame.isReceived = true;
thisFrame.bus = 0;
thisFrame.extended = false;
QList<QByteArray> tokens = line.mid(41, thisFrame.len * 3).split(' ');
for (unsigned int d = 0; d < thisFrame.len; d++)
thisFrame.timestamp = (uint32_t)(line.mid(10, 8).simplified().toFloat() * 1000.0);
thisFrame.ID = line.mid(28, 8).simplified().toUInt(NULL, 16);
if (thisFrame.ID < 0x1FFFFFFF)
{
if (tokens[d] != "")
thisFrame.len = line.mid(38,1).toInt();
thisFrame.isReceived = true;
thisFrame.bus = 0;
thisFrame.extended = false;
QList<QByteArray> tokens = line.mid(41, thisFrame.len * 3).split(' ');
for (unsigned int d = 0; d < thisFrame.len; d++)
{
thisFrame.data[d] = tokens[d].toInt(NULL, 16);
if (tokens[d] != "")
{
thisFrame.data[d] = tokens[d].toInt(NULL, 16);
}
else thisFrame.data[d] = 0;
}
else thisFrame.data[d] = 0;
frames->append(thisFrame);
}
}
else if (fileVersion == 2)
{
thisFrame.timestamp = (uint32_t)(line.mid(8, 13).simplified().toFloat() * 1000.0);
thisFrame.ID = line.mid(25, 8).simplified().toUInt(NULL, 16);
if (thisFrame.ID < 0x1FFFFFFF)
{
thisFrame.len = line.mid(37,2).trimmed().toInt();
qDebug() << thisFrame.len;
thisFrame.isReceived = true;
thisFrame.bus = 0;
thisFrame.extended = false;
QList<QByteArray> tokens = line.mid(40, thisFrame.len * 3).split(' ');
for (unsigned int d = 0; d < thisFrame.len; d++)
{
if (tokens[d] != "")
{
thisFrame.data[d] = tokens[d].toInt(NULL, 16);
}
else thisFrame.data[d] = 0;
}
frames->append(thisFrame);
}
frames->append(thisFrame);
}
}
//else foundErrors = true;
@@ -478,6 +595,77 @@ bool FrameFileIO::loadPCANFile(QString filename, QVector<CANFrame>* frames)
return !foundErrors;
}
//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:
// 47.971842 2 248 Rx d 8 FF FF FF FF FF FF FF FF
//That can be simplified to look like this:
//47.971842 2 248 Rx d 8 FF FF FF FF FF FF FF FF
//Which is then easy to parse by splitting on the spaces
//Time bus id dir ? len databytes
bool FrameFileIO::loadCanalyzerASC(QString filename, QVector<CANFrame>* frames)
{
QFile *inFile = new QFile(filename);
CANFrame thisFrame;
QByteArray line;
int lineCounter = 0;
bool foundErrors = false;
bool inHeader = true;
if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text))
{
delete inFile;
return false;
}
while (!inFile->atEnd()) {
lineCounter++;
if (lineCounter > 100)
{
qApp->processEvents();
lineCounter = 0;
}
line = inFile->readLine();
if (line.startsWith("//"))
{
inHeader = false;
continue;
}
if (inHeader) continue;
if (line.length() > 2)
{
QList<QByteArray> tokens = line.simplified().split(' ');
thisFrame.timestamp = (uint32_t)(tokens[0].toFloat() * 1000000.0);
thisFrame.ID = tokens[2].toUInt(NULL, 16);
thisFrame.len = tokens[5].toUInt();
thisFrame.isReceived = tokens[3].toUpper().contains("RX");
thisFrame.bus = tokens[1].toUInt();
thisFrame.extended = (thisFrame.ID > 0x7FF);
for (unsigned int d = 6; d < (6 + thisFrame.len); d++)
{
if (tokens.count() > d)
{
thisFrame.data[d - 6] = tokens[d].toInt(NULL, 16);
}
else //expected byte wasn't there to read. Set it zero and set error flag
{
thisFrame.data[d - 6] = 0;
foundErrors = true;
}
}
frames->append(thisFrame);
}
//else foundErrors = true;
}
inFile->close();
delete inFile;
return !foundErrors;
}
bool FrameFileIO::loadCanalyzerBLF(QString filename, QVector<CANFrame> *frames)
{
BLFHandler blf;
return blf.loadBLF(filename, frames);
}
//The "native" file format for this program
bool FrameFileIO::loadNativeCSVFile(QString filename, QVector<CANFrame>* frames)
+3
View File
@@ -42,6 +42,9 @@ public:
static bool loadCanDumpFile(QString, QVector<CANFrame>*);
static bool loadPCANFile(QString, QVector<CANFrame>*);
static bool loadKvaserFile(QString, QVector<CANFrame>*, bool);
static bool loadCanalyzerASC(QString, QVector<CANFrame>*);
static bool loadCanalyzerBLF(QString, QVector<CANFrame>*);
static bool loadCANHackerFile(QString filename, QVector<CANFrame>* frames);
static bool saveCRTDFile(QString, const QVector<CANFrame>*);
static bool saveNativeCSVFile(QString, const QVector<CANFrame>*);
static bool saveGenericCSVFile(QString, const QVector<CANFrame>*);
+21
View File
@@ -5,6 +5,7 @@
#include <QMenu>
#include <QSettings>
#include "connections/canconmanager.h"
#include "helpwindow.h"
/*
* Notes about new functionality:
@@ -93,14 +94,34 @@ FramePlaybackWindow::~FramePlaybackWindow()
void FramePlaybackWindow::showEvent(QShowEvent *)
{
readSettings();
installEventFilter(this);
}
void FramePlaybackWindow::closeEvent(QCloseEvent *event)
{
Q_UNUSED(event);
removeEventFilter(this);
writeSettings();
}
bool FramePlaybackWindow::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("playbackwindow.html");
break;
}
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
return false;
}
void FramePlaybackWindow::readSettings()
{
QSettings settings;
+1
View File
@@ -66,6 +66,7 @@ private:
void readSettings();
void writeSettings();
void calculateWhichBus();
bool eventFilter(QObject *obj, QEvent *event);
};
#endif // FRAMEPLAYBACKWINDOW_H
+51 -15
View File
@@ -5,6 +5,7 @@
#include <QFileDialog>
#include <QDebug>
#include "mainwindow.h"
#include "helpwindow.h"
#include "connections/canconmanager.h"
/*
@@ -26,18 +27,7 @@ FrameSenderWindow::FrameSenderWindow(const QVector<CANFrame> *frames, QWidget *p
intervalTimer->setTimerType(Qt::PreciseTimer);
intervalTimer->setInterval(1);
QStringList headers;
headers << "En" << "Bus" << "ID" << "Len" << "Data" << "Trigger" << "Modifications" << "Count";
ui->tableSender->setColumnCount(8);
ui->tableSender->setColumnWidth(0, 50);
ui->tableSender->setColumnWidth(1, 50);
ui->tableSender->setColumnWidth(2, 50);
ui->tableSender->setColumnWidth(3, 50);
ui->tableSender->setColumnWidth(4, 220);
ui->tableSender->setColumnWidth(5, 220);
ui->tableSender->setColumnWidth(6, 220);
ui->tableSender->setColumnWidth(7, 80);
ui->tableSender->setHorizontalHeaderLabels(headers);
setupGrid();
createBlankRow();
connect(ui->tableSender, SIGNAL(cellChanged(int,int)), this, SLOT(onCellChanged(int,int)));
@@ -51,23 +41,59 @@ FrameSenderWindow::FrameSenderWindow(const QVector<CANFrame> *frames, QWidget *p
intervalTimer->start();
elapsedTimer.start();
installEventFilter(this);
}
void FrameSenderWindow::setupGrid()
{
QStringList headers;
headers << "En" << "Bus" << "ID" << "Len" << "Data" << "Trigger" << "Modifications" << "Count";
ui->tableSender->setColumnCount(8);
ui->tableSender->setColumnWidth(0, 50);
ui->tableSender->setColumnWidth(1, 50);
ui->tableSender->setColumnWidth(2, 50);
ui->tableSender->setColumnWidth(3, 50);
ui->tableSender->setColumnWidth(4, 220);
ui->tableSender->setColumnWidth(5, 220);
ui->tableSender->setColumnWidth(6, 220);
ui->tableSender->setColumnWidth(7, 80);
ui->tableSender->setHorizontalHeaderLabels(headers);
}
FrameSenderWindow::~FrameSenderWindow()
{
removeEventFilter(this);
delete ui;
intervalTimer->stop();
delete intervalTimer;
}
bool FrameSenderWindow::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("customsender.html");
break;
}
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
return false;
}
void FrameSenderWindow::createBlankRow()
{
int row = ui->tableSender->rowCount();
ui->tableSender->insertRow(row);
QTableWidgetItem *item = new QTableWidgetItem();
item->setFlags(item->flags() |Qt::ItemIsUserCheckable);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(Qt::Unchecked);
inhibitChanged = true;
ui->tableSender->setItem(row, 0, item);
@@ -241,6 +267,8 @@ void FrameSenderWindow::loadGrid()
loadSenderFile(filename);
}
}
setupGrid();
}
void FrameSenderWindow::saveSenderFile(QString filename)
@@ -288,22 +316,30 @@ void FrameSenderWindow::loadSenderFile(QString filename)
}
ui->tableSender->clear();
while (ui->tableSender->rowCount() > 0) ui->tableSender->removeRow(0);
sendingData.clear();
while (!inFile->atEnd()) {
inhibitChanged = true;
line = inFile->readLine().simplified();
if (line.length() > 2)
{
QList<QByteArray> tokens = line.split('#');
int row = ui->tableSender->rowCount();
ui->tableSender->insertRow(row);
ui->tableSender->item(row, 0)->setFlags(Qt::ItemIsUserCheckable);
QTableWidgetItem *item = new QTableWidgetItem();
ui->tableSender->setItem(row, 0, item);
ui->tableSender->item(row, 0)->setFlags(item->flags() | Qt::ItemIsUserCheckable);
if (tokens[0] == "T")
{
ui->tableSender->item(row, 0)->setCheckState(Qt::Checked);
}
else ui->tableSender->item(row, 0)->setCheckState(Qt::Unchecked);
for (int i = 0; i < 7; i++) ui->tableSender->item(row, i)->setText(tokens[i]);
for (int i = 1; i < 7; i++)
{
ui->tableSender->setItem(row, i, new QTableWidgetItem(QString(tokens[i])));
}
inhibitChanged = false;
for (int k = 0; k < 7; k++) processCellChange(row, k);
}
+2
View File
@@ -53,6 +53,8 @@ private:
void processCellChange(int line, int col);
void buildFrameCache();
void processIncomingFrame(CANFrame *frame);
bool eventFilter(QObject *obj, QEvent *event);
void setupGrid();
};
#endif // FRAMESENDERWINDOW_H
+94
View File
@@ -0,0 +1,94 @@
#include <QDebug>
#include "helpwindow.h"
#include "ui_helpwindow.h"
HelpWindow* HelpWindow::self = 0;
HelpWindow::HelpWindow(QWidget *parent) :
QDialog(parent),
ui(new Ui::HelpWindow)
{
ui->setupUi(this);
m_helpEngine = new QHelpEngineCore("SavvyCAN.qhc", this);
if (!m_helpEngine->setupData()) {
delete m_helpEngine;
m_helpEngine = 0;
qDebug() << "Could not load help file!";
}
readSettings();
}
HelpWindow::~HelpWindow()
{
writeSettings();
delete ui;
}
void HelpWindow::closeEvent(QCloseEvent *event)
{
Q_UNUSED(event);
writeSettings();
}
void HelpWindow::readSettings()
{
QSettings settings;
if (settings.value("Main/SaveRestorePositions", false).toBool())
{
resize(settings.value("HelpViewer/WindowSize", QSize(600, 700)).toSize());
move(settings.value("HelpViewer/WindowPos", QPoint(50, 50)).toPoint());
}
}
void HelpWindow::writeSettings()
{
QSettings settings;
if (settings.value("Main/SaveRestorePositions", false).toBool())
{
settings.setValue("HelpViewer/WindowSize", size());
settings.setValue("HelpViewer/WindowPos", pos());
}
}
HelpWindow* HelpWindow::getRef()
{
if (!self)
{
self = new HelpWindow();
}
return self;
}
void HelpWindow::showHelp(QString help)
{
if (m_helpEngine) {
qDebug() << "Searching for " << help;
QByteArray helpData = m_helpEngine->fileData(QUrl("qthelp://org.sphinx.savvycan.181/doc/" + help));
ui->textHelp->setText(helpData);
}
else
{
qDebug() << "Help engine not loaded!";
}
readSettings();
self->show();
}
/*
QVariant HelpBrowser::loadResource(int type, const QUrl &name)
{
QByteArray ba;
if (type < 4 && m_helpEngine) {
QUrl url(name);
if (name.isRelative())
url = source().resolved(url);
ba = m_helpEngine->fileData(url);
}
return ba;
}
*/
+31
View File
@@ -0,0 +1,31 @@
#ifndef HELPWINDOW_H
#define HELPWINDOW_H
#include <QDialog>
#include <QtHelp>
namespace Ui {
class HelpWindow;
}
class HelpWindow : public QDialog
{
Q_OBJECT
public:
explicit HelpWindow(QWidget *parent = 0);
~HelpWindow();
void showHelp(QString help);
static HelpWindow *getRef();
private:
void readSettings();
void writeSettings();
void closeEvent(QCloseEvent *event);
Ui::HelpWindow *ui;
static HelpWindow *self;
QHelpEngineCore *m_helpEngine;
};
#endif // HELPWINDOW_H
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>HelpWindow</class>
<widget class="QDialog" name="HelpWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>700</height>
</rect>
</property>
<property name="windowTitle">
<string>SavvyCAN Help</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTextBrowser" name="textHelp"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+7
View File
@@ -6,6 +6,13 @@ int main(int argc, char *argv[])
QApplication a(argc, argv);
MainWindow w;
QSettings settings;
int fontSize = settings.value("Main/FontSize", 9).toUInt();
QFont sysFont = QFont(); //get default font
sysFont.setPointSize(fontSize);
a.setFont(sysFont);
w.show();
return a.exec();
+35
View File
@@ -1,5 +1,6 @@
#include "mainsettingsdialog.h"
#include "ui_mainsettingsdialog.h"
#include "helpwindow.h"
MainSettingsDialog::MainSettingsDialog(QWidget *parent) :
QDialog(parent),
@@ -27,6 +28,11 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) :
ui->cbValidate->setChecked(settings->value("Main/ValidateComm", true).toBool());
ui->spinPlaybackSpeed->setValue(settings->value("Playback/DefSpeed", 5).toInt());
ui->lineClockFormat->setText(settings->value("Main/TimeFormat", "MMM-dd HH:mm:ss.zzz").toString());
ui->lineRemoteHost->setText(settings->value("Remote/Host", "api.savvycan.com").toString());
ui->lineRemotePort->setText(settings->value("Remote/Port", "21315").toString()); // = 0x5343 = SC. Yep, really creative port number
ui->cbAutoStartRemote->setChecked(settings->value("Remote/AutoStart", false).toBool());
ui->spinFontSize->setValue(settings->value("Main/FontSize", ui->cbDisplayHex->font().pointSize()).toUInt());
bool secondsMode = settings->value("Main/TimeSeconds", false).toBool();
bool clockMode = settings->value("Main/TimeClock", false).toBool();
@@ -73,6 +79,10 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) :
connect(ui->cbUseFiltered, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
connect(ui->lineClockFormat, SIGNAL(editingFinished()), this, SLOT(updateSettings()));
connect(ui->cbUseOpenGL, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
connect(ui->cbAutoStartRemote, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
connect(ui->lineRemoteHost, SIGNAL(editingFinished()), this, SLOT(updateSettings()));
connect(ui->lineRemotePort, SIGNAL(editingFinished()), this, SLOT(updateSettings()));
installEventFilter(this);
}
MainSettingsDialog::~MainSettingsDialog()
@@ -84,9 +94,30 @@ MainSettingsDialog::~MainSettingsDialog()
void MainSettingsDialog::closeEvent(QCloseEvent *event)
{
Q_UNUSED(event);
removeEventFilter(this);
updateSettings();
settings->sync();
}
bool MainSettingsDialog::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("preferences.html");
break;
}
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
return false;
}
void MainSettingsDialog::updateSettings()
{
settings->setValue("Main/UseHex", ui->cbDisplayHex->isChecked());
@@ -104,6 +135,10 @@ void MainSettingsDialog::updateSettings()
settings->setValue("Main/UseFiltered", ui->cbUseFiltered->isChecked());
settings->setValue("Main/UseOpenGL", ui->cbUseOpenGL->isChecked());
settings->setValue("Main/TimeFormat", ui->lineClockFormat->text());
settings->setValue("Main/FontSize", ui->spinFontSize->value());
settings->setValue("Remote/Host", ui->lineRemoteHost->text());
settings->setValue("Remote/Port", ui->lineRemotePort->text());
settings->setValue("Remote/AutoStart", ui->cbAutoStartRemote->isChecked());
settings->sync();
emit updatedSettings();
+2 -1
View File
@@ -19,7 +19,7 @@ public:
signals:
void updatedSettings();
private slots:
public slots:
void updateSettings();
private:
@@ -27,6 +27,7 @@ private:
QSettings *settings;
void closeEvent(QCloseEvent *event);
bool eventFilter(QObject *obj, QEvent *event);
};
#endif // MAINSETTINGSDIALOG_H
+58 -10
View File
@@ -6,6 +6,7 @@
#include <QtSerialPort/QSerialPortInfo>
#include "connections/canconmanager.h"
#include "connections/connectionwindow.h"
#include "helpwindow.h"
#include "utility.h"
/*
@@ -47,15 +48,11 @@ MainWindow::MainWindow(QWidget *parent) :
ui->canFramesView->setModel(proxyModel);
settingsDialog = new MainSettingsDialog(); //instantiate the settings dialog so it can initialize settings if this is the first run or the config file was deleted.
settingsDialog->updateSettings(); //write out all the settings. If this is the first run it'll write defaults out.
readSettings();
ui->canFramesView->setColumnWidth(0, 150);
ui->canFramesView->setColumnWidth(1, 70);
ui->canFramesView->setColumnWidth(2, 40);
ui->canFramesView->setColumnWidth(3, 40);
ui->canFramesView->setColumnWidth(4, 40);
ui->canFramesView->setColumnWidth(5, 40);
ui->canFramesView->setColumnWidth(6, 225);
QHeaderView *HorzHdr = ui->canFramesView->horizontalHeader();
HorzHdr->setStretchLastSection(true); //causes the data column to automatically fill the tableview
connect(HorzHdr, SIGNAL(sectionClicked(int)), this, SLOT(headerClicked(int)));
@@ -101,7 +98,7 @@ MainWindow::MainWindow(QWidget *parent) :
connect(ui->action_Playback, &QAction::triggered, this, &MainWindow::showPlaybackWindow);
connect(ui->actionFlow_View, &QAction::triggered, this, &MainWindow::showFlowViewWindow);
connect(ui->action_Custom, &QAction::triggered, this, &MainWindow::showFrameSenderWindow);
connect(ui->canFramesView, &QAbstractItemView::clicked, this, &MainWindow::gridClicked);
connect(ui->canFramesView, &QAbstractItemView::pressed, this, &MainWindow::gridClicked);
connect(ui->canFramesView, &QAbstractItemView::doubleClicked, this, &MainWindow::gridDoubleClicked);
connect(ui->cbInterpret, &QAbstractButton::toggled, this, &MainWindow::interpretToggled);
connect(ui->cbOverwrite, &QAbstractButton::toggled, this, &MainWindow::overwriteToggled);
@@ -134,10 +131,12 @@ MainWindow::MainWindow(QWidget *parent) :
lbStatusConnected.setText(tr("Connected to 0 buses"));
updateFileStatus();
lbStatusDatabase.setText(tr("No DBC database loaded"));
//lbStatusDatabase.setText(tr("No DBC database loaded"));
ui->statusBar->addWidget(&lbStatusConnected);
ui->statusBar->addWidget(&lbStatusFilename);
ui->statusBar->addWidget(&lbStatusDatabase);
//ui->statusBar->addWidget(&lbStatusDatabase);
ui->lblRemoteConn->setVisible(false);
ui->lineRemoteKey->setVisible(false);
ui->lbFPS->setText("0");
ui->lbNumFrames->setText("0");
@@ -171,6 +170,12 @@ MainWindow::MainWindow(QWidget *parent) :
connectionWindow = new ConnectionWindow();
connect(this, SIGNAL(suspendCapturing(bool)), connectionWindow, SLOT(setSuspendAll(bool)));
ui->actionFirmware_Update->setVisible(false);
ui->actionMotorControlConfig->setVisible(false);
ui->actionSignal_Viewer->setVisible(false);
ui->actionSingle_Multi_State_2->setVisible(false);
installEventFilter(this);
}
MainWindow::~MainWindow()
@@ -235,10 +240,31 @@ void MainWindow::exitApp()
void MainWindow::closeEvent(QCloseEvent *event)
{
Q_UNUSED(event);
removeEventFilter(this);
writeSettings();
exitApp();
}
bool MainWindow::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("mainscreen.html");
return true;
break;
}
return false;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
return false;
}
void MainWindow::updateSettings()
{
readUpdateableSettings();
@@ -252,11 +278,23 @@ void MainWindow::readSettings()
{
resize(settings.value("Main/WindowSize", QSize(800, 750)).toSize());
move(settings.value("Main/WindowPos", QPoint(100, 100)).toPoint());
ui->canFramesView->setColumnWidth(0, settings.value("Main/TimeColumn", 150).toUInt()); //time stamp
ui->canFramesView->setColumnWidth(1, settings.value("Main/IDColumn", 70).toUInt()); //frame ID
ui->canFramesView->setColumnWidth(2, settings.value("Main/ExtColumn", 40).toUInt()); //extended
ui->canFramesView->setColumnWidth(3, settings.value("Main/DirColumn", 40).toUInt()); //direction
ui->canFramesView->setColumnWidth(4, settings.value("Main/BusColumn", 40).toUInt()); //bus
ui->canFramesView->setColumnWidth(5, settings.value("Main/LengthColumn", 40).toUInt()); //length
ui->canFramesView->setColumnWidth(6, settings.value("Main/AsciiColumn", 50).toUInt()); //ascii
ui->canFramesView->setColumnWidth(7, settings.value("Main/DataColumn", 225).toUInt()); //data
}
if (settings.value("Main/AutoScroll", false).toBool())
{
ui->cbAutoScroll->setChecked(true);
}
int fontSize = settings.value("Main/FontSize", 9).toUInt();
QFont newFont = QFont(ui->canFramesView->font());
newFont.setPointSize(fontSize);
ui->canFramesView->setFont(newFont);
readUpdateableSettings();
}
@@ -284,6 +322,14 @@ void MainWindow::writeSettings()
{
settings.setValue("Main/WindowSize", size());
settings.setValue("Main/WindowPos", pos());
settings.setValue("Main/TimeColumn", ui->canFramesView->columnWidth(0));
settings.setValue("Main/IDColumn", ui->canFramesView->columnWidth(1));
settings.setValue("Main/ExtColumn", ui->canFramesView->columnWidth(2));
settings.setValue("Main/DirColumn", ui->canFramesView->columnWidth(3));
settings.setValue("Main/BusColumn", ui->canFramesView->columnWidth(4));
settings.setValue("Main/LengthColumn", ui->canFramesView->columnWidth(5));
settings.setValue("Main/AsciiColumn", ui->canFramesView->columnWidth(6));
settings.setValue("Main/DataColumn", ui->canFramesView->columnWidth(7));
}
}
@@ -311,6 +357,7 @@ void MainWindow::headerClicked(int logicalIndex)
void MainWindow::gridClicked(QModelIndex idx)
{
//qDebug() << "Grid Clicked";
if (ui->canFramesView->rowHeight(idx.row()) > normalRowHeight)
{
ui->canFramesView->setRowHeight(idx.row(), normalRowHeight);
@@ -322,6 +369,7 @@ void MainWindow::gridClicked(QModelIndex idx)
void MainWindow::gridDoubleClicked(QModelIndex idx)
{
//qDebug() << "Grid double clicked";
//grab ID and timestamp and send them away
CANFrame frame = model->getListReference()->at(idx.row());
emit sendCenterTimeID(frame.ID, frame.timestamp / 1000000.0);
+1
View File
@@ -176,6 +176,7 @@ private:
void killWindow(QDialog *win);
void readSettings();
void writeSettings();
bool eventFilter(QObject *obj, QEvent *event);
};
#endif // MAINWINDOW_H
+3014 -2656
View File
File diff suppressed because it is too large Load Diff
+756 -726
View File
File diff suppressed because it is too large Load Diff
+21
View File
@@ -1,6 +1,7 @@
#include "discretestatewindow.h"
#include "ui_discretestatewindow.h"
#include "mainwindow.h"
#include "helpwindow.h"
DiscreteStateWindow::DiscreteStateWindow(const QVector<CANFrame> *frames, QWidget *parent) :
QDialog(parent),
@@ -55,10 +56,12 @@ DiscreteStateWindow::DiscreteStateWindow(const QVector<CANFrame> *frames, QWidge
});
refreshFilterList();
installEventFilter(this);
}
DiscreteStateWindow::~DiscreteStateWindow()
{
removeEventFilter(this);
timer->stop();
for (int i = 0; i < stateFrames.count(); i++)
@@ -71,6 +74,24 @@ DiscreteStateWindow::~DiscreteStateWindow()
delete ui;
}
bool DiscreteStateWindow::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("discretestate.html");
break;
}
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
return false;
}
void DiscreteStateWindow::typeChanged()
{
if (ui->rbLogged->isChecked())
+1
View File
@@ -53,6 +53,7 @@ private:
void refreshFilterList();
void closeEvent(QCloseEvent *event);
bool eventFilter(QObject *obj, QEvent *event);
void readSettings();
void writeSettings();
void updateStateLabel();
+22 -1
View File
@@ -1,6 +1,6 @@
#include "filecomparatorwindow.h"
#include "ui_filecomparatorwindow.h"
#include "helpwindow.h"
#include <QSettings>
FileComparatorWindow::FileComparatorWindow(QWidget *parent) :
@@ -16,10 +16,13 @@ FileComparatorWindow::FileComparatorWindow(QWidget *parent) :
ui->lblFirstFile->setText("");
ui->lblRefFrames->setText("0");
installEventFilter(this);
}
FileComparatorWindow::~FileComparatorWindow()
{
removeEventFilter(this);
delete ui;
}
@@ -34,6 +37,24 @@ void FileComparatorWindow::closeEvent(QCloseEvent *event)
writeSettings();
}
bool FileComparatorWindow::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("filecomparison.html");
break;
}
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
return false;
}
void FileComparatorWindow::readSettings()
{
QSettings settings;
+1
View File
@@ -43,6 +43,7 @@ private:
void calculateDetails();
void showEvent(QShowEvent *);
void closeEvent(QCloseEvent *event);
bool eventFilter(QObject *obj, QEvent *event);
void readSettings();
void writeSettings();
};
+12 -5
View File
@@ -1,6 +1,7 @@
#include "flowviewwindow.h"
#include "ui_flowviewwindow.h"
#include "mainwindow.h"
#include "helpwindow.h"
const QColor FlowViewWindow::graphColors[8] = {Qt::blue, Qt::green, Qt::black, Qt::red, //0 1 2 3
Qt::gray, Qt::yellow, Qt::cyan, Qt::darkMagenta}; //4 5 6 7
@@ -193,6 +194,9 @@ bool FlowViewWindow::eventFilter(QObject *obj, QEvent *event)
case Qt::Key_Y:
btnFwdOneClick();
break;
case Qt::Key_F1:
HelpWindow::getRef()->showHelp("flowview.html");
break;
}
return true;
} else {
@@ -357,6 +361,9 @@ void FlowViewWindow::saveFileFlow()
void FlowViewWindow::updatedFrames(int numFrames)
{
QVector<double>newX[8];
QVector<double>newY[8];
CANFrame thisFrame;
if (numFrames == -1) //all frames deleted. Kill the display
{
@@ -409,18 +416,18 @@ void FlowViewWindow::updatedFrames(int numFrames)
if (ui->cbTimeGraph->isChecked())
{
if (secondsMode){
x[k].append((double)(thisFrame.timestamp) / 1000000.0);
newX[k].append((double)(thisFrame.timestamp) / 1000000.0);
}
else
{
x[k].append(thisFrame.timestamp);
newX[k].append(thisFrame.timestamp);
}
}
else
{
x[k].append(x[k].count());
newX[k].append(x[k].count());
}
y[k].append(thisFrame.data[k]);
newY[k].append(thisFrame.data[k]);
needRefresh = true;
}
}
@@ -436,7 +443,7 @@ void FlowViewWindow::updatedFrames(int numFrames)
{
for (int k = 0; k < 8; k++)
{
graphRef[k]->setData(x[k], y[k]);
graphRef[k]->addData(newX[k], newY[k]);
}
ui->graphView->replot();
updateDataView();
+21
View File
@@ -1,6 +1,7 @@
#include "frameinfowindow.h"
#include "ui_frameinfowindow.h"
#include "mainwindow.h"
#include "helpwindow.h"
#include <QtDebug>
FrameInfoWindow::FrameInfoWindow(const QVector<CANFrame> *frames, QWidget *parent) :
@@ -42,6 +43,7 @@ FrameInfoWindow::FrameInfoWindow(const QVector<CANFrame> *frames, QWidget *paren
ui->graphHistogram->setAntialiasedElements(QCP::aeNone);
}
installEventFilter(this);
}
void FrameInfoWindow::showEvent(QShowEvent* event)
@@ -56,8 +58,27 @@ void FrameInfoWindow::showEvent(QShowEvent* event)
}
}
bool FrameInfoWindow::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("framedetails.html");
break;
}
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
return false;
}
FrameInfoWindow::~FrameInfoWindow()
{
removeEventFilter(this);
delete ui;
}

Some files were not shown because too many files have changed in this diff Show More