/************************************************************************** * * Copyright (c) 2024-2024 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 DDInterface.c * * @author (last) Sean * @date (last) 30-Jul-2024 * * @author (original) Sean * @date (original) 30-Jul-2024 * ***************************************************************************/ #include "DDInterface.h" #include "MessagePayloads.h" #include "Messaging.h" #include "ModeInitPOST.h" #include "OperationModes.h" #include "PersistentAlarm.h" #include "SystemCommTD.h" #include "TaskGeneral.h" #include "Timers.h" /** * @addtogroup DDInterface * @{ */ // ********** private definitions ********** #define DD_DATA_FRESHNESS_TIMEOUT_MS ( 3 * MS_PER_SECOND ) ///< DD data freshness timeout (in ms). // ********** private data ********** // DD status static DD_OP_MODE_T ddCurrentOpMode; ///< Current DD operation mode. static U32 ddSubMode; ///< Current state (sub-mode) of current DD operation mode. static F32 dialysatePressure; ///< Current dialysate pressure reported by DD. static BOOL ddStartCommandSent; ///< Flag indicates command to start DD has been sent. static BOOL ddStarted; ///< Flag indicates whether we have commanded the DD to start or stop. static BOOL ddOpModeDataFreshFlag; ///< Flag to signal the handleDDOpMode() to process fresh dd op mode data. static BOOL ddDialysatePressureFreshFlag; ///< Flag to signal static DIALYSATE_DELIVERY_REQ_PAYLOAD_T dialysateDeliveryCmdSet; ///< Set of dialysate delivery parameters to send to the DD during treatment. // DD command response static DD_CMD_RESPONSE_T ddCmdResp[ NUM_OF_DD_COMMANDS ]; ///< Keep the latest DD command response for each command. // ********** private function prototypes ********** static void checkDDRestart( void ); static void checkDDDataFreshness( ALARM_ID_T alarmID, BOOL *ddFreshDataFlag ); /*********************************************************************//** * @brief * The initDDInterface function initializes the DDInterface unit. * @details \b Inputs: none * @details \b Outputs: DDInterface unit initialized. * @return none *************************************************************************/ void initDDInterface( void ) { U32 i; ddCurrentOpMode = DD_MODE_INIT; ddSubMode = 0; dialysatePressure = 0.0F; ddOpModeDataFreshFlag = FALSE; ddDialysatePressureFreshFlag = FALSE; resetDDInterface(); // initialize DD command response for ( i = 0; i < NUM_OF_DD_COMMANDS; i++ ) { ddCmdResp[ i ].commandID = DD_CMD_NONE; ddCmdResp[ i ].rejected = TRUE; ddCmdResp[ i ].rejectCode = DD_CMD_REQUEST_REJECT_REASON_NONE; } // initPersistentAlarm( ALARM_ID_TD_DD_NEW_OPERATION_MODE_MESSAGE_NOT_RECEIVE, DD_DATA_FRESHNESS_TIMEOUT_MS, DD_DATA_FRESHNESS_TIMEOUT_MS ); } /*********************************************************************//** * @brief * The resetDDInterface function resets the DDInterface unit before starting * a new treatment work flow. * @details \b Inputs: none * @details \b Outputs: DDInterface unit reset. * @return none *************************************************************************/ void resetDDInterface( void ) { ddStarted = FALSE; ddStartCommandSent = FALSE; dialysateDeliveryCmdSet.start = FALSE; dialysateDeliveryCmdSet.dialRate = 0.0F; dialysateDeliveryCmdSet.ufRate = 0.0F; dialysateDeliveryCmdSet.dialTemp = 0.0F; dialysateDeliveryCmdSet.bypassDialyzer = TRUE; dialysateDeliveryCmdSet.acidType = 0; dialysateDeliveryCmdSet.bicarbType = 0; } /**********************************************************************//** * @brief * The checkDDDataFreshness function checks the freshness of data coming from * the DD sub-system. * @details \b Alarm: Given alarm is triggered if DD is communicating but has * not published new data for too long. * @details \b Inputs: DD communicating flag * @details \b Outputs: none * @param alarm ID of alarm to check * @param ddFreshDataFlag Pointer to flag indicating whether new data has been * received since last time this function has seen it. * @return None *************************************************************************/ static void checkDDDataFreshness( ALARM_ID_T alarmID, BOOL *ddFreshDataFlag ) { if ( TRUE == *ddFreshDataFlag ) { *ddFreshDataFlag = FALSE; checkPersistentAlarm( alarmID, FALSE, 0.0, 0.0 ); } else { // Alarm if not receiving DD fresh data message in timely manner if ( TRUE == isDDCommunicating() ) { checkPersistentAlarm( alarmID, TRUE, 0.0, 0.0 ); } else { checkPersistentAlarm( alarmID, FALSE, 0.0, 0.0 ); } } } /*********************************************************************//** * @brief * The execDDInterfaceMonitor function executes the DD Interface monitoring * function. Ensures DD is sending fresh data in a timely manner. * @details \b Inputs: ddOpModeDataFreshFlag * @details \b Outputs: ddOpModeDataFreshFlag * @return none *************************************************************************/ void execDDInterfaceMonitor( void ) { // if ( getCPLDACPowerLossDetected() != TRUE ) // { // Trigger alarm if not receiving new DD op mode message in timely manner // checkDDDataFreshness( ALARM_ID_TD_DD_NEW_OPERATION_MODE_MESSAGE_NOT_RECEIVE, &ddOpModeDataFreshFlag ); // if ( TRUE == isAlarmActive( ALARM_ID_TD_DD_NEW_OPERATION_MODE_MESSAGE_NOT_RECEIVE ) ) // { // // we don't want to keep thinking DD is in a useful mode - set it to fault mode until DD is able to report its' true mode status // setDDOpMode( DD_MODE_FAUL, 0 ); // } // Check to see if DD has restarted // checkDDRestart(); // } } /*********************************************************************//** * @brief * The getDDOpMode function gets the current latest reported DD operating mode. * @details \b Inputs: ddCurrentOpMode * @details \b Outputs: none * @return Latest reported DD operating mode. *************************************************************************/ DD_OP_MODE_T getDDOpMode( void ) { return ddCurrentOpMode; } /*********************************************************************//** * @brief * The getDDSubMode function gets the latest reported DD operating sub-mode. * @details \b Inputs: ddSubMode * @details \b Outputs: none * @return Latest reported DD operating sub-mode. *************************************************************************/ U32 getDDSubMode( void ) { return ddSubMode; } /*********************************************************************//** * @brief * The setDDOpMode function sets the latest DD operating mode reported by * the DD (called by DD published message handler). * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if reported DD mode is invalid. * @details \b Inputs: none * @details \b Outputs: ddCurrentOpMode, ddSubMode, ddOpModeDataFreshFlag * @param message Pointer to the DD operation mode broadcast message * @return TRUE if message handled successfully, FALSE if not *************************************************************************/ BOOL setDDOpMode( MESSAGE_T *message ) { BOOL result = FALSE; U32 opMode, subMode; // parse message payload memcpy( &opMode, &message->payload[ 0 ], sizeof( U32 ) ); memcpy( &subMode, &message->payload[ sizeof( U32 ) ], sizeof( U32 ) ); if ( ( message->hdr.payloadLen == sizeof( OP_MODE_PAYLOAD_T ) ) && ( opMode < NUM_OF_DD_MODES ) ) { // reset POST passed flag if DD restarted or faulted or went to service mode if ( ( opMode < DD_MODE_STAN ) && ( ddCurrentOpMode >= DD_MODE_STAN ) ) { signalDDPOSTFinalResult( FALSE ); } // update DD op mode and sub-mode ddCurrentOpMode = (DD_OP_MODE_T)opMode; ddSubMode = subMode; result = TRUE; } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_INVALID_DD_OPERATING_MODE, opMode ); } ddOpModeDataFreshFlag = TRUE; return result; } /*********************************************************************//** * @brief * The getDialysatePressure function gets the latest reported dialysate * pressure. * @details \b Inputs: dialysatePressure * @details \b Outputs: none * @return Latest reported dialysate pressure. *************************************************************************/ F32 getDialysatePressure( void ) { return dialysatePressure; } /*********************************************************************//** * @brief * The setDialysatePressure function sets the latest dialysate pressure * reported by the DD sub-system. * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if message invalid. * @details \b Inputs: none * @details \b Outputs: dialysatePressure, ddDialysatePressureFreshFlag * @param message Pointer to the DD pressure data broadcast message * @return TRUE if message handled successfully, FALSE if not *************************************************************************/ BOOL setDialysatePressure( MESSAGE_T *message ) { BOOL result = FALSE; // PRESSURE_DD_DATA_T payload; // TODO - restore when payload type moved to DDDefs.h. Also, do we need to handle FP pressures as well? // // // parse message payload // memcpy( &payload, &message->payload[ 0 ], sizeof( PRESSURE_DD_DATA_T ) ); // // if ( message->hdr.payloadLen == sizeof( PRESSURE_DD_DATA_T ) ) // { // // update dialysate pressure // dialysatePressure = payload.d41Pressure; // result = TRUE; // } // else // { // SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_INVALID_DD_PRESSURE_DATA, (U32)message->hdr.payloadLen ); // } // // ddDialysatePressureFreshFlag = TRUE; return result; } /*********************************************************************//** * @brief * The cmdStartGenerateDialysate function sends a generate dialysate command * to the DD with a given set of details. DD will transition to dialysate * delivery mode if it hasn't already. * @details \b Inputs: none * @details \b Outputs: dialysateDeliveryCmdSet * @details \b Message \b Sent: Start/continue generate dialysate command. * @param qd Target dialysate flow rate (Qd). * @param quf Target ultrafiltration rate (Quf). * @param dialTemp Target dialysate temperature in deg C. * @param bypass Flag indicating whether dialyzer should be bypassed. * @param acid Type of acid concentrate used. * @param bicarb Type of bicarbonate concentrate used. * @return none *************************************************************************/ void cmdStartGenerateDialysate( F32 qd, F32 quf, F32 dialTemp, BOOL bypass, ACID_CONCENTRATE_TYPE_T acid, BICARB_CONCENTRATE_TYPE_T bicarb ) { dialysateDeliveryCmdSet.start = TRUE; dialysateDeliveryCmdSet.dialRate = qd; dialysateDeliveryCmdSet.ufRate = quf; dialysateDeliveryCmdSet.dialTemp = dialTemp; dialysateDeliveryCmdSet.bypassDialyzer = bypass; dialysateDeliveryCmdSet.acidType = (U32)acid; dialysateDeliveryCmdSet.bicarbType = (U32)bicarb; #ifndef TEST_UI_ONLY sendMessage( MSG_ID_DD_GEN_DIALYSATE_REQUEST_DATA, COMM_BUFFER_OUT_CAN_TD_2_DD, (U08*)(&dialysateDeliveryCmdSet), sizeof( DIALYSATE_DELIVERY_REQ_PAYLOAD_T ) ); #endif ddStartCommandSent = TRUE; } /*********************************************************************//** * @brief * The cmdChangeQd function sends a generate dialysate command to the DD * with a given new dialysate rate. * @details \b Inputs: none * @details \b Outputs: dialysateDeliveryCmdSet * @details \b Message \b Sent: Continue generate dialysate command w/ new Qd. * @param qd Target dialysate flow rate (Qd). * @return none *************************************************************************/ void cmdChangeQd( F32 qd ) { if ( TRUE == dialysateDeliveryCmdSet.start ) { dialysateDeliveryCmdSet.dialRate = qd; #ifndef TEST_UI_ONLY sendMessage( MSG_ID_DD_GEN_DIALYSATE_REQUEST_DATA, COMM_BUFFER_OUT_CAN_TD_2_DD, (U08*)(&dialysateDeliveryCmdSet), sizeof( DIALYSATE_DELIVERY_REQ_PAYLOAD_T ) ); #endif } else { // TODO - s/w fault? } } /*********************************************************************//** * @brief * The cmdChangeQuf function sends a generate dialysate command to the DD * with a given new ultrafiltration rate. * @details \b Inputs: none * @details \b Outputs: dialysateDeliveryCmdSet * @details \b Message \b Sent: Continue generate dialysate command w/ new Quf. * @param quf Target ultrafiltration flow rate (in L/hr). * @return none *************************************************************************/ void cmdChangeQuf( F32 quf ) { if ( TRUE == dialysateDeliveryCmdSet.start ) { dialysateDeliveryCmdSet.ufRate = quf; #ifndef TEST_UI_ONLY sendMessage( MSG_ID_DD_GEN_DIALYSATE_REQUEST_DATA, COMM_BUFFER_OUT_CAN_TD_2_DD, (U08*)(&dialysateDeliveryCmdSet), sizeof( DIALYSATE_DELIVERY_REQ_PAYLOAD_T ) ); #endif } else { // TODO - s/w fault? } } /*********************************************************************//** * @brief * The cmdBypassDialyzer function sends a generate dialysate command to the DD * with a given flag indicating whether dialyzer should be bypassed. * @details \b Inputs: none * @details \b Outputs: dialysateDeliveryCmdSet * @details \b Message \b Sent: Continue generate dialysate command w/ new * dialyzer bypass flag. * @param bypass Flag indicating whether dialyzer should be bypassed. * @return none *************************************************************************/ void cmdBypassDialyzer( BOOL bypass ) { if ( TRUE == dialysateDeliveryCmdSet.start ) { dialysateDeliveryCmdSet.bypassDialyzer = bypass; #ifndef TEST_UI_ONLY sendMessage( MSG_ID_DD_GEN_DIALYSATE_REQUEST_DATA, COMM_BUFFER_OUT_CAN_TD_2_DD, (U08*)(&dialysateDeliveryCmdSet), sizeof( DIALYSATE_DELIVERY_REQ_PAYLOAD_T ) ); #endif } else { // TODO - s/w fault? } } /*********************************************************************//** * @brief * The cmdStopGenerateDialysate function sends a stop generate dialysate command * to the DD. DD will transition back to standby mode. * @details \b Inputs: none * @details \b Outputs: dialysateDeliveryCmdSet * @details \b Message \b Sent: Stop generate dialysate command. * @return none *************************************************************************/ void cmdStopGenerateDialysate( void ) { memset( &dialysateDeliveryCmdSet, 0, sizeof( DIALYSATE_DELIVERY_REQ_PAYLOAD_T ) ); dialysateDeliveryCmdSet.start = FALSE; #ifndef TEST_UI_ONLY sendMessage( MSG_ID_DD_GEN_DIALYSATE_REQUEST_DATA, COMM_BUFFER_OUT_CAN_TD_2_DD, (U08*)(&dialysateDeliveryCmdSet), sizeof( DIALYSATE_DELIVERY_REQ_PAYLOAD_T ) ); #endif } /*********************************************************************//** * @brief * The handleDDCommandResponse function processes the latest DD command response. * @details \b Alarm: ALARM_ID_TD_DD_COMMAND_INVALID_PARAMETER_FAULT if reported * response indicates DD rejected command due to invalid parameter. * @details \b Alarm: ALARM_ID_TD_SOFTWARE_FAULT if reported response is to an * invalid command. * @details \b Inputs: none * @details \b Outputs: ddCmdResp[] * @param ddCmdRespPtr Pointer to the received DD command response * @return none *************************************************************************/ void handleDDCommandResponse( DD_CMD_RESPONSE_T *ddCmdRespPtr ) { if ( ddCmdRespPtr->commandID < NUM_OF_DD_COMMANDS ) { if ( DD_CMD_NONE != ddCmdRespPtr->commandID ) { memcpy( &ddCmdResp[ ddCmdRespPtr->commandID ], ddCmdRespPtr, sizeof( DD_CMD_RESPONSE_T ) ); } } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_DD_INVALID_COMMAND_RESPONSE_ID, ddCmdRespPtr->commandID ); } if ( TRUE == ddCmdRespPtr->rejected ) { switch ( ddCmdRespPtr->rejectCode ) { case DD_CMD_REQUEST_REJECT_REASON_INVALID_PARAMETER: // SET_ALARM_WITH_1_U32_DATA( ALARM_ID_TD_DD_COMMAND_INVALID_PARAMETER_FAULT, ddCmdRespPtr->commandID ); break; case DD_CMD_REQUEST_REJECT_REASON_NONE: case DD_CMD_REQUEST_REJECT_REASON_INVALID_MODE: default: // Our state machines will detect and handle DD mode out of sync break; } } } /*********************************************************************//** * @brief * The getDDCommandResponse function gets the latest command response from DD. * @details \b Inputs: ddCmdResp[] * @details \b Outputs: cmdRespPtr reference variable is set to the received * command response. * @param commandID ID of specific command that caller wants a response to * @param cmdRespPtr Pointer to command response record to populate with received * response * @return TRUE if a response was received for the given command, FALSE if not. *************************************************************************/ BOOL getDDCommandResponse( U32 commandID, DD_CMD_RESPONSE_T *cmdRespPtr ) { BOOL hasCommandResp = FALSE; if ( commandID == ddCmdResp[ commandID ].commandID ) { hasCommandResp = TRUE; memcpy( cmdRespPtr, &ddCmdResp[ commandID ], sizeof( DD_CMD_RESPONSE_T ) ); } return hasCommandResp; } /*********************************************************************//** * @brief * The checkDDRestart function checks to see if the DD sub-system has restarted * after an operation was started by TD. * @details \b Alarm: ALARM_ID_TD_DD_RESTARTED_FAULT if DD has restarted unexpectedly. * @details \b Inputs: ddStartCommandSent, ddStarted, ddCurrentOpMode * @details \b Outputs: ddStartCommandSent, ddStarted * @return none *************************************************************************/ static void checkDDRestart( void ) { if ( ( ddStartCommandSent == TRUE ) && ( DD_MODE_GEND == ddCurrentOpMode ) ) { ddStartCommandSent = FALSE; ddStarted = TRUE; } // if ( TRUE == ddStarted ) // { // if ( ( DD_MODE_FAUL != ddCurrentOpMode ) && ( DD_MODE_GEND != ddCurrentOpMode ) && // ( DD_MODE_FILL != ddCurrentOpMode ) && ( DD_MODE_DRAI != ddCurrentOpMode ) ) // { // activateAlarmNoData( ALARM_ID_TD_DD_RESTARTED_FAULT ); // ddStarted = FALSE; // do not want to re-trigger alarm after alarm is cleared // } // } } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /**@}*/