/************************************************************************** * * Copyright (c) 2019-2020 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 Reservoirs.c * * @author (last) Quang Nguyen * @date (last) 26-Aug-2020 * * @author (original) Sean * @date (original) 18-Mar-2020 * ***************************************************************************/ #include // for memcpy() #include "Heaters.h" #include "LoadCell.h" #include "ModeRecirculate.h" #include "OperationModes.h" #include "Reservoirs.h" #include "SystemCommMessages.h" #include "TaskGeneral.h" #include "Timers.h" #include "Valves.h" /** * @addtogroup Reservoirs * @{ */ // ********** private definitions ********** #define MIN_RESERVOIR_VOLUME_ML 0 ///< Minimum reservoir volume in mL. #define MAX_RESERVOIR_VOLUME_ML 2000 ///< Maximum reservoir volume in mL. #define DEFAULT_FILL_VOLUME_ML 1700 ///< Default fill volume for treatment in mL. #define DISINFECT_FILL_VOLUME_ML 2400 ///> Fill volume for disinfection in mL. #define MAX_FILL_VOLUME_ML MAX_RESERVOIR_VOLUME_ML ///> Maximum fill volume in mL. #define DEFAULT_DRAIN_VOLUME_ML 100 ///> Default drain volume in mL. #define MAX_DRAIN_VOLUME_ML MAX_RESERVOIR_VOLUME_ML ///> Maximum drain volume in mL. #define MIN_DRAIN_VOLUME_ML 100 ///> Minimum drain volume in mL. #define MAX_RESERVOIR_WEIGHT 10000 ///> Maximum reservoir weight in grams. #define RESERVOIR_DATA_PUB_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ///< interval (ms/task time) at which the reservoir data is published on the CAN bus. // ********** private data ********** static U32 reservoirDataPublicationTimerCounter = 0; ///< used to schedule reservoir data publication to CAN bus. static OVERRIDE_U32_T activeReservoir = { 0, 0, 0, 0 }; ///< The active reservoir that the DG is filling/draining/etc. static OVERRIDE_U32_T fillVolumeTargetMl = { 0, 0, 0, 0 }; ///< The target reservoir fill volume (in mL). static OVERRIDE_U32_T drainVolumeTargetMl = { 0, 0, 0, 0 }; ///< The target reservoir drain volume (in mL). static LOAD_CELL_ID_T associatedLoadCell[ NUM_OF_RESERVOIRS ] = { LOAD_CELL_A1, LOAD_CELL_B1 }; ///< The reservoirs' associate load cell. static LOAD_CELL_ID_T redundantLoadCell[ NUM_OF_RESERVOIRS ] = { LOAD_CELL_A2, LOAD_CELL_B2 }; ///< The reservoirs' associate redundant load cell. static F32 reservoirLowestWeight[ NUM_OF_RESERVOIRS ] = { MAX_RESERVOIR_WEIGHT, MAX_RESERVOIR_WEIGHT }; ///< The reservoirs' lowest weight during draining. static U32 reservoirWeightUnchangeStartTime[ NUM_OF_RESERVOIRS ] = { 0, 0 }; ///< The reservoirs' weight start time when weight stop decreasing. static BOOL tareLoadCellRequest; ///< Flag indicates if load cell tare has been requested by HD. // ********** private function prototypes ********** static RESERVOIR_ID_T getActiveReservoir( void ); static U32 getReservoirFillVolumeTargetMl( void ); static U32 getReservoirDrainVolumeTargetMl( void ); /*********************************************************************//** * @brief * The initReservoirs function initializes the Reservoirs module. * @details Inputs: none * @details Outputs: Reservoirs module initialized * @return none *************************************************************************/ void initReservoirs( void ) { activeReservoir.data = RESERVOIR_2; setValveState( VRF, VALVE_STATE_R1_C_TO_NC ); setValveState( VRD, VALVE_STATE_R1_C_TO_NC ); setValveState( VRO, VALVE_STATE_R2_C_TO_NC ); setValveState( VRI, VALVE_STATE_R2_C_TO_NC ); fillVolumeTargetMl.data = DEFAULT_FILL_VOLUME_ML; drainVolumeTargetMl.data = DEFAULT_DRAIN_VOLUME_ML; } /*********************************************************************//** * @brief * The execReservoirs function manages periodic tasks for the Reservoirs module. * @details Inputs: none * @details Outputs: Reservoir data broadcast on interval * @return none *************************************************************************/ void execReservoirs( void ) { // publish active reservoir, fill/drain volume targets at 1 Hz. if ( ++reservoirDataPublicationTimerCounter >= RESERVOIR_DATA_PUB_INTERVAL ) { U32 actRes = getActiveReservoir(); U32 filVol = getReservoirFillVolumeTargetMl(); U32 drnVol = getReservoirDrainVolumeTargetMl(); broadcastReservoirData( actRes, filVol, drnVol ); reservoirDataPublicationTimerCounter = 0; } } /*********************************************************************//** * @brief * The setActiveReservoir function sets the given reservoir as active * (meaning HD will be drawing from this reservoir). * @details Inputs: none * @details Outputs: Specified reservoir is set as active. * @param resID ID of reservoir to set as active * @return TRUE if set active reservoir command successful, FALSE if not. *************************************************************************/ BOOL setActiveReservoirCmd( RESERVOIR_ID_T resID ) { BOOL result = FALSE; // switch reservoir command only valid in re-circulate mode if ( DG_MODE_CIRC == getCurrentOperationMode() ) { switch ( resID ) { case RESERVOIR_1: activeReservoir.data = (U32)resID; result = TRUE; setValveState( VRF, VALVE_STATE_R2_C_TO_NO ); setValveState( VRD, VALVE_STATE_R2_C_TO_NO ); setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); setValveState( VRI, VALVE_STATE_R1_C_TO_NO ); break; case RESERVOIR_2: activeReservoir.data = (U32)resID; result = TRUE; setValveState( VRF, VALVE_STATE_R1_C_TO_NC ); // TODO - valve states are reversed for the two reservoirs for now - revert back when load cells are fixed. setValveState( VRD, VALVE_STATE_R1_C_TO_NC ); setValveState( VRO, VALVE_STATE_R2_C_TO_NC ); setValveState( VRI, VALVE_STATE_R2_C_TO_NC ); break; default: // invalid reservoir given - cmd will be NAK'd w/ false result. break; } } return result; } /*********************************************************************//** * @brief * The startFill function handles a fill command from the HD. * @details Inputs: none * @details Outputs: move to fill mode * @param fillToVolMl Target volume (in mL) to fill reservoir to * @return TRUE if fill command successful, FALSE if not. *************************************************************************/ BOOL startFillCmd( U32 fillToVolMl ) { BOOL result = FALSE; // fill command only valid in re-circulate mode if ( ( DG_MODE_CIRC == getCurrentOperationMode() ) && ( DG_RECIRCULATE_MODE_STATE_RECIRC_WATER == getCurrentRecirculateState() ) ) { // validate parameters if ( fillToVolMl < MAX_FILL_VOLUME_ML ) { fillVolumeTargetMl.data = fillToVolMl; requestNewOperationMode( DG_MODE_FILL ); result = TRUE; } } return result; } /*********************************************************************//** * @brief * The stopFill function handles a stop fill command from the HD. * @details Inputs: none * @details Outputs: move to standby mode * @return TRUE if stop fill command successful, FALSE if not. *************************************************************************/ BOOL stopFillCmd( void ) { BOOL result = FALSE; // stop fill command only valid in fill mode if ( DG_MODE_FILL == getCurrentOperationMode() ) { fillVolumeTargetMl.data = 0; requestNewOperationMode( DG_MODE_CIRC ); result = TRUE; } return result; } /*********************************************************************//** * @brief * The startDrain function handles a drain command from the HD. * @details Inputs: none * @details Outputs: Start draining in re-circulate mode * @param drainToVolMl Target volume (in mL) to drain reservoir to * @return TRUE if drain command successful, FALSE if not. *************************************************************************/ BOOL startDrainCmd( DRAIN_CMD_T drainCmd ) { BOOL result = FALSE; // drain command only valid in re-circulate mode if ( DG_MODE_CIRC == getCurrentOperationMode() ) { // validate parameters if ( ( drainCmd.targetVolume >= MIN_DRAIN_VOLUME_ML ) && ( drainCmd.targetVolume <= MAX_DRAIN_VOLUME_ML ) ) { drainVolumeTargetMl.data = drainCmd.targetVolume; tareLoadCellRequest = drainCmd.tareLoadCell; requestNewOperationMode( DG_MODE_DRAI ); result = TRUE; } } return result; } /*********************************************************************//** * @brief * The stopDrain function handles a stop drain command from the HD. * @details Inputs: none * @details Outputs: move to standby mode * @return TRUE if stop drain command successful, FALSE if not. *************************************************************************/ BOOL stopDrainCmd( void ) { BOOL result = FALSE; // stop drain command only valid in drain mode if ( DG_MODE_DRAI == getCurrentOperationMode() ) { drainVolumeTargetMl.data = 0; requestNewOperationMode( DG_MODE_CIRC ); result = TRUE; } return result; } /*********************************************************************//** * @brief * The startTrimmerHeater function handles a start trimmer heater command * from the HD. * @details Inputs: none * @details Outputs: start trimmer heater * @return TRUE if stop drain command successful, FALSE if not. *************************************************************************/ BOOL startTrimmerHeaterCmd( void ) { BOOL result = FALSE; result = startTrimmerHeater(); return result; } /*********************************************************************//** * @brief * The stopTrimmerHeater function handles a stop trimmer heater command * from the HD. * @details Inputs: none * @details Outputs: stop trimmer heater * @return TRUE if stop drain command successful, FALSE if not. *************************************************************************/ BOOL stopTrimmerHeaterCmd( void ) { BOOL result = TRUE; stopTrimmerHeater(); return result; } /*********************************************************************//** * @brief * The getInactiveReservoir function gets the inactive reservoir. * @details Inputs: activeReservoir * @details Outputs: none * @return the currently inactive reservoir. *************************************************************************/ RESERVOIR_ID_T getInactiveReservoir( void ) { RESERVOIR_ID_T inactiveReservoir = RESERVOIR_1; if ( RESERVOIR_1 == getActiveReservoir() ) { inactiveReservoir = RESERVOIR_2; } return inactiveReservoir; } /*********************************************************************//** * @brief * The hasTargetFillVolumeReached function checks if the target fill volume * for specific reservoir has been reached. * @details Inputs: fillVolumeTargetMl * @details Outputs: none * @param reservoirId reservoir id * @return TRUE if target fill volume has been reached, FALSE if not. *************************************************************************/ BOOL hasTargetFillVolumeBeenReached( RESERVOIR_ID_T reservoirId ) { F32 const loadcellWeight = getLoadCellFilteredWeight( associatedLoadCell[ reservoirId ] ); U32 const targetFillVolume = getReservoirFillVolumeTargetMl(); BOOL const hasTargetReached = ( loadcellWeight >= targetFillVolume ); return hasTargetReached; } /*********************************************************************//** * @brief * The hasTargetDrainVolumeReached function checks if the target drain volume * for specific reservoir has been reached or exceed time limit. * @details Inputs: drainVolumeTargetMl * @details Outputs: none * @param reservoirId reservoir id * @param timeout timeout period when weight remains the same * @return TRUE if target drain volume has been reached or exceeds time limit, FALSE if not. *************************************************************************/ BOOL hasTargetDrainVolumeBeenReached( RESERVOIR_ID_T reservoirId, U32 timeout ) { BOOL result = FALSE; F32 const loadcellWeight = getLoadCellFilteredWeight( associatedLoadCell[ reservoirId ] ); U32 const targetDrainVolume = getReservoirDrainVolumeTargetMl(); if ( loadcellWeight < reservoirLowestWeight[ reservoirId ] ) { reservoirLowestWeight[ reservoirId ] = loadcellWeight; reservoirWeightUnchangeStartTime[ reservoirId ] = getMSTimerCount(); } BOOL const hasTimeOut = didTimeout( reservoirWeightUnchangeStartTime[ reservoirId ], timeout ); BOOL const hasTargetReached = ( targetDrainVolume >= loadcellWeight ); if ( hasTimeOut || hasTargetReached ) { result = TRUE; reservoirLowestWeight[ reservoirId ] = MAX_RESERVOIR_WEIGHT; if ( tareLoadCellRequest ) { tareLoadCellRequest = FALSE; tareLoadCell( associatedLoadCell[ reservoirId ] ); tareLoadCell( redundantLoadCell[ reservoirId ] ); } } return result; } /*********************************************************************//** * @brief * The resetReservoirLoadCellsOffset function sets the reservoir's load cells * offset to zero. * @details Inputs: associateLoadCell[], redundantLoadCell[] * @details Outputs: reset reservoir's associate load cells auto calibration offset * @param reservoirId reservoir id * @return none *************************************************************************/ void resetReservoirLoadCellsOffset( RESERVOIR_ID_T reservoirId ) { resetLoadCellOffset( associatedLoadCell[ reservoirId ] ); resetLoadCellOffset( redundantLoadCell[ reservoirId ] ); } /*********************************************************************//** * @brief * The getActiveReservoir function gets the active reservoir. * @details Inputs: activeReservoir * @details Outputs: none * @return the currently active reservoir. *************************************************************************/ static RESERVOIR_ID_T getActiveReservoir( void ) { RESERVOIR_ID_T result = (RESERVOIR_ID_T)activeReservoir.data; if ( OVERRIDE_KEY == activeReservoir.override ) { result = (RESERVOIR_ID_T)activeReservoir.ovData; } return result; } /*********************************************************************//** * @brief * The getReservoirFillVolumeTargetMl function gets the reservoir fill volume (in mL). * @details Inputs: fillVolumeTargetMl * @details Outputs: none * @return the current target reservoir fill volume (in mL). *************************************************************************/ static U32 getReservoirFillVolumeTargetMl( void ) { U32 result = fillVolumeTargetMl.data; if ( OVERRIDE_KEY == fillVolumeTargetMl.override ) { result = fillVolumeTargetMl.ovData; } return result; } /*********************************************************************//** * @brief * The getReservoirDrainVolumeTargetMl function gets the reservoir drain volume (in mL). * @details Inputs: drainVolumeTargetMl * @details Outputs: none * @return the current target reservoir drain volume (in mL). *************************************************************************/ static U32 getReservoirDrainVolumeTargetMl( void ) { U32 result = drainVolumeTargetMl.data; if ( OVERRIDE_KEY == drainVolumeTargetMl.override ) { result = drainVolumeTargetMl.ovData; } return result; } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testSetDGActiveReservoirOverride function overrides the active reservoir. * @details Inputs: activeReservoir * @details Outputs: activeReservoir * @param value override active reservoir ID * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetDGActiveReservoirOverride( RESERVOIR_ID_T value ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; activeReservoir.ovData = value; activeReservoir.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The activeReservoir function resets the override of the active reservoir. * @details Inputs: activeReservoir * @details Outputs: activeReservoir * @return TRUE if override reset successful, FALSE if not *************************************************************************/ BOOL testResetDGActiveReservoirOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; activeReservoir.override = OVERRIDE_RESET; activeReservoir.ovData = activeReservoir.ovInitData; } return result; } /*********************************************************************//** * @brief * The testSetReservoirFillVolumeMlOverride function overrides the target * reservoir fill volume (in mL). * @details Inputs: fillVolumeTargetMl * @details Outputs: fillVolumeTargetMl * @param value override target reservoir fill volume (in mL) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetReservoirFillVolumeMlOverride( U32 value ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; fillVolumeTargetMl.ovData = value; fillVolumeTargetMl.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The testResetReservoirFillVolumeMlOverride function resets the override of * the target reservoir fill volume. * @details Inputs: fillVolumeTargetMl * @details Outputs: fillVolumeTargetMl * @return TRUE if override reset successful, FALSE if not *************************************************************************/ BOOL testResetReservoirFillVolumeMlOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; fillVolumeTargetMl.override = OVERRIDE_RESET; fillVolumeTargetMl.ovData = fillVolumeTargetMl.ovInitData; } return result; } /*********************************************************************//** * @brief * The testSetReservoirDrainVolumeMlOverride function overrides the target * reservoir drain volume (in mL). * @details Inputs: drainVolumeTargetMl * @details Outputs: drainVolumeTargetMl * @param value override target reservoir drain volume (in mL) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetReservoirDrainVolumeMlOverride( U32 value ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; drainVolumeTargetMl.ovData = value; drainVolumeTargetMl.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The testResetReservoirDrainVolumeMlOverride function resets the override of * the target reservoir drain volume. * @details Inputs: drainVolumeTargetMl * @details Outputs: drainVolumeTargetMl * @return TRUE if override reset successful, FALSE if not *************************************************************************/ BOOL testResetReservoirDrainVolumeMlOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; drainVolumeTargetMl.override = OVERRIDE_RESET; drainVolumeTargetMl.ovData = drainVolumeTargetMl.ovInitData; } return result; } /**@}*/