Index: firmware/App/Modes/ModeTxParams.c =================================================================== diff -u -r79c2105d7ec35f3caeb977f6e2cc1b494853d211 -r395522dffef1348e176564925656012f529c1910 --- firmware/App/Modes/ModeTxParams.c (.../ModeTxParams.c) (revision 79c2105d7ec35f3caeb977f6e2cc1b494853d211) +++ firmware/App/Modes/ModeTxParams.c (.../ModeTxParams.c) (revision 395522dffef1348e176564925656012f529c1910) @@ -1,17 +1,17 @@ /************************************************************************** * -* Copyright (c) 2025-2025 Diality Inc. - All Rights Reserved. +* Copyright (c) 2025-2026 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 ModeTxParams.c * -* @author (last) Sean -* @date (last) 18-Mar-2025 +* @author (last) Dara Navaei +* @date (last) 25-Sep-2025 * -* @author (original) Sean -* @date (original) 18-Mar-2025 +* @author (original) Sean Nash +* @date (original) 21-Apr-2025 * ***************************************************************************/ @@ -27,6 +27,7 @@ #include "OperationModes.h" #include "Pressures.h" #include "StateTxDialysis.h" +#include "Switches.h" #include "Utilities.h" #include "Valve3Way.h" #include "Valves.h" @@ -38,14 +39,15 @@ // ********** private definitions ********** -#define MAX_DIALYSATE_VOLUME_ML ( 150 * ML_PER_LITER ) ///< Maximum dialysate volume (in mL) -#define MAX_HEPARIN_VOLUME_ML 10 ///< Maximum heparin volume ( in mL ) - +#define MAX_HEPARIN_VOLUME_ML 10.0F ///< Maximum heparin volume ( in mL ) #define NO_HEPARIN_PRE_STOP_TIME_SET 0 ///< Zero value indicates no Heparin pre-stop time was set by user - #define NO_HEPARIN_TYPE_SET 0xFFFFFFFF ///< UI will send this value for Heparin type if Heparin not used + #define INSTIT_CHEM_DISINFECT_ENABLE_RANGE 1 ///< Institutional record chemical disinfect enable/disable allowable range +#define SYRINGE_PUMP_PRIME_VOLUME_ML 0.353F ///< Target syringe prime volume (in mL). TODO- move these to syringe pump controller when implemented +#define SYRINGE_PUMP_FILL_VOLUME_OFFSET_ML 0.8F ///< Advised fill volume offset due to HW variance. + /// Record for range and default of treatment parameters. typedef struct { @@ -83,32 +85,60 @@ U32 rejReason; ///< Rejection reason code (if rejected). F32 ufVolumeMl; ///< Validated ultrafiltration volume (in mL). } UF_VOLUME_VAL_RESP_DATA_PAYLOAD_T; + +/// Record structure for set treatment parameter Dialin message. +typedef struct +{ + U32 paramID; ///< Treatment parameter ID. + CRITICAL_DATAS_T value; ///< Set value. +} DIALIN_SET_TX_PARAM_PAYLOAD_T; #pragma pack(pop) +/// Dialyzer volume data record structure. +typedef struct { + U32 bloodVolume; ///< Blood volume of the dialyzer in mL. + U32 dialysateVolume; ///< Dialysate volume of the dialyzer in mL. +} DIALYZER_VOLUME_DATA_T; + // ********** private data ********** static TD_TREATMENT_PARAMS_MODE_STATE_T currentTreatmentParamsState; ///< Current state of treatment parameters mode state machine. /// Treatment parameter properties (types, ranges and defaults). const TREATMENT_PARAMS_PROPERTIES_T TREAT_PARAMS_PROPERTIES[ NUM_OF_TREATMENT_PARAMS ] = -{ - { CRITICAL_DATA_TYPE_U32, {.uInt=100}, {.uInt=500}, {.uInt=100} }, // TREATMENT_PARAM_BLOOD_FLOW - { CRITICAL_DATA_TYPE_U32, {.uInt=50}, {.uInt=600}, {.uInt=100} }, // TREATMENT_PARAM_DIALYSATE_FLOW - { CRITICAL_DATA_TYPE_U32, {.uInt=60}, {.uInt=480}, {.uInt=240} }, // TREATMENT_PARAM_TREATMENT_DURATION +{ // type min max default + { CRITICAL_DATA_TYPE_U32, {.uInt=50}, {.uInt=500}, {.uInt=50} }, // TREATMENT_PARAM_BLOOD_FLOW + { CRITICAL_DATA_TYPE_U32, {.uInt=50}, {.uInt=600}, {.uInt=50} }, // TREATMENT_PARAM_DIALYSATE_FLOW + { CRITICAL_DATA_TYPE_U32, {.uInt=60}, {.uInt=480}, {.uInt=60} }, // TREATMENT_PARAM_TREATMENT_DURATION { CRITICAL_DATA_TYPE_U32, {.uInt=100}, {.uInt=300}, {.uInt=100} }, // TREATMENT_PARAM_SALINE_BOLUS_VOLUME + { CRITICAL_DATA_TYPE_U32, {.uInt=0}, {.uInt=480}, {.uInt=0} }, // TREATMENT_PARAM_HEPARIN_STOP_TIME + { CRITICAL_DATA_TYPE_U32, {.uInt=0}, {.uInt=0}, {.uInt=0} }, // TREATMENT_PARAM_HEPARIN_TYPE { CRITICAL_DATA_TYPE_U32, {.uInt=0}, {.uInt=2}, {.uInt=0} }, // TREATMENT_PARAM_ACID_CONCENTRATE { CRITICAL_DATA_TYPE_U32, {.uInt=0}, {.uInt=0}, {.uInt=0} }, // TREATMENT_PARAM_BICARB_CONCENTRATE - { CRITICAL_DATA_TYPE_U32, {.uInt=0}, {.uInt=5}, {.uInt=0} }, // TREATMENT_PARAM_DIALYZER_TYPE - { CRITICAL_DATA_TYPE_U32, {.uInt=0}, {.uInt=60}, {.uInt=30} }, // TREATMENT_PARAM_BP_MEAS_INTERVAL + { CRITICAL_DATA_TYPE_U32, {.uInt=0}, {.uInt=6}, {.uInt=0} }, // TREATMENT_PARAM_DIALYZER_TYPE + { CRITICAL_DATA_TYPE_U32, {.uInt=0}, {.uInt=60}, {.uInt=0} }, // TREATMENT_PARAM_BP_MEAS_INTERVAL { CRITICAL_DATA_TYPE_U32, {.uInt=100}, {.uInt=300}, {.uInt=200} }, // TREATMENT_PARAM_RINSEBACK_FLOW_RATE + { CRITICAL_DATA_TYPE_U32, {.uInt=200}, {.uInt=400}, {.uInt=300} }, // TREATMENT_PARAM_RINSEBACK_VOLUME { CRITICAL_DATA_TYPE_S32, {.sInt=120}, {.sInt=200}, {.sInt=120} }, // TREATMENT_PARAM_ART_PRES_LIMIT_WINDOW { CRITICAL_DATA_TYPE_S32, {.sInt=100}, {.sInt=200}, {.sInt=100} }, // TREATMENT_PARAM_VEN_PRES_LIMIT_WINDOW { CRITICAL_DATA_TYPE_S32, {.sInt=20}, {.sInt=35}, {.sInt=20} }, // TREATMENT_PARAM_VEN_PRES_LIMIT_ASYMMETRIC { CRITICAL_DATA_TYPE_S32, {.sInt=40}, {.sInt=100}, {.sInt=40} }, // TREATMENT_PARAM_TMP_PRES_LIMIT_WINDOW - { CRITICAL_DATA_TYPE_F32, {.sFlt=35.0}, {.sFlt=37.0}, {.sFlt=37.0} }, // TREATMENT_PARAM_DIALYSATE_TEMPERATURE + { CRITICAL_DATA_TYPE_F32, {.sFlt=35.0}, {.sFlt=38.0}, {.sFlt=37.0} }, // TREATMENT_PARAM_DIALYSATE_TEMPERATURE + { CRITICAL_DATA_TYPE_F32, {.sFlt=0.0}, {.sFlt=1.0}, {.sFlt=0.0} }, // TREATMENT_PARAM_HEPARIN_DISPENSE_RATE + { CRITICAL_DATA_TYPE_F32, {.sFlt=0.0}, {.sFlt=2.0}, {.sFlt=0.0} }, // TREATMENT_PARAM_HEPARIN_BOLUS_VOLUME { CRITICAL_DATA_TYPE_F32, {.sFlt=0.0}, {.sFlt=8.0}, {.sFlt=0.0} }, // TREATMENT_PARAM_UF_VOLUME }; +/// Dialyzer volumes (blood and dialysate) table in mL for each type of dialyzer. +const DIALYZER_VOLUME_DATA_T dialyzerVolumeTable[ NUM_OF_DIALYZER_TYPES ] = + { { 82, 170 }, // DIALYZER_TYPE_BBRAUN_PRO_13H + { 100, 200 }, // DIALYZER_TYPE_BBRAUN_PRO_16H + { 120, 257 }, // DIALYZER_TYPE_BBRAUN_PRO_19H + { 87, 233 }, // DIALYZER_TYPE_FRESENIUS_OPTIFLUX_F160NRE + { 102, 280 }, // DIALYZER_TYPE_FRESENIUS_OPTIFLUX_F180NRE + { 113, 348 }, // DIALYZER_TYPE_FRESENIUS_OPTIFLUX_F200NRE + { 142, 304 } }; // DIALYZER_TYPE_FRESENIUS_OPTIFLUX_F250NRE + // Current treatment parameter values static CRITICAL_DATA_T treatmentParameters[ NUM_OF_TREATMENT_PARAMS ]; ///< Treatment parameters. static CRITICAL_DATAS_T stagedParams[ NUM_OF_TREATMENT_PARAMS ]; ///< Temporary staged treatment parameters for validation and awaiting user confirmation. @@ -169,7 +199,7 @@ initTreatmentMode(); initDialysis(); -// doorClosedRequired( FALSE, FALSE ); + doorClosedRequired( FALSE ); setCurrentSubState( NO_SUB_STATE ); // Set user alarm recovery actions allowed in this mode @@ -469,6 +499,9 @@ TREATMENT_PARAMS_DATA_PAYLOAD_T params; U32 rejReasons[ NUM_OF_TREATMENT_PARAMS ]; + // Initialize reject reasons to zeroes + memset( (U08*)&rejReasons[0], 0, sizeof( U32 ) * NUM_OF_TREATMENT_PARAMS ); + // Verify message payload length is valid if ( sizeof( TREATMENT_PARAMS_DATA_PAYLOAD_T ) == message->hdr.payloadLen ) { @@ -485,35 +518,35 @@ paramsAreInRange = checkTreatmentParamsInRange( &rejReasons[0] ); // Validate dependencies - paramsAreConsistent = TRUE; // checkTreatmentParamsDependencies( &rejReasons[0] ); // TODO + paramsAreConsistent = checkTreatmentParamsDependencies( &rejReasons[0] ); // Determine overall validity of received treatment parameters if ( ( TRUE == paramsAreInRange ) && ( TRUE == paramsAreConsistent ) ) { paramsAreInvalid = FALSE; validTreatParamsReceived = TRUE; } - } - // Respond to set treatment parameters request message - sendTreatmentParamsResponse( paramsAreInvalid, &rejReasons[0] ); - // Send initial adjustable ranges to UI so UF volume range will be known when UF volume prompted for later - if ( TRUE == validTreatParamsReceived ) - { - U32 setTxDuration = stagedParams[ TREATMENT_PARAM_TREATMENT_DURATION ].uInt; - TREATMENT_PARAM_RANGE_BROADCAST_PAYLOAD_T payload; + // Respond to set treatment parameters request message + sendTreatmentParamsResponse( paramsAreInvalid, &rejReasons[0] ); + // Send initial adjustable ranges to UI so UF volume range will be known when UF volume prompted for later + if ( TRUE == validTreatParamsReceived ) + { + U32 setTxDuration = stagedParams[ TREATMENT_PARAM_TREATMENT_DURATION ].uInt; + TREATMENT_PARAM_RANGE_BROADCAST_PAYLOAD_T payload; - payload.minTreatmentTime = treatmentParameters[ TREATMENT_PARAM_TREATMENT_DURATION ].minimum.uInt; - payload.maxTreatmentTime = treatmentParameters[ TREATMENT_PARAM_TREATMENT_DURATION ].maximum.uInt; - payload.minUFVolume = 0.0F; - payload.maxUFVolume = MIN( (F32)setTxDuration * MAX_UF_RATE_ML_MIN, (F32)MAX_UF_VOLUME_ML ); - payload.minDialRate = treatmentParameters[ TREATMENT_PARAM_DIALYSATE_FLOW ].minimum.uInt; - payload.maxDialRate = treatmentParameters[ TREATMENT_PARAM_DIALYSATE_FLOW ].maximum.uInt; - sendMessage( MSG_ID_TD_TREATMENT_PARAM_RANGES, COMM_BUFFER_OUT_CAN_TD_2_UI, (U08*)(&payload), sizeof( TREATMENT_PARAM_RANGE_BROADCAST_PAYLOAD_T ) ); + payload.minTreatmentTime = treatmentParameters[ TREATMENT_PARAM_TREATMENT_DURATION ].minimum.uInt; + payload.maxTreatmentTime = treatmentParameters[ TREATMENT_PARAM_TREATMENT_DURATION ].maximum.uInt; + payload.minUFVolume = 0.0F; + payload.maxUFVolume = MIN( (F32)setTxDuration * MAX_UF_RATE_ML_MIN, (F32)MAX_UF_VOLUME_ML ); + payload.minDialRate = treatmentParameters[ TREATMENT_PARAM_DIALYSATE_FLOW ].minimum.uInt; + payload.maxDialRate = treatmentParameters[ TREATMENT_PARAM_DIALYSATE_FLOW ].maximum.uInt; + sendMessage( MSG_ID_TD_TREATMENT_PARAM_RANGES, COMM_BUFFER_OUT_CAN_TD_2_UI, (U08*)(&payload), sizeof( TREATMENT_PARAM_RANGE_BROADCAST_PAYLOAD_T ) ); + } + + result = ( TRUE == paramsAreInvalid ? FALSE : TRUE ); } - result = ( TRUE == paramsAreInvalid ? FALSE : TRUE ); - return result; } @@ -550,6 +583,46 @@ /*********************************************************************//** * @brief + * The checkTreatmentParamsDependencies function checks dependencies between + * received treatment parameters. + * @details \b Inputs: stagedParams[] + * @details \b Outputs: reasons[] + * @param reasons Pointer to array of reject reason codes for each parameter + * @return TRUE if treatment parameter dependencies are ok, FALSE if not + *************************************************************************/ +static BOOL checkTreatmentParamsDependencies( U32 *reasons ) +{ + BOOL result = TRUE; + U32 txDur = stagedParams[ TREATMENT_PARAM_TREATMENT_DURATION ].uInt; + U32 hepST = stagedParams[ TREATMENT_PARAM_HEPARIN_STOP_TIME ].uInt; + F32 hepDR = stagedParams[ TREATMENT_PARAM_HEPARIN_DISPENSE_RATE ].sFlt; + F32 hepBV = stagedParams[ TREATMENT_PARAM_HEPARIN_BOLUS_VOLUME ].sFlt; + F32 txVol = hepBV + ( hepDR * ( hepST / (F32)SEC_PER_MIN ) ) + SYRINGE_PUMP_PRIME_VOLUME_ML + SYRINGE_PUMP_FILL_VOLUME_OFFSET_ML; + + // Verify Heparin stop time is only set if Heparin dispense rate is set + if ( ( hepST > 0 ) && ( hepDR < NEARLY_ZERO ) ) + { + reasons[ TREATMENT_PARAM_HEPARIN_STOP_TIME ] = REQUEST_REJECT_REASON_HEPARIN_STOP_TIME_WITH_NO_DISPENSE; + result = FALSE; + } + // Verify Heparin stop time does not exceed Tx duration + else if ( hepST > txDur ) + { + reasons[ TREATMENT_PARAM_HEPARIN_STOP_TIME ] = REQUEST_REJECT_REASON_HEPARIN_STOP_TIME_EXCEEDS_DURATION; + result = FALSE; + } + // Verify Heparin stop time does not exceed max 10 mL Heparin volume + else if ( txVol > MAX_HEPARIN_VOLUME_ML ) + { + reasons[ TREATMENT_PARAM_HEPARIN_STOP_TIME ] = REQUEST_REJECT_REASON_HEPARIN_VOLUME_EXCEEDS_10_ML; + result = FALSE; + } + + return result; +} + +/*********************************************************************//** + * @brief * The validateAndSetUFVolume function validates received ultrafiltration * volume treatment parameter. * @details \b Message \b Sent: MSG_ID_TD_RESP_ULTRAFILTRATION_VOLUME_TO_VALIDATE @@ -632,11 +705,12 @@ BOOL signalUserConfirmationOfTreatmentParameters( MESSAGE_T *message ) { BOOL result = FALSE; - BOOL confirmed = FALSE; // Verify message payload length is valid if ( sizeof( BOOL ) == message->hdr.payloadLen ) { + BOOL confirmed = FALSE; + memcpy( &confirmed, message->payload, sizeof( BOOL ) ); if ( ( MODE_TPAR == getCurrentOperationMode() ) && ( TD_TREATMENT_PARAMS_MODE_STATE_WAIT_4_UI_2_CONFIRM == currentTreatmentParamsState ) ) { @@ -789,8 +863,7 @@ setPressureLimitsToOuterBounds(); // Go to pre-treatment mode -// requestNewOperationMode( MODE_PRET ); // TODO - restore when pre-tx mode implemented - requestNewOperationMode( MODE_TREA ); + requestNewOperationMode( MODE_PRET ); treatParamsConfirmed = FALSE; } @@ -850,6 +923,58 @@ /*********************************************************************//** * @brief + * The getDialyzerBloodVolume function gets the blood side volume (in mL) + * for a given dialyzer type. + * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if given dialyzer type is invalid + * @details \b Inputs : dialyzerVolumeTable[] + * @details \b Outputs : none + * @param dialyzer The type of dialyzer to get the blood side volume for + * @return The blood side volume for the given dialyzer type + *************************************************************************/ +U32 getDialyzerBloodVolume( DIALYZER_TYPE_T dialyzer ) +{ + U32 result = 0; + + if ( dialyzer < NUM_OF_DIALYZER_TYPES ) + { + result = dialyzerVolumeTable[ dialyzer ].bloodVolume; + } + else + { + SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_DIALYZER_TYPE_INVALID1, (U32)dialyzer ) + } + + return result; +} + +/*********************************************************************//** + * @brief + * The getDialyzerDialysateVolume function gets the dialysate side volume (in mL) + * for a given dialyzer type. + * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if given dialyzer type is invalid + * @details \b Inputs : dialyzerVolumeTable[] + * @details \b Outputs : none + * @param dialyzer The type of dialyzer to get the dialysate side volume for + * @return The dialysate side volume for the given dialyzer type + *************************************************************************/ +U32 getDialyzerDialysateVolume( DIALYZER_TYPE_T dialyzer ) +{ + U32 result = 0; + + if ( dialyzer < NUM_OF_DIALYZER_TYPES ) + { + result = dialyzerVolumeTable[ dialyzer ].dialysateVolume; + } + else + { + SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_DIALYZER_TYPE_INVALID2, (U32)dialyzer ) + } + + return result; +} + +/*********************************************************************//** + * @brief * The isTreatmentParamInRange function determines whether a given treatment * parameter is in range. * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if given param is invalid @@ -1180,4 +1305,88 @@ return value; } + +/************************************************************************* + * TEST SUPPORT FUNCTIONS + *************************************************************************/ + + +/*********************************************************************//** + * @brief + * The testSetTreatmentParameter function sets a given treatment parameter + * to a given value. + * @details \b Inputs: none + * @details \b Outputs: treatmentParameters[] + * @param message Set treatment parameter message from Dialin which includes + * the ID of the treatment parameter to be set and the value to set it to. + * @return TRUE if set request is successful, FALSE if not + *************************************************************************/ +BOOL testSetTreatmentParameter( MESSAGE_T *message ) +{ + BOOL result = FALSE; + + // Verify payload length is valid + if ( message->hdr.payloadLen == sizeof( DIALIN_SET_TX_PARAM_PAYLOAD_T ) ) + { + DIALIN_SET_TX_PARAM_PAYLOAD_T payload; + + memcpy( (U08*)&payload, &message->payload[0], sizeof( DIALIN_SET_TX_PARAM_PAYLOAD_T ) ); + + // Verify treatment parameter ID is valid + if ( (TREATMENT_PARAM_T)payload.paramID < NUM_OF_TREATMENT_PARAMS ) + { + // Set the given Tx param to the given value + if ( (TREATMENT_PARAM_T)payload.paramID <= TREATMENT_PARAM_LAST_UINT ) + { + result = setTreatmentParameterU32( (TREATMENT_PARAM_T)payload.paramID, payload.value.uInt ); + } + else if ( (TREATMENT_PARAM_T)payload.paramID >= TREATMENT_PARAM_FIRST_F32 ) + { + result = setTreatmentParameterF32( (TREATMENT_PARAM_T)payload.paramID, payload.value.sFlt ); + } + else + { + result = setTreatmentParameterS32( (TREATMENT_PARAM_T)payload.paramID, payload.value.sInt ); + } + } + } + + return result; +} + +/*********************************************************************//** + * @brief + * The testTxParamsRequest function publishes the complete set of treatment + * parameters. + * @details \b Message \b Sent: MSG_ID_TD_RSP_CURRENT_TREATMENT_PARAMETERS + * @details \b Inputs: treatmentParameters[] + * @details \b Outputs: none + * @param message The message from Dialin requesting that treatment parameters + * be published. + * @return TRUE if publish treatment parameters request is successful, FALSE if not + *************************************************************************/ +BOOL testTxParamsRequest( MESSAGE_T *message ) +{ + BOOL result = FALSE; + + // Verify payload length + if ( 0 == message->hdr.payloadLen ) + { + CURRENT_TREATMENT_PARAMS_DATA_PAYLOAD_T payload; + TREATMENT_PARAM_T param; + + // Build and publish current treatment parameters record + for ( param = (TREATMENT_PARAM_T)0; param < NUM_OF_TREATMENT_PARAMS; param++ ) + { + stagedParams[ param ] = treatmentParameters[ param ].data; + } + memcpy( (U08*)&payload, (U08*)&stagedParams[0], sizeof( CURRENT_TREATMENT_PARAMS_DATA_PAYLOAD_T ) ); + sendMessage( MSG_ID_TD_RSP_CURRENT_TREATMENT_PARAMETERS, COMM_BUFFER_OUT_CAN_PC, (U08*)&payload, sizeof( CURRENT_TREATMENT_PARAMS_DATA_PAYLOAD_T ) ); + + result = TRUE; + } + + return result; +} + /**@}*/