Index: firmware/App/Modes/Rinseback.c =================================================================== diff -u -rb5accb82ac043938255883b6c60a6f81795569b0 -rccfd15568f1e3d304320c2babb2fd4bcf0413304 --- firmware/App/Modes/Rinseback.c (.../Rinseback.c) (revision b5accb82ac043938255883b6c60a6f81795569b0) +++ firmware/App/Modes/Rinseback.c (.../Rinseback.c) (revision ccfd15568f1e3d304320c2babb2fd4bcf0413304) @@ -1,17 +1,17 @@ /************************************************************************** * -* Copyright (c) 2019-2020 Diality Inc. - All Rights Reserved. +* Copyright (c) 2019-2021 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 Rinseback.c +* @file Rinseback.c * -* @author (last) Sean Nash -* @date (last) 14-Jan-2021 +* @author (last) Quang Nguyen +* @date (last) 01-Sep-2021 * -* @author (original) Sean -* @date (original) 14-Jan-2021 +* @author (original) Sean Nash +* @date (original) 20-Jan-2021 * ***************************************************************************/ @@ -43,17 +43,20 @@ #define RINSEBACK_FLOW_RATE_ADJ_ML_MIN 25 ///< Adjustment amount (in mL/min) to apply when user requests increase/decrease in flow rate. #define MIN_RINSEBACK_FLOW_RATE_ML_MIN 50 ///< Minimum rinseback flow rate (in mL/min). #define MAX_RINSEBACK_FLOW_RATE_ML_MIN 150 ///< Maximum rinseback flow rate (in mL/min). + +#ifndef DISABLE_PUMP_FLOW_CHECKS /// Maximum rinseback volume measured by independent means (as % of target). static const F32 MAX_RINSEBACK_SAFETY_VOLUME_ML = ( TARGET_RINSEBACK_VOLUME_ML * 1.2 ); /// Minimum rinseback volume measured by independent means (as % of target). static const F32 MIN_RINSEBACK_SAFETY_VOLUME_ML = ( TARGET_RINSEBACK_VOLUME_ML * 0.8 ); +#endif /// Interval at which rinseback progress is to be published to UI. -static const U32 RINSEBACK_DATA_PUBLISH_INTERVAL = ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ); +#define RINSEBACK_DATA_PUBLISH_INTERVAL ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) /// Maximum time allowed for rinseback operation until full volume is delivered. Timer is reset whenever BP is running. static const U32 MAX_RINSEBACK_TIME = ( 5 * SEC_PER_MIN * ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ); /// Maximum time allowed for each additional rinseback volume delivery. -static const U32 MAX_RINSEBACK_ADDITIONAL_TIME = ( 15 * MS_PER_SECOND / TASK_GENERAL_INTERVAL ); +static const U32 MAX_RINSEBACK_ADDITIONAL_TIME = ( 20 * MS_PER_SECOND / TASK_GENERAL_INTERVAL ); /// Multiplier to convert flow (mL/min) into volume (mL) for period of general task interval. static const F32 RINSEBACK_FLOW_INTEGRATOR = 1.0 / (F32)( SEC_PER_MIN * ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ); @@ -63,14 +66,16 @@ static U32 rinsebackRate_mL_min; ///< Rinseback rate to use/adjust for this current rinseback only. static U32 rinsebackTimerCtr; ///< Timer counter for time spent in rinseback sub-mode. -static F32 cumulativeRinsebackVolume_mL; ///< Total cumulative rinseback volume (in mL). +static OVERRIDE_F32_T cumulativeRinsebackVolume_mL = { 0.0, 0.0, 0.0, 0 }; ///< Total cumulative rinseback volume (in mL). +static OVERRIDE_F32_T rinsebackVolumeDelivered_Safety = { 0.0, 0.0, 0.0, 0 }; ///< The cumulative independent rinseback volume (in mL) calculated so far. static F32 targetRinsebackVolumePlusAdditional_mL; ///< Target rinseback volume w/ additional volume(s) added (in mL). static F32 additionalRinsebackVolume_mL; ///< Total volume (in mL) delivered so far for additional volume request. static S32 rinsebackMotorCount; ///< The cumulative sum of BP motor encoder counts used for independent rinseback volume check. static U32 rinsebackLastMotorCount; ///< The last BP motor encoder count read for independent rinseback volume check. -static F32 rinsebackVolumeDelivered_Safety; ///< The cumulative independent rinseback volume (in mL) calculated so far. static U32 rinsebackAdditionalTimerCtr; ///< Timer counter for duration of an additional rinseback delivery. static U32 rinsebackPublishTimerCtr; ///< Timer counter for determining interval for rinseback status to be published. +/// Interval (in task intervals) at which to publish rinseback data to CAN bus. +static OVERRIDE_U32_T rinsebackPublishInterval = { RINSEBACK_DATA_PUBLISH_INTERVAL, RINSEBACK_DATA_PUBLISH_INTERVAL, RINSEBACK_DATA_PUBLISH_INTERVAL, 0 }; static BOOL startRinsebackRequested; ///< Flag indicates user requesting rinseback start (confirming saline bag move to arterial line end). static BOOL incrRinsebackFlowRateRequested; ///< Flag indicates user requesting rinseback flow rate be increased. @@ -90,6 +95,9 @@ static void setupForRinsebackDelivery( U32 rate ); static void setupForRinsebackStopOrPause( void ); +static F32 getRinsebackVolume( void ); +static F32 getRinsebackSafetyVolume( void ); + static RINSEBACK_STATE_T handleRinsebackStopInitState( void ); static RINSEBACK_STATE_T handleRinsebackRunState( void ); static RINSEBACK_STATE_T handleRinsebackPausedState( void ); @@ -108,6 +116,7 @@ static BOOL handleEndTreatmentUserAction( REQUEST_REJECT_REASON_CODE_T *rejReason ); static void publishRinsebackData( void ); +static U32 getPublishRinsebackInterval( void ); /*********************************************************************//** * @brief @@ -122,11 +131,11 @@ rinsebackRate_mL_min = getTreatmentParameterU32( TREATMENT_PARAM_RINSEBACK_FLOW_RATE ); targetRinsebackVolumePlusAdditional_mL = TARGET_RINSEBACK_VOLUME_ML; rinsebackTimerCtr = 0; - cumulativeRinsebackVolume_mL = 0.0; + cumulativeRinsebackVolume_mL.data = 0.0; + rinsebackVolumeDelivered_Safety.data = 0.0; additionalRinsebackVolume_mL = 0.0; rinsebackMotorCount = 0; rinsebackLastMotorCount = getBloodPumpMotorCount(); - rinsebackVolumeDelivered_Safety = 0.0; rinsebackAdditionalTimerCtr = 0; rinsebackPublishTimerCtr = 0; resetRinsebackFlags(); @@ -230,6 +239,46 @@ /*********************************************************************//** * @brief + * The getRinsebackVolume function gets the calculated blood prime volume + * delivered. + * @details Inputs: cumulativeRinsebackVolume_mL + * @details Outputs: none + * @return the current rinseback volume delivered (in mL). + *************************************************************************/ +static F32 getRinsebackVolume( void ) +{ + F32 result = cumulativeRinsebackVolume_mL.data; + + if ( OVERRIDE_KEY == cumulativeRinsebackVolume_mL.override ) + { + result = cumulativeRinsebackVolume_mL.ovData; + } + + return result; +} + +/*********************************************************************//** + * @brief + * The getRinsebackSafetyVolume function gets the calculated independent + * blood prime volume delivered. + * @details Inputs: rinsebackVolumeDelivered_Safety + * @details Outputs: none + * @return the current rinseback safety volume delivered (in mL). + *************************************************************************/ +static F32 getRinsebackSafetyVolume( void ) +{ + F32 result = rinsebackVolumeDelivered_Safety.data; + + if ( OVERRIDE_KEY == rinsebackVolumeDelivered_Safety.override ) + { + result = rinsebackVolumeDelivered_Safety.ovData; + } + + return result; +} + +/*********************************************************************//** + * @brief * The getCurrentRinsebackState function returns the current state of the * rinseback sub-mode. * @details Inputs: rinsebackState @@ -306,6 +355,7 @@ // From moment we start rinseback, we consider blood side of dialyzer no longer fully primed setBloodIsPrimed( FALSE ); result = RINSEBACK_RUN_STATE; + sendTreatmentLogEventData( MID_TREATMENT_RINSE_BACK_EVENT, 0.0, 0.0 ); } // Has user requested to return to treatment? else if ( TRUE == backToTreatmentRequested ) @@ -343,10 +393,10 @@ rinsebackTimerCtr = 0; // Update rinseback volume delivered so far - cumulativeRinsebackVolume_mL += ( getMeasuredBloodFlowRate() * RINSEBACK_FLOW_INTEGRATOR ); + cumulativeRinsebackVolume_mL.data += ( getMeasuredBloodFlowRate() * RINSEBACK_FLOW_INTEGRATOR ); // update independent calculated safety volume delivered so far rinsebackMotorCount = u32BiDiffWithWrap( rinsebackLastMotorCount, getBloodPumpMotorCount() ) / BP_HALL_EDGE_COUNTS_PER_REV; - rinsebackVolumeDelivered_Safety = ( (F32)rinsebackMotorCount * VOLUME_PER_BP_MOTOR_REV_ML ); // TODO - include upstream pressure compensation to this calc + rinsebackVolumeDelivered_Safety.data = ( (F32)rinsebackMotorCount * VOLUME_PER_BP_MOTOR_REV_ML ); // TODO - include upstream pressure compensation to this calc // Has user requested to end rinseback? if ( TRUE == endRinsebackRequested ) @@ -355,16 +405,16 @@ result = RINSEBACK_STOP_STATE; } // Has rinseback completed? - else if ( cumulativeRinsebackVolume_mL >= TARGET_RINSEBACK_VOLUME_ML ) + else if ( getRinsebackVolume() >= TARGET_RINSEBACK_VOLUME_ML ) { setRinsebackIsCompleted( TRUE ); setupForRinsebackStopOrPause(); result = RINSEBACK_STOP_STATE; #ifndef DISABLE_PUMP_FLOW_CHECKS // Check for under-delivery - if ( rinsebackVolumeDelivered_Safety < MIN_RINSEBACK_SAFETY_VOLUME_ML ) + if ( getRinsebackSafetyVolume() < MIN_RINSEBACK_SAFETY_VOLUME_ML ) { - SET_ALARM_WITH_2_F32_DATA( ALARM_ID_RINSEBACK_VOLUME_CHECK_FAILURE, TARGET_RINSEBACK_VOLUME_ML, rinsebackVolumeDelivered_Safety ); + SET_ALARM_WITH_2_F32_DATA( ALARM_ID_RINSEBACK_VOLUME_CHECK_FAILURE, TARGET_RINSEBACK_VOLUME_ML, getRinsebackSafetyVolume() ); } #endif } @@ -383,11 +433,11 @@ } #ifndef DISABLE_PUMP_FLOW_CHECKS // Has independent safety volume exceeded safety limit? - else if ( rinsebackVolumeDelivered_Safety > MAX_RINSEBACK_SAFETY_VOLUME_ML ) + else if ( getRinsebackSafetyVolume() > MAX_RINSEBACK_SAFETY_VOLUME_ML ) { setRinsebackIsCompleted( TRUE ); setupForRinsebackStopOrPause(); - SET_ALARM_WITH_2_F32_DATA( ALARM_ID_RINSEBACK_VOLUME_CHECK_FAILURE, TARGET_RINSEBACK_VOLUME_ML, rinsebackVolumeDelivered_Safety ); + SET_ALARM_WITH_2_F32_DATA( ALARM_ID_RINSEBACK_VOLUME_CHECK_FAILURE, TARGET_RINSEBACK_VOLUME_ML, getRinsebackSafetyVolume() ); result = RINSEBACK_STOP_STATE; } #endif @@ -469,7 +519,7 @@ signalRinsebackToRecirc(); } // Has rinseback operation exceeded max time w/o delivering full volume? - if ( ( rinsebackTimerCtr > MAX_RINSEBACK_TIME ) && ( cumulativeRinsebackVolume_mL < TARGET_RINSEBACK_VOLUME_ML ) ) + if ( ( rinsebackTimerCtr > MAX_RINSEBACK_TIME ) && ( getRinsebackVolume() < TARGET_RINSEBACK_VOLUME_ML ) ) { signalGoToTreatmentStopped(); activateAlarmNoData( ALARM_ID_TREATMENT_RINSEBACK_TIMEOUT_ALARM ); @@ -478,8 +528,8 @@ { additionalRinsebackRequested = FALSE; // deliver additional rinseback volume only if max volume not reached and max time not reached - if ( ( ( cumulativeRinsebackVolume_mL + TARGET_ADDITIONAL_RINSEBACK_VOLUME_ML ) <= MAX_TOTAL_RINSEBACK_VOLUME_ML ) && - ( ( rinsebackTimerCtr < MAX_RINSEBACK_TIME ) || ( cumulativeRinsebackVolume_mL >= TARGET_RINSEBACK_VOLUME_ML ) ) ) + if ( ( ( getRinsebackVolume() + TARGET_ADDITIONAL_RINSEBACK_VOLUME_ML ) <= MAX_TOTAL_RINSEBACK_VOLUME_ML ) && + ( ( rinsebackTimerCtr < MAX_RINSEBACK_TIME ) || ( getRinsebackVolume() >= TARGET_RINSEBACK_VOLUME_ML ) ) ) { rinsebackAdditionalTimerCtr = 0; additionalRinsebackVolume_mL = 0.0; @@ -518,12 +568,12 @@ // update additional rinseback volume delivered so far additionalRinsebackVolume_mL += rinsebackVolumeSinceLast; - cumulativeRinsebackVolume_mL += rinsebackVolumeSinceLast; + cumulativeRinsebackVolume_mL.data += rinsebackVolumeSinceLast; rinsebackAdditionalTimerCtr++; // Has additional rinseback completed or timed out? if ( ( additionalRinsebackVolume_mL >= TARGET_ADDITIONAL_RINSEBACK_VOLUME_ML ) || - ( cumulativeRinsebackVolume_mL >= MAX_TOTAL_RINSEBACK_VOLUME_ML ) || + ( getRinsebackVolume() >= MAX_TOTAL_RINSEBACK_VOLUME_ML ) || ( rinsebackAdditionalTimerCtr >= MAX_RINSEBACK_ADDITIONAL_TIME ) ) { setupForRinsebackStopOrPause(); @@ -694,7 +744,7 @@ { BOOL result = FALSE; - if ( ( rinsebackRate_mL_min + RINSEBACK_FLOW_RATE_ADJ_ML_MIN ) >= MIN_RINSEBACK_FLOW_RATE_ML_MIN ) + if ( ( rinsebackRate_mL_min - RINSEBACK_FLOW_RATE_ADJ_ML_MIN ) >= MIN_RINSEBACK_FLOW_RATE_ML_MIN ) { if ( RINSEBACK_RUN_STATE == rinsebackState ) { @@ -811,14 +861,14 @@ if ( RINSEBACK_STOP_STATE == rinsebackState ) { - if ( ( cumulativeRinsebackVolume_mL + TARGET_ADDITIONAL_RINSEBACK_VOLUME_ML ) <= MAX_TOTAL_RINSEBACK_VOLUME_ML ) + if ( ( getRinsebackVolume() + TARGET_ADDITIONAL_RINSEBACK_VOLUME_ML ) <= MAX_TOTAL_RINSEBACK_VOLUME_ML ) { result = TRUE; additionalRinsebackRequested = TRUE; } else { - *rejReason = REQUEST_REJECT_REASON_RINSEBACK_MAX_VOLUME_REACHED; + *rejReason = REQUEST_REJECT_REASON_ADDL_RINSEBACK_MAX_VOLUME_REACHED; } } else @@ -929,24 +979,22 @@ *************************************************************************/ static void publishRinsebackData( void ) { - if ( ++rinsebackPublishTimerCtr >= RINSEBACK_DATA_PUBLISH_INTERVAL ) + if ( ++rinsebackPublishTimerCtr >= getPublishRinsebackInterval() ) { RINSEBACK_DATA_PAYLOAD_T data; U32 timeout = MAX_RINSEBACK_TIME / ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ); U32 countdown = ( rinsebackTimerCtr >= MAX_RINSEBACK_TIME ? 0 : ( MAX_RINSEBACK_TIME - rinsebackTimerCtr ) / ( MS_PER_SECOND / TASK_GENERAL_INTERVAL ) ); + data.targetRinsebackVolumeMl = TARGET_RINSEBACK_VOLUME_ML; rinsebackPublishTimerCtr = 0; - // If we have completed rinseback, timeout is no longer in force - indicate by zeroing timeout - if ( ( rinsebackState > RINSEBACK_PAUSED_STATE ) && ( cumulativeRinsebackVolume_mL >= TARGET_RINSEBACK_VOLUME_ML ) ) + // If we have completed rinseback, timeout is no longer in force - indicate by zeroing timeout. Also include any additionals to target. + if ( ( rinsebackState > RINSEBACK_PAUSED_STATE ) && ( getRinsebackVolume() >= TARGET_RINSEBACK_VOLUME_ML ) ) { + data.targetRinsebackVolumeMl = targetRinsebackVolumePlusAdditional_mL; timeout = 0; } - data.targetRinsebackVolumeMl = TARGET_RINSEBACK_VOLUME_ML; - if ( RINSEBACK_RUN_ADDITIONAL_STATE == rinsebackState ) - { - data.targetRinsebackVolumeMl = targetRinsebackVolumePlusAdditional_mL; - } - data.deliveredRinsebackVolumeMl = cumulativeRinsebackVolume_mL; + data.deliveredRinsebackVolumeMl = getRinsebackVolume(); + data.safetyRinsebackVolumeMl = getRinsebackSafetyVolume(); data.rinsebackFlowRateMlMin = rinsebackRate_mL_min; if ( RINSEBACK_RUN_ADDITIONAL_STATE == rinsebackState ) { @@ -958,4 +1006,167 @@ } } +/*********************************************************************//** + * @brief + * The getPublishRinsebackInterval function gets the rinseback data + * publication interval. + * @details Inputs: rinsebackPublishInterval + * @details Outputs: none + * @return the current rinseback publication interval (in task intervals). + *************************************************************************/ +static U32 getPublishRinsebackInterval( void ) +{ + U32 result = rinsebackPublishInterval.data; + + if ( OVERRIDE_KEY == rinsebackPublishInterval.override ) + { + result = rinsebackPublishInterval.ovData; + } + + return result; +} + + +/************************************************************************* + * TEST SUPPORT FUNCTIONS + *************************************************************************/ + + +/*********************************************************************//** + * @brief + * The testSetRinsebackVolumeOverride function overrides the calculated + * rinseback volume delivered. + * @details Inputs: none + * @details Outputs: cumulativeRinsebackVolume_mL + * @param vol override calculated rinseback volume (in mL) + * @return TRUE if override successful, FALSE if not + *************************************************************************/ +BOOL testSetRinsebackVolumeOverride( F32 vol ) +{ + BOOL result = FALSE; + + if ( TRUE == isTestingActivated() ) + { + result = TRUE; + cumulativeRinsebackVolume_mL.ovData = vol; + cumulativeRinsebackVolume_mL.override = OVERRIDE_KEY; + } + + return result; +} + +/*********************************************************************//** + * @brief + * The testResetRinsebackVolumeOverride function resets the override of the + * calculated rinseback volume. + * @details Inputs: none + * @details Outputs: cumulativeRinsebackVolume_mL + * @return TRUE if reset successful, FALSE if not + *************************************************************************/ +BOOL testResetRinsebackVolumeOverride( void ) +{ + BOOL result = FALSE; + + if ( TRUE == isTestingActivated() ) + { + result = TRUE; + cumulativeRinsebackVolume_mL.override = OVERRIDE_RESET; + cumulativeRinsebackVolume_mL.ovData = cumulativeRinsebackVolume_mL.ovInitData; + } + + return result; +} + +/*********************************************************************//** + * @brief + * The testSetRinsebackSafetyVolumeOverride function overrides the calculated + * rinseback safety volume. + * @details Inputs: none + * @details Outputs: rinsebackVolumeDelivered_Safety + * @param vol override calculated rinseback safety volume (in mL) + * @return TRUE if override successful, FALSE if not + *************************************************************************/ +BOOL testSetRinsebackSafetyVolumeOverride( F32 vol ) +{ + BOOL result = FALSE; + + if ( TRUE == isTestingActivated() ) + { + result = TRUE; + rinsebackVolumeDelivered_Safety.ovData = vol; + rinsebackVolumeDelivered_Safety.override = OVERRIDE_KEY; + } + + return result; +} + +/*********************************************************************//** + * @brief + * The testResetRinsebackSafetyVolumeOverride function resets the override of the + * calculated rinseback safety volume. + * @details Inputs: none + * @details Outputs: rinsebackVolumeDelivered_Safety + * @return TRUE if reset successful, FALSE if not + *************************************************************************/ +BOOL testResetRinsebackSafetyVolumeOverride( void ) +{ + BOOL result = FALSE; + + if ( TRUE == isTestingActivated() ) + { + result = TRUE; + rinsebackVolumeDelivered_Safety.override = OVERRIDE_RESET; + rinsebackVolumeDelivered_Safety.ovData = rinsebackVolumeDelivered_Safety.ovInitData; + } + + return result; +} + +/*********************************************************************//** + * @brief + * The testSetRinsebackPublishIntervalOverride function sets the override of the + * rinseback data publication interval. + * @details Inputs: none + * @details Outputs: rinsebackPublishInterval + * @param ms milliseconds between rinseback broadcasts + * @return TRUE if override set successful, FALSE if not + *************************************************************************/ +BOOL testSetRinsebackPublishIntervalOverride( U32 ms ) +{ + BOOL result = FALSE; + + if ( TRUE == isTestingActivated() ) + { + U32 intvl = ms / TASK_GENERAL_INTERVAL; + + result = TRUE; + rinsebackPublishInterval.ovData = intvl; + rinsebackPublishInterval.override = OVERRIDE_KEY; + } + + return result; +} + +/*********************************************************************//** + * @brief + * The testResetRinsebackPublishIntervalOverride function resets the override of the + * rinseback data publication interval. + * @details Inputs: none + * @details Outputs: rinsebackPublishInterval + * @return TRUE if override reset successful, FALSE if not + *************************************************************************/ +BOOL testResetRinsebackPublishIntervalOverride( void ) +{ + BOOL result = FALSE; + + if ( TRUE == isTestingActivated() ) + { + result = TRUE; + rinsebackPublishInterval.override = OVERRIDE_RESET; + rinsebackPublishInterval.ovData = rinsebackPublishInterval.ovInitData; + } + + return result; +} + /**@}*/