/************************************************************************** * * Copyright (c) 2020-2025 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 ConductivitySensors.c * * @author (last) Dara Navaei * @date (last) 28-May-2025 * * @author (original) Quang Nguyen * @date (original) 13-Jul-2020 * ***************************************************************************/ #include // Used for calculating the polynomial calibration equation. #include // For memcpy #include "ConductivitySensors.h" #include "FPGA.h" #include "NVDataMgmt.h" #include "MessageSupport.h" #include "ModeFill.h" #include "OperationModes.h" #include "PersistentAlarm.h" #include "Reservoirs.h" #include "SystemCommMessages.h" #include "TaskGeneral.h" #include "TaskPriority.h" #include "TemperatureSensors.h" #include "Utilities.h" #include "Valves.h" /** * @addtogroup ConductivitySensors * @{ */ // ********** private definitions ********** #define SIEMENS_TO_MICROSIEMENS_CONVERSION 1000000 ///< Siemens to microSiemens conversion factor. #define COND_CPI_SENSOR_PROBE_TYPE 100 ///< 1 K cell constant conductivity probe. #define COND_CPO_SENSOR_PROBE_TYPE 10 ///< 0.1 K cell constant conductivity probe. #define COND_SENSOR_DECIMAL_CONVERSION 100.0F ///< Conductivity value from FPGA has two decimal place. #define COND_SENSOR_REFERENCE_TEMPERATURE 25.0F ///< Reference temperature for conductivity sensor. #define COND_SENSOR_REPORT_PERIOD ( MS_PER_SECOND / TASK_PRIORITY_INTERVAL ) ///< Broadcast conductivity values message every second. #define MAX_COND_SENSOR_CPI_WARNING_HIGH_US_PER_CM 2000.0F ///< Maximum allowed high conductivity value in uS/cm. #define MIN_COND_SENSOR_CPI_WARNING_HIGH_US_PER_CM 1990.0F ///< Minimum allowed high conductivity value in uS/cm. #define MIN_CPI_INLET_ALARM_RECOVERY_OFFSET_US_PER_CM 10.0F ///< Minimum inlet water conductivity recovery offset value in uS/cm. #define MAX_RO_ONLY_COND_SENSOR_CPI_HIGH_US_PER_CM 100.0F ///< Maximum RO only mode high conductivity value in uS/cm. #define MIN_RO_ONLY_COND_SENSOR_CPI_HIGH_US_PER_CM 90.0F ///< Minimum RO only mode high conductivity value in uS/cm. #define MAX_ALLOWED_UNCHANGED_CONDUCTIVITY_READS ( MS_PER_SECOND / TASK_PRIORITY_INTERVAL ) ///< New reading every 800 ms, expect to get valid new reading in 1s. #define MAX_CONDUCTIVITY_SENSOR_FAILURES 2 ///< maximum number of conductivity sensor errors within window period before alarm. #define MAX_CONDUCTIVITY_SENSOR_FAILURE_WINDOW_MS ( 60 * MS_PER_SECOND ) ///< Conductivity sensor error window. #define INLET_WATER_COND_SENSOR_OUT_OF_RANGE_TIMEOUT_MS ( 30 * MS_PER_SECOND ) ///< Inlet water conductivity sensor out of range timeout in milliseconds. #define INLET_WATER_COND_SENSOR_OUT_OF_RANGE_CLEAR_MS ( 1 * MS_PER_SECOND ) ///< Inlet water conductivity sensor out of range clear in milliseconds. #define EMSTAT_PICO_MEASUREMENT_OFFSET 0x8000000 ///< Emstat measurement offset. // The below Emstat status values come from the MethodScript-v1_2-1.pdf. See page 10 table 4 for further information // The first hex value is the status ID meaning that all of these are the status of the sensor. The second hex value is the content. #define EMSTAT_PICO_STATUS_OK 0x10 ///< Emstat measurement good status. #define EMSTAT_PICO_STATUS_TIMING_NOT_MET 0x11 ///< Emstat measurement takes too long status. #define EMSTAT_PICO_STATUS_95_PCT_OF_MAX_ADC 0x12 ///< Emstat measurement overload, >95% of max ADC value status. #define EMSTAT_PICO_STATUS_2_PCT_OF_MAX_ADC 0x14 ///< Emstat measurement underload, <2% of max ADC value status. #define EMSTAT_PICO_STATUS_80_PCT_OF_MAX_ADC 0x18 ///< Emstat measurement overload warning, >80% of max ADC value status. #define EMSTAT_PICO_FIFO_EMPTY_MASK 0x8000 ///< Emstat buffer empty indication bit. #define EMSTAT_NUM_OF_SENSORS_PER_BOARD 2 ///< Emstat Pico number of sensors per board. #define EMSTAT_CPI_OR_CD1_INDEX 0 ///< Emstat board CPi index number. #define EMSTAT_CPO_OR_CD2_INDEX 1 ///< Emstat board CPo index number. #define EMSTAT_PACKAGE_BUFFER_SIZE 50 ///< EmStat package buffer size #define EMSTAT_RX_FIFO_COUNT_MASK 0x7FFF ///< EmStat Rx fifo count mask #define DATA_PUBLISH_COUNTER_START_COUNT 40 ///< Data publish counter start count. #define COND_SENSOR_BAD_STATUS_PERSISTENCE_PERIOD ( 1 * MS_PER_SECOND ) ///< Conductivity sensor bad status persistence period. #define COND_SENSORS_FPGA_ERROR_TIMEOUT_MS ( 2 * MS_PER_SECOND ) ///< Conductivity sensors FPGA error timeout in milliseconds. #define COND_SENSORS_BAD_CHAR_TIME_OUT_MS ( 2 * MS_PER_SECOND ) ///< Conductivity sensor bad received character timeout in milliseconds. #define MIN_CPI_CPO_COND_VALUES_AFTER_CALS_US_PER_CM 20.0F ///< Minimum allowed CPi and CPo conductivity values after calculations in uS/cm. #pragma pack(push,1) /// Emstat pico measurement data package structure typedef struct { U16 type; ///< Measurement variable type. U08 value[7]; ///< Measurement value. U08 prefix; ///< Prefix character for SI prefixes. U08 reserved1; ///< Comma separator. U16 status; ///< Status for measurement data package. U08 reserved2[4]; ///< Comma separator and index of current range. } EMSTAT_VARIABLE_T; /// Emstat conductivity sensor and its corresponding temperature sensor typedef struct { CONDUCTIVITY_SENSORS_T condSnsr; ///< Emstat conductivity sensor on the board. TEMPERATURE_SENSORS_T condSnsrTempSnsr; ///< Emstat conductivity sensor temperature sensor. ALARM_ID_T condSnsrHex2StrAlarm; ///< Emstat conductivity sensor hex to string alarm. } EMSTAT_COND_SENSORS_T; /// Emstat board structure typedef struct { BOOL packageStarted; ///< Emstat package started flag. U08 packageIndex; ///< Emstat package index number. U08 package[ EMSTAT_PACKAGE_BUFFER_SIZE ]; ///< Emstat read buffer package. EMSTAT_COND_SENSORS_T sensors[ EMSTAT_NUM_OF_SENSORS_PER_BOARD ]; ///< Emstat conductivity and corresponding temperature sensors. } EMSTAT_READ_T; /// Conductivity sensors structure typedef struct { U08 readCount; ///< Conductivity sensor FPGA read count (For V3). U32 internalErrorCount; ///< Conductivity sensor internal error count. U32 badCharErrorCount; ///< Conductivity sensor bad char error count OVERRIDE_F32_T compensatedCondValue; ///< Conductivity sensor compensated value F32 rawCondValue; ///< Conductivity sensor raw value. U32 sensorStatus; ///< Conductivity sensor status. U32 rawEmstatCondValue; ///< Conductivity sensor from Emstat. } COND_SENSOR_STATUS_T; #pragma pack(pop) // ********** private data ********** /// Emstat boards for CPi/CPo and CD1/CD2 typedef enum EmstatBoards { EMSTAT_CPI_CPO_BOARD = 0, ///< Emstat CPi/CPo board. EMSTAT_CD1_CD2_BOARD, ///< Emstat CD1/CD2 board. NUM_OF_EMSTAT_BOARDS ///< Number of Emstat boards. } EMSTAT_BOARD_T; static OVERRIDE_U32_T conductivityDataPublishInterval = { COND_SENSOR_REPORT_PERIOD, COND_SENSOR_REPORT_PERIOD, 0, 0 }; ///< Conductivity sensors publish time interval override. static F32 roRejectionRatio; ///< Latest RO rejection ratio. static U32 condDataPublishCounter; ///< Conductivity sensors data publish timer counter. static EMSTAT_READ_T emstatBoardRead[ NUM_OF_EMSTAT_BOARDS ]; ///< EMSTAT board read. static COND_SENSOR_STATUS_T condSensorStatus[ NUM_OF_CONDUCTIVITY_SENSORS ]; ///< Conductivity sensors status. static DG_COND_SENSORS_CAL_RECORD_T condSensorsCalRecord; ///< Conductivity sensors' calibration record. static DG_COND_SENSORS_TEMP_COMP_CAL_RECORD_T condSensorsTempCompCalRecord; ///< Conductivity sensors' temperature compensation calibration record. static CAL_DATA_DG_COND_SENSORS_T condSensorCalTable[ NUM_OF_CONDUCTIVITY_SENSORS ]; ///< Conductivity sensors calibration table. static F32 minInletWaterCondAlarmLimitUSPCM; ///< Min inlet water conductivity alarm limit in uS/cm. // ********** private function prototypes ********** static F32 calcCompensatedConductivity( U32 sensorID, F32 conductivity, F32 temperature ); static void calcRORejectionRatio( void ); static void processCPiCPoSensorRead( U32 sensorId, U08 emstatBoardSensorIndex, U32 fgpaRead, U08 fpgaReadCount, U08 fpgaErrorCount, U08 fpgaSensorFault ); static U32 prefixStrToSIFactor( U08 prefix ); static void processEmstatBoard( EMSTAT_BOARD_T board ); static void processEmstatSensorRead( EMSTAT_READ_T* readPackage, U08 emstatByte ); static void processEmstatMeasurementDataPackets( U08 boardSensorIndex, EMSTAT_READ_T* readPackage, EMSTAT_VARIABLE_T* receivedPackets ); static F32 getCalibrationAppliedConductivityValue( U32 sensorId, F32 compensatedValue ); /*********************************************************************//** * @brief * The initConductivitySensors function initializes the ConductivitySensors module. * @details Inputs: none * @details Outputs: ConductivitySensors module initialized * @return none *************************************************************************/ void initConductivitySensors( void ) { U08 i; roRejectionRatio = 0.0F; condDataPublishCounter = DATA_PUBLISH_COUNTER_START_COUNT; minInletWaterCondAlarmLimitUSPCM = 0.0F; for ( i = 0; i < NUM_OF_CONDUCTIVITY_SENSORS; i++ ) { memset( &condSensorStatus[ i ], 0x0, sizeof( COND_SENSOR_STATUS_T ) ); benignPolynomialCalRecord( &condSensorsCalRecord.condSensors[ i ] ); switch( i ) { case CONDUCTIVITYSENSORS_CPI_SENSOR: condSensorCalTable[ i ] = CAL_DATA_CPI_COND_SENSOR; break; case CONDUCTIVITYSENSORS_CPO_SENSOR: condSensorCalTable[ i ] = CAL_DATA_CPO_COND_SENSOR; break; case CONDUCTIVITYSENSORS_CD1_SENSOR: condSensorCalTable[ i ] = CAL_DATA_CD1_COND_SENSOR; break; case CONDUCTIVITYSENSORS_CD2_SENSOR: condSensorCalTable[ i ] = CAL_DATA_CD2_COND_SENSOR; break; #ifndef _VECTORCAST_ default: // This switch is in a for loop so VectorCAST cannot reach to default. SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, SW_FAULT_ID_INVALID_CONDUCTIVITY_SENSOR_ID, i ) break; #endif } } // Reset all the read packages of the Emstat boards for ( i = 0; i < NUM_OF_EMSTAT_BOARDS; i++ ) { memset( &emstatBoardRead[ i ], 0x0, sizeof( EMSTAT_BOARD_T ) ); } // Each Emstat board covers two conductivity sensors // CPi/CPo Emstat board conductivity sensors and their corresponding temperature sensors emstatBoardRead[ EMSTAT_CPI_CPO_BOARD ].sensors[ EMSTAT_CPI_OR_CD1_INDEX ].condSnsr = CONDUCTIVITYSENSORS_CPI_SENSOR; emstatBoardRead[ EMSTAT_CPI_CPO_BOARD ].sensors[ EMSTAT_CPI_OR_CD1_INDEX ].condSnsrTempSnsr = TEMPSENSORS_INLET_PRIMARY_HEATER; emstatBoardRead[ EMSTAT_CPI_CPO_BOARD ].sensors[ EMSTAT_CPI_OR_CD1_INDEX ].condSnsrHex2StrAlarm = ALARM_ID_DG_CPI_COND_SENSOR_INVALID_CHAR; emstatBoardRead[ EMSTAT_CPI_CPO_BOARD ].sensors[ EMSTAT_CPO_OR_CD2_INDEX ].condSnsr = CONDUCTIVITYSENSORS_CPO_SENSOR; emstatBoardRead[ EMSTAT_CPI_CPO_BOARD ].sensors[ EMSTAT_CPO_OR_CD2_INDEX ].condSnsrTempSnsr = TEMPSENSORS_OUTLET_PRIMARY_HEATER; emstatBoardRead[ EMSTAT_CPI_CPO_BOARD ].sensors[ EMSTAT_CPO_OR_CD2_INDEX ].condSnsrHex2StrAlarm = ALARM_ID_DG_CPO_COND_SENSOR_INVALID_CHAR; // CD1/CD2 Emstat board conductivity sensors and their corresponding temperature sensors // NOTE: For CD1, use TD2 temperature sensor for compensation since it is a more accurate temperature sensor emstatBoardRead[ EMSTAT_CD1_CD2_BOARD ].sensors[ EMSTAT_CPI_OR_CD1_INDEX ].condSnsr = CONDUCTIVITYSENSORS_CD1_SENSOR; emstatBoardRead[ EMSTAT_CD1_CD2_BOARD ].sensors[ EMSTAT_CPI_OR_CD1_INDEX ].condSnsrTempSnsr = TEMPSENSORS_CONDUCTIVITY_SENSOR_2; emstatBoardRead[ EMSTAT_CD1_CD2_BOARD ].sensors[ EMSTAT_CPI_OR_CD1_INDEX ].condSnsrHex2StrAlarm = ALARM_ID_DG_CD1_COND_SENSOR_INVALID_CHAR; emstatBoardRead[ EMSTAT_CD1_CD2_BOARD ].sensors[ EMSTAT_CPO_OR_CD2_INDEX ].condSnsr = CONDUCTIVITYSENSORS_CD2_SENSOR; emstatBoardRead[ EMSTAT_CD1_CD2_BOARD ].sensors[ EMSTAT_CPO_OR_CD2_INDEX ].condSnsrTempSnsr = TEMPSENSORS_CONDUCTIVITY_SENSOR_2; emstatBoardRead[ EMSTAT_CD1_CD2_BOARD ].sensors[ EMSTAT_CPO_OR_CD2_INDEX ].condSnsrHex2StrAlarm = ALARM_ID_DG_CD2_COND_SENSOR_INVALID_CHAR; // For V3 conductivity sensors setFPGACPiProbeType( COND_CPI_SENSOR_PROBE_TYPE ); setFPGACPoProbeType( COND_CPO_SENSOR_PROBE_TYPE ); initTimeWindowedCount( TIME_WINDOWED_COUNT_FPGA_CONDUCTIVITY_SENSOR_ERROR, MAX_CONDUCTIVITY_SENSOR_FAILURES, MAX_CONDUCTIVITY_SENSOR_FAILURE_WINDOW_MS ); initPersistentAlarm( ALARM_ID_DG_INLET_WATER_CONDUCTIVITY_IN_HIGH_RANGE, INLET_WATER_COND_SENSOR_OUT_OF_RANGE_CLEAR_MS, INLET_WATER_COND_SENSOR_OUT_OF_RANGE_TIMEOUT_MS ); initPersistentAlarm( ALARM_ID_DG_INLET_WATER_CONDUCTIVITY_IN_LOW_RANGE, INLET_WATER_COND_SENSOR_OUT_OF_RANGE_CLEAR_MS, INLET_WATER_COND_SENSOR_OUT_OF_RANGE_TIMEOUT_MS ); initPersistentAlarm( ALARM_ID_DG_CLEANING_MODE_INLET_WATER_COND_TOO_HIGH, 0, INLET_WATER_COND_SENSOR_OUT_OF_RANGE_TIMEOUT_MS ); initPersistentAlarm( ALARM_ID_DG_CLEANING_MODE_INLET_WATER_COND_TOO_LOW, 0, INLET_WATER_COND_SENSOR_OUT_OF_RANGE_TIMEOUT_MS ); // Initialize the conductivity sensors' FPGA alarms initFPGAPersistentAlarm( FPGA_PERS_ERROR_CPI_CPO_COND_SENSORS, ALARM_ID_DG_CPI_CPO_SENSORS_FPGA_FAULT, COND_SENSORS_FPGA_ERROR_TIMEOUT_MS, COND_SENSORS_FPGA_ERROR_TIMEOUT_MS ); initFPGAPersistentAlarm( FPGA_PERS_ERROR_CD1_CD2_COND_SENSORS, ALARM_ID_DG_CD1_CD2_SENSORS_FPGA_FAULT, COND_SENSORS_FPGA_ERROR_TIMEOUT_MS, COND_SENSORS_FPGA_ERROR_TIMEOUT_MS ); } /*********************************************************************//** * @brief * The execConductivitySensors function gets conductivity sensors' latest * readings from FPGA and advertises them over CAN. * @details Inputs: none * @details Outputs: Conductivity sensors' latest reading is updated and * advertised. * @return none *************************************************************************/ void execConductivitySensors( void ) { // Check if a new calibration is available if ( TRUE == isNewCalibrationRecordAvailable() ) { getNVRecord2Driver( GET_CAL_CONDUCTIVITY_SENSORS, (U08*)&condSensorsCalRecord, sizeof( condSensorsCalRecord ), NUM_OF_CAL_DATA_COND_SENSORS, ALARM_ID_DG_COND_SENSORS_INVALID_CAL_RECORD ); getNVRecord2Driver( GET_CAL_CONDUCTIVITY_SENSORS_TEMP_COMP, (U08*)&condSensorsTempCompCalRecord, sizeof( DG_COND_SENSORS_TEMP_COMP_CAL_RECORD_T ), NUM_OF_CAL_DATA_COND_SENSORS_TEMP_COMP, ALARM_ID_DG_COND_SENSORS_INVALID_TEMP_COMP_CAL_RECORD ); } #ifndef _RELEASE_ if ( HW_CONFIG_BETA == getHardwareConfigStatus() ) { processCPiCPoSensorRead( CONDUCTIVITYSENSORS_CPI_SENSOR, EMSTAT_CPI_OR_CD1_INDEX, getFPGACPi(), getFPGACPiReadCount(), getFPGACPiErrorCount(), getFPGACPiFault() ); processCPiCPoSensorRead( CONDUCTIVITYSENSORS_CPO_SENSOR, EMSTAT_CPO_OR_CD2_INDEX, getFPGACPo(), getFPGACPoReadCount(), getFPGACPoErrorCount(), getFPGACPoFault() ); } else #endif { processEmstatBoard( EMSTAT_CPI_CPO_BOARD ); } processEmstatBoard( EMSTAT_CD1_CD2_BOARD ); if ( ++condDataPublishCounter >= getU32OverrideValue( &conductivityDataPublishInterval ) ) { CONDUCTIVITY_DATA_T data; calcRORejectionRatio(); data.roRejectionRatio = roRejectionRatio; data.cpi = getConductivityValue( CONDUCTIVITYSENSORS_CPI_SENSOR ); data.cpo = getConductivityValue( CONDUCTIVITYSENSORS_CPO_SENSOR ); data.cd1 = getConductivityValue( CONDUCTIVITYSENSORS_CD1_SENSOR ); data.cd2 = getConductivityValue( CONDUCTIVITYSENSORS_CD2_SENSOR ); data.cpiRaw = condSensorStatus[ CONDUCTIVITYSENSORS_CPI_SENSOR ].rawCondValue; data.cpoRaw = condSensorStatus[ CONDUCTIVITYSENSORS_CPO_SENSOR ].rawCondValue; data.cd1Raw = condSensorStatus[ CONDUCTIVITYSENSORS_CD1_SENSOR ].rawCondValue; data.cd2Raw = condSensorStatus[ CONDUCTIVITYSENSORS_CD2_SENSOR ].rawCondValue; data.cpiSensorStatus = condSensorStatus[ CONDUCTIVITYSENSORS_CPI_SENSOR ].sensorStatus; data.cpoSensorStatus = condSensorStatus[ CONDUCTIVITYSENSORS_CPO_SENSOR ].sensorStatus; data.cd1SensorStatus = condSensorStatus[ CONDUCTIVITYSENSORS_CD1_SENSOR ].sensorStatus; data.cd2SensorStatus = condSensorStatus[ CONDUCTIVITYSENSORS_CD2_SENSOR ].sensorStatus; condDataPublishCounter = 0; broadcastData( MSG_ID_DG_CONDUCTIVITY_DATA, COMM_BUFFER_OUT_CAN_DG_BROADCAST, (U08*)&data, sizeof( CONDUCTIVITY_DATA_T ) ); } } /*********************************************************************//** * @brief * The execConductivitySensorsSelfTest function executes the conductivity * sensors' self-test. * @details Inputs: none * @details Outputs: none * @return PressuresSelfTestResult (SELF_TEST_STATUS_T) *************************************************************************/ SELF_TEST_STATUS_T execConductivitySensorsSelfTest( void ) { SELF_TEST_STATUS_T result = SELF_TEST_STATUS_IN_PROGRESS; BOOL calStatus = FALSE; calStatus |= getNVRecord2Driver( GET_CAL_CONDUCTIVITY_SENSORS, (U08*)&condSensorsCalRecord, sizeof( DG_COND_SENSORS_CAL_RECORD_T ), NUM_OF_CAL_DATA_COND_SENSORS, ALARM_ID_DG_COND_SENSORS_INVALID_CAL_RECORD ); calStatus |= getNVRecord2Driver( GET_CAL_CONDUCTIVITY_SENSORS_TEMP_COMP, (U08*)&condSensorsTempCompCalRecord, sizeof( DG_COND_SENSORS_TEMP_COMP_CAL_RECORD_T ), NUM_OF_CAL_DATA_COND_SENSORS_TEMP_COMP, ALARM_ID_DG_COND_SENSORS_INVALID_TEMP_COMP_CAL_RECORD ); result = ( TRUE == calStatus ? SELF_TEST_STATUS_PASSED : SELF_TEST_STATUS_FAILED ); return result; } /*********************************************************************//** * @brief * The checkInletWaterConductivity function checks inlet water conductivity value * and triggers an alarm when conductivity value is out of allowed range. * @details Inputs: CPi sensor conductivity * @details Outputs: Trigger alarms when conductivity is out of allowed range * @return none *************************************************************************/ void checkInletWaterConductivity( void ) { F32 conductivity = getConductivityValue( CONDUCTIVITYSENSORS_CPI_SENSOR ); HD_MODE_SUB_MODE_T opMode; getHDOperationMode( &opMode ); if ( VALVE_STATE_OPEN == getValveStateName( VPI ) && ( opMode.hdMode <= MODE_TREA ) ) { #ifndef _RELEASE_ if ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_WATER_QUALITY_CHECK ) != SW_CONFIG_ENABLE_VALUE ) #endif { DG_OP_MODE_T opMode = getCurrentOperationMode(); BOOL isConductTooLow = FALSE; BOOL isConductTooHigh = FALSE; if ( FALSE == isROOnlyModeEnabled() ) { isConductTooLow = ( conductivity < minInletWaterCondAlarmLimitUSPCM ? TRUE : FALSE ); isConductTooHigh = ( conductivity > MAX_COND_SENSOR_CPI_WARNING_HIGH_US_PER_CM ? TRUE : FALSE ); } else { isConductTooLow = FALSE; isConductTooHigh = ( conductivity > MAX_RO_ONLY_COND_SENSOR_CPI_HIGH_US_PER_CM ? TRUE : FALSE ); } switch( opMode ) { case DG_MODE_GENE: case DG_MODE_FILL: case DG_MODE_DRAI: case DG_MODE_STAN: if ( TRUE == isAlarmActive( ALARM_ID_DG_INLET_WATER_CONDUCTIVITY_IN_LOW_RANGE ) ) { isConductTooLow = ( conductivity >= ( minInletWaterCondAlarmLimitUSPCM + MIN_CPI_INLET_ALARM_RECOVERY_OFFSET_US_PER_CM ) ? FALSE : TRUE ); } // Per PRS 403 checkPersistentAlarm( ALARM_ID_DG_INLET_WATER_CONDUCTIVITY_IN_LOW_RANGE, isConductTooLow, conductivity, minInletWaterCondAlarmLimitUSPCM ); if ( TRUE == isAlarmActive( ALARM_ID_DG_INLET_WATER_CONDUCTIVITY_IN_HIGH_RANGE ) ) { if ( FALSE == isROOnlyModeEnabled() ) { isConductTooHigh = ( conductivity <= MIN_COND_SENSOR_CPI_WARNING_HIGH_US_PER_CM ? FALSE : TRUE ); } else { isConductTooHigh = ( conductivity <= MIN_RO_ONLY_COND_SENSOR_CPI_HIGH_US_PER_CM ? FALSE : TRUE ); } } // Per PRS 404 checkPersistentAlarm( ALARM_ID_DG_INLET_WATER_CONDUCTIVITY_IN_HIGH_RANGE, isConductTooHigh, conductivity, MAX_COND_SENSOR_CPI_WARNING_HIGH_US_PER_CM ); break; case DG_MODE_FLUS: case DG_MODE_HEAT: case DG_MODE_HCOL: case DG_MODE_CHEM: case DG_MODE_CHFL: case DG_MODE_ROPS: // Per PRS 403 checkPersistentAlarm( ALARM_ID_DG_CLEANING_MODE_INLET_WATER_COND_TOO_HIGH, isConductTooHigh, conductivity, MAX_COND_SENSOR_CPI_WARNING_HIGH_US_PER_CM ); // Per PRS 404 checkPersistentAlarm( ALARM_ID_DG_CLEANING_MODE_INLET_WATER_COND_TOO_LOW, isConductTooLow, conductivity, minInletWaterCondAlarmLimitUSPCM ); break; default: // NOTE: Do nothing for the rest of the modes break; } } } else { // VPI is closed - clear all alarms checkPersistentAlarm( ALARM_ID_DG_INLET_WATER_CONDUCTIVITY_IN_LOW_RANGE, FALSE, conductivity, minInletWaterCondAlarmLimitUSPCM ); checkPersistentAlarm( ALARM_ID_DG_INLET_WATER_CONDUCTIVITY_IN_HIGH_RANGE, FALSE, conductivity, MAX_COND_SENSOR_CPI_WARNING_HIGH_US_PER_CM ); checkPersistentAlarm( ALARM_ID_DG_CLEANING_MODE_INLET_WATER_COND_TOO_HIGH, FALSE, conductivity, MAX_COND_SENSOR_CPI_WARNING_HIGH_US_PER_CM ); checkPersistentAlarm( ALARM_ID_DG_CLEANING_MODE_INLET_WATER_COND_TOO_LOW, FALSE, conductivity, minInletWaterCondAlarmLimitUSPCM ); } } /*********************************************************************//** * @brief * The setCondcutivitySensorCalTable function sets the calibration table to * be use of the corresponding conductivity sensor. * @details Inputs: none * @details Outputs: condSensorCalTable * @param sensor the conductivity sensor ID to use the calibration table for * @param calTable the calibration table to be used for the selected conductivity * sensor * @return none *************************************************************************/ void setCondcutivitySensorCalTable( CONDUCTIVITY_SENSORS_T sensor, CAL_DATA_DG_COND_SENSORS_T calTable ) { if ( sensor < NUM_OF_CONDUCTIVITY_SENSORS ) { if ( calTable < NUM_OF_CAL_DATA_COND_SENSORS ) { condSensorCalTable[ sensor ] = calTable; } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, SW_FAULT_ID_DG_INVALID_COND_SNSNR_CAL_TABLE_SELECTED, calTable ) } } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, SW_FAULT_ID_INVALID_CONDUCTIVITY_SENSOR_ID, sensor ) } } /*********************************************************************//** * @brief * The setMinInletWaterConductivityAlarmLimitUSPCM function sets the min * inlet water conductivity alarm limit in uS/cm. * @details Inputs: none * @details Outputs: minInletWaterConductivityAlarmLimitUSPCM * @param conductivity value * @return none *************************************************************************/ void setMinInletWaterConductivityAlarmLimitUSPCM( F32 valueUSPM ) { minInletWaterCondAlarmLimitUSPCM = valueUSPM; SEND_EVENT_WITH_2_F32_DATA( DG_EVENT_MIN_INLET_WATER_COND_ALARM_FROM_HD_INSTIT_RECORD, minInletWaterCondAlarmLimitUSPCM, 0.0F ) } /*********************************************************************//** * @brief * The getConductivityValue function gets the compensated conductivity * value for a given conductivity sensor id. * @details Inputs: compensatedConductivityValues[] * @details Outputs: none * @param sensorId conductivity sensor id * @return compensated conductivity *************************************************************************/ F32 getConductivityValue( U32 sensorId ) { F32 result = 0.0F; if ( sensorId < NUM_OF_CONDUCTIVITY_SENSORS ) { // NOTE: the compensated value is set into a local variable and then passed to the override function // to prevent memory failure OVERRIDE_F32_T value = condSensorStatus[ sensorId ].compensatedCondValue; result = getF32OverrideValue( &value ); } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, SW_FAULT_ID_INVALID_CONDUCTIVITY_SENSOR_ID, sensorId ); } return result; } /*********************************************************************//** * @brief * The calcCompensatedConductivity function calculates the compensated * conductivity based on given temperature and conductivity taken at * reference temperature of 25 degree Celsius. * @details Inputs: conductivity, temperature * @details Outputs: none * @param sensorID the ID of the conductivity sensor * @param conductivity conductivity value * @param temperature temperature to compensate conductivity with * @return compensated conductivity based on temperature *************************************************************************/ static F32 calcCompensatedConductivity( U32 sensorID, F32 conductivity, F32 temperature ) { F32 gain = condSensorsTempCompCalRecord.condSensorsTempComp[ (CAL_DATA_DG_COND_SENSORS_TEMP_COMP_T)sensorID ].gain; F32 coeff = condSensorsTempCompCalRecord.condSensorsTempComp[ (CAL_DATA_DG_COND_SENSORS_TEMP_COMP_T)sensorID ].coefficient; F32 offset = condSensorsTempCompCalRecord.condSensorsTempComp[ (CAL_DATA_DG_COND_SENSORS_TEMP_COMP_T)sensorID ].offset; F32 compensation = ( gain * coeff ) + offset; // EC = EC_25 * (1 + compensation * ( temperature - 25 )) F32 compensatedCoef = ( 1.0F + ( compensation * ( temperature - COND_SENSOR_REFERENCE_TEMPERATURE ) ) ); return conductivity / compensatedCoef; } /*********************************************************************//** * @brief * The calcRORejectionRatio function calculates the RO rejection ratio using * the CPi sensor conductivity value and CPo sensor conductivity value. * @details Inputs: CPi sensor conductivity, CPo sensor conductivity * @details Outputs: RO rejection ratio * @return none *************************************************************************/ static void calcRORejectionRatio( void ) { F32 cpi = getConductivityValue( CONDUCTIVITYSENSORS_CPI_SENSOR ); F32 cpo = getConductivityValue( CONDUCTIVITYSENSORS_CPO_SENSOR ); roRejectionRatio = RO_REJECTION_RATIO_OUT_OF_RANGE_VALUE; if ( fabs(cpi) >= NEARLY_ZERO ) { roRejectionRatio = cpo / cpi; } } /*********************************************************************//** * @brief * The processCPiCPoSensorRead function checks if there is an error in FPGA * and FPGA read count. If there is any error in the FPGA error, it raises an * alarm. If the read count has changed, the new reading will be processed. * @details Inputs: none * @details Outputs: none * @param sensorId Conductivity sensor id to process * @param emstatBoardSensorIndex the sensor index number on an EMSTAT board * @param fgpaRead FPGA conductivity reading value * @param fpgaReadCount FPGA read count * @param fpgaErrorCount FPGA error count * @param fpgaSensorFault FPGA sensor fault * @return none *************************************************************************/ static void processCPiCPoSensorRead( U32 sensorId, U08 emstatBoardSensorIndex, U32 fgpaRead, U08 fpgaReadCount, U08 fpgaErrorCount, U08 fpgaSensorFault ) { if ( ( 0 == fpgaErrorCount ) && ( 0 == fpgaSensorFault ) ) { if ( ( condSensorStatus[ sensorId ].readCount != fpgaReadCount ) ) { // The corresponding temperature sensor of a conductivity sensor is maintained in the EMSTAT boards' structure since the // EMSTAT sensors will be the permanent sensors from DVT onward. F32 temperature = getTemperatureValue( emstatBoardRead[ EMSTAT_CPI_CPO_BOARD ].sensors[ emstatBoardSensorIndex ].condSnsrTempSnsr ); F32 conductivity = ( (F32)( fgpaRead ) / COND_SENSOR_DECIMAL_CONVERSION ); F32 compensatedCond = calcCompensatedConductivity( sensorId, conductivity, temperature ); condSensorStatus[ sensorId ].readCount = fpgaReadCount; condSensorStatus[ sensorId ].internalErrorCount = 0; condSensorStatus[ sensorId ].compensatedCondValue.data = getCalibrationAppliedConductivityValue( sensorId, compensatedCond ); condSensorStatus[ sensorId ].rawCondValue = conductivity; // store raw conductivity data from CPi and CPo } else { if ( ++condSensorStatus[ sensorId ].internalErrorCount > MAX_ALLOWED_UNCHANGED_CONDUCTIVITY_READS ) { #ifndef _RELEASE_ if ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_COND_SENSOR_CHECK ) != SW_CONFIG_ENABLE_VALUE ) #endif { SET_ALARM_WITH_1_U32_DATA( ALARM_ID_DG_CONDUCTIVITY_SENSOR_FAULT, sensorId ); } } } } else { if ( TRUE == incTimeWindowedCount( TIME_WINDOWED_COUNT_FPGA_CONDUCTIVITY_SENSOR_ERROR ) ) { #ifndef _RELEASE_ if ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_COND_SENSOR_CHECK ) != SW_CONFIG_ENABLE_VALUE ) #endif { SET_ALARM_WITH_1_U32_DATA( ALARM_ID_DG_CONDUCTIVITY_SENSOR_FAULT, sensorId ); } } } } /*********************************************************************//** * @brief * The prefixStrToSIFactor function returns SI factor based on a given ascii prefix. * @details Inputs: none * @details Outputs: none * @param prefix ascii value of the prefix * @return SI factor of the given ascii prefix *************************************************************************/ static U32 prefixStrToSIFactor( U08 prefix ) { U32 result; switch ( prefix ) { case 'm': result = 1000; break; case 'u': result = 1000000; break; default: result = 1; break; } return result; } /*********************************************************************//** * @brief * The processEmstatBoard function processes the Emsat boards * @details Inputs: emstatBoardRead * @details Outputs: emstatBoardRead * @param board the enum of the Emstat board * @return none *************************************************************************/ static void processEmstatBoard( EMSTAT_BOARD_T board ) { U08 emstatByte = 0; U16 rxFifoCount = 0; switch ( board ) { case EMSTAT_CPI_CPO_BOARD: // Check for continuous incoming bytes rxFifoCount = getFPGAEmstatCPiCPoRxFifoCount() & EMSTAT_RX_FIFO_COUNT_MASK; checkFPGAPersistentAlarms( FPGA_PERS_ERROR_CPI_CPO_COND_SENSORS, rxFifoCount ); // Only process the sensor read if there is a new byte if ( 0 != rxFifoCount ) { emstatByte = getFPGAEmstatCPiCPoByteOut(); processEmstatSensorRead( &emstatBoardRead[ EMSTAT_CPI_CPO_BOARD ], emstatByte ); } break; case EMSTAT_CD1_CD2_BOARD: // Check for continuous incoming bytes rxFifoCount = getFPGAEmstatCD1CD2RxFifoCount() & EMSTAT_RX_FIFO_COUNT_MASK; checkFPGAPersistentAlarms( FPGA_PERS_ERROR_CD1_CD2_COND_SENSORS, rxFifoCount ); // Only process the sensor read if there is a new byte if ( 0 != rxFifoCount) { emstatByte = getFPGAEmstatCD1CD2OutByte(); processEmstatSensorRead( &emstatBoardRead[ EMSTAT_CD1_CD2_BOARD ], emstatByte ); } break; default: SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_SOFTWARE_FAULT, SW_FAULT_ID_INVALID_EMSTAT_CONDUCTIVITY_BOARD_SELECTED, board ); break; } } /*********************************************************************//** * @brief * The processEmstatSensorRead function processes the Emsat boards' data * that is received from the boards * @details Inputs: none * @details Outputs: none * @param read package the structure of each Emstat board * @param emstatByte the received byte from the sensor * @return none *************************************************************************/ static void processEmstatSensorRead( EMSTAT_READ_T* readPackage, U08 emstatByte ) { switch ( emstatByte ) { case 'P': readPackage->packageStarted = TRUE; readPackage->packageIndex = 0; break; case ';': if ( TRUE == readPackage->packageStarted ) { processEmstatMeasurementDataPackets( EMSTAT_CPI_OR_CD1_INDEX, readPackage, (EMSTAT_VARIABLE_T*)readPackage->package ); readPackage->packageIndex = 0; } break; case '\n': if ( TRUE == readPackage->packageStarted ) { processEmstatMeasurementDataPackets( EMSTAT_CPO_OR_CD2_INDEX, readPackage, (EMSTAT_VARIABLE_T*)readPackage->package ); readPackage->packageStarted = FALSE; } break; default: if ( TRUE == readPackage->packageStarted ) { if ( readPackage->packageIndex < EMSTAT_PACKAGE_BUFFER_SIZE ) { readPackage->package[ readPackage->packageIndex++ ] = emstatByte; } } break; } } /*********************************************************************//** * @brief * The processEmstatMeasurementDataPackets function processes the Emsat boards' * received data packets. * @details Inputs: condSensorStatus, * @details Outputs: condSensorStatus * @param boardSenosrIndex the sensor index on each board (i.e CPi = 0 & CPo = 1 * in EMSTAT_CPI_CPO_BOARD) * @param read package the structure of each Emstat board * @return none *************************************************************************/ static void processEmstatMeasurementDataPackets( U08 boardSensorIndex, EMSTAT_READ_T* readPackage, EMSTAT_VARIABLE_T* receivedPackets ) { CONDUCTIVITY_SENSORS_T sensorId = readPackage->sensors[ boardSensorIndex ].condSnsr; BOOL convStatus = hexStrToDec( (U08*)&receivedPackets->status, &condSensorStatus[ sensorId ].sensorStatus, sizeof( receivedPackets->status ) ); BOOL isSensorStatusBad = ( EMSTAT_PICO_STATUS_TIMING_NOT_MET == condSensorStatus[ sensorId ].sensorStatus ? TRUE : FALSE ); ALARM_ID_T badHexAlarm = readPackage->sensors[ boardSensorIndex ].condSnsrHex2StrAlarm; BOOL isConvNotValid = FALSE; if ( FALSE == isSensorStatusBad ) { U32 prefix = prefixStrToSIFactor( receivedPackets->prefix ); convStatus = hexStrToDec( receivedPackets->value, &condSensorStatus[ sensorId ].rawEmstatCondValue, sizeof( receivedPackets->value ) ); F32 resistance = ( ( F32 )( condSensorStatus[ sensorId ].rawEmstatCondValue - EMSTAT_PICO_MEASUREMENT_OFFSET ) / prefix ); F32 temperature = getTemperatureValue( readPackage->sensors[ boardSensorIndex ].condSnsrTempSnsr ); F32 conductivity = ( 1.0F / resistance ) * SIEMENS_TO_MICROSIEMENS_CONVERSION; F32 compensatedCond = calcCompensatedConductivity( sensorId, conductivity, temperature ); F32 calAppliedCond = getCalibrationAppliedConductivityValue( sensorId, compensatedCond ); if ( ( CONDUCTIVITYSENSORS_CPI_SENSOR == sensorId ) || ( CONDUCTIVITYSENSORS_CPO_SENSOR == sensorId ) ) { // If the CPi or CPo sensors values < 20 uS/cm after compensation and calibration, they are set to 20 uS/cm. if ( calAppliedCond < MIN_CPI_CPO_COND_VALUES_AFTER_CALS_US_PER_CM ) { calAppliedCond = MIN_CPI_CPO_COND_VALUES_AFTER_CALS_US_PER_CM; } } condSensorStatus[ sensorId ].internalErrorCount = 0; condSensorStatus[ sensorId ].compensatedCondValue.data = calAppliedCond; condSensorStatus[ sensorId ].rawCondValue = conductivity; } isConvNotValid = ( TRUE == convStatus ? FALSE : TRUE ); // Check the conductivity sensors bad status alarm if ( TRUE == isConvNotValid ) { if ( ++condSensorStatus[ sensorId ].badCharErrorCount > MAX_CONDUCTIVITY_SENSOR_FAILURES ) { SET_ALARM_WITH_1_U32_DATA( badHexAlarm, sensorId ); } } else { condSensorStatus[ sensorId ].badCharErrorCount = 0; } if ( EMSTAT_PICO_STATUS_TIMING_NOT_MET == condSensorStatus[ sensorId ].sensorStatus ) { if ( ++condSensorStatus[ sensorId ].internalErrorCount > MAX_CONDUCTIVITY_SENSOR_FAILURES ) { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DG_CONDUCTIVITY_SENSOR_FAULT, sensorId, condSensorStatus[ sensorId ].sensorStatus ); } } else { condSensorStatus[ sensorId ].internalErrorCount = 0; } } /*********************************************************************//** * @brief * The getCalibrationAppliedConductivityValue function gets the temperature * compensated conductivity value and applies calibration to it. * @details Inputs: condSensorsCalRecord * @details Outputs: none * @param sensorId which is the conductivity sensor ID * @param compensatedValue which is the temperature compensated conductivity * value of the conductivity sensor * @return the calibration applied conductivity value *************************************************************************/ static F32 getCalibrationAppliedConductivityValue( U32 sensorId, F32 compensatedValue ) { CAL_DATA_DG_COND_SENSORS_T calTableId = condSensorCalTable[ sensorId ]; F32 conductivity = 0.0F; conductivity = pow( compensatedValue, 4 ) * condSensorsCalRecord.condSensors[ calTableId ].fourthOrderCoeff + pow( compensatedValue, 3 ) * condSensorsCalRecord.condSensors[ calTableId ].thirdOrderCoeff + pow( compensatedValue, 2 ) * condSensorsCalRecord.condSensors[ calTableId ].secondOrderCoeff + compensatedValue * condSensorsCalRecord.condSensors[ calTableId ].gain + condSensorsCalRecord.condSensors[ calTableId ].offset; return conductivity; } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testSetConductivityOverride function overrides the compensated * conductivity value of given sensor id. * @details Inputs: none * @details Outputs: condSensorStatus * @param sensorId conductivity sensor id * @param value override compensated conductivity value * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetConductivityOverride( U32 sensorId, F32 value ) { BOOL result = FALSE; if ( sensorId < NUM_OF_CONDUCTIVITY_SENSORS ) { if ( TRUE == isTestingActivated() ) { result = TRUE; condSensorStatus[ sensorId ].compensatedCondValue.ovData = value; condSensorStatus[ sensorId ].compensatedCondValue.override = OVERRIDE_KEY; } } return result; } /*********************************************************************//** * @brief * The testResetConductivityOverride function resets the override of the * conductivity sensor value. * @details Inputs: none * @details Outputs: condSensorStatus * @param sensorId conductivity sensor id * @return TRUE if reset successful, FALSE if not *************************************************************************/ BOOL testResetConductivityOverride( U32 sensorId ) { BOOL result = FALSE; if ( sensorId < NUM_OF_CONDUCTIVITY_SENSORS ) { if ( TRUE == isTestingActivated() ) { result = TRUE; condSensorStatus[ sensorId ].compensatedCondValue.ovData = condSensorStatus[ sensorId ].compensatedCondValue.ovInitData; condSensorStatus[ sensorId ].compensatedCondValue.override = OVERRIDE_RESET; } } return result; } /*********************************************************************//** * @brief * The testSetConductivityDataPublishIntervalOverride function overrides * the conductivity data publish interval. * @details Inputs: conductivityDataPublishInterval * @details Outputs: conductivityDataPublishInterval * @param value override conductivity data publish interval with (in ms) * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetConductivityDataPublishIntervalOverride( U32 interval_ms ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; conductivityDataPublishInterval.ovData = interval_ms / TASK_PRIORITY_INTERVAL; conductivityDataPublishInterval.override = OVERRIDE_KEY; } return result; } /*********************************************************************//** * @brief * The testResetConductivityDataPublishIntervalOverride function resets * the override of the conductivity data publish interval. * @details Inputs: conductivityDataPublishInterval * @details Outputs: conductivityDataPublishInterval * @return TRUE if override reset successful, FALSE if not *************************************************************************/ BOOL testResetConductivityDataPublishIntervalOverride( void ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { result = TRUE; conductivityDataPublishInterval.ovData = conductivityDataPublishInterval.ovInitData; conductivityDataPublishInterval.override = OVERRIDE_RESET; } return result; } /*********************************************************************//** * @brief * The testSetConductivitySensorCalibrationTable function sets the conductivity * sensor's calibration table * @details Inputs: none * @details Outputs: none * @param data pointer to the conductivity sensor calibration table data * structure * @return TRUE if set was successful otherwise, FALSE *************************************************************************/ BOOL testSetConductivitySensorCalibrationTable( CONDUCTIVITY_SENSOR_CAL_TABLE_T* data ) { BOOL result = FALSE; if ( TRUE == isTestingActivated() ) { CAL_DATA_DG_COND_SENSORS_T calTableId = (CAL_DATA_DG_COND_SENSORS_T)data->calTableId; CONDUCTIVITY_SENSORS_T sensorId = (CONDUCTIVITY_SENSORS_T)data->sensorId; switch( sensorId ) { case CONDUCTIVITYSENSORS_CPI_SENSOR: if ( CAL_DATA_CPI_COND_SENSOR == calTableId ) { setCondcutivitySensorCalTable( sensorId, calTableId ); result = TRUE; } break; case CONDUCTIVITYSENSORS_CPO_SENSOR: if ( CAL_DATA_CPO_COND_SENSOR == calTableId ) { setCondcutivitySensorCalTable( sensorId, calTableId ); result = TRUE; } break; case CONDUCTIVITYSENSORS_CD1_SENSOR: if ( CAL_DATA_CD1_COND_SENSOR == calTableId ) { setCondcutivitySensorCalTable( sensorId, calTableId ); result = TRUE; } break; case CONDUCTIVITYSENSORS_CD2_SENSOR: if ( ( CAL_DATA_CD2_COND_SENSOR == calTableId ) || ( CAL_DATA_CD2_COND_SENSOR_CHEM_DISINFECT == calTableId ) || ( CAL_DATA_CD2_COND_SENSOR_FILL_BICARB_TEST == calTableId ) ) { setCondcutivitySensorCalTable( sensorId, calTableId ); result = TRUE; } break; default: // Sensor ID out of range just reject with a FALSE break; } } return result; } /**@}*/