/************************************************************************** * * Copyright (c) 2019-2020 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) Quang Nguyen * @date (last) 29-Jul-2020 * * @author (original) Quang Nguyen * @date (original) 13-Jul-2020 * ***************************************************************************/ #include "ConductivitySensors.h" #include "FPGA.h" #include "PersistentAlarm.h" #include "SystemCommMessages.h" #include "TaskGeneral.h" #include "TaskPriority.h" #include "TemperatureSensors.h" #include "Utilities.h" /** * @addtogroup ConductivitySensors * @{ */ // ********** private definitions ********** #define COND_CPO_SENSOR_PROBE_TYPE 10 ///< 0.1 K cell constant conductivity probe #define COND_SENSOR_DECIMAL_CONVERSION 100 ///< Conductivity value from FPGA has two decimal place #define COND_SENSOR_TEMPERATURE_COEF 0.02 ///< Linear temperature coefficient of variation at 25 Celcius for fresh water #define COND_SENSOR_REFERENCE_TEMPERATURE 25 ///< Reference temperature for conductivity sensor #define COND_SENSOR_REPORT_PERIOD (MS_PER_SECOND / TASK_PRIORITY_INTERVAL) ///< Broadcast conductivity values message every second #define COND_SENSOR_CPI_CPO_MAX_VALUE 2000 ///< Maximum inlet water conductivity #define COND_SENSOR_CPI_CPO_MIN_VALUE 100 ///< Minimum inlet water conductivity #define COND_SENSOR_PERSISTENCE_COUNT (5 * MS_PER_SECOND / TASK_GENERAL_INTERVAL) ///< Number of persistence count for conductivity sensor out of range error #define MAX_ALLOWED_UNCHANGED_CONDUCTIVITY_READS 100 ///< New reading every 640 ms, expect to get new reading in 1s #define MAX_CONDUCTIVITY_SENSOR_FAILURES 5 ///< 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 RO_REJECTION_RATIO_REPORT_PERIOD (MS_PER_SECOND / TASK_PRIORITY_INTERVAL) ///< Broadcast RO rejection ratio message every second #define RO_REJECTION_RATIO_OUT_OF_RANGE_VALUE 1 ///< Out of range value for RO rejection ratio when CPi conductivity is zero #define MAX_RO_REJECTION_RATIO_ALLOW 0.1 ///< Maximum RO Rejection ratio #define MAX_CPO_CONDUCTIVITY_ALLOW 15 ///< Maximum CPo sensor conductivity value #define RO_REJECTION_RATIO_PERSISTENCE_COUNT (10 * MS_PER_SECOND / TASK_GENERAL_INTERVAL) ///< Number of persistence count for RO rejection ro // ********** private data ********** /// Conductivity sensors' associated temperature sensors static U32 associateTempSensor[ NUM_OF_CONDUCTIVITY_SENSORS ] = { TEMPSENSORS_INLET_PRIMARY_HEATER, ///< Inlet temperature sensor TEMPSENSORS_OUTLET_PRIMARY_HEATER, ///< Outlet temperature sensor }; static U08 readCount[ NUM_OF_CONDUCTIVITY_SENSORS ]; ///< Read count for conductivity readings. static U32 internalErrorCount[ NUM_OF_CONDUCTIVITY_SENSORS ]; ///< Internal error count for conductivity readings. static OVERRIDE_F32_T compensatedConductivityValues[ NUM_OF_CONDUCTIVITY_SENSORS ]; ///< Latest compensated conductivity values. static OVERRIDE_F32_T roRejectionRatio; ///< Latest RO rejection ratio static OVERRIDE_U32_T conductivityDataPublishInterval = { COND_SENSOR_REPORT_PERIOD, COND_SENSOR_REPORT_PERIOD, 0, 0 }; ///< Conductivity sensors publish time interval override static U32 conductivityDataPublicationTimerCounter = 0; ///< Conductivity sensors data publish timer counter // ********** private function prototypes ********** static void processCPiCPoSensorRead( U32 sensorId, U32 fgpaRead, U08 fpgaReadCount, U08 fpgaErrorCount, U08 fpgaSensorFault ); static F32 calcCompensatedConductivity( F32 conductivity, F32 temperature); static void calcRORejectionRatio( void ); static F32 getRORejectionRatio( void ); static DATA_GET_PROTOTYPE( U32, getConductivityDataPublishInterval ); /************************************************************************* * @brief * The initConductivitySensors function initializes the ConductivitySensors module. * @details * Inputs : none * Outputs : ConductivitySensors module initialized * @return none *************************************************************************/ void initConductivitySensors( void ) { U32 i; for ( i = 0; i < NUM_OF_CONDUCTIVITY_SENSORS; i++ ) { readCount[ i ] = 0; internalErrorCount[ i ] = 0; compensatedConductivityValues[ i ].data = 0.0; compensatedConductivityValues[ i ].ovData = 0.0; compensatedConductivityValues[ i ].ovInitData = 0.0; compensatedConductivityValues[ i ].override = OVERRIDE_RESET; } roRejectionRatio.data = 0.0; roRejectionRatio.ovData = 0.0; roRejectionRatio.ovInitData = 0.0; roRejectionRatio.override = OVERRIDE_RESET; 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_INLET_WATER_HIGH_CONDUCTIVITY, ALARM_DATA_TYPE_F32, COND_SENSOR_PERSISTENCE_COUNT, COND_SENSOR_PERSISTENCE_COUNT ); initPersistentAlarm( ALARM_ID_INLET_WATER_LOW_CONDUCTIVITY, ALARM_DATA_TYPE_F32, COND_SENSOR_PERSISTENCE_COUNT, COND_SENSOR_PERSISTENCE_COUNT ); initPersistentAlarm( ALARM_ID_RO_REJECTION_RATIO_OUT_OF_RANGE, ALARM_DATA_TYPE_F32, RO_REJECTION_RATIO_PERSISTENCE_COUNT, RO_REJECTION_RATIO_PERSISTENCE_COUNT ); } /************************************************************************* * @brief * The execConductivitySensors function gets conductivity sensors' latest * readings from FPGA and advertises them over CAN. * @details * Inputs : none * Outputs : Conductivity sensors' latest reading is updated and advertised. * @return none *************************************************************************/ void execConductivitySensors( void ) { processCPiCPoSensorRead( CONDUCTIVITYSENSORS_CPI_SENSOR, getFPGACPi(), getFPGACPiReadCount(), getFPGACPiErrorCount(), getFPGACPiFault() ); processCPiCPoSensorRead( CONDUCTIVITYSENSORS_CPO_SENSOR, getFPGACPo(), getFPGACPoReadCount(), getFPGACPoErrorCount(), getFPGACPoFault() ); if ( ++conductivityDataPublicationTimerCounter >= getConductivityDataPublishInterval() ) { conductivityDataPublicationTimerCounter = 0; calcRORejectionRatio(); broadcastConductivityData( getRORejectionRatio(), getConductivityValue(CONDUCTIVITYSENSORS_CPI_SENSOR), getConductivityValue(CONDUCTIVITYSENSORS_CPO_SENSOR) ); } } /************************************************************************* * @brief * The checkInletWaterConductivity checks inlet water conductivity value * and triggers an alarm when conductivity value is out of allowed range. * @details * Inputs : none * Outputs : Trigger alarms when conductivity is out of allowed range * @return none *************************************************************************/ void checkInletWaterConductivity( void ) { F32 const conductivity = getConductivityValue( CONDUCTIVITYSENSORS_CPI_SENSOR ); BOOL const isCondTooHigh = ( conductivity > COND_SENSOR_CPI_CPO_MAX_VALUE ); BOOL const isCondTooLow = ( conductivity < COND_SENSOR_CPI_CPO_MIN_VALUE ); checkPersistentAlarm( ALARM_ID_INLET_WATER_HIGH_CONDUCTIVITY, isCondTooHigh, conductivity ); checkPersistentAlarm( ALARM_ID_INLET_WATER_LOW_CONDUCTIVITY, isCondTooLow, conductivity ); } /************************************************************************* * @brief * The checkRORejectionRatio checks RO rejection ratio and outlet water * conductivity. The function triggers an alarm when RO rejection ratio or * outlet water conductivity is out of allowed range for period of time. * @details * Inputs : none * Outputs : Triggered alarm * @return none *************************************************************************/ void checkRORejectionRatio( void ) { F32 const roRejectionRatio = getRORejectionRatio(); F32 const cpo = getConductivityValue( CONDUCTIVITYSENSORS_CPO_SENSOR ); BOOL const isRORejectionRatioOutOfRange = ( roRejectionRatio > MAX_RO_REJECTION_RATIO_ALLOW ) || ( cpo >= MAX_CPO_CONDUCTIVITY_ALLOW ); checkPersistentAlarm( ALARM_ID_RO_REJECTION_RATIO_OUT_OF_RANGE, isRORejectionRatioOutOfRange, roRejectionRatio ); } /************************************************************************* * @brief * The getConductivityValue function gets the compensated conductivity * value for a given conductivity sensor id. * @details * Inputs : compensatedConductivityValues[] * Outputs : none * @param sensorId Id of conductivity sensor to get conductivity value * @return compensated conductivity *************************************************************************/ F32 getConductivityValue( U32 sensorId ) { F32 result = 0; if ( sensorId < NUM_OF_CONDUCTIVITY_SENSORS ) { if ( OVERRIDE_KEY == compensatedConductivityValues[ sensorId ].override ) { result = compensatedConductivityValues[ sensorId ].ovData; } else { result = compensatedConductivityValues[ sensorId ].data; } } else { activateAlarmNoData( ALARM_ID_DG_SOFTWARE_FAULT ); } return result; } /************************************************************************* * @brief * The calcCompensatedConductivity function calculates the compensated * conductivity based on given temperature and conductivity taken at * reference temperature of 25 Celcius. * @details * Inputs : temperature * Outputs : none * @param conductivity Conductivity value * @param temperature Temperature to compensate conductivity with * @return compensated conductivity based on temperature *************************************************************************/ static F32 calcCompensatedConductivity( F32 conductivity, F32 temperature) { // EC = EC_25 * (1 + temp_coef * (temperature - 25)) F32 const compensatedCoef = ( 1 + ( COND_SENSOR_TEMPERATURE_COEF * (temperature - COND_SENSOR_REFERENCE_TEMPERATURE) ) ); return conductivity * compensatedCoef; } /************************************************************************* * @brief * The getRORejectionRatio function gets the latest RO rejection ratio * @details * Inputs: roRejectionRatio * Outputs: none * @return RO rejection ratio *************************************************************************/ F32 getRORejectionRatio( void ) { F32 result = roRejectionRatio.data; if ( OVERRIDE_KEY == roRejectionRatio.override ) { result = roRejectionRatio.ovData; } return result; } /************************************************************************* * @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 and CPo sensor conductivity * Outputs: RO rejection ratio * @return none *************************************************************************/ static void calcRORejectionRatio( void ) { F32 const cpi = getConductivityValue( CONDUCTIVITYSENSORS_CPI_SENSOR ); F32 const cpo = getConductivityValue( CONDUCTIVITYSENSORS_CPO_SENSOR ); roRejectionRatio.data = RO_REJECTION_RATIO_OUT_OF_RANGE_VALUE; if ( cpi > 0 ) { roRejectionRatio.data = 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 * Outputs : none * @param sensorId Conductivity sensor id to process * @param fgpaRead FPGA conductivity reading value * @param fpgaReadCount FPGA read count * @param fpgaErrorCount FPGA error count * @return none *************************************************************************/ static void processCPiCPoSensorRead( U32 sensorId, U32 fgpaRead, U08 fpgaReadCount, U08 fpgaErrorCount, U08 fpgaSensorFault ) { if ( ( fpgaErrorCount == 0 ) && ( fpgaSensorFault == 0 ) ) { if ( ( readCount[ sensorId ] != fpgaReadCount ) ) { F32 const temperature = getTemperatureValue( associateTempSensor[ sensorId ] ); F32 const conductivity = ( (F32)( fgpaRead ) / COND_SENSOR_DECIMAL_CONVERSION ); readCount[ sensorId ] = fpgaReadCount; internalErrorCount[ sensorId ] = 0; compensatedConductivityValues[ sensorId ].data = calcCompensatedConductivity( conductivity, temperature ); } else { ++internalErrorCount[ sensorId ]; if ( internalErrorCount[ sensorId ] > MAX_ALLOWED_UNCHANGED_CONDUCTIVITY_READS ) { SET_ALARM_WITH_1_U32_DATA( ALARM_ID_CONDUCTIVITY_SENSOR_FAULT, sensorId ); } } } else { if ( TRUE == incTimeWindowedCount( TIME_WINDOWED_COUNT_FPGA_CONDUCTIVITY_SENSOR_ERROR ) ) { SET_ALARM_WITH_1_U32_DATA( ALARM_ID_CONDUCTIVITY_SENSOR_FAULT, sensorId ); } } } /************************************************************************* * @brief * The getConductivityDataPublishInterval function gets the conductivity * data publication interval. * @details * Inputs : conductivityDataPublishInterval * Outputs : none * @return the current conductivity data publication interval (in ms/task interval). *************************************************************************/ static U32 getConductivityDataPublishInterval( void ) { U32 result = conductivityDataPublishInterval.data; if ( OVERRIDE_KEY == conductivityDataPublishInterval.override ) { result = conductivityDataPublishInterval.ovData; } return result; } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /************************************************************************* * @brief * The testSetConductivityOverride function overrides the compensated * conductivity value of given sensor id. * @details * Inputs : compensatedConductivityValues[] * Outputs : compensatedConductivityValues[] * @param sensorId Id of conductivity sensor to get conductivity value * @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 ( isTestingActivated() ) { result = TRUE; compensatedConductivityValues[ sensorId ].ovData = value; compensatedConductivityValues[ sensorId ].override = OVERRIDE_KEY; } } return result; } /************************************************************************* * @brief * The testResetConductivityOverride function resets the override of the \n * conductivity sensor value. * @details * Inputs : compensatedConductivityValues[] * Outputs : compensatedConductivityValues[] * @param sensorId Id of the conductivity sensor to override. * @return TRUE if reset successful, FALSE if not *************************************************************************/ BOOL testResetConductivityOverride( U32 sensorId ) { BOOL result = FALSE; if ( sensorId < NUM_OF_CONDUCTIVITY_SENSORS ) { if ( isTestingActivated() ) { result = TRUE; compensatedConductivityValues[ sensorId ].ovData = compensatedConductivityValues[ sensorId ].ovInitData; compensatedConductivityValues[ sensorId ].override = OVERRIDE_RESET; } } return result; } /************************************************************************* * @brief * The testSetRORejectionRatioOverride function overrides the RO Rejection ratio. * @details * Inputs : roRejectionRatio * Outputs : roRejectionRatio * @param value Override RO Rejection ratio value * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testSetRORejectionRatioOverride( F32 value ) { BOOL result = FALSE; if ( isTestingActivated() ) { result = TRUE; roRejectionRatio.ovData = value; roRejectionRatio.override = OVERRIDE_KEY; } return result; } /************************************************************************* * @brief * The testResetRORejectionRatioOverride function resets the override of * the RO Rejection ratio. * @details * Inputs : roRejectionRatio * Outputs : roRejectionRatio * @return TRUE if reset successful, FALSE if not *************************************************************************/ BOOL testResetRORejectionRatioOverride( void ) { BOOL result = FALSE; if ( isTestingActivated() ) { result = TRUE; roRejectionRatio.ovData = roRejectionRatio.ovInitData; roRejectionRatio.override = OVERRIDE_RESET; } return result; } /************************************************************************* * @brief * The testSetConductivityDataPublishIntervalOverride function overrides * the conductivity data publish interval. * @details * Inputs : conductivityDataPublishInterval * 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 ( 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 * Outputs : conductivityDataPublishInterval * @return TRUE if override reset successful, FALSE if not *************************************************************************/ BOOL testResetConductivityDataPublishIntervalOverride( void ) { BOOL result = FALSE; if ( isTestingActivated() ) { result = TRUE; conductivityDataPublishInterval.ovData = conductivityDataPublishInterval.ovInitData; conductivityDataPublishInterval.override = OVERRIDE_RESET; } return result; } /**@}*/