/*! * * 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 MessageInterpreter.cpp * \author (last) Peter Lucia * \date (last) 15-Oct-2020 * \author (original) Behrouz NematiPour * \date (original) 26-Aug-2020 * */ #include "MessageInterpreter.h" // Qt #include // #include // Project #include "Logger.h" #include "format.h" using namespace Can; using namespace Model; #define DEBUG_RECEIVE_SIGNAL(vID, vMODEL) // qDebug() << vID << vMODEL; // a macro to simplify the transmit message // would be better later to be replaced by a template method // like the notify method of received messages #define INTERPRET_RECEIVED_MESSAGE(vMODEL) \ if ( ! length ) { logInvalidLength(vActionId); return false; } \ vPayload = Format::fromVariant(vData); \ LOG_EVENT(vMODEL::toString(vData)); \ DEBUG_RECEIVE_SIGNAL(0, typeid(vMODEL).name()) // another version of the INTERPRET_RECEIVED_MESSAGE for empty messages // same later improvements apply to this MACRO as well. #define INTERPRET_RECVD_MT_MESSAGE(vMODEL) \ if ( length ) { logInvalidLength(vActionId); return false; } \ vPayload = Format::fromVariant(vData); \ LOG_EVENT(vMODEL::toString(vData)); \ DEBUG_RECEIVE_SIGNAL(0, typeid(vMODEL).name()) /*! * \brief MessageInterpreter::MessageInterpreter * \details Constructor * \param parent - QObject parent owner object. * Qt handles the children destruction by their parent objects life-cycle. */ MessageInterpreter::MessageInterpreter(QObject *parent) : QObject(parent) { } /*! * \brief MessageInterpreter::notify * \details Checks and prepares the model with the Message Data * Regarding the type of message logs the message received. * Notifies observers by emitting the didActionReceive( < Data > ) signal * \param vMessage - The Denali message * \param vID - The Message ID to be checked against * \param vModel - The appropriate model for the Message Data * \param vData - A QVariant list of the Message Data which will be used for debugging if needed. * \return true on successful check and prepare. */ template bool MessageInterpreter::notify(const Message &vMessage, QVariantList &vData, Gui::GuiActionType vIdCheck) { bool ok = false; TModel tModel; if ( ! isValidMessage(vMessage, vIdCheck) ) return ok; ok = tModel.fromByteArray(vMessage.data); tModel.toVariantList(vData); // coco begin validated : Tested manually. This code will never go false // because the isValidMessage is catching errors. // only is checking here for developer safety if logic has changed. if ( ! ok ) return false; // coco end emit didActionReceive(tModel.data()); logReceivedMessage(tModel); DEBUG_RECEIVE_SIGNAL(vIdCheck, typeid(TModel).name()) return ok; } /*! * \brief MessageInterpreter::isType * \details Checks if this is the message intended to be * \param vMessage - The message * \param vType - The type of the message to be checked against * \return true on correct type */ bool MessageInterpreter::isType(const Message &vMessage, Gui::GuiActionType vType) const { if ( vMessage.actionId != vType ) { return false; } return true; } /*! * \brief MessageInterpreter::isPayloadLenValid * \details Checks if the Data length has been defined for this type of message * if not logs Undefined Data Length error * if defined checks if the correct length of data is provided for this type of message. * if not logs Incorrect Data Length error * otherwise returns true * \param vMessage - The message * \param vType - The type of the message to be checked against * \return true on correct data length for the type vType */ bool MessageInterpreter::isPayloadLenValid(const Message &vMessage, Gui::GuiActionType vType) const { QString mActionIdHexString = Format::toHexString(vMessage.actionId); if ( ! payloadLen.contains(vType) ) { LOG_DEBUG(QString("Undefined data length for received Message with ID '%1'").arg(mActionIdHexString)); return false; } if ( vMessage.data.length() < payloadLen[vType] ) { LOG_DEBUG(QString("Incorrect data length for received Message with ID '%1'").arg(mActionIdHexString)); return false; } return true; } /*! * \brief MessageInterpreter::logInvalidLength * \details Logs invalid data length for the message type vActionId * \param vActionId - Message Type */ void MessageInterpreter::logInvalidLength(const Gui::GuiActionType &vActionId) { QString mActionIdHexString = Format::toHexString(vActionId); LOG_DEBUG(QString("Incorrect data length for transmit message with ID '%1'") .arg(mActionIdHexString)); } /*! * \brief MessageInterpreter::validateMessage * \details Validate the message by checking its type and data * \param vMessage - The message * \param vType - The type of the message to be checked against * \return true on valid massage */ bool MessageInterpreter::isValidMessage(const Message &vMessage, Gui::GuiActionType vType) const { if ( ! isType (vMessage, vType) ) return false; if ( ! isPayloadLenValid(vMessage, vType) ) return false; return true; } /*! * \brief MessageInterpreter::printUnhandled * \details Prints out the formatted string of the vMessage of type Message * In case the Message ID of received CANBUS message * is known to the interpreter but has not been handled/implemented. * \param vMessage - The message contains Unhandled Message ID */ void MessageInterpreter::printUnhandled(const Message &vMessage) const { if ( gDisableUnhandledReport ) return; QString mActionIdHexString = Format::toHexString(vMessage.actionId, false, eLenMessageIDDigits); QString logMessage = tr("Unhandled Message ID (HD)") + '\n' + QString("%1 # %2 %3") .arg(int(vMessage.can_id), 3, 16, QChar('0')) .arg(mActionIdHexString) .arg(QString(vMessage.data.toHex('.'))); LOG_DEBUG(logMessage); } /*! * \brief MessageInterpreter::logReceived * \details Regarding the type of message logs the message received. * \param vModel - the MAbstract model type */ void MessageInterpreter::logReceivedMessage(const Model::MAbstract &vModel) { switch (vModel.typeText()) { case Model::MAbstract::Type_Enum::eDatum: LOG_DATUM(vModel.toString()); break; case Model::MAbstract::Type_Enum::eEvent: LOG_EVENT(vModel.toString()); break; } } /*! * \brief MessageInterpreter::interpretMessage * \details This method will be called * to interpret messages from UI regarding vActionId. * \param vActionId - The ActionID of type GuiActionType * to be interpreted to hex representation of Message ID. * \param vData - The data which has to be sent over the CANBUS. * \param vPayload - The Payload of the frame of Type QByteArray of hex values * Which has been interpreted from vData of Type QVariantList * \return true if the vActionId is valid. * This return value will be used later for error handling. */ bool MessageInterpreter::interpretMessage(const Gui::GuiActionType &vActionId, const QVariantList &vData, QByteArray &vPayload) { bool ok = true; QString mSenderID = "UI,"; vPayload.clear(); int length = vData.length(); // DEBUG: LOG_EVENT(QString("0x%0").arg(vActionId, 4, 16,QChar('0'))); switch (vActionId) { // notice we are in transmit mode case Gui::GuiActionType::ID_Acknow: // len: 0, can have zero len break; // No data, Just registered case Gui::GuiActionType::ID_KeepAlive: // len: 255, can have any len if ( length ) { // this message has a variable length vPayload = Format::fromVariant(vData[0]); } LOG_EVENT(mSenderID + QString("CheckIn")); break; case Gui::GuiActionType::ID_RawData: // len: 255, can have any len if ( length ) { // this message has a variable length vPayload = Format::fromVariant(vData[0]); } LOG_EVENT(mSenderID + QString("RawData")); break; case Gui::GuiActionType::ID_PowerOff : INTERPRET_RECEIVED_MESSAGE(AdjustPowerOffRequestData ); break; // in-treatment case Gui::GuiActionType::ID_AdjustDurationReq : INTERPRET_RECEIVED_MESSAGE(AdjustDurationRequestData ); break; case Gui::GuiActionType::ID_AdjustBloodDialysateReq : INTERPRET_RECEIVED_MESSAGE(AdjustBloodDialysateRequestData ); break; case Gui::GuiActionType::ID_AdjustPressuresLimitsReq : INTERPRET_RECEIVED_MESSAGE(AdjustPressuresLimitsRequestData ); break; case Gui::GuiActionType::ID_AdjustSalineReq : INTERPRET_RECEIVED_MESSAGE(AdjustSalineRequestData ); break; case Gui::GuiActionType::ID_AdjustHeparinReq : INTERPRET_RECEIVED_MESSAGE(AdjustHeparinRequestData ); break; // in-treatment - ultrafiltration case Gui::GuiActionType::ID_AdjustUltrafiltrationInitReq : INTERPRET_RECEIVED_MESSAGE(AdjustUltrafiltrationInitRequestData ); break; case Gui::GuiActionType::ID_AdjustUltrafiltrationStateReq : INTERPRET_RECEIVED_MESSAGE(AdjustUltrafiltrationStateRequestData ); break; case Gui::GuiActionType::ID_AdjustUltrafiltrationEditReq : INTERPRET_RECEIVED_MESSAGE(AdjustUltrafiltrationEditRequestData ); break; case Gui::GuiActionType::ID_AdjustUltrafiltrationConfirmReq : INTERPRET_RECEIVED_MESSAGE(AdjustUltrafiltrationConfirmRequestData ); break; // pre-Treatment case Gui::GuiActionType::ID_StartTreatmentReq : INTERPRET_RECEIVED_MESSAGE(StartTreatmentRequestData ); break; case Gui::GuiActionType::ID_ConfirmTreatmentReq : INTERPRET_RECEIVED_MESSAGE(ConfirmTreatmentRequestData ); break; case Gui::GuiActionType::ID_EndTreatmentReq : INTERPRET_RECVD_MT_MESSAGE(EndTreatmentRequestData ); break; case Gui::GuiActionType::ID_CreateTreatmentReq : INTERPRET_RECEIVED_MESSAGE(AdjustTreatmentParametersRequestData ); break; // alarms case Gui::GuiActionType::ID_AlarmSilenceReq : INTERPRET_RECEIVED_MESSAGE(AlarmSilenceRequestData ); break; case Gui::GuiActionType::ID_AlarmUserActionReq : INTERPRET_RECEIVED_MESSAGE(AlarmUserActionRequestData ); break; // coco begin validated: Manually tested. This model class is a placeholder for the message 63(0x3F00) and there is no use case for this now. case Gui::GuiActionType::ID_AlarmClearedConditionReq : INTERPRET_RECEIVED_MESSAGE(AlarmClearedConditionRequestData ); break; // coco end default: QString mActionIdHexString = Format::toHexString(vActionId); LOG_DEBUG(mSenderID + tr("Unknown transmit Message with ID '%1'").arg(mActionIdHexString)); ok = false; break; } return ok; } /*! * \brief MessageInterpreter::interpretMessage * \details This method will call appropriate message interpreter * for received messages from HD or DG regarding the Can_Id. * \param vCan_Id - The Channel Id of the CANBUS frame. * \param vMessage - The complete message of type Message which needs to be interpreted. * \param vActionId - The ActionId of GuiActionType which will be extracted from vMessage. * \param vData - The values of QVariantList which is understandable for UI * and has been extracted from hex values of the CANBUS Message Payload * regarding each Message Id definition. * \return true if the message channel is in the range which can be interpreted, false otherwise. * This return value will be used later to emit MessageDispatcher::didActionReceive signal or not */ bool MessageInterpreter::interpretMessage(const Message &vMessage, QVariantList &vData) { bool ok = false; switch (vMessage.can_id) { case eChlid_HD_UI : case eChlid_HD_Alarm: case eChlid_HD_Sync : ok = interpretMessage_HD(vMessage, vData); break; case eChlid_DG_HD : case eChlid_DG_UI : // case eChlid_DG_Alarm: // commented out for now. Currently there is no message in this category. case eChlid_DG_Sync : ok = interpretMessage_DG(vMessage, vData); break; default: break; } return ok; } /*! * \brief MessageInterpreter::interpretMessage_HD * \details This method will be called * for received messages from HD to interpret the vMessage of type Message * to vData of type QVariantList which UI understands regarding the Can_Id. * \param vMessage - The complete message of type Message which needs to be interpreted. * \param vActionId - The ActionId of GuiActionType which will be extracted from vMessage. * \param vData - The values of QVariantList which is understandable for UI * and has been extracted from hex values of the CANBUS Message Payload * in vMessage of type Message regarding each Message Id definition. * \return true if the message CANBUS channel is in the range which can be interpreted, false otherwise. * This return value will be used later to emit MessageDispatcher::didActionReceive signal or not */ bool MessageInterpreter::interpretMessage_HD(const Message &vMessage, QVariantList &vData) { bool ok = false; vData.clear(); switch (vMessage.actionId) { // notice we are in receive mode // ----- Debug case Gui::GuiActionType::ID_CANBusFaultCount : ok = canbusFaultCountData (vMessage, vData); break; // TODO : implement notify<>() // ----- Datum case Gui::GuiActionType::ID_TreatmentTime : ok = notify(vMessage, vData, Gui::GuiActionType::ID_TreatmentTime ); break; case Gui::GuiActionType::ID_BloodFlow : ok = notify(vMessage, vData, Gui::GuiActionType::ID_BloodFlow ); break; case Gui::GuiActionType::ID_DialysateInletFlow : ok = notify(vMessage, vData, Gui::GuiActionType::ID_DialysateInletFlow ); break; case Gui::GuiActionType::ID_DialysateOutletFlow : ok = notify(vMessage, vData, Gui::GuiActionType::ID_DialysateOutletFlow ); break; case Gui::GuiActionType::ID_TreatmentRanges : ok = notify(vMessage, vData, Gui::GuiActionType::ID_TreatmentRanges ); break; case Gui::GuiActionType::ID_PressureOcclusion : ok = notify(vMessage, vData, Gui::GuiActionType::ID_PressureOcclusion ); break; case Gui::GuiActionType::ID_TreatmentStates : ok = notify(vMessage, vData, Gui::GuiActionType::ID_TreatmentStates ); break; case Gui::GuiActionType::ID_PrimingData : ok = notify(vMessage, vData, Gui::GuiActionType::ID_PrimingData ); break; case Gui::GuiActionType::ID_Saline : ok = notify(vMessage, vData, Gui::GuiActionType::ID_Saline ); break; case Gui::GuiActionType::ID_Heparin : ok = notify(vMessage, vData, Gui::GuiActionType::ID_Heparin ); break; // ----- Events case Gui::GuiActionType::ID_HDOperationModeData : ok = notify(vMessage, vData, Gui::GuiActionType::ID_HDOperationModeData ); break; case Gui::GuiActionType::ID_HDDebugText : ok = notify(vMessage, vData, Gui::GuiActionType::ID_HDDebugText ); break; case Gui::GuiActionType::ID_Acknow : ok = true; break; // TODO : implement notify<>() case Gui::GuiActionType::ID_PowerOff : ok = notify(vMessage, vData, Gui::GuiActionType::ID_PowerOff ); break; case Gui::GuiActionType::ID_ShuttingDown : ok = true; LOG_EVENT("HD,ShuttingDown"); break; // TODO : implement notify<>() case Gui::GuiActionType::ID_CreateTreatmentRsp : ok = notify(vMessage, vData, Gui::GuiActionType::ID_CreateTreatmentRsp ); break; // Adjustment Response Messages case Gui::GuiActionType::ID_AdjustDurationRsp : ok = notify(vMessage, vData, Gui::GuiActionType::ID_AdjustDurationRsp ); break; case Gui::GuiActionType::ID_AdjustBloodDialysateRsp : ok = notify(vMessage, vData, Gui::GuiActionType::ID_AdjustBloodDialysateRsp ); break; case Gui::GuiActionType::ID_AdjustPressuresLimitsRsp : ok = notify(vMessage, vData, Gui::GuiActionType::ID_AdjustPressuresLimitsRsp ); break; case Gui::GuiActionType::ID_AdjustSalineRsp : ok = notify(vMessage, vData, Gui::GuiActionType::ID_AdjustSalineRsp ); break; case Gui::GuiActionType::ID_AdjustUltrafiltrationInitRsp : ok = notify(vMessage, vData, Gui::GuiActionType::ID_AdjustUltrafiltrationInitRsp ); break; case Gui::GuiActionType::ID_AdjustUltrafiltrationStateRsp : ok = notify(vMessage, vData, Gui::GuiActionType::ID_AdjustUltrafiltrationStateRsp ); break; case Gui::GuiActionType::ID_AdjustHeparinRsp : ok = notify(vMessage, vData, Gui::GuiActionType::ID_AdjustHeparinRsp ); break; // these need to be standard and use notify as well case Gui::GuiActionType::ID_AdjustUltrafiltrationEditRsp : ok = adjustUltrafiltrationEdit (vMessage, vData); break; // TODO : implement notify<>() case Gui::GuiActionType::ID_AdjustUltrafiltrationConfirmRsp : ok = adjustUltrafiltrationConfirm (vMessage, vData); break; // TODO : implement notify<>() case Gui::GuiActionType::ID_StartTreatmentRsp : ok = notify(vMessage, vData, Gui::GuiActionType::ID_StartTreatmentRsp ); break; case Gui::GuiActionType::ID_EndTreatmentRsp : ok = notify(vMessage, vData, Gui::GuiActionType::ID_EndTreatmentRsp ); break; // Alarms case Gui::GuiActionType::ID_AlarmStatus : ok = notify(vMessage, vData, Gui::GuiActionType::ID_AlarmStatus ); break; case Gui::GuiActionType::ID_AlarmTriggered : ok = notify(vMessage, vData, Gui::GuiActionType::ID_AlarmTriggered ); break; case Gui::GuiActionType::ID_AlarmCleared : ok = notify(vMessage, vData, Gui::GuiActionType::ID_AlarmCleared ); break; // coco begin validated: Manually tested. This model class is a placeholder for the message 63(0x3F00) and there is no use case for this now. case Gui::GuiActionType::ID_AlarmClearedConditionRsp : ok = notify(vMessage, vData, Gui::GuiActionType::ID_AlarmClearedConditionRsp ); break; // coco end // unhandles messages: these will only be logged as received message // there has nothing been defined for these messages. default : printUnhandled(vMessage); break; } return ok; } /*! * \brief MessageInterpreter::interpretMessage_DG * \details This method will be called * for received messages from DG to interpret the vMessage of type Message * to vData of type QVariantList which UI understands regarding the Can_Id. * \param vMessage - The complete message of type Message which needs to be interpreted. * \param vActionId - The ActionId of GuiActionType which will be extracted from vMessage. * \param vData - The values of QVariantList which is understandable for UI * and has been extracted from hex values of the CANBUS Message Payload * in vMessage of type Message regarding each Message Id definition. * \return true if the message CANBUS channel is in the range which can be interpreted, false otherwise. * This return value will be used later to emit MessageDispatcher::didActionReceive signal or not */ bool MessageInterpreter::interpretMessage_DG(const Message &vMessage, QVariantList &vData) { bool ok = false; vData.clear(); switch (vMessage.actionId) { // notice we are in receive mode case Gui::GuiActionType::ID_DGCheckIn: // TODO : implement notify<>() ok = true; LOG_EVENT(QString("DG,CheckIn," + QVariant(vData).toStringList().join(','))); break; case Gui::GuiActionType::ID_DGROPumpData : ok = notify(vMessage, vData, Gui::GuiActionType::ID_DGROPumpData ); break; case Gui::GuiActionType::ID_DGPressuresData : ok = notify(vMessage, vData, Gui::GuiActionType::ID_DGPressuresData ); break; case Gui::GuiActionType::ID_DGDrainPumpData : ok = notify(vMessage, vData, Gui::GuiActionType::ID_DGDrainPumpData ); break; case Gui::GuiActionType::ID_DGOperationModeData : ok = notify(vMessage, vData, Gui::GuiActionType::ID_DGOperationModeData ); break; case Gui::GuiActionType::ID_DGReservoirData : ok = notify(vMessage, vData, Gui::GuiActionType::ID_DGReservoirData ); break; case Gui::GuiActionType::ID_DGValvesStatesData : ok = notify(vMessage, vData, Gui::GuiActionType::ID_DGValvesStatesData ); break; case Gui::GuiActionType::ID_DGHeatersData : ok = notify(vMessage, vData, Gui::GuiActionType::ID_DGHeatersData ); break; case Gui::GuiActionType::ID_DGLoadCellReadingsData : ok = notify(vMessage, vData, Gui::GuiActionType::ID_DGLoadCellReadingsData); break; case Gui::GuiActionType::ID_DGTemperaturesData : ok = notify(vMessage, vData, Gui::GuiActionType::ID_DGTemperaturesData ); break; case Gui::GuiActionType::ID_DGDebugText : ok = notify(vMessage, vData, Gui::GuiActionType::ID_DGDebugText ); break; // unhandled messages: these will only be logged as received message // there has nothing been defined for these messages. default: printUnhandled (vMessage); break; } return ok; } // ---------- ---------- Message handlers ---------- ---------- // // ---------- ---------- ---------- ---------- ---------- Debug ---------- ---------- ---------- ---------- ---------- // /*! * \brief MessageInterpreter::canbusFaultCountData * \details This method interprets Fault Count message data * in vMessage of type Message. This message is only used for debugging purposes. * \param vMessage - The vMessage of type Message which contains all the data, * require to be interpreted. * \param vData - Fault Count data * \return true if the data can be extracted as defined for Fault Count Message ID */ bool MessageInterpreter::canbusFaultCountData(const Message &vMessage, QVariantList &vData) { // TODO : review other methods bool ok = false; if ( ! isValidMessage(vMessage, Gui::GuiActionType::ID_CANBusFaultCount) ) return ok; QVariantList mData; int index = 0; Types::U32 mCanBUSFaultCount; ok = GetValue(vMessage.data, index, mCanBUSFaultCount); // coco begin validated : developer safety if for any reason length of CanBUSFaultCount set to 0 if (ok) { // coco end vData += mCanBUSFaultCount.value; } return ok; } // ---------- ---------- ---------- ---------- ---------- Adjustments ---------- ---------- ---------- ---------- ---------- // /*! * \brief MessageInterpreter::adjustUltrafiltrationEditData * \details This method interprets Treatment Ultrafiltration Volume Adjustment Response message data * in vMessage of type Message. * \param vMessage - The vMessage of type Message which contains all the data, * require to be interpreted. * \param vData - Treatment Ultrafiltration Volume Adjustment Response data * \return true if the data can be extracted as defined for Treatment Ultrafiltration Volume Adjustment Response Message ID */ bool MessageInterpreter::adjustUltrafiltrationEdit(const Message &vMessage, QVariantList &vData) { bool ok = false; if ( ! isValidMessage(vMessage, Gui::GuiActionType::ID_AdjustUltrafiltrationEditRsp) ) return ok; Model::MAdjustUltrafiltrationEditResponse mData; ok = mData.fromByteArray(vMessage.data); LOG_EVENT("HD," + mData.toString()); // TODO: Not Standard mData.toVariantList(vData); emit didActionReceive(mData.data()); return ok; } /*! * \brief MessageInterpreter::adjustUltrafiltrationConfirmData * \details This method interprets Treatment Ultrafiltration Volume Adjustment Confirm Response message data * in vMessage of type Message. * \param vMessage - The vMessage of type Message which contains all the data, * require to be interpreted. * \param vData - Treatment Ultrafiltration Volume Adjustment Confirm Response data * \return true if the data can be extracted as defined for Treatment Ultrafiltration Volume Adjustment Confirm Response Message ID */ bool MessageInterpreter::adjustUltrafiltrationConfirm(const Message &vMessage, QVariantList &vData) { bool ok = false; if ( ! isValidMessage(vMessage, Gui::GuiActionType::ID_AdjustUltrafiltrationConfirmRsp) ) return ok; Model::MAdjustUltrafiltrationConfirmResponse mData; ok = mData.fromByteArray(vMessage.data); LOG_EVENT("HD," + mData.toString()); // TODO: Not Standard mData.toVariantList(vData); emit didActionReceive(mData.data()); return ok; }