Index: firmware/App/Controllers/BalancingChamber.c =================================================================== diff -u -re0c45c725884d780b76dd54a617ab6ed333d7ba2 -rbba59b7f6fd3d655a595fddb00af295ff895f593 --- firmware/App/Controllers/BalancingChamber.c (.../BalancingChamber.c) (revision e0c45c725884d780b76dd54a617ab6ed333d7ba2) +++ firmware/App/Controllers/BalancingChamber.c (.../BalancingChamber.c) (revision bba59b7f6fd3d655a595fddb00af295ff895f593) @@ -47,6 +47,11 @@ #define SPENT_FILL_COMPLETE_PRES_QD_LOW_PSIG 3.5F ///< Spent fill complete pressure (PSI) when Diener 1000 and Qd <= 200. #define SPENT_FILL_COMPLETE_PRES_QD_MID_PSIG 15.0F ///< Spent fill complete pressure (PSI) when Diener 1000 and 200 < Qd <= 400. #define SPENT_FILL_COMPLETE_PRES_QD_HIGH_PSIG 20.0F ///< Spent fill complete pressure (PSI) when Diener 1000 and Qd > 400. +#define SPENT_FILL_COMPLETE_QD_SLOPE_MAX_MLPM 300.0F ///< For Qd <= 300 mL/min, spent fill completion uses pressure-rise slope detection. +#define SPENT_FILL_COMPLETE_QD_TWO_HIT_MAX_MLPM 150.0F ///< For Qd <= 150 mL/min, require two rise detections; above this and <=300, one rise detection is enough. +#define SPENT_FILL_COMPLETE_DP_RISE_PSIG 0.5F ///< Minimum pressure rise per 50 ms sample to indicate spent-side rising edge at low Qd. +#define SPENT_FILL_COMPLETE_DP_RISE_CONSEC_COUNT 2U ///< Maximum number of rise detections tracked before declaring completion. +#define SPENT_FILL_DETECT_COUNT_TOL_PCT 0.20F ///< Allowed detection timing tolerance as a fraction of expected BC switching count. #define QD_THRESHOLD_LOW_MLPM 200.0F ///< Qd threshold (mL/min) below which low pressure limit applies. #define QD_THRESHOLD_HIGH_MLPM 400.0F ///< Qd threshold (mL/min) below which mid pressure limit applies. #define SPENT_DIFF_COUNT_ZERO 0 ///< Zero count difference for spent side fill comparing target count @@ -88,6 +93,11 @@ static U32 balChamberFillTimeoutCount; ///< Timeout count (in task interval) to detect BC fill timeout. static S32 diffSpentFillCompleteCount; ///< Difference between spent target fill to actual fill count static BOOL isSpentFillComplete; ///< Flag indicating spent side fill complete. +static BOOL spentFillSamplesInitialized; ///< Low-Qd slope: FALSE until first VALVES_CLOSE tick after dosing baseline is stored. +static F32 lastPrevSpentDialPressure; ///< Low-Qd slope: spent pressure last sample of dosing (tick before VALVES_CLOSE); then n-2 in VC. +static F32 prevSpentDialPressure; ///< Spent pressure previous sample (n-1) during VALVES_CLOSE for low-Qd slope detection. +static U32 spentFillRiseConsecCounter; ///< Counter of consecutive low-Qd spent pressure rises above threshold. +static U32 spentFillRiseMissCounter; ///< Counter of misses after first low-Qd rise detection. //TODO: remove later once level sensor working static U32 bicarbChamberPeriodicFillCounter; @@ -149,6 +159,11 @@ balChamberFillTimeoutCount = 0; diffSpentFillCompleteCount = 0; isSpentFillComplete = FALSE; + spentFillSamplesInitialized = FALSE; + lastPrevSpentDialPressure = 0.0F; + prevSpentDialPressure = 0.0F; + spentFillRiseConsecCounter = 0; + spentFillRiseMissCounter = 0; //TODO:remove once level sensor working bicarbChamberPeriodicFillCounter = 0; } @@ -459,6 +474,11 @@ isPressureStabilizedDuringFill = FALSE; isPressureDroppedDuringFill = FALSE; isSpentFillComplete = FALSE; + spentFillSamplesInitialized = FALSE; + lastPrevSpentDialPressure = 0.0F; + prevSpentDialPressure = 0.0F; + spentFillRiseConsecCounter = 0; + spentFillRiseMissCounter = 0U; F32 acidVolume = getF32OverrideValue( &acidDoseVolume ); F32 bicarbVolume = getF32OverrideValue( &bicarbDoseVolume ); @@ -539,11 +559,16 @@ if ( BAL_CHAMBER_SW_STATE1 == balChamberSWState ) { - state = BAL_CHAMBER_STATE1_VALVES_CLOSE; + // Low-Qd spent slope: baseline = spent pressure on last dosing tick (this task period) before VALVES_CLOSE. + lastPrevSpentDialPressure = spentDialPressure; + spentFillSamplesInitialized = FALSE; + state = BAL_CHAMBER_STATE1_VALVES_CLOSE; } else { - state = BAL_CHAMBER_STATE2_VALVES_CLOSE; + lastPrevSpentDialPressure = spentDialPressure; + spentFillSamplesInitialized = FALSE; + state = BAL_CHAMBER_STATE2_VALVES_CLOSE; } } @@ -682,6 +707,11 @@ balChamberSWState = BAL_CHAMBER_SW_STATE2; currentBalChamberFillCounter = 0; balChamberFillTimeoutCount = 0; + spentFillSamplesInitialized = FALSE; + lastPrevSpentDialPressure = 0.0F; + prevSpentDialPressure = 0.0F; + spentFillRiseConsecCounter = 0; + spentFillRiseMissCounter = 0U; isSpentFillComplete = FALSE; F32 acidVolume = getF32OverrideValue( &acidDoseVolume ); @@ -864,6 +894,10 @@ F32 qdMlpm = getTDDialysateFlowrate(); BOOL result = FALSE; F32 spentFillCompletePresPsig; + BOOL useSlopeDetector = FALSE; + BOOL isSpentFillCompleteDetected = FALSE; + BOOL isDetectionInCountWindow = TRUE; + U32 requiredRiseCount = SPENT_FILL_COMPLETE_DP_RISE_CONSEC_COUNT; // Diener 2000 pump: spent fill complete pressure use legacy 25 PSI. Other pump: depends on Qd. if ( TRUE == getTestConfigStatus( TEST_CONFIG_DD_ENABLE_DIENER_2000_PUMP ) ) @@ -886,8 +920,93 @@ } } - if ( ( spentDialPressure >= spentFillCompletePresPsig ) && ( isSpentFillComplete != TRUE ) ) + // For low Qd on Diener-1000 path use a slope-based rising-edge check; otherwise retain absolute-pressure completion. + useSlopeDetector = ( ( TRUE != getTestConfigStatus( TEST_CONFIG_DD_ENABLE_DIENER_2000_PUMP ) ) && + ( qdMlpm <= SPENT_FILL_COMPLETE_QD_SLOPE_MAX_MLPM ) ); + + if ( TRUE == useSlopeDetector ) { + F32 dp = 0.0F; + requiredRiseCount = ( qdMlpm <= SPENT_FILL_COMPLETE_QD_TWO_HIT_MAX_MLPM ) ? SPENT_FILL_COMPLETE_DP_RISE_CONSEC_COUNT : 1U; + + // First VALVES_CLOSE tick: prev = current spent; lastPrev was set at dosing completion (transition into VALVES_CLOSE). + if ( FALSE == spentFillSamplesInitialized ) + { + prevSpentDialPressure = spentDialPressure; + spentFillSamplesInitialized = TRUE; + return; + } + + // Compute rise using a 100 ms gap (n-2 sample minus current), then shift sample history by one. + dp = lastPrevSpentDialPressure - spentDialPressure; + + if ( dp > SPENT_FILL_COMPLETE_DP_RISE_PSIG ) + { + spentFillRiseConsecCounter = MIN( spentFillRiseConsecCounter + 1U, requiredRiseCount ); + spentFillRiseMissCounter = 0; + } + else + { + if ( spentFillRiseConsecCounter > 0 ) + { + // Allow one missed sample before resetting so brief noise dip doesn't discard the first hit. + spentFillRiseMissCounter++; + + if ( spentFillRiseMissCounter >= 2 ) + { + spentFillRiseConsecCounter = 0; + spentFillRiseMissCounter = 0; + } + } + else + { + spentFillRiseMissCounter = 0; + } + } + + isSpentFillCompleteDetected = ( spentFillRiseConsecCounter >= requiredRiseCount ); + lastPrevSpentDialPressure = prevSpentDialPressure; + prevSpentDialPressure = spentDialPressure; + } + else + { + spentFillRiseConsecCounter = 0; + spentFillRiseMissCounter = 0; + spentFillSamplesInitialized = FALSE; + lastPrevSpentDialPressure = spentDialPressure; + prevSpentDialPressure = spentDialPressure; + isSpentFillCompleteDetected = ( spentDialPressure >= spentFillCompletePresPsig ); + } + + if ( ( TRUE == isSpentFillCompleteDetected ) && ( isSpentFillComplete != TRUE ) ) + { + // Validate detection time against expected BC switching count to reject false early/late detections. + if ( TRUE == useSlopeDetector ) + { + U32 minDetectCount = 0; + U32 detectTolCount = (U32)( (F32)balChamberValveClosePeriod * SPENT_FILL_DETECT_COUNT_TOL_PCT ); + U32 maxDetectCount = balChamberValveClosePeriod + detectTolCount; + + if ( balChamberValveClosePeriod > detectTolCount ) + { + minDetectCount = balChamberValveClosePeriod - detectTolCount; + } + + isDetectionInCountWindow = ( ( currentBalChamberFillCounter >= minDetectCount ) && + ( currentBalChamberFillCounter <= maxDetectCount ) ); + + if ( FALSE == isDetectionInCountWindow ) + { + // Reject this detection and continue monitoring for a valid low-Qd rising edge. + spentFillRiseConsecCounter = 0; + spentFillRiseMissCounter = 0; + isSpentFillCompleteDetected = FALSE; + } + } + } + + if ( ( TRUE == isSpentFillCompleteDetected ) && ( isSpentFillComplete != TRUE ) ) + { // Check spent fill time against the balancing chamber closing period diffSpentFillCompleteCount = balChamberValveClosePeriod - currentBalChamberFillCounter; absDiffSpentFillCount = abs(diffSpentFillCompleteCount);