/************************************************************************** * * 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 "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 0 ///< Default drain volume in mL. #define MAX_DRAIN_VOLUME_ML MAX_RESERVOIR_VOLUME_ML ///< Maximum 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). /// The reservoirs' associate load cell. static LOAD_CELL_ID_T associatedLoadCell[ NUM_OF_DG_RESERVOIRS ] = { LOAD_CELL_RESERVOIR_1_PRIMARY, LOAD_CELL_RESERVOIR_2_PRIMARY }; /// The reservoirs' associate redundant load cell. static LOAD_CELL_ID_T redundantLoadCell[ NUM_OF_DG_RESERVOIRS ] = { LOAD_CELL_RESERVOIR_1_BACKUP, LOAD_CELL_RESERVOIR_2_BACKUP }; /// The reservoirs' lowest weight during draining. static F32 reservoirLowestWeight[ NUM_OF_DG_RESERVOIRS ] = { MAX_RESERVOIR_WEIGHT, MAX_RESERVOIR_WEIGHT }; static U32 reservoirWeightUnchangeStartTime[ NUM_OF_DG_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 DG_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 = (U32)DG_RESERVOIR_1; 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 setActiveReservoirCmd 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 none *************************************************************************/ void setActiveReservoirCmd( DG_RESERVOIR_ID_T resID ) { DG_CMD_RESPONSE_T cmdResponse; cmdResponse.commandID = DG_CMD_SWITCH_RESERVOIR; cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; // switch reservoir command only valid in re-circulate mode if ( DG_MODE_CIRC == getCurrentOperationMode() ) { switch ( resID ) { case DG_RESERVOIR_1: activeReservoir.data = (U32)resID; cmdResponse.rejected = FALSE; setValveState( VRF, VALVE_STATE_R2_C_TO_NO ); #ifndef V_2_SYSTEM setValveState( VRD1, VALVE_STATE_CLOSED ); #else setValveState( VRD, VALVE_STATE_R2_C_TO_NO ); #endif setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); setValveState( VRI, VALVE_STATE_R1_C_TO_NO ); break; case DG_RESERVOIR_2: activeReservoir.data = (U32)resID; cmdResponse.rejected = FALSE; setValveState( VRF, VALVE_STATE_R1_C_TO_NC ); #ifndef V_2_SYSTEM setValveState( VRD2, VALVE_STATE_CLOSED ); #else setValveState( VRD, VALVE_STATE_R1_C_TO_NC ); #endif 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. cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_PARAMETER; break; } } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } sendCommandResponseMsg( &cmdResponse ); } /*********************************************************************//** * @brief * The changeValveSettingCmd function changes valve settings according to * the given setting ID. * @details Inputs: none * @details Outputs: Specified valve settings has been setup. * @param valveSettingID ID of valve setting to change valves to * @return none *************************************************************************/ void changeValveSettingCmd( DG_VALVE_SETTING_ID_T valveSettingID ) { DG_CMD_RESPONSE_T cmdResponse; cmdResponse.commandID = DG_CMD_VALVE_SETTING; cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; // valve setting command only valid in re-circulate mode if ( DG_MODE_CIRC == getCurrentOperationMode() ) { switch ( valveSettingID ) { case DG_VALVE_SETTING_R1_TO_R2: cmdResponse.rejected = FALSE; setValveState( VRO, VALVE_STATE_R1_C_TO_NO ); setValveState( VRI, VALVE_STATE_R2_C_TO_NC ); break; case DG_VALVE_SETTING_R2_TO_R1: cmdResponse.rejected = FALSE; setValveState( VRO, VALVE_STATE_R2_C_TO_NC ); setValveState( VRI, VALVE_STATE_R1_C_TO_NO ); break; default: // invalid reservoir given - cmd will be NAK'd w/ false result. cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_PARAMETER; break; } } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } sendCommandResponseMsg( &cmdResponse ); } /*********************************************************************//** * @brief * The startFillCmd 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 none *************************************************************************/ void startFillCmd( U32 fillToVolMl ) { DG_CMD_RESPONSE_T cmdResponse; cmdResponse.commandID = DG_CMD_START_FILL; cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; // 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 ); cmdResponse.rejected = FALSE; } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_PARAMETER; } } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } sendCommandResponseMsg( &cmdResponse ); } /*********************************************************************//** * @brief * The stopFillCmd function handles a stop fill command from the HD. * @details Inputs: none * @details Outputs: move to re-circulate mode * @return none *************************************************************************/ void stopFillCmd( void ) { DG_CMD_RESPONSE_T cmdResponse; cmdResponse.commandID = DG_CMD_STOP_FILL; cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; // stop fill command only valid in fill mode if ( DG_MODE_FILL == getCurrentOperationMode() ) { fillVolumeTargetMl.data = 0; requestNewOperationMode( DG_MODE_CIRC ); cmdResponse.rejected = FALSE; } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } sendCommandResponseMsg( &cmdResponse ); } /*********************************************************************//** * @brief * The startDrainCmd function handles a drain command from the HD. * @details Inputs: none * @details Outputs: Start draining in re-circulate mode * @param drainCmd drain command data record * @return none *************************************************************************/ void startDrainCmd( DRAIN_CMD_T drainCmd ) { DG_CMD_RESPONSE_T cmdResponse; cmdResponse.commandID = DG_CMD_START_DRAIN; cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; // drain command only valid in re-circulate mode if ( DG_MODE_CIRC == getCurrentOperationMode() ) { // validate parameters if ( drainCmd.targetVolume <= MAX_DRAIN_VOLUME_ML ) { drainVolumeTargetMl.data = drainCmd.targetVolume; tareLoadCellRequest = drainCmd.tareLoadCell; requestNewOperationMode( DG_MODE_DRAI ); cmdResponse.rejected = FALSE; } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_PARAMETER; } } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } sendCommandResponseMsg( &cmdResponse ); } /*********************************************************************//** * @brief * The stopDrainCmd function handles a stop drain command from the HD. * @details Inputs: none * @details Outputs: move to re-circulate mode * @return none *************************************************************************/ void stopDrainCmd( void ) { DG_CMD_RESPONSE_T cmdResponse; cmdResponse.commandID = DG_CMD_STOP_DRAIN; cmdResponse.rejected = TRUE; cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_NONE; // stop drain command only valid in drain mode if ( DG_MODE_DRAI == getCurrentOperationMode() ) { drainVolumeTargetMl.data = 0; requestNewOperationMode( DG_MODE_CIRC ); cmdResponse.rejected = FALSE; } else { cmdResponse.rejectCode = DG_CMD_REQUEST_REJECT_REASON_INVALID_MODE; } sendCommandResponseMsg( &cmdResponse ); } /*********************************************************************//** * @brief * The tareReservoir function sets the tare load cell variable to TRUE. * @details Inputs: none * @details Outputs: none * @return none *************************************************************************/ void tareReservoir( void ) { tareLoadCellRequest = TRUE; } /*********************************************************************//** * @brief * The resetReservoirsLowestWeight function resets the lowest load cell * weight of the reservoirs. * @details Inputs: reservoirLowestWeight * @details Outputs: reservoirLowestWeight * @return none *************************************************************************/ void resetReservoirsLowestWeight( void ) { reservoirLowestWeight[ DG_RESERVOIR_1 ] = MAX_RESERVOIR_WEIGHT; reservoirLowestWeight[ DG_RESERVOIR_2 ] = MAX_RESERVOIR_WEIGHT; } /*********************************************************************//** * @brief * The getInactiveReservoir function gets the inactive reservoir. * @details Inputs: activeReservoir * @details Outputs: none * @return the currently inactive reservoir. *************************************************************************/ DG_RESERVOIR_ID_T getInactiveReservoir( void ) { DG_RESERVOIR_ID_T inactiveReservoir = DG_RESERVOIR_1; if ( DG_RESERVOIR_1 == getActiveReservoir() ) { inactiveReservoir = DG_RESERVOIR_2; } return inactiveReservoir; } /*********************************************************************//** * @brief * The getReservoirWeight function returns the large filtered weight * of the reservoir's associated load cell. * @details Inputs: associatedLoadCell[] * @details Outputs: none * @param reservoirId id of reservoir to get weight from * @return large filtered weight *************************************************************************/ F32 getReservoirWeight( DG_RESERVOIR_ID_T reservoirId ) { return getLoadCellLargeFilteredWeight( associatedLoadCell[ reservoirId ] ); } /*********************************************************************//** * @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( DG_RESERVOIR_ID_T reservoirId ) { F32 const loadcellWeight = getLoadCellSmallFilteredWeight( 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( DG_RESERVOIR_ID_T reservoirId, U32 timeout ) { BOOL result = FALSE; F32 const loadcellWeight = getLoadCellSmallFilteredWeight( 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 ( ( TRUE == hasTimeOut ) || ( ( TRUE == hasTargetReached ) && ( FALSE == tareLoadCellRequest ) ) ) { result = TRUE; reservoirLowestWeight[ reservoirId ] = MAX_RESERVOIR_WEIGHT; if ( TRUE == tareLoadCellRequest ) { tareLoadCellRequest = FALSE; tareLoadCell( associatedLoadCell[ reservoirId ] ); tareLoadCell( redundantLoadCell[ reservoirId ] ); } } return result; } /*********************************************************************//** * @brief * The getActiveReservoir function gets the active reservoir. * @details Inputs: activeReservoir * @details Outputs: none * @return the currently active reservoir. *************************************************************************/ static DG_RESERVOIR_ID_T getActiveReservoir( void ) { DG_RESERVOIR_ID_T result = (DG_RESERVOIR_ID_T)activeReservoir.data; if ( OVERRIDE_KEY == activeReservoir.override ) { result = (DG_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( DG_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; } /**@}*/