#include "dbchandler.h" #include #include #include #include #include #include #include #include #include #include #include #include "utility.h" #include "connections/canconmanager.h" #include DBCHandler* DBCHandler::instance = nullptr; static bool parseValueDescriptions(QString tokenString, QList &values) { QRegularExpression regex; QRegularExpressionMatch match; DBC_VAL_ENUM_ENTRY val; values.clear(); tokenString = tokenString.trimmed(); while (tokenString.length() > 2) { regex.setPattern("^([+-]?\\d+) \\\"(.*?)\\\"(.*)"); match = regex.match(tokenString); if (!match.hasMatch()) return false; val.value = match.captured(1).toInt(); val.descript = match.captured(2); values.append(val); tokenString = match.captured(3).trimmed(); } return values.count() > 0; } DBC_SIGNAL* DBCSignalHandler::findSignalByIdx(int idx) { if (sigs.count() == 0) return nullptr; if (idx < 0) return nullptr; if (idx >= sigs.count()) return nullptr; return &sigs[idx]; } DBC_SIGNAL* DBCSignalHandler::findSignalByName(QString name) { if (sigs.count() == 0) return nullptr; for (int i = 0; i < sigs.count(); i++) { if (sigs[i].name.compare(name, Qt::CaseInsensitive) == 0) { return &sigs[i]; } } return nullptr; } bool DBCSignalHandler::addSignal(DBC_SIGNAL &sig) { sigs.append(sig); return true; } bool DBCSignalHandler::removeSignal(DBC_SIGNAL *sig) { qDebug() << "Total # of signals: " << getCount(); for (int i = 0; i < getCount(); i++) { if (sigs[i].name == sig->name) { sigs.removeAt(i); qDebug() << "Removed signal at idx " << i; } } return true; } bool DBCSignalHandler::removeSignal(int idx) { if (sigs.count() == 0) return false; if (idx < 0) return false; if (idx >= sigs.count()) return false; sigs.removeAt(idx); return true; } bool DBCSignalHandler::removeSignal(QString name) { bool foundSome = false; if (sigs.count() == 0) return false; for (int i = sigs.count() - 1; i >= 0; i--) { if (sigs[i].name.compare(name, Qt::CaseInsensitive) == 0) { sigs.removeAt(i); foundSome = true; } } return foundSome; } void DBCSignalHandler::removeAllSignals() { sigs.clear(); } int DBCSignalHandler::getCount() { return sigs.count(); } // sort by multiplex value ascending, -1 if non multiplexed/multiplexor static int signalMuxSortValue(DBC_SIGNAL &sig) { if (sig.isMultiplexed) return sig.getSimpleMultiplexValue(); return -1; } bool signal_cmp(DBC_SIGNAL a, DBC_SIGNAL b) { int muxA = signalMuxSortValue(a); int muxB = signalMuxSortValue(b); if (muxA != muxB) return muxA < muxB; if (a.startBit == b.startBit) { return QString::compare(a.name, b.name, Qt::CaseInsensitive) < 0; } else { return a.startBit < b.startBit; } } void DBCSignalHandler::sort() { std::sort(sigs.begin(), sigs.end(), signal_cmp); } DBC_MESSAGE* DBCMessageHandler::findMsgByID(uint32_t id) { if (messages.count() == 0) return nullptr; DBC_MESSAGE *bestMatch = nullptr; for (int i = 0; i < messages.count(); i++) { if ( messages[i].ID == id ) { return &messages[i]; } if (matchingCriteria == J1939) { // include data page and extended data page in the pgn uint32_t pgn = (id & 0x3FFFF00) >> 8; if ( (pgn & 0xFF00) <= 0xEF00 ) { // PDU1 format pgn &= 0x3FF00; if ((messages[i].ID & 0x3FF0000) == (pgn << 8)) { bestMatch = &messages[i]; } } else { // PDU2 format if ((messages[i].ID & 0x3FFFF00) == (pgn << 8)) { bestMatch = &messages[i]; } } } else if (matchingCriteria == GMLAN) { // Match the bits 14-26 (Arbitration Id) of GMLAN 29bit header uint32_t arbId = id &0x3FFE000; if ( (arbId != 0) && (messages[i].ID & 0x3FFE000) == arbId ) bestMatch = &messages[i]; } } return bestMatch; } DBC_MESSAGE* DBCMessageHandler::findMsgByIdx(int idx) { if (messages.count() == 0) return nullptr; if (idx < 0) return nullptr; if (idx >= messages.count()) return nullptr; return &messages[idx]; } DBC_MESSAGE* DBCMessageHandler::findMsgByName(QString name) { if (messages.count() == 0) return nullptr; for (int i = 0; i < messages.count(); i++) { if (messages[i].name.compare(name, Qt::CaseInsensitive) == 0) { return &messages[i]; } } return nullptr; } //allow for finding a message just by part of the name DBC_MESSAGE* DBCMessageHandler::findMsgByPartialName(QString name) { if (messages.count() == 0) return nullptr; for (int i = 0; i < messages.count(); i++) { if (messages[i].name.contains(name, Qt::CaseInsensitive)) { return &messages[i]; } } return nullptr; } QList DBCMessageHandler::findMsgsByNode(DBC_NODE* node) { QList messagesForNode; if (messages.count() == 0) return messagesForNode; for (int i = 0; i < messages.count(); i++) { if (messages[i].sender == node) { messagesForNode.append(&messages[i]); } } return messagesForNode; } bool DBCMessageHandler::addMessage(DBC_MESSAGE &msg) { messages.append(msg); return true; } bool DBCMessageHandler::removeMessage(DBC_MESSAGE *msg) { qDebug() << "Total # of messages: " << getCount(); for (int i = 0; i < getCount(); i++) { if (messages[i].name == msg->name) { messages.removeAt(i); qDebug() << "Removed message at idx " << i; break; } } return true; } bool DBCMessageHandler::removeMessageByIndex(int idx) { if (messages.count() == 0) return false; if (idx < 0) return false; if (idx >= messages.count()) return false; messages.removeAt(idx); return true; } bool DBCMessageHandler::removeMessage(uint32_t ID) { bool foundSome = false; if (messages.count() == 0) return false; for (int i = messages.count() - 1; i >= 0; i--) { if (messages[i].ID == ID) { messages.removeAt(i); foundSome = true; } } return foundSome; } bool DBCMessageHandler::removeMessage(QString name) { bool foundSome = false; if (messages.count() == 0) return false; for (int i = messages.count() - 1; i >= 0; i--) { if (messages[i].name.compare(name, Qt::CaseInsensitive) == 0) { messages.removeAt(i); foundSome = true; } } return foundSome; } void DBCMessageHandler::removeAllMessages() { messages.clear(); } int DBCMessageHandler::getCount() { return messages.count(); } bool msg_cmp(DBC_MESSAGE a, DBC_MESSAGE b) { return a.ID < b.ID; } void DBCMessageHandler::sort() { std::sort(messages.begin(), messages.end(), msg_cmp); for (int i = 0; i < messages.count(); i++) { messages[i].sigHandler->sort(); } } bool DBCMessageHandler::filterLabeling() { return filterLabelingEnabled; } void DBCMessageHandler::setFilterLabeling(bool filterLabeling) { filterLabelingEnabled = filterLabeling; } MatchingCriteria_t DBCMessageHandler::getMatchingCriteria() { return matchingCriteria; } void DBCMessageHandler::setMatchingCriteria(MatchingCriteria_t _matchingCriteria) { matchingCriteria = _matchingCriteria; } DBCFile::DBCFile() { messageHandler = new DBCMessageHandler; messageHandler->setMatchingCriteria(EXACT); messageHandler->setFilterLabeling(false); isDirty = false; fileName = ""; } DBCFile::DBCFile(const DBCFile& cpy) : QObject() { messageHandler = new DBCMessageHandler; for (int i = 0 ; i < cpy.messageHandler->getCount() ; i++) messageHandler->addMessage(*cpy.messageHandler->findMsgByIdx(i)); messageHandler->setMatchingCriteria(cpy.messageHandler->getMatchingCriteria()); messageHandler->setFilterLabeling(cpy.messageHandler->filterLabeling()); fileName = cpy.fileName; filePath = cpy.filePath; assocBuses = cpy.assocBuses; dbc_nodes.clear(); dbc_nodes.append(cpy.dbc_nodes); dbc_value_tables.clear(); dbc_value_tables.append(cpy.dbc_value_tables); dbc_attributes.clear(); dbc_attributes.append(cpy.dbc_attributes); isDirty = cpy.isDirty; } DBCFile& DBCFile::operator=(const DBCFile& cpy) { if (this != &cpy) // protect against invalid self-assignment { messageHandler = cpy.messageHandler; fileName = cpy.fileName; filePath = cpy.filePath; assocBuses = cpy.assocBuses; dbc_nodes.clear(); dbc_nodes.append(cpy.dbc_nodes); dbc_value_tables.clear(); dbc_value_tables.append(cpy.dbc_value_tables); dbc_attributes.clear(); dbc_attributes.append(cpy.dbc_attributes); } return *this; } void DBCFile::sort() { std::sort(dbc_nodes.begin(), dbc_nodes.end()); //sort node names messageHandler->sort(); //sort messages, each of which sorts its signals too } DBC_NODE* DBCFile::findNodeByIdx(int idx) { if (idx < 0) return nullptr; if (idx >= dbc_nodes.count()) return nullptr; return &dbc_nodes[idx]; } DBC_NODE* DBCFile::findNodeByName(QString name) { if (dbc_nodes.length() == 0) return nullptr; for (int i = 0; i < dbc_nodes.length(); i++) { if (name.compare(dbc_nodes[i].name, Qt::CaseInsensitive) == 0) { return &dbc_nodes[i]; } } return nullptr; } DBC_NODE* DBCFile::findNodeByNameAndComment(QString fullname) { QString nameAndComment; if (dbc_nodes.length() == 0) return nullptr; for (int i = 0; i < dbc_nodes.length(); i++) { if(dbc_nodes[i].comment.isEmpty()) nameAndComment = dbc_nodes[i].name; else nameAndComment = dbc_nodes[i].name + " - " + dbc_nodes[i].comment; if (fullname.compare(nameAndComment, Qt::CaseInsensitive) == 0) { return &dbc_nodes[i]; } } return nullptr; } DBC_VAL_TABLE* DBCFile::findValueTableByName(QString name) { if (dbc_value_tables.length() == 0) return nullptr; for (int i = 0; i < dbc_value_tables.length(); i++) { if (name.compare(dbc_value_tables[i].name, Qt::CaseInsensitive) == 0) { return &dbc_value_tables[i]; } } return nullptr; } QString DBCFile::getFullFilename() { return filePath + fileName; } QString DBCFile::getFilename() { return fileName; } QString DBCFile::getFilenameNoExt() { return fileName.split(".dbc")[0]; } QString DBCFile::getPath() { return filePath; } int DBCFile::getAssocBus() { return assocBuses; } void DBCFile::setAssocBus(int bus) { if (bus < -1) return; // To allow setting bus numbers even before connection is configured, do not enforce "valid" bus numbers //int numBuses = CANConManager::getInstance()->getNumBuses(); //if (bus >= numBuses) return; assocBuses = bus; } DBC_ATTRIBUTE *DBCFile::findAttributeByName(QString name, DBC_ATTRIBUTE_TYPE type) { if (dbc_attributes.length() == 0) return nullptr; for (int i = 0; i < dbc_attributes.length(); i++) { if (dbc_attributes[i].name.compare(name, Qt::CaseInsensitive) == 0 && ((type == dbc_attributes[i].attrType) || (type == ATTR_TYPE_ANY) ) ) { return &dbc_attributes[i]; } } return nullptr; } DBC_ATTRIBUTE *DBCFile::findAttributeByIdx(int idx) { if (idx < 0) return nullptr; if (idx >= dbc_attributes.count()) return nullptr; return &dbc_attributes[idx]; } void DBCFile::findAttributesByType(DBC_ATTRIBUTE_TYPE typ, QList *list) { if (!list) return; list->clear(); foreach (DBC_ATTRIBUTE attr, dbc_attributes) { if (attr.attrType == typ) list->append(attr); } } void DBCFile::setDirtyFlag() { isDirty = true; } //BE CAREFUL HERE. Do not clear the dirty flag unless you're absolutely sure nothing has changed. //Currently the signal editor clears this flag if the entire undo buffer is emptied but still //it's possible that signals or messages were deleted or added so this is potentially not that safe //It would be better if every node, message, and signal had a dirty flag. Then the DBCFile getDirtyFlag //function could traverse the tree and see if anything is dirty. void DBCFile::clearDirtyFlag() { isDirty = false; } bool DBCFile::getDirtyFlag() { return isDirty; } DBC_MESSAGE* DBCFile::parseMessageLine(QString line) { QRegularExpression regex; QRegularExpressionMatch match; DBC_MESSAGE *msgPtr; 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()) { DBC_MESSAGE msg; uint32_t ID = match.captured(1).toULong(); //the ID is always stored in decimal format msg.ID = ID & 0x1FFFFFFFul; msg.extendedID = (ID & 0x80000000ul) ? true : false; msg.name = match.captured(2); msg.len = match.captured(3).toUInt(); msg.sender = findNodeByName(match.captured(4)); if (!msg.sender) msg.sender = findNodeByIdx(0); messageHandler->addMessage(msg); msgPtr = messageHandler->findMsgByID(msg.ID); } else msgPtr = nullptr; return msgPtr; } DBC_SIGNAL* DBCFile::parseSignalLine(QString line, DBC_MESSAGE *msg) { QRegularExpression regex; QRegularExpressionMatch match; int offset = 0; bool isMessageMultiplexor = false; //bool isMultiplexed = false; DBC_SIGNAL sig; qDebug() << "Found a SG line"; regex.setPattern("^SG\\_ *([-\\w]+) +M *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)"); match = regex.match(line); if (match.hasMatch()) { qDebug() << "Multiplexor signal"; isMessageMultiplexor = true; sig.isMultiplexor = true; } else { regex.setPattern("^SG\\_ *([-\\w]+) +m(\\d+) *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)"); match = regex.match(line); if (match.hasMatch()) { qDebug() << "Multiplexed signal"; sig.addMultiplexRange(match.captured(2).toInt(), match.captured(2).toInt()); offset = 1; } else { regex.setPattern("^SG\\_ *([-\\w]+) +m(\\d+)M *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)"); match = regex.match(line); if (match.hasMatch()) { qDebug() << "Extended Multiplexor Signal"; sig.isMultiplexor = true; //we don't set the local isMessageMultiplexor variable because this isn't the top level multiplexor sig.addMultiplexRange(match.captured(2).toInt(), match.captured(2).toInt()); offset = 1; } else { qDebug() << "standard signal"; regex.setPattern("^SG\\_ *([-\\w]+) *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)"); match = regex.match(line); sig.isMultiplexed = false; sig.isMultiplexor = false; } } } //captured 1 is the signal name //captured 2 would be multiplex value if this is a multiplex signal. Then offset the rest of these by 1 //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()) { sig.name = match.captured(1); sig.startBit = match.captured(2 + offset).toInt(); sig.signalSize = match.captured(3 + offset).toInt(); int val = match.captured(4 + offset).toInt(); if (val < 2) { if (match.captured(5 + offset) == "+") sig.valType = UNSIGNED_INT; else sig.valType = SIGNED_INT; } switch (val) { case 0: //big endian mode sig.intelByteOrder = false; break; case 1: //little endian mode sig.intelByteOrder = true; break; case 2: sig.valType = SP_FLOAT; break; case 3: sig.valType = DP_FLOAT; break; case 4: sig.valType = STRING; break; case 5: //single point float in little endian sig.valType = SP_FLOAT; sig.intelByteOrder = true; break; case 6: //double point float in little endian sig.valType = DP_FLOAT; sig.intelByteOrder = true; break; } sig.factor = match.captured(6 + offset).toDouble(); sig.bias = match.captured(7 + offset).toDouble(); sig.min = match.captured(8 + offset).toDouble(); sig.max = match.captured(9 + offset).toDouble(); sig.unitName = match.captured(10 + offset); if (match.captured(11 + offset).contains(',')) { QString tmp = match.captured(11 + offset).split(',')[0]; sig.receiver = findNodeByName(tmp); } else sig.receiver = findNodeByName(match.captured(11 + offset)); if (!sig.receiver) sig.receiver = findNodeByIdx(0); //apply default if there was no match sig.parentMessage = msg; if (msg) { msg->sigHandler->addSignal(sig); if (isMessageMultiplexor) msg->multiplexorSignal = msg->sigHandler->findSignalByName(sig.name); return msg->sigHandler->findSignalByName(sig.name); } else return nullptr; } return nullptr; } //SG_MUL_VAL_ 2024 S1_PID_0D_VehicleSpeed S1 13-13; bool DBCFile::parseSignalMultiplexValueLine(QString line) { QRegularExpression regex; QRegularExpressionMatch match; qDebug() << "Found a multiplex definition line"; regex.setPattern("^SG\\_MUL\\_VAL\\_ (\\d+) ([-\\w]+) ([-\\w]+) ((?:\\d+\\-\\d+[\\s]*[\\,]?[\\s]*)*)"); match = regex.match(line); //captured 1 is message ID //Captured 2 is signal name //Captured 3 is parent multiplexor //captured 4 is the lower bound //captured 5 is the upper bound if (match.hasMatch()) { DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toULong() & 0x1FFFFFFFUL); if (msg != nullptr) { DBC_SIGNAL *thisSignal = msg->sigHandler->findSignalByName(match.captured(2)); if (thisSignal != nullptr) { DBC_SIGNAL *parentSignal = msg->sigHandler->findSignalByName(match.captured(3)); if (parentSignal != nullptr) { const QStringList ranges = match.captured(4).split(QChar(','), Qt::SkipEmptyParts); for (const QString &range : ranges) { //now need to add "thisSignal" to the children multiplexed signals of "parentSignal" const QStringList rangeSides = range.trimmed().split(QChar('-')); if (rangeSides.count() != 2) { qDebug() << QString("Malformed range definition: '%2' found in the multiplexed signal: %1") .arg(match.captured(1).arg(range.trimmed())); return false; } int rangeMin = rangeSides.at(0).toInt(); int rangeMax = rangeSides.at(1).toInt(); thisSignal->multiplexParent = parentSignal; thisSignal->addMultiplexRange(rangeMin, rangeMax); } parentSignal->multiplexedChildren.append(thisSignal); return true; } } } } return false; } bool DBCFile::parseSignalValueTypeLine(QString line) { QRegularExpression regex; QRegularExpressionMatch match; qDebug() << "Found a signal valtype line"; regex.setPattern("^SIG\\_VALTYPE\\_ *(\\d+) *([-\\w]+) *: *(\\d+);"); match = regex.match(line); // captured 1 is the message id // captured 2 is the signal name // captured 3 is the valtype if (!match.hasMatch()) { return false; } uint32_t id = match.captured(1).toULong() & 0x1FFFFFFFUL; DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toULong() & 0x1FFFFFFFUL); if (msg == nullptr) { return false; } DBC_SIGNAL *thisSignal = msg->sigHandler->findSignalByName(match.captured(2)); if (thisSignal == nullptr) { return false; } int valType = match.captured(3).toInt(); switch (valType) { case 1: { thisSignal->valType = SP_FLOAT; break; } case 2: { thisSignal->valType = DP_FLOAT; break; } default: { return false; } } return true; } bool DBCFile::parseValueTableLine(QString line) { QRegularExpression regex; QRegularExpressionMatch match; regex.setPattern("^VAL\\_TABLE\\_ ([-\\w]+) (.*);"); match = regex.match(line); //captured 1 is the table name //captured 2 is a series of values in the form (number "text") if (match.hasMatch()) { DBC_VAL_TABLE table; table.name = match.captured(1); if (!parseValueDescriptions(match.captured(2), table.valList)) return false; DBC_VAL_TABLE *existingTable = findValueTableByName(table.name); if (existingTable) existingTable->valList = table.valList; else dbc_value_tables.append(table); return true; } return false; } bool DBCFile::parseValueLine(QString line) { QRegularExpression regex; QRegularExpressionMatch match; 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); DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toULong() & 0x1FFFFFFFul); if (msg != nullptr) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(match.captured(2)); if (sig != nullptr) { QString tokenString = match.captured(3); QList values; DBC_VAL_TABLE *valueTable = findValueTableByName(tokenString.trimmed()); if (valueTable) { sig->valList = valueTable->valList; return true; } if (parseValueDescriptions(tokenString, values)) { sig->valList.append(values); return true; } } } } return false; } bool DBCFile::parseAttributeLine(QString line) { QRegularExpression regex; QRegularExpressionMatch match; regex.setPattern("^BA\\_ \\\"*([-\\w]+)\\\"* BO\\_ (\\d+) \\\"*([#\\w]+)\\\"*"); match = regex.match(line); //captured 1 is the attribute name //captured 2 is the message ID number (frame ID) //captured 3 is the attribute value if (match.hasMatch()) { qDebug() << "Found an attribute setting line for a message"; DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1)); if (foundAttr) { qDebug() << "That message attribute does exist"; DBC_MESSAGE *foundMsg = messageHandler->findMsgByID(match.captured(2).toULong() & 0x1FFFFFFFul); if (foundMsg) { qDebug() << "It references a valid, registered message"; DBC_ATTRIBUTE_VALUE *foundAttrVal = foundMsg->findAttrValByName(match.captured(1)); if (foundAttrVal) { foundAttrVal->value = processAttributeVal(match.captured(3), foundAttr->valType); } else { DBC_ATTRIBUTE_VALUE val; val.attrName = match.captured(1); val.value = processAttributeVal(match.captured(3), foundAttr->valType); foundMsg->attributes.append(val); } } } } regex.setPattern("^BA\\_ \\\"*([-\\w]+)\\\"* SG\\_ (\\d+) \\\"*([-\\w]+)\\\"* \\\"*([#\\w]+)\\\"*"); match = regex.match(line); //captured 1 is the attribute name //captured 2 is the message ID number (frame ID) //captured 3 is the signal name to bind to //captured 4 is the attribute value if (match.hasMatch()) { qDebug() << "Found an attribute setting line for a signal"; DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1)); if (foundAttr) { qDebug() << "That signal attribute does exist"; DBC_MESSAGE *foundMsg = messageHandler->findMsgByID(match.captured(2).toULong() & 0x1FFFFFFFUL); if (foundMsg) { qDebug() << "It references a valid, registered message"; DBC_SIGNAL *foundSig = foundMsg->sigHandler->findSignalByName(match.captured(3)); if (foundSig) { DBC_ATTRIBUTE_VALUE *foundAttrVal = foundSig->findAttrValByName(match.captured(1)); if (foundAttrVal) foundAttrVal->value = processAttributeVal(match.captured(3), foundAttr->valType); else { DBC_ATTRIBUTE_VALUE val; val.attrName = match.captured(1); val.value = processAttributeVal(match.captured(3), foundAttr->valType); foundSig->attributes.append(val); } } } } } regex.setPattern("^BA\\_ \\\"*([-\\w]+)\\\"* BU\\_ \\\"*([-\\w]+)\\\"* \\\"*([#\\w]+)\\\"*"); match = regex.match(line); //captured 1 is the attribute name //captured 2 is the name of the node //captured 3 is the attribute value if (match.hasMatch()) { qDebug() << "Found an attribute setting line for a node"; DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1)); if (foundAttr) { qDebug() << "That node attribute does exist"; DBC_NODE *foundNode = findNodeByName(match.captured(2)); if (foundNode) { qDebug() << "References a valid node name"; DBC_ATTRIBUTE_VALUE *foundAttrVal = foundNode->findAttrValByName(match.captured(1)); if (foundAttrVal) foundAttrVal->value = processAttributeVal(match.captured(3), foundAttr->valType); else { DBC_ATTRIBUTE_VALUE val; val.attrName = match.captured(1); val.value = processAttributeVal(match.captured(3), foundAttr->valType); foundNode->attributes.append(val); } } return true; } } return false; } bool DBCFile::parseDefaultAttrLine(QString line) { QRegularExpression regex; QRegularExpressionMatch match; regex.setPattern("^BA\\_DEF\\_DEF\\_ \\\"*([-\\w]+)\\\"* \\\"*([#\\w]*)\\\"*"); match = regex.match(line); //captured 1 is the name of the attribute //captured 2 is the default value for that attribute if (match.hasMatch()) { qDebug() << "Found an attribute default value line, searching for an attribute named " << match.captured(1) << "with data " << match.captured(2); DBC_ATTRIBUTE *found = findAttributeByName(match.captured(1)); if (found) { switch (found->valType) { case ATTR_STRING: found->defaultValue = match.captured(2); break; case ATTR_FLOAT: found->defaultValue = match.captured(2).toFloat(); break; case ATTR_INT: found->defaultValue = match.captured(2).toInt(); break; case ATTR_ENUM: QString temp = match.captured(2); found->defaultValue = 0; for (int x = 0; x < found->enumVals.count(); x++) { if (!found->enumVals[x].compare(temp, Qt::CaseInsensitive)) { found->defaultValue = x; break; } } } qDebug() << "Matched an attribute. Setting default value to " << found->defaultValue; return true; } } return false; } bool DBCFile::loadFile(QString fileName) { QFile *inFile = new QFile(fileName); QString line, rawLine; QRegularExpression regex; QRegularExpressionMatch match; DBC_MESSAGE *currentMessage = nullptr; DBC_ATTRIBUTE attr; int numSigFaults = 0, numMsgFaults = 0; int linesSinceYield = 0; QString fileBaseName = QFileInfo(fileName).baseName(); bool inMultilineBU = false; bool inMultilineValTable = false; QString multilineValTable; qDebug() << "DBC File: " << fileName; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; qDebug() << "Could not load the file!"; return false; } qDebug() << "Starting DBC load"; dbc_nodes.clear(); dbc_value_tables.clear(); messageHandler->removeAllMessages(); messageHandler->setMatchingCriteria(EXACT); messageHandler->setFilterLabeling(false); DBC_NODE falseNode; falseNode.name = "Vector__XXX"; falseNode.comment = "Default node if none specified"; dbc_nodes.append(falseNode); while (!inFile->atEnd()) { rawLine = QString(inFile->readLine()); line = rawLine.simplified(); linesSinceYield++; if (linesSinceYield > 100) { qApp->processEvents(); } if (inMultilineBU) { if (rawLine.startsWith("\t") || rawLine.startsWith(" ")) { DBC_NODE node; node.sourceFileName = fileBaseName; node.name = line; dbc_nodes.append(node); } else inMultilineBU = false; } if (inMultilineValTable) { multilineValTable.append(" " + line); if (line.endsWith(";")) { parseValueTableLine(multilineValTable); multilineValTable.clear(); inMultilineValTable = false; } continue; } if (!inMultilineBU) { if (line.startsWith("BO_ ")) //defines a message { currentMessage = parseMessageLine(line); if (currentMessage == nullptr) numMsgFaults++; } if (line.startsWith("SG_ ")) //defines a signal { if (!parseSignalLine(line, currentMessage)) numSigFaults++; } if (line.startsWith("SG_MUL_VAL_ ")) //defines a signal multiplexing value definition { if (!parseSignalMultiplexValueLine(line)) numSigFaults++; } if (line.startsWith("SIG_VALTYPE_ ")) //defines a signal value type { if (!parseSignalValueTypeLine(line)) numSigFaults++; } if (line.startsWith("BU_:")) //line specifies the nodes on this canbus { 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 (nodeStrings[i].length() > 1) { DBC_NODE node; node.sourceFileName = fileBaseName; node.name = nodeStrings[i]; dbc_nodes.append(node); } } inMultilineBU = true; //we might be... Need to check next line. } } 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); DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toULong() & 0x1FFFFFFFul); if (msg != nullptr) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(match.captured(2)); if (sig != nullptr) { sig->comment = 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); DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toULong() & 0x1FFFFFFFul); if (msg != nullptr) { msg->comment = 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); DBC_NODE *node = findNodeByName(match.captured(1)); if (node != nullptr) { node->comment = match.captured(2); } } } if (line.startsWith("VAL_TABLE_ ")) { if (line.endsWith(";")) { parseValueTableLine(line); } else { multilineValTable = line; inMultilineValTable = true; } } //VAL_ (1090) (VCUPresentParkLightOC) (1 "Error present" 0 "Error not present") ; if (line.startsWith("VAL_ ")) { parseValueLine(line); } if (line.startsWith("BA_DEF_ SG_ ")) { qDebug() << "Found a SG attribute line"; if (parseAttribute(line.right(line.length() - 12), attr)) { //qDebug() << "Success"; attr.attrType = ATTR_TYPE_SIG; dbc_attributes.append(attr); } } else if (line.startsWith("BA_DEF_ BO_ ")) //definition of a message attribute { qDebug() << "Found a BO attribute line"; if (parseAttribute(line.right(line.length() - 12), attr)) { qDebug() << "Success"; attr.attrType = ATTR_TYPE_MESSAGE; dbc_attributes.append(attr); } } else if (line.startsWith("BA_DEF_ BU_ ")) //definition of a node attribute { qDebug() << "Found a BU attribute line"; if (parseAttribute(line.right(line.length() - 12), attr)) { //qDebug() << "Success"; attr.attrType = ATTR_TYPE_NODE; dbc_attributes.append(attr); } } else if (line.startsWith("BA_DEF_ ")) //definition of a root attribute { qDebug() << "Found a BU attribute line"; if (parseAttribute(line.right(line.length() - 9), attr)) { //qDebug() << "Success"; attr.attrType = ATTR_TYPE_GENERAL; dbc_attributes.append(attr); } } if (line.startsWith("BA_DEF_DEF_ ")) //definition of default value for an attribute { parseDefaultAttrLine(line); } //BA_ "GenMsgCycleTime" BO_ 101 100; if (line.startsWith("BA_ ")) //set value of attribute { parseAttributeLine(line); } } } //upon loading the file add our custom foreground and background color attributes if they don't exist already if (!findAttributeByName("GenMsgBackgroundColor")) { attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = QApplication::palette().color(QPalette::Base).name(); attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "GenMsgBackgroundColor"; attr.valType = ATTR_STRING; dbc_attributes.append(attr); } if (!findAttributeByName("GenMsgForegroundColor")) { attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = QApplication::palette().color(QPalette::WindowText).name(); attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "GenMsgForegroundColor"; attr.valType = ATTR_STRING; dbc_attributes.append(attr); } DBC_ATTRIBUTE *mc_attr = findAttributeByName("matchingcriteria"); if (mc_attr) { messageHandler->setMatchingCriteria((MatchingCriteria_t)mc_attr->defaultValue.toInt()); } else { messageHandler->setMatchingCriteria(EXACT); } DBC_ATTRIBUTE *fl_attr = findAttributeByName("filterlabeling"); if (fl_attr) { messageHandler->setFilterLabeling(fl_attr->defaultValue.toInt()); } else { messageHandler->setFilterLabeling(false); } QColor DefaultBG = QColor(findAttributeByName("GenMsgBackgroundColor")->defaultValue.toString()); QColor DefaultFG = QColor(findAttributeByName("GenMsgForegroundColor")->defaultValue.toString()); for (int x = 0; x < messageHandler->getCount(); x++) { DBC_MESSAGE *msg = messageHandler->findMsgByIdx(x); msg->bgColor = DefaultBG; msg->fgColor = DefaultFG; DBC_ATTRIBUTE_VALUE *thisBG = msg->findAttrValByName("GenMsgBackgroundColor"); DBC_ATTRIBUTE_VALUE *thisFG = msg->findAttrValByName("GenMsgForegroundColor"); if (thisBG) msg->bgColor = QColor(thisBG->value.toString()); if (thisFG) msg->fgColor = QColor(thisFG->value.toString()); for (int y = 0; y < msg->sigHandler->getCount(); y++) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(y); //if this doesn't have a multiplex parent set but is multiplexed then it must have used //simple multiplexing instead of any extended specification. So, fill in the multiplexor signal here //and also write the extended entry for it too. if (sig->isMultiplexed && (sig->multiplexParent == nullptr) && (msg->multiplexorSignal) ) { sig->multiplexParent = msg->multiplexorSignal; msg->multiplexorSignal->multiplexedChildren.append(sig); } if ( sig->isMultiplexed && (!msg->multiplexorSignal) ) //marked multiplexed but there is no multiplexor. { sig->isMultiplexed = false; //can't multiplex if there is no multiplexor! } } } if (numSigFaults > 0 || numMsgFaults > 0) { QMessageBox msgBox; QString msg = "DBC file loaded with errors!\n"; msg += "Number of faulty message entries: " + QString::number(numMsgFaults) + "\n"; msg += "Number of faulty signal entries: " + QString::number(numSigFaults) + "\n\n"; msg += "Faulty entries have not been loaded.\n\n"; msg += "All other entries are, however, loaded."; msgBox.setText(msg); msgBox.exec(); } inFile->close(); delete inFile; QStringList fileList = fileName.split('/'); this->fileName = fileList[fileList.length() - 1]; //whoops... same name as parameter in this function. filePath = fileName.left(fileName.length() - this->fileName.length()); assocBuses = -1; isDirty = false; return true; } QVariant DBCFile::processAttributeVal(QString input, DBC_ATTRIBUTE_VAL_TYPE typ) { QVariant out; switch (typ) { case ATTR_STRING: out = input; break; case ATTR_FLOAT: out = input.toFloat(); break; case ATTR_INT: case ATTR_ENUM: out = input.toInt(); break; } return out; } bool DBCFile::parseAttribute(QString inpString, DBC_ATTRIBUTE &attr) { bool goodAttr = false; QRegularExpression regex; QRegularExpressionMatch match; regex.setPattern("\\\"*(\\w+)\\\"* \\\"*(\\w+)\\\"* (\\d+) (\\d+)"); match = regex.match(inpString); //captured 1 is the name of the attribute to set up //captured 2 is the type of signal attribute to create. //captured 3 is the lower bound value for this attribute //captured 4 is the upper bound value for this attribute if (match.hasMatch()) { qDebug() << "Parsing an attribute with low/high values"; attr.name = match.captured(1); QString typ = match.captured(2); attr.attrType = ATTR_TYPE_SIG; attr.lower = 0; attr.upper = 0; attr.valType = ATTR_STRING; if (!typ.compare("INT", Qt::CaseInsensitive)) { qDebug() << "INT attribute named " << attr.name; attr.valType = ATTR_INT; attr.lower = match.captured(3).toInt(); attr.upper = match.captured(4).toInt(); goodAttr = true; } if (!typ.compare("FLOAT", Qt::CaseInsensitive)) { qDebug() << "FLOAT attribute named " << attr.name; attr.valType = ATTR_FLOAT; attr.lower = match.captured(3).toDouble(); attr.upper = match.captured(4).toDouble(); goodAttr = true; } if (!typ.compare("STRING", Qt::CaseInsensitive)) { qDebug() << "STRING attribute named " << attr.name; attr.valType = ATTR_STRING; goodAttr = true; } } else { regex.setPattern("\\\"*(\\w+)\\\"* \\\"*(\\w+)\\\"* (.*)"); match = regex.match(inpString); //Same as above but no upper/lower bound values. if (match.hasMatch()) { qDebug() << "Parsing an attribute without boundaries"; attr.name = match.captured(1); QString typ = match.captured(2); attr.lower = 0; attr.upper = 0; attr.attrType = ATTR_TYPE_SIG; if (!typ.compare("INT", Qt::CaseInsensitive)) { qDebug() << "INT attribute named " << attr.name; attr.valType = ATTR_INT; goodAttr = true; } if (!typ.compare("FLOAT", Qt::CaseInsensitive)) { qDebug() << "FLOAT attribute named " << attr.name; attr.valType = ATTR_FLOAT; goodAttr = true; } if (!typ.compare("STRING", Qt::CaseInsensitive)) { qDebug() << "STRING attribute named " << attr.name; attr.valType = ATTR_STRING; goodAttr = true; } if (!typ.compare("ENUM", Qt::CaseInsensitive)) { qDebug() << "ENUM attribute named " << attr.name; QStringList enumLst = match.captured(3).split(','); foreach (QString enumStr, enumLst) { attr.enumVals.append(Utility::unQuote(enumStr)); qDebug() << "Enum value: " << enumStr; } attr.valType = ATTR_ENUM; goodAttr = true; } } } return goodAttr; } bool DBCFile::saveFile(QString fileName) { // sort(); messageHandler->sort(); // sort messages, each of which sorts its signals too int nodeNumber = 1; int msgNumber = 1; int sigNumber = 1; QFile *outFile = new QFile(fileName); QString nodesOutput, msgOutput, commentsOutput, valuesOutput, valueTableOutput, extMultiplexOutput; QString defaultsOutput, attrValOutput, sigValTypeOutput; bool hasExtendedMultiplexing = false; if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) { delete outFile; return false; } //right now it outputs a standard hard coded boilerplate outFile->write("VERSION \"\"\n"); outFile->write("\n"); outFile->write("\n"); outFile->write("NS_ :\n"); outFile->write(" NS_DESC_\n"); outFile->write(" CM_\n"); outFile->write(" BA_DEF_\n"); outFile->write(" BA_\n"); outFile->write(" VAL_\n"); outFile->write(" CAT_DEF_\n"); outFile->write(" CAT_\n"); outFile->write(" FILTER\n"); outFile->write(" BA_DEF_DEF_\n"); outFile->write(" EV_DATA_\n"); outFile->write(" ENVVAR_DATA_\n"); outFile->write(" SGTYPE_\n"); outFile->write(" SGTYPE_VAL_\n"); outFile->write(" BA_DEF_SGTYPE_\n"); outFile->write(" BA_SGTYPE_\n"); outFile->write(" SIG_TYPE_REF_\n"); outFile->write(" VAL_TABLE_\n"); outFile->write(" SIG_GROUP_\n"); outFile->write(" SIG_VALTYPE_\n"); outFile->write(" SIGTYPE_VALTYPE_\n"); outFile->write(" BO_TX_BU_\n"); outFile->write(" BA_DEF_REL_\n"); outFile->write(" BA_REL_\n"); outFile->write(" BA_DEF_DEF_REL_\n"); outFile->write(" BU_SG_REL_\n"); outFile->write(" BU_EV_REL_\n"); outFile->write(" BU_BO_REL_\n"); outFile->write(" SG_MUL_VAL_\n"); outFile->write("\n"); outFile->write("BS_: \n"); //Build list of nodes line nodesOutput.append("BU_: "); for (int x = 0; x < dbc_nodes.count(); x++) { DBC_NODE node = dbc_nodes[x]; if (node.name.compare("Vector__XXX", Qt::CaseInsensitive) != 0) { if (node.name.length() < 1) //detect an empty string and fill it out with something { node.name = "NODE" + QString::number(nodeNumber); nodeNumber++; } nodesOutput.append(node.name + " "); if (node.comment.length() > 0) { commentsOutput.append("CM_ BU_ " + node.name + " \"" + node.comment + "\";\n"); } if (node.attributes.count() > 0) { foreach (DBC_ATTRIBUTE_VALUE val, node.attributes) { attrValOutput.append("BA_ \"" + val.attrName + "\" BU_ "); switch (val.value.type()) { case QVariant::Type::String: attrValOutput.append("\"" + val.value.toString() + "\";\n"); break; case QVariant::Type::Bool: attrValOutput.append(QString::number(val.value.toBool() ? 1 : 0) + ";\n"); break; default: attrValOutput.append(val.value.toString() + ";\n"); break; } } } } } nodesOutput.append("\n"); outFile->write(nodesOutput.toUtf8()); for (int x = 0; x < dbc_value_tables.count(); x++) { DBC_VAL_TABLE table = dbc_value_tables[x]; valueTableOutput.append("VAL_TABLE_ " + table.name); for (int v = 0; v < table.valList.count(); v++) { DBC_VAL_ENUM_ENTRY val = table.valList[v]; valueTableOutput.append(" " + QString::number(val.value) + " \"" + val.descript + "\""); } valueTableOutput.append(";\n"); } if (!valueTableOutput.isEmpty()) { outFile->write(valueTableOutput.toUtf8()); valueTableOutput.clear(); } //Go through all messages one at at time issuing the message line then all signals in there too. for (int x = 0; x < messageHandler->getCount(); x++) { DBC_MESSAGE *msg = messageHandler->findMsgByIdx(x); if (msg->name.length() < 1) //detect an empty string and fill it out with something { msg->name = "MSG" + QString::number(msgNumber); msgNumber++; } uint32_t ID = msg->ID; if (msg->ID > 0x7FF || msg->extendedID) { ID += 0x80000000ul; //set bit 31 if this ID is extended. } msgOutput.append("BO_ " + QString::number(ID) + " " + msg->name + ": " + QString::number(msg->len) + " " + msg->sender->name + "\n"); if (msg->comment.length() > 0) { commentsOutput.append("CM_ BO_ " + QString::number(ID) + " \"" + msg->comment + "\";\n"); } //If this message has attributes then compile them into attributes list to output later on. if (msg->attributes.count() > 0) { foreach (DBC_ATTRIBUTE_VALUE val, msg->attributes) { attrValOutput.append("BA_ \"" + val.attrName + "\" BO_ " + QString::number(ID) + " "); switch (val.value.type()) { case QVariant::Type::String: attrValOutput.append("\"" + val.value.toString() + "\";\n"); break; case QVariant::Type::Bool: attrValOutput.append(QString::number(val.value.toBool() ? 1 : 0) + ";\n"); break; default: attrValOutput.append(val.value.toString() + ";\n"); break; } } } for (int s = 0; s < msg->sigHandler->getCount(); s++) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(s); if (sig->name.length() < 1) //detect an empty string and fill it out with something { sig->name = "SIG" + QString::number(sigNumber); sigNumber++; } msgOutput.append(" SG_ " + sig->name); if (sig->isMultiplexed) { msgOutput.append(" m" + QString::number(sig->getSimpleMultiplexValue())); } if (sig->isMultiplexor) { if (!sig->isMultiplexed) msgOutput.append(" "); msgOutput.append("M"); } //check for the two telltale signs that we've got extended multiplexing going on. if (sig->isMultiplexed && sig->isMultiplexor) hasExtendedMultiplexing = true; if (sig->hasExtendedMultiplexing) hasExtendedMultiplexing = true; msgOutput.append(" : " + QString::number(sig->startBit) + "|" + QString::number(sig->signalSize) + "@"); switch (sig->valType) { case UNSIGNED_INT: if (sig->intelByteOrder) msgOutput.append("1+"); else msgOutput.append("0+"); break; case SIGNED_INT: case SP_FLOAT: case DP_FLOAT: if (sig->intelByteOrder) msgOutput.append("1-"); else msgOutput.append("0-"); break; case STRING: msgOutput.append("4-"); break; default: msgOutput.append("0-"); break; } msgOutput.append(" (" + QString::number(sig->factor) + "," + QString::number(sig->bias) + ") [" + QString::number(sig->min) + "|" + QString::number(sig->max) + "] \"" + sig->unitName + "\" " + sig->receiver->name + "\n"); if (sig->valType == SP_FLOAT || sig->valType == DP_FLOAT) sigValTypeOutput.append("SIG_VALTYPE_ " + QString::number(ID) + " " + sig->name + " : " + QString::number(sig->valType == SP_FLOAT ? 1 : 2) + ";\n"); if (sig->comment.length() > 0) { commentsOutput.append("CM_ SG_ " + QString::number(ID) + " " + sig->name + " \"" + sig->comment + "\";\n"); } //if this signal has attributes then compile them in a special list of attributes if (sig->attributes.count() > 0) { foreach (DBC_ATTRIBUTE_VALUE val, sig->attributes) { attrValOutput.append("BA_ \"" + val.attrName + "\" SG_ " + QString::number(ID) + " " + sig->name + " "); switch (val.value.type()) { case QVariant::Type::String: attrValOutput.append("\"" + val.value.toString() + "\";\n"); break; case QVariant::Type::Bool: attrValOutput.append(QString::number(val.value.toBool() ? 1 : 0) + ";\n"); break; default: attrValOutput.append(val.value.toString() + ";\n"); break; } } } if (sig->valList.count() > 0) { valuesOutput.append("VAL_ " + QString::number(ID) + " " + sig->name); for (int v = 0; v < sig->valList.count(); v++) { DBC_VAL_ENUM_ENTRY val = sig->valList[v]; valuesOutput.append(" " + QString::number(val.value) + " \"" + val.descript +"\""); } valuesOutput.append(";\n"); } } msgOutput.append("\n"); //write it out every message so the string doesn't end up too huge outFile->write(msgOutput.toUtf8()); msgOutput.clear(); //got to reset it after writing } outFile->write(commentsOutput.toUtf8()); commentsOutput.clear(); //Now dump out all of the stored attributes for (int x = 0; x < dbc_attributes.count(); x++) { msgOutput.append("BA_DEF_ "); switch (dbc_attributes[x].attrType) { case ATTR_TYPE_GENERAL: break; case ATTR_TYPE_NODE: msgOutput.append("BU_ "); break; case ATTR_TYPE_MESSAGE: msgOutput.append("BO_ "); break; case ATTR_TYPE_SIG: msgOutput.append("SG_ "); break; case ATTR_TYPE_ANY: break; } msgOutput.append("\"" + dbc_attributes[x].name + "\" "); switch (dbc_attributes[x].valType) { case ATTR_INT: msgOutput.append("INT " + QString::number(dbc_attributes[x].lower) + " " + QString::number(dbc_attributes[x].upper)); break; case ATTR_FLOAT: msgOutput.append("FLOAT " + QString::number(dbc_attributes[x].lower) + " " + QString::number(dbc_attributes[x].upper)); break; case ATTR_STRING: msgOutput.append("STRING "); break; case ATTR_ENUM: msgOutput.append("ENUM "); foreach (QString str, dbc_attributes[x].enumVals) { msgOutput.append("\"" + str + "\","); } msgOutput.truncate(msgOutput.length() - 1); //remove trailing , break; } msgOutput.append(";\n"); outFile->write(msgOutput.toUtf8()); msgOutput.clear(); //got to reset it after writing if (dbc_attributes[x].defaultValue.isValid()) { defaultsOutput.append("BA_DEF_DEF_ \"" + dbc_attributes[x].name + "\" "); switch (dbc_attributes[x].valType) { case ATTR_STRING: defaultsOutput.append("\"" + dbc_attributes[x].defaultValue.toString() + "\";\n"); break; case ATTR_ENUM: defaultsOutput.append("\"" + dbc_attributes[x].enumVals[dbc_attributes[x].defaultValue.toInt()] + "\";\n"); break; case ATTR_INT: defaultsOutput.append(QString::number(dbc_attributes[x].defaultValue.toLongLong()) + ";\n"); break; case ATTR_FLOAT: defaultsOutput.append(dbc_attributes[x].defaultValue.toString() + ";\n"); break; } } } //extended multiplexing uses SG_MUL_VAL_ to specify the relationships. If a signal is marked //as multiplexed then output a record for it. That's the most complete option. We've already //given the single value multiplex above for backward compatibility with things that don't support extended mode if (hasExtendedMultiplexing) { for (int x = 0; x < messageHandler->getCount(); x++) { DBC_MESSAGE *msg = messageHandler->findMsgByIdx(x); uint32_t ID = msg->ID; if (msg->ID > 0x7FF || msg->extendedID) { ID += 0x80000000ul; //set bit 31 if this ID is extended. } for (int s = 0; s < msg->sigHandler->getCount(); s++) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(s); if (sig->isMultiplexed) { msgOutput.append("SG_MUL_VAL_ " + QString::number(ID) + " "); msgOutput.append(sig->name + " " + sig->multiplexParent->name + " "); msgOutput.append(sig->multiplexDbcString() + ";"); msgOutput.append("\n"); extMultiplexOutput.append(msgOutput); msgOutput.clear(); //got to reset it after writing } } } } //now write out all of the accumulated comments and value tables from above outFile->write(defaultsOutput.toUtf8()); outFile->write(attrValOutput.toUtf8()); outFile->write(valuesOutput.toUtf8()); outFile->write(extMultiplexOutput.toUtf8()); if (!sigValTypeOutput.isEmpty()) outFile->write(sigValTypeOutput.toUtf8()); attrValOutput.clear(); defaultsOutput.clear(); valuesOutput.clear(); extMultiplexOutput.clear(); sigValTypeOutput.clear(); outFile->close(); delete outFile; isDirty = false; QStringList fileList = fileName.split('/'); this->fileName = fileList[fileList.length() - 1]; //whoops... same name as parameter in this function. filePath = fileName.left(fileName.length() - this->fileName.length()); return true; } void DBCHandler::saveDBCFile(int idx) { QSettings settings; if (loadedFiles.count() == 0) return; if (idx < 0) return; if (idx >= loadedFiles.count()) return; QString filename; QFileDialog dialog; QStringList filters; filters.append(QString(tr("DBC File (*.dbc)"))); dialog.setDirectory(settings.value("DBC/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::AnyFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.selectFile(loadedFiles[idx].getFullFilename()); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; if (!filename.contains('.')) filename += ".dbc"; loadedFiles[idx].saveFile(filename); settings.setValue("DBC/LoadSaveDirectory", dialog.directory().path()); } } int DBCHandler::createBlankFile() { DBCFile newFile; DBC_ATTRIBUTE attr; //add our custom attributes to the new file so that we know they're already there. attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = QApplication::palette().color(QPalette::Base).name(); qDebug() << attr.defaultValue; attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "GenMsgBackgroundColor"; attr.valType = ATTR_STRING; newFile.dbc_attributes.append(attr); attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = QApplication::palette().color(QPalette::WindowText).name(); qDebug() << attr.defaultValue; attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "GenMsgForegroundColor"; attr.valType = ATTR_STRING; newFile.dbc_attributes.append(attr); attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = 0; attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "matchingcriteria"; attr.valType = ATTR_INT; newFile.dbc_attributes.append(attr); attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = 0; attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "filterlabeling"; attr.valType = ATTR_INT; newFile.dbc_attributes.append(attr); DBC_NODE falseNode; falseNode.name = "Vector__XXX"; falseNode.comment = "Default node if none specified"; newFile.dbc_nodes.append(falseNode); newFile.setAssocBus(-1); loadedFiles.append(newFile); return loadedFiles.count(); } DBCFile* DBCHandler::loadDBCFile(QString filename) { DBCFile newFile; if (newFile.loadFile(filename)) { loadedFiles.append(newFile); } else { //createBlankFile(); } if (loadedFiles.count()> 0) return &loadedFiles.last(); else return nullptr; } //the only reason to even bother sending the index is to see if //the user wants to replace an already loaded DBC. //Otherwise add a new one. Well, always add a new one. //If a valid index is passed we'll remove that one and then commence //adding. Otherwise, just go straight to adding. DBCFile* DBCHandler::loadDBCFile(int idx) { if (idx > -1 && idx < loadedFiles.count()) removeDBCFile(idx); QString filename; QFileDialog dialog; QSettings settings; QStringList filters; filters.append(QString(tr("DBC File (*.dbc)"))); dialog.setDirectory(settings.value("DBC/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; //right now there is only one file type that can be loaded here so just do it. settings.setValue("DBC/LoadSaveDirectory", dialog.directory().path()); return loadDBCFile(filename); } return nullptr; } DBCFile* DBCHandler::loadSecretCSVFile(QString filename) { DBCFile *thisFile; DBC_MESSAGE *pMsg; DBC_SIGNAL *pSig; QByteArray line; int lineCounter = 0; createBlankFile(); thisFile = &loadedFiles.last(); QFile *inFile = new QFile(filename); if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return nullptr; } //burn first two lines. contains header line = inFile->readLine().simplified().toUpper(); line = inFile->readLine().simplified().toUpper(); while (!inFile->atEnd()) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } line = inFile->readLine().simplified().toUpper(); qDebug() << line; QList tokens = line.split(','); if (tokens.length() == 10) { //Message,CAN ID,Signal,Short Name,Start Byte,Start Bit,Len,Data,Range,Conversion //Whl_Rotational_Stat_CheckVal_CE,$0C0,Wheel Rotational Status Check Data,WhlRotatStatChkData, 0, 7, 40,PKT,N/A,N/A // 0 1 2 3 4 5 6 7 8 9 if (tokens[0].length() > 2) //start of a signal def that starts a new message { DBC_MESSAGE msg; msg.name = tokens[0]; msg.ID = tokens[1].mid(1).toLong(nullptr, 16); msg.comment = ""; msg.len = 8; msg.sender = thisFile->findNodeByIdx(0); msg.bgColor = QColor(thisFile->findAttributeByName("GenMsgBackgroundColor")->defaultValue.toString()); msg.fgColor = QColor(thisFile->findAttributeByName("GenMsgForegroundColor")->defaultValue.toString()); thisFile->messageHandler->addMessage(msg); pMsg = thisFile->messageHandler->findMsgByID(msg.ID); DBC_SIGNAL sig; sig.parentMessage = pMsg; sig.name = tokens[3]; sig.comment = tokens[2]; int startBit = (tokens[4].toInt() * 8) + tokens[5].toInt(); sig.startBit = startBit; sig.signalSize = tokens[6].toInt(); sig.intelByteOrder = false; //always for global-a? sig.receiver = thisFile->findNodeByIdx(0); QList rangeToks = tokens[8].split('-'); if (rangeToks.length() == 2) { sig.min = rangeToks[0].simplified().toDouble(); sig.max = rangeToks[1].simplified().toDouble(); } if (tokens[9].startsWith("E = N")) //not a value table, instead do scaling and bias { QList scalingToks = tokens[9].simplified().split(' '); sig.factor = scalingToks[4].toDouble(); if (scalingToks.count() > 5) { if (scalingToks[5] == "+") { sig.bias = scalingToks[6].toDouble(); } if (scalingToks[5] == "-") { sig.bias = scalingToks[6].toDouble() * 1.0; } } else { sig.factor = 1; sig.bias = 0; } } else if (tokens[9].startsWith("$")) //one or more values table entries { //$0=Inactive QList valToks = tokens[9].simplified().mid(1).split('='); DBC_VAL_ENUM_ENTRY entry; entry.value = valToks[0].toInt(); entry.descript = valToks[1]; sig.valList.append(entry); } pMsg->sigHandler->addSignal(sig); pSig = pMsg->sigHandler->findSignalByIdx(pMsg->sigHandler->getCount()-1); } // 0 1 2 3 4 5 6 7 8 9 //Message,CAN ID,Signal,Short Name,Start Byte,Start Bit,Len,Data,Range,Conversion //,,Wheel Rotational Status Check Data : Left Driven Sequence Number,WRSCD_LftDrvnSqNm, 0, 7, 2,UNM,0 - 3 ,E = N * 1 // 2 3 4 5 6 7 8 9 else if (tokens[2].length() > 2) //signal definition continuation of previous message { DBC_SIGNAL sig; sig.parentMessage = pMsg; sig.name = tokens[3]; sig.comment = tokens[2]; int startBit = (tokens[4].toInt() * 8) + tokens[5].toInt(); sig.startBit = startBit; sig.signalSize = tokens[6].toInt(); sig.intelByteOrder = false; //always for global-a? sig.receiver = thisFile->findNodeByIdx(0); QList rangeToks = tokens[8].split('-'); if (rangeToks.length() == 2) { sig.min = rangeToks[0].simplified().toDouble(); sig.max = rangeToks[1].simplified().toDouble(); } if (tokens[9].startsWith("E = N")) //not a value table, instead do scaling and bias { QList scalingToks = tokens[9].simplified().split(' '); sig.factor = scalingToks[4].toDouble(); if (scalingToks.count() > 5) { if (scalingToks[5] == "+") { sig.bias = scalingToks[6].toDouble(); } if (scalingToks[5] == "-") { sig.bias = scalingToks[6].toDouble() * 1.0; } } else { sig.bias = 0; sig.factor = 1; } } else if (tokens[9].startsWith("$")) //one or more values table entries { //$0=Inactive QList valToks = tokens[9].simplified().mid(1).split('='); DBC_VAL_ENUM_ENTRY entry; entry.value = valToks[0].toInt(); entry.descript = valToks[1]; sig.valList.append(entry); } pMsg->sigHandler->addSignal(sig); pSig = pMsg->sigHandler->findSignalByIdx(pMsg->sigHandler->getCount()-1); } else if (tokens[9].length() > 2) //additional values { //$0=Inactive QList valToks = tokens[9].simplified().mid(1).split('='); DBC_VAL_ENUM_ENTRY entry; entry.value = valToks[0].toInt(); entry.descript = valToks[1]; pSig->valList.append(entry); } } } thisFile->setDirtyFlag(); return thisFile; } DBCFile* DBCHandler::loadJSONFile(QString filename) { QSettings settings; DBCFile *thisFile; DBC_MESSAGE *pMsg; createBlankFile(); thisFile = &loadedFiles.last(); QFile *inFile = new QFile(filename); if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "Could not open JSON file for reading."; delete inFile; return nullptr; } QByteArray wholeFileData = inFile->readAll(); inFile->close(); delete inFile; //qDebug() << "Data Length: " << wholeFileData.length(); QJsonDocument jsonDoc = QJsonDocument::fromJson(wholeFileData); if (jsonDoc.isNull()) { qDebug() << "Couldn't load and parse the JSON file for some reason."; return nullptr; } qDebug() << "Loaded JSON"; QJsonObject jsonObj = jsonDoc.object(); QJsonObject jsonMessages = jsonObj["messages"].toObject(); QJsonObject::iterator iter; for (iter = jsonMessages.begin(); iter != jsonMessages.end(); iter++) { qDebug() << iter.key(); DBC_MESSAGE msg; msg.ID = static_cast(iter->toObject().find("message_id").value().toInt()); msg.name = QString(iter.key().toUtf8()); msg.len = static_cast(iter->toObject().find("length_bytes").value().toInt()); msg.sender = thisFile->findNodeByIdx(0); QString nodeName = iter->toObject().find("originNode").value().toString(); msg.sender = thisFile->findNodeByName(nodeName); msg.bgColor = QColor(thisFile->findAttributeByName("GenMsgBackgroundColor")->defaultValue.toString()); msg.fgColor = QColor(thisFile->findAttributeByName("GenMsgForegroundColor")->defaultValue.toString()); if (!msg.sender && nodeName.length() > 1) { DBC_NODE node; node.name = nodeName; thisFile->dbc_nodes.append(node); msg.sender = thisFile->findNodeByName(nodeName); } if (nodeName.length() < 2) msg.sender = thisFile->findNodeByIdx(0); thisFile->messageHandler->addMessage(msg); pMsg = thisFile->messageHandler->findMsgByID(msg.ID); if (!pMsg) { qDebug() << "pMsg was null... I have no idea how that is possible. DEBUG ME"; return nullptr; } QJsonObject jsonSigs = iter->toObject().find("signals").value().toObject(); QJsonObject::iterator sigIter; for (sigIter = jsonSigs.begin(); sigIter != jsonSigs.end(); sigIter++) { qDebug() << sigIter.key(); DBC_SIGNAL sig; QJsonObject sigObj = sigIter->toObject(); if (sigObj.isEmpty()) { qDebug() << "EMPTY!?"; } sig.name = QString(sigIter.key().toUtf8()); sig.factor = sigObj.find("scale").value().toDouble(); sig.bias = sigObj.find("offset").value().toDouble(); sig.max = sigObj.find("max").value().toDouble(); sig.min = sigObj.find("min").value().toDouble(); sig.startBit = sigObj.find("start_position").value().toInt(); sig.unitName = sigObj.find("units").value().toString(); sig.signalSize = sigObj.find("width").value().toInt(); sig.isMultiplexed = false; sig.isMultiplexor = false; sig.parentMessage = pMsg; if (!sigObj.find("mux_id")->isUndefined()) { QJsonValue muxVal = sigObj.find("mux_id").value(); sig.addMultiplexRange(muxVal.toInt(), muxVal.toInt()); } QJsonValue muxerVal = sigObj.find("is_muxer").value(); if (!muxerVal.isNull()) { if (muxerVal.toBool()) { sig.isMultiplexor = true; } } QJsonObject valuesObj = sigObj.find("value_description")->toObject(); if (!valuesObj.isEmpty()) { QJsonObject::iterator valIter; for (valIter = valuesObj.begin(); valIter != valuesObj.end(); valIter++) { DBC_VAL_ENUM_ENTRY valEnum; valEnum.value = valIter.value().toInt(); valEnum.descript = valIter.key().toUtf8(); sig.valList.append(valEnum); } } QJsonArray rxArray = sigObj.find("receivers")->toArray(); if (rxArray.size() < 1) sig.receiver = thisFile->findNodeByIdx(0); else { qDebug() << rxArray[0].toString(); sig.receiver = thisFile->findNodeByName(rxArray[0].toString()); if (!sig.receiver && rxArray[0].toString().length() > 1) { DBC_NODE node; node.name = rxArray[0].toString(); thisFile->dbc_nodes.append(node); sig.receiver = thisFile->findNodeByName(rxArray[0].toString()); } if (!sig.receiver) sig.receiver = thisFile->findNodeByIdx(0); } if (!sigObj.find("endianness").value().toString().compare("BIG")) { sig.intelByteOrder = false; } else sig.intelByteOrder = true; if (!sigObj.find("signedness").value().toString().compare("UNSIGNED")) { sig.valType = DBC_SIG_VAL_TYPE::UNSIGNED_INT; } else sig.valType = DBC_SIG_VAL_TYPE::SIGNED_INT; pMsg->sigHandler->addSignal(sig); if (sig.isMultiplexor) //if this signal was the multiplexor then store that info { DBC_SIGNAL *pSig = pMsg->sigHandler->findSignalByName(sig.name); if (pSig) pMsg->multiplexorSignal = pSig; } } } for (int x = 0; x < thisFile->messageHandler->getCount(); x++) { DBC_MESSAGE *msg = thisFile->messageHandler->findMsgByIdx(x); for (int y = 0; y < msg->sigHandler->getCount(); y++) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(y); //if this doesn't have a multiplex parent set but is multiplexed then it must have used //simple multiplexing instead of any extended specification. So, fill in the multiplexor signal here //and also write the extended entry for it too. if (sig->isMultiplexed && (sig->multiplexParent == nullptr) ) { sig->multiplexParent = msg->multiplexorSignal; msg->multiplexorSignal->multiplexedChildren.append(sig); } } } thisFile->setDirtyFlag(); return thisFile; } void DBCHandler::removeDBCFile(int idx) { if (loadedFiles.count() == 0) return; if (idx < 0) return; if (idx >= loadedFiles.count()) return; loadedFiles.removeAt(idx); } void DBCHandler::removeAllFiles() { loadedFiles.clear(); } void DBCHandler::swapFiles(int pos1, int pos2) { if (loadedFiles.count() < 2) return; if (pos1 < 0) return; if (pos1 >= loadedFiles.count()) return; if (pos2 < 0) return; if (pos2 >= loadedFiles.count()) return; loadedFiles.swapItemsAt(pos1, pos2); } /* * Convenience function that encapsulates a whole lot of the details. * You give it a canbus frame and it'll tell you whether there is a loaded DBC file that can * interpret that frame for you. * Returns nullptr if there is no message definition that matches. */ DBC_MESSAGE* DBCHandler::findMessage(const CANFrame &frame) { for(int i = 0; i < loadedFiles.count(); i++) { if (loadedFiles[i].getAssocBus() == -1 || frame.bus == loadedFiles[i].getAssocBus()) { DBC_MESSAGE* msg = loadedFiles[i].messageHandler->findMsgByID(frame.frameId()); if (msg != nullptr) return msg; } } return nullptr; } DBC_MESSAGE* DBCHandler::findMessage(uint32_t id) { for(int i = 0; i < loadedFiles.count(); i++) { DBC_MESSAGE* msg = loadedFiles[i].messageHandler->findMsgByID(id); if (msg != nullptr) { return msg; } } return nullptr; } // This function won't care which bus the DBC file is associated, but will return any message as long as ID matches and the file // has filter labeling enabled. // Returns the found message as well as the matching criteria (exact/J1939/GMLAN) // Used for quickly populating the Frame Filtering section with interpreted values DBC_MESSAGE* DBCHandler::findMessageForFilter(uint32_t id, MatchingCriteria_t * matchingCriteria) { for(int i = 0; i < loadedFiles.count(); i++) { if (loadedFiles[i].messageHandler->filterLabeling()) { DBC_MESSAGE* msg = loadedFiles[i].messageHandler->findMsgByID(id); if (msg != nullptr) { if (matchingCriteria) *matchingCriteria = loadedFiles[i].messageHandler->getMatchingCriteria(); return msg; } } } return nullptr; } /* * As above, a real shortcut function that searches all files in order to try to find a message with the given name * This has pitfalls because the same message name can easily exist in multiple dbc files AND nodes in a single file * By DBC standards only the MSG ID is required to be unique */ DBC_MESSAGE* DBCHandler::findMessage(const QString msgName) { DBC_MESSAGE *msg = nullptr; for(int i = 0; i < loadedFiles.count(); i++) { DBCFile * file = getFileByIdx(i); msg = file->messageHandler->findMsgByName(msgName); if (msg) return msg; //if it's not null then we have a match so return it } return nullptr; //no match, tough luck, return null } DBC_MESSAGE* DBCHandler::findMessage(const QString msgName, const QString nodeName, const QString fileNameNoExt) { DBC_MESSAGE *msg = nullptr; for(int i = 0; i < loadedFiles.count(); i++) { DBCFile * file = getFileByIdx(i); if(file->getFilenameNoExt() == fileNameNoExt) { int msgCount = file->messageHandler->getCount(); for(int f = 0; f < msgCount; f++) { msg = file->messageHandler->findMsgByIdx(f); if (msg && msg->name == msgName && msg->sender->name == nodeName) return msg; //if it's not null and the node name matches return it } } } return nullptr; //no match, tough luck, return null } DBC_MESSAGE* DBCHandler::findMessage(const QString msgName, const QString fullyQualifiedNodeName) { QStringList nodeNameParts = fullyQualifiedNodeName.split(Utility::fullyQualifiedNameSeperator); if(nodeNameParts.count() != 2) { qDebug() << "Error parsing fully qualified node name for message search"; return nullptr; } QString fileNameNoExt = nodeNameParts[0]; QString nodeName = nodeNameParts[1]; DBC_MESSAGE *msg = nullptr; msg = findMessage(msgName, nodeName, fileNameNoExt); return msg; } int DBCHandler::getFileCount() { return loadedFiles.count(); } DBCFile* DBCHandler::getFileByIdx(int idx) { if (loadedFiles.count() == 0) return nullptr; if (idx < 0) return nullptr; if (idx >= loadedFiles.count()) return nullptr; return &loadedFiles[idx]; } DBCFile* DBCHandler::getFileByName(QString name) { if (loadedFiles.count() == 0) return nullptr; for (int i = 0; i < loadedFiles.count(); i++) { if (loadedFiles[i].getFilename().compare(name, Qt::CaseInsensitive) == 0) { return &loadedFiles[i]; } } return nullptr; } DBCHandler::DBCHandler() { // Load previously saved DBC file settings QSettings settings; qDebug() <<"Settings file: " << settings.fileName(); int filecount = settings.value("DBC/FileCount", 0).toInt(); qDebug() << "Previously loaded DBC file count: " << filecount; for (int i=0; isetAssocBus(bus); MatchingCriteria_t matchingCriteria = (MatchingCriteria_t)settings.value("DBC/MatchingCriteria_" + QString::number(i),0).toInt(); DBC_ATTRIBUTE attr; attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = matchingCriteria; attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "matchingcriteria"; attr.valType = ATTR_INT; // file->dbc_attributes.append(attr); // file->messageHandler->setMatchingCriteria(matchingCriteria); int filterLabeling = settings.value("DBC/FilterLabeling_" + QString::number(i),0).toInt(); attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = filterLabeling; attr.enumVals.clear(); attr.lower = 0; attr.upper = 0; attr.name = "filterlabeling"; attr.valType = ATTR_INT; // file->dbc_attributes.append(attr); // file->messageHandler->setFilterLabeling(filterLabeling); qInfo() << "Loaded DBC file" << filename << " (bus:" << bus << ", Matching Criteria:" << (int)matchingCriteria << "Filter labeling: " << (filterLabeling?"enabled":"disabled") << ")"; } } } DBCHandler* DBCHandler::getReference() { if (!instance) instance = new DBCHandler(); return instance; }