diff --git a/SavvyCAN.pro b/SavvyCAN.pro index 434e9c1..f24ccdc 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -6,8 +6,7 @@ QT += core gui -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets serialport printsupport - +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets serialport printsupport qml CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT CONFIG += c++11 @@ -36,7 +35,10 @@ SOURCES += main.cpp\ filecomparatorwindow.cpp \ mainsettingsdialog.cpp \ firmwareuploaderwindow.cpp \ - discretestatewindow.cpp + discretestatewindow.cpp \ + scriptingwindow.cpp \ + scriptcontainer.cpp \ + canfilter.cpp HEADERS += mainwindow.h \ can_structs.h \ @@ -61,7 +63,10 @@ HEADERS += mainwindow.h \ filecomparatorwindow.h \ mainsettingsdialog.h \ firmwareuploaderwindow.h \ - discretestatewindow.h + discretestatewindow.h \ + scriptingwindow.h \ + scriptcontainer.h \ + canfilter.h FORMS += mainwindow.ui \ graphingwindow.ui \ @@ -76,7 +81,8 @@ FORMS += mainwindow.ui \ filecomparatorwindow.ui \ mainsettingsdialog.ui \ firmwareuploaderwindow.ui \ - discretestatewindow.ui + discretestatewindow.ui \ + scriptingwindow.ui DISTFILES += 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..bd2d578 100644 --- a/canframemodel.cpp +++ b/canframemodel.cpp @@ -251,25 +251,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 +280,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 +301,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(); } } 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 90a5308..dba170f 100644 --- a/config.h +++ b/config.h @@ -1,7 +1,7 @@ #ifndef CONFIG #define CONFIG -#define VERSION 141 +#define VERSION 142 //try to keep this in sync. //SavvyCAN will complain if you connect a GVRET board with a revision 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/mainwindow.cpp b/mainwindow.cpp index bd53ef5..e57d5ea 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -86,6 +86,7 @@ MainWindow::MainWindow(QWidget *parent) : worker = new SerialWorker(model); worker->moveToThread(&serialWorkerThread); connect(&serialWorkerThread, &QThread::finished, worker, &QObject::deleteLater); + connect(&serialWorkerThread, SIGNAL(started()), worker, SLOT(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); @@ -98,7 +99,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; @@ -110,6 +111,7 @@ MainWindow::MainWindow(QWidget *parent) : settingsDialog = NULL; firmwareUploaderWindow = NULL; discreteStateWindow = NULL; + scriptingWindow = NULL; dbcHandler = new DBCHandler; bDirty = false; inhibitFilterUpdate = false; @@ -143,6 +145,7 @@ MainWindow::MainWindow(QWidget *parent) : 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->actionScripting_INterface, SIGNAL(triggered(bool)), this, SLOT(showScriptingWindow())); 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())); @@ -237,6 +240,12 @@ MainWindow::~MainWindow() delete discreteStateWindow; } + if (scriptingWindow) + { + scriptingWindow->close(); + delete scriptingWindow; + } + delete ui; delete dbcHandler; model->clearFrames(); @@ -1035,6 +1044,15 @@ void MainWindow::showSingleMultiWindow() discreteStateWindow->show(); } +void MainWindow::showScriptingWindow() +{ + if (!scriptingWindow) + { + scriptingWindow = new ScriptingWindow(model->getListReference()); + } + scriptingWindow->show(); +} + void MainWindow::showRangeWindow() { //not done yet diff --git a/mainwindow.h b/mainwindow.h index 6149657..c4c3bc5 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -19,6 +19,7 @@ #include "mainsettingsdialog.h" #include "firmwareuploaderwindow.h" #include "discretestatewindow.h" +#include "scriptingwindow.h" namespace Ui { class MainWindow; @@ -56,6 +57,7 @@ private slots: void showComparisonWindow(); void showSettingsDialog(); void showFirmwareUploaderWindow(); + void showScriptingWindow(); void exitApp(); void handleSaveDecoded(); void changeBaudRates(); @@ -121,6 +123,7 @@ private: MainSettingsDialog *settingsDialog; DiscreteStateWindow *discreteStateWindow; FirmwareUploaderWindow *firmwareUploaderWindow; + ScriptingWindow *scriptingWindow; //various private storage QLabel lbStatusConnected; diff --git a/mainwindow.ui b/mainwindow.ui index 7ce39f6..516ea6d 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -293,7 +293,7 @@ 0 0 800 - 21 + 27 @@ -311,6 +311,7 @@ + @@ -442,6 +443,11 @@ Firmware Uploader + + + Scripting Interface + + diff --git a/scriptcontainer.cpp b/scriptcontainer.cpp new file mode 100644 index 0000000..c146800 --- /dev/null +++ b/scriptcontainer.cpp @@ -0,0 +1,99 @@ +#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); + if (result.isError() && errorWidget) + { + errorWidget->clear(); + 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) +{ + qDebug() << "Called set filter"; + qDebug() << id.toInt() << "*" << mask.toInt() << "*" << bus.toInt(); +} + +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"; +} + +void ScriptContainer::sendFrame(QJSValue id, QJSValue length, QJSValue data) +{ + qDebug() << "called send frame"; +} + +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..09c4f90 --- /dev/null +++ b/scriptcontainer.h @@ -0,0 +1,39 @@ +#ifndef SCRIPTCONTAINER_H +#define SCRIPTCONTAINER_H + +#include +#include +#include + +class ScriptContainer : public QObject +{ + Q_OBJECT + +public: + ScriptContainer(); + 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 id, QJSValue length, QJSValue data); + +private slots: + void tick(); + +private: + QJSEngine scriptEngine; + QJSValue compiledScript; + QJSValue setupFunction; + QJSValue gotFrameFunction; + QJSValue tickFunction; + QTimer timer; + QListWidget *errorWidget; +}; + +#endif // SCRIPTCONTAINER_H diff --git a/scriptingwindow.cpp b/scriptingwindow.cpp new file mode 100644 index 0000000..3acc807 --- /dev/null +++ b/scriptingwindow.cpp @@ -0,0 +1,144 @@ +#include "scriptingwindow.h" +#include "ui_scriptingwindow.h" + +#include +#include + +ScriptingWindow::ScriptingWindow(const QVector *frames, QWidget *parent) : + QDialog(parent), + ui(new Ui::ScriptingWindow) +{ + ui->setupUi(this); + + modelFrames = frames; + + connect(ui->btnLoadScript, SIGNAL(pressed()), this, SLOT(loadNewScript())); + connect(ui->btnNewScript, SIGNAL(pressed()), this, SLOT(createNewScript())); + connect(ui->btnRecompile, SIGNAL(pressed()), this, SLOT(recompileScript())); + connect(ui->btnRemoveScript, SIGNAL(pressed()), this, SLOT(deleteCurrentScript())); + connect(ui->btnRevertScript, SIGNAL(pressed()), this, SLOT(revertScript())); + connect(ui->btnSaveScript, SIGNAL(pressed()), this, SLOT(saveScript())); + + currentScript = new ScriptContainer(); + currentScript->setErrorWidget(ui->listErrors); +} + +ScriptingWindow::~ScriptingWindow() +{ + delete ui; +} + +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(); + 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); + 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->toPlainText().toUtf8()); + outFile->close(); + delete outFile; + } + } +} + +void ScriptingWindow::revertScript() +{ + +} + +void ScriptingWindow::recompileScript() +{ + currentScript->scriptText = ui->txtScriptSource->toPlainText(); + currentScript->compileScript(); +} diff --git a/scriptingwindow.h b/scriptingwindow.h new file mode 100644 index 0000000..4f22260 --- /dev/null +++ b/scriptingwindow.h @@ -0,0 +1,44 @@ +#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); + ~ScriptingWindow(); + +private slots: + void loadNewScript(); + void createNewScript(); + void deleteCurrentScript(); + void refreshSourceWindow(); + void saveScript(); + void revertScript(); + void recompileScript(); + +public slots: + + +private: + 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..8b8b92a --- /dev/null +++ b/scriptingwindow.ui @@ -0,0 +1,118 @@ + + + ScriptingWindow + + + + 0 + 0 + 994 + 858 + + + + Scripting Interface + + + + + + + + Loaded Scripts + + + Qt::AlignCenter + + + + + + + + + + Load New Script + + + + + + + Create New Script + + + + + + + Remove Current Script + + + + + + + + + + + Script Editing Window + + + Qt::AlignCenter + + + + + + + false + + + + + + + + + Save + + + + + + + Revert + + + + + + + Recompile + + + + + + + + + Current Scripting Errors + + + Qt::AlignCenter + + + + + + + + + + + + + diff --git a/serialworker.cpp b/serialworker.cpp index bd821d6..9586c74 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() @@ -158,6 +158,10 @@ void SerialWorker::sendFrame(const CANFrame *frame, int bus = 0) //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(*frame, false); + gotFrames++; + if (!connected) return; ID = frame->ID; @@ -181,10 +185,6 @@ void SerialWorker::sendFrame(const CANFrame *frame, int bus = 0) if (!serial->isOpen()) return; //qDebug() << "writing " << buffer.length() << " bytes to serial port"; serial->write(buffer); - - //show our sent frames in the list too. - canModel->addFrame(frame, false); - gotFrames++; } //a simple way for another thread to pass us a bunch of frames to send. @@ -426,14 +426,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; + } } } @@ -442,7 +445,7 @@ void SerialWorker::handleTick() emit frameUpdateTick(framesPerSec / 4, gotFrames); //sends stats to interested parties canModel->sendBulkRefresh(gotFrames); gotFrames = 0; - if (doValidation) sendCommValidation(); + if (doValidation && connected) 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);