/*! * * 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 CloudSyncController.cpp * \author (last) Behrouz NematiPour * \author (original) Behrouz NematiPour * \date (last) 13-Oct-2021 * \date (original) 13-Oct-2021 * */ #include "CloudSyncController.h" // Qt #include // Project #include "MainTimer.h" #include "MessageDispatcher.h" #include "GuiController.h" #include "DeviceController.h" #include "FileHandler.h" /*! * \brief CloudSyncController::CloudSyncController * \details Constructor * \param parent - QObject parent owner object. * Qt handles the children destruction by their parent objects life-cycle. */ CloudSyncController::CloudSyncController(QObject *parent) : QObject(parent) { checkDate(); sendUIBuff("Ready"); startTimer(_interval); } /*! * \brief CloudSyncController initializer */ bool CloudSyncController::init() { if ( _init ) return false; _init = true; initConnections(); LOG_DEBUG(tr("%1 Initialized").arg(metaObject()->className())); return true; } /*! * \brief CloudSyncController::init * \details Initialized the Class by calling the init() method first * And initializes the thread vThread by calling initThread * on success init(). * \param vThread - the thread * \return returns the return value of the init() method */ bool CloudSyncController::init(QThread &vThread) { if ( ! init() ) return false; initThread(vThread); return true; } /*! * \brief CloudSyncController::quit * \details quits the class * Calls quitThread */ void CloudSyncController::quit() { // coco begin validated: CloudSync termination is not correctly done in coco!!! // it has been tested and works perfectly fine in normal run. quitThread(); // validated } // coco end /*! * \brief CloudSyncController::initConnections * \details Initializes the required signal/slot connection between this class and other objects * to be able to communicate. */ void CloudSyncController::initConnections() { connect(&_DeviceController , SIGNAL(didWatchFileChange (const QString &)), this , SLOT( onWatchFileChange (const QString &))); connect(&_MessageDispatcher , SIGNAL(didActionReceive (GuiActionType,const QVariantList &)), this , SLOT( onActionReceive (GuiActionType,const QVariantList &))); } /*! * \brief CloudSyncController::initThread * \details Moves this object into the thread vThread. * And checks that this method is called from main thread. * Also connects quitThread to CloudSync aboutToQuit. * \param vThread - the thread */ void CloudSyncController::initThread(QThread &vThread) { // runs in main thread Q_ASSERT_X(QThread::currentThread() == qApp->thread(), __func__, "The Class initialization must be done in Main Thread" ); _thread = &vThread; _thread->setObjectName(QString("%1_Thread").arg(metaObject()->className())); connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(quit())); _thread->start(); moveToThread(_thread); } /*! * \brief CloudSyncController::quitThread * \details Moves this object to main thread to be handled by QCloudSync * And to be destroyed there. */ void CloudSyncController::quitThread() { // coco begin validated: CloudSync termination is not correctly done in coco!!! // it has been tested and works perfectly fine in normal run. if ( ! _thread ) return; // runs in thread moveToThread(qApp->thread()); // validated } // coco end /*! * \brief CloudSyncController::timerEvent * \details The timer event handler which currently is triggered on each second to check for the date change, * Which caused the watched file change and needs to updated the watched list. * The check-in (watch dog) also needs to be here. */ void CloudSyncController::timerEvent(QTimerEvent *) { // TODO: touch the inp file to as a check-in for CloudSync to know we are up // a simple touch or a check-in message? checkDate(); } /*! * \brief CloudSyncController::onWatchFileChange * \details This slot will be called when the Device Controller identifies any changes in the watched files. * \param vFile - watched file * \note The DeviceController will emit the signal on any watched file update, it's up to the CloudSyncController to filter the result. */ void CloudSyncController::onWatchFileChange(const QString &vFile) { if ( vFile != _date_out_File ) return; // ignore unwanted file updates. QString content; Storage::FileHandler::read(vFile, content); sendUIResponse(content); } /*! * \brief CloudSyncController::addCSBuffWatch * \details Adds a watcher on the CloudSync Application output file. * \sa _out_File */ bool CloudSyncController::addCSBuffWatch() { bool ok = Storage::FileHandler::makeFolder(_location); QVariantList args {}; Errors_Enum error = eError_OK; if ( ! ok ) { error = eError_LogFolder; args = {{ _location }}; ok = false; goto lErr; } _date_out_File = _location + // The location _dateFormatted + _dateSeparator + _out_File; // The file name // watching for the cloud sync output file buffer. _DeviceController.doAddWatch(_date_out_File); return ok; lErr: toLog(error, args); return ok; } /*! * \brief CloudSyncController::interpret * \details Checks the received buffer to make sure it has all the required parameters * \param vIndex * \return true if the buffer is correct. */ bool CloudSyncController::interpret(const QString &vContent, Message &vMessage) { bool ok = true; QVariantList args {}; Errors_Enum error = eError_OK; QStringList lines = vContent.split('\n',QString::SkipEmptyParts); QString buffer = lines.last(); LOG_DEBUG(QString("CloudSync Message received [%1]").arg(buffer)); int index = -1; Message message ; QStringList items = buffer.split(_separator); int count = items.count(); // check the required message length if ( count < eMessage_Count ) { error = eError_Count ; ok = false; goto lErr; } index = eMessage_Timestamp ; message.timestamp = items[index].toInt (&ok); if (!ok) { error = eError_Timestamp ; ok = false; goto lErr; } index = eMessage_Sequence ; message.sequence = items[index].toUInt(&ok); if (!ok) { error = eError_Sequence ; ok = false; goto lErr; } index = eMessage_MessageID ; message.id = items[index].toInt (&ok); if (!ok) { error = eError_MessageID ; ok = false; goto lErr; } index = eMessage_ParamCount; message.paramCount = items[index].toInt (&ok); if (!ok) { error = eError_ParamCount ; ok = false; goto lErr; } index = eMessage_CRC ; message.crc = items[index].toUInt(&ok); if (!ok) { error = eError_CRC ; ok = false; goto lErr; } // check the parameters count if ( count - eMessage_Count < message.paramCount ) { error = eError_Parameter ; ok = false; goto lErr; } // getting the parameters for ( int index = eMessage_Count; index < message.paramCount; index++ ) { message.params.append( items[index] ); } vMessage = message; return true; lErr: // building the error info for each error switch (error) { case eError_Count : args = { count , eMessage_Count }; break; case eError_Timestamp : args = { items[index].trimmed() }; break; case eError_Sequence : args = { items[index].trimmed() }; break; case eError_MessageID : args = { items[index].trimmed() }; break; case eError_ParamCount : args = { items[index].trimmed() }; break; case eError_CRC : args = { items[index].trimmed() }; break; case eError_Parameter : args = { count - eMessage_Count , message.paramCount }; break; default : break; } LOG_DEBUG(toText(error) + " " + toInfo(error, args)); return false; } /*! * \brief CloudSyncController::toText * \details returns the error message of the error id. * \param vErrorID - Error id * \param vMessage - error message * \return the error message or empty string if the error is 0 (no error), or Unknown Error if not found */ QString CloudSyncController::toText(CloudSyncController::Errors_Enum vErrorID) { QString text; if ( vErrorID == 0 ) return text; switch (vErrorID) { case eError_Count : text = tr( "E,CS,Incorrect header" ) ; break; case eError_Timestamp : text = tr( "E,CS,Incorrect timestamp" ) ; break; case eError_Sequence : text = tr( "E,CS,Incorrect sequence" ) ; break; case eError_MessageID : text = tr( "E,CS,Incorrect ID" ) ; break; case eError_ParamCount : text = tr( "E,CS,Incorrect parameter length" ) ; break; case eError_CRC : text = tr( "E,CS,Incorrect CRC" ) ; break; case eError_Parameter : text = tr( "E,CS,Incorrect parameter count" ) ; break; case eError_NoHistory : text = tr( "E,CS,No history available for the request" ) ; break; case eError_LogFolder : text = tr( "E,CS,The log folder cannot be created." ) ; break; case eError_LogFileInp : text = tr( "E,CS,Error writing to the input file." ) ; break; default : text = tr( "E,CS,Unknown Error" ) ; break; } return text; } /*! * \brief CloudSyncController::toInfo * \details Provides extra information, related to each error, to be concatenated later to the error description. * \param vErrorID - the error id * \param vInfoItems - the information items to be used in creating the info. * \return the extra info for the error as string */ QString CloudSyncController::toInfo(CloudSyncController::Errors_Enum vErrorID, const QVariantList &vInfoItems) { // IMPORTANT: Please be careful here used QVariantList::value to act as a loose list. // It means it is designed to not throw out of bound error and just use "~" as a missing info argument. auto item = [=](uint i) { return vInfoItems.value(i,"~").toString(); }; QString info = ""; switch (vErrorID) { case eError_Count : info = QString( "[%1:%2/%3]" ).arg( vErrorID ).arg( item(0) ).arg( item(1) ) ; break; case eError_Timestamp : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; case eError_Sequence : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; case eError_MessageID : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; case eError_ParamCount : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; case eError_CRC : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; case eError_Parameter : info = QString( "[%1:%2/%3]" ).arg( vErrorID ).arg( item(0) ).arg( item(1) ) ; break; case eError_NoHistory : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; default : info = QString( "[%1]" ).arg( vErrorID ) ; break; } return info; } /*! * \brief CloudSyncController::toLog * \details logs the error in debug mode (service log) * \param vErrorID - error id * \param vInfoItems - extra information */ void CloudSyncController::toLog(CloudSyncController::Errors_Enum vErrorID, const QVariantList &vInfoItems) { LOG_DEBUG(toText(vErrorID) + " " + toInfo(vErrorID, vInfoItems)); } /*! * \brief CloudSyncController::sendUIResponse * \details the function to be called after reading the CloudSync out file to interpret the content of the file. * \param vContent - the content to be interpreted. * \return true if the content has a meaning for the interpreter, false otherwise. */ bool CloudSyncController::sendUIResponse(const QString &vContent) { // IMPORTANT: // please note the following in development testing it has been found out that. // Do not use gedit. // if the out.buf is edited by the "gedit" application the watch will be broken. // The "nano" application works fine. // A simple python3 code to open file and write to file works as well and that's sufficient for UI<=>CS communication. // TODO: messages have to have a sequence. // if the seq is duplicate it will be ignored. // seq, id, payload // qDebug() << vContent; bool ok = true; Message message; if ( ! interpret(vContent, message) ) return false; // The error handling internal to interpret method. switch (message.id) { case eMessageID_DeviceState : ok = sendUIHistory(message.id); break; // The device state is stored in history but the other might be different so the switch is used. default : break; } return ok; } /*! * \brief CloudSyncController::checkDate * \details Checks the date and updated the watched file in case the date changed. */ void CloudSyncController::checkDate() { _datetime = QDateTime::currentDateTime(); _secSinceEpoch = _datetime.toSecsSinceEpoch(); _timeFormatted = _datetime.toString(_timeFormat); QString dateFormatted = _datetime.toString(_dateFormat); if (_dateFormatted != dateFormatted) { _dateFormatted = dateFormatted; // TODO: do we need to remove previous watch? addCSBuffWatch(); // bool out is not used here and error handling is internal to the addCSBuffWatch } } /*! * \brief CloudSyncController::sendUIBuff * \details Sends the UI Buffer to the UI input file. * \param vData - the data to be sent out. * \return true on successful writing to the file buffer. * \sa _inp_File */ bool CloudSyncController::sendUIBuff(const QString &vData) { bool ok = true; QVariantList args ; Errors_Enum error = eError_OK; QString inpBuff; _date_inp_File = _location + // The location _dateFormatted + _dateSeparator + _inp_File; // The file name inpBuff = QString::number(_secSinceEpoch); inpBuff += _separator + QString::number(_seq++); inpBuff += _separator + vData; inpBuff += '\n'; // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // writing the message into the buffer. if ( ! Storage::FileHandler::write(_date_inp_File, inpBuff) ) { error = eError_LogFileInp; args = { _date_inp_File }; ok = false; goto lErr; } return ok; lErr: toLog(error, args); return ok; } /*! * \brief CloudSyncController::sendUIHistory * \details sends a response to the cloud sync upon request from the stored history. * \param vAction - the request id, which in current design is same as the Message comes to the UI. * \return true if a message history is available, false otherwise. */ bool CloudSyncController::sendUIHistory(qint32 vAction) { bool ok = true; QVariantList args ; Errors_Enum error = eError_OK; if ( ! _lastReceivedData.contains( vAction ) ) { error = eError_NoHistory; args = { vAction }; ok = false; goto lErr; } sendUIBuff(QString("%1,%2").arg(vAction).arg(_lastReceivedData[ vAction ].join(_separator))); return ok; lErr: toLog(error, args); return ok; } /*! * \brief CloudSyncController::onActionReceive * \details The slot which will be called when a CANBus message is received, and will be sent to CloudSync if related. * \param vAction - The message action * \param vData - The message data */ void CloudSyncController::onActionReceive(GuiActionType vAction, const QVariantList &vData) { QString inpBuff; // convert the data to the string list store/fetching it out from history. // the original vData will be used if their actual values needed. QStringList data; for (auto datum : vData) { data += datum.toString(); } switch (vAction) { case GuiActionType::ID_HDOperationModeData: case GuiActionType::ID_PreTreatmentStates : case GuiActionType::ID_TreatmentStates : case GuiActionType::ID_PostTreatmentStates: case GuiActionType::ID_DisinfectStates : // TODO: This section is the translation/mapping section // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv // preparing the message // store the last message data _lastReceivedData[eMessageID_DeviceState] = data; // prepare the buffer inpBuff = QString::number(eMessageID_DeviceState); for (auto var : vData) { inpBuff += _separator + var.toString(); } if ( ! sendUIBuff(inpBuff) ) break; default: break; } }