/*! * * 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 ApplicationController.cpp * \author (last) Dara Navaei * \date (last) 26-Mar-2024 * \author (original) Behrouz NematiPour * \date (original) 26-Aug-2020 * */ #include "ApplicationController.h" // Qt #include // Project #include "MainTimer.h" #include "MessageDispatcher.h" #include "Logger.h" #include "DeviceController.h" //#include "FileHandler.h" #include "GuiController.h" #include "Settings.h" #include "MSettings.h" #include "BluetoothInterface.h" /*! * \brief ApplicationController::ApplicationController * \details Constructor * \param parent - QObject parent owner object. * Qt handles the children destruction by their parent objects life-cycle. */ ApplicationController::ApplicationController(QObject *parent) : QObject(parent) { _post.setParent(this); } /*! * \brief ApplicationController initializer */ bool ApplicationController::init() { if ( _init ) return false; _init = true; initConnections(); LOG_DEBUG(QString("%1 Initialized").arg(metaObject()->className())); return true; } /*! * \brief ApplicationController::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 ApplicationController::init(QThread &vThread) { if ( ! init() ) return false; initThread(vThread); return true; } /*! * \brief ApplicationController::quit * \details quits the class * Calls quitThread */ void ApplicationController::quit() { quitThread(); // validated } /*! * \brief ApplicationController::initConnections * \details Initializes the required signal/slot connection between this class and other objects * to be able to communicate. */ void ApplicationController::initConnections() { connect(&_post , SIGNAL(didOSVersion (bool)), this , SLOT( onOSVersion (bool))); connect(&_post , SIGNAL( didEthernet (bool)), this , SLOT(onPOSTEthernet (bool))); connect(&_post , SIGNAL( didWiFi (bool)), this , SLOT(onPOSTWiFi (bool))); connect(&_post , SIGNAL( didBluetooth(bool)), this , SLOT(onPOSTBluetooth(bool))); connect(&_post , SIGNAL( didCloudSync(bool)), this , SLOT(onPOSTCloudSync(bool))); connect(&_post , SIGNAL( didFail (Gui::GuiAlarmID )), this , SLOT(onPOSTFail (Gui::GuiAlarmID ))); connect(&_post , SIGNAL( didDone (bool )), this , SLOT (onPOSTDone (bool ))); connect(&_MainTimer , SIGNAL( didTimeout()), this , SLOT(onMainTimerTimeout())); // From GUI connect(&_GuiController , SIGNAL(didActionTransmit(GuiActionType, const QVariantList &)), this , SLOT( onActionTransmit(GuiActionType, const QVariantList &))); connect(&_GuiController , SIGNAL(didQuitApplication()), this , SLOT( onQuitApplication())); connect(&_GuiController , SIGNAL(didTreatmentRangesDone(bool)), this , SLOT( onTreatmentRangesDone(bool))); // From HD/DG connect(&_MessageDispatcher, SIGNAL(didActionReceive(GuiActionType, const QVariantList &)), this , SLOT( onActionReceive(GuiActionType, const QVariantList &))); connect(&_MessageDispatcher, SIGNAL(didFailedTransmit(Sequence)), this , SLOT( onFailedTransmit(Sequence))); // USB drive connect(&_GuiController , SIGNAL(didUSBDriveUmount()), this , SLOT( onUSBDriveUmount())); connect(&_DeviceController , SIGNAL(didUSBDriveMount ()), this , SLOT( onUSBDriveMount ())); connect(&_DeviceController , SIGNAL(didUSBDriveRemove()), this , SLOT( onUSBDriveRemove())); connect(&_DeviceController , SIGNAL(didUSBSpaceChange(bool, qint64, qint64, quint8)), this , SLOT( onUSBSpaceChange(bool, qint64, qint64, quint8))); // SD Card connect(&_DeviceController , SIGNAL(didSDCardStateChange(bool, bool)), this , SLOT( onSDCardStateChange(bool, bool))); connect(&_DeviceController , SIGNAL(didSDCardSpaceChange(bool, qint64, qint64, quint8)), this , SLOT( onSDCardSpaceChange(bool, qint64, qint64, quint8))); connect(&_DeviceController , SIGNAL(didSDCardSpaceTooLow(quint8)), this , SLOT( onSDCardSpaceTooLow(quint8))); // Configuration Partition connect(&_DeviceController , SIGNAL(didCryptSetupMount(bool)), this , SLOT( onCryptSetupMount(bool))); connect(&_GuiController , SIGNAL(didExportLog (const GuiStringIndexMap &)), this , SLOT( onExportLog (const GuiStringIndexMap &))); connect(&_GuiController , SIGNAL(didExportService (const GuiStringIndexMap &)), this , SLOT( onExportService (const GuiStringIndexMap &))); connect(&_GuiController , SIGNAL(didExportTreatment (const GuiStringIndexMap &)), this , SLOT( onExportTreatment (const GuiStringIndexMap &))); connect(&_Logger , SIGNAL(didExportLogs ()), this , SLOT( onExport ())); connect(&_Logger , SIGNAL(didExportStat (quint32, const QString &, quint8)), this , SLOT( onExportStat (quint32, const QString &, quint8))); connect(&_Logger , SIGNAL(didLogIOFail ()), this , SLOT( onLogIOFail ())); connect(&_settingsWatcher, SIGNAL(finished ()), this , SLOT(onSettingsUpdate())); // Device Signal/Slots DEVICE_APP_INIT_CONNECTIONS_LIST // ---- Signal/Slots ADJUST_TRANSMT_MODEL_BRIDGE_CONNECTIONS(_GuiController ) ACTION_RECEIVE_MODEL_BRIDGE_CONNECTIONS(_MessageDispatcher) } /*! * \brief ApplicationController::initThread * \details Moves this object into the thread vThread. * And checks that this method is called from main thread. * Also connects quitThread to application aboutToQuit. * \param vThread - the thread */ void ApplicationController::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())); moveToThread(_thread); _thread->start(); } /*! * \brief ApplicationController::quitThread * \details Moves this object to main thread to be handled by QApplication * And to be destroyed there. */ void ApplicationController::quitThread() { if ( ! _thread ) return; // runs in thread moveToThread(qApp->thread()); // validated } /*! * \brief ApplicationController::onFailedTransmit * \details Called when failed to get a response back from the HD * \param seq - The sequence that failed to send to the HD. */ void ApplicationController::onFailedTransmit(Sequence seq) { emit didFailedTransmit(seq); } /*! * \brief Process the requested action * \details Processes the requested action * \param vAction - User requested Action * \param vData - Action data to be transmitted. */ void ApplicationController::onActionTransmit(GuiActionType vAction, const QVariantList &vData) { emit didActionTransmit(vAction, vData); } /*! * \brief An action has been confirmed * \details GUI requested an action. * In response HD confirmed the action. * \param vAction - Received action * \param vData - data of the action */ void ApplicationController::onActionReceive (GuiActionType vAction, const QVariantList &vData) { emit didActionReceive (vAction, vData); } /*! * \brief ApplicationController::onMainTimerTimeout * \details This slot is called by MainTimer::didTimeout each second * to call required methods like checkIn */ void ApplicationController::onMainTimerTimeout() { checkIn(); } /*! * \brief ApplicationController::onUSBDriveMount * \details This is the slot which connects to the _DeviceController didUSBDriveMount signal * and notifies the other classes (GuiController) by emitting its signal didUSBDriveMount */ void ApplicationController::onUSBDriveMount () { emit didUSBDriveMount(); } /*! * \brief ApplicationController::onUSBDriveRemove * \details This is the slot which connects to the _GuiController didUSBDriveUmount signal * and notifies the other classes (DeviceController) by emitting its signal didUSBDriveUmount */ void ApplicationController::onUSBDriveUmount() { emit didUSBDriveUmount(); } /*! * \brief ApplicationController::onUSBDriveRemove * \details This is the slot which connects to the _DeviceController didUSBDriveRemove signal * and notifies the other classes (GuiController) by emitting its signal didUSBDriveRemove */ void ApplicationController::onUSBDriveRemove() { emit didUSBDriveRemove(); } void ApplicationController::onUSBSpaceChange(bool vReady, qint64 vTotal, qint64 vAvailable, quint8 vPercent) { //DEBUG:0: qDebug() << "ApplicationController::onUSBSpaceChange" << vReady << vTotal << vAvailable << vPercent; emit didUSBSpaceChange(vReady, vTotal, vAvailable, vPercent); } /*! * \brief ApplicationController::onSDCardStateChange * \details This is the slot which connects to the _DeviceController didSDCardStateChange signal * and notifies the other classes (GuiController) by emitting its signal didSDCardStateChange * \param vIsReady - SDCard is Ready * \param vIsReadOnly - SDCard is ReadOnly */ void ApplicationController::onSDCardStateChange(bool vIsReady, bool vIsReadOnly) { //DEBUG:0: qDebug() << " ***** ApplicationController " << Storage::SDCard_Base_Path_Name << vIsReady; emit didSDCardStateChange(vIsReady, vIsReadOnly); } void ApplicationController::onSDCardSpaceChange(bool vReady, qint64 vTotal, qint64 vAvailable, quint8 vPercent) { emit didSDCardSpaceChange(vReady, vTotal, vAvailable, vPercent); } /*! * \brief ApplicationController::onSDCardSpaceTooLow * \details The handler slot for the didSDCardSpaceTooLow signal comes form DeviceController * \param vAvailablePercent - the minimum limit of available storage space */ void ApplicationController::onSDCardSpaceTooLow(quint8 vAvailablePercent) { emit didSDCardSpaceTooLow(vAvailablePercent); } /*! * \brief ApplicationController::onExportLog * \details the slot which will be called by UI to so the log export. */ void ApplicationController::onExportLog(const GuiStringIndexMap &vExportList) { LOG_EXPORTLOG(vExportList); } /*! * \brief ApplicationController::onExportService * \details the slot which will be called by UI to do the service log export. */ void ApplicationController::onExportService(const GuiStringIndexMap &vExportList) { LOG_EXPORTERR(vExportList); } /*! * \brief ApplicationController::onExportTreatment * \details the slot which will be called by UI to do the treatment treatment log export. */ void ApplicationController::onExportTreatment(const GuiStringIndexMap &vExportList) { LOG_EXPORTTRT(vExportList); } /*! * \brief ApplicationController::onExport * \details the slot which will be called by logger is done exporting. */ void ApplicationController::onExport() { emit didExport(); } void ApplicationController::onExportStat(quint32 vIndex, const QString &vFileName, quint8 vPercent) { // DEBUG: qDebug() << "1" << vIndex << vFileName << vPercent; emit didExportStat(vIndex, vFileName, vPercent); } /*! * \brief ApplicationController::checkIn * \details This is the message which has to be send over the CANBus * as an monitor for other nodes on the bus to notify UI is alive */ void ApplicationController::checkIn() { // DEBUG: return; #ifndef DISABLE_KEEP_ALIVE QVariantList mData; int mFakeDataLen = gFakeData.length(); if (mFakeDataLen) { if (gFakeSeqAtBegin) { createFakeSeqAtBeginLongMessage(mData, mFakeDataLen); } else { createFakeSequencedLongMessage (mData, mFakeDataLen); } } else { mData += static_cast(GuiActionData::NoData); } // AUTOGEN ISSUE onActionTransmit(GuiActionType::ID_TDCheckIn, mData); #endif } /*! * \brief ApplicationController::createFakeSequencedLongMessage * \details This method is creating the fake message with frame sequence * which we use for Denali Message test * \param vFakeDataLen */ void ApplicationController::createFakeSequencedLongMessage(QVariantList &vData, const int vFakeDataLen) { QByteArray data; if (vFakeDataLen == 1 && gFakeData == QByteArray::fromHex(gFakeData_default)) { static quint16 txCount = 0; Types::U16 seq; quint8 dataBytesLeft = 0; const quint8 crcBytesLen = 2; for (int i = 0; i < 13; i++) { switch (i) { case 0: // First frame : HEAD seq.value = txCount; data += seq.bytes[0]; data += seq.bytes[1]; break; case 12: // Last frame with CRC dataBytesLeft = 8 - sizeof(seq) - crcBytesLen; for (int j = 0; j < dataBytesLeft; j++) { data += (char)(0); } seq.value = txCount; data += seq.bytes[0]; data += seq.bytes[1]; break; default: // Middle Frames dataBytesLeft = 8 - sizeof(seq); for (int j = 0; j < dataBytesLeft; j++) { data += (char)(0); } seq.value = txCount; data += seq.bytes[0]; data += seq.bytes[1]; break; } Types::safeIncrement(txCount); } vData += QByteArray::fromHex(data.toHex()); } else { vData += gFakeData; } } /*! * \brief ApplicationController::createFakeSequencedAtBeginLongMessage * \details This method is creating the fake message with frame sequence * which we use for Denali Message test * \param vFakeDataLen */ void ApplicationController::createFakeSeqAtBeginLongMessage(QVariantList &vData, const int vFakeDataLen) { QByteArray data; if (vFakeDataLen == 1 && gFakeData == QByteArray::fromHex(gFakeData_default)) { static quint32 txCount = 0; Types::U32 seq; quint8 dataBytesLeft = 0; const quint8 crcBytesLen = 2; for (int i = 0; i < 13; i++) { switch (i) { case 0: // First frame : HEAD seq.value = txCount; data += seq.bytes[0]; data += seq.bytes[1]; //data += seq.bytes[3]; // Chopped off //data += seq.bytes[4]; // Chopped off break; case 12: // Last frame with CRC seq.value = txCount; data += seq.bytes[0]; data += seq.bytes[1]; data += seq.bytes[2]; data += seq.bytes[3]; dataBytesLeft = 8 - sizeof(seq) - crcBytesLen; for (int j = 0; j < dataBytesLeft; j++) { data += (char)(0); } break; default: // Middle Frames dataBytesLeft = 8 - sizeof(seq); seq.value = txCount; data += seq.bytes[0]; data += seq.bytes[1]; data += seq.bytes[2]; data += seq.bytes[3]; for (int j = 0; j < dataBytesLeft; j++) { data += (char)(0); } break; } Types::safeIncrement(txCount); } vData += QByteArray::fromHex(data.toHex()); } else { vData += gFakeData; } } void ApplicationController::postDoneRequest() { // AUTOGEN ISSUE //AdjustUIPostFinalResultRequestData data; //data.mResult = _post.isDone(); //emit didAdjustment(data); } /*! * \brief ApplicationController::initSettings * \details The external method available to request for initializing the settings * To start the task in Application Tread, emits a signal which will call a slot to take care of the execution. */ void ApplicationController::initSettings() { QFuture mFuture = QtConcurrent::run( [=]() -> int { // made the call a lambda to make sure there is no function to accidentally being called, out of thread [developer safety]. //TODO The Settings shall be the Singleton SettingsController and modify the MSettings like the others. return Storage::Settings::readConfigurations(); }); _settingsWatcher.setFuture(mFuture); } /*! * \brief onSettingsUpdate * \details when the Settings reads the .conf files and fills the MSettings emits this finished signal * then this slot is called to notify the GuiController about the settings that being ready. */ void ApplicationController::onSettingsUpdate() { // AUTOGEN ISSUE //onActionReceive(SettingsData()); /// POST /// //call initialization functions when setting's ready. //TODO move the initSettig in the Application POST since it is part of the post. _settingsError = _settingsWatcher.result(); //DEBUG qDebug() << " ***** " << sender() << _settingsError; _post.isDone( ! _settingsError ); if ( _settingsError ) { LOG_APPED_PO(QString("Settings read failed")); alarmTrigger(Gui::GuiAlarmID::ALARM_ID_UI_POST_FAILURE_SETTINGS_BAD); } else { _Settings.datetimeFormat(); } // this singal can be emmited here [ others are postponed to the onTreatmentRangesDone ] // here we know the CRC was fine with not empty configuration and all files can be read. emit didSettingsDone( ); // MessageDispatcher -> MessageInterpreter : updateUnhandledMessages } /*! * \brief ApplicationController::onOSVersion * \details Notifies the OS Version POST status */ void ApplicationController::onOSVersion(bool vPass) { // sending the data first, therefore in the slot for the didPOST we have the vPass and the Data to decide to use the data or not. emit didPOSTOSVersionData (_post.osVersion()); emit didPOSTOSVersion (vPass); } /*! * \brief ApplicationController::onPOSTEthernet * \details sends the Ethernet mac to device controller */ void ApplicationController::onPOSTEthernet(bool vPass) { // sending the data first, therefore in the slot for the didPOST we have the vPass and the Data to decide to use the data or not. emit didPOSTEthernetData(_post.macEthernet()); emit didPOSTEthernet (vPass); } /*! * \brief ApplicationController::onPOSTWiFi * \details Starts the WiFi Interface */ void ApplicationController::onPOSTWiFi(bool vPass) { if (vPass) { // _WifiInterface.doStart(); // =========================================================== FIX ME } // sending the data first, therefore in the slot for the didPOST we have the vPass and the Data to decide to use the data or not. emit didPOSTWirelessData(_post.macWireless()); emit didPOSTWireless (vPass); //DEBUG qDebug() << " ---------- " << _post.macWireless(); } /*! * \brief ApplicationController::onPOSTBluetooth * \details Starts the Bluetooth Interface */ void ApplicationController::onPOSTBluetooth(bool vPass) { if (vPass) { _BluetoothInterface.valid(true); _BluetoothInterface.doStart(); } else { _BluetoothInterface.doNotifyStatePOSTError(); } // sending the data first, therefore in the slot for the didPOST we have the vPass and the Data to decide to use the data or not. emit didPOSTBluetoothData (_post.macBluetooth()); emit didPOSTBluetooth (vPass); } /*! * \brief ApplicationController::onPOSTCloudSync * \details Notifies the CloudSync POST status */ void ApplicationController::onPOSTCloudSync(bool vPass) { // sending the data first, therefore in the slot for the didPOST we have the vPass and the Data to decide to use the data or not. emit didPOSTCloudSyncData("" /*_post.netCloudSync*/); // not needed and POST is not getting it yet. [ApplicationController => DeviceController] emit didPOSTCloudSync (vPass); } /*! * \brief ApplicationController::alarmTrigger * \details Queues the alarms, or send the list, or send one alarm * \param vAlarmID - The alarm ID to be triggered or queued. * \param vSend - if true the list of queued alarms will be send * \param vSingle - if true and vSend is true only the alarm vAlarmID will be triggered. */ void ApplicationController::alarmTrigger(GuiAlarmID vAlarmID, bool vSend, bool vSingle) { static QVector failList; if ( vSend ) { if ( vSingle ) { emit didActionTransmit(Gui::GuiActionType::ID_AlarmTriggered, {vAlarmID ,0,0,0,0,0,0,0}); } else { for ( auto alarmID : failList ) { //DEBUG qDebug()<< "sending POST fail alarm# "<< alarmID; emit didActionTransmit(Gui::GuiActionType::ID_AlarmTriggered, {alarmID ,0,0,0,0,0,0,0}); } } } else { failList.append(vAlarmID); } } /*! * \brief ApplicationController::onPOSTFail * \details Sends the Alarm Trigger message, if the vSend is true, * or * Queues the alarms if vSend is false. * \param vAlarmID - The alarm ID * \param vSend - False to queue the vAlarmID alarm ID, or True to send the list of the queued alarms. */ void ApplicationController::onPOSTFail(GuiAlarmID vAlarmID) { alarmTrigger(vAlarmID, false, false); } /*! * \brief ApplicationController::onPOSTDone * \details Sends the POST Final message */ void ApplicationController::onPOSTDone(bool /*vPass*/) { LOG_DEBUG("ApplicationPOST Done"); /// in manufacturing or update mode the configurations must reside in /root/home /// therefore the settings can be initialized after POST. #ifdef LEAHI_DIGI_BOARD initSettings(); #else #ifdef BUILD_FOR_DESKTOP initSettings(); #else if ( gEnableManufacturing || gEnableUpdating ) initSettings(); #endif #endif } void ApplicationController::onQuitApplication() { //DEBUG qDebug() << metaObject()->className() << __FUNCTION__ << QThread::currentThread(); emit didQuitApplication(); qApp->quit(); } /*! * \brief ApplicationController::versionsRequest * \details Sends a version request */ void ApplicationController::versionsRequest() { // AUTOGEN ISSUE //AdjustVersionsRequestData adjustVersionsRequestData; //emit didAdjustment(adjustVersionsRequestData); LOG_DEBUG("POSTVersionReq Sent"); } /*! * \brief ApplicationController::institutionalRequest * \details Sends an institutional record request */ void ApplicationController::institutionalRequest() { // AUTOGEN ISSUE //AdjustInstitutionalRequestData adjustInstitutionalRequestData; //emit didAdjustment(adjustInstitutionalRequestData); LOG_DEBUG("POSTInstitutionalRecordReq Sent"); } /*! * \brief ApplicationController::onstartPOST * \details The POST entry point * - Sends the first async check-in to the HD to let HD know it can start it's POST and UI is ready to communicate. * - Connects to the POST process to be able to trigger an alarm during the POST to be listed in the active alarms list. * - Connects to the POST process to be able to send the final UI POST result. * - Starts the UI POST * \return void */ void ApplicationController::onstartPOST() { LOG_DEBUG("ApplicationPost Start"); _post.start(); } /*! * \brief ApplicationController::onCryptSetupMount * \details It is the slot to handle _DeviceController::didCryptSetupMount signal. * Tells the settings start initiate. */ void ApplicationController::onCryptSetupMount(bool vPass) { //DEBUG qDebug() << " ***** " << Q_FUNC_INFO << vPass; if ( ! vPass ) { _post.isDone(vPass); LOG_APPED_PO(QString("Configuration partition encryption failed.")); alarmTrigger(Gui::GuiAlarmID::ALARM_ID_UI_POST_FAILURE_SETTINGS_BAD); } // if ( vPass ) // it needs more investigation initSettings(); } /*! * \brief ApplicationController::onLogIOFail * \details This slot is when an SD I/O failure is indicated */ void ApplicationController::onLogIOFail() { //DEBUG qDebug()<<"SENDING " << Gui::GuiAlarmID::ALARM_ID_HD_UI_SDCARD_FAILURE; alarmTrigger(Gui::GuiAlarmID::ALARM_ID_UI_EVNT_FAILURE_SDCARD, true, true); // send, single, this happens after POST. } /*! * \brief ApplicationController::onTreatmentRangesDone * \details This slot is when the treatment ranges during initial post process is done */ void ApplicationController::onTreatmentRangesDone(bool vPass) { //DEBUG qDebug() << " ***** " << Q_FUNC_INFO; if ( ! vPass ) { _post.isDone(vPass); LOG_APPED_PO(QString("Configuration treatment ranges failed.")); alarmTrigger(Gui::GuiAlarmID::ALARM_ID_UI_POST_FAILURE_SETTINGS_BAD); } // HD POST // UI is done, let HD start the communication [ UI Check-in is the HD commjunication listener starter ] checkIn(); emit didCheckInBegin (); alarmTrigger (Gui::GuiAlarmID::ALARM_ID_NO_ALARM, true); // send queued fails postDoneRequest (); // send HD the POST result versionsRequest (); // send HD the version request institutionalRequest (); // send HD the institutional record request emit didPOSTPass (_post.isDone( )); // GuiController -> GuiView : didPOSTPass(bool) }