diff --git a/SavvyCAN.pro b/SavvyCAN.pro index 0064629..4137dc9 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -23,7 +23,9 @@ SOURCES += main.cpp\ frameplaybackwindow.cpp \ serialworker.cpp \ candatagrid.cpp \ - flowviewwindow.cpp + flowviewwindow.cpp \ + framesenderwindow.cpp \ + dbcloader.cpp HEADERS += mainwindow.h \ can_structs.h \ @@ -36,7 +38,11 @@ HEADERS += mainwindow.h \ frameplaybackwindow.h \ serialworker.h \ candatagrid.h \ - flowviewwindow.h + flowviewwindow.h \ + framesenderwindow.h \ + can_trigger_structs.h \ + dbc_classes.h \ + dbcloader.h FORMS += mainwindow.ui \ graphingwindow.ui \ @@ -44,7 +50,8 @@ FORMS += mainwindow.ui \ newgraphdialog.ui \ frameplaybackwindow.ui \ candatagrid.ui \ - flowviewwindow.ui + flowviewwindow.ui \ + framesenderwindow.ui DISTFILES += diff --git a/can_trigger_structs.h b/can_trigger_structs.h new file mode 100644 index 0000000..385fbb9 --- /dev/null +++ b/can_trigger_structs.h @@ -0,0 +1,77 @@ +#ifndef CAN_TRIGGER_STRUCTS_H +#define CAN_TRIGGER_STRUCTS_H + +#include "can_structs.h" + +#include + +//Stores a single trigger. +class Trigger +{ +public: + bool readyCount; //ready to do millisecond ticks? + int ID; //which ID to match against + int milliseconds; //interval for triggering + int msCounter; //how many MS have ticked since last trigger + int maxCount; //max # of these frames to trigger for + int currCount; //how many we have triggered for so far. + int bus; //which bus to monitor (-1 if we aren't picky) +}; + +//referece for a source location for a single modifier. +//If ID is zero then this is a numeric operand. The number is then +//stored in databyte +//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. +class ModifierOperand +{ +public: + int ID; + int bus; + int databyte; +}; + +//list of operations that can be done between the two operands +enum ModifierOperationType +{ + ADDITION, + SUBTRACTION, + MULTIPLICATION, + DIVISION, + AND, + OR, + XOR, + NOT +}; + +//A single modifier operation +class ModifierOp +{ +public: + ModifierOperand first; + ModifierOperand second; + ModifierOperationType operation; +}; + +//All the operations that entail a single modifier. For instance D0+1+D2 is two operations +class Modifier +{ +public: + int destByte; + QList operations; +}; + +//A single line from the data grid. Inherits from CANFrame so it stores a canbus frame +//plus the extra stuff for the data grid +class FrameSendData : public CANFrame +{ +public: + bool enabled; + int count; + QList triggers; + QList modifiers; +}; + +#endif // CAN_TRIGGER_STRUCTS_H + diff --git a/dbc_classes.h b/dbc_classes.h new file mode 100644 index 0000000..dca1f21 --- /dev/null +++ b/dbc_classes.h @@ -0,0 +1,77 @@ +#ifndef DBC_CLASSES_H +#define DBC_CLASSES_H + +#include +#include + +/*classes to encapsulate data from a DBC file. Really, the stuff of interest + are the nodes, messages, signals, attributes, and comments. + + These things sort of form a hierarchy. Nodes send and receive messages. + Messages are comprised of signals. Signals and messages have attributes. + All of them can have comments. +*/ + +enum DBC_SIG_VAL_TYPE +{ + UNSIGNED_INT, + SIGNED_INT, + SP_FLOAT, + DP_FLOAT, + STRING +}; + +enum DBC_ATTRIBUTE_VAL_TYPE +{ + HEX, + FLOAT, + QSTRING, + ENUM +}; + +class DBC_ATTRIBUTE +{ + QString name; + DBC_ATTRIBUTE_VAL_TYPE type; + double startVal; + double endVal; + QStringList enumVals; //also used for STRING type but then you're assured to have only one +}; + +class DBC_NODE +{ +public: + QString name; + QString comment; + QList attributes; +}; + +class DBC_MESSAGE +{ + int ID; + QString name; + QString comment; + int len; + DBC_NODE *sender; + QList attributes; +}; + +class DBC_SIGNAL +{ + QString name; + int startBit; + int signalSize; + bool intelByteOrder; //true is obviously little endian. False is big endian + DBC_SIG_VAL_TYPE valType; + double factor; + double bias; + double min; + double max; + DBC_NODE *receiver; + QString unitName; + QString comment; + QList attributes; +}; + +#endif // DBC_CLASSES_H + diff --git a/dbcloader.cpp b/dbcloader.cpp new file mode 100644 index 0000000..9bc96f2 --- /dev/null +++ b/dbcloader.cpp @@ -0,0 +1,7 @@ +#include "dbcloader.h" + +DBCLoader::DBCLoader(QObject *parent) : QObject(parent) +{ + +} + diff --git a/dbcloader.h b/dbcloader.h new file mode 100644 index 0000000..8693a30 --- /dev/null +++ b/dbcloader.h @@ -0,0 +1,18 @@ +#ifndef DBCLOADER_H +#define DBCLOADER_H + +#include +#include "dbc_classes.h" + +class DBCLoader : public QObject +{ + Q_OBJECT +public: + explicit DBCLoader(QObject *parent = 0); + +signals: + +public slots: +}; + +#endif // DBCLOADER_H diff --git a/framesenderwindow.cpp b/framesenderwindow.cpp new file mode 100644 index 0000000..ab6f292 --- /dev/null +++ b/framesenderwindow.cpp @@ -0,0 +1,665 @@ +#include "framesenderwindow.h" +#include "ui_framesenderwindow.h" +#include "utility.h" + +FrameSenderWindow::FrameSenderWindow(QList *frames, QWidget *parent) : + QDialog(parent), + ui(new Ui::FrameSenderWindow) +{ + ui->setupUi(this); + + modelFrames = frames; + + intervalTimer = new QTimer(); + intervalTimer->setInterval(1); + + QStringList headers; + headers << "En" << "Bus" << "ID" << "Len" << "Data" << "Trigger" << "Modifications" << "Count"; + ui->tableSender->setColumnCount(8); + ui->tableSender->setColumnWidth(0, 40); + ui->tableSender->setColumnWidth(1, 40); + ui->tableSender->setColumnWidth(2, 40); + ui->tableSender->setColumnWidth(3, 40); + ui->tableSender->setColumnWidth(4, 200); + ui->tableSender->setColumnWidth(5, 200); + ui->tableSender->setColumnWidth(6, 200); + ui->tableSender->setColumnWidth(7, 80); + ui->tableSender->setHorizontalHeaderLabels(headers); + ui->tableSender->insertRow(0); + + connect(ui->tableSender, SIGNAL(cellChanged(int,int)), this, SLOT(onCellChanged(int,int))); + +} + +FrameSenderWindow::~FrameSenderWindow() +{ + delete ui; + + intervalTimer->stop(); + delete intervalTimer; +} + +void FrameSenderWindow::onCellChanged(int row, int col) +{ + if (row == ui->tableSender->rowCount() - 1) + { + ui->tableSender->insertRow(ui->tableSender->rowCount()); + } +} + +/// +/// Called every millisecond to set the system update figures and send frames if necessary. +/// +/// +/// +void FrameSenderWindow::handleTick() +{ + FrameSendData sendData; + Trigger trigger; + Modifier modifier; + for (int i = 0; i < sendingData.count(); i++) + { + sendData = sendingData.at(i); + if (!sendData.enabled) continue; //abort any processing on this if it is not enabled. + if (sendData.triggers.count() == 0) return; + for (int j = 0; j < sendData.triggers.count(); j++) + { + trigger = sendData.triggers.at(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? + if (++trigger.msCounter >= trigger.milliseconds) + { + trigger.msCounter = 0; + sendData.count++; + trigger.currCount++; + doModifiers(i); + //updateGridRow(i); + //parent.SendCANFrame(sendingData[i], sendingData[i].bus); + if (trigger.ID > 0) trigger.readyCount = false; //reset flag if this is a timed ID trigger + } + } + } +} + +/// +/// given an index into the sendingData list we run the modifiers that it has set up +/// +/// The index into the sendingData list +void FrameSenderWindow::doModifiers(int idx) +{ + int shadowReg = 0; //shadow register we use to accumulate results + int first=0, second=0; + + FrameSendData sendData = sendingData.at(idx); + Modifier mod; + ModifierOp op; + + if (sendData.modifiers.count() == 0) return; //if no modifiers just leave right now + + for (int i = 0; i < sendData.modifiers.count(); i++) + { + mod = sendData.modifiers.at(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; + } + } + //Finally, drop the result into the proper data byte + mod.destByte = (unsigned char) shadowReg; + } +} + +int FrameSenderWindow::fetchOperand(int idx, ModifierOperand op) +{ + CANFrame *tempFrame = NULL; + if (op.ID == 0) //numeric constant + { + return op.databyte; + } + else if (op.ID == -2) + { + return sendingData.at(idx).data[op.databyte]; + } + else //look up external data byte + { + tempFrame = lookupFrame(op.ID, op.bus); + if (tempFrame != NULL) + { + return tempFrame->data[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* FrameSenderWindow::lookupFrame(int ID, int bus) +{ + CANFrame frame; + for (int a = 0; a < frameCache.count(); a++) + { + frame = frameCache.at(a); + if ((ID == frame.ID) && ((bus == frame.bus) || bus == -1)) + { + return &frame; + } + } + return NULL; +} + +/// +/// Process a single line from the dataGrid. Right now it seems to not trigger at all after the first adding of the code but that seems to maybe +/// be because whichever field you where just in will show up as nothing to the code. +/// +/// +void FrameSenderWindow::processModifierText(int line) +{ + QString modString; + int numOps; + + //Example line: + //d0 = D0 + 1,d1 = id:0x200:d3 + id:0x200:d4 AND 0xF0 + //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 that for ease of parsing will require spaces between tokens + modString = ui->tableSender->item(line, 6)->text().toUpper(); + if (modString != "") + { + QStringList mods = modString.split(','); + sendingData[line].modifiers.clear(); + sendingData[line].modifiers.reserve(mods.length()); + for (int i = 0; i < mods.length(); i++) + { + Modifier thisMod; + thisMod.destByte = 0; + //now split by space to extract tokens + QStringList modToks = mods[i].split(' '); + if (modToks.length() >= 5) //any valid modifier that this code can process has at least 5 tokens (D0 = D0 + 1) + { + //valid token assignment will have a data byte as the first token and = as the second + if (modToks[0].length() == 2 && modToks[0].startsWith('D')) + { + numOps = ((modToks.length() - 5) / 2) + 1; + thisMod.operations.clear(); + thisMod.operations.reserve(numOps); + thisMod.destByte = modToks[0].right(modToks[0].length() - 1).toInt(); + //Now start at token 2 and extract all operations. All ops past the first one + //use the implicit shadow register as the first operand.The contents of the shadow + //register are what is copied to the destination byte at the end. + //each op is of the form . first and second could have more subtokens + int currToken = 2; + QStringList firstToks, secondToks; + for (int j = 0; j < numOps; j++) + { + ModifierOp thisOp; + if (j == 0) + { + firstToks = modToks[currToken++].toUpper().split(':'); + parseOperandString(firstToks, thisOp.first); + } + else + { + thisOp.first.ID = -1; + } + thisOp.operation = parseOperation(modToks[currToken++].toUpper()); + secondToks = modToks[currToken++].toUpper().split(':'); + thisOp.second.bus = sendingData[line].bus; + thisOp.second.ID = sendingData[line].ID; + parseOperandString(secondToks, thisOp.second); + thisMod.operations.append(thisOp); + } + } + } + else + { + QStringList firstToks; + numOps = 1; + thisMod.destByte = modToks[0].right(modToks[0].length() - 1).toInt(); + ModifierOp thisOp; + thisOp.operation = ADDITION; + thisOp.second.ID = 0; + thisOp.second.databyte = 0; + firstToks = modToks[2].toUpper().split(':'); + parseOperandString(firstToks, thisOp.first); + thisMod.operations.append(thisOp); + } + + sendingData[line].modifiers.append(thisMod); + } + } + //there is no else for the modifiers. We'll accept there not being any +} + +void FrameSenderWindow::processTriggerText(int line) +{ + QString trigger; + + //Example line: + //id=0x200 5ms 10x bus0,1000ms + //trigger has two levels of syntactic parsing. First you split by comma to get each + //actual trigger. Then you split by spaces to get the tokens within each trigger + trigger = ui->tableSender->item(line, 5)->text().toUpper(); + if (trigger != "") + { + QStringList triggers = trigger.split(','); + sendingData[line].triggers.clear(); + sendingData[line].triggers.reserve(triggers.length()); + for (int k = 0; k < triggers.length(); k++) + { + Trigger thisTrigger; + //start out by setting defaults - should be moved to constructor for class Trigger. + 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.readyCount = true; + + QStringList trigToks = triggers[k].split(' '); + for (int x = 0; x < trigToks.length(); x++) + { + QString tok = trigToks.at(x); + if (tok.left(3) == "ID=") + { + thisTrigger.ID = Utility::ParseStringToNum(tok.right(tok.length() - 3)); + if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 10000000; + + if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 0; //by default don't count, just send it upon trigger + thisTrigger.readyCount = false; //won't try counting until trigger hits + } + else if (tok.endsWith("MS")) + { + thisTrigger.milliseconds = Utility::ParseStringToNum(tok.left(tok.length()-2)); + if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 10000000; + if (thisTrigger.ID == -1) thisTrigger.ID = 0; + } + else if (tok.endsWith("X")) + { + thisTrigger.maxCount = Utility::ParseStringToNum(tok.left(tok.length() - 1)); + if (thisTrigger.ID == -1) thisTrigger.ID = 0; + if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 10; + } + else if (tok.startsWith("BUS")) + { + thisTrigger.bus = Utility::ParseStringToNum(tok.right(tok.length() - 3)); + } + } + //now, find anything that wasn't set and set it to defaults + if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 10000000; + if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 100; + if (thisTrigger.ID == -1) thisTrigger.ID = 0; + sendingData[line].triggers.append(thisTrigger); + } + } + else //setup a default single shot trigger + { + Trigger thisTrigger; + thisTrigger.bus = -1; + thisTrigger.ID = 0; + thisTrigger.maxCount = 1; + thisTrigger.milliseconds = 10; + sendingData[line].triggers.append(thisTrigger); + } +} + +//Turn a set of tokens into an operand +void FrameSenderWindow::parseOperandString(QStringList tokens, ModifierOperand &operand) +{ + //example string -> bus:0:id:200:d3 + + operand.bus = -1; + operand.ID = -2; + operand.databyte = 0; + + for (int i = 0; i < tokens.length(); i++) + { + if (tokens[i] == "BUS") + { + operand.bus = Utility::ParseStringToNum(tokens[++i]); + } + else if (tokens[i] == "ID") + { + operand.ID = Utility::ParseStringToNum(tokens[++i]); + } + else if (tokens[i].length() == 2 && tokens[i].startsWith("D")) + { + operand.databyte = Utility::ParseStringToNum(tokens[i].right(tokens[i].length() - 1)); + } + else + { + operand.databyte = Utility::ParseStringToNum(tokens[i]); + operand.ID = 0; //special ID to show this is a number not a look up. + } + } +} + +ModifierOperationType FrameSenderWindow::parseOperation(QString op) +{ + if (op == "+") return ADDITION; + if (op == "-") return SUBTRACTION; + if (op == "*") return MULTIPLICATION; + if (op == "/") return DIVISION; + if (op == "&") return AND; + if (op == "AND") return AND; + if (op == "|") return OR; + if (op == "OR") return OR; + if (op == "^") return XOR; + if (op == "XOR") return XOR; + return ADDITION; +} +//The below is c# code from the old project. It won't run as-is and must be ported to C++/Qt + +/* +/// +/// Updaet the DataGridView with the newest info from sendingData +/// +/// +private void updateGridRow(int idx) +{ + FrameSendData temp = sendingData[idx]; + int gridLine = idx; + StringBuilder dataString = new StringBuilder(); + dataGridView1.Rows[gridLine].Cells[7].Value = temp.count.ToString(); + for (int i = 0; i < temp.len; i++) + { + dataString.Append("0x"); + dataString.Append(temp.data[i].ToString("X2")); + dataString.Append(" "); + } + dataGridView1.Rows[gridLine].Cells[4].Value = dataString.ToString(); +} + +public delegate void GotCANDelegate(CANFrame frame); +/// +/// Event callback for reception of canbus frames +/// +/// The frame that came in +public void GotCANFrame(CANFrame frame) +{ + if (this.InvokeRequired) + { + this.Invoke(new GotCANDelegate(GotCANFrame), frame); + } + else + { + //search list of frames and add if no frame with this ID is found + //or update if one was found. + bool found = false; + for (int a = 0; a < frames.Count; a++) + { + if ((frame.ID == frames[a].ID) && (frame.bus == frames[a].bus)) + { + found = true; + frames[a].len = frame.len; + frames[a].data = frame.data; + } + } + if (!found) + { + frames.Add(frame); + } + + //now that frame cache has been updated, try to see if this incoming frame + //satisfies any triggers + + for (int b = 0; b < sendingData.Count; b++) + { + if (sendingData[b].triggers == null) continue; + for (int c = 0; c < sendingData[b].triggers.Length; c++) + { + Trigger thisTrigger = sendingData[b].triggers[c]; + if (thisTrigger.ID > 0) + { + if ((thisTrigger.bus == frame.bus) || (thisTrigger.bus == -1)) + { + if (thisTrigger.ID == frame.ID) + { + //seems to match this trigger. + if (thisTrigger.currCount < thisTrigger.maxCount) + { + if (thisTrigger.milliseconds == 0) //don't want to time the frame, send it right away + { + sendingData[b].triggers[c].currCount++; + sendingData[b].count++; + doModifiers(b); + updateGridRow(b); + parent.SendCANFrame(sendingData[b], sendingData[b].bus); + } + else //instead of immediate sending we start the timer + { + sendingData[b].triggers[c].readyCount = true; + } + } + } + } + } + } + } + } +} + +private void dataGridView1_RowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e) +{ + int rec = e.RowIndex; + if (rec != -1) sendingData.RemoveAt(rec); +} + +private void processCellChange(int line, int col) +{ + FrameSendData tempData; + int tempVal; + + //if this is a new line then create the base object for the line + if ((line >= sendingData.Count) || (sendingData[line] == null)) + { + tempData = new FrameSendData(); + sendingData.Add(tempData); + } + + sendingData[line].count = 0; + + switch (col) + { + case 0: //Enable check box + if (dataGridView1.Rows[line].Cells[0].Value != null) + { + sendingData[line].enabled = (bool)dataGridView1.Rows[line].Cells[0].Value; + } + else sendingData[line].enabled = false; + break; + case 1: //Bus designation + tempVal = Utility.ParseStringToNum((string)dataGridView1.Rows[line].Cells[1].Value); + if (tempVal < 0) tempVal = 0; + if (tempVal > 1) tempVal = 1; + sendingData[line].bus = tempVal; + break; + case 2: //ID field + tempVal = Utility.ParseStringToNum((string)dataGridView1.Rows[line].Cells[2].Value); + if (tempVal < 1) tempVal = 1; + if (tempVal > 0x7FFFFFFF) tempVal = 0x7FFFFFFF; + sendingData[line].ID = tempVal; + if (sendingData[line].ID <= 0x7FF) sendingData[line].extended = false; + else sendingData[line].extended = true; + break; + case 3: //length field + tempVal = Utility.ParseStringToNum((string)dataGridView1.Rows[line].Cells[3].Value); + if (tempVal < 0) tempVal = 0; + if (tempVal > 8) tempVal = 8; + sendingData[line].len = tempVal; + break; + case 4: //Data bytes + for (int i = 0; i < 8; i++) sendingData[line].data[i] = 0; + + string[] tokens = ((string)dataGridView1.Rows[line].Cells[4].Value).Split(' '); + for (int j = 0; j < tokens.Length; j++) + { + sendingData[line].data[j] = (byte)Utility.ParseStringToNum(tokens[j]); + } + break; + case 5: //triggers + processTriggerText(line); + break; + case 6: //modifiers + processModifierText(line); + break; + } +} + +private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e) +{ + processCellChange(e.RowIndex, e.ColumnIndex); +} + +private void loadFromFileToolStripMenuItem_Click(object sender, EventArgs e) +{ + DialogResult res; + //First zap all existing data. Ask first. Might not need to actually ask here since you can abort the file open + //and be OK that way too. + if (sendingData.Count > 0) + { + res = MessageBox.Show("Proceding will erase the current\ncontents of the grid.", "Warning", MessageBoxButtons.YesNo); + if (res == DialogResult.No) return; + } + res = openFileDialog1.ShowDialog(); + if (res == DialogResult.OK) + { + sendingData.Clear(); + dataGridView1.Rows.Clear(); + Stream inputFile = openFileDialog1.OpenFile(); + StreamReader inputReader = new StreamReader(inputFile); + string line; + int row; + try + { + while (!inputReader.EndOfStream) + { + line = inputReader.ReadLine(); + string[] tokens = line.Split('#'); + row = dataGridView1.Rows.Add(); + if (tokens[0] == "T") + { + dataGridView1.Rows[row].Cells[0].Value = true; + } + else dataGridView1.Rows[row].Cells[0].Value = false; + for (int j = 1; j < 7; j++) + { + dataGridView1.Rows[row].Cells[j].Value = tokens[j]; + } + for (int k = 0; k < 7; k++) processCellChange(row, k); + } + } + catch (Exception ee) + { + Debug.Print(ee.ToString()); + } + } +} + +private void saveGridToolStripMenuItem_Click(object sender, EventArgs e) +{ + StringBuilder buildStr = new StringBuilder(); + DialogResult res; + res = saveFileDialog1.ShowDialog(); + if (res == DialogResult.OK) + { + Stream outputFile = saveFileDialog1.OpenFile(); + StreamWriter outputWriter = new StreamWriter(outputFile); + try + { + for (int i = 0; i < sendingData.Count; i++) + { + buildStr.Clear(); + if (dataGridView1.Rows[i].Cells[0].Value != null) + { + if ((bool)dataGridView1.Rows[i].Cells[0].Value == true) + { + buildStr.Append("T"); + } + else buildStr.Append("F"); + } + else buildStr.Append("F"); + buildStr.Append("#"); + for (int j = 1; j < 7; j++) + { + buildStr.Append(dataGridView1.Rows[i].Cells[j].Value); + buildStr.Append("#"); + } + outputWriter.WriteLine(buildStr.ToString()); + } + } + catch (Exception ee) + { + Debug.Print(ee.ToString()); + } + outputWriter.Flush(); + outputWriter.Close(); + } +} + +//Note for the below three functions that they all handle the fact that a data view grid will always have an extra row that is blank +//and available for a new record. Because of this the grid count is always one too high. +private void button1_Click(object sender, EventArgs e) //enable all lines in the grid +{ + for (int i = 0; i < dataGridView1.Rows.Count - 1; i++) + { + dataGridView1.Rows[i].Cells[0].Value = true; + sendingData[i].enabled = true; + } +} + +private void button2_Click(object sender, EventArgs e) //disable all lines in the grid +{ + for (int i = 0; i < dataGridView1.Rows.Count - 1; i++) + { + dataGridView1.Rows[i].Cells[0].Value = false; + sendingData[i].enabled = false; + } +} + +private void button3_Click(object sender, EventArgs e) //delete all entries in the grid and data struct +{ + if (dataGridView1.Rows.Count == 1) return; + for (int i = dataGridView1.Rows.Count - 2; i >= 0; i--) + { + sendingData[i].enabled = false; + sendingData.RemoveAt(i); + dataGridView1.Rows.RemoveAt(i); + } +} + +*/ diff --git a/framesenderwindow.h b/framesenderwindow.h new file mode 100644 index 0000000..ff6ad39 --- /dev/null +++ b/framesenderwindow.h @@ -0,0 +1,41 @@ +#ifndef FRAMESENDERWINDOW_H +#define FRAMESENDERWINDOW_H + +#include +#include +#include "can_structs.h" +#include "can_trigger_structs.h" + +namespace Ui { +class FrameSenderWindow; +} + +class FrameSenderWindow : public QDialog +{ + Q_OBJECT + +public: + explicit FrameSenderWindow(QList *frames, QWidget *parent = 0); + ~FrameSenderWindow(); + +private slots: + void onCellChanged(int, int); + void handleTick(); + +private: + Ui::FrameSenderWindow *ui; + QList sendingData; + QList frameCache; + QList *modelFrames; + QTimer *intervalTimer; + + void doModifiers(int); + int fetchOperand(int, ModifierOperand); + CANFrame* lookupFrame(int, int); + void processModifierText(int); + void processTriggerText(int); + void parseOperandString(QStringList tokens, ModifierOperand&); + ModifierOperationType parseOperation(QString); +}; + +#endif // FRAMESENDERWINDOW_H diff --git a/framesenderwindow.ui b/framesenderwindow.ui new file mode 100644 index 0000000..e5ddf0b --- /dev/null +++ b/framesenderwindow.ui @@ -0,0 +1,83 @@ + + + FrameSenderWindow + + + + 0 + 0 + 1008 + 611 + + + + Frame Sender + + + + + + true + + + + + + + + + Enable All + + + + + + + Disable All + + + + + + + Clear Grid + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 80 + 20 + + + + + + + + Load to Grid + + + + + + + Save Grid + + + + + + + + + + diff --git a/mainwindow.cpp b/mainwindow.cpp index ef53f1a..1a986df 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -74,6 +74,7 @@ MainWindow::MainWindow(QWidget *parent) : frameInfoWindow = NULL; playbackWindow = NULL; flowViewWindow = NULL; + frameSenderWindow = NULL; connect(ui->btnConnect, SIGNAL(clicked(bool)), this, SLOT(connButtonPress())); connect(ui->actionOpen_Log_File, SIGNAL(triggered(bool)), this, SLOT(handleLoadFile())); @@ -84,6 +85,11 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->action_Playback, SIGNAL(triggered(bool)), this, SLOT(showPlaybackWindow())); connect(ui->btnBaudSet, SIGNAL(clicked(bool)), this, SLOT(changeBaudRates())); 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->actionEdit_Messages_Signals, SIGNAL(triggered(bool)), this, SLOT(showEditSignalsWindow())); + connect(ui->actionSave_DBC_File, SIGNAL(triggered(bool)), this, SLOT(handleSaveDBC())); + } MainWindow::~MainWindow() @@ -588,6 +594,193 @@ void MainWindow::handleSaveFile() if (dialog.selectedNameFilter() == filters[4]) saveMicrochipFile(filename); } +void MainWindow::handleLoadDBC() +{ + QString filename; + QFileDialog dialog(this); + + QStringList filters; + filters.append(QString(tr("DBC File (*.dbc)"))); + + dialog.setFileMode(QFileDialog::ExistingFile); + dialog.setNameFilters(filters); + dialog.setViewMode(QFileDialog::Detail); + + if (dialog.exec()) + { + filename = dialog.selectedFiles()[0]; + } + + //right now there is only one file type that can be loaded here so just do it. + QFile *inFile = new QFile(filename); + QString line; + QRegularExpression regex; + QRegularExpressionMatch match; + + if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) + return; + + qDebug() << "Starting DBC load"; + + while (!inFile->atEnd()) { + line = QString(inFile->readLine().toUpper().simplified()); + if (line.startsWith("BO_ ")) + { + qDebug() << "Found a BO line"; + regex.setPattern("^BO\\_ (\\w+) (\\w+) *: (\\w+) (\\w+)"); + match = regex.match(line); + //captured 1 = the ID in decimal + //captured 2 = The message name + //captured 3 = the message length + //captured 4 = the NODE responsible for this message + if (match.hasMatch()) + { + } + } + if (line.startsWith("SG_ ")) + { + qDebug() << "Found a SG line"; + regex.setPattern("^SG\\_ (\\w+) : (\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)"); + match = regex.match(line); + //captured 1 is the signal name + //captured 2 is the starting bit + //captured 3 is the length in bits + //captured 4 is the byte order / value type + //captured 5 specifies signed/unsigned for ints + //captured 6 is the scaling factor + //captured 7 is the offset + //captured 8 is the minimum value + //captured 9 is the maximum value + //captured 10 is the unit + //captured 11 is the receiving node + + if (match.hasMatch()) + { + } + } + if (line.startsWith("BU_:")) + { + qDebug() << "Found a BU line"; + regex.setPattern("^BU\\_\\:(.*)"); + match = regex.match(line); + //captured 1 = a list of node names separated by spaces. No idea how many yet + if (match.hasMatch()) + { + QStringList nodeStrings = match.captured(1).split(' '); + qDebug() << "Found " << nodeStrings.count() << " node names"; + for (int i = 0; i < nodeStrings.count(); i++) + { + qDebug() << nodeStrings[i]; + } + } + } + if (line.startsWith("CM_ SG_ ")) + { + qDebug() << "Found an SG comment line"; + regex.setPattern("^CM\\_ SG\\_ *(\\w+) *(\\w+) *\\\"(.*)\\\";"); + match = regex.match(line); + //captured 1 is the ID to match against to get to the message + //captured 2 is the signal name from that message + //captured 3 is the comment itself + if (match.hasMatch()) + { + qDebug() << "Comment was: " << match.captured(3); + } + } + if (line.startsWith("CM_ BO_ ")) + { + qDebug() << "Found a BO comment line"; + regex.setPattern("^CM\\_ BO\\_ *(\\w+) *\\\"(.*)\\\";"); + match = regex.match(line); + //captured 1 is the ID to match against to get to the message + //captured 2 is the comment itself + if (match.hasMatch()) + { + qDebug() << "Comment was: " << match.captured(2); + } + + } + if (line.startsWith("CM_ BU_ ")) + { + qDebug() << "Found a BU comment line"; + regex.setPattern("^CM\\_ BU\\_ *(\\w+) *\\\"(.*)\\\";"); + match = regex.match(line); + //captured 1 is the Node name + //captured 2 is the comment itself + if (match.hasMatch()) + { + qDebug() << "Comment was: " << match.captured(2); + } + } + //VAL_ (1090) (VCUPresentParkLightOC) (1 "Error present" 0 "Error not present") ; + if (line.startsWith("VAL_ ")) + { + qDebug() << "Found a value definition line"; + regex.setPattern("^VAL\\_ (\\w+) (\\w+) (.*);"); + match = regex.match(line); + //captured 1 is the ID to match against + //captured 2 is the signal name to match against + //captured 3 is a series of values in the form (number "text") that is, all sep'd by spaces + if (match.hasMatch()) + { + qDebug() << "Data was: " << match.captured(3); + } + } + + /* + if (line.startsWith("BA_DEF_ SG_ ")) + { + qDebug() << "Found a SG attribute line"; + regex.setPattern("^BA\\_DEF\\_ SG\\_ +\\\"([A-Za-z0-9\-_]+)\\\" +(.+);"); + match = regex.match(line); + //captured 1 is the Node name + //captured 2 is the comment itself + if (match.hasMatch()) + { + qDebug() << "Comment was: " << match.captured(2); + } + } + if (line.startsWith("BA_DEF_ BO_ ")) + { + + } + if (line.startsWith("BA_DEF_ BU_ ")) + { + + } +*/ + + + } + inFile->close(); + +} + +void MainWindow::handleSaveDBC() +{ + QString filename; + QFileDialog dialog(this); + + QStringList filters; + filters.append(QString(tr("DBC File (*.dbc)"))); + + dialog.setFileMode(QFileDialog::AnyFile); + dialog.setNameFilters(filters); + dialog.setViewMode(QFileDialog::Detail); + dialog.setAcceptMode(QFileDialog::AcceptSave); + + if (dialog.exec()) + { + filename = dialog.selectedFiles()[0]; + } + +} + +void MainWindow::showEditSignalsWindow() +{ + +} + void MainWindow::connButtonPress() { for (int x = 0; x < ports.count(); x++) @@ -616,6 +809,12 @@ void MainWindow::showFrameDataAnalysis() frameInfoWindow->show(); } +void MainWindow::showFrameSenderWindow() +{ + if (!frameSenderWindow) frameSenderWindow = new FrameSenderWindow(model->getListReference()); + frameSenderWindow->show(); +} + void MainWindow::showPlaybackWindow() { if (!playbackWindow) diff --git a/mainwindow.h b/mainwindow.h index c6d2250..7a0a33c 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -10,6 +10,7 @@ #include "frameinfowindow.h" #include "frameplaybackwindow.h" #include "flowviewwindow.h" +#include "framesenderwindow.h" namespace Ui { class MainWindow; @@ -26,12 +27,16 @@ public: private slots: void handleLoadFile(); void handleSaveFile(); + void handleLoadDBC(); + void handleSaveDBC(); + void showEditSignalsWindow(); void connButtonPress(); void showGraphingWindow(); void showFrameDataAnalysis(); void clearFrames(); void showPlaybackWindow(); void showFlowViewWindow(); + void showFrameSenderWindow(); void changeBaudRates(); public slots: @@ -52,6 +57,7 @@ private: FrameInfoWindow *frameInfoWindow; FramePlaybackWindow *playbackWindow; FlowViewWindow *flowViewWindow; + FrameSenderWindow *frameSenderWindow; void loadCRTDFile(QString); void loadNativeCSVFile(QString); void loadGenericCSVFile(QString); diff --git a/mainwindow.ui b/mainwindow.ui index f2b03a0..e38161f 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -219,6 +219,9 @@ + + + @@ -283,6 +286,21 @@ Frame Data Analysis + + + Load DBC File + + + + + Edit Messages/Signals + + + + + Save DBC File + +