safety state and input error detection
This commit is contained in:
		
							
								
								
									
										7
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @ -12,7 +12,12 @@ | ||||
|         "inputs.h": "c", | ||||
|         "cstdlib": "c", | ||||
|         "typeinfo": "c", | ||||
|         "limits": "c" | ||||
|         "limits": "c", | ||||
|         "bit": "c", | ||||
|         "type_traits": "c", | ||||
|         "cmath": "c", | ||||
|         "*.tcc": "c", | ||||
|         "*.inc": "c" | ||||
|     }, | ||||
|     "idf.openOcdConfigs": [ | ||||
|         "board/esp32-wrover-kit-3.3v.cfg" | ||||
|  | ||||
							
								
								
									
										218
									
								
								main/inputs.c
									
									
									
									
									
								
							
							
						
						
									
										218
									
								
								main/inputs.c
									
									
									
									
									
								
							| @ -6,27 +6,8 @@ | ||||
|  | ||||
| #include "inputs.h" | ||||
|  | ||||
| #define MAX(a, b) ((a) > (b) ? (a) : (b)) | ||||
|  | ||||
| #define MAX_DN18B20_SENSORS 4U | ||||
| #define PERIODIC_INTERVAL 1U // read and compute the inputs every 1sec | ||||
| #define AVG10_SAMPLE_SIZE 10U | ||||
| #define AVG60_SAMPLE_SIZE 60U | ||||
|  | ||||
| typedef struct _Average | ||||
| { | ||||
|     float value; | ||||
|     float samples[MAX(AVG10_SAMPLE_SIZE, AVG60_SAMPLE_SIZE)]; | ||||
|     size_t bufferIndex; | ||||
|     size_t bufferCount; | ||||
| } sAverage; | ||||
|  | ||||
| typedef struct _Measurement | ||||
| { | ||||
|     float value; | ||||
|     sAverage average10s; | ||||
|     sAverage average60s; | ||||
| } sMeasurement; | ||||
|  | ||||
| static const char *TAG = "smart-oil-heater-control-system-inputs"; | ||||
| const uint8_t uBurnerFaultPin = 19U; | ||||
| @ -43,10 +24,10 @@ size_t sSensorCount = 0U; | ||||
|  | ||||
| static SemaphoreHandle_t xMutexAccessInputs = NULL; | ||||
| static eBurnerErrorState sBurnerErrorState; | ||||
| static sMeasurement fChamperTemperature; | ||||
| static sMeasurement fOutdoorTemperature; | ||||
| static sMeasurement fInletFlowTemperature; | ||||
| static sMeasurement fReturnFlowTemperature; | ||||
| static sMeasurement sChamperTemperature; | ||||
| static sMeasurement sOutdoorTemperature; | ||||
| static sMeasurement sInletFlowTemperature; | ||||
| static sMeasurement sReturnFlowTemperature; | ||||
|  | ||||
| void taskInput(void *pvParameters); | ||||
| void updateAverage(sMeasurement *pMeasurement); | ||||
| @ -92,7 +73,7 @@ void initInputs(void) | ||||
|  | ||||
| void updateAverage(sMeasurement *pMeasurement) | ||||
| { /* Average form the last 10sec */ | ||||
|     pMeasurement->average10s.samples[pMeasurement->average10s.bufferIndex] = pMeasurement->value; | ||||
|     pMeasurement->average10s.samples[pMeasurement->average10s.bufferIndex] = pMeasurement->fCurrentValue; | ||||
|     pMeasurement->average10s.bufferIndex = (pMeasurement->average10s.bufferIndex + 1) % AVG10_SAMPLE_SIZE; | ||||
|  | ||||
|     if (pMeasurement->average10s.bufferCount < AVG10_SAMPLE_SIZE) | ||||
| @ -102,7 +83,7 @@ void updateAverage(sMeasurement *pMeasurement) | ||||
|  | ||||
|     if (pMeasurement->average10s.bufferCount == 0U) | ||||
|     { | ||||
|         pMeasurement->average10s.value = pMeasurement->value; | ||||
|         pMeasurement->average10s.fValue = pMeasurement->fCurrentValue; | ||||
|     } | ||||
|  | ||||
|     float sum = 0.0; | ||||
| @ -111,10 +92,10 @@ void updateAverage(sMeasurement *pMeasurement) | ||||
|         sum += pMeasurement->average10s.samples[i]; | ||||
|     } | ||||
|  | ||||
|     pMeasurement->average10s.value = sum / pMeasurement->average10s.bufferCount; | ||||
|     pMeasurement->average10s.fValue = sum / pMeasurement->average10s.bufferCount; | ||||
|  | ||||
|     /* Average form the last 60sec */ | ||||
|     pMeasurement->average60s.samples[pMeasurement->average60s.bufferIndex] = pMeasurement->value; | ||||
|     pMeasurement->average60s.samples[pMeasurement->average60s.bufferIndex] = pMeasurement->fCurrentValue; | ||||
|     pMeasurement->average60s.bufferIndex = (pMeasurement->average60s.bufferIndex + 1) % AVG60_SAMPLE_SIZE; | ||||
|  | ||||
|     if (pMeasurement->average60s.bufferCount < AVG60_SAMPLE_SIZE) | ||||
| @ -124,7 +105,7 @@ void updateAverage(sMeasurement *pMeasurement) | ||||
|  | ||||
|     if (pMeasurement->average60s.bufferCount == 0U) | ||||
|     { | ||||
|         pMeasurement->average60s.value = pMeasurement->value; | ||||
|         pMeasurement->average60s.fValue = pMeasurement->fCurrentValue; | ||||
|     } | ||||
|  | ||||
|     sum = 0.0; | ||||
| @ -133,7 +114,7 @@ void updateAverage(sMeasurement *pMeasurement) | ||||
|         sum += pMeasurement->average60s.samples[i]; | ||||
|     } | ||||
|  | ||||
|     pMeasurement->average60s.value = sum / pMeasurement->average60s.bufferCount; | ||||
|     pMeasurement->average60s.fValue = sum / pMeasurement->average60s.bufferCount; | ||||
| } | ||||
|  | ||||
| void taskInput(void *pvParameters) | ||||
| @ -141,168 +122,133 @@ void taskInput(void *pvParameters) | ||||
|     while (1) | ||||
|     { | ||||
|         vTaskDelay(PERIODIC_INTERVAL * 1000U / portTICK_PERIOD_MS); | ||||
|         if (xSemaphoreTake(xMutexAccessInputs, portMAX_DELAY) == pdTRUE) | ||||
|         { | ||||
|             sChamperTemperature.state = MEASUREMENT_FAULT; | ||||
|             sOutdoorTemperature.state = MEASUREMENT_FAULT; | ||||
|             sInletFlowTemperature.state = MEASUREMENT_FAULT; | ||||
|             sReturnFlowTemperature.state = MEASUREMENT_FAULT; | ||||
|  | ||||
|         if (gpio_get_level(uBurnerFaultPin) == 1) | ||||
|         { | ||||
|             sBurnerErrorState = FAULT; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             sBurnerErrorState = NO_ERROR; | ||||
|         } | ||||
|  | ||||
|         if (ds18x20_scan_devices(uDS18B20Pin, uOneWireAddresses, MAX_DN18B20_SENSORS, &sSensorCount) != ESP_OK) | ||||
|         { | ||||
|             ESP_LOGE(TAG, "1-Wire device scan error!"); | ||||
|         } | ||||
|  | ||||
|         if (!sSensorCount) | ||||
|         { | ||||
|             ESP_LOGW(TAG, "No 1-Wire devices detected!"); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             ESP_LOGI(TAG, "%d 1-Wire devices detected", sSensorCount); | ||||
|  | ||||
|             if (sSensorCount > MAX_DN18B20_SENSORS) | ||||
|             if (gpio_get_level(uBurnerFaultPin) == 1) | ||||
|             { | ||||
|                 sSensorCount = MAX_DN18B20_SENSORS; | ||||
|                 ESP_LOGW(TAG, "More 1-Wire devices found than expected!"); | ||||
|             } | ||||
|  | ||||
|             if (ds18x20_measure_and_read_multi(uDS18B20Pin, uOneWireAddresses, sSensorCount, fDS18B20Temps) != ESP_OK) | ||||
|             { | ||||
|                 ESP_LOGE(TAG, "1-Wire devices read error"); | ||||
|                 sBurnerErrorState = FAULT; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 for (int j = 0; j < sSensorCount; j++) | ||||
|                 sBurnerErrorState = NO_ERROR; | ||||
|             } | ||||
|  | ||||
|             if (ds18x20_scan_devices(uDS18B20Pin, uOneWireAddresses, MAX_DN18B20_SENSORS, &sSensorCount) != ESP_OK) | ||||
|             { | ||||
|                 ESP_LOGE(TAG, "1-Wire device scan error!"); | ||||
|             } | ||||
|  | ||||
|             if (!sSensorCount) | ||||
|             { | ||||
|                 ESP_LOGW(TAG, "No 1-Wire devices detected!"); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 ESP_LOGI(TAG, "%d 1-Wire devices detected", sSensorCount); | ||||
|  | ||||
|                 if (sSensorCount > MAX_DN18B20_SENSORS) | ||||
|                 { | ||||
|                     float temp_c = fDS18B20Temps[j]; | ||||
|                     ESP_LOGI(TAG, "Sensor: %08" PRIx64 " reports %lf°C", (uint64_t)uOneWireAddresses[j], temp_c); | ||||
|                     if (xSemaphoreTake(xMutexAccessInputs, portMAX_DELAY) == pdTRUE) | ||||
|                     sSensorCount = MAX_DN18B20_SENSORS; | ||||
|                     ESP_LOGW(TAG, "More 1-Wire devices found than expected!"); | ||||
|                 } | ||||
|  | ||||
|                 if (ds18x20_measure_and_read_multi(uDS18B20Pin, uOneWireAddresses, sSensorCount, fDS18B20Temps) != ESP_OK) | ||||
|                 { | ||||
|                     ESP_LOGE(TAG, "1-Wire devices read error"); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     for (int j = 0; j < sSensorCount; j++) | ||||
|                     { | ||||
|                         float temp_c = fDS18B20Temps[j]; | ||||
|                         ESP_LOGI(TAG, "Sensor: %08" PRIx64 " reports %lf°C", (uint64_t)uOneWireAddresses[j], temp_c); | ||||
|  | ||||
|                         switch ((uint64_t)uOneWireAddresses[j]) | ||||
|                         { | ||||
|                         case ((uint64_t)uChamperTempSensorAddr): | ||||
|                             fChamperTemperature.value = temp_c; | ||||
|                             updateAverage(&fChamperTemperature); | ||||
|                             sChamperTemperature.fCurrentValue = temp_c; | ||||
|                             sChamperTemperature.state = MEASUREMENT_NO_ERROR; | ||||
|                             updateAverage(&sChamperTemperature); | ||||
|                             break; | ||||
|                         case ((uint64_t)uOutdoorTempSensorAddr): | ||||
|                             fOutdoorTemperature.value = temp_c; | ||||
|                             updateAverage(&fOutdoorTemperature); | ||||
|                             sOutdoorTemperature.fCurrentValue = temp_c; | ||||
|                             sOutdoorTemperature.state = MEASUREMENT_NO_ERROR; | ||||
|                             updateAverage(&sOutdoorTemperature); | ||||
|                             break; | ||||
|                         case ((uint64_t)uInletFlowTempSensorAddr): | ||||
|                             fInletFlowTemperature.value = temp_c; | ||||
|                             updateAverage(&fInletFlowTemperature); | ||||
|                             sInletFlowTemperature.fCurrentValue = temp_c; | ||||
|                             sInletFlowTemperature.state = MEASUREMENT_NO_ERROR; | ||||
|                             updateAverage(&sInletFlowTemperature); | ||||
|                             break; | ||||
|                         case ((uint64_t)uReturnFlowTempSensorAddr): | ||||
|                             fReturnFlowTemperature.value = temp_c; | ||||
|                             updateAverage(&fReturnFlowTemperature); | ||||
|                             sReturnFlowTemperature.fCurrentValue = temp_c; | ||||
|                             sReturnFlowTemperature.state = MEASUREMENT_NO_ERROR; | ||||
|                             updateAverage(&sReturnFlowTemperature); | ||||
|                             break; | ||||
|                         default: | ||||
|                             break; | ||||
|                         } | ||||
|                         xSemaphoreGive(xMutexAccessInputs); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             xSemaphoreGive(xMutexAccessInputs); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| float getChamberTemperature(eMeasurementMode mode) | ||||
| sMeasurement getChamberTemperature(void) | ||||
| { | ||||
|     float ret = 0.0f; | ||||
|     sMeasurement ret; | ||||
|     ret.state = MEASUREMENT_FAULT; | ||||
|     if (xSemaphoreTake(xMutexAccessInputs, portMAX_DELAY) == pdTRUE) | ||||
|     { | ||||
|         switch (mode) | ||||
|         { | ||||
|         case CURRENT: | ||||
|             ret = fChamperTemperature.value; | ||||
|             break; | ||||
|         case AVERAGE_10S: | ||||
|             ret = fChamperTemperature.average10s.value; | ||||
|             break; | ||||
|         case AVERAGE_60S: | ||||
|             ret = fChamperTemperature.average60s.value; | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|         ret = sChamperTemperature; | ||||
|         xSemaphoreGive(xMutexAccessInputs); | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| float getOutdoorTemperature(eMeasurementMode mode) | ||||
|  | ||||
| sMeasurement getOutdoorTemperature(void) | ||||
| { | ||||
|     float ret = 0.0f; | ||||
|     sMeasurement ret; | ||||
|     ret.state = MEASUREMENT_FAULT; | ||||
|     if (xSemaphoreTake(xMutexAccessInputs, portMAX_DELAY) == pdTRUE) | ||||
|     { | ||||
|         switch (mode) | ||||
|         { | ||||
|         case CURRENT: | ||||
|             ret = fOutdoorTemperature.value; | ||||
|             break; | ||||
|         case AVERAGE_10S: | ||||
|             ret = fOutdoorTemperature.average10s.value; | ||||
|             break; | ||||
|         case AVERAGE_60S: | ||||
|             ret = fOutdoorTemperature.average60s.value; | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|         ret = sOutdoorTemperature; | ||||
|         xSemaphoreGive(xMutexAccessInputs); | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| float getInletFlowTemperature(eMeasurementMode mode) | ||||
|  | ||||
| sMeasurement getInletFlowTemperature(void) | ||||
| { | ||||
|     float ret = 0.0f; | ||||
|     sMeasurement ret; | ||||
|     ret.state = MEASUREMENT_FAULT; | ||||
|     if (xSemaphoreTake(xMutexAccessInputs, portMAX_DELAY) == pdTRUE) | ||||
|     { | ||||
|         switch (mode) | ||||
|         { | ||||
|         case CURRENT: | ||||
|             ret = fInletFlowTemperature.value; | ||||
|             break; | ||||
|         case AVERAGE_10S: | ||||
|             ret = fInletFlowTemperature.average10s.value; | ||||
|             break; | ||||
|         case AVERAGE_60S: | ||||
|             ret = fInletFlowTemperature.average60s.value; | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|         ret = sInletFlowTemperature; | ||||
|         xSemaphoreGive(xMutexAccessInputs); | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| float getReturnFlowTemperature(eMeasurementMode mode) | ||||
|  | ||||
| sMeasurement getReturnFlowTemperature(void) | ||||
| { | ||||
|     float ret = 0.0f; | ||||
|     sMeasurement ret; | ||||
|     ret.state = MEASUREMENT_FAULT; | ||||
|     if (xSemaphoreTake(xMutexAccessInputs, portMAX_DELAY) == pdTRUE) | ||||
|     { | ||||
|         switch (mode) | ||||
|         { | ||||
|         case CURRENT: | ||||
|             ret = fReturnFlowTemperature.value; | ||||
|             break; | ||||
|         case AVERAGE_10S: | ||||
|             ret = fReturnFlowTemperature.average10s.value; | ||||
|             break; | ||||
|         case AVERAGE_60S: | ||||
|             ret = fReturnFlowTemperature.average60s.value; | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|         ret = sReturnFlowTemperature; | ||||
|         xSemaphoreGive(xMutexAccessInputs); | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| eBurnerErrorState getBurnerError(void) | ||||
| { | ||||
|     eBurnerErrorState ret = FAULT; | ||||
|  | ||||
| @ -1,21 +1,40 @@ | ||||
| #pragma once | ||||
|  | ||||
| #define MAX(a, b) ((a) > (b) ? (a) : (b)) | ||||
| #define AVG10_SAMPLE_SIZE 10U | ||||
| #define AVG60_SAMPLE_SIZE 60U | ||||
|  | ||||
| typedef enum _BurnerErrorState | ||||
| { | ||||
|     NO_ERROR, | ||||
|     FAULT | ||||
| } eBurnerErrorState; | ||||
|  | ||||
| typedef enum _MeasurementMode | ||||
| typedef enum _MeasurementErrorState | ||||
| { | ||||
|     CURRENT, | ||||
|     AVERAGE_10S, | ||||
|     AVERAGE_60S | ||||
| } eMeasurementMode; | ||||
|     MEASUREMENT_NO_ERROR, | ||||
|     MEASUREMENT_FAULT | ||||
| } eMeasurementErrorState; | ||||
|  | ||||
| typedef struct _Average | ||||
| { | ||||
|     float fValue; | ||||
|     float samples[MAX(AVG10_SAMPLE_SIZE, AVG60_SAMPLE_SIZE)]; | ||||
|     size_t bufferIndex; | ||||
|     size_t bufferCount; | ||||
| } sAverage; | ||||
|  | ||||
| typedef struct _Measurement | ||||
| { | ||||
|     float fCurrentValue; | ||||
|     sAverage average10s; | ||||
|     sAverage average60s; | ||||
|     eMeasurementErrorState state; | ||||
| } sMeasurement; | ||||
|  | ||||
| void initInputs(void); | ||||
| float getChamberTemperature(eMeasurementMode mode); | ||||
| float getOutdoorTemperature(eMeasurementMode mode); | ||||
| float getInletFlowTemperature(eMeasurementMode mode); | ||||
| float getReturnFlowTemperature(eMeasurementMode mode); | ||||
| sMeasurement getChamberTemperature(void); | ||||
| sMeasurement getOutdoorTemperature(void); | ||||
| sMeasurement getInletFlowTemperature(void); | ||||
| sMeasurement getReturnFlowTemperature(void); | ||||
| eBurnerErrorState getBurnerError(void); | ||||
| @ -19,6 +19,7 @@ | ||||
| #include "metrics.h" | ||||
| #include "outputs.h" | ||||
| #include "inputs.h" | ||||
| #include "safety.h" | ||||
|  | ||||
| static EventGroupHandle_t s_wifi_event_group; | ||||
|  | ||||
| @ -42,7 +43,7 @@ void initMetrics(void) | ||||
|     BaseType_t taskCreated = xTaskCreate( | ||||
|         taskMetrics,   // Function to implement the task | ||||
|         "taskMetrics", // Task name | ||||
|         4096,          // Stack size (in words, not bytes) | ||||
|         16384,         // Stack size (in words, not bytes) | ||||
|         NULL,          // Parameters to the task function (none in this case) | ||||
|         5,             // Task priority (higher number = higher priority) | ||||
|         NULL           // Task handle (optional) | ||||
| @ -123,62 +124,78 @@ void taskMetrics(void *pvParameters) | ||||
|  | ||||
|         /*Chamber Temperature*/ | ||||
|         strcpy(aMetrics[u16MetricCounter].caMetricName, "chamber_temperature"); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getChamberTemperature(CURRENT); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getChamberTemperature().fCurrentValue; | ||||
|         u16MetricCounter++; | ||||
|  | ||||
|         /*Outdoor Temperature*/ | ||||
|         strcpy(aMetrics[u16MetricCounter].caMetricName, "outdoor_temperature"); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getOutdoorTemperature(CURRENT); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getOutdoorTemperature().fCurrentValue; | ||||
|         u16MetricCounter++; | ||||
|  | ||||
|         /*Chamber Temperature*/ | ||||
|         strcpy(aMetrics[u16MetricCounter].caMetricName, "inlet_flow_temperature"); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getInletFlowTemperature(CURRENT); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getInletFlowTemperature().fCurrentValue; | ||||
|         u16MetricCounter++; | ||||
|  | ||||
|         /*Chamber Temperature*/ | ||||
|         strcpy(aMetrics[u16MetricCounter].caMetricName, "return_flow_temperature"); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature(CURRENT); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature().fCurrentValue; | ||||
|         u16MetricCounter++; | ||||
|  | ||||
|         /*Chamber Temperature Average 10s*/ | ||||
|         strcpy(aMetrics[u16MetricCounter].caMetricName, "chamber_temperature_avg10"); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getChamberTemperature(AVERAGE_10S); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getChamberTemperature().average10s.fValue; | ||||
|         u16MetricCounter++; | ||||
|  | ||||
|         /*Outdoor Temperature Average 10s*/ | ||||
|         strcpy(aMetrics[u16MetricCounter].caMetricName, "outdoor_temperature_avg10"); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getOutdoorTemperature(AVERAGE_10S); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getOutdoorTemperature().average10s.fValue; | ||||
|         u16MetricCounter++; | ||||
|  | ||||
|         /*Chamber Temperature Average 10s*/ | ||||
|         strcpy(aMetrics[u16MetricCounter].caMetricName, "inlet_flow_temperature_avg10"); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getInletFlowTemperature(AVERAGE_10S); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getInletFlowTemperature().average10s.fValue; | ||||
|         u16MetricCounter++; | ||||
|  | ||||
|         /*Chamber Temperature Average 10s*/ | ||||
|         strcpy(aMetrics[u16MetricCounter].caMetricName, "return_flow_temperature_avg10"); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature(AVERAGE_10S); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature().average10s.fValue; | ||||
|         u16MetricCounter++; | ||||
|  | ||||
|                 /*Chamber Temperature Average 60s*/ | ||||
|         /*Chamber Temperature Average 60s*/ | ||||
|         strcpy(aMetrics[u16MetricCounter].caMetricName, "chamber_temperature_avg60"); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getChamberTemperature(AVERAGE_60S); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getChamberTemperature().average60s.fValue; | ||||
|         u16MetricCounter++; | ||||
|  | ||||
|         /*Outdoor Temperature Average 60s*/ | ||||
|         strcpy(aMetrics[u16MetricCounter].caMetricName, "outdoor_temperature_avg60"); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getOutdoorTemperature(AVERAGE_60S); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getOutdoorTemperature().average60s.fValue; | ||||
|         u16MetricCounter++; | ||||
|  | ||||
|         /*Chamber Temperature Average 60s*/ | ||||
|         strcpy(aMetrics[u16MetricCounter].caMetricName, "inlet_flow_temperature_avg60"); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getInletFlowTemperature(AVERAGE_60S); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getInletFlowTemperature().average60s.fValue; | ||||
|         u16MetricCounter++; | ||||
|  | ||||
|         /*Chamber Temperature Average 60s*/ | ||||
|         strcpy(aMetrics[u16MetricCounter].caMetricName, "return_flow_temperature_avg60"); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature(AVERAGE_60S); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature().average60s.fValue; | ||||
|         u16MetricCounter++; | ||||
|  | ||||
|         /*Sensor status*/ | ||||
|         sSensorSanityCheck aChecks[NUMBER_OF_SENSOR_SANITY_CHECKS]; | ||||
|         getSensorSanityStates(aChecks); | ||||
|         for (size_t i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++) | ||||
|         { | ||||
|             strcpy(aMetrics[u16MetricCounter].caMetricName, aChecks[i].name); | ||||
|             strcat(aMetrics[u16MetricCounter].caMetricName, "_status"); | ||||
|             aMetrics[u16MetricCounter].fMetricValue = aChecks[i].status; | ||||
|             u16MetricCounter++; | ||||
|         } | ||||
|  | ||||
|         /*Safety state*/ | ||||
|         strcpy(aMetrics[u16MetricCounter].caMetricName, "safety_state"); | ||||
|         aMetrics[u16MetricCounter].fMetricValue = getSafetyState(); | ||||
|         u16MetricCounter++; | ||||
|  | ||||
|         vSetMetrics(aMetrics, u16MetricCounter); | ||||
|  | ||||
							
								
								
									
										102
									
								
								main/safety.c
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								main/safety.c
									
									
									
									
									
								
							| @ -1,6 +1,7 @@ | ||||
| #include "freertos/FreeRTOS.h" | ||||
| #include "freertos/task.h" | ||||
| #include "esp_log.h" | ||||
| #include <string.h> | ||||
| #include "safety.h" | ||||
|  | ||||
| #define PERIODIC_INTERVAL 1U            // run safety checks every 1sec | ||||
| @ -8,11 +9,12 @@ | ||||
|  | ||||
| static const char *TAG = "smart-oil-heater-control-system-safety"; | ||||
| static SemaphoreHandle_t xMutexAccessSafety = NULL; | ||||
| sSensorSanityCheck sanityChecks[NUMBER_OF_SENSOR_SANITY_CHECKS] = { | ||||
|     {0U, "chamber_temperature", {95.0f, -10.0f}, 0.0f, 0U, getChamberTemperature}, | ||||
|     {0U, "outdoor_temperature", {45.0f, -20.0f}, 0.0f, 0U, getOutdoorTemperature}, | ||||
|     {0U, "inlet_flow_temperature", {95.0f, -10.0f}, 0.0f, 0U, getInletFlowTemperature}, | ||||
|     {0U, "return_flow_temperature", {95.0f, -10.0f}, 0.0f, 0U, getReturnFlowTemperature}}; | ||||
| static sSensorSanityCheck sanityChecks[NUMBER_OF_SENSOR_SANITY_CHECKS] = { | ||||
|     {SENSOR_NO_ERROR, "chamber_temperature", {95.0f, -10.0f}, 0.0f, 0U, getChamberTemperature}, | ||||
|     {SENSOR_NO_ERROR, "outdoor_temperature", {45.0f, -20.0f}, 0.0f, 0U, getOutdoorTemperature}, | ||||
|     {SENSOR_NO_ERROR, "inlet_flow_temperature", {95.0f, -10.0f}, 0.0f, 0U, getInletFlowTemperature}, | ||||
|     {SENSOR_NO_ERROR, "return_flow_temperature", {95.0f, -10.0f}, 0.0f, 0U, getReturnFlowTemperature}}; | ||||
| static eSafetyState sSafetyState = SAFETY_NO_ERROR; | ||||
|  | ||||
| void taskSafety(void *pvParameters); | ||||
| void checkSensorSanity(void); | ||||
| @ -30,7 +32,7 @@ void initSafety(void) | ||||
|     BaseType_t taskCreated = xTaskCreate( | ||||
|         taskSafety,   // Function to implement the task | ||||
|         "taskSafety", // Task name | ||||
|         2048,         // Stack size (in words, not bytes) | ||||
|         4096,         // Stack size (in words, not bytes) | ||||
|         NULL,         // Parameters to the task function (none in this case) | ||||
|         5,            // Task priority (higher number = higher priority) | ||||
|         NULL          // Task handle (optional) | ||||
| @ -52,13 +54,18 @@ void taskSafety(void *pvParameters) | ||||
|     { | ||||
|         vTaskDelay(PERIODIC_INTERVAL * 1000U / portTICK_PERIOD_MS); | ||||
|  | ||||
|         checkSensorSanity(); | ||||
|         if (xSemaphoreTake(xMutexAccessSafety, portMAX_DELAY) == pdTRUE) | ||||
|         { | ||||
|  | ||||
|             checkSensorSanity(); | ||||
|             xSemaphoreGive(xMutexAccessSafety); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void checkSensorSanity(void) | ||||
| { | ||||
|  | ||||
|     sSafetyState = SAFETY_NO_ERROR; | ||||
|     for (int i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++) | ||||
|     { | ||||
|         // printf("Check sanity of sensor %s:\n", sanityChecks[i].name); | ||||
| @ -66,34 +73,47 @@ void checkSensorSanity(void) | ||||
|         // printf("  Sensor Limits: Max = %.2f, Min = %.2f\n", sanityChecks[i].sSensorLimit.max, sanityChecks[i].sSensorLimit.min); | ||||
|         // printf("  Last Sensor Temperature: %.2f\n", sanityChecks[i].fSensorTemperatureLast); | ||||
|  | ||||
|         const float fSensorTemperatureCurrent = sanityChecks[i].getSensor(CURRENT); | ||||
|         if (fSensorTemperatureCurrent == sanityChecks[i].fSensorTemperatureLast) | ||||
|         const sMeasurement sCurrentMeasurement = sanityChecks[i].getSensor(); | ||||
|  | ||||
|         if (sCurrentMeasurement.state == MEASUREMENT_FAULT) | ||||
|         { | ||||
|             sanityChecks[i].uUnchangedCounter++; | ||||
|             if (sanityChecks[i].uUnchangedCounter >= (SENSOR_GRACE_PERIOD / PERIODIC_INTERVAL)) | ||||
|             { | ||||
|                 ESP_LOGE(TAG, "%s Sensor reported unchanged value! %lf == %lf", sanityChecks[i].name, fSensorTemperatureCurrent, sanityChecks[i].fSensorTemperatureLast); | ||||
|                 sanityChecks[i].status = 1U; | ||||
|             } | ||||
|             ESP_LOGE(TAG, "%s Sensor not found!", sanityChecks[i].name); | ||||
|             sanityChecks[i].status = SENSOR_NOT_FOUND; | ||||
|             sSafetyState = SAFETY_SENSOR_ERROR; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             sanityChecks[i].fSensorTemperatureLast = fSensorTemperatureCurrent; | ||||
|  | ||||
|             if (fSensorTemperatureCurrent > sanityChecks[i].sSensorLimit.max) | ||||
|             if (sCurrentMeasurement.fCurrentValue == sanityChecks[i].fSensorTemperatureLast) | ||||
|             { | ||||
|                 ESP_LOGE(TAG, "%s Sensor reported too high value! %lf > %lf", sanityChecks[i].name, fSensorTemperatureCurrent, sanityChecks[i].sSensorLimit.max); | ||||
|                 sanityChecks[i].status = 1U; | ||||
|             } | ||||
|             else if (fSensorTemperatureCurrent < sanityChecks[i].sSensorLimit.min) | ||||
|             { | ||||
|                 ESP_LOGE(TAG, "%s Sensor reported too low value! %lf < %lf", sanityChecks[i].name, fSensorTemperatureCurrent, sanityChecks[i].sSensorLimit.min); | ||||
|                 sanityChecks[i].status = 1U; | ||||
|                 sanityChecks[i].uUnchangedCounter++; | ||||
|                 if (sanityChecks[i].uUnchangedCounter >= (SENSOR_GRACE_PERIOD / PERIODIC_INTERVAL)) | ||||
|                 { | ||||
|                     ESP_LOGE(TAG, "%s Sensor reported unchanged value! %lf == %lf", sanityChecks[i].name, sCurrentMeasurement.fCurrentValue, sanityChecks[i].fSensorTemperatureLast); | ||||
|                     sanityChecks[i].status = SENSOR_UNCHANGED; | ||||
|                     sSafetyState = SAFETY_SENSOR_ERROR; | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 sanityChecks[i].uUnchangedCounter = 0U; | ||||
|                 sanityChecks[i].status = 0U; | ||||
|                 sanityChecks[i].fSensorTemperatureLast = sCurrentMeasurement.fCurrentValue; | ||||
|  | ||||
|                 if (sCurrentMeasurement.fCurrentValue > sanityChecks[i].sSensorLimit.max) | ||||
|                 { | ||||
|                     ESP_LOGE(TAG, "%s Sensor reported too high value! %lf > %lf", sanityChecks[i].name, sCurrentMeasurement.fCurrentValue, sanityChecks[i].sSensorLimit.max); | ||||
|                     sanityChecks[i].status = SENSOR_TOO_HIGH; | ||||
|                     sSafetyState = SAFETY_SENSOR_ERROR; | ||||
|                 } | ||||
|                 else if (sCurrentMeasurement.fCurrentValue < sanityChecks[i].sSensorLimit.min) | ||||
|                 { | ||||
|                     ESP_LOGE(TAG, "%s Sensor reported too low value! %lf < %lf", sanityChecks[i].name, sCurrentMeasurement.fCurrentValue, sanityChecks[i].sSensorLimit.min); | ||||
|                     sanityChecks[i].status = SENSOR_TOO_LOW; | ||||
|                     sSafetyState = SAFETY_SENSOR_ERROR; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     sanityChecks[i].uUnchangedCounter = 0U; | ||||
|                     sanityChecks[i].status = SENSOR_NO_ERROR; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         // printf("  Status: %u\n", sanityChecks[i].status); | ||||
| @ -106,9 +126,27 @@ void setSafeState(void) | ||||
|     setBurnerState(DISABLED); | ||||
| } | ||||
|  | ||||
| /* | ||||
| sSafetyStateElement *getSafetyStates(void) | ||||
| void getSensorSanityStates(sSensorSanityCheck *pSensorSanityChecks) | ||||
| { | ||||
|     return safetyStates; | ||||
|     if (xSemaphoreTake(xMutexAccessSafety, portMAX_DELAY) == pdTRUE) | ||||
|     { | ||||
|         for (size_t i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++) | ||||
|         { | ||||
|             // Copy only the needed attributes | ||||
|             pSensorSanityChecks[i].status = sanityChecks[i].status; | ||||
|             strcpy(pSensorSanityChecks[i].name, sanityChecks[i].name); | ||||
|         } | ||||
|         xSemaphoreGive(xMutexAccessSafety); | ||||
|     } | ||||
| } | ||||
| */ | ||||
|  | ||||
| eSafetyState getSafetyState(void) | ||||
| { | ||||
|     eSafetyState state = SAFETY_NO_ERROR; | ||||
|     if (xSemaphoreTake(xMutexAccessSafety, portMAX_DELAY) == pdTRUE) | ||||
|     { | ||||
|         state = sSafetyState; | ||||
|         xSemaphoreGive(xMutexAccessSafety); | ||||
|     } | ||||
|     return state; | ||||
| } | ||||
| @ -6,7 +6,22 @@ | ||||
| #define MAX_ERROR_STRING_SIZE 64U | ||||
| #define NUMBER_OF_SENSOR_SANITY_CHECKS 4U | ||||
|  | ||||
| typedef float (*GetSensorValue)(eMeasurementMode); | ||||
| typedef enum _SensorErrorState | ||||
| { | ||||
|     SENSOR_NO_ERROR, | ||||
|     SENSOR_TOO_HIGH, | ||||
|     SENSOR_TOO_LOW, | ||||
|     SENSOR_UNCHANGED, | ||||
|     SENSOR_NOT_FOUND | ||||
| } eSensorErrorState; | ||||
|  | ||||
| typedef enum _SafetyState | ||||
| { | ||||
|     SAFETY_NO_ERROR, | ||||
|     SAFETY_SENSOR_ERROR, | ||||
| } eSafetyState; | ||||
|  | ||||
| typedef sMeasurement (*GetSensorValue)(); | ||||
| typedef struct _TemperatureSensorLimit | ||||
| { | ||||
|     float max; // Maximum temperature limit | ||||
| @ -14,7 +29,7 @@ typedef struct _TemperatureSensorLimit | ||||
| } sTemperatureSensorLimit; | ||||
| typedef struct _SensorSanityCheck | ||||
| { | ||||
|     uint8_t status; | ||||
|     eSensorErrorState status; | ||||
|     char name[MAX_ERROR_STRING_SIZE]; | ||||
|     sTemperatureSensorLimit sSensorLimit; | ||||
|     float fSensorTemperatureLast; | ||||
| @ -23,5 +38,5 @@ typedef struct _SensorSanityCheck | ||||
| } sSensorSanityCheck; | ||||
|  | ||||
| void initSafety(void); | ||||
|  | ||||
| //sSensorSanityCheck *getSafetyStates(void); | ||||
| void getSensorSanityStates(sSensorSanityCheck *pSensorSanityChecks); | ||||
| eSafetyState getSafetyState(void); | ||||
		Reference in New Issue
	
	Block a user