Merge branch 'master' of github.com:collin80/SavvyCAN

This commit is contained in:
Collin Kidder
2024-07-09 20:20:54 -04:00
11 changed files with 225 additions and 26 deletions
+4 -3
View File
@@ -32,6 +32,7 @@ of this program. It can load and save in several formats:
9. Vehicle Spy log files 9. Vehicle Spy log files
10. CANDump / Kayak (Read only) 10. CANDump / Kayak (Read only)
11. PCAN Viewer (Read Only) 11. PCAN Viewer (Read Only)
12. Wireshark socketcan PCAP file (Read only)
## Dependencies ## Dependencies
@@ -47,9 +48,9 @@ to download it separately.
This project requires 5.14.0 or higher because of a dependency on QtSerialBus and other new additions to Qt. This project requires 5.14.0 or higher because of a dependency on QtSerialBus and other new additions to Qt.
NOTE: As the code in this master branch sits, it will not properly compile with QT6. NOTE: As the code in this master branch sits, it does compile with QT6. Support for QT6 is approximately "beta" quality. Most all functions should work, please send issues if found.
However, there is a QT6WIP branch that should successfully compile. The QT6WIP branch may not
function entirely properly yet. It appears that the current binary build for MacOS requires at least MacOS 10.15
## Instructions for compiling: ## Instructions for compiling:
+4
View File
@@ -4,6 +4,10 @@
# #
#------------------------------------------------- #-------------------------------------------------
!versionAtLeast(QT_VERSION, 5.14.0) {
error("Current version of Qt ($${QT_VERSION}) is too old, this project requires Qt 5.14 or newer")
}
QT = core gui printsupport qml serialbus serialport widgets help network opengl QT = core gui printsupport qml serialbus serialport widgets help network opengl
CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT
+20 -1
View File
@@ -491,7 +491,26 @@ void LAWICELSerial::readSerialData()
{ {
qDebug() << "Got CR!"; qDebug() << "Got CR!";
buildFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, QDateTime::currentMSecsSinceEpoch() * 1000l)); if (useSystemTime)
{
buildTimestamp = QDateTime::currentMSecsSinceEpoch() * 1000l;
}
else
{
//If total length is greater than command, header and data, timestamps must be enabled.
if (data.length() > (5 + mBuildLine.mid(4, 1).toInt() * 2 + 1))
{
//Four bytes after the end of the data bytes.
buildTimestamp = mBuildLine.mid(5 + mBuildLine.mid(4, 1).toInt() * 2, 4).toInt(nullptr, 16) * 1000l;
}
else
{
//Default to system time if timestamps are disabled.
buildTimestamp = QDateTime::currentMSecsSinceEpoch() * 1000l;
}
}
buildFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, buildTimestamp));
switch (mBuildLine[0].toLatin1()) switch (mBuildLine[0].toLatin1())
{ {
case 't': //standard frame case 't': //standard frame
+1
View File
@@ -61,6 +61,7 @@ protected:
QSerialPort *serial; QSerialPort *serial;
int framesRapid; int framesRapid;
CANFrame buildFrame; CANFrame buildFrame;
qint64 buildTimestamp;
bool can0Enabled; bool can0Enabled;
bool can0ListenOnly; bool can0ListenOnly;
bool canFd; bool canFd;
+2 -2
View File
@@ -214,12 +214,12 @@ QString DBC_SIGNAL::makePrettyOutput(double floatVal, int64_t intVal, bool outpu
} }
} }
if (!foundVal) outputString += QString::number(intVal); if (!foundVal) outputString += QString::number(intVal);
if (outputUnit) outputString += unitName; if (outputUnit) outputString += " " + unitName;
} }
else //otherwise display the actual number and unit (if it exists) else //otherwise display the actual number and unit (if it exists)
{ {
outputString += (isInteger ? QString::number(intVal) : QString::number(floatVal)); outputString += (isInteger ? QString::number(intVal) : QString::number(floatVal));
if (outputUnit) outputString += unitName; if (outputUnit) outputString += " " + unitName;
} }
return outputString; return outputString;
} }
+43
View File
@@ -674,6 +674,44 @@ bool DBCFile::parseSignalMultiplexValueLine(QString line)
return false; return false;
} }
bool DBCFile::parseSignalValueTypeLine(QString line)
{
QRegularExpression regex;
QRegularExpressionMatch match;
qDebug() << "Found a signal valtype line";
regex.setPattern("^SIG\\_VALTYPE\\_ *(\\d+) *([-\\w]+) *: *(\\d+);");
match = regex.match(line);
// captured 1 is the message id
// captured 2 is the signal name
// captured 3 is the valtype
if (!match.hasMatch()) { return false; }
uint32_t id = match.captured(1).toULong() & 0x1FFFFFFFUL;
DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toULong() & 0x1FFFFFFFUL);
if (msg == nullptr) { return false; }
DBC_SIGNAL *thisSignal = msg->sigHandler->findSignalByName(match.captured(2));
if (thisSignal == nullptr) { return false; }
int valType = match.captured(3).toInt();
switch (valType) {
case 1: {
thisSignal->valType = SP_FLOAT;
break;
}
case 2: {
thisSignal->valType = DP_FLOAT;
break;
}
default: {
return false;
}
}
return true;
}
bool DBCFile::parseValueLine(QString line) bool DBCFile::parseValueLine(QString line)
{ {
QRegularExpression regex; QRegularExpression regex;
@@ -941,6 +979,11 @@ bool DBCFile::loadFile(QString fileName)
if (!parseSignalMultiplexValueLine(line)) numSigFaults++; if (!parseSignalMultiplexValueLine(line)) numSigFaults++;
} }
if (line.startsWith("SIG_VALTYPE_ ")) //defines a signal value type
{
if (!parseSignalValueTypeLine(line)) numSigFaults++;
}
if (line.startsWith("BU_:")) //line specifies the nodes on this canbus if (line.startsWith("BU_:")) //line specifies the nodes on this canbus
{ {
qDebug() << "Found a BU line"; qDebug() << "Found a BU line";
+1
View File
@@ -106,6 +106,7 @@ private:
bool parseSignalMultiplexValueLine(QString line); bool parseSignalMultiplexValueLine(QString line);
DBC_MESSAGE* parseMessageLine(QString line); DBC_MESSAGE* parseMessageLine(QString line);
bool parseValueLine(QString line); bool parseValueLine(QString line);
bool parseSignalValueTypeLine(QString line);
bool parseAttributeLine(QString line); bool parseAttributeLine(QString line);
bool parseDefaultAttrLine(QString line); bool parseDefaultAttrLine(QString line);
}; };
+125 -14
View File
@@ -191,6 +191,7 @@ bool FrameFileIO::loadFrameFile(QString &fileName, QVector<CANFrame>* frameCache
filters.append(QString(tr("CLX000 (*.txt *.TXT)"))); filters.append(QString(tr("CLX000 (*.txt *.TXT)")));
filters.append(QString(tr("CANServer Binary Log (*.log *.LOG)"))); filters.append(QString(tr("CANServer Binary Log (*.log *.LOG)")));
filters.append(QString(tr("Wireshark (*.pcap *.PCAP *.pcapng *.PCAPNG)"))); filters.append(QString(tr("Wireshark (*.pcap *.PCAP *.pcapng *.PCAPNG)")));
filters.append(QString(tr("Wireshark SocketCAN (*.pcap *.PCAP")));
dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString());
dialog.setFileMode(QFileDialog::ExistingFile); dialog.setFileMode(QFileDialog::ExistingFile);
@@ -237,6 +238,7 @@ bool FrameFileIO::loadFrameFile(QString &fileName, QVector<CANFrame>* frameCache
if (selectedNameFilter == filters[22]) result = loadCLX000File(filename, frameCache); if (selectedNameFilter == filters[22]) result = loadCLX000File(filename, frameCache);
if (selectedNameFilter == filters[23]) result = loadCANServerFile(filename, frameCache); if (selectedNameFilter == filters[23]) result = loadCANServerFile(filename, frameCache);
if (selectedNameFilter == filters[24]) result = loadWiresharkFile(filename, frameCache); if (selectedNameFilter == filters[24]) result = loadWiresharkFile(filename, frameCache);
if (selectedNameFilter == filters[25]) result = loadWiresharkSocketCANFile(filename, frameCache);
progress.cancel(); progress.cancel();
@@ -288,6 +290,28 @@ bool FrameFileIO::autoDetectLoadFile(QString filename, QVector<CANFrame>* frames
} }
} }
// Attempt to load socket CAN first to avoid generic wireshark logic catching it
qDebug() << "Attempting Wireshark Socket CAN Log";
if (isWiresharkSocketCANFile(filename))
{
if (loadWiresharkSocketCANFile(filename, frames))
{
qDebug() << "Loaded as Wireshark SocketCAN Log successfully!";
return true;
}
}
// This and the decoder above were both moved above TeslaAPFile as they match based on magic numbers and sometimes these files were falling into the TeslaAP decoder
qDebug() << "Attempting Wireshark Log";
if (isWiresharkFile(filename))
{
if (loadWiresharkFile(filename, frames))
{
qDebug() << "Loaded as Wireshark Log successfully!";
return true;
}
}
qDebug() << "Attempting Tesla AP Snapshot"; qDebug() << "Attempting Tesla AP Snapshot";
if (isTeslaAPFile(filename)) if (isTeslaAPFile(filename))
{ {
@@ -308,16 +332,6 @@ bool FrameFileIO::autoDetectLoadFile(QString filename, QVector<CANFrame>* frames
} }
} }
qDebug() << "Attempting Wireshark Log";
if (isWiresharkFile(filename))
{
if (loadWiresharkFile(filename, frames))
{
qDebug() << "Loaded as Wireshark Log successfully!";
return true;
}
}
qDebug() << "Attempting canalyzer ASC"; qDebug() << "Attempting canalyzer ASC";
if (isCanalyzerASC(filename)) if (isCanalyzerASC(filename))
{ {
@@ -5023,7 +5037,7 @@ bool FrameFileIO::loadWiresharkFile(QString filename, QVector<CANFrame>* frames)
QByteArray ba = filename.toLocal8Bit(); QByteArray ba = filename.toLocal8Bit();
pcap_data_file = pcap_open_offline(ba.data(), errbuf); pcap_data_file = pcap_open_offline(ba.data(), errbuf, PCAP_LINKTYPE_ANY);
if (!pcap_data_file) { if (!pcap_data_file) {
return false; return false;
} }
@@ -5050,9 +5064,13 @@ bool FrameFileIO::loadWiresharkFile(QString filename, QVector<CANFrame>* frames)
thisFrame.isReceived = true; // TODO: check if tx detection is possible thisFrame.isReceived = true; // TODO: check if tx detection is possible
thisFrame.setFrameType(QCanBusFrame::DataFrame); thisFrame.setFrameType(QCanBusFrame::DataFrame);
if ((0x80 & *(packetData+19))) {
thisFrame.setExtendedFrameFormat(true);
thisFrame.setFrameId((0x3f & *(packetData+19))<<24 | (0xff & *(packetData+18)) << 16 | (0xff & *(packetData+17)) << 8 | (0xff & *(packetData+16)));
} else {
thisFrame.setExtendedFrameFormat(false);
thisFrame.setFrameId((0xff & *(packetData+17)) << 8 | (0xff & *(packetData+16))); thisFrame.setFrameId((0xff & *(packetData+17)) << 8 | (0xff & *(packetData+16)));
if (thisFrame.frameId() <= 0x7FF) thisFrame.setExtendedFrameFormat(false); }
else thisFrame.setExtendedFrameFormat(true);
thisFrame.bus = 0; thisFrame.bus = 0;
int numBytes = *(packetData+20); int numBytes = *(packetData+20);
QByteArray bytes(numBytes, 0); QByteArray bytes(numBytes, 0);
@@ -5079,7 +5097,7 @@ bool FrameFileIO::isWiresharkFile(QString filename)
char errbuf[PCAP_ERRBUF_SIZE]; char errbuf[PCAP_ERRBUF_SIZE];
QByteArray ba = filename.toLocal8Bit(); QByteArray ba = filename.toLocal8Bit();
pcap_data_file = pcap_open_offline(ba.data(), errbuf); pcap_data_file = pcap_open_offline(ba.data(), errbuf, PCAP_LINKTYPE_ANY);
if (!pcap_data_file) { if (!pcap_data_file) {
return false; return false;
} }
@@ -5089,3 +5107,96 @@ bool FrameFileIO::isWiresharkFile(QString filename)
return true; return true;
} }
bool FrameFileIO::loadWiresharkSocketCANFile(QString filename, QVector<CANFrame>* frames)
{
pcap_t *pcap_data_file;
CANFrame thisFrame;
long long startTimestamp = 0;
long long timeStamp;
int lineCounter = 0;
bool foundErrors = false;
pcap_pkthdr packetHeader;
const char *packetData = NULL;
char errbuf[PCAP_ERRBUF_SIZE];
QByteArray ba = filename.toLocal8Bit();
pcap_data_file = pcap_open_offline(ba.data(), errbuf, PCAP_LINKTYPE_SOCKETCAN);
if (!pcap_data_file) {
return false;
}
packetData = (const char*)pcap_next(pcap_data_file, &packetHeader);
while (packetData) {
lineCounter++;
if (lineCounter > 100) {
qApp->processEvents();
lineCounter = 0;
}
thisFrame.bus = 0;
// Timestamp
timeStamp = packetHeader.ts.tv_sec * 1000000 + packetHeader.ts.tv_usec;
if (0 == startTimestamp) {
startTimestamp = timeStamp;
}
timeStamp -= startTimestamp;
thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, timeStamp));
// ID and extended frame format
const quint32 can_id = qFromBigEndian<quint32>(packetData);
if (can_id & 0x80000000) {
thisFrame.setExtendedFrameFormat(true);
thisFrame.setFrameId(0x1fffffff & can_id);
} else {
thisFrame.setExtendedFrameFormat(false);
thisFrame.setFrameId(0x7ff & can_id);
}
// Frame type
if (can_id & 0x20000000U) {
thisFrame.setFrameType(QCanBusFrame::ErrorFrame);
} else if (can_id & 0x40000000U) {
thisFrame.setFrameType(QCanBusFrame::RemoteRequestFrame);
} else {
thisFrame.setFrameType(QCanBusFrame::DataFrame);
}
// Direction - This isn't actually officially supported, but CAN Bus Debugger device logs set this byte to 1 to indicate a TX frame and 0 for RX
quint8 direction = (quint8) *(packetData + 6);
thisFrame.isReceived = (direction != 1);
// Data
quint8 numBytes = (quint8) *(packetData + 4);
if (numBytes > 8) {
numBytes = 8;
}
QByteArray bytes(numBytes, 0);
for (int d = 0; d < numBytes; d++) {
bytes[d] = *(packetData + 8 + d);
}
thisFrame.setPayload(bytes);
frames->append(thisFrame);
packetData = (const char*) pcap_next(pcap_data_file, &packetHeader);
}
pcap_close(pcap_data_file);
pcap_data_file = NULL;
return !foundErrors;
}
bool FrameFileIO::isWiresharkSocketCANFile(QString filename)
{
pcap_t *pcap_data_file;
char errbuf[PCAP_ERRBUF_SIZE];
QByteArray ba = filename.toLocal8Bit();
pcap_data_file = pcap_open_offline(ba.data(), errbuf, PCAP_LINKTYPE_SOCKETCAN);
if (!pcap_data_file) {
return false;
}
pcap_close(pcap_data_file);
pcap_data_file = NULL;
return true;
}
+2
View File
@@ -54,6 +54,7 @@ public:
static bool loadCLX000File(QString filename, QVector<CANFrame>* frames); static bool loadCLX000File(QString filename, QVector<CANFrame>* frames);
static bool loadCANServerFile(QString filename, QVector<CANFrame>* frames); static bool loadCANServerFile(QString filename, QVector<CANFrame>* frames);
static bool loadWiresharkFile(QString filename, QVector<CANFrame>* frames); static bool loadWiresharkFile(QString filename, QVector<CANFrame>* frames);
static bool loadWiresharkSocketCANFile(QString filename, QVector<CANFrame>* frames);
//functions that pre-scan a file to try to figure out if they could read it. Used to automatically determine //functions that pre-scan a file to try to figure out if they could read it. Used to automatically determine
//file type and load it. //file type and load it.
@@ -80,6 +81,7 @@ public:
static bool isCLX000File(QString filename); static bool isCLX000File(QString filename);
static bool isCANServerFile(QString filename); static bool isCANServerFile(QString filename);
static bool isWiresharkFile(QString filename); static bool isWiresharkFile(QString filename);
static bool isWiresharkSocketCANFile(QString filename);
static bool saveCRTDFile(QString, const QVector<CANFrame>*); static bool saveCRTDFile(QString, const QVector<CANFrame>*);
static bool saveNativeCSVFile(QString, const QVector<CANFrame>*); static bool saveNativeCSVFile(QString, const QVector<CANFrame>*);
+18 -3
View File
@@ -19,7 +19,7 @@
static unsigned char pcap_buffer[MAX_CAN_PACKET_SIZE]; static unsigned char pcap_buffer[MAX_CAN_PACKET_SIZE];
static pcap_t p; static pcap_t p;
pcap *pcap_open_offline(const char *filename, char *error_text) { pcap *pcap_open_offline(const char *filename, char *error_text, int expected_link_type) {
FILE *file; FILE *file;
snprintf(error_text, PCAP_ERRBUF_SIZE, "OK"); snprintf(error_text, PCAP_ERRBUF_SIZE, "OK");
@@ -48,6 +48,23 @@ pcap *pcap_open_offline(const char *filename, char *error_text) {
return (NULL); return (NULL);
} }
unsigned int link_type;
fseek(file, PCAP_FILE_HEADER_LENGTH - 4, SEEK_SET);
bytes_read = fread(&link_type, 1, sizeof(link_type), file);
if (bytes_read != sizeof(link_type)) {
snprintf(error_text, PCAP_ERRBUF_SIZE, "Cannot read linktype word");
fclose(file);
return (NULL);
}
if (expected_link_type >= 0) {
// Check the link type
if ((int) link_type != expected_link_type) {
snprintf(error_text, PCAP_ERRBUF_SIZE, "This link type is not supported by this decoder");
fclose(file);
return (NULL);
}
}
// set the format // set the format
// and seek past file header // and seek past file header
if (MAGIC_NG == magic) { if (MAGIC_NG == magic) {
@@ -61,14 +78,12 @@ pcap *pcap_open_offline(const char *filename, char *error_text) {
return (NULL); return (NULL);
} }
fseek(file, section_length, SEEK_SET); fseek(file, section_length, SEEK_SET);
} else { } else {
p.is_ng = 0; p.is_ng = 0;
fseek(file, PCAP_FILE_HEADER_LENGTH, SEEK_SET); fseek(file, PCAP_FILE_HEADER_LENGTH, SEEK_SET);
} }
p.file = file; p.file = file;
return(&p); return(&p);
} }
+4 -2
View File
@@ -8,7 +8,9 @@
#include <winsock.h> #include <winsock.h>
#endif #endif
#define PCAP_ERRBUF_SIZE 256 #define PCAP_ERRBUF_SIZE (256)
#define PCAP_LINKTYPE_SOCKETCAN (227)
#define PCAP_LINKTYPE_ANY (-1)
struct pcap_pkthdr { struct pcap_pkthdr {
struct timeval ts; /* time stamp */ struct timeval ts; /* time stamp */
@@ -23,7 +25,7 @@ struct pcap {
typedef struct pcap pcap_t; typedef struct pcap pcap_t;
pcap *pcap_open_offline(const char *, char *); pcap *pcap_open_offline(const char *, char *, int);
const unsigned char *pcap_next(pcap_t *, struct pcap_pkthdr *); const unsigned char *pcap_next(pcap_t *, struct pcap_pkthdr *);