/*! * * Copyright (c) 2019-2020 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 * date 12/6/2019 * author Behrouz NematiPour * */ #include "messagebuilder.h" // Qt // Project #include "logger.h" #include "crc.h" #include "format.h" // namespace using 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 vActions 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 vAction - The ActionID of the requested message. * \param vData - The payload of the message. * \param vFrameList - The list of frames which has been created by vAction and vData to be sent. * \return false on error */ bool MessageBuilder::buildFrames(Gui::GuiActionType vAction, const QByteArray &vData, FrameList &vFrameList, Sequence vSequence) { QByteArray mPayload ; addSyncByte (mPayload); // Sync Byte addSequence (mPayload, vSequence); // adding sequence if ( ! addActionId (mPayload, vAction ) ) { // MessageID return false; } if ( ! addData (mPayload, vAction, vData) ) { // Regarding Payload Length, Adding required Data return false; } addCRC (mPayload); // CRC 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()); // Padded to 8 byte frame 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, Sequence vSequence) { Sequence_Bytes mSequence; mSequence.value = vSequence; for (quint8 index = 0; index < sizeof(mSequence); index++) { vPayload += mSequence.bytes[index]; } } /*! * \brief MessageBuilder::addActionId * \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 vAction - The ActionID of the message which needs to be appended * to the Payload vPayload */ bool MessageBuilder::addActionId(QByteArray &vPayload, Gui::GuiActionType vAction) { if (vAction != Gui::GuiActionType::Unknown) { quint16 mAction = static_cast(vAction); vPayload += (mAction >> 8) & 0xFF; // high byte vPayload += mAction & 0xFF; // low byte } else { QString mHexString = Format::toHexString(vAction, false, eLenMessageIDDigits); LOG_ERROR(tr("Incorrect Action ID '%1'").arg(mHexString)); return false; } 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 vAction - 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 vAction */ bool MessageBuilder::addData(QByteArray &vPayload, Gui::GuiActionType vAction, const QByteArray &vData) { quint8 len = payloadLen[vAction]; // 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) { if (vData.length() > eLenMaxData) { len = eLenMaxData ; } else { len = vData.length(); } } if (vData.length() < len) { QString mHexString = Format::toHexString(vAction, false, eLenMessageIDDigits); LOG_ERROR(tr("Not enough data has been provided for the Message ID '%1'").arg(mHexString)); LOG_ERROR(vData.toHex('.')); return false; } 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 { quint8 crc = crc8(vData); return crc; } // 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 int len = vData.length(); if ( ! len ) return false; vActual = vData.back(); vExpected = calcCRC(vData.mid(0, len - 1)); bool ok = vExpected == vActual; // it's very good but I'm not sure if it's correct. //bool ok = calcCRC(vData) == 0; return ok; #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, Can_Id::eChlid_NONE); QByteArray crcData = vMessage.head + vMessage.data; quint8 mExpected = 0; quint8 mBeenRead = 0; if ( ! checkCRC(crcData, mExpected, mBeenRead ) ) { // CRC is always next byte after Data static quint64 erCRC = 0; LOG_ERROR(tr("%1 - CRC error, expected %2 but got %3 : %4") .arg(++erCRC) .arg(Format::toHexString(mExpected, true, eLenCRCDigits)) .arg(Format::toHexString(mBeenRead, true, eLenCRCDigits)) .arg(Format::toHexString(vMessage.data)) ); return false; } return true; } /*! * \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 vCan_Id - The CANBUS frame channel id * \return false if the payload doesn't contain sync byte (Payload_Data::ePayload_Sync) */ bool MessageBuilder::buildMessage(const QByteArray &vPayload, Message &vMessage, Can_Id vCan_Id) { QByteArray mPayload = vPayload; if (vMessage.data.isEmpty()) { // message is empty so expected a header if (hasSyncByte(mPayload)) { // Got header consoleOut(vPayload, true, vCan_Id); vMessage.can_id = vCan_Id; vMessage.head = getHeader (mPayload); // keep header before taking it out of the payload. doesn't affect payload vMessage.sequence = getSequence (mPayload); vMessage.actionId = getActionId (mPayload); vMessage.length = getLength (mPayload); vMessage.data = getData (mPayload, vMessage.length); vMessage.initialized = true; } else { // Expected Header but got pure data LOG_ERROR(tr("Expected Header, got frame without Sync byte")); printPayload(vPayload, false ,vCan_Id); return false; } } else { consoleOut(vPayload, false ,vCan_Id); 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()) { bool ok = checkCRC(vMessage); if (!ok) return false; } return true; } /*! * \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) { 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 doesn't contain sync byte (Payload_Data::ePayload_Sync) * as this value will be used for crc8 calculation * and that doesn't 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_ERROR("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. */ Gui::GuiActionType MessageBuilder::getActionId(QByteArray &vPayload) { quint16 mActionId; mActionId = (vPayload[0] << 8) | vPayload[1]; vPayload = vPayload.mid(eLenActionId); return static_cast(mActionId); } /*! * \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 doesn't 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. 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, int vLen) { QByteArray mData; if (vLen <= eLenMaxHeaderData) { mData = vPayload.mid(0, vLen); } else { mData = vPayload; } return mData; } /*! * \brief MessageBuilder::printPayload * \details Sends out formatted message to the console * for debugging purposes. * Since this method is only formatting the payload * and doesn't 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 vCan_Id - 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, bool vIsHeader, Can_Id vCan_Id, bool vUseColor) { if (vCan_Id == Can_Id::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(vCan_Id, false, eLenChannelDigits).toLatin1() + " " + byteList.join('.'); fprintf(stderr, "%s\n", view.constData()); } else { view = Format::toHexString(vCan_Id, false, eLenChannelDigits).toLatin1() + " " + vPayload.toHex('.'); 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, bool vIsHeader, Can_Id vCan_Id, bool vUseColor) { if ( ! _enableConsoleOut) return; LOG_EVENT_ONCE(QObject::tr("console out MessageDispatcher Enabled")); printPayload(vPayload, vIsHeader, vCan_Id, vUseColor); }