#include "framesenderobject.h" #include "mainwindow.h" FrameSenderObject::FrameSenderObject(const QVector<CANFrame> *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]; } /// <summary> /// Called every millisecond to set the system update figures and send frames if necessary. /// </summary> /// <param name="sender"></param> /// <param name="timerEventArgs"></param> /// void FrameSenderObject::timerTriggered() { FrameSendData *sendData; Trigger *trigger; sendingList.clear(); if(mutex.tryLock()) { /* * Requested tick interval was 1ms but the actual interval could be wildly different. So, we track * by counting microseconds and accumulating. This creates decent stability even for long intervals */ quint64 elapsed = sendingElapsed.nsecsElapsed() / 1000ul; if (elapsed == 0) elapsed = 1; sendingElapsed.start(); sendingLastTimeStamp += elapsed; //qDebug() << sendingLastTimeStamp; //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) || (trigger->maxCount == -1) ) 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 //qDebug() << trigger->msCounter; if (trigger->msCounter >= (trigger->milliseconds * 1000)) { trigger->msCounter -= (trigger->milliseconds * 1000); 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; } } } } } /// <summary> /// given an index into the sendingData list we run the modifiers that it has set up /// </summary> /// <param name="idx">The index into the sendingData list</param> 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; } } /// <summary> /// Try to find the most recent frame given the input criteria /// </summary> /// <param name="ID">The ID to find</param> /// <param name="bus">Which bus to look on (-1 if you don't care)</param> /// <returns></returns> 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; }