Index: firmware/App/Modes/ModeStandby.c =================================================================== diff -u -r46b163d19c65e8c21db7b0247bbb1af0dba1ece5 -r99ec83eff4683ad69a249f935d74cb9226984d21 --- firmware/App/Modes/ModeStandby.c (.../ModeStandby.c) (revision 46b163d19c65e8c21db7b0247bbb1af0dba1ece5) +++ firmware/App/Modes/ModeStandby.c (.../ModeStandby.c) (revision 99ec83eff4683ad69a249f935d74cb9226984d21) @@ -1,14 +1,14 @@ /************************************************************************** * -* Copyright (c) 2019-2022 Diality Inc. - All Rights Reserved. +* Copyright (c) 2019-2023 Diality Inc. - All Rights Reserved. * * 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 ModeStandby.c * -* @author (last) Dara Navaei -* @date (last) 22-Sep-2022 +* @author (last) Sean Nash +* @date (last) 13-Mar-2023 * * @author (original) Dara Navaei * @date (original) 05-Nov-2019 @@ -20,6 +20,7 @@ #include "Battery.h" #include "BloodFlow.h" #include "Buttons.h" +#include "Compatible.h" #include "DGInterface.h" #include "DialInFlow.h" #include "DialOutFlow.h" @@ -30,6 +31,7 @@ #include "ModeTreatment.h" #include "ModeTreatmentParams.h" #include "OperationModes.h" +#include "RTC.h" #include "Switches.h" #include "SyringePump.h" #include "SystemComm.h" @@ -46,7 +48,9 @@ // ********** private definitions ********** #define DISINFECTS_DATA_PUB_INTERVAL ( 1 * MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ///< Disinfects data publish interval in counts. -#define SERVICE_TIME_INTERVAL_MS ( 6 * 30 * SECONDS_IN_A_DAY * MS_PER_SECOND ) ///< HD/DG 6-month service interval in milliseconds. +#define DISINFECTS_TIME_INTERVAL_S ( 2 * SECONDS_IN_A_DAY ) ///< HD/DG 2-day service interval in seconds. +#define FLUSH_TIME_INTERVAL_S ( 30 * SEC_PER_MIN * MS_PER_SECOND ) ///< Flush time interval in seconds. +#define MAX_ALLOWED_RO_FILTER_TEMP_FOR_TX_C 44.0F ///< Maximum allowed temperature to start a treatment in C. // ********** private data ********** @@ -56,14 +60,20 @@ static BOOL flushStartReqReceived; ///< Flag indicates user has requested initiation of flush mode. static BOOL heatDisinfectStartReqReceived; ///< Flag indicates user has requested initiation of heat disinfect mode. static BOOL chemDisinfectStartReqReceived; ///< Flag indicates user has requested initiation of chemical disinfect mode. +static GENERIC_CONFIRM_ID_T disinfectCancelReqID; ///< ID of requested cancel disinfect mode. static DG_DISINFECT_STATE_T dgDisinfectState; ///< DG disinfect state to be boadcast to UI. +static U32 dataPublishCounter; ///< Disinfects data publish counter. +static BOOL homingInitiated; ///< Boolean flag to indicate homing is initiated. -static U32 dataPublishCounter = 0; ///< Disinfects data publish counter. /// Interval (in task intervals) at which to publish standby mode data to CAN bus. static OVERRIDE_U32_T standbyModePublishInterval = { DISINFECTS_DATA_PUB_INTERVAL, DISINFECTS_DATA_PUB_INTERVAL, DISINFECTS_DATA_PUB_INTERVAL, 0 }; +static const U32 SERVICE_TIME_INTERVAL_S = (U32)( 365 * 0.5 * SECONDS_IN_A_DAY ); ///< HD/DG 6-month service interval in seconds. + // ********** private function prototypes ********** +static void handleDisinfectCancel( BOOL stop ); + static HD_STANDBY_STATE_T handleStandbyModeStartState( void ); static HD_STANDBY_STATE_T handleStandbyModeWaitForTreatmentState( void ); static HD_STANDBY_STATE_T handleStandbyModeWaitForDisinfectState( void ); @@ -80,6 +90,8 @@ static HD_STANDBY_STATE_T handleStandbyModeWaitForDGChemDisinfectStartState( void ); static HD_STANDBY_STATE_T handleStandbyModeDGChemDisininfectInProgressState( void ); +static BOOL isDGDisinfectValid( void ); +static BOOL haveHDDGServicesBeenExpired( REQUEST_REJECT_REASON_CODE_T* rejReason ); static void publishDisinfectData( void ); /*********************************************************************//** @@ -88,7 +100,7 @@ * @details Inputs: none * @details Outputs: currentStandbyState, treatStartReqReceived, * flushStartReqReceived, dataPublishCounter, heatDisinfectStartReqReceived, - * chemDisinfectStartReqReceived, dgDisinfectState + * chemDisinfectStartReqReceived, dgDisinfectState, homingInitiated * @return none *************************************************************************/ void initStandbyMode( void ) @@ -99,7 +111,9 @@ dataPublishCounter = 0; heatDisinfectStartReqReceived = FALSE; chemDisinfectStartReqReceived = FALSE; + disinfectCancelReqID = GENERIC_CONFIRM_ID_NONE; dgDisinfectState = DG_DISINFECT_NOT_RUNNING_STATE; + homingInitiated = FALSE; } /*********************************************************************//** @@ -118,10 +132,10 @@ initStandbyMode(); initDGInterface(); resetAirTrap(); + resetBloodPumpRotorCount(); + resetDialInPumpRotorCount(); + resetPreLoadStatus(); - doorClosedRequired( FALSE, FALSE ); - syringeDetectionRequired( FALSE ); - // Set user alarm recovery actions allowed in this mode setAlarmUserActionEnabled( ALARM_USER_ACTION_RESUME, FALSE ); setAlarmUserActionEnabled( ALARM_USER_ACTION_RINSEBACK, FALSE ); @@ -143,9 +157,19 @@ // If we just exited Post Treatment Mode, goto disinfect sub state. if ( MODE_POST == previousOpMode ) { + doorClosedRequired( FALSE, FALSE ); // door no longer required to be closed in standby mode currentStandbyState = STANDBY_WAIT_FOR_DISINFECT_STATE; } + else + { + doorClosedRequired( TRUE, FALSE ); + } + syringeDetectionRequired( FALSE ); + // Request DG service record and usage information from DG + sendDGServiceRequestToDG(); + sendDGUsageInfoRequestToDG(); + return currentStandbyState; } @@ -160,12 +184,13 @@ { BOOL stop = isStopButtonPressed(); -#ifndef RUN_WITHOUT_DG + handleDisinfectCancel( stop ); + // State machine to get DG to prep a reservoir so we can start a treatment switch ( currentStandbyState ) { case STANDBY_START_STATE: - currentStandbyState = handleStandbyModeStartState();; + currentStandbyState = handleStandbyModeStartState(); break; case STANDBY_WAIT_FOR_TREATMENT_STATE: @@ -217,33 +242,7 @@ currentStandbyState = STANDBY_START_STATE; break; } -#else - // State machine to get DG to prep a reservoir so we can start a treatment - switch ( currentStandbyState ) - { - case STANDBY_START_STATE: - currentStandbyState = STANDBY_WAIT_FOR_TREATMENT_STATE; - // Temporary test code - TODO - remove later - homeBloodPump(); - homeDialInPump(); - homeDialOutPump(); - break; - case STANDBY_WAIT_FOR_TREATMENT_STATE: - if ( TRUE == treatStartReqReceived ) - { - requestNewOperationMode( MODE_TPAR ); - treatStartReqReceived = FALSE; - } - break; - - default: - SET_ALARM_WITH_2_U32_DATA( ALARM_ID_HD_SOFTWARE_FAULT, SW_FAULT_ID_MODE_STANDBY_INVALID_STATE, currentStandbyState ); - currentStandbyState = STANDBY_START_STATE; - break; - } -#endif - return currentStandbyState; } @@ -258,34 +257,63 @@ BOOL signalUserInitiateTreatment( void ) { BOOL result = FALSE; + DG_VERSIONS_T dgVersion = getDGVersion(); REQUEST_REJECT_REASON_CODE_T rejReason = REQUEST_REJECT_REASON_NONE; + // Verify HD is in standby mode waiting for treatment start request if ( ( MODE_STAN != getCurrentOperationMode() ) || ( STANDBY_WAIT_FOR_TREATMENT_STATE != currentStandbyState ) ) { rejReason = REQUEST_REJECT_REASON_NOT_ALLOWED_IN_CURRENT_MODE; } - - if ( TRUE != isDGCommunicating() ) + // Verify DG is communicating with HD + else if ( TRUE != isDGCommunicating() ) { rejReason = REQUEST_REJECT_REASON_DG_COMM_LOST; } - - if ( ( DG_MODE_STAN != getDGOpMode() ) || ( DG_STANDBY_MODE_STATE_IDLE != getDGSubMode() ) ) + // Verify DG software is compatible with HD software + else if ( dgVersion.compatibilityRev != SW_COMPATIBILITY_REV ) { + rejReason = REQUEST_REJECT_REASON_DG_INCOMPATIBLE; + } + // Verify DG is not busy + else if ( ( DG_MODE_STAN != getDGOpMode() ) || ( DG_STANDBY_MODE_STATE_IDLE != getDGSubMode() ) ) + { rejReason = REQUEST_REJECT_REASON_DG_NOT_IN_STANDBY_IDLE_STATE; } - - if ( FALSE == isBatteryCharged() ) + // Verify HD battery has sufficient charge to hold up logic/sensors for at least 10 minutes + else if ( FALSE == isBatteryCharged() ) { rejReason = REQUEST_REJECT_REASON_BATTERY_IS_NOT_CHARGED; } +#ifdef _RELEASE_ + else +#else + else if ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_SERVICE_AND_DISINFECT_CHECK ) != SW_CONFIG_ENABLE_VALUE ) +#endif + { + // Verify HD and DG are not over due for service + if ( haveHDDGServicesBeenExpired( &rejReason ) != TRUE ) + { + // Verify DG is disinfected + if ( FALSE == isDGDisinfectValid() ) + { + rejReason = REQUEST_REJECT_REASON_DG_DISINFECT_HAS_BEEN_EXPIRED; + } + else if ( getHeatDisinfectTemperatureSensorValue() > MAX_ALLOWED_RO_FILTER_TEMP_FOR_TX_C ) + { + rejReason = REQUEST_REJECT_REASON_DG_RO_FILTER_TEMPERATURE_OUT_OF_RANGE; + } + } + } + // If no reason to reject request to start treatment, set flag to initiate treatment workflow if ( REQUEST_REJECT_REASON_NONE == rejReason ) { - result = TRUE; + result = TRUE; treatStartReqReceived = TRUE; } + // Respond to request to start treatment sendInitiateTreatmentResponseMsg( result, rejReason ); return result; @@ -470,6 +498,87 @@ /*********************************************************************//** * @brief + * The handleDisinfectCancel function handles + * DG disinfect cancel UI interaction. + * @details Inputs: none + * @details Outputs: none + * @param stop button status + * @return none + *************************************************************************/ +static void handleDisinfectCancel( BOOL stop ) +{ + CONFIRMATION_REQUEST_STATUS_T confirm_status; + GENERIC_CONFIRM_ID_T confirm_id; + + if ( ( STANDBY_DG_FLUSH_IN_PROGRESS_STATE == currentStandbyState ) || + ( STANDBY_DG_HEAT_DISINFECT_IN_PROGRESS_STATE == currentStandbyState ) || + ( STANDBY_DG_CHEM_DISINFECT_IN_PROGRESS_STATE == currentStandbyState ) ) + { + if ( ( TRUE == stop ) && ( GENERIC_CONFIRM_ID_NONE == disinfectCancelReqID ) ) + { + if ( STANDBY_DG_FLUSH_IN_PROGRESS_STATE == currentStandbyState ) + { + confirm_id = GENERIC_CONFIRM_ID_DISINFECT_STOP_WATERFLUSH; + } + else if ( STANDBY_DG_HEAT_DISINFECT_IN_PROGRESS_STATE == currentStandbyState ) + { + confirm_id = GENERIC_CONFIRM_ID_DISINFECT_STOP_HEAT; + } + else if ( STANDBY_DG_CHEM_DISINFECT_IN_PROGRESS_STATE == currentStandbyState ) + { + confirm_id = GENERIC_CONFIRM_ID_DISINFECT_STOP_CHEMICAL; + } + // Send message to UI to indicate user request to cancel disinfect + disinfectCancelReqID = addConfirmationRequest( confirm_id, GENERIC_CONFIRM_CMD_REQUEST_OPEN, 0 ); + } + else if ( GENERIC_CONFIRM_ID_NONE != disinfectCancelReqID ) + { + // Get the confirmation request. It consumes the request if completed and responds to UI. + confirm_status = getConfirmationRequestStatus( disinfectCancelReqID ); + switch ( confirm_status ) + { + case CONFIRMATION_REQUEST_STATUS_ACCEPTED : + // Clear request active status + disinfectCancelReqID = GENERIC_CONFIRM_ID_NONE; + + switch ( currentStandbyState ) + { + case STANDBY_DG_FLUSH_IN_PROGRESS_STATE: + cmdStopDGFlush(); + break; + + case STANDBY_DG_HEAT_DISINFECT_IN_PROGRESS_STATE: + cmdStopDGHeatDisinfect(); + break; + + case STANDBY_DG_CHEM_DISINFECT_IN_PROGRESS_STATE: + cmdStopDGChemicalDisinfect(); + break; + + default: + // UI Confirm already closed. Nothing to do. + break; + } + break; + + case CONFIRMATION_REQUEST_STATUS_TIMEOUT : + case CONFIRMATION_REQUEST_STATUS_REJECTED : + // Clear request active status + disinfectCancelReqID = GENERIC_CONFIRM_ID_NONE; + break; + + case CONFIRMATION_REQUEST_STATUS_PENDING: + case CONFIRMATION_REQUEST_STATUS_UNUSED: + default : + // Nothing to do + break; + } + } + } +} + +/*********************************************************************//** + * @brief * The handleStandbyModeStartState function handles the standby start state. * This state waits for the door to be closed and then initiates homing of * pumps and valves and transitions to the wait for treatment state. @@ -479,46 +588,53 @@ *************************************************************************/ static HD_STANDBY_STATE_T handleStandbyModeStartState( void ) { - static BOOL homingInitiated = FALSE; HD_STANDBY_STATE_T state = STANDBY_START_STATE; // Wait for door to be closed so we can home actuators - if ( ( homingInitiated != TRUE ) && ( STATE_CLOSED == getSwitchStatus( FRONT_DOOR ) ) ) + if ( STATE_CLOSED == getSwitchStatus( FRONT_DOOR ) ) { - VALVE_T valve; + // If we haven't already initiated homing of actuators, initiate now + if ( homingInitiated != TRUE ) + { + VALVE_T valve; - // Home pumps and valves - for ( valve = VDI; valve < NUM_OF_VALVES; ++valve ) + // Home pumps and valves + for ( valve = VDI; valve < NUM_OF_VALVES; ++valve ) + { + homeValve( valve ); + } + homeBloodPump(); + homeDialInPump(); + homeDialOutPump(); + retractSyringePump(); + + homingInitiated = TRUE; + } + else { - homeValve( valve ); + // If homing has been initiated, wait for syringe pump to home and the verify force sensor calibration +#ifndef _RELEASE_ + if ( SW_CONFIG_ENABLE_VALUE == getSoftwareConfigStatus( SW_CONFIG_DISABLE_SYRINGE_PUMP ) ) + { + state = STANDBY_WAIT_FOR_TREATMENT_STATE; // Go to wait for treatment state after above check + } + else +#endif + { + if ( ( TRUE == homingInitiated ) && ( TRUE == isSyringePumpHome() ) && ( TRUE == isSyringePumpStopped() ) ) + { + syringePumpVerifyForceSensorDACCalibration(); + homingInitiated = FALSE; // reset for next time + doorClosedRequired( FALSE, FALSE ); // door no longer required to be closed in standby mode + state = STANDBY_WAIT_FOR_TREATMENT_STATE; // Go to wait for treatment state after above check + } + } } - homeBloodPump(); - homeDialInPump(); - homeDialOutPump(); - retractSyringePump(); - - homingInitiated = TRUE; } - else - { - // Trigger door open alarm to prompt user to close the door - activateAlarmNoData( ALARM_ID_CARTRIDGE_DOOR_OPENED ); - homingInitiated = FALSE; - } - // If homing has been initiated, wait for syringe pump to home and the verify force sensor calibration - if ( ( TRUE == homingInitiated ) && ( TRUE == isSyringePumpHome() ) ) - { - syringePumpVerifyForceSensorDACCalibration(); - homingInitiated = FALSE; // reset for next time - state = STANDBY_WAIT_FOR_TREATMENT_STATE; // Go to wait for treatment state after above check - } - return state; } -// Verify calibration - /*********************************************************************//** * @brief * The handleStandbyModeWaitForTreatmentState function handles wait for @@ -529,7 +645,7 @@ *************************************************************************/ static HD_STANDBY_STATE_T handleStandbyModeWaitForTreatmentState( void ) { - HD_STANDBY_STATE_T state = STANDBY_WAIT_FOR_TREATMENT_STATE; + HD_STANDBY_STATE_T state = STANDBY_WAIT_FOR_TREATMENT_STATE; DG_OP_MODE_T dgOperationMode = getDGOpMode(); // If DG is filling while we are in standby mode, abort the fill @@ -544,7 +660,18 @@ cmdStopDG(); } - // If treatment start is requested by user, initiate treatment workflow (transition to treatment params mode). TODO - check required conditions before allowing treatment start, reject if necessary. + // If DG is communicating and we don't yet have DG version info, request it + if ( TRUE == isDGCommunicating() ) + { + DG_VERSIONS_T dgVersion = getDGVersion(); + + if ( 0 == dgVersion.compatibilityRev ) + { + sendFWVersionRequest(); + } + } + + // If treatment start is requested by user, initiate treatment workflow (transition to treatment params mode). if ( TRUE == treatStartReqReceived ) { // Initialize treatment modes before starting a new treatment @@ -832,6 +959,102 @@ /*********************************************************************//** * @brief + * The isDGDisinfectValid function checks whether the DG disinfects is + * acceptable to start another treatment. + * @details Inputs: none + * @details Outputs: none + * @return TRUE if the disinfect is valid otherwise, FALSE + ***********************************************************************/ +static BOOL isDGDisinfectValid( void ) +{ + DG_SERVICE_AND_USAGE_DATA_T data; + BOOL status = FALSE; + + getHDVersionDGServiceAndUsageData( &data ); + + if ( TRUE == data.isDGUsageInfoAvailable ) + { + HD_USAGE_INFO_RECORD_T usageRecord; + getNVRecord2Driver( GET_USAGE_RECORD, (U08*)&usageRecord, sizeof( HD_USAGE_INFO_RECORD_T ), 0, ALARM_ID_NO_ALARM ); + + U32 lastChemCompleteDate = data.dgUsageInfo.lastChemDisCompleteDateEpoch; + U32 lastChemFlushCompleteDate = data.dgUsageInfo.lastChemDisFlushCompleteDateEpoch; + U32 lastHeatCompleteDate = data.dgUsageInfo.lastHeatDisCompleteDateEpoch; + U32 lastFlushCompleteDate = data.dgUsageInfo.lastBasicFlushCompleteDateEpoch; + U32 lastStartTxTimeDate = usageRecord.txLastStartTimeEpoch; + + // Last Treatment Start < Last Heat Disinfect Complete or Last Treatment Start < Last Chem Disinfect Complete so it means at least a heat disinfect + // or a chemical disinfect has been done since the last treatment + BOOL hasDisBeenDone = ( ( ( lastStartTxTimeDate < lastChemCompleteDate ) || ( lastStartTxTimeDate < lastHeatCompleteDate ) ) ? TRUE : FALSE ); + + // Last Chem Disinfect Complete < Current Time – Chem Disinfect Interval, so the chemical disinfect that has been done has not been expired + BOOL isChemDisValid = ( lastChemCompleteDate < ( getRTCTimestamp() - DISINFECTS_TIME_INTERVAL_S ) ? TRUE : FALSE ); + + // Last Heat Disinfect Complete < Current Time – Heat Disinfect Interval, so the heat disinfect that has been done has not been expired + BOOL isHeatDisValid = ( lastHeatCompleteDate < ( getRTCTimestamp() - DISINFECTS_TIME_INTERVAL_S ) ? TRUE : FALSE ); + + // Last Chem Flush Complete < Last Chem Disinfect Start, so after running a chemical disinfect, a chemical disinfect flush has been done + BOOL isChemFlushComplete = ( lastChemFlushCompleteDate > lastChemCompleteDate ? TRUE : FALSE ); + + // If either of the basic flush, heat disinfect, or chemical disinfect flush have been done within the interval, it means the filters have been flushed + BOOL isBasicFlushValid = ( lastFlushCompleteDate < ( getRTCTimestamp() - FLUSH_TIME_INTERVAL_S ) ? TRUE : TRUE ); + BOOL isHeatDisFlushValid = ( lastHeatCompleteDate < ( getRTCTimestamp() - FLUSH_TIME_INTERVAL_S ) ? TRUE : TRUE ); + BOOL isChemFlushValid = ( lastChemFlushCompleteDate < ( getRTCTimestamp() - FLUSH_TIME_INTERVAL_S ) ? TRUE : TRUE ); + BOOL isFlushValid = ( isBasicFlushValid || isHeatDisFlushValid || isChemFlushValid ); + + // If all of the above conditions are true, it means we can start a treatment + if ( ( TRUE == hasDisBeenDone ) && ( TRUE == isChemDisValid ) && ( TRUE == isHeatDisValid ) && ( TRUE == isChemFlushComplete ) && ( TRUE == isFlushValid ) ) + { + status = TRUE; + } + } + + return status; +} + +/*********************************************************************//** + * @brief + * The haveHDDGServicesBeenExpired function checks whether the last DG/HD + * service time is still within the interval or not. + * @details Inputs: none + * @details Outputs: none + * @param rejReason pointer to the provided reject reason buffer to be send to UI + * @return TRUE if the service for HD or DG has expired otherwise FALSE + ***********************************************************************/ +static BOOL haveHDDGServicesBeenExpired( REQUEST_REJECT_REASON_CODE_T* rejReason ) +{ + BOOL status = FALSE; + DG_SERVICE_AND_USAGE_DATA_T dgData; + HD_SERVICE_RECORD_T hdServiceRecord; + + getHDVersionDGServiceAndUsageData( &dgData ); + getNVRecord2Driver( GET_SRV_RECORD, (U08*)&hdServiceRecord, sizeof( HD_SERVICE_RECORD_T ), 0, ALARM_ID_NO_ALARM ); + + if ( TRUE == dgData.isDGServiceRecordAvailable ) + { + U32 dgSrvcElapsedTimeS = getRTCTimestamp() - dgData.dgServiceRecord.lastServiceEpochDate; + U32 hdSrvcElapsedTimeS = getRTCTimestamp() - hdServiceRecord.lastServiceEpochDate; + BOOL hasDGSrvcBeenExpired = ( dgSrvcElapsedTimeS > SERVICE_TIME_INTERVAL_S ? TRUE : FALSE ); + BOOL hasHDSrvcBeenExpied = ( hdSrvcElapsedTimeS > SERVICE_TIME_INTERVAL_S ? TRUE : FALSE ); + + if ( TRUE == hasDGSrvcBeenExpired ) + { + status = TRUE; + *rejReason = REQUEST_REJECT_REASON_DG_SERVICE_IS_DUE; + } + + if ( TRUE == hasHDSrvcBeenExpied ) + { + status = TRUE; + *rejReason = REQUEST_REJECT_REASON_HD_SERVICE_IS_DUE; + } + } + + return status; +} + +/*********************************************************************//** + * @brief * The publishDisinfectData function publishes disinfects data at * the set interval. * @details Inputs: dataPublishCounter @@ -840,7 +1063,7 @@ *************************************************************************/ static void publishDisinfectData( void ) { - if ( ++dataPublishCounter > DISINFECTS_DATA_PUB_INTERVAL ) + if ( ++dataPublishCounter >= DISINFECTS_DATA_PUB_INTERVAL ) { DG_DISINFECT_UI_STATES_T state = getDGDisinfectsStates(); DISINFECTS_DATA_T data;