diff --git a/SavvyCAN.pro b/SavvyCAN.pro index def354a..5512e42 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -26,6 +26,7 @@ SOURCES += main.cpp\ connections/lawicel_serial.cpp \ connections/mqtt_bus.cpp \ dbc/dbcnodeduplicateeditor.cpp \ + framesenderobject.cpp \ mqtt/qmqtt_client.cpp \ mqtt/qmqtt_client_p.cpp \ mqtt/qmqtt_frame.cpp \ @@ -113,6 +114,7 @@ HEADERS += mainwindow.h \ connections/mqtt_bus.h \ dbc/dbcnodeduplicateeditor.h \ dbc/dbcnoderebaseeditor.h \ + framesenderobject.h \ mqtt/qmqtt.h \ mqtt/qmqtt_client.h \ mqtt/qmqtt_client_p.h \ diff --git a/can_trigger_structs.h b/can_trigger_structs.h index 32ef0ef..177df4f 100644 --- a/can_trigger_structs.h +++ b/can_trigger_structs.h @@ -4,6 +4,7 @@ #include "can_structs.h" #include +#include enum TriggerMask { @@ -38,6 +39,7 @@ public: //If ID is -1 then this is the temporary storage register. This is a shadow //register used to accumulate the results of a multi operation modifier. //if ID is -2 then this is a look up of our own data bytes stored in the class data. +//Of course, if the ID is positive then we grab bytes or signals from newest message with that ID class ModifierOperand { public: @@ -45,6 +47,7 @@ public: int bus; int databyte; bool notOper; //should a bitwise NOT be applied to this prior to doing the actual calculation? + QString signalName; //if ID is positive and there is text in here then we'll look up the signal and use its value }; //list of operations that can be done between the two operands @@ -73,7 +76,8 @@ public: class Modifier { public: - int destByte; + int destByte; //if -1 then target one of this ID's signals instead + QString signalName; QList operations; }; diff --git a/framesenderobject.cpp b/framesenderobject.cpp new file mode 100644 index 0000000..9212747 --- /dev/null +++ b/framesenderobject.cpp @@ -0,0 +1,453 @@ +#include "framesenderobject.h" +#include "mainwindow.h" + +FrameSenderObject::FrameSenderObject(const QVector *frames) +{ + mThread_p = new QThread(); + + statusCounter = 0; + modelFrames = frames; + dbcHandler = DBCHandler::getReference(); +} + +FrameSenderObject::~FrameSenderObject() +{ + mThread_p->quit(); + mThread_p->wait(); + delete mThread_p; +} + +void FrameSenderObject::piStart() +{ + sendingTimer = new QTimer(); + sendingTimer->setTimerType(Qt::PreciseTimer); + sendingTimer->setInterval(1); + + connect(sendingTimer, &QTimer::timeout, this, &FrameSenderObject::timerTriggered); +} + +void FrameSenderObject::piStop() +{ + sendingTimer->stop(); + delete sendingTimer; +} + +void FrameSenderObject::initialize() +{ + if( mThread_p && (mThread_p != QThread::currentThread()) ) + { + /* move ourself to the thread */ + moveToThread(mThread_p); /*TODO handle errors */ + /* connect started() */ + connect(mThread_p, SIGNAL(started()), this, SLOT(initialize())); + /* start the thread */ + mThread_p->start(QThread::HighPriority); + return; + } + + /* set started flag */ + //mStarted = true; + + connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int))); + + /* in multithread case, this will be called before entering thread event loop */ + return piStart(); + +} + +void FrameSenderObject::finalize() +{ + /* 1) execute in mThread_p context */ + if( mThread_p && (mThread_p != QThread::currentThread()) ) + { + /* if thread is finished, it means we call this function for the second time so we can leave */ + if( !mThread_p->isFinished() ) + { + /* we need to call piStop() */ + QMetaObject::invokeMethod(this, "finalize", + Qt::BlockingQueuedConnection); + /* 3) stop thread */ + mThread_p->quit(); + if(!mThread_p->wait()) { + qDebug() << "can't stop thread"; + } + } + return; + } + + /* 2) call piStop in mThread context */ + return piStop(); +} + +void FrameSenderObject::startSending() +{ + /* make sure we execute in mThread context */ + if( mThread_p && (mThread_p != QThread::currentThread()) ) { + QMetaObject::invokeMethod(this, "startSending", + Qt::BlockingQueuedConnection); + return; + } + + sendingElapsed.start(); + sendingTimer->start(); +} + +void FrameSenderObject::stopSending() +{ + /* make sure we execute in mThread context */ + if( mThread_p && (mThread_p != QThread::currentThread()) ) { + QMetaObject::invokeMethod(this, "stopPlayback", + Qt::BlockingQueuedConnection); + return; + } + + sendingTimer->stop(); //pushing this button halts automatic playback + //emit statusUpdate(currentPosition); +} + +void FrameSenderObject::addSendRecord(FrameSendData record) +{ + /* make sure we execute in mThread context */ + if( mThread_p && (mThread_p != QThread::currentThread()) ) { + QMetaObject::invokeMethod(this, "addSendRecord", + Qt::BlockingQueuedConnection, + Q_ARG(FrameSendData, record)); + return; + } + sendingData.append(record); +} + +void FrameSenderObject::removeSendRecord(int idx) +{ + /* make sure we execute in mThread context */ + if( mThread_p && (mThread_p != QThread::currentThread()) ) { + QMetaObject::invokeMethod(this, "removeSendRecord", + Qt::BlockingQueuedConnection, + Q_ARG(int, idx)); + return; + } + sendingData.removeAt(idx); +} + +/* + * Note: Getting a reference to an item in a QList is a dangerous thing. If anyone + * or anything happens to reallocate the list you're screwed. So, call this, do the + * work, and drop the reference as soon as possible. +*/ +FrameSendData *FrameSenderObject::getSendRecordRef(int idx) +{ + if (idx < 0 || idx >= sendingData.count()) return nullptr; + return &sendingData[idx]; +} + +/// +/// Called every millisecond to set the system update figures and send frames if necessary. +/// +/// +/// +/// +void FrameSenderObject::timerTriggered() +{ + FrameSendData *sendData; + Trigger *trigger; + + sendingList.clear(); + if(mutex.tryLock()) + { + //get elapsed microseconds since last tick (in case timer skips or is otherwise inaccurate, though there are no guarantees about elapsed timer either) + quint64 elapsed = sendingElapsed.nsecsElapsed() / 1000; + if (elapsed == 0) elapsed = 1; + sendingElapsed.start(); + sendingLastTimeStamp += elapsed; + //qDebug() << playbackLastTimeStamp; + //qDebug() << "El: " << elapsed; + statusCounter++; + for (int i = 0; i < sendingData.count(); i++) + { + sendData = &sendingData[i]; + if (!sendData->enabled) + { + if (sendData->triggers.count() > 0) + { + for (int j = 0; j < sendData->triggers.count(); j++) //resetting currCount when line is disabled + { + sendData->triggers[j].currCount = 0; + } + } + continue; //abort any processing on this if it is not enabled. + } + if (sendData->triggers.count() == 0) + { + qDebug() << "No triggers to process"; + break; + } + for (int j = 0; j < sendData->triggers.count(); j++) + { + trigger = &sendData->triggers[j]; + if (trigger->currCount >= trigger->maxCount) continue; //don't process if we've sent max frames we were supposed to + if (!trigger->readyCount) continue; //don't tick if not ready to tick + //is it time to fire? + trigger->msCounter += elapsed; //gives proper tracking even if timer doesn't fire as fast as it should + if (trigger->msCounter >= trigger->milliseconds) + { + trigger->msCounter -= trigger->milliseconds; + sendData->count++; + trigger->currCount++; + doModifiers(i); + //updateGridRow(i); + //qDebug() << "About to try to send a frame"; + //CANConManager::getInstance()->sendFrame(sendingData[i]); + sendingList.append(sendingData[i]); //queue it instead of immediate sending + if (trigger->ID > 0) trigger->readyCount = false; //reset flag if this is a timed ID trigger + } + } + } + + //if we have any frames to send after the above then send as a batch + if (sendingList.count() > 0) CANConManager::getInstance()->sendFrames(sendingList); + mutex.unlock(); + } + else + { + qDebug() << "framesenderobject::handleTick() couldn't get mutex, elapsed is: " << sendingElapsed.elapsed(); + } +} + +void FrameSenderObject::buildFrameCache() +{ + CANFrame thisFrame; + frameCache.clear(); + for (int i = 0; i < modelFrames->count(); i++) + { + thisFrame = modelFrames->at(i); + if (!frameCache.contains(thisFrame.frameId())) + { + frameCache.insert(thisFrame.frameId(), thisFrame); + } + else + { + frameCache[thisFrame.frameId()] = thisFrame; + } + } +} + +//remember, negative numbers are special -1 = all frames deleted, -2 = totally new set of frames. +void FrameSenderObject::updatedFrames(int numFrames) +{ + CANFrame thisFrame; + if (numFrames == -1) //all frames deleted. + { + } + else if (numFrames == -2) //all new set of frames. + { + buildFrameCache(); + } + else //just got some new frames. See if they are relevant. + { + if (numFrames > modelFrames->count()) return; + qDebug() << "New frames in sender window"; + //run through the supposedly new frames in order + for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) + { + thisFrame = modelFrames->at(i); + if (!frameCache.contains(thisFrame.frameId())) + { + frameCache.insert(thisFrame.frameId(), thisFrame); + } + else + { + frameCache[thisFrame.frameId()] = thisFrame; + } + processIncomingFrame(&thisFrame); + } + } +} + +void FrameSenderObject::processIncomingFrame(CANFrame *frame) +{ + for (int sd = 0; sd < sendingData.count(); sd++) + { + if (sendingData[sd].triggers.count() == 0) continue; + bool passedChecks = true; + for (int trig = 0; trig < sendingData[sd].triggers.count(); trig++) + { + Trigger *thisTrigger = &sendingData[sd].triggers[trig]; + //need to ensure that this trigger is actually related to frames incoming. + //Otherwise ignore it here. Only frames that have BUS and/or ID set as trigger will work here. + if (!(thisTrigger->triggerMask & (TriggerMask::TRG_BUS | TriggerMask::TRG_ID) ) ) continue; + + passedChecks = true; + //qDebug() << "Trigger ID: " << thisTrigger->ID; + //qDebug() << "Frame ID: " << frame->frameId(); + + //Check to see if we have a bus trigger condition and if so does it match + if (thisTrigger->bus != frame->bus && (thisTrigger->triggerMask & TriggerMask::TRG_BUS) ) + passedChecks = false; + + //check to see if we have an ID trigger condition and if so does it match + if ((thisTrigger->triggerMask & TriggerMask::TRG_ID) && (uint32_t)thisTrigger->ID != frame->frameId() ) + passedChecks = false; + + //check to see if we're limiting the trigger by max count and have we reached that count? + if ( (thisTrigger->triggerMask & TriggerMask::TRG_COUNT) && (thisTrigger->currCount >= thisTrigger->maxCount) ) + passedChecks = false; + + //if the above passed then are we triggering not only on ID but also signal? + if (passedChecks && (thisTrigger->triggerMask & (TriggerMask::TRG_SIGNAL | TriggerMask::TRG_ID) ) ) + { + bool sigCheckPassed = false; + DBC_MESSAGE *msg = dbcHandler->findMessage(thisTrigger->ID); + if (msg) + { + DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(thisTrigger->sigName); + if (sig) + { + //first of all, is this signal really in this message we got? + if (sig->isSignalInMessage(*frame)) sigCheckPassed = true; + //if it was and we're also filtering on value then try that next + if (sigCheckPassed && (thisTrigger->triggerMask & TriggerMask::TRG_SIGVAL)) + { + double sigval = 0.0; + if (sig->processAsDouble(*frame, sigval)) + { + if (abs(sigval - thisTrigger->sigValueDbl) > 0.001) + { + sigCheckPassed = false; + } + } + else sigCheckPassed = false; + } + } + else sigCheckPassed = false; + } + else sigCheckPassed = false; + passedChecks &= sigCheckPassed; //passedChecks can only be true if both are + } + + //if all the above says it's OK then we'll go ahead and send that message. + //If a message that comes through here has a MS value then we use it as a delay + //after the check passes. This allows for delaying the sending of the frame if that + //is required. Otherwise, just send it immediately. + if (passedChecks) + { + if (thisTrigger->milliseconds == 0) //immediate reply + { + thisTrigger->currCount++; + sendingData[sd].count++; + doModifiers(sd); + //updateGridRow(sd); + CANConManager::getInstance()->sendFrame(sendingData[sd]); + } + else //delayed sending frame + { + thisTrigger->readyCount = true; + } + } + } + } +} + + +/// +/// given an index into the sendingData list we run the modifiers that it has set up +/// +/// The index into the sendingData list +void FrameSenderObject::doModifiers(int idx) +{ + int shadowReg = 0; //shadow register we use to accumulate results + int first=0, second=0; + + FrameSendData *sendData = &sendingData[idx]; + Modifier *mod; + ModifierOp op; + + if (sendData->modifiers.count() == 0) return; //if no modifiers just leave right now + + //qDebug() << "Executing mods"; + + for (int i = 0; i < sendData->modifiers.count(); i++) + { + mod = &sendData->modifiers[i]; + for (int j = 0; j < mod->operations.count(); j++) + { + op = mod->operations.at(j); + if (op.first.ID == -1) + { + first = shadowReg; + } + else first = fetchOperand(idx, op.first); + second = fetchOperand(idx, op.second); + switch (op.operation) + { + case ADDITION: + shadowReg = first + second; + break; + case AND: + shadowReg = first & second; + break; + case DIVISION: + shadowReg = first / second; + break; + case MULTIPLICATION: + shadowReg = first * second; + break; + case OR: + shadowReg = first | second; + break; + case SUBTRACTION: + shadowReg = first - second; + break; + case XOR: + shadowReg = first ^ second; + break; + case MOD: + shadowReg = first % second; + } + } + //Finally, drop the result into the proper data byte + QByteArray newArr(sendData->payload()); + newArr[mod->destByte] = (char) shadowReg; + sendData->setPayload(newArr); + } +} + +int FrameSenderObject::fetchOperand(int idx, ModifierOperand op) +{ + CANFrame *tempFrame = nullptr; + if (op.ID == 0) //numeric constant + { + if (op.notOper) return ~op.databyte; + else return op.databyte; + } + else if (op.ID == -2) //fetch data from a data byte within the output frame + { + if (op.notOper) return ~((unsigned char)sendingData.at(idx).payload()[op.databyte]); + else return (unsigned char)sendingData.at(idx).payload()[op.databyte]; + } + else //look up external data byte + { + tempFrame = lookupFrame(op.ID, op.bus); + if (tempFrame != nullptr) + { + if (op.notOper) return ~((unsigned char)tempFrame->payload()[op.databyte]); + else return (unsigned char)tempFrame->payload()[op.databyte]; + } + else return 0; + } +} + +/// +/// Try to find the most recent frame given the input criteria +/// +/// The ID to find +/// Which bus to look on (-1 if you don't care) +/// +CANFrame* FrameSenderObject::lookupFrame(int ID, int bus) +{ + if (!frameCache.contains(ID)) return nullptr; + + if (bus == -1 || frameCache[ID].bus == bus) return &frameCache[ID]; + + return nullptr; +} + diff --git a/framesenderobject.h b/framesenderobject.h new file mode 100644 index 0000000..47a5800 --- /dev/null +++ b/framesenderobject.h @@ -0,0 +1,81 @@ +#ifndef FRAMESENDEROBJECT_H +#define FRAMESENDEROBJECT_H + +#include +#include +#include +#include +#include +#include +#include "can_structs.h" +#include "connections/canconmanager.h" +#include "can_trigger_structs.h" +#include "dbc/dbchandler.h" + +class FrameSenderObject : public QObject +{ + Q_OBJECT + +public: + FrameSenderObject(const QVector *frames); + ~FrameSenderObject(); + +public slots: + /** + * @brief start the device. This calls piStarted + * @note starts the working thread if required (piStart is called in the working thread context) + */ + void initialize(); + + /** + * @brief stop the device. This calls piStop + * @note if a working thread is used, piStop is called before exiting the working thread + */ + void finalize(); + + void startSending(); + void stopSending(); + + void addSendRecord(FrameSendData record); + void removeSendRecord(int idx); + FrameSendData *getSendRecordRef(int idx); + +signals: + +private slots: + void timerTriggered(); + void updatedFrames(int); + +private: + QList sendingList; + int currentPosition; + QTimer *sendingTimer; + QElapsedTimer sendingElapsed; + quint64 sendingLastTimeStamp; + int statusCounter; + QList sendingData; + QThread* mThread_p; + QHash frameCache; //hash with frame ID as the key and the most recent frame as the value + const QVector *modelFrames; + bool inhibitChanged = false; + QMutex mutex; + DBCHandler *dbcHandler; + + void doModifiers(int); + int fetchOperand(int, ModifierOperand); + CANFrame* lookupFrame(int, int); + void buildFrameCache(); + void processIncomingFrame(CANFrame *frame); + + /** + * @brief starts the device + */ + void piStart(); + + /** + * @brief stops the device + */ + void piStop(); +}; + +#endif // FRAMESENDEROBJECT_H diff --git a/framesenderwindow.cpp b/framesenderwindow.cpp index a6e867c..cd4b32f 100644 --- a/framesenderwindow.cpp +++ b/framesenderwindow.cpp @@ -63,8 +63,8 @@ void FrameSenderWindow::setupGrid() ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_EXT, 50); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_REM, 50); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_DATA, 220); - ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_TRIGGER, 220); - ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_MODS, 220); + ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_TRIGGER, 270); + ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_MODS, 270); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_COUNT, 80); ui->tableSender->setHorizontalHeaderLabels(headers); } @@ -693,6 +693,12 @@ void FrameSenderWindow::processModifierText(int line) //Example line: //d0 = D0 + 1,d1 = id:0x200:d3 + id:0x200:d4 AND 0xF0 - Original version //D0=D0+1,D1=ID:0x200:D3+ID:0x200:D4&0xF0 + + //[BMS_TargetVoltage]=45 would set the value of signal BMS_TargetVoltage to 45 + + //[BMS_TargetVoltage]=[0x234:BMS_CurrentVoltage] + 4 would instead grab the value + //of BMS_CurrentVoltage from ID 0x234, add 4 to it, and set BMS_TargetVoltage to that value. + //This is certainly much harder to parse than the trigger definitions. //the left side of the = has to be D0 to D7. After that there is a string of //data. Spaces used to be required but no longer are. This makes parsing harder but data entry easier @@ -710,22 +716,33 @@ void FrameSenderWindow::processModifierText(int line) Modifier thisMod; thisMod.destByte = 0; - QString leftSide = Utility::grabAlphaNumeric(mods[i]); - if (leftSide.startsWith("D") && leftSide.length() == 2) + QRegularExpression regex; + QRegularExpressionMatch match; + + regex.setPattern("^\\[(\\w+)]=(\\w+)"); + match = regex.match(mods[i]); + if (match.hasMatch()) { - thisMod.destByte = leftSide.right(1).toInt(); + thisMod.destByte = -1; + thisMod.signalName = match.captured(1); + mods[i] = match.captured(2); thisMod.operations.clear(); } else { - qDebug() << "Something wrong with lefthand val"; - continue; - } - if (!(Utility::grabOperation(mods[i]) == "=")) - { - qDebug() << "Err: No = after lefthand val"; - continue; + QString leftSide = Utility::grabAlphaNumeric(mods[i]); + if (leftSide.startsWith("D") && leftSide.length() == 2) + { + thisMod.destByte = leftSide.right(1).toInt(); + thisMod.operations.clear(); + if (!(Utility::grabOperation(mods[i]) == "=")) + { + qDebug() << "Err: No = after lefthand val"; + continue; + } + } } + abort = false; token = Utility::grabAlphaNumeric(mods[i]); diff --git a/mainwindow.cpp b/mainwindow.cpp index 8b14149..bcc65cf 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -164,6 +164,8 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->btnExpandAll, &QAbstractButton::clicked, this, &MainWindow::expandAllRows); connect(ui->btnCollapseAll, &QAbstractButton::clicked, this, &MainWindow::collapseAllRows); + connect(ui->tableSimpleSender, SIGNAL(cellChanged(int,int)), this, SLOT(onSenderCellChanged(int,int))); + lbStatusConnected.setText(tr("Connected to 0 buses")); lbHelp.setText(tr("Press F1 on any screen for help")); lbHelp.setAlignment(Qt::AlignCenter); @@ -227,12 +229,35 @@ MainWindow::MainWindow(QWidget *parent) : ui->actionMotorControlConfig->setVisible(false); ui->actionSingle_Multi_State_2->setVisible(false); + QStringList headers; + headers << "En" << "Bus" << "ID" << "Len" << "Ext" << "Rem" << "Data" + << "Interval" << "Count"; + ui->tableSimpleSender->setColumnCount(9); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_EN, 70); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_BUS, 70); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_ID, 70); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_LEN, 70); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_EXT, 70); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_REM, 70); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_DATA, 300); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_INTERVAL, 100); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_COUNT, 100); + ui->tableSimpleSender->setHorizontalHeaderLabels(headers); + + createSenderRow(); + + frameSender = new FrameSenderObject(model->getListReference()); + + frameSender->initialize(); //creates the thread and sets things up + frameSender->startSending(); //start the timer in the object so enabled things can send + installEventFilter(this); } MainWindow::~MainWindow() { updateTimer.stop(); + frameSender->stopSending(); killEmAll(); //Ride the lightning delete ui; delete model; @@ -438,6 +463,174 @@ void MainWindow::writeSettings() } } +void MainWindow::onSenderCellChanged(int row, int col) +{ + if (inhibitSenderChanged) return; + qDebug() << "onCellChanged"; + if (row == ui->tableSimpleSender->rowCount() - 1) + { + createSenderRow(); + } + + processSenderCellChange(row, col); +} + +void MainWindow::processSenderCellChange(int line, int col) +{ + qDebug() << "processSenderCellChange"; + FrameSendData *tempData; + QStringList tokens; + int tempVal; + + int numBuses = CANConManager::getInstance()->getNumBuses(); + QByteArray arr; + + tempData = frameSender->getSendRecordRef(line); + + if (!tempData) + { + FrameSendData dat; + frameSender->addSendRecord(dat); + tempData = frameSender->getSendRecordRef(line); + } + + if (!tempData) + { + qDebug() << "No data to modify in processSenderCellChange. This is a bug!"; + return; + } + + switch (col) + { + case SIMP_COL::SC_COL_EN: //Enable check box + if (ui->tableSimpleSender->item(line, 0)->checkState() == Qt::Checked) + { + tempData->enabled = true; + } + else tempData->enabled = false; + qDebug() << "Setting enabled to " << tempData->enabled; + break; + case SIMP_COL::SC_COL_BUS: //Bus designation + tempVal = Utility::ParseStringToNum(ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_BUS)->text()); + if (tempVal < -1) tempVal = -1; + if (tempVal >= numBuses) tempVal = numBuses - 1; + tempData->bus = tempVal; + qDebug() << "Setting bus to " << tempVal; + break; + case SIMP_COL::SC_COL_ID: //ID field + tempVal = Utility::ParseStringToNum(ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_ID)->text()); + if (tempVal < 0) tempVal = 0; + if (tempVal > 0x7FFFFFFF) tempVal = 0x7FFFFFFF; + tempData->setFrameId(tempVal); + if (tempData->frameId() > 0x7FF) { + tempData->setExtendedFrameFormat(true); + ui->tableSimpleSender->blockSignals(true); + ui->tableSimpleSender->item(line, ST_COLS::SENDTAB_COL_EXT)->setCheckState(Qt::Checked); + ui->tableSimpleSender->blockSignals(false); + } + qDebug() << "setting ID to " << tempVal; + break; + case SIMP_COL::SC_COL_LEN: + tempVal = Utility::ParseStringToNum(ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_LEN)->text()); + if (tempVal < 0) tempVal = 0; + if (tempVal > 8) tempVal = 8; + arr.resize(tempVal); + tempData->setPayload(arr); + break; + case SIMP_COL::SC_COL_EXT: + if (ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_EXT)->checkState() == Qt::Checked) { + tempData->setExtendedFrameFormat(true); + } else { + tempData->setExtendedFrameFormat(false); + } + break; + case SIMP_COL::SC_COL_REM: + if (ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_REM)->checkState() == Qt::Checked) { + tempData->setFrameType(QCanBusFrame::RemoteRequestFrame); + } else { + tempData->setFrameType(QCanBusFrame::DataFrame); + } + break; + case SIMP_COL::SC_COL_DATA: //Data bytes + for (int i = 0; i < 8; i++) tempData->payload().data()[i] = 0; + +#if QT_VERSION >= QT_VERSION_CHECK( 5, 14, 0 ) + tokens = ui->tableSimpleSender->item(line, ST_COLS::SENDTAB_COL_DATA)->text().split(" ", Qt::SkipEmptyParts); +#else + tokens = ui->tableSender->item(line, ST_COLS::SENDTAB_COL_DATA)->text().split(" ", QString::SkipEmptyParts); +#endif + arr.clear(); + arr.reserve(tokens.count()); + for (int j = 0; j < tokens.count(); j++) + { + arr.append((uint8_t)Utility::ParseStringToNum(tokens[j])); + } + tempData->setPayload(arr); + break; + case SIMP_COL::SC_COL_INTERVAL: //interval in ms + + QString trigger = ui->tableSimpleSender->item(line, ST_COLS::SENDTAB_COL_TRIGGER)->text().toUpper(); + + Trigger thisTrigger; + thisTrigger.bus = -1; //-1 means we don't care which + thisTrigger.ID = -1; //the rest of these being -1 means nothing has changed it + thisTrigger.maxCount = -1; + thisTrigger.milliseconds = -1; + thisTrigger.currCount = 0; + thisTrigger.msCounter = 0; + thisTrigger.triggerMask = 0; + thisTrigger.readyCount = true; + + tempData->triggers.clear(); + tempData->triggers.reserve(1); + + if (trigger != "") + { + thisTrigger.milliseconds = Utility::ParseStringToNum(trigger); + thisTrigger.triggerMask |= TriggerMask::TRG_MS; + } + + if (thisTrigger.milliseconds < 1) thisTrigger.milliseconds = 1; + + tempData->triggers.append(thisTrigger); + + break; + } +} + + +void MainWindow::createSenderRow() +{ + int row = ui->tableSimpleSender->rowCount(); + ui->tableSimpleSender->insertRow(row); + + QTableWidgetItem *item = new QTableWidgetItem(); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + inhibitSenderChanged = true; + ui->tableSimpleSender->setItem(row, SIMP_COL::SC_COL_EN, item); + + item = new QTableWidgetItem(); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + ui->tableSimpleSender->setItem(row, SIMP_COL::SC_COL_EXT, item); + + item = new QTableWidgetItem(); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + ui->tableSimpleSender->setItem(row, SIMP_COL::SC_COL_REM, item); + + for (int i = 1; i <= SIMP_COL::SC_COL_COUNT; i++) + { + if (i != SIMP_COL::SC_COL_EXT && i != SIMP_COL::SC_COL_REM) { + item = new QTableWidgetItem(""); + ui->tableSimpleSender->setItem(row, i, item); + } + } + + inhibitSenderChanged = false; +} + void MainWindow::updateConnectionSettings(QString connectionType, QString port, int speed0, int speed1) { Q_UNUSED(connectionType); @@ -1371,6 +1564,11 @@ CANFrameModel* MainWindow::getCANFrameModel() return model; } + +/* + * All functions past this point set up the various other windows that can be opened +*/ + void MainWindow::showSettingsDialog() { if (!settingsDialog) diff --git a/mainwindow.h b/mainwindow.h index dbc7cdf..29daf73 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -10,7 +10,7 @@ #include "framefileio.h" #include "dbc/dbchandler.h" #include "bus_protocols/isotp_handler.h" - +#include "framesenderobject.h" #include "re/graphingwindow.h" #include "re/frameinfowindow.h" #include "frameplaybackwindow.h" @@ -40,6 +40,19 @@ class ConnectionWindow; class ISOTP_InterpreterWindow; class ScriptingWindow; +enum SIMP_COL +{ + SC_COL_EN = 0, + SC_COL_BUS = 1, + SC_COL_ID = 2, + SC_COL_LEN = 3, + SC_COL_EXT = 4, + SC_COL_REM = 5, + SC_COL_DATA = 6, + SC_COL_INTERVAL = 7, + SC_COL_COUNT = 8, +}; + namespace Ui { class MainWindow; } @@ -114,6 +127,7 @@ private slots: void filterClearAll(); void headerClicked (int logicalIndex); void DBCSettingsUpdated(); + void onSenderCellChanged(int, int); public slots: void gotFrames(int); @@ -142,6 +156,7 @@ private: QByteArray inputBuffer; QTimer updateTimer; QElapsedTimer *elapsedTime; + FrameSenderObject *frameSender; int framesPerSec; int rxFrames; bool inhibitFilterUpdate; @@ -151,6 +166,7 @@ private: bool CSVAbsTime; bool bDirty; //have frames been added or subtracted since the last save/load? bool useFiltered; //should sub-windows use the unfiltered or filtered frames list? + bool inhibitSenderChanged; bool continuousLogging; int continuousLogFlushCounter; @@ -211,6 +227,8 @@ private: bool eventFilter(QObject *obj, QEvent *event); void manageRowExpansion(); void disableAutoRowExpansion(); + void createSenderRow(); + void processSenderCellChange(int line, int col); }; #endif // MAINWINDOW_H diff --git a/ui/framesenderwindow.ui b/ui/framesenderwindow.ui index 4e027bd..0f0bebf 100644 --- a/ui/framesenderwindow.ui +++ b/ui/framesenderwindow.ui @@ -6,7 +6,7 @@ 0 0 - 1008 + 1350 611 diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 3b71991..80af4a8 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 800 - 682 + 1177 + 717 @@ -26,340 +26,347 @@ SavvyCAN - + - - - - 11 - false - - - - true - - - QAbstractItemView::ScrollPerPixel - - - false - - - - - + - - - true - - - Remote Connection Active: - - - Qt::AlignCenter - - - - - - - true - - - - 1000 - 50 - - - - - 150 - 50 - - - - - false - - - - 8 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 120 - 120 - 120 - - - - - - + 11 - true - true + false - - - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - Total Frames Captured: - - - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 0 - - - - Frames Per Second: - - - - - - - - 0 - 0 - - - - - - - - - - - Suspend Capturing - - - false - - - - - - - Normalize Frame Timing - - - - - - - Clear Frames - - - - - - - Keep Filters When Clearing - - - - - - - Auto Scroll Window - - - - - - + true - - Overwrite Mode + + QAbstractItemView::ScrollPerPixel - - - - - - Qt::Horizontal - - - - - - - Interpret Frames - - - - - - - Expand All Rows - - - - - - - Collapse All Rows - - - - - - - Bus Filtering: - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 175 - 40 - - - - QListView::LeftToRight - - - - - - - Frame Filtering: - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 0 - 160 - - - - - 175 - 16777215 - - - + false - + - + + + true + - All + Remote Connection Active: + + + Qt::AlignCenter - - - None + + + true + + + + 1000 + 50 + + + + + 150 + 50 + + + + + false + + + + 8 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 120 + 120 + 120 + + + + + + + + + 11 + true + true + + + + + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + Total Frames Captured: + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + Frames Per Second: + + + + + + + + 0 + 0 + + + + + + + + + + + Suspend Capturing + + + false + + + + + + + Normalize Frame Timing + + + + + + + Clear Frames + + + + + + + Keep Filters When Clearing + + + + + + + Auto Scroll Window + + + + + + + true + + + Overwrite Mode + + + + + + + Qt::Horizontal + + + + + + + Interpret Frames + + + + + + + Expand All Rows + + + + + + + Collapse All Rows + + + + + + + Bus Filtering: + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 175 + 40 + + + + QListView::LeftToRight + + + + + + + Frame Filtering: + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 0 + 160 + + + + + 175 + 16777215 + + + + false + + + + + + + + + All + + + + + + + None + + + + + + + + @@ -367,7 +374,7 @@ 0 0 - 800 + 1177 26