diff --git a/README.md b/README.md index 0637898..27b7b88 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,13 @@ of this program. It can load and save in several formats: 5. Generic CSV file (ID,D0 D1 D2 D3 D4 D5 D6 D7) +Requires QScintilla library available at: + +https://www.riverbankcomputing.com/software/qscintilla/download + +Also uses QCustomPlot available at: + +http://www.qcustomplot.com/ + +However, this source code is integrated into the source for SavvyCAN and one isn't required to download it separately. + diff --git a/SavvyCAN.pro b/SavvyCAN.pro index d2a0dde..40140e3 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -6,11 +6,11 @@ QT += core gui -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets serialport printsupport network +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets serialport printsupport qml CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT -CONFIG += c++11 +CONFIG += c++11 qscintilla2 TARGET = SavvyCAN TEMPLATE = app @@ -37,7 +37,11 @@ SOURCES += main.cpp\ mainsettingsdialog.cpp \ firmwareuploaderwindow.cpp \ discretestatewindow.cpp \ - connectionwindow.cpp + connectionwindow.cpp \ + scriptingwindow.cpp \ + scriptcontainer.cpp \ + canfilter.cpp \ + rangestatewindow.cpp HEADERS += mainwindow.h \ can_structs.h \ @@ -63,7 +67,11 @@ HEADERS += mainwindow.h \ mainsettingsdialog.h \ firmwareuploaderwindow.h \ discretestatewindow.h \ - connectionwindow.h + connectionwindow.h \ + scriptingwindow.h \ + scriptcontainer.h \ + canfilter.h \ + rangestatewindow.h FORMS += mainwindow.ui \ graphingwindow.ui \ @@ -79,7 +87,9 @@ FORMS += mainwindow.ui \ mainsettingsdialog.ui \ firmwareuploaderwindow.ui \ discretestatewindow.ui \ - connectionwindow.ui + connectionwindow.ui \ + scriptingwindow.ui \ + rangestatewindow.ui DISTFILES += diff --git a/can_structs.h b/can_structs.h index f230964..92e4491 100644 --- a/can_structs.h +++ b/can_structs.h @@ -9,6 +9,7 @@ public: int ID; int bus; bool extended; + bool isReceived; //did we receive this or send it? int len; unsigned char data[8]; uint64_t timestamp; diff --git a/canfilter.cpp b/canfilter.cpp new file mode 100644 index 0000000..dec357f --- /dev/null +++ b/canfilter.cpp @@ -0,0 +1,27 @@ +#include "canfilter.h" + +CANFilter::CANFilter() +{ + ID = 0; + mask = 0; + bus = -1; +} + +void CANFilter::setFilter(uint32_t id, uint32_t mask, int bus) +{ + this->ID = id; + this->mask = mask; + this->bus = bus; +} + +bool CANFilter::checkFilter(uint32_t id, int bus) +{ + if (bus == -1 || bus == this->bus) + { + uint32_t result = id & this->mask; + if (result == this->ID) return true; + } + return false; + +} + diff --git a/canfilter.h b/canfilter.h new file mode 100644 index 0000000..91a84e3 --- /dev/null +++ b/canfilter.h @@ -0,0 +1,19 @@ +#ifndef CANFILTER_H +#define CANFILTER_H + +#include + +class CANFilter +{ +public: + CANFilter(); + void setFilter(uint32_t id, uint32_t mask, int bus); + bool checkFilter(uint32_t id, int bus); + +private: + uint32_t ID; + uint32_t mask; + int bus; +}; + +#endif // CANFILTER_H diff --git a/canframemodel.cpp b/canframemodel.cpp index e62766c..beb978a 100644 --- a/canframemodel.cpp +++ b/canframemodel.cpp @@ -17,7 +17,7 @@ int CANFrameModel::totalFrameCount() int CANFrameModel::columnCount(const QModelIndex &index) const { Q_UNUSED(index); - return 6; + return 7; } CANFrameModel::CANFrameModel(QObject *parent) @@ -159,7 +159,7 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const if (!index.isValid()) return QVariant(); - if (index.row() >= filteredFrames.count()) + if (index.row() >= (filteredFrames.count())) return QVariant(); if (role == Qt::DisplayRole) { @@ -168,7 +168,7 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const { case 0: //timestamp if (!timeSeconds) return QString::number(thisFrame.timestamp); - else return QString::number(thisFrame.timestamp / 1000000.0f); + else return QString::number((double)thisFrame.timestamp / 1000000.0, 'f', 6); break; case 1: //id return Utility::formatNumber(thisFrame.ID); @@ -176,13 +176,17 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const case 2: //ext return QString::number(thisFrame.extended); break; - case 3: //bus + case 3: //direction + if (thisFrame.isReceived) return QString(tr("Rx")); + else return QString(tr("Tx")); + break; + case 4: //bus return QString::number(thisFrame.bus); break; - case 4: //len + case 5: //len return QString::number(thisFrame.len); break; - case 5: //data + case 6: //data for (int i = 0; i < thisFrame.len; i++) { tempString.append(Utility::formatNumber(thisFrame.data[i])); @@ -234,12 +238,15 @@ QVariant CANFrameModel::headerData(int section, Qt::Orientation orientation, return QString(tr("Ext")); break; case 3: - return QString(tr("Bus")); + return QString(tr("Dir")); break; case 4: - return QString(tr("Len")); + return QString(tr("Bus")); break; case 5: + return QString(tr("Len")); + break; + case 6: return QString(tr("Data")); break; } @@ -251,25 +258,27 @@ QVariant CANFrameModel::headerData(int section, Qt::Orientation orientation, return QVariant(); } -void CANFrameModel::addFrame(CANFrame &frame, bool autoRefresh = false) +void CANFrameModel::addFrame(const CANFrame &frame, bool autoRefresh = false) { mutex.lock(); - frame.timestamp -= timeOffset; + CANFrame tempFrame; + tempFrame = frame; + tempFrame.timestamp -= timeOffset; //if this ID isn't found in the filters list then add it and show it by default - if (!filters.contains(frame.ID)) + if (!filters.contains(tempFrame.ID)) { - filters.insert(frame.ID, true); + filters.insert(tempFrame.ID, true); needFilterRefresh = true; } if (!overwriteDups) { - frames.append(frame); - if (filters[frame.ID]) + frames.append(tempFrame); + if (filters[tempFrame.ID]) { if (autoRefresh) beginInsertRows(QModelIndex(), filteredFrames.count() + 1, filteredFrames.count() + 1); - filteredFrames.append(frame); + filteredFrames.append(tempFrame); if (autoRefresh) endInsertRows(); } } @@ -278,20 +287,20 @@ void CANFrameModel::addFrame(CANFrame &frame, bool autoRefresh = false) bool found = false; for (int i = 0; i < frames.count(); i++) { - if (frames[i].ID == frame.ID) + if (frames[i].ID == tempFrame.ID) { - frames.replace(i, frame); + frames.replace(i, tempFrame); found = true; break; } } if (!found) { - frames.append(frame); - if (filters[frame.ID]) + frames.append(tempFrame); + if (filters[tempFrame.ID]) { if (autoRefresh) beginInsertRows(QModelIndex(), filteredFrames.count() + 1, filteredFrames.count() + 1); - filteredFrames.append(frame); + filteredFrames.append(tempFrame); if (autoRefresh) endInsertRows(); } } @@ -299,10 +308,10 @@ void CANFrameModel::addFrame(CANFrame &frame, bool autoRefresh = false) { for (int j = 0; j < filteredFrames.count(); j++) { - if (filteredFrames[j].ID == frame.ID) + if (filteredFrames[j].ID == tempFrame.ID) { if (autoRefresh) beginResetModel(); - filteredFrames.replace(j, frame); + filteredFrames.replace(j, tempFrame); if (autoRefresh) endResetModel(); } } @@ -383,6 +392,11 @@ void CANFrameModel::clearFrames() */ void CANFrameModel::insertFrames(const QVector &newFrames) { + //not resetting the model here because the serial worker automatically does a bulk refresh every 1/4 second + //and that refresh will cause the view to update. If you do both it usually ends up thinking you have + //double the number of frames. + //beginResetModel(); + mutex.lock(); int insertedFiltered = 0; for (int i = 0; i < newFrames.count(); i++) { @@ -398,9 +412,10 @@ void CANFrameModel::insertFrames(const QVector &newFrames) filteredFrames.append(newFrames[i]); } } - - beginInsertRows(QModelIndex(), filteredFrames.count() + 1, filteredFrames.count() + insertedFiltered); - endInsertRows(); + mutex.unlock(); + //endResetModel(); + //beginInsertRows(QModelIndex(), filteredFrames.count() + 1, filteredFrames.count() + insertedFiltered); + //endInsertRows(); if (needFilterRefresh) emit updatedFiltersList(); } diff --git a/canframemodel.h b/canframemodel.h index 98a3d5c..764c073 100644 --- a/canframemodel.h +++ b/canframemodel.h @@ -23,7 +23,7 @@ public: int columnCount(const QModelIndex &) const; int totalFrameCount(); - void addFrame(CANFrame &, bool); + void addFrame(const CANFrame &, bool); void sendRefresh(); void sendRefresh(int); void sendBulkRefresh(int); diff --git a/config.h b/config.h index d70123e..4c832a0 100644 --- a/config.h +++ b/config.h @@ -1,12 +1,12 @@ #ifndef CONFIG #define CONFIG -#define VERSION 140 +#define VERSION 145 //try to keep this in sync. //SavvyCAN will complain if you connect a GVRET board with a revision //less than this number. -#define CURRENT_GVRET_VER 325 //330 is the newest but it is WIP +#define CURRENT_GVRET_VER 331 #endif // CONFIG diff --git a/dbchandler.cpp b/dbchandler.cpp index db6257e..b39e35e 100644 --- a/dbchandler.cpp +++ b/dbchandler.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "utility.h" DBCHandler::DBCHandler(QObject *parent) : QObject(parent) { @@ -538,8 +539,7 @@ QString DBCHandler::processSignal(const CANFrame &frame, const DBC_SIGNAL &sig) { int64_t result = 0; - int bit; - int sigSize; + bool isSigned = false; if (sig.valType == STRING) { QString buildString; @@ -547,62 +547,10 @@ QString DBCHandler::processSignal(const CANFrame &frame, const DBC_SIGNAL &sig) int bytes = sig.signalSize / 8; for (int x = 0; x < bytes; x++) buildString.append(frame.data[startByte + x]); return buildString; - } + } - sigSize = sig.signalSize; - - if (sig.intelByteOrder) - { - bit = sig.startBit; - for (int bitpos = 0; bitpos < sigSize; bitpos++) - { - if (frame.data[bit / 8] & (1 << (bit % 8))) - result += (1ULL << bitpos); - - bit++; - } - } - else //motorola / big endian mode - { - bit = sig.startBit; - for (int bitpos = 0; bitpos < sigSize; bitpos++) - { - if (frame.data[bit / 8] & (1 << (bit % 8))) - result += (1ULL << (sigSize - bitpos - 1)); - - if ((bit % 8) == 0) - bit += 15; - else bit--; - - } - } - - if (sig.valType == SIGNED_INT) - { - int mask = (1 << (sig.signalSize - 1)); - if ((result & mask) == mask) //is the highest bit possible for this signal size set? - { - /* - * if so we need to also set every bit higher in the result int too. - * This leads to the below two lines that are nasty. Here's the theory behind that... - * If the value is signed and the highest bit is set then it is negative. To create - * a negative value out of this even though the variable result is 64 bit we have to - * run 1's all of the way up to bit 63 in result. -1 is all ones for whatever size integer - * you have. So, it's 64 1's in this case. - * signedMask is done this way: - * first you take the signal size and shift 1 up that far. Then subtract one. Lets - * see that for a 16 bit signal: - * (1 << 16) - 1 = the first 16 bits set as 1's. So far so good. We then negate the whole - * thing which flips all bits. Thus signedMask ends up with 1's everwhere that the signal - * doesn't take up in the 64 bit signed integer result. Then, result has an OR operation on - * it with the old value and -1 masked so that the the 1 bits from -1 don't overwrite bits from the - * actual signal. This extends the sign bits out so that the integer result reads as the proper negative - * value. We dont need to do any of this if the sign bit wasn't set. - */ - int signedMask = ~((1 << sig.signalSize) - 1); - result = (-1 & signedMask) | result; - } - } + if (sig.valType == SIGNED_INT) isSigned = true; + result = Utility::processIntegerSignal(frame.data, sig.startBit, sig.signalSize, sig.intelByteOrder, isSigned); double endResult = ((double)result * sig.factor) + sig.bias; result = (int) endResult; diff --git a/flowviewwindow.ui b/flowviewwindow.ui index a3b3ddf..81ddf9f 100644 --- a/flowviewwindow.ui +++ b/flowviewwindow.ui @@ -799,7 +799,7 @@ - Trigger Values + Data Seek Values diff --git a/framefileio.cpp b/framefileio.cpp index de72df4..38bd344 100644 --- a/framefileio.cpp +++ b/framefileio.cpp @@ -19,6 +19,7 @@ QString FrameFileIO::loadFrameFile(QVector* frameCache) filters.append(QString(tr("Generic ID/Data CSV (*.csv)"))); filters.append(QString(tr("BusMaster Log (*.log)"))); filters.append(QString(tr("Microchip Log (*.can)"))); + filters.append(QString(tr("Vector trace files (*.trace)"))); dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilters(filters); @@ -43,6 +44,7 @@ QString FrameFileIO::loadFrameFile(QVector* frameCache) if (dialog.selectedNameFilter() == filters[2]) result = loadGenericCSVFile(filename, frameCache); if (dialog.selectedNameFilter() == filters[3]) result = loadLogFile(filename, frameCache); if (dialog.selectedNameFilter() == filters[4]) result = loadMicrochipFile(filename, frameCache); + if (dialog.selectedNameFilter() == filters[5]) result = loadTraceFile(filename, frameCache); progress.cancel(); @@ -92,7 +94,7 @@ bool FrameFileIO::loadCRTDFile(QString filename, QVector* frames) return false; } - line = inFile->readLine(); //read out the header first and discard it. + line = inFile->readLine().toUpper(); //read out the header first and discard it. while (!inFile->atEnd()) { lineCounter++; @@ -119,11 +121,14 @@ bool FrameFileIO::loadCRTDFile(QString filename, QVector* frames) } //qDebug() << "decimal places " << decimalPlaces; thisFrame.timestamp = (int64_t)(tokens[0].toDouble() * multiplier); - if (tokens[1] == "R11" || tokens[1] == "R29") + char firstChar = tokens[1].left(1)[0]; + if (firstChar == 'R' || firstChar == 'T') { thisFrame.ID = tokens[2].toInt(NULL, 16); - if (tokens[1] == "R29") thisFrame.extended = true; + if (tokens[1] == "R29" || tokens[1] == "T29") thisFrame.extended = true; else thisFrame.extended = false; + if (firstChar == 'T') thisFrame.isReceived = false; + else thisFrame.isReceived = true; thisFrame.bus = 0; thisFrame.len = tokens.length() - 3; for (int d = 0; d < thisFrame.len; d++) @@ -169,11 +174,15 @@ bool FrameFileIO::saveCRTDFile(QString filename, const QVector* frames outFile->write(QString::number(frames->at(c).timestamp / 1000000.0, 'f', 6).toUtf8()); outFile->putChar(' '); + + if (frames->at(c).isReceived) outFile->putChar('R'); + else outFile->putChar('T'); + if (frames->at(c).extended) { - outFile->write("R29 "); + outFile->write("29 "); } - else outFile->write("R11 "); + else outFile->write("11 "); outFile->write(QString::number(frames->at(c).ID, 16).toUpper().rightJustified(8, '0').toUtf8()); outFile->putChar(' '); @@ -197,6 +206,7 @@ bool FrameFileIO::loadNativeCSVFile(QString filename, QVector* frames) QFile *inFile = new QFile(filename); CANFrame thisFrame; QByteArray line; + int fileVersion = 1; long long timeStamp = Utility::GetTimeMS(); int lineCounter = 0; @@ -206,7 +216,8 @@ bool FrameFileIO::loadNativeCSVFile(QString filename, QVector* frames) return false; } - line = inFile->readLine(); //read out the header first and discard it. + line = inFile->readLine().toUpper(); //read out the header first and discard it. + if (line.at(23) == 'D') fileVersion = 2; //Dir is found starting at position 23 if this is a V2 file while (!inFile->atEnd()) { lineCounter++; @@ -234,11 +245,27 @@ bool FrameFileIO::loadNativeCSVFile(QString filename, QVector* frames) thisFrame.ID = tokens[1].toInt(NULL, 16); if (tokens[2].toUpper().contains("TRUE")) thisFrame.extended = 1; else thisFrame.extended = 0; - thisFrame.bus = tokens[3].toInt(); - thisFrame.len = tokens[4].toInt(); - for (int c = 0; c < 8; c++) thisFrame.data[c] = 0; - for (int d = 0; d < thisFrame.len; d++) - thisFrame.data[d] = tokens[5 + d].toInt(NULL, 16); + + if (fileVersion == 1) + { + thisFrame.isReceived = true; + thisFrame.bus = tokens[3].toInt(); + thisFrame.len = tokens[4].toInt(); + for (int c = 0; c < 8; c++) thisFrame.data[c] = 0; + for (int d = 0; d < thisFrame.len; d++) + thisFrame.data[d] = tokens[5 + d].toInt(NULL, 16); + } + else if (fileVersion == 2) + { + if (tokens[3].at(0) == 'R') thisFrame.isReceived = true; + else thisFrame.isReceived = false; + thisFrame.bus = tokens[4].toInt(); + thisFrame.len = tokens[5].toInt(); + for (int c = 0; c < 8; c++) thisFrame.data[c] = 0; + for (int d = 0; d < thisFrame.len; d++) + thisFrame.data[d] = tokens[6 + d].toInt(NULL, 16); + } + frames->append(thisFrame); } } @@ -258,7 +285,7 @@ bool FrameFileIO::saveNativeCSVFile(QString filename, const QVector* f return false; } - outFile->write("Time Stamp,ID,Extended,Bus,LEN,D1,D2,D3,D4,D5,D6,D7,D8"); + outFile->write("Time Stamp,ID,Extended,Dir,Bus,LEN,D1,D2,D3,D4,D5,D6,D7,D8"); outFile->write("\n"); for (int c = 0; c < frames->count(); c++) @@ -279,6 +306,9 @@ bool FrameFileIO::saveNativeCSVFile(QString filename, const QVector* f if (frames->at(c).extended) outFile->write("true,"); else outFile->write("false,"); + if (frames->at(c).isReceived) outFile->write("Rx,"); + else outFile->write("Tx,"); + outFile->write(QString::number(frames->at(c).bus).toUtf8()); outFile->putChar(44); @@ -447,7 +477,7 @@ bool FrameFileIO::loadLogFile(QString filename, QVector* frames) lineCounter = 0; } - line = inFile->readLine(); + line = inFile->readLine().toUpper(); if (line.startsWith("***")) continue; if (line.length() > 1) { @@ -456,6 +486,8 @@ bool FrameFileIO::loadLogFile(QString filename, QVector* frames) timeStamp = (timeToks[0].toInt() * (1000ul * 1000ul * 60ul * 60ul)) + (timeToks[1].toInt() * (1000ul * 1000ul * 60ul)) + (timeToks[2].toInt() * (1000ul * 1000ul)) + (timeToks[3].toInt() * 100ul); thisFrame.timestamp = timeStamp; + if (tokens[1].at(0) == 'R') thisFrame.isReceived = true; + else thisFrame.isReceived = false; thisFrame.ID = tokens[3].right(tokens[3].length() - 2).toInt(NULL, 16); if (tokens[4] == "s") thisFrame.extended = false; else thisFrame.extended = true; @@ -511,7 +543,8 @@ bool FrameFileIO::saveLogFile(QString filename, const QVector* frames) tempStamp = timestamp.addMSecs(frames->at(c).timestamp / 1000); outFile->write(tempStamp.toString("h:m:s:z").toUtf8()); - outFile->write(" Rx "); + if (frames->at(c).isReceived) outFile->write(" Rx "); + else outFile->write(" Tx "); outFile->write(QString::number(frames->at(c).bus).toUtf8() + " "); outFile->write(QString::number(frames->at(c).ID, 16).toUpper().rightJustified(8, '0').toUtf8()); if (frames->at(c).extended) outFile->write(" x "); @@ -547,7 +580,7 @@ bool FrameFileIO::loadMicrochipFile(QString filename, QVector* frames) CANFrame thisFrame; QByteArray line; bool inComment = false; - long long timeStamp = Utility::GetTimeMS(); + long long timeStamp; int lineCounter = 0; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) @@ -578,9 +611,10 @@ bool FrameFileIO::loadMicrochipFile(QString filename, QVector* frames) if (!inComment) { QList tokens = line.split(';'); - timeStamp += 5; + timeStamp = tokens[0].toInt() * 1000; thisFrame.timestamp = timeStamp; - + if (tokens[1].at(0) == 'R') thisFrame.isReceived = true; + else thisFrame.isReceived = false; thisFrame.ID = Utility::ParseStringToNum(tokens[2]); if (thisFrame.ID <= 0x7FF) thisFrame.extended = false; else thisFrame.extended = true; @@ -641,7 +675,8 @@ bool FrameFileIO::saveMicrochipFile(QString filename, const QVector* f } outFile->write(QString::number((int)(frames->at(c).timestamp / 1000)).toUtf8()); - outFile->write(";RX;"); + if (frames->at(c).isReceived) outFile->write(";RX;"); + else outFile->write(";TX;"); outFile->write("0x" + QString::number(frames->at(c).ID, 16).toUpper().rightJustified(8, '0').toUtf8() + ";"); outFile->write(QString::number(frames->at(c).len).toUtf8() + ";"); @@ -658,3 +693,182 @@ bool FrameFileIO::saveMicrochipFile(QString filename, const QVector* f delete outFile; return true; } + + +/* +; CAN Logger trace file +; Device Serial Number : +; Start Time : Tue, Nov 17, 2015 :: 11:19:59 +; +; Column description : +; ~~~~~~~~~~~~~~~~~~~~~ +; +; + Message Number +; | +; | + Time Stamp (ms) +; | | +; | | + Message ID (hex) +; | | | +; | | | + Data Length Code +; | | | | +; | | | | + Data Bytes (hex) +; | | | | | +;---+----- -----+------ ----+--- + -+ -- -- -- -- -- -- -- + 0 00:00:00:0008 00000268 8 55 00 84 03 A8 0D 38 00 + 1 00:00:00:0021 0000000E 8 1F D3 3F FF 08 FF E0 CB + +It appears that all lines that start in a semicolon are comments +The rest of the lines need the whitespace stripped from beginning and end +and then it appears to be tab delimited. The first field is a sequence number +and irrelevant. The second field is a funky timestamp, then message id, etc as +shown in the file comments. The bytes seem to be space delimited and in hex +*/ + +bool FrameFileIO::loadTraceFile(QString filename, QVector* frames) +{ + QFile *inFile = new QFile(filename); + CANFrame thisFrame; + QByteArray line; + long long timeStamp = 0; + int lineCounter = 0; + + 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(); + line = line.trimmed(); + if (line.length() > 2) + { + if (line.startsWith(";")) + { + // a comment. Ignore it. + } + else + { + QList tokens = line.split('\t'); + + QList timestampToks = tokens[1].split(':'); + + timeStamp = timestampToks[0].toInt() * 1000000ul * 60 * 60; + timeStamp += timestampToks[1].toInt() * 1000000ul * 60; + timeStamp += timestampToks[2].toInt() * 1000000ul; + timeStamp += timestampToks[3].toInt() * 100; + + thisFrame.timestamp = timeStamp; + + thisFrame.ID = tokens[2].toLong(NULL, 16); + if (thisFrame.ID <= 0x7FF) thisFrame.extended = false; + else thisFrame.extended = true; + thisFrame.bus = 0; + thisFrame.len = tokens[3].toInt(); + + QList dataToks = tokens[4].split(' '); + for (int d = 0; d < thisFrame.len; d++) thisFrame.data[d] = (unsigned char)dataToks[d].toInt(NULL, 16); + frames->append(thisFrame); + } + } + } + inFile->close(); + delete inFile; + return true; +} + +bool FrameFileIO::saveTraceFile(QString filename, const QVector * frames) +{ + QFile *outFile = new QFile(filename); + QDateTime timestamp; + int lineCounter = 0; + uint64_t tempTime; + int tempTimePiece; + + timestamp = QDateTime::currentDateTime(); + + if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) + { + delete outFile; + return false; + } + + outFile->write("; SavvyCAN CAN Logger trace file\n"); + outFile->write("; Device Serial Number : 0000 \n"); + outFile->write("; Start Time : "); + outFile->write(timestamp.toString("ddd, MMM dd, yyyy :: h:m:s\n").toUtf8()); + outFile->write(";\n"); + outFile->write("; Column description :\n"); + outFile->write("; ~~~~~~~~~~~~~~~~~~~~~\n"); + outFile->write(";\n"); + outFile->write("; + Message Number\n"); + outFile->write("; |\n"); + outFile->write("; | + Time Stamp (ms)\n"); + outFile->write("; | |\n"); + outFile->write("; | | + Message ID (hex)\n"); + outFile->write("; | | |\n"); + outFile->write("; | | | + Data Length Code\n"); + outFile->write("; | | | |\n"); + outFile->write("; | | | | + Data Bytes (hex)\n"); + outFile->write("; | | | | |\n"); + outFile->write(";---+----- -----+------ ----+--- + -+ -- -- -- -- -- -- --\n"); + + + for (int c = 0; c < frames->count(); c++) + { + lineCounter++; + if ((lineCounter % 100) == 0) + { + qApp->processEvents(); + //lineCounter = 0; + } + + + //1F D3 3F FF 08 FF E0 CB + outFile->write(QString::number(lineCounter).rightJustified(10, ' ').toUtf8()); + outFile->write("\t"); + + tempTime = frames->at(c).timestamp; + tempTimePiece = tempTime / 1000000ul / 60 / 60; + tempTime -= tempTimePiece * 1000000ul * 60 * 60; + outFile->write(QString::number(tempTimePiece).rightJustified(2, '0').toUtf8()); + outFile->write(":"); + + tempTimePiece = tempTime / 1000000ul / 60; + tempTime -= tempTimePiece * 1000000ul * 60; + outFile->write(QString::number(tempTimePiece).rightJustified(2, '0').toUtf8()); + outFile->write(":"); + + tempTimePiece = tempTime / 1000000ul; + tempTime -= tempTimePiece * 1000000ul; + outFile->write(QString::number(tempTimePiece).rightJustified(2, '0').toUtf8()); + outFile->write(":"); + + tempTimePiece = tempTime / 100; + outFile->write(QString::number(tempTimePiece).rightJustified(4, '0').toUtf8()); + outFile->write("\t"); + + outFile->write(QString::number(frames->at(c).ID, 16).toUpper().rightJustified(8, '0').toUtf8() + "\t"); + + outFile->write(QString::number(frames->at(c).len).toUtf8() + "\t"); + + for (int temp = 0; temp < frames->at(c).len; temp++) + { + outFile->write(QString::number(frames->at(c).data[temp], 16).toUpper().rightJustified(2, '0').toUtf8()); + outFile->putChar(' '); + } + + outFile->write("\n"); + + } + outFile->close(); + delete outFile; + return true; +} diff --git a/framefileio.h b/framefileio.h index 0e86402..095dd29 100644 --- a/framefileio.h +++ b/framefileio.h @@ -27,11 +27,13 @@ public: static bool loadGenericCSVFile(QString, QVector*); static bool loadLogFile(QString, QVector*); static bool loadMicrochipFile(QString, QVector*); + static bool loadTraceFile(QString, QVector*); static bool saveCRTDFile(QString, const QVector*); static bool saveNativeCSVFile(QString, const QVector*); static bool saveGenericCSVFile(QString, const QVector*); static bool saveLogFile(QString, const QVector*); static bool saveMicrochipFile(QString, const QVector*); + static bool saveTraceFile(QString, const QVector*); static QString loadFrameFile(QVector*); }; diff --git a/frameplaybackwindow.cpp b/frameplaybackwindow.cpp index 64b66fc..8e76c7c 100644 --- a/frameplaybackwindow.cpp +++ b/frameplaybackwindow.cpp @@ -388,6 +388,7 @@ void FramePlaybackWindow::btnLoadFile() currentSeqNum = 0; currentSeqItem = &seqItems[0]; } + refreshIDList(); updateFrameLabel(); } } @@ -416,6 +417,7 @@ void FramePlaybackWindow::btnLoadLive() currentSeqNum = 0; currentSeqItem = &seqItems[0]; } + refreshIDList(); updateFrameLabel(); btnStopClick(); } diff --git a/mainwindow.cpp b/mainwindow.cpp index c68358a..2358769 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -49,15 +49,13 @@ MainWindow::MainWindow(QWidget *parent) : readSettings(); - QStringList headers; - headers << "Timestamp" << "ID" << "Ext" << "Bus" << "Len" << "Data"; - //model->setHorizontalHeaderLabels(headers); ui->canFramesView->setColumnWidth(0, 110); ui->canFramesView->setColumnWidth(1, 70); ui->canFramesView->setColumnWidth(2, 40); ui->canFramesView->setColumnWidth(3, 40); ui->canFramesView->setColumnWidth(4, 40); - ui->canFramesView->setColumnWidth(5, 275); + ui->canFramesView->setColumnWidth(5, 40); + ui->canFramesView->setColumnWidth(6, 275); QHeaderView *HorzHdr = ui->canFramesView->horizontalHeader(); HorzHdr->setStretchLastSection(true); //causes the data column to automatically fill the tableview //enabling the below line kills performance in every way imaginable. Left here as a warning. Do not do this. @@ -66,6 +64,7 @@ MainWindow::MainWindow(QWidget *parent) : worker = new SerialWorker(model); worker->moveToThread(&serialWorkerThread); connect(&serialWorkerThread, &QThread::finished, worker, &QObject::deleteLater); + connect(&serialWorkerThread, &QThread::started, worker, &SerialWorker::run); //setup timers within the proper thread connect(this, &MainWindow::sendSerialPort, worker, &SerialWorker::setSerialPort, Qt::QueuedConnection); connect(worker, &SerialWorker::frameUpdateTick, this, &MainWindow::gotFrames, Qt::QueuedConnection); connect(this, &MainWindow::updateBaudRates, worker, &SerialWorker::updateBaudRates, Qt::QueuedConnection); @@ -78,7 +77,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(this, &MainWindow::stopFrameCapturing, worker, &SerialWorker::stopFrameCapture); connect(this, &MainWindow::settingsUpdated, worker, &SerialWorker::readSettings); serialWorkerThread.start(); - serialWorkerThread.setPriority(QThread::HighPriority); + serialWorkerThread.setPriority(QThread::HighPriority); graphingWindow = NULL; frameInfoWindow = NULL; @@ -91,46 +90,50 @@ MainWindow::MainWindow(QWidget *parent) : firmwareUploaderWindow = NULL; discreteStateWindow = NULL; connectionWindow = NULL; + scriptingWindow = NULL; + rangeWindow = NULL; dbcHandler = new DBCHandler; bDirty = false; inhibitFilterUpdate = false; model->setDBCHandler(dbcHandler); - connect(ui->actionOpen_Log_File, SIGNAL(triggered(bool)), this, SLOT(handleLoadFile())); - connect(ui->actionGraph_Dta, SIGNAL(triggered(bool)), this, SLOT(showGraphingWindow())); - connect(ui->actionFrame_Data_Analysis, SIGNAL(triggered(bool)), this, SLOT(showFrameDataAnalysis())); - connect(ui->btnClearFrames, SIGNAL(clicked(bool)), this, SLOT(clearFrames())); - connect(ui->actionSave_Log_File, SIGNAL(triggered(bool)), this, SLOT(handleSaveFile())); - connect(ui->actionSave_Filtered_Log_File, SIGNAL(triggered(bool)), this, SLOT(handleSaveFilteredFile())); - connect(ui->actionLoad_Filter_Definition, SIGNAL(triggered(bool)), this, SLOT(handleLoadFilters())); - connect(ui->actionSave_Filter_Definition, SIGNAL(triggered(bool)), this, SLOT(handleSaveFilters())); - connect(ui->action_Playback, SIGNAL(triggered(bool)), this, SLOT(showPlaybackWindow())); - connect(ui->actionFlow_View, SIGNAL(triggered(bool)), this, SLOT(showFlowViewWindow())); - connect(ui->action_Custom, SIGNAL(triggered(bool)), this, SLOT(showFrameSenderWindow())); - connect(ui->actionLoad_DBC_File, SIGNAL(triggered(bool)), this, SLOT(handleLoadDBC())); - connect(ui->actionSave_DBC_File, SIGNAL(triggered(bool)), this, SLOT(handleSaveDBC())); - connect(ui->canFramesView, SIGNAL(clicked(QModelIndex)), this, SLOT(gridClicked(QModelIndex))); - connect(ui->canFramesView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(gridDoubleClicked(QModelIndex))); - connect(ui->cbInterpret, SIGNAL(toggled(bool)), this, SLOT(interpretToggled(bool))); - connect(ui->cbOverwrite, SIGNAL(toggled(bool)), this, SLOT(overwriteToggled(bool))); - connect(ui->actionEdit_Messages_Signals, SIGNAL(triggered(bool)), this, SLOT(showDBCEditor())); - connect(ui->btnCaptureToggle, SIGNAL(clicked(bool)), this, SLOT(toggleCapture())); - connect(ui->actionExit_Application, SIGNAL(triggered(bool)), this, SLOT(exitApp())); - connect(ui->actionFuzzy_Scope, SIGNAL(triggered(bool)), this, SLOT(showFuzzyScopeWindow())); - connect(ui->actionRange_State, SIGNAL(triggered(bool)), this, SLOT(showRangeWindow())); - connect(ui->actionSave_Decoded_Frames, SIGNAL(triggered(bool)), this, SLOT(handleSaveDecoded())); - connect(ui->actionSingle_Multi_State, SIGNAL(triggered(bool)), this, SLOT(showSingleMultiWindow())); - connect(ui->actionFile_Comparison, SIGNAL(triggered(bool)), this, SLOT(showComparisonWindow())); - connect(ui->btnNormalize, SIGNAL(clicked(bool)), this, SLOT(normalizeTiming())); - connect(ui->actionPreferences, SIGNAL(triggered(bool)), this, SLOT(showSettingsDialog())); - connect(model, SIGNAL(updatedFiltersList()), this, SLOT(updateFilterList())); - connect(ui->listFilters, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(filterListItemChanged(QListWidgetItem*))); - connect(ui->btnFilterAll, SIGNAL(clicked(bool)), this, SLOT(filterSetAll())); - connect(ui->btnFilterNone, SIGNAL(clicked(bool)), this, SLOT(filterClearAll())); - connect(ui->actionFirmware_Uploader, SIGNAL(triggered(bool)), this, SLOT(showFirmwareUploaderWindow())); connect(ui->actionSetup, SIGNAL(triggered(bool)), SLOT(showConnectionSettingsWindow())); connect(ui->actionConnect, SIGNAL(triggered(bool)), this, SLOT(connButtonPress())); + connect(ui->actionOpen_Log_File, &QAction::triggered, this, &MainWindow::handleLoadFile); + connect(ui->actionGraph_Dta, &QAction::triggered, this, &MainWindow::showGraphingWindow); + connect(ui->actionFrame_Data_Analysis, &QAction::triggered, this, &MainWindow::showFrameDataAnalysis); + connect(ui->btnClearFrames, &QAbstractButton::clicked, this, &MainWindow::clearFrames); + connect(ui->actionSave_Log_File, &QAction::triggered, this, &MainWindow::handleSaveFile); + connect(ui->actionSave_Filtered_Log_File, &QAction::triggered, this, &MainWindow::handleSaveFilteredFile); + connect(ui->actionLoad_Filter_Definition, &QAction::triggered, this, &MainWindow::handleLoadFilters); + connect(ui->actionSave_Filter_Definition, &QAction::triggered, this, &MainWindow::handleSaveFilters); + connect(ui->action_Playback, &QAction::triggered, this, &MainWindow::showPlaybackWindow); + connect(ui->btnBaudSet, &QAbstractButton::clicked, this, &MainWindow::changeBaudRates); + connect(ui->actionFlow_View, &QAction::triggered, this, &MainWindow::showFlowViewWindow); + connect(ui->action_Custom, &QAction::triggered, this, &MainWindow::showFrameSenderWindow); + connect(ui->actionLoad_DBC_File, &QAction::triggered, this, &MainWindow::handleLoadDBC); + connect(ui->actionSave_DBC_File, &QAction::triggered, this, &MainWindow::handleSaveDBC); + connect(ui->canFramesView, &QAbstractItemView::clicked, 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); + connect(ui->actionEdit_Messages_Signals, &QAction::triggered, this, &MainWindow::showDBCEditor); + connect(ui->btnCaptureToggle, &QAbstractButton::clicked, this, &MainWindow::toggleCapture); + connect(ui->actionExit_Application, &QAction::triggered, this, &MainWindow::exitApp); + connect(ui->actionFuzzy_Scope, &QAction::triggered, this, &MainWindow::showFuzzyScopeWindow); + connect(ui->actionRange_State_2, &QAction::triggered, this, &MainWindow::showRangeWindow); + connect(ui->actionSave_Decoded_Frames, &QAction::triggered, this, &MainWindow::handleSaveDecoded); + connect(ui->actionSingle_Multi_State_2, &QAction::triggered, this, &MainWindow::showSingleMultiWindow); + connect(ui->actionFile_Comparison, &QAction::triggered, this, &MainWindow::showComparisonWindow); + connect(ui->actionScripting_INterface, &QAction::triggered, this, &MainWindow::showScriptingWindow); + connect(ui->btnNormalize, &QAbstractButton::clicked, this, &MainWindow::normalizeTiming); + connect(ui->actionPreferences, &QAction::triggered, this, &MainWindow::showSettingsDialog); + connect(model, &CANFrameModel::updatedFiltersList, this, &MainWindow::updateFilterList); + connect(ui->listFilters, &QListWidget::itemChanged, this, &MainWindow::filterListItemChanged); + connect(ui->btnFilterAll, &QAbstractButton::clicked, this, &MainWindow::filterSetAll); + connect(ui->btnFilterNone, &QAbstractButton::clicked, this, &MainWindow::filterClearAll); + connect(ui->actionFirmware_Uploader, &QAction::triggered, this, &MainWindow::showFirmwareUploaderWindow); lbStatusConnected.setText(tr("Not connected")); updateFileStatus(); @@ -223,6 +226,18 @@ MainWindow::~MainWindow() connectionWindow->close(); delete connectionWindow; } + + if (scriptingWindow) + { + scriptingWindow->close(); + delete scriptingWindow; + } + + if (rangeWindow) + { + rangeWindow->close(); + delete rangeWindow; + } delete ui; delete dbcHandler; @@ -492,6 +507,7 @@ void MainWindow::handleLoadFile() filters.append(QString(tr("Generic ID/Data CSV (*.csv)"))); filters.append(QString(tr("BusMaster Log (*.log)"))); filters.append(QString(tr("Microchip Log (*.can)"))); + filters.append(QString(tr("Vector Trace Files (*.trace)"))); dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilters(filters); @@ -520,13 +536,13 @@ void MainWindow::handleLoadFile() if (dialog.selectedNameFilter() == filters[2]) result = FrameFileIO::loadGenericCSVFile(filename, &tempFrames); if (dialog.selectedNameFilter() == filters[3]) result = FrameFileIO::loadLogFile(filename, &tempFrames); if (dialog.selectedNameFilter() == filters[4]) result = FrameFileIO::loadMicrochipFile(filename, &tempFrames); + if (dialog.selectedNameFilter() == filters[5]) result = FrameFileIO::loadTraceFile(filename, &tempFrames); progress.cancel(); if (result) { model->insertFrames(tempFrames); - QStringList fileList = filename.split('/'); loadedFileName = fileList[fileList.length() - 1]; @@ -552,6 +568,7 @@ void MainWindow::handleSaveFile() filters.append(QString(tr("Generic ID/Data CSV (*.csv)"))); filters.append(QString(tr("BusMaster Log (*.log)"))); filters.append(QString(tr("Microchip Log (*.can)"))); + filters.append(QString(tr("Vector Trace Files (*.trace)"))); dialog.setFileMode(QFileDialog::AnyFile); dialog.setNameFilters(filters); @@ -599,6 +616,12 @@ void MainWindow::handleSaveFile() result = FrameFileIO::saveMicrochipFile(filename, frames); } + if (dialog.selectedNameFilter() == filters[5]) + { + if (!filename.contains('.')) filename += ".trace"; + result = FrameFileIO::saveTraceFile(filename, frames); + } + progress.cancel(); if (result) @@ -622,6 +645,7 @@ void MainWindow::handleSaveFilteredFile() filters.append(QString(tr("Generic ID/Data CSV (*.csv)"))); filters.append(QString(tr("BusMaster Log (*.log)"))); filters.append(QString(tr("Microchip Log (*.log)"))); + filters.append(QString(tr("Vector Trace Files (*.trace)"))); dialog.setFileMode(QFileDialog::AnyFile); dialog.setNameFilters(filters); @@ -669,6 +693,12 @@ void MainWindow::handleSaveFilteredFile() result = FrameFileIO::saveMicrochipFile(filename, frames); } + if (dialog.selectedNameFilter() == filters[5]) + { + if (!filename.contains('.')) filename += ".trace"; + result = FrameFileIO::saveTraceFile(filename, frames); + } + progress.cancel(); if (result) @@ -988,7 +1018,7 @@ void MainWindow::showFrameSenderWindow() else frameSenderWindow = new FrameSenderWindow(model->getFilteredListReference()); - connect(frameSenderWindow, SIGNAL(sendCANFrame(const CANFrame*,int)), worker, SLOT(sendFrame(const CANFrame*,int))); + connect(frameSenderWindow, &FrameSenderWindow::sendCANFrame, worker, &SerialWorker::sendFrame); } frameSenderWindow->show(); } @@ -1034,9 +1064,23 @@ void MainWindow::showSingleMultiWindow() discreteStateWindow->show(); } +void MainWindow::showScriptingWindow() +{ + if (!scriptingWindow) + { + scriptingWindow = new ScriptingWindow(model->getListReference()); + connect(scriptingWindow, &ScriptingWindow::sendCANFrame, worker, &SerialWorker::sendFrame); + } + scriptingWindow->show(); +} + void MainWindow::showRangeWindow() { - //not done yet + if (!rangeWindow) + { + rangeWindow = new RangeStateWindow(model->getListReference()); + } + rangeWindow->show(); } void MainWindow::showFuzzyScopeWindow() diff --git a/mainwindow.h b/mainwindow.h index dcd29b3..38007f6 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -20,6 +20,8 @@ #include "firmwareuploaderwindow.h" #include "discretestatewindow.h" #include "connectionwindow.h" +#include "scriptingwindow.h" +#include "rangestatewindow.h" namespace Ui { class MainWindow; @@ -57,7 +59,11 @@ private slots: void showComparisonWindow(); void showSettingsDialog(); void showFirmwareUploaderWindow(); +<<<<<<< HEAD void showConnectionSettingsWindow(); +======= + void showScriptingWindow(); +>>>>>>> WIP2 void exitApp(); void handleSaveDecoded(); void changeBaudRates(); @@ -123,7 +129,12 @@ private: MainSettingsDialog *settingsDialog; DiscreteStateWindow *discreteStateWindow; FirmwareUploaderWindow *firmwareUploaderWindow; +<<<<<<< HEAD ConnectionWindow *connectionWindow; +======= + ScriptingWindow *scriptingWindow; + RangeStateWindow *rangeWindow; +>>>>>>> WIP2 //various private storage QLabel lbStatusConnected; diff --git a/mainwindow.ui b/mainwindow.ui index 9226a3e..87b22e4 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -204,13 +204,12 @@ RE Tools - - - + + @@ -218,7 +217,7 @@ - + @@ -263,16 +262,6 @@ Custom - - - Single / Multi State - - - - - Range State - - Flow View @@ -366,6 +355,19 @@ Connect + + + Scripting Interface + + + + + Range State + + + + + Single/Multi State diff --git a/rangestatewindow.cpp b/rangestatewindow.cpp new file mode 100644 index 0000000..371f8aa --- /dev/null +++ b/rangestatewindow.cpp @@ -0,0 +1,429 @@ +#include "rangestatewindow.h" +#include "ui_rangestatewindow.h" +#include "mainwindow.h" +#include "utility.h" + +RangeStateWindow::RangeStateWindow(const QVector *frames, QWidget *parent) : + QDialog(parent), + ui(new Ui::RangeStateWindow) +{ + ui->setupUi(this); + + modelFrames = frames; + + ui->graphSignal->xAxis->setRange(0, 8); + ui->graphSignal->yAxis->setRange(-10, 265); //run range a bit outside possible number so they aren't plotted in a hard to see place + ui->graphSignal->xAxis->setVisible(false); + ui->graphSignal->yAxis->setVisible(false); + + //ui->graphSignal->axisRect()->setupFullAxesBox(); + + ui->cbSignalMode->addItem(tr("Big Endian")); + ui->cbSignalMode->addItem(tr("Little Endian")); + ui->cbSignalMode->addItem(tr("Try Both")); + + ui->cbSignedMode->addItem(tr("Signed Value")); + ui->cbSignedMode->addItem(tr("Unsigned Value")); + ui->cbSignedMode->addItem(tr("Try Both")); + +//lambda expressions used here because these are tiny functions that don't have any reason to be full named functions +//unfortunately the fact that valueChanged is overloaded makes the syntax here HORRIBLE. That sucks. + connect(ui->spinMaxSigSize, static_cast(&QSpinBox::valueChanged), + [=](int newVal) + { + if (newVal < ui->spinMinSigSize->value()) ui->spinMinSigSize->setValue(newVal); + }); + + connect(ui->spinMinSigSize, static_cast(&QSpinBox::valueChanged), + [=](int newVal) + { + if (newVal > ui->spinMaxSigSize->value()) ui->spinMaxSigSize->setValue(newVal); + }); + + connect(ui->btnAllFilter, &QAbstractButton::clicked, + [=]() + { + for (int i = 0; i < ui->listFilter->count(); i++) + { + QListWidgetItem *item = ui->listFilter->item(i); + item->setCheckState(Qt::Checked); + idFilters[Utility::ParseStringToNum(item->text())] = true; + } + }); + + connect(ui->btnNoneFilter, &QAbstractButton::clicked, + [=]() + { + for (int i = 0; i < ui->listFilter->count(); i++) + { + QListWidgetItem *item = ui->listFilter->item(i); + item->setCheckState(Qt::Unchecked); + idFilters[Utility::ParseStringToNum(item->text())] = false; + } + }); + + connect(ui->listFilter, &QListWidget::itemChanged, + [=](QListWidgetItem *item) + { + bool isChecked = false; + int id = Utility::ParseStringToNum(item->text()); + if (item->checkState() == Qt::Checked) isChecked = true; + idFilters[id] = isChecked; + }); + + connect(ui->btnRecalc, &QAbstractButton::clicked, this, &RangeStateWindow::recalcButton); + connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int))); + connect(ui->listCandidates, &QListWidget::currentRowChanged, this, &RangeStateWindow::clickedSignalList); +} + +RangeStateWindow::~RangeStateWindow() +{ + delete ui; +} + +void RangeStateWindow::showEvent(QShowEvent* event) +{ + QDialog::showEvent(event); + + readSettings(); + + refreshFilterList(); +} + +void RangeStateWindow::closeEvent(QCloseEvent *event) +{ + Q_UNUSED(event); + writeSettings(); +} + +void RangeStateWindow::readSettings() +{ + QSettings settings; + if (settings.value("Main/SaveRestorePositions", false).toBool()) + { + resize(settings.value("RangeStateView/WindowSize", QSize(765, 615)).toSize()); + move(settings.value("RangeStateView/WindowPos", QPoint(50, 50)).toPoint()); + } +} + +void RangeStateWindow::writeSettings() +{ + QSettings settings; + + if (settings.value("Main/SaveRestorePositions", false).toBool()) + { + settings.setValue("RangeStateView/WindowSize", size()); + settings.setValue("RangeStateView/WindowPos", pos()); + } +} + +void RangeStateWindow::updatedFrames(int numFrames) +{ + CANFrame thisFrame; + if (numFrames == -1) //all frames deleted. We don't need to do a thing on this window but erase everything in the filters section + { + ui->listFilter->clear(); + idFilters.clear(); + } + else if (numFrames == -2) //all new set of frames. Reset + { + refreshFilterList(); + } + else //just got some new frames. See if we need to update the filters list. Otherwise nothing to do - no recalc happens until the button is pressed + { + for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) + { + thisFrame = modelFrames->at(i); + if (!idFilters.contains(thisFrame.ID)) + { + idFilters.insert(thisFrame.ID, true); + QListWidgetItem* listItem = new QListWidgetItem(Utility::formatNumber(thisFrame.ID), ui->listFilter); + listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable); // set checkable flag + listItem->setCheckState(Qt::Checked); //default all filters to be set active + } + } + } +} + +void RangeStateWindow::refreshFilterList() +{ + int id; + + idFilters.clear(); + ui->listFilter->clear(); + + for (int i = 0; i < modelFrames->length(); i++) + { + id = modelFrames->at(i).ID; + if (!idFilters.contains(id)) + { + idFilters.insert(id, true); + QListWidgetItem* listItem = new QListWidgetItem(Utility::formatNumber(id), ui->listFilter); + listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable); // set checkable flag + listItem->setCheckState(Qt::Checked); //default all filters to be set active + } + } + + ui->listFilter->sortItems(); +} + +void RangeStateWindow::recalcButton() +{ + QHash::iterator iter; + int id; + + ui->listCandidates->clear(); + foundSignals.clear(); + + for (iter = idFilters.begin(); iter != idFilters.end(); ++iter) + { + if (iter.value() == true) + { + qDebug() << "Processing for ID: " << iter.key(); + //so, we're supposed to process this frame ID. We'll need to create a frame cache for it + frameCache.clear(); + frameCache.reserve(modelFrames->count()); //block allocate more than enough space + id = iter.key(); + for (int j = 0; j < modelFrames->count(); j++) + { + if (modelFrames->at(j).ID == id) frameCache.append(modelFrames->at(j)); + } + //now we've got a list with all the same ID. Time to send it off for processing + signalsFactory(); + } + } + + qDebug() << "Found " << foundSignals.count() << " signals total."; +} + +/* + * Uses the settings exposed to the user to generate a set of candidate signals that should be checked. + * The user could specify signal sizes, granularity, endian type and we generate all the permutations from there + * Should process from max to min and stop when a valid signal is found (at least as an option) to declutter a bit. + * Mostly what we're interested in is the largest signal that matches +*/ +void RangeStateWindow::signalsFactory() +{ + int minSig = ui->spinMinSigSize->value(); + int maxSig = ui->spinMaxSigSize->value(); + int granularity = ui->spinGranularity->value(); + int sigType = ui->cbSignalMode->currentIndex() + 1; + int signedType = ui->cbSignedMode->currentIndex() + 1; + int maxBits = frameCache.at(0).len * 8; + int sens = ui->slideSensitivity->value(); + + for (int sigSize = maxSig; sigSize >= minSig; sigSize--) + { + for (int startBit = 0; startBit < maxBits; startBit += granularity) + { + if (sigType & 1) + { + if (signedType & 1) processSignal(startBit, sigSize, sens, true, true); + if (signedType & 2) processSignal(startBit, sigSize, sens, true, false); + } + if (sigType & 2) + { + if (signedType & 1) processSignal(startBit, sigSize, sens, false, true); + if (signedType & 2) processSignal(startBit, sigSize, sens, false, false); + } + //have to try both types even with 8 bit and smaller signals + //because they could cross byte boundaries. Could check whether they + //do and not try both types if it is impossible. + } + } +} + +/* + * Given the signal we generate the relevant data and figure out whether this signal seems to be a smooth range signal +*/ +bool RangeStateWindow::processSignal(int startBit, int bitLength, int sensitivity, bool bigEndian, bool isSigned) +{ + qDebug() << ""; + qDebug() << "S:" << startBit << " B:" << bitLength << " Sens:" << sensitivity << " Big E:" << bigEndian << " Signed: " << isSigned; + + QVector scaledVals; + QVector diff1; + QVector diff2; + int64_t valu; + int64_t highestValue = -1000000000000LL; + int64_t lowestValue = 1000000000000LL; + double multiplier; + int numFrames = frameCache.count(); + + scaledVals.reserve(frameCache.count()); + diff1.reserve(frameCache.count() - 1); + diff2.reserve(frameCache.count() - 2); + + int i; + + for (i = 0; i < numFrames; i++) + { + valu = Utility::processIntegerSignal(frameCache.at(i).data, startBit, bitLength, !bigEndian, isSigned); + if (valu < lowestValue) lowestValue = valu; + if (valu > highestValue) highestValue = valu; + } + + qDebug() << "Min: " << lowestValue << " Max: " << highestValue; + + if (lowestValue == highestValue) return false; //a signal that never changes is worthless and not a range signal + + //multiplier is calculated such that the range of the signal is scaled down to the value of sensitivity. + //so, if we have a sensitivity of 50 then the range is scaled so that it is 50 + //this has the effect of smoothing the data a bit by completely removing the fine detail. It's irrelevant to + //an algorithm like this anyway and just gets in the way. + //Also, we're going to offset by lowest value so that everything is based at a value of 0 as the low end. + //All of this drastically modifies the actual signal but that's OK, we're looking for patterns here not + //being faithful to the signal (what a dirty cheater) + int range = highestValue - lowestValue; + multiplier = (double)sensitivity / (double)range; + + for (i = 0; i < numFrames; i++) scaledVals.append((int)((Utility::processIntegerSignal(frameCache.at(i).data, startBit, bitLength, !bigEndian, isSigned) - lowestValue) * multiplier)); + + for (i = 1; i < numFrames; i++) + { + valu = scaledVals[i-1] - scaledVals[i]; + diff1.append(valu); + } + + for (i = 1; i < diff1.count(); i++) + { + valu = diff1[i-1] - diff1[i]; + diff2.append(valu); + } + + //now the differences are all stored so let's go through and see if they seem to suggest a ramping sort of signal or not. + //for a first test lets let through any signal where the acceleration values are all much smaller than the signal range + bool isGood = true; + int comparisonValue = Utility::Lerp(sensitivity / 5, sensitivity / 40, (sensitivity - 10) / 240.0); + qDebug() << "range: " << sensitivity << " comparisonvalue: " << comparisonValue; + int overValues = 0; + for (i = 0; i < diff2.count(); i++) + { + if (abs(diff1[i]) > comparisonValue) overValues++; + } + int maxOvers = Utility::Lerp(4, numFrames / 50.0, 1.0 - ((sensitivity -10) / 240.0)); + qDebug() << "Max Overs: " << maxOvers; + if (overValues > maxOvers) isGood = false; + qDebug() << "Is this signal good: " << isGood << " Num overs: " << overValues; + if (isGood) + { + //createGraph(scaledVals); + QString temp; + temp = "ID: " + QString::number(frameCache.at(0).ID, 16) + " startBit: " + QString::number(startBit) + " len: " + QString::number(bitLength); + int64_t foundSig; + foundSig = frameCache.at(0).ID; + foundSig += (int64_t)startBit << 32; + foundSig += (int64_t)bitLength << 40; + + if (isSigned) + { + temp += " Signed"; + foundSig += (int64_t)1 << 48; + } + else + { + temp += " Unsigned"; + } + + if (bigEndian) + { + temp += " BigEndian"; + foundSig += (int64_t)1 << 49; + } + else + { + temp += " LittleEndian"; + } + + ui->listCandidates->addItem(temp); + foundSignals.append(foundSig); + } + return isGood; +} + +//graphs the vector such that the X axis is just the index into the vector and Y is perfectly graphed within the window +void RangeStateWindow::createGraph(QVector values) +{ + + int tempVal; + float minval=1000000, maxval = -100000; + + int numEntries = values.count(); + + ui->graphSignal->clearGraphs(); + + QVector x(numEntries), y(numEntries); + + for (int j = 0; j < numEntries; j++) + { + tempVal = values[j]; + + x[j] = j; + + y[j] = tempVal; + if (y[j] < minval) minval = y[j]; + if (y[j] > maxval) maxval = y[j]; + } + + qDebug() << "YMin: " << minval << " YMax: " << maxval; + + double ymin, ymax; + + ymin = minval; + + if (ymin < 0) ymin *= 1.2; + else ymin *= 0.8; + + ymax = maxval; + if (ymax < 0) ymax * 0.8; + else ymax *= 1.2; + + if (fabs(ymin) < 0.01) ymin -= (ymax / 60.0); + if (fabs(ymax) < 0.01) ymax -= (ymin / 60.0); + + qDebug() << "YFm: " << ymin << " YFM: " << ymax; + + ui->graphSignal->addGraph(); + ui->graphSignal->graph()->setName("Graph"); + ui->graphSignal->graph()->setData(x,y); + ui->graphSignal->graph()->setLineStyle(QCPGraph::lsLine); //connect points with lines + QPen graphPen; + graphPen.setColor(Qt::black); + graphPen.setWidth(1); + ui->graphSignal->graph()->setPen(graphPen); + ui->graphSignal->xAxis->setRange(0, numEntries); + ui->graphSignal->yAxis->setRange(ymin, ymax); + ui->graphSignal->replot(); +} + +void RangeStateWindow::clickedSignalList(int idx) +{ + if (idx == -1) return; //just in case... + + int id, startBit, bitLength; + bool isSigned = false, isBigEndian = false; + int64_t valu; + + valu = foundSignals.at(idx); + id = valu & 0xFFFFFFFFULL; + startBit = (valu >> 32) & 0xFF; + bitLength = (valu >> 40) & 0xFF; + if (valu & (1LL << 48)) isSigned = true; + if (valu & (1LL << 49)) isBigEndian = true; + + qDebug() << "I:" << id << " sb:" << startBit << " len:" << bitLength << " signed:" << isSigned << " big:" << isBigEndian; + + frameCache.clear(); + frameCache.reserve(modelFrames->count()); //block allocate more than enough space + + for (int j = 0; j < modelFrames->count(); j++) + { + if (modelFrames->at(j).ID == id) frameCache.append(modelFrames->at(j)); + } + + int numFrames = frameCache.count(); + QVector values; + values.reserve(numFrames); + for (int i = 0; i < numFrames; i++) values.append((int)((Utility::processIntegerSignal(frameCache.at(i).data, startBit, bitLength, !isBigEndian, isSigned)))); + createGraph(values); +} diff --git a/rangestatewindow.h b/rangestatewindow.h new file mode 100644 index 0000000..4f59457 --- /dev/null +++ b/rangestatewindow.h @@ -0,0 +1,41 @@ +#ifndef RANGESTATEWINDOW_H +#define RANGESTATEWINDOW_H + +#include +#include "can_structs.h" + +namespace Ui { +class RangeStateWindow; +} + +class RangeStateWindow : public QDialog +{ + Q_OBJECT + +public: + explicit RangeStateWindow(const QVector *frames, QWidget *parent = 0); + ~RangeStateWindow(); + void showEvent(QShowEvent*); + +private slots: + void updatedFrames(int); + void recalcButton(); + void clickedSignalList(int idx); + +private: + Ui::RangeStateWindow *ui; + const QVector *modelFrames; + QVector frameCache; + QList foundSignals; + QHash idFilters; + + void refreshFilterList(); + void closeEvent(QCloseEvent *event); + void readSettings(); + void writeSettings(); + void signalsFactory(); + bool processSignal(int startBit, int bitLength, int sensitivity, bool bigEndian, bool isSigned); + void createGraph(QVector values); +}; + +#endif // RANGESTATEWINDOW_H diff --git a/rangestatewindow.ui b/rangestatewindow.ui new file mode 100644 index 0000000..f9a7507 --- /dev/null +++ b/rangestatewindow.ui @@ -0,0 +1,207 @@ + + + RangeStateWindow + + + + 0 + 0 + 763 + 938 + + + + Range State Window + + + + + + + + Candidate Signals: + + + + + + + + + + Graphical Representation of Selected Signal: + + + + + + + + 0 + 0 + + + + + + + + + + + + + + Sensitivity + + + + + + + 10 + + + 250 + + + Qt::Horizontal + + + + + + + Min Signal Size: + + + + + + + 1 + + + 32 + + + + + + + Max Signal Size: + + + + + + + 1 + + + 32 + + + + + + + Granularity: + + + + + + + 1 + + + 32 + + + + + + + Signal Mode: + + + + + + + + + + Signed Mode: + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + ID Filter: + + + + + + + + + + All + + + + + + + None + + + + + + + + + + + Recalculate Candidate Signals + + + + + + + + QCustomPlot + QWidget +
qcustomplot.h
+ 1 +
+
+ + +
diff --git a/scriptcontainer.cpp b/scriptcontainer.cpp new file mode 100644 index 0000000..236b979 --- /dev/null +++ b/scriptcontainer.cpp @@ -0,0 +1,148 @@ +#include "scriptcontainer.h" + +#include +#include + +ScriptContainer::ScriptContainer() +{ + fileName = QString(); + filePath = QString(); + scriptText = QString(); + + connect(&timer, SIGNAL(timeout()), this, SLOT(tick())); +} + +void ScriptContainer::compileScript() +{ + QJSValue result = scriptEngine.evaluate(scriptText, fileName); + + errorWidget->clear(); + + if (result.isError() && errorWidget) + { + errorWidget->addItem("SCRIPT EXCEPTION!"); + errorWidget->addItem("Line: " + result.property("lineNumber").toString()); + errorWidget->addItem(result.property("message").toString()); + errorWidget->addItem("Stack:"); + errorWidget->addItem(result.property("stack").toString()); + } + else + { + compiledScript = result; + + QJSValue hostObj = scriptEngine.newQObject(this); + scriptEngine.globalObject().setProperty("host", hostObj); + + setupFunction = scriptEngine.globalObject().property("setup"); + gotFrameFunction = scriptEngine.globalObject().property("gotFrame"); + tickFunction = scriptEngine.globalObject().property("tick"); + + if (setupFunction.isCallable()) + { + qDebug() << "setup exists"; + QJSValue res = setupFunction.call(); + if (res.isError()) + { + errorWidget->addItem("Error in setup function on line " + res.property("lineNumber").toString()); + errorWidget->addItem(res.property("message").toString()); + } + } + + if (gotFrameFunction.isCallable()) qDebug() << "gotFrame exists"; + if (tickFunction.isCallable()) qDebug() << "tick exists"; + } +} + +void ScriptContainer::setErrorWidget(QListWidget *list) +{ + errorWidget = list; +} + +void ScriptContainer::setFilter(QJSValue id, QJSValue mask, QJSValue bus) +{ + uint32_t idVal = id.toUInt(); + uint32_t maskVal = mask.toUInt(); + int busVal = bus.toInt(); + qDebug() << "Called set filter"; + qDebug() << idVal << "*" << maskVal << "*" << busVal; + CANFilter filter; + filter.setFilter(idVal, maskVal, busVal); + filters.append(filter); +} + +void ScriptContainer::setTickInterval(QJSValue interval) +{ + int intervalValue = interval.toInt(); + qDebug() << "called set tick interval with value " << intervalValue; + if (intervalValue > 0) + { + timer.setInterval(intervalValue); + timer.start(); + } + else timer.stop(); +} + +void ScriptContainer::clearFilters() +{ + qDebug() << "Called clear filters"; + filters.clear(); +} + +void ScriptContainer::sendFrame(QJSValue bus, QJSValue id, QJSValue length, QJSValue data) +{ + CANFrame frame; + frame.extended = false; + frame.ID = id.toInt(); + frame.len = length.toInt(); + if (frame.len < 0) frame.len = 0; + if (frame.len > 8) frame.len = 8; + + if (!data.isArray()) qDebug() << "data isn't an array"; + + for (int i = 0; i < frame.len; i++) + { + frame.data[i] = (uint8_t)data.property(i).toInt(); + } + + frame.bus = bus.toInt(); + if (frame.bus < 0) frame.bus = 0; + if (frame.bus > 1) frame.bus = 1; + + if (frame.ID > 0x7FF) frame.extended = true; + + //qDebug() << "Sending frame from script"; + emit sendCANFrame(&frame, frame.bus); +} + +void ScriptContainer::gotFrame(const CANFrame &frame) +{ + if (!gotFrameFunction.isCallable()) return; //nothing to do if we can't even call the function + //qDebug() << "Got frame in script interface"; + for (int i = 0; i < filters.length(); i++) + { + if (filters[i].checkFilter(frame.ID, frame.bus)) + { + QJSValueList args; + args << frame.bus << frame.ID << frame.len; + QJSValue dataBytes = scriptEngine.newArray(frame.len); + for (int j = 0; j < frame.len; j++) dataBytes.setProperty(j, QJSValue(frame.data[j])); + args.append(dataBytes); + gotFrameFunction.call(args); + return; //as soon as one filter matches we jump out + } + } +} + +void ScriptContainer::tick() +{ + if (tickFunction.isCallable()) + { + //qDebug() << "Calling tick function"; + QJSValue res = tickFunction.call(); + if (res.isError()) + { + errorWidget->addItem("Error in tick function on line " + res.property("lineNumber").toString()); + errorWidget->addItem(res.property("message").toString()); + } + } +} diff --git a/scriptcontainer.h b/scriptcontainer.h new file mode 100644 index 0000000..b570f12 --- /dev/null +++ b/scriptcontainer.h @@ -0,0 +1,48 @@ +#ifndef SCRIPTCONTAINER_H +#define SCRIPTCONTAINER_H + +#include "can_structs.h" +#include "canfilter.h" + +#include +#include +#include + +class ScriptContainer : public QObject +{ + Q_OBJECT + +public: + ScriptContainer(); + void gotFrame(const CANFrame &frame); + + QString fileName; + QString filePath; + QString scriptText; + +public slots: + void compileScript(); + void setErrorWidget(QListWidget *list); + void setFilter(QJSValue id, QJSValue mask, QJSValue bus); + void setTickInterval(QJSValue interval); + void clearFilters(); + void sendFrame(QJSValue bus, QJSValue id, QJSValue length, QJSValue data); + +private slots: + void tick(); + +signals: + void sendCANFrame(const CANFrame *, int); + +private: + QJSEngine scriptEngine; + QJSValue compiledScript; + QJSValue setupFunction; + QJSValue gotFrameFunction; + QJSValue tickFunction; + QTimer timer; + QListWidget *errorWidget; + QList filters; +}; + +#endif // SCRIPTCONTAINER_H diff --git a/scriptingwindow.cpp b/scriptingwindow.cpp new file mode 100644 index 0000000..0b0520d --- /dev/null +++ b/scriptingwindow.cpp @@ -0,0 +1,203 @@ +#include "scriptingwindow.h" +#include "ui_scriptingwindow.h" + +#include +#include +#include + +#include "mainwindow.h" + +ScriptingWindow::ScriptingWindow(const QVector *frames, QWidget *parent) : + QDialog(parent), + ui(new Ui::ScriptingWindow) +{ + ui->setupUi(this); + + readSettings(); + + modelFrames = frames; + + connect(ui->btnLoadScript, &QAbstractButton::pressed, this, &ScriptingWindow::loadNewScript); + connect(ui->btnNewScript, &QAbstractButton::pressed, this, &ScriptingWindow::createNewScript); + connect(ui->btnRecompile, &QAbstractButton::pressed, this, &ScriptingWindow::recompileScript); + connect(ui->btnRemoveScript, &QAbstractButton::pressed, this, &ScriptingWindow::deleteCurrentScript); + connect(ui->btnRevertScript, &QAbstractButton::pressed, this, &ScriptingWindow::revertScript); + connect(ui->btnSaveScript, &QAbstractButton::pressed, this, &ScriptingWindow::saveScript); + connect(MainWindow::getReference(), &MainWindow::framesUpdated, this, &ScriptingWindow::updatedFrames); + + ui->txtScriptSource->setLexer(new QsciLexerJavaScript(ui->txtScriptSource)); +} + +ScriptingWindow::~ScriptingWindow() +{ + delete ui; +} + +void ScriptingWindow::updatedFrames(int numFrames) +{ + CANFrame thisFrame; + //-1 means all frames deleted and -2 means a full refresh, neither of which we care about here. + if (numFrames > 0) + { + qDebug() << "Got frames into script window: " << numFrames; + //for every new frame pass it on to each script container. The container will determine if it needs to actually + //notify the script and do that if applicable. + for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) + { + thisFrame = modelFrames->at(i); + for (int j = 0; j < scripts.length(); j++) + { + scripts[j]->gotFrame(thisFrame); + } + } + } +} + +void ScriptingWindow::closeEvent(QCloseEvent *event) +{ + Q_UNUSED(event); + writeSettings(); +} + +void ScriptingWindow::readSettings() +{ + QSettings settings; + if (settings.value("Main/SaveRestorePositions", false).toBool()) + { + resize(settings.value("ScriptingWindow/WindowSize", QSize(860, 650)).toSize()); + move(settings.value("ScriptingWindow/WindowPos", QPoint(100, 100)).toPoint()); + } +} + +void ScriptingWindow::writeSettings() +{ + QSettings settings; + + if (settings.value("Main/SaveRestorePositions", false).toBool()) + { + settings.setValue("ScriptingWindow/WindowSize", size()); + settings.setValue("ScriptingWindow/WindowPos", pos()); + } +} + +void ScriptingWindow::showEvent(QShowEvent* event) +{ + QDialog::showEvent(event); +} + +void ScriptingWindow::loadNewScript() +{ + QString filename; + QFileDialog dialog; + bool result = false; + ScriptContainer *container; + + QStringList filters; + filters.append(QString(tr("Javascript File (*.js)"))); + + dialog.setFileMode(QFileDialog::ExistingFile); + dialog.setNameFilters(filters); + dialog.setViewMode(QFileDialog::Detail); + + if (dialog.exec() == QDialog::Accepted) + { + filename = dialog.selectedFiles()[0]; + + if (dialog.selectedNameFilter() == filters[0]) + { + QFile scriptFile(filename); + + if (scriptFile.open(QIODevice::ReadOnly)) + { + QString contents = scriptFile.readAll(); + scriptFile.close(); + QStringList fileList = filename.split('/'); + QString justFileName = fileList[fileList.length() - 1]; + ui->listLoadedScripts->addItem(justFileName); + container = new ScriptContainer(); + container->fileName = justFileName; + container->filePath = filename; + container->scriptText = contents; + container->setErrorWidget(ui->listErrors); + container->compileScript(); + connect(container, &ScriptContainer::sendCANFrame, this, &ScriptingWindow::sendCANFrame); + scripts.append(container); + currentScript = container; + ui->txtScriptSource->setText(container->scriptText); + ui->txtScriptSource->setEnabled(true); + } + } + } +} + +void ScriptingWindow::createNewScript() +{ + ScriptContainer *container; + + container = new ScriptContainer(); + + container->fileName = "UNNAMED_" + QString::number((qrand() % 10000)) + ".js"; + container->filePath = QString(); + container->scriptText = QString(); + container->setErrorWidget(ui->listErrors); + connect(container, &ScriptContainer::sendCANFrame, this, &ScriptingWindow::sendCANFrame); + scripts.append(container); + ui->listLoadedScripts->addItem(container->fileName); + currentScript = container; + ui->txtScriptSource->setText(container->scriptText); + ui->txtScriptSource->setEnabled(true); +} + +void ScriptingWindow::deleteCurrentScript() +{ + +} + +void ScriptingWindow::refreshSourceWindow() +{ + ui->txtScriptSource->setText(currentScript->scriptText); +} + +void ScriptingWindow::saveScript() +{ + QString filename; + QFileDialog dialog(this); + + QStringList filters; + filters.append(QString(tr("Javascript File (*.js)"))); + + dialog.setFileMode(QFileDialog::AnyFile); + dialog.setNameFilters(filters); + dialog.setViewMode(QFileDialog::Detail); + dialog.setAcceptMode(QFileDialog::AcceptSave); + + if (dialog.exec() == QDialog::Accepted) + { + filename = dialog.selectedFiles()[0]; + if (!filename.contains('.')) filename += ".js"; + if (dialog.selectedNameFilter() == filters[0]) + { + QFile *outFile = new QFile(filename); + + if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) + { + delete outFile; + return; + } + outFile->write(ui->txtScriptSource->text().toUtf8()); + outFile->close(); + delete outFile; + } + } +} + +void ScriptingWindow::revertScript() +{ + +} + +void ScriptingWindow::recompileScript() +{ + currentScript->scriptText = ui->txtScriptSource->text(); + currentScript->compileScript(); +} diff --git a/scriptingwindow.h b/scriptingwindow.h new file mode 100644 index 0000000..d27620b --- /dev/null +++ b/scriptingwindow.h @@ -0,0 +1,51 @@ +#ifndef SCRIPTINGWINDOW_H +#define SCRIPTINGWINDOW_H + +#include "can_structs.h" +#include "scriptcontainer.h" + +#include +#include + +namespace Ui { +class ScriptingWindow; +} + +class ScriptingWindow : public QDialog +{ + Q_OBJECT + +public: + explicit ScriptingWindow(const QVector *frames, QWidget *parent = 0); + void showEvent(QShowEvent*); + ~ScriptingWindow(); + +private slots: + void loadNewScript(); + void createNewScript(); + void deleteCurrentScript(); + void refreshSourceWindow(); + void saveScript(); + void revertScript(); + void recompileScript(); + void updatedFrames(int); + +public slots: + +signals: + void sendCANFrame(const CANFrame *, int); + +private: + void closeEvent(QCloseEvent *event); + void readSettings(); + void writeSettings(); + + Ui::ScriptingWindow *ui; + QList scripts; + ScriptContainer *currentScript; + const QVector *modelFrames; + + +}; + +#endif // SCRIPTINGWINDOW_H diff --git a/scriptingwindow.ui b/scriptingwindow.ui new file mode 100644 index 0000000..5137ac6 --- /dev/null +++ b/scriptingwindow.ui @@ -0,0 +1,128 @@ + + + ScriptingWindow + + + + 0 + 0 + 862 + 651 + + + + Scripting Interface + + + + + + + + Loaded Scripts + + + Qt::AlignCenter + + + + + + + + + + Load New Script + + + + + + + Create New Script + + + + + + + Remove Current Script + + + + + + + + + + + Script Editing Window + + + Qt::AlignCenter + + + + + + + + + + + + + + + + + + + Save + + + + + + + Revert + + + + + + + Recompile + + + + + + + + + Current Scripting Errors + + + Qt::AlignCenter + + + + + + + + + + + + + QsciScintilla + QFrame +
Qsci/qsciscintilla.h
+
+
+ + +
diff --git a/serialworker.cpp b/serialworker.cpp index be2f63e..51cea4a 100644 --- a/serialworker.cpp +++ b/serialworker.cpp @@ -40,6 +40,19 @@ SerialWorker::~SerialWorker() if (ticker != NULL) ticker->stop(); } +void SerialWorker::run() +{ + ticker = new QTimer; + connect(ticker, SIGNAL(timeout()), this, SLOT(handleTick())); + + elapsedTime = new QTime; + elapsedTime->start(); + + ticker->setInterval(250); //tick four times per second + ticker->setSingleShot(false); //keep ticking + ticker->start(); +} + void SerialWorker::readSettings() { QSettings settings; @@ -110,19 +123,6 @@ void SerialWorker::setSerialPort(QSerialPortInfo *port) else connected = true; connect(serial, SIGNAL(readyRead()), this, SLOT(readSerialData())); if (doValidation) QTimer::singleShot(1000, this, SLOT(connectionTimeout())); - if (ticker == NULL) - { - ticker = new QTimer; - connect(ticker, SIGNAL(timeout()), this, SLOT(handleTick())); - } - if (elapsedTime == NULL) - { - elapsedTime = new QTime; - elapsedTime->start(); - } - ticker->setInterval(250); //tick four times per second - ticker->setSingleShot(false); //keep ticking - ticker->start(); } void SerialWorker::connectionTimeout() @@ -155,9 +155,17 @@ void SerialWorker::sendFrame(const CANFrame *frame, int bus = 0) QByteArray buffer; int c; int ID; + CANFrame tempFrame = *frame; + tempFrame.isReceived = false; //qDebug() << "Sending out frame with id " << frame->ID; + //show our sent frames in the list too. This happens even if we're not connected. + canModel->addFrame(tempFrame, false); + gotFrames++; + + if (serial == NULL) return; + if (!serial->isOpen()) return; if (!connected) return; ID = frame->ID; @@ -177,8 +185,6 @@ void SerialWorker::sendFrame(const CANFrame *frame, int bus = 0) } buffer[8 + frame->len] = 0; - if (serial == NULL) return; - if (!serial->isOpen()) return; //qDebug() << "writing " << buffer.length() << " bytes to serial port"; serial->write(buffer); } @@ -187,7 +193,7 @@ void SerialWorker::sendFrame(const CANFrame *frame, int bus = 0) //Don't get carried away here. The GVRET firmware only has finite //buffers and besides, the other end will get buried in traffic. void SerialWorker::sendFrameBatch(const QList *frames) -{ +{ for (int i = 0; i < frames->length(); i++) sendFrame(&frames->at(i), frames->at(i).bus); } @@ -306,6 +312,7 @@ void SerialWorker::procRXChar(unsigned char c) //qDebug() << "emit from serial handler to main form id: " << buildFrame->ID; if (capturing) { + buildFrame->isReceived = true; canModel->addFrame(*buildFrame, false); gotFrames++; if (buildFrame->ID == targetID) emit gotTargettedFrame(canModel->rowCount() - 1); @@ -422,14 +429,17 @@ void SerialWorker::handleTick() { //qDebug() << "Tick!"; - if (!gotValidated) + if (connected) { - if (serial->isOpen()) //if it's still false we have a problem... + if (!gotValidated) { - qDebug() << "Comm validation failed. "; - closeSerialPort(); //start by stopping everything. - QTimer::singleShot(500, this, SLOT(handleReconnect())); - return; + if (serial->isOpen()) //if it's still false we have a problem... + { + qDebug() << "Comm validation failed. "; + closeSerialPort(); //start by stopping everything. + QTimer::singleShot(500, this, SLOT(handleReconnect())); + return; + } } } @@ -438,7 +448,7 @@ void SerialWorker::handleTick() emit frameUpdateTick(framesPerSec / 4, gotFrames); //sends stats to interested parties canModel->sendBulkRefresh(gotFrames); gotFrames = 0; - if (doValidation) sendCommValidation(); + if (doValidation && serial) sendCommValidation(); } void SerialWorker::handleReconnect() diff --git a/serialworker.h b/serialworker.h index e945e0b..0741fab 100644 --- a/serialworker.h +++ b/serialworker.h @@ -54,6 +54,7 @@ private slots: //we receive things in slots void handleReconnect(); public slots: + void run(); void setSerialPort(QSerialPortInfo*); void closeSerialPort(); void sendFrame(const CANFrame *, int); diff --git a/utility.h b/utility.h index 9e86fa7..42fab5e 100644 --- a/utility.h +++ b/utility.h @@ -113,6 +113,67 @@ public: { return (value1 * (1.0 - samplePoint)) + (value2 * samplePoint); } + + static int64_t processIntegerSignal(const uint8_t *data, int startBit, int sigSize, bool littleEndian, bool isSigned) + { + + int64_t result = 0; + int bit; + + if (littleEndian) + { + bit = startBit; + for (int bitpos = 0; bitpos < sigSize; bitpos++) + { + if (data[bit / 8] & (1 << (bit % 8))) + result += (1ULL << bitpos); + bit++; + } + } + else //motorola / big endian mode + { + bit = startBit; + for (int bitpos = 0; bitpos < sigSize; bitpos++) + { + if (data[bit / 8] & (1 << (bit % 8))) + result += (1ULL << (sigSize - bitpos - 1)); + + if ((bit % 8) == 0) + bit += 15; + else bit--; + + } + } + + if (isSigned) + { + int64_t mask = (1ULL << (sigSize - 1)); + if ((result & mask) == mask) //is the highest bit possible for this signal size set? + { + /* + * if so we need to also set every bit higher in the result int too. + * This leads to the below two lines that are nasty. Here's the theory behind that... + * If the value is signed and the highest bit is set then it is negative. To create + * a negative value out of this even though the variable result is 64 bit we have to + * run 1's all of the way up to bit 63 in result. -1 is all ones for whatever size integer + * you have. So, it's 64 1's in this case. + * signedMask is done this way: + * first you take the signal size and shift 1 up that far. Then subtract one. Lets + * see that for a 16 bit signal: + * (1 << 16) - 1 = the first 16 bits set as 1's. So far so good. We then negate the whole + * thing which flips all bits. Thus signedMask ends up with 1's everwhere that the signal + * doesn't take up in the 64 bit signed integer result. Then, result has an OR operation on + * it with the old value and -1 masked so that the the 1 bits from -1 don't overwrite bits from the + * actual signal. This extends the sign bits out so that the integer result reads as the proper negative + * value. We dont need to do any of this if the sign bit wasn't set. + */ + int64_t signedMask = ~((1ULL << sigSize) - 1); + result = (-1LL & signedMask) | result; + } + } + + return result; + } }; #endif // UTILITY_H