/*! * * Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. * \copyright * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * * \file MessageBuilder.cpp * \author (last) Behrouz NematiPour * \date (last) 18-Apr-2022 * \author (original) Behrouz NematiPour * \date (original) 26-Aug-2020 * */ #include "MessageBuilder.h" // Project #include "crc.h" #include "format.h" namespace Can { /*! * \brief MessageBuilder::MessageBuilder * \details Constructor * \param parent - QObject parent owner object. * Qt handles the children destruction by their parent objects life-cycle. */ MessageBuilder::MessageBuilder(QObject *parent) : QObject(parent) { } /*! * \brief MessageBuilder::buildFrames * \details This method builds list of frames out of the vMsgIds of type GuiActionType * and vData of type QByteArray which has been requested to be sent by UI. * The message will be chopped into 8 bytes frames to be able to be send * by fixed length CANBus protocol. * \param vMsgId - The ActionID of the requested message. * \param vData - The payload of the message. * \param vFrameList - The list of frames which has been created by vMsgId and vData to be sent. * \return true on successful to build a frame */ bool MessageBuilder::buildFrames(const MsgId vMsgId, const QByteArray &vData, FrameList &vFrameList, const Sequence vSequence) { QByteArray mPayload; addSyncByte(mPayload); addSequence(mPayload, vSequence); if (addMsgId(mPayload, vMsgId) == false) { return false; } if (addData(mPayload, vMsgId, vData) == false) { return false; } addCRC(mPayload); const quint16 len = mPayload.length(); if (len > eLenCanFrame) { quint8 frameCount = len / eLenCanFrame; if (len % eLenCanFrame) { ++frameCount; } for (quint8 i = 0; i < frameCount; i++) { vFrameList += mPayload.mid(i * eLenCanFrame, eLenCanFrame); } } else { vFrameList += mPayload; } addPadding(vFrameList.last()); return true; } /*! * \brief MessageBuilder::addSyncByte * \details Adds the sync/start byte at the end of the Payload vPayload of type QByteArray * \param vPayload - payload which is going to be constructed by appending byte */ void MessageBuilder::addSyncByte(QByteArray &vPayload) { vPayload.append(ePayload_Sync); // Sync byte } /*! * \brief MessageBuilder::addSequence * \details Adds the sequence number to the Denali message. * \param vPayload - The message payload to be used for adding the sequence number to. * \param vSequence - The sequence number */ void MessageBuilder::addSequence(QByteArray &vPayload, const Sequence vSequence) { Sequence_Bytes mSequence; mSequence.value = vSequence; for (quint8 index = 0; index < sizeof(mSequence); index++) { vPayload += mSequence.bytes[index]; } } /*! * \brief MessageBuilder::addMsgId * \details Adds the sync/start byte at the end of the Payload vPayload of type QByteArray * \param vPayload - payload which is going to be constructed by appending byte * \param vMsgId - The ActionID of the message which needs to be appended * to the Payload vPayload */ bool MessageBuilder::addMsgId(QByteArray &vPayload, const MsgId vMsgId) { quint16 mAction = static_cast(vMsgId); vPayload += (mAction >> 8) & 0xFF; // high byte vPayload += mAction & 0xFF; // low byte return true; } /*! * \brief MessageBuilder::addData * \details Regarding ActionID appends correct bytes amount of vData to the vPayload * \param vPayload - payload which is going to be constructed by appending byte * \param vMsgId - The ActionID of the message which needs to be appended * \param vData - The data which is going to be message payload. * \return false if the vData of type QByteArray is not sufficient regarding vMsgId */ bool MessageBuilder::addData(QByteArray &vPayload, MsgId const vMsgId, const QByteArray &vData) { // quint8 len = payloadLen[vMsgId]; // // if len has been set to max(255) // // it means it has no limit and can be as long as 255 bytes // if (len == eLenMaxData) { // len = (vData.length() > eLenMaxData) ? eLenMaxData : vData.length(); // } // if (vData.length() < len) { // // QString mHexMIdString = Format::toHexString(vMsgId, false, eLenMessageIDDigits); // // QString mHexDatString = vData.toHex('.').toUpper(); // // LOG_DEBUG(QString("Not enough data has been provided for the Message ID '%1'\r\n%2") // // .arg(mHexMIdString) // // .arg(mHexDatString) // // ); // return false; // } Q_UNUSED(vMsgId) const quint8 len = (vData.length() > eLenMaxData) ? eLenMaxData : vData.length(); vPayload += len; vPayload += vData.mid(0, len); // Adding required Data return true; } /*! * \brief MessageBuilder::addCRC * \details Appends calculated crc8 (1 byte) of the vPayload at the end of the vPayload * \param vPayload - The Payload which is going to be used for crc8 calculation * \note The first byte will be excluded and is not part of the crc8 calculation. * Since the first byte is the Sync and has always constant value of A5. * SW/FW agreement. */ void MessageBuilder::addCRC(QByteArray &vPayload) { // sync byte should not be Used for crc calculation vPayload += calcCRC(vPayload.mid(1)); } /*! * \brief MessageBuilder::addPadding * \details This method is appending bytes containing 0x00 * to keep the length of the frame 8 bytes. * \param vPayload - The payload of the CANBus message */ void MessageBuilder::addPadding(QByteArray &vPayload) { vPayload = vPayload.leftJustified(eLenCanFrame, '\0'); } /*! * \brief MessageBuilder::calcCRC * \details Calculates the crc8 * \param vData - The data of type QByteArray to be used for crc8 calculation. * \return returns a byte contains crc8 */ quint8 MessageBuilder::calcCRC(const QByteArray &vData) // TODO : This section better to be in the MessageModel { return crc8(vData); } // CRC is always next byte after Data /*! * \brief MessageBuilder::checkCRC * \details This method checks the crc8 of the vData of type QByteArray * by using the last byte as the crc8 of the rest of the data * \param vData - The data of type QByteArray to be used for crc8 calculation. * \param vExpected - The expected CRC value * \param vActual - The actual value which has been read * \return returns false if the crc8 is not correct or the data is empty. */ bool MessageBuilder::checkCRC(const QByteArray &vData, quint8 &vExpected, quint8 &vActual) // TODO : This section better to be in the MessageModel { #ifndef DISABLE_CRC const int len = vData.length(); if (! len) { return false; } vActual = vData.back(); vExpected = calcCRC(vData.mid(0, len - 1)); return (vExpected == vActual); #else return true; #endif } /*! * \brief MessageBuilder::checkCRC * \details Overloaded CheckCRC which checks the CRC and Log the error if there is. * \param vMessage - The message to check for the CRC * \return false if has error */ bool MessageBuilder::checkCRC(const Message &vMessage) { consoleOut("", false, CanId::eChlid_NONE); QByteArray crcData = vMessage.head + vMessage.data; quint8 mExpected = 0; quint8 mBeenRead = 0; return checkCRC(crcData, mExpected, mBeenRead); } /*! * \brief MessageBuilder::buildMessage * \details Builds Message out of vPayload of type QByteArray * by adding Sync byte, ActionID(MessageID), data length, data, CANBus channel id for header * and keeps collecting data from payload up until the specified length. * \param vPayload - The payload of the CANBus message * \param vMessage - The Message variable which is going to be built * \param vCanId - The CANBus frame channel id * \return false if the payload does not contain sync byte (Payload_Data::ePayload_Sync) */ bool MessageBuilder::buildMessage(const QByteArray &vPayload, Message &vMessage, const CanId vCanId) { QByteArray mPayload = vPayload; if (vMessage.data.isEmpty()) { // message is empty so expected a header if (hasSyncByte(mPayload)) { // Got header consoleOut(vPayload, true, vCanId); vMessage.canId = vCanId; vMessage.head = getHeader(mPayload); // keep header before taking it out of the payload. does not affect payload vMessage.sequence = getSequence(mPayload); vMessage.msgId = getActionId(mPayload); vMessage.length = getLength(mPayload); vMessage.data = getData(mPayload, vMessage.length); vMessage.initialized = true; } else { // Expected Header but got pure data // LOG_DEBUG(QString("Expected Header, got frame without Sync byte")); qDebug().noquote() << QString("Expected Header, got frame without Sync byte"); printPayload(vPayload, false ,vCanId); return false; } } else { consoleOut(vPayload, false ,vCanId); vMessage.data += vPayload.mid(0, vMessage.length - vMessage.data.length()); } // TODO : This section better to be in the MessageModel // and when Message model identifies the message is complete // will SIGNAL builder to check for crc. if (vMessage.isComplete() && checkCRC(vMessage) == false) { return false; } return true; } /*! * \brief MessageBuilder::enableConsoleOut * \details Enables or Disables the console output and logs the status * \param vEnabled */ void MessageBuilder::enableConsoleOut(const bool vEnabled) { if (_enableConsoleOut != vEnabled) { _enableConsoleOut = vEnabled; } // if (_enableConsoleOut) { // LOG_DEBUG("Console out MessageBuilder enabled"); // } else { // LOG_DEBUG("Console out MessageBuilder disabled"); // } } /*! * \brief MessageBuilder::hasSyncByte * \details Checks for Sync byte and take it out of vPayload * \param vPayload - The payload of type QByteArray * \return true if the first byte of the vPayload is sync byte * (Payload_Data::ePayload_Sync) * \note Removes the first 1 byte of sync byte from vPayload * It starts from the first byte. */ bool MessageBuilder::hasSyncByte(QByteArray &vPayload) { const quint8 mSyncByte = vPayload[0]; if (mSyncByte == ePayload_Sync) { vPayload = vPayload.mid(eLenSyncByte); return true; } return false; } /*! * \brief MessageBuilder::getSequence * \details Extract the 2 bytes of the sequence * out of the vPayload of type QByteArray * \param vPayload - The payload of the CANBus message * \return Returns ActionId of type GuiActionType * \note Removes the 2 bytes of ActionID from vPayload * It starts from the first byte so those 2 bytes should be the first 2 bytes. */ Sequence MessageBuilder::getSequence(QByteArray &vPayload) { Sequence_Bytes mSequence; int index = 0; Types::getValue<>(vPayload, index, mSequence); vPayload = vPayload.mid(eLenSequence); return mSequence.value; } /*! * \brief MessageBuilder::getHeader * \details Collect the 3 bytes (Frame_Data::eLenHeaderInfo) * as header portion of the payload * which is the MessageID (2 bytes) , data length (1 byte) * It does not contain sync byte (Payload_Data::ePayload_Sync) * as this value will be used for crc8 calculation * and that does not include sync byte (Payload_Data::ePayload_Sync) * \param vPayload - The payload of the CANBus message * \return Returns 3 byte of header data of type QByteArray * \note As it's obvious from the function parameter it's not changing the vPayload * Just has been mentioned to be consistent with the other methods of buildMessage. * It starts from the first byte so the vPayload should not have the sync byte. */ QByteArray MessageBuilder::getHeader(const QByteArray &vPayload) { QByteArray headInfo; if (vPayload.length() < eLenHeaderInfo) { // LOG_DEBUG("Incorrect Message Header"); return headInfo; } for (int i = 0; i < eLenHeaderInfo; i++) { headInfo += vPayload[i]; } return headInfo; } /*! * \brief MessageBuilder::getActionId * \details Extracts the 2 bytes ActionID (Frame_Data::eLenActionId) * out of the vPayload of type QByteArray * \param vPayload - The payload of the CANBus message * \return Returns ActionId of type GuiActionType * \note Removes the 2 bytes of ActionID from vPayload * It starts from the first byte so those 2 bytes should be the first 2 bytes. */ quint16 MessageBuilder::getActionId(QByteArray &vPayload) { MsgId_Bytes mMsgId; int index = 0; Types::getValue<>(vPayload, index, mMsgId); vPayload = vPayload.mid(eLenActionId); return mMsgId.value; } /*! * \brief MessageBuilder::getLength * \details Extracts the 1 byte data length out of the vPayload * and removes the first 1 byte * \param vPayload - The payload of the CANBus message * \return returns the length of the data which is added by 1 to include 1 byte crc * \note the length of the data should not be more than 255 which requires 1 byte only * so technically it does not need to return a value of type more than 1 byte * But some room reserved for later huge buffer passing * And also as 1 byte crc8 is included as part of data * it make it 256 which is going to be more than a byte. * \note Removes the 1 byte of data length from vPayload * It starts from the first byte so that byte should be the first 1 byte. */ int MessageBuilder::getLength(QByteArray &vPayload) { // on the line bellow it has to be cast to unsigned otherwise FF will be converted to -1 and + 1 becomes 0. const int mlen = static_cast(vPayload[0]) + 1; // Add CRC to the length of data vPayload = vPayload.mid(eLenLength); return mlen; } /*! * \brief MessageBuilder::getData * \details Extract data from vPayload * if vLen is less or equal to Frame_Data::eLenMaxHeaderData * it gets data of first byte up to the len of vLen * otherwise returns the whole vPayload as the data * \param vPayload - The payload of the CANBus message * \param vLen - the length of the data * \return The data to be returned */ QByteArray MessageBuilder::getData(const QByteArray &vPayload, const int vLen) { return (vLen <= eLenMaxHeaderData) ? vPayload.mid(0, vLen) : vPayload; } /*! * \brief MessageBuilder::printPayload * \details Sends out formatted message to the console * for debugging purposes. * Since this method is only formatting the payload * and does not extract the content of the payload * it gets required information from outside through it's parameters. * \param vPayload - The payload of the CANBus message * \param vIsHeader - Should be sent as true if this payload contains header data * \param vCanId - CANBus channel id * \param vUseColor - Use coloring or just space formatted output * if vUseColor passed true is uses different color for Sync byte, MessageID & data */ void MessageBuilder::printPayload(const QByteArray &vPayload, const bool vIsHeader, const CanId vCanId, bool const vUseColor) { if (vCanId == CanId::eChlid_NONE) { // qDebug() << " "; return; } QByteArray view; if (vUseColor) { QList byteList; byteList = vPayload.toHex('.').split('.'); for (int i = 0; i < byteList.length(); i++) { if (vIsHeader) { if (i == 0) { byteList[i] = QByteArray("\033[32m") + byteList[i].constData(); } if (i == 1 || i == 2) { byteList[i] = QByteArray("\033[33m") + byteList[i].constData(); } if (i > 2) { byteList[i] = QByteArray("\033[36m") + byteList[i].constData() + QByteArray("\033[0m"); } } else { byteList[i] = QByteArray("\033[36m") + byteList[i].constData() + QByteArray("\033[0m"); } } view = Format::toHexString(vCanId, false, eLenChannelDigits).toLatin1() + " " + byteList.join('.'); // the fprintf is used for the colored output fprintf(stderr, "%s\n", view.constData()); } else { view = Format::toHexString(vCanId, false, eLenChannelDigits).toLatin1() + " " + vPayload.toHex('.'); // the fprintf is used for the colored output fprintf(stderr, "%s\n", view.constData()); } } /*! * \brief MessageBuilder::consoleOut * \details An overloaded method of the MessageBuilder::printPayload * which is only printPayload if the console output is enabled * by setting MessageBuilder::enableConsoleOut * \note please refer to MessageBuilder::printPayload */ void MessageBuilder::consoleOut(const QByteArray &vPayload, const bool vIsHeader, const CanId vCanId, const bool vUseColor) { if (_enableConsoleOut) { printPayload(vPayload, vIsHeader, vCanId, vUseColor); } } } // namespace Can