/*! * * Copyright (c) 2021-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 DeviceController.h * \author (last) Behrouz NematiPour * \date (last) 04-Apr-2024 * \author (original) Behrouz NematiPour * \date (original) 03-Jun-2021 * */ #pragma once // Qt #include #include #include // Project #include "main.h" // Doxygen : do not remove #include "DeviceGlobals.h" #include "DeviceModels.h" #include "DeviceError.h" // Define #define _DeviceController Device::DeviceController::I() // forward declarations class tst_initializations; namespace Device { using namespace Model; /*! * \brief The TimedProcess class * \details This is a timed process class which has a timeout * and will kill the process if it can't be finished within the given timeout * in milliseconds. * Following is the defferent approaches to do the upload the logs: * \startuml * == Latest == * note right Current * Latest log to be uploaded * Pros: * 1 - Latest includes the latest detected alarms to be available for download, * In case of a device issue to be investigated. * 2 - Delay will be part of the compression time, on upload interval * 3 - Possible change of the log file from .u.log to .log in compresssed file * 4 - File name on the cloud and on the device will be the same * 5 - One compression at a time when needed * Cons: * 1 - Oldest that are not uploaed can get deleted, before gets uploaded * end note * * Current -> Current : .log * * Trigger -> NOT_Upload : .log => .u.log * NOT_Upload -> NOT_Upload : .u.log : latest * NOT_Upload -> NOT_Upload : .tar.gz : create * NOT_Upload -> NOT_Upload : .u.log : empty * note right * Empty the log which has already compressed won't get extra space, * therfore the log retention will not start deleting other logs while we uploading. * end note * WIP_Upload -> CloudSync : .tar.gz : notify * CloudSync -> DID_Upload : .tar.gz : uploaded * DID_Upload -> DID_Upload : .u.log : delete * DID_Upload -> NOT_Upload * Trigger -> Trigger : .log => .u.log * Trigger -> Trigger : .log => .u.log * * 'newpage --------------------------------------- * * == Oldest == * note right Current * Latest log to be uploaded * Pros: * 1 - Oldest that are not uploaed can get deleted, before gets uploaded * 2 - Delay will be part of the compression time, on upload interval * 3 - Possible change of the log file from .u.log to .log in compresssed file * 4 - File name on the cloud and on the device will be the same * 5 - One compression at a time when needed * Cons: * 1 - Latest includes the latest detected alarms will wait until all the other logs are downloaded * end note * * Current -> Current : .log * * Trigger -> Trigger : .log => .u.log * Trigger -> Trigger : .log => .u.log * Trigger -> NOT_Upload : .log => .u.log * NOT_Upload -> NOT_Upload : .u.log : oldest * NOT_Upload -> NOT_Upload : .tar.gz : create * NOT_Upload -> NOT_Upload : .u.log : empty * note right * Empty the log which has already compressed won't get extra space, * therfore the log retention will not start deleting other logs while we uploading. * end note * WIP_Upload -> CloudSync : .tar.gz : notify * CloudSync -> DID_Upload : .tar.gz : uploaded * DID_Upload -> DID_Upload : .u.log : delete * DID_Upload -> NOT_Upload * * 'newpage ' --------------------------------------- * * == KISS - Oldest == * note right Current * Latest log to be uploaded * Pros: * 1 - Simple design and less file handling * 2 - Delay will be part of the compression time * Cons: * 1 - Multiple compression may be in progeress at a time and utilizes CPU, since the compression is done same time, * in oppose to the other ones which will compress when needed [ one at a time ]. * 2 - File name on the cloud and on the device will NOT be the same * end note * * Current -> Current : .log * Trigger -> Trigger : .u.tar.gz : create * Trigger -> Trigger : .u.tar.gz : create * Trigger -> NOT_Upload : .u.tar.gz : create * NOT_Upload -> NOT_Upload : .u.tar.gz : oldest * WIP_Upload -> CloudSync : .u.tar.gz : notify * CloudSync -> DID_Upload : .u.tar.gz : uploaded * DID_Upload -> DID_Upload : .u.tar.gz => .tar.gz * DID_Upload -> NOT_Upload * \enduml * * */ class TimedProcess : public QObject { Q_OBJECT explicit TimedProcess(QObject *parent = nullptr) : QObject(parent) { } int _pid = 0; int _timeout = 0; QProcess *_process = nullptr; QString _command = ""; QStringList _arguments = {}; private slots: void onFinish(int) { killTimer(_pid); // emit _process->finished(-1, QProcess::CrashExit); _process->kill(); _process = nullptr; //->deleteLater(); deleteLater(); } public: TimedProcess(QProcess *vProcess, const QString &vCommand, int vTimeout, QStringList vArguments = {}) : QObject(), _timeout(vTimeout), _process(vProcess), _command(vCommand), _arguments(vArguments) { connect(_process, SIGNAL( finished(int)), this , SLOT(onFinish (int))); } void start() { #ifdef BUILD_FOR_DESKTOP //FIXME : Revert this back to the #ifdef and remove the #if(n)def to avoid executing the shell scripts on VM. qDebug() << "DeviceController.TimedProcess: " << _timeout << _command << _arguments; QFileInfo fileInfo(_command); QString path = fileInfo.absolutePath(); QString name = "test.sh"; _command = QString("%1/%2").arg(path, name); #endif _pid = startTimer(_timeout); _process->setWorkingDirectory(Storage::Scripts_Path_Name()); //FIXME: get it in the DeviceController once and send it to constructor. _process->start(_command, _arguments); } protected: void timerEvent(QTimerEvent *) { onFinish(-1); } }; /*! * \brief The DeviceController class * \details This class is the device controller meaning all the device interactions and settings like Volume(Device), Brightness, WiFi, Date, Time, etc * has to be done through this controller and it's dedicated thread and by using the QProcess(es) which is a call to the administrative shell scripts. * It also is watching for the USB and SD-Card drives in Linux file system. * It has the interval of 1000 ms and will look if any device between /dev/sda1 to /dev/sdz1 exists then will mount it in /media/usb, * and does check if the SD-Card has been mounted under /media/sdcard, * but for SD-Card does not try to mount it and it has to be mounted prior to UI Application start * and that's because from UI Application perspective it is not a removable/hot-plug device (although physically it is). * This class works in its own thread and will send notification by emitting signals about the USB device status * which are Mounted, Unmounted, Removed * and SD-Card space conditions changes. * \note : - 'Removed' is when no USB device present. * - Only brightness is using this class currently */ class DeviceController : public QObject { Q_OBJECT // Singleton SINGLETON(DeviceController) // friends friend class ::tst_initializations; bool _mounted = false; bool _umounted = false; bool _removed = false; const int _interval = 1000; // in ms = 1s const int _pendingInterval = 60 ; // 1m int _pendingCounter = 0 ; // QString _pendingLog = ""; QString _interface = "wlan0"; const qint8 _minRequiredAvailableSpacePercent = 10; QThread *_thread = nullptr; bool _init = false; QFileSystemWatcher _fileSystemWatcher; QString _osVersion = ""; QString _macEthernet = ""; QString _macWireless = ""; QString _macBluetooth = ""; QString _netCloudSync = ""; bool _hasThread = false; bool _hasSalt = false; bool _wifiAvailable = false; //TODO: all of these should use the TimesProcess (instead of QProcess) // to be able to kill the process automatically after the set time out. DEVICE_DEV_DEFINITION_LIST public slots: bool init(); bool init(QThread &vThread); void quit(); void doScreenshot(const QImage &vImage, const QString &vFileName); private slots: void onScreenshot(const QImage &vImage, const QString &vFileName); void onUSBDriveUmount(); void onWatchFileChanged(const QString &vFile); void onEventThreadChange (); /*! * \brief onPOSTData * \details These signals will be emitted when UI is done with the POST and will let DeviceView update its property(ies). * \param vOSVersion - OS Version * \param vMacEthernet - Ethernet Mac Adress * \param vMacWireless - Wireless Mac Adress * \param vMacBluetooth - Bluetooth Mac Adress * \param vNetCloudSync - CloudSync IP Adress */ void onPOSTOSVersionData (const QString &vOSVersion ); void onPOSTEthernetData (const QString &vMacAddress); void onPOSTWirelessData (const QString &vMacAddress); void onPOSTBluetoothData (const QString &vMacAddress); void onPOSTCloudSyncData (const QString &vNetAddress); void onLogBackup (const QString &vFileName ); void onLogUpload (const QString &vFileName ); protected: void timerEvent(QTimerEvent *) override; private: void initConnections(); void initThread(QThread &vThread); void quitThread(); bool usbSeek(QString &vDevice); bool driveSpaceCheck(const QString &vPath, qint64 &vTotalBytes, qint64 &vAvailableBytes, bool *vIsReadOnly = nullptr); template bool checkError(DeviceError::Scripts_Error_Enum vError, TModel &vModel,QString vExtraLogInfo = ""); DeviceError::Scripts_Error_Enum checkScript(QString &vScript, const QString &vShellScript); bool addWatch(const QString &vFilePath); void checkConfugurationMountReady(); void findPendingLogs(); void checkWiFi(); void checkWifiConnectionReady(); bool logBackup(const QString &vFileName); bool logUpload(const QString &vFileName); signals: /*! * \brief didScreenshot * \details The screenshot signal to run the onScreenshot slot in device controller thread. * \param vImage - The image source object * \param vFileName - The filename to same the image to */ void didScreenshot(const QImage &vImage, const QString &vFilenNme); /*! * \brief didUSBDriveMount * \details notifies UI when USB device is available and has been mounted. */ void didUSBDriveMount (); /*! * \brief didUSBDriveUmount * \details notifies USB watcher on UI(user) request for USB umount. */ void didUSBDriveUmount(); /*! * \brief didUSBDriveRemove * \details notifies UI when USB device has been removed(not exists). */ void didUSBDriveRemove(); void didUSBStateChange(bool vIsReady, bool vIsReadOnly); void didUSBSpaceChange(bool vReady, qint64 vTotal, qint64 vAvailable, quint8 vPercent); /*! * \brief didSDCardFreeSpaceChange * \param vReady - Device is mounted and ready * \note if device ejected manually system assumes it's still ready. * \param vTotal - Returns the total volume size in bytes. * Returns -1 if QStorageInfo object is not valid * \param vAvailable - Returns the size (in bytes) available for the current user. * It returns the total size available if the user is the root user or a system administrator. * This size can be less than or equal to the free size returned by bytesFree() function. * Returns -1 if QStorageInfo object is not valid. * \param vPercent - The percentage of available space. * \note Will emitted if only one of the publishing parameter changes. */ void didSDCardSpaceChange(bool vReady, qint64 vTotal, qint64 vAvailable, quint8 vPercent); /*! * \brief didSDCardStateChange * \details If SDCard state changes like removed or is not present this signal will emit. */ void didSDCardStateChange(bool vIsReady, bool vIsReadOnly); /*! * \brief didSDCardSpaceTooLow * \details this signal will emit ones the available space left on the SD-Card * is less than minimum required percentage defined in _minRequiredAvailableSpacePercent. * \param vAvailablePercent */ //TODO need to rename to be more generic, being used for SD card and settings partition space low void didSDCardSpaceTooLow(quint8 vAvailablePercent); /*! * \brief didSettingsPartitionSpaceChange * \param vReady - Device is mounted and ready * \note if device ejected manually system assumes it's still ready. * \param vTotal - Returns the total volume size in bytes. * Returns -1 if QStorageInfo object is not valid * \param vAvailable - Returns the size (in bytes) available for the current user. * It returns the total size available if the user is the root user or a system administrator. * This size can be less than or equal to the free size returned by bytesFree() function. * Returns -1 if QStorageInfo object is not valid. * \param vPercent - The percentage of available space. * \note Will emitted if only one of the publishing parameter changes. */ void didSettingsPartitionSpaceChange(bool vReady, qint64 vTotal, qint64 vAvailable, quint8 vPercent); /*! * \brief didSettingsPartitionStateChange * \details If the settings partition state changes like removed or is not present this signal will emit. */ void didSettingsPartitionStateChange(bool vIsReady, bool vIsReadOnly); void didActionReceive( const DeviceBrightnessResponseData &vBrightness ); void didWatchFileChange(const QString &vFile); /*! * \brief didEventThreadChange * \details this signal will be emitted when the curr */ void didEventThreadChange (QPrivateSignal); void didPOSTOSVersionData (const QString &vOSVersion ); void didPOSTEthernetData (const QString &vMacAddress); void didPOSTWirelessData (const QString &vMacAddress); void didPOSTBluetoothData (const QString &vMacAddress); void didPOSTCloudSyncData (const QString &vNetAddress); /*! * \brief didCryptSetupMount * \details will be emitted when decrypting the configuration partition is. * to notify the ApplicationController to call initSettings */ void didCryptSetupMount ( bool vPass ); /*! * \brief didFactoryReset * \details will be emitted when the Factory Reset is done, * to notify the CloudSyncController to do the Factory Reset/Logs Removal. * \param vPass */ void didFactoryReset ( bool vPass ); /*! * \brief didDecommissioning * \details will be emitted when the Decommissioning is done, * to notify the CloudSyncController to do the Decommissioning/TokenRemoval. * \param vPass */ void didDecommissioning ( bool vPass ); /*! * \brief didPendingLog * \details will be emitted when a log file is pending to be uploaded. * \param vFileName - the pending log file name */ void didPendingLog (const QString &vFileName, const QString vChecksum ); /*! * \brief logBackup * \details notification of the log back up complete (either case of pass or fail) * \param vFileName - file name to backup * \param vPass - false if the backup failed. * \return void */ void didLogBackup ( bool vPass, const QString &vFileName ); /*! * \brief didLogUpload * \details notification of pending log upload file rename complete (rename and remove the 'u.' (pending sub-ext)) * \param vPass - true if the pending log file name rename was successful. * \param vFileName - the pending log file name (meaning it includes 'u.' ) */ void didLogUpload ( bool vPass, const QString &vFileName ); /*! * \brief didWiFiIP * \details notifies UI when WiFi netowrk data has changed * \param vData - WiFi data to display on UI * \param vWifiError - Let UI know the message cominmg is a error not IP */ void didWiFiIP ( const QString &vData, bool vWifiError); private: // ----- USB void usbCheck (); void usbError (const QString &vDevice ); void usbMountReq(const QString &vDevice, bool vIsMount = true ); void usbMount (const QString &vDevice ); void usbUmount (const QString &vDevice ); void usbRemove (); void sdcardSpaceCheck(); void usbSpaceCheck(); void settingsPartitionSpaceCheck(); // ----- WiFi void wifiListRequest(); void wifiInfoRequest(); void wifiConnectRequest(const DeviceWifiConnectRequestData &vData); // ----- Factory Reset void factoryResetRequest(); // ----- Decommission void decommissionRequest(const DeviceDecommissionRequestData &vData); // ----- RootSSHAccess void rootSSHAccessRequest(const DeviceRootSSHAccessRequestData &vData); SAFE_CALL_EX2(doAddWatch, const QString &, bool) }; }