Index: sources/storage/TreatmentLog.cpp =================================================================== diff -u -r20b286ab574a6b8ad25a51aeb3477506f7dafc5d -r6c6f1f5d466badd9b4fd67be7c907234c342b2a2 --- sources/storage/TreatmentLog.cpp (.../TreatmentLog.cpp) (revision 20b286ab574a6b8ad25a51aeb3477506f7dafc5d) +++ sources/storage/TreatmentLog.cpp (.../TreatmentLog.cpp) (revision 6c6f1f5d466badd9b4fd67be7c907234c342b2a2) @@ -1,15 +1,15 @@ /*! * - * Copyright (c) 2019-2020 Diality Inc. - All Rights Reserved. - * copyright + * Copyright (c) 2021-2023 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 TreatmentLog.cpp - * author (last) Behrouz NematiPour - * date (last) 4/30/2021 - * author (original) Behrouz NematiPour - * date (original) 4/30/2021 + * \file TreatmentLog.cpp + * \author (last) Behrouz NematiPour + * \date (last) 07-Apr-2023 + * \author (original) Behrouz NematiPour + * \date (original) 04-May-2021 * */ #include "TreatmentLog.h" @@ -22,14 +22,18 @@ #include "FileHandler.h" #include "Logger.h" #include "MSettings.h" +#include "CloudSyncController.h" +#include "GuiGlobals.h" +#include "MAlarmStatusData.h" + using namespace Storage; #define NONE "N/A" #define FLOAT3 0,'f',3 #define ADDTITLE(vTITLE) logContent += QString("[%1]\n").arg(vTITLE) #define ADDALINE(vTEXT ) logContent += QString("%1\n" ).arg(vTEXT ) -#define ADDTOLOG(vINDEX) index = vINDEX; logContent += _titles[index] + sep + _values[index] + sep + _units[index] + "\n"; +#define ADDTOLOG(vINDEX) index = vINDEX; logContent += title(index) + sep + value(index) + sep + unit(index) + "\n"; /*! * \brief TreatmentLog::TreatmentLog @@ -41,6 +45,7 @@ logPath(Logger::eLogTrtmt, _Logger.logPath(Logger::eLogTrtmt)); } + /*! \brief Connection Initializer \details All the class signal/slot connections are defined here. */ @@ -53,42 +58,62 @@ connect(&_saveWatcher , SIGNAL(finished()), this , SLOT(onSave ())); + + connect(&_CloudSyncController , SIGNAL(didCloudSyncStatus(bool)), + this , SLOT( onCloudSyncStatus(bool))); + + connect(&_CloudSyncController , SIGNAL(didTxCodeReceive(const QString &)), + this , SLOT( onTxCodeReceive(const QString &))); + } /*! + * \brief TreatmentLog::timerEvent + * \details The overloaded method of the main class to capture the QObject timer. + */ +void TreatmentLog::timerEvent(QTimerEvent *) +{ + testPendingTxReports(); +} + +/*! * \brief TreatmentLog::initModel * Initializing the model for the constant values. * \param vData - the response model data. */ -void TreatmentLog::initModel(const AdjustTreatmentLogResponseData &vData) +void TreatmentLog::initModel(const AdjustTreatmentLogResponseData &vData, const QString &vPatientID) { // Formatted values QString mStrText = "%1"; - // qDebug() << _Settings.groups(); - // SRSUI910 : PRS187 : Clinical - Data - Order + // DEBUG: qDebug() << _Settings.groups(); + // SRSUI910 : PRS178 : Clinical - Data - Order - QString mTreatmentDuration = QTime (0, 0).addSecs(vData.mTreatmentDuration ).toString("HH:mm"); - QString mActualTreatmentDuration = QTime (0, 0).addSecs(vData.mActualTreatmentDuration ).toString("HH:mm"); - QString mTreatmentDateTime = QDateTime::fromSecsSinceEpoch ( vData.mTreatmentDateTime ).toString("yyyy/MM/dd HH:mm"); - quint32 mHeparinConcentration = 1000; // vData.mHeparinConcentration + QString mTreatmentDurationFmt = QTime (0, 0).addSecs(int(vData.mTreatmentDuration )).toString(_timeFormat); + QString mActualTreatmentDurationFmt = QTime (0, 0).addSecs(int(vData.mActualTreatmentDuration )).toString(_timeFormat); + QString mTreatmentDuration = QString::number(vData.mTreatmentDuration / 60 ); // Converted seconds to minutes. + QString mActualTreatmentDuration = QString::number(vData.mActualTreatmentDuration / 60 ); // Converted seconds to minutes. + QString mTreatmentStartDateTime = Format::fromEpoch( vData.mTreatmentStartEpoch , _datetimeFormat); + QString mTreatmentEndDateTime = Format::fromEpoch( vData.mTreatmentEndEpoch , _datetimeFormat); + // TODO : The settings needs modification not only to define the category and groups in a common header but also the settings itself needs some mods. // - the category shall become as part of the group // - the structure of the settings should become horizontal which vertical now. - QString mAcidConcentrateType = _Settings.key("Acid Concentrate" , vData.mAcidConcentrateType ); - QString mBicarbonateConcentrateType = _Settings.key("Bicarbonate Concentrate" , vData.mBicarbonateConcentrateType ); - QString mDialyzerType = _Settings.key("Dialyzer Type" , vData.mDialyzerType ); - QString mHeparinType = _Settings.key("Heparin Type" , vData.mHeparinType ); - QString mWaterSampleTestResult = _Settings.key("Water Sample Result" , vData.mWaterSampleTestResult ); + QString mCategory = Storage::Settings_Category_ConfigurationsDataList; + QString mAcidConcentrateType = _Settings.key(mCategory, "Acid Concentrate Options" , vData.mAcidConcentrateType ); + QString mBicarbonateConcentrateType = _Settings.key(mCategory, "Bicarbonate Concentrate Options" , vData.mBicarbonateConcentrateType ); + QString mDialyzerType = _Settings.key(mCategory, "Dialyzer Type Options" , vData.mDialyzerType ); + QString mHeparinType = _Settings.key(mCategory, "Heparin Type Options" , vData.mHeparinType ); + QString mWaterSampleTestResult = _Settings.key(mCategory, "Water Sample Result" , vData.mWaterSampleTestResult ); // init/fill/clear the _values _values.clear(); for (int i = 0; i < eTreatmentLogIndexCount; i++) _values << ""; + _deviceID = mStrText.arg(vData.mDeviceID ); - _values[eDeviceID ] = mStrText.arg(vData.mDeviceID ); - + _values[ePatientID ] = vPatientID.trimmed() ; _values[eBloodFlowRate ] = mStrText.arg(vData.mBloodFlowRate ); _values[eDialysateFlowRate ] = mStrText.arg(vData.mDialysateFlowRate ); _values[eTreatmentDuration ] = mTreatmentDuration ; @@ -102,25 +127,24 @@ _values[eDialysateTemperature ] = mStrText.arg(vData.mDialysateTemperature ,FLOAT3 ); _values[eDialyzerType ] = mDialyzerType ; _values[eHeparinType ] = mHeparinType ; - _values[eHeparinConcentration ] = mStrText.arg(mHeparinConcentration ); // Decided to be removed, but has to be here until it is actually removed from the message + _values[eHeparinConcentration ] = mStrText.arg(vData.mHeparinConcentration ); _values[eHeparinBolusVolume ] = mStrText.arg(vData.mHeparinBolusVolume ,FLOAT3 ); _values[eHeparinDispenseRate ] = mStrText.arg(vData.mHeparinDispenseRate ,FLOAT3 ); _values[eHeparinStop ] = mStrText.arg(vData.mHeparinStop ); _values[eHeparinDeliveredVolume ] = mStrText.arg(vData.mHeparinDeliveredVolume ,FLOAT3 ); - _values[eTreatmentDateTime ] = mTreatmentDateTime ; + _values[eTreatmentStartDateTime ] = mTreatmentStartDateTime ; + _values[eTreatmentEndDateTime ] = mTreatmentEndDateTime ; _values[eWaterSampleTestResult ] = mWaterSampleTestResult ; _values[eDialysateVolumeUsed ] = mStrText.arg(vData.mDialysateVolumeUsed ,FLOAT3 ); + _values[eOriginUFVolume ] = mStrText.arg(vData.mOriginUFVolume ,FLOAT3 ); _values[eTargetUFVolume ] = mStrText.arg(vData.mTargetUFVolume ,FLOAT3 ); _values[eActualUFVolume ] = mStrText.arg(vData.mActualUFVolume ,FLOAT3 ); + _values[eOriginUFRate ] = mStrText.arg(vData.mOriginUFRate ,FLOAT3 ); _values[eTargetUFRate ] = mStrText.arg(vData.mTargetUFRate ,FLOAT3 ); _values[eActualUFRate ] = mStrText.arg(vData.mActualUFRate ,FLOAT3 ); _values[eSalineBolusVolume ] = mStrText.arg(vData.mSalineBolusVolume ); - _values[eAverageBloodFlow ] = mStrText.arg(vData.mAverageBloodFlow ,FLOAT3 ); - _values[eAverageDialysateFlow ] = mStrText.arg(vData.mAverageDialysateFlow ,FLOAT3 ); - _values[eAverageDialysateTemp ] = mStrText.arg(vData.mAverageDialysateTemp ,FLOAT3 ); - _values[eAverageArterialPressure ] = mStrText.arg(vData.mAverageArterialPressure ,FLOAT3 ); - _values[eAverageVenousPressure ] = mStrText.arg(vData.mAverageVenousPressure ,FLOAT3 ); - _values[eEndTreatmentEarlyAlarm ] = mStrText.arg(vData.mEndTreatmentEarlyAlarm ); + + emit didTxCodeReceive("..."); /* put ... just to notify user about the wait for data */ } /*! @@ -147,7 +171,7 @@ * \details sets the treatment log path if the given type is treatment log, with the path, vLogPath. * If the given vLogPath is empty, the defaults in Storage will be used. * \sa Storage::SDCard_Base_Path_Name - * \sa Storage::Treatment_Log_Folder + * \sa Storage::Log_Folder_Treatment * \param vLogType - The Log type of type Logger::LogType * \param vLogPath - the path to be set and used. */ @@ -156,13 +180,18 @@ if (vLogType == Logger::eLogTrtmt) { if ( vLogPath.trimmed().isEmpty() ) { _treatmentLogPath = QString("%1%2") - .arg(Storage::SDCard_Base_Path_Name) - .arg(Storage::Treatment_Log_Folder ); + .arg(Storage::Settings_Path_Name) + .arg(Storage::Log_Folder_Treatment ); } else { _treatmentLogPath = vLogPath; } - LOG_DEBUG(QString("Treatment log folder has been set to %1.").arg(_treatmentLogPath)); + _treatmentLogPath_Pending = QString("%1%2") + .arg(Storage::Settings_Path_Name) + .arg(Storage::Log_Folder_Pending ); + + LOG_DEBUG(QString("Treatment log folder has been set to %1" ).arg(_treatmentLogPath )); + LOG_DEBUG(QString("Treatment log pending folder has been set to %1" ).arg(_treatmentLogPath_Pending )); } } @@ -196,6 +225,7 @@ */ bool TreatmentLog::saveLog() { + _lastTxInfo.clear(); bool ok = (unsigned)_values.count() >= eTreatmentLogIndexCount; if (!ok) return false; @@ -206,16 +236,13 @@ QString end = "%1" ; uint index = 0 ; - ADDTITLE("Title"); - ADDALINE(csv.arg(tr("Patient Name")) + NONE); - ADDALINE(csv.arg(tr("Patient ID" )) + NONE); - ADDTOLOG( eTreatmentDateTime ); - ADDTOLOG( eDeviceID ); + // ADDTITLE("Title"); // will be added with Tx Code when gets out of pending, by receiving the Tx Code from CloudSync + ADDTOLOG( ePatientID ); - ADDTITLE("Treatment Prescription" ); + ADDTITLE("Treatment Parameters" ); + ADDTOLOG( eTreatmentDuration ); ADDTOLOG( eBloodFlowRate ); ADDTOLOG( eDialysateFlowRate ); - ADDTOLOG( eActualTreatmentDuration ); ADDTOLOG( eAcidConcentrateType ); ADDTOLOG( eBicarbonateConcentrateType ); ADDTOLOG( ePotassiumConcentration ); @@ -230,34 +257,30 @@ ADDTOLOG( eHeparinDispenseRate ); ADDTOLOG( eHeparinStop ); - ADDTITLE("Treatment Parameters" ); - ADDALINE(csv.arg(tr("Start Time")) + NONE); - ADDALINE(csv.arg(tr("End Time" )) + NONE); - ADDTOLOG( eTreatmentDuration ); + ADDTITLE("Treatment Time" ); + ADDTOLOG( eTreatmentStartDateTime ); + ADDTOLOG( eTreatmentEndDateTime ); + ADDTOLOG( eActualTreatmentDuration ); - ADDTITLE("Post-Treatment Data" ); + ADDTITLE("Device Treatment Data" ); ADDTOLOG( eDialysateVolumeUsed ); + ADDTOLOG( eOriginUFVolume ); ADDTOLOG( eTargetUFVolume ); ADDTOLOG( eActualUFVolume ); + ADDTOLOG( eOriginUFRate ); + ADDTOLOG( eTargetUFRate ); + ADDTOLOG( eActualUFRate ); ADDTOLOG( eSalineBolusVolume ); ADDTOLOG( eHeparinDeliveredVolume ); ADDTITLE("Extra" ); ADDTOLOG( eWaterSampleTestResult ); - ADDTOLOG( eTargetUFRate ); - ADDTOLOG( eActualUFRate ); - ADDTOLOG( eAverageBloodFlow ); - ADDTOLOG( eAverageDialysateFlow ); - ADDTOLOG( eAverageDialysateTemp ); - ADDTOLOG( eAverageArterialPressure ); - ADDTOLOG( eAverageVenousPressure ); - ADDTOLOG( eEndTreatmentEarlyAlarm ); ADDTITLE("Treatment Data" ); for ( const TreatmentLogAvrgeData &item : _treatmentLogAvrgeData ) { QString line; line += csv.arg(item.mTimeStamp ); - line += csv.arg(NONE ); +// line += csv.arg(NONE ); // removed during our meeting with Sean and Jahnavi 04/11/2022@16:00. line += csv.arg(item.mBloodFlowRate ); line += csv.arg(item.mDialysateFlowRate ); line += csv.arg(item.mUFRate ,FLOAT3 ); @@ -272,10 +295,11 @@ ADDTITLE("Treatment Alarms" ); for ( const TreatmentLogAlarmData &item : _treatmentLogAlarmData ) { QString line; + // TODO : QString alarmText = Model::MAlarmStatus::toText(static_cast(item.mAlarmID)); line += csv.arg(item.mTimeStamp ); - line += csv.arg(item.mAlarmID ); - line += csv.arg(item.mParam1 ); - line += end.arg(item.mParam2 ); + line += csv.arg(item.mAlarmID ); // may need to change, during our discussion with Sean and I 04/11/2022. to alarmText + line += csv.arg(item.mParam1 ); // may need to remove, during our discussion with Sean and I 04/11/2022. + line += end.arg(item.mParam2 ); // may need to remove, during our discussion with Sean and I 04/11/2022. ADDALINE(line); } @@ -290,23 +314,29 @@ } ADDALINE(""); - QString mDateTime = _values[eTreatmentDateTime]; - mDateTime.replace("/", "" ); // remove date separator - mDateTime.replace(":", "" ); // remove time separator - mDateTime.replace(" ", "_"); // replace spaces + _lastTxInfo.mDateTime = _values[eTreatmentStartDateTime]; + _lastTxInfo.mDateTime.replace("/", "" ); // remove date separator + _lastTxInfo.mDateTime.replace(":", "" ); // remove time separator + _lastTxInfo.mDateTime.replace(" ", "_"); // replace spaces - QString mDeviceID = _values[eDeviceID]; - QString mFileName = QString("%1_%2.log") - .arg(mDateTime) - .arg(mDeviceID); + _lastTxInfo.mDeviceID = _deviceID; + _lastTxInfo.mPatientID = _values[ePatientID]; + _lastTxInfo.mFileName = QString("%1%2_%3.log") + .arg(_treatmentLogPath_Pending) + .arg(_lastTxInfo.mDateTime) + .arg(_lastTxInfo.mDeviceID); - ok = Storage::FileHandler::makeFolder(_treatmentLogPath); - if ( ! ok ) { LOG_DEBUG(QString("Cannot create folder %1").arg(_treatmentLogPath)); return ok; } - ok = Storage::FileHandler::write(mFileName.prepend(_treatmentLogPath), logContent, false); - if ( ! ok ) { LOG_DEBUG(QString("Cannot write to file %1").arg(mFileName )); return ok; } + ok = Storage::FileHandler::makeFolder(_treatmentLogPath_Pending); + if ( ! ok ) { LOG_DEBUG(QString("Cannot create folder %1").arg(_treatmentLogPath_Pending)); return ok; } + ok = Storage::FileHandler::write(_lastTxInfo.mFileName, logContent, false); + if ( ! ok ) { LOG_DEBUG(QString("Cannot write to file %1").arg(_lastTxInfo.mFileName)); return ok; } _treatmentLogAvrgeData.clear(); _treatmentLogAlarmData.clear(); _treatmentLogEventData.clear(); + _lastTxInfo.mStatus = ok; + + sendPending(); // reset the timer to find the latest saved pending and ask for Tx Code. + return ok; } @@ -344,16 +374,36 @@ } /*! - * \brief TreatmentLog::exportLog - * The actual treatment log export function which does the export of the treatment log into the USB drive. - * \return + * \brief TreatmentLog::exportLog + * \details The actual treatment log export function which does the export of the treatment log into the USB drive. + * \return true on successful export. */ bool TreatmentLog::exportLog() { - bool ok; - QString exportPath = Storage::USB_Mount_Point; - Storage::FileHandler::makeFolder(exportPath); - ok = FileHandler::copyFolder(_treatmentLogPath , exportPath); + bool ok = true; + QString status = ""; + QString dstPath = Storage::USB_Mount_Point ; + dstPath += Storage::Log_Folder_Treatment ; + QString srcFile = _lastTxInfo.mFileName ; + QString srcFileName = QFileInfo(srcFile).fileName() ; + QString dstFile = dstPath + srcFileName; + // HERE: expose to the UI dialog as the rejection/notification result + if ( ! Storage::FileHandler::makeFolder ( dstPath ) ) { status = QString( "Couldn't create folder on USB drive to export TxLog" ); ok = false; goto lOut; } + if ( ! QFileInfo::exists ( srcFile ) ) { status = QString( "Treatment Log '%1' doesn't exist" ).arg( srcFile ); ok = false; goto lOut; } + if ( QFileInfo::exists ( dstFile ) ) { status = QString( "Treatment Log '%1' already exists" ).arg( dstFile ); ok = false; goto lOut; } + if ( ! QFile::copy (srcFile, dstFile ) ) { status = QString( "Unable to Export TxLog '%1' to '%2'" ).arg( srcFile ).arg( dstFile ); ok = false; goto lOut; } + +lOut: + if ( ! ok ) { + LOG_DEBUG(status); + status = "Unable to export treatment log '" + srcFileName +"'"; + } + else { + status = "Treatment log '" + srcFileName + "' exported successfully"; + LOG_APPED_UI(status); + } + + emit didNotification(status); return ok; } @@ -366,3 +416,120 @@ LOG_DEBUG(QString("Export Treatment Log Ended: %1").arg(_exportWatcher.result())); isIdle(true); } + +/*! + * \brief TreatmentLog::onCloudSyncStatus + * \details This is the handler to the slot CloudSyncController::didCloudSyncStatus, + * which will be emitted when the Cloud is running and the device is registered, with vReady as true. + * Therefore pending Treatment logs can be sent. + * or with ready as false if CloudSync stops or for any error can't communicate to send the logs. + */ +void TreatmentLog::onCloudSyncStatus(bool vReady) +{ + static int id; + if ( vReady ) { + id = startTimer(_interval); + } + else { + killTimer(id); + } +} + +/*! + * \brief TreatmentLog::onTxCodeReceive + * \details this slot will update the treatment code sent by CS. + * \param vTxCode - The treatment code sent by CS. + * \note there should not be a getter for this, for thread safety, and who needs the data needs to connect to the signal. + */ +void TreatmentLog::onTxCodeReceive(const QString &vTxCode) +{ + _txCode = vTxCode; + // This has to be checked before the addTxCode, + // because that function will change the _lastTxInfo.mFilename after it is moved from pending. + bool isLastTxInfo = ! _lastTxInfo.mFileName.isEmpty() && _pendingTx == _lastTxInfo.mFileName; + + // getting the Tx out of pending and add the received Tx in the file + addTxCode(); + + if ( isLastTxInfo ) { + // avoid updating the screen with another pending Tx in queue + emit didTxCodeReceive(_txCode); + } +} + +/*! + * \brief TreatmentLog::addTxCode + * \details Adds the [Title] and Tx Code to the file and moves it from pending. + * \return true on success, false on any case of read, write, remove. + */ +bool TreatmentLog::addTxCode() +{ + bool ok = true; + QString src = _pendingTx; + QString dst = _treatmentLogPath + QFileInfo(src).fileName(); + QString logContent; + ADDTITLE("Title"); + ADDALINE(QString("Tx Code,%1,").arg(_txCode)); + + ok = FileHandler::read (src, logContent, true ); // reads the file and appends the content to logContent + if ( ! ok ) { + LOG_DEBUG(QString("Couldn't read pending treatment log file '%1'").arg(src)); + return ok; + } + + ok = FileHandler::write (dst, logContent ); + if ( ! ok ) { + LOG_DEBUG(QString("Couldn't settle pending treatment log file '%1'").arg(src)); + return ok; + } + + QFile::remove(src); + if ( ! ok ) { + LOG_DEBUG(QString("Couldn't remove pending treatment log file '%1'").arg(src)); + return ok; + } + _lastTxInfo.mFileName = dst; // Update the last Tx file to the new location [ export ] + sendPending(); // start looking for the next pending, instead of waiting to timeout + return ok; +} + +/*! + * \brief TreatmentLog::sendPending + * \details Resets the pending counter to immediately/ASAP as for the pending and won't waits for the timeout. + */ +void TreatmentLog::sendPending() +{ + _pendingCounter = 0; +} + +/*! + * \brief TreatmentLog::testPendingTxReports + * \details this function count downs for the _pendingInterval + * when the _pendingCounter reaches 0 will search for the files + * in the _TreatmentLog.logPathPending() + * and if there is any will get the recent file in the list + * and asks for the TxCode by emitting the didTxPending signal + */ +void TreatmentLog::testPendingTxReports() +{ + if ( _pendingCounter ) { + _pendingCounter -- ; + return; + } + else { + _pendingCounter = _pendingInterval; // every minute + } + + QFileInfoList pendingFiles; + pendingFiles = Storage::FileHandler::find(_TreatmentLog.logPathPending(), {"*.log"}); + // look into the list. + // if there are pending files, + // send a request only for the top on the list + // * When gets the Tx Code, moves from pending then next one comes to top + // the process repeats until there is no file in pending + if ( pendingFiles.count() ) { + // the most recent/first Tx file, to first ask for the current treatment which has just saved as pending on screen + _pendingTx = pendingFiles.first().absoluteFilePath(); + emit didTxPending( _pendingTx ); + } +}