From cd739857408b6bc9901cad44f52690f84ad77d3cd0943c2d3b648f2e72c840eb Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 10:54:32 +0100 Subject: [PATCH 01/17] Wrong memset size --- main/inputs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main/inputs.c b/main/inputs.c index a9ef723..1b40193 100644 --- a/main/inputs.c +++ b/main/inputs.c @@ -94,17 +94,17 @@ void initMeasurement(sMeasurement *pMeasurement) pMeasurement->average10s.fValue = INITIALISATION_VALUE; pMeasurement->average10s.bufferCount = 0U; pMeasurement->average10s.bufferIndex = 0U; - memset(pMeasurement->average10s.samples, 0U, AVG10S_SAMPLE_SIZE); + memset(pMeasurement->average10s.samples, 0U, sizeof(float) * AVG10S_SAMPLE_SIZE); pMeasurement->average60s.fValue = INITIALISATION_VALUE; pMeasurement->average60s.bufferCount = 0U; pMeasurement->average60s.bufferIndex = 0U; - memset(pMeasurement->average60s.samples, 0U, AVG60S_SAMPLE_SIZE); + memset(pMeasurement->average60s.samples, 0U, sizeof(float) * AVG60S_SAMPLE_SIZE); pMeasurement->predict60s.fValue = INITIALISATION_VALUE; pMeasurement->predict60s.bufferCount = 0U; pMeasurement->predict60s.bufferIndex = 0U; - memset(pMeasurement->predict60s.samples, 0U, PRED60S_SAMPLE_SIZE); + memset(pMeasurement->predict60s.samples, 0U, sizeof(float) * PRED60S_SAMPLE_SIZE); } void updateAverage(sMeasurement *pMeasurement) -- 2.50.1 From 0775fda0ca5330e4e897d4ddc98b0fc083b0586b506f5c3ff2b1d7738715f54f Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 10:55:15 +0100 Subject: [PATCH 02/17] Off-by-one error (buffer overread) --- main/inputs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/inputs.c b/main/inputs.c index 1b40193..ad6be27 100644 --- a/main/inputs.c +++ b/main/inputs.c @@ -122,7 +122,7 @@ void updateAverage(sMeasurement *pMeasurement) } float sum = 0.0; - for (int i = 0; i <= pMeasurement->average10s.bufferCount; i++) + for (int i = 0; i < pMeasurement->average10s.bufferCount; i++) { sum += pMeasurement->average10s.samples[i]; } -- 2.50.1 From 09a3c3a22dfe22236ac26280ee896e955bd12f0eaf540daaf964690b51303278 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 10:58:12 +0100 Subject: [PATCH 03/17] Misuse of ESP_ERROR_CHECK --- main/metrics.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/metrics.c b/main/metrics.c index 6f1b2db..f87763a 100644 --- a/main/metrics.c +++ b/main/metrics.c @@ -307,7 +307,7 @@ void taskMetrics(void *pvParameters) aMetrics[u16MetricCounter].i64MetricValue = ap.rssi; u16MetricCounter++; - ESP_ERROR_CHECK(u16MetricCounter > METRIC_MAX_COUNT); + configASSERT(u16MetricCounter > METRIC_MAX_COUNT); vSetMetrics(aMetrics, u16MetricCounter); } } -- 2.50.1 From 781f9a14453d9c08c9d50959915e83dd038bcf1a20b0684bec0e92fbdb124e14 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 11:02:31 +0100 Subject: [PATCH 04/17] ncorrect memset with strlen --- main/metrics.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/metrics.c b/main/metrics.c index f87763a..2aabf7a 100644 --- a/main/metrics.c +++ b/main/metrics.c @@ -307,7 +307,7 @@ void taskMetrics(void *pvParameters) aMetrics[u16MetricCounter].i64MetricValue = ap.rssi; u16MetricCounter++; - configASSERT(u16MetricCounter > METRIC_MAX_COUNT); + configASSERT(!(u16MetricCounter > METRIC_MAX_COUNT)); vSetMetrics(aMetrics, u16MetricCounter); } } @@ -317,7 +317,7 @@ void vSetMetrics(sMetric *paMetrics, uint16_t u16Size) if (xSemaphoreTakeRecursive(xMutexAccessMetricResponse, pdMS_TO_TICKS(5000)) == pdTRUE) { - memset(caHtmlResponse, 0U, strlen(caHtmlResponse)); + memset(caHtmlResponse, 0U, HTML_RESPONSE_SIZE); for (uint16_t u16Index = 0U; u16Index < u16Size; u16Index++) { char caValueBuffer[64]; -- 2.50.1 From 267197ec209b28e9707e673ceee37806a7dd808ad7d8a063d5f231ac1dbddae6 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 11:06:10 +0100 Subject: [PATCH 05/17] Missing mutex protection --- main/outputs.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/main/outputs.c b/main/outputs.c index 7536e76..c96ff41 100644 --- a/main/outputs.c +++ b/main/outputs.c @@ -55,7 +55,17 @@ void initOutputs(void) eOutput getCirculationPumpState(void) { - return sCirculationPumpState; + eOutput ret = ENABLED; + if (xSemaphoreTakeRecursive(xMutexAccessOutputs, pdMS_TO_TICKS(5000)) == pdTRUE) + { + ret = sCirculationPumpState; + xSemaphoreGiveRecursive(xMutexAccessOutputs); + } + else + { + ESP_LOGE(TAG, "Unable to take mutex: getCirculationPumpState()"); + } + return ret; } void setCirculationPumpState(eOutput in) -- 2.50.1 From 8c3dbc2886d0dd13f865fb52cf56659eca3759bf81c8d9c49850006a43b97394 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 11:31:34 +0100 Subject: [PATCH 06/17] Unprotected shared state access --- main/control.c | 187 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 123 insertions(+), 64 deletions(-) diff --git a/main/control.c b/main/control.c index 1098bc7..911421d 100644 --- a/main/control.c +++ b/main/control.c @@ -25,9 +25,9 @@ (60U * 4U) // Burner fault detection after 4 minutes static const char *TAG = "smart-oil-heater-control-system-control"; -static eControlState sControlState = CONTROL_STARTING; +static eControlState gControlState = CONTROL_STARTING; // Control table for daily schedules -static const sControlDay aControlTable[] = { +static const sControlDay gControlTable[] = { {MONDAY, 2U, {{{4, 45}, @@ -85,15 +85,25 @@ static const sControlDay aControlTable[] = { RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMBER_TEMPERATURE_TARGET}}}, }; -static sControlTemperatureEntry currentControlEntry = - aControlTable[0].aTemperatureEntries[0]; +static sControlTemperatureEntry gCurrentControlEntry = + gControlTable[0].aTemperatureEntries[0]; +static SemaphoreHandle_t xMutexAccessControl = NULL; // Function prototypes void taskControl(void *pvParameters); void findControlCurrentTemperatureEntry(void); +void setControlState(eControlState state); void initControl(void) { + + xMutexAccessControl = xSemaphoreCreateRecursiveMutex(); + if (xMutexAccessControl == NULL) + { + ESP_LOGE(TAG, "Unable to create mutex"); + } + xSemaphoreGiveRecursive(xMutexAccessControl); + BaseType_t taskCreated = xTaskCreate(taskControl, // Function to implement the task "taskControl", // Task name @@ -128,7 +138,7 @@ void taskControl(void *pvParameters) if (getSafetyState() != SAFETY_NO_ERROR) { ESP_LOGW(TAG, "Control not possible due to safety fault!"); - sControlState = CONTROL_FAULT_SAFETY; + setControlState(CONTROL_FAULT_SAFETY); if (bHeatingInAction) { ESP_LOGW(TAG, "Disabling burner due to safety fault"); @@ -143,7 +153,7 @@ void taskControl(void *pvParameters) if (getSntpState() != SYNC_SUCCESSFUL) { ESP_LOGW(TAG, "Control not possible due to SNTP fault!"); - sControlState = CONTROL_FAULT_SNTP; + setControlState(CONTROL_FAULT_SNTP); if (bHeatingInAction) { ESP_LOGW(TAG, "Disabling burner due to SNTP fault"); @@ -155,8 +165,6 @@ void taskControl(void *pvParameters) } findControlCurrentTemperatureEntry(); - sControlTemperatureEntry currentControlEntry = - getControlCurrentTemperatureEntry(); if (getOutdoorTemperature().fDampedValue >= SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH) @@ -178,10 +186,10 @@ void taskControl(void *pvParameters) // ESP_LOGI(TAG, "Outdoor temperature too warm: Disabling heating"); setBurnerState(DISABLED); setSafetyControlState(DISABLED); - sControlState = CONTROL_OUTDOOR_TOO_WARM; + setControlState(CONTROL_OUTDOOR_TOO_WARM); } else if ((getReturnFlowTemperature().average60s.fValue <= - currentControlEntry.fReturnFlowTemperature) && + getControlCurrentTemperatureEntry().fReturnFlowTemperature) && (getChamberTemperature().fCurrentValue <= CHAMBER_TEMPERATURE_THRESHOLD)) { @@ -192,12 +200,12 @@ void taskControl(void *pvParameters) setBurnerState(ENABLED); setSafetyControlState(ENABLED); i64BurnerEnableTimestamp = esp_timer_get_time(); - sControlState = CONTROL_HEATING; + setControlState(CONTROL_HEATING); } else { // ESP_LOGI(TAG, "Return flow temperature too warm: Disabling heating"); - sControlState = CONTROL_RETURN_FLOW_TOO_WARM; + setControlState(CONTROL_RETURN_FLOW_TOO_WARM); } } @@ -205,9 +213,9 @@ void taskControl(void *pvParameters) if (bHeatingInAction) { if ((getChamberTemperature().fCurrentValue >= - currentControlEntry.fChamberTemperature) || + getControlCurrentTemperatureEntry().fChamberTemperature) || (getChamberTemperature().predict60s.fValue >= - currentControlEntry.fChamberTemperature)) + getControlCurrentTemperatureEntry().fChamberTemperature)) { ESP_LOGI(TAG, "Chamber target temperature reached: Disabling burner"); bHeatingInAction = false; @@ -224,7 +232,7 @@ void taskControl(void *pvParameters) // ESP_LOGW(TAG, "Burner fault detected: Disabling burner"); bHeatingInAction = false; eBurnerState = BURNER_FAULT; - sControlState = CONTROL_FAULT_BURNER; + setControlState(CONTROL_FAULT_BURNER); setBurnerState(DISABLED); setSafetyControlState(ENABLED); } @@ -253,7 +261,37 @@ void taskControl(void *pvParameters) } // End of while(1) } -eControlState getControlState(void) { return sControlState; } +void setControlState(eControlState state) +{ + + if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE) + { + gControlState = state; + xSemaphoreGiveRecursive(xMutexAccessControl); + } + else + { + ESP_LOGE(TAG, "Unable to take mutex: setControlState()"); + } +} + +eControlState getControlState(void) +{ + + eControlState ret = CONTROL_FAULT_SAFETY; + + if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE) + { + ret = gControlState; + xSemaphoreGiveRecursive(xMutexAccessControl); + } + else + { + ESP_LOGE(TAG, "Unable to take mutex: getControlState()"); + } + + return ret; +} eControlWeekday getControlCurrentWeekday(void) { @@ -294,66 +332,87 @@ void findControlCurrentTemperatureEntry(void) int currentHour = timeinfo.tm_hour; int currentMinute = timeinfo.tm_min; - // ESP_LOGI(TAG, "Searching for control entry - Day: %d, Time: %02d:%02d", currentDay, currentHour, currentMinute); - - // Search through all days and entries - for (int dayIndex = 0; dayIndex < 7; dayIndex++) + if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE) { - const sControlDay *day = &aControlTable[dayIndex]; - for (int entryIndex = 0; entryIndex < day->entryCount; entryIndex++) + // ESP_LOGI(TAG, "Searching for control entry - Day: %d, Time: %02d:%02d", currentDay, currentHour, currentMinute); + + // Search through all days and entries + for (int dayIndex = 0; dayIndex < 7; dayIndex++) { - const sControlTemperatureEntry *entry = &day->aTemperatureEntries[entryIndex]; + const sControlDay *day = &gControlTable[dayIndex]; - // Check if this entry is in the future (next active entry) - bool isFutureDay = (day->day > currentDay); - bool isTodayFutureTime = (day->day == currentDay) && - ((entry->timestamp.hour > currentHour) || - (entry->timestamp.hour == currentHour && - entry->timestamp.minute > currentMinute)); - - if (isFutureDay || isTodayFutureTime) + for (int entryIndex = 0; entryIndex < day->entryCount; entryIndex++) { - // Found next scheduled entry, so determine the previous (active) one - if (entryIndex > 0) + const sControlTemperatureEntry *entry = &day->aTemperatureEntries[entryIndex]; + + // Check if this entry is in the future (next active entry) + bool isFutureDay = (day->day > currentDay); + bool isTodayFutureTime = (day->day == currentDay) && + ((entry->timestamp.hour > currentHour) || + (entry->timestamp.hour == currentHour && + entry->timestamp.minute > currentMinute)); + + if (isFutureDay || isTodayFutureTime) { - // Use previous entry from same day - currentControlEntry = day->aTemperatureEntries[entryIndex - 1]; + + // Found next scheduled entry, so determine the previous (active) one + if (entryIndex > 0) + { + // Use previous entry from same day + gCurrentControlEntry = day->aTemperatureEntries[entryIndex - 1]; + } + else if (dayIndex > 0) + { + // Use last entry from previous day + const sControlDay *previousDay = &gControlTable[dayIndex - 1]; + gCurrentControlEntry = previousDay->aTemperatureEntries[previousDay->entryCount - 1]; + } + else + { + // First entry of the week - wrap to last entry of Sunday + const sControlDay *sunday = &gControlTable[6]; + gCurrentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1]; + } + /* + ESP_LOGI(TAG, "Active entry found - Time: %02d:%02d, " + "Return Temp: %lf, Chamber Temp: %lf", + gCurrentControlEntry.timestamp.hour, + gCurrentControlEntry.timestamp.minute, + gCurrentControlEntry.fReturnFlowTemperature, + gCurrentControlEntry.fChamberTemperature); + */ + return; } - else if (dayIndex > 0) - { - // Use last entry from previous day - const sControlDay *previousDay = &aControlTable[dayIndex - 1]; - currentControlEntry = previousDay->aTemperatureEntries[previousDay->entryCount - 1]; - } - else - { - // First entry of the week - wrap to last entry of Sunday - const sControlDay *sunday = &aControlTable[6]; - currentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1]; - } - /* - ESP_LOGI(TAG, "Active entry found - Time: %02d:%02d, " - "Return Temp: %lf, Chamber Temp: %lf", - currentControlEntry.timestamp.hour, - currentControlEntry.timestamp.minute, - currentControlEntry.fReturnFlowTemperature, - currentControlEntry.fChamberTemperature); - */ - return; } } + + // If we reached here, current time is after all entries this week + // Use the last entry (Sunday evening) + const sControlDay *sunday = &gControlTable[6]; + gCurrentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1]; + + // ESP_LOGI(TAG, "Using last entry of week - Time: %02d:%02d", gCurrentControlEntry.timestamp.hour, gCurrentControlEntry.timestamp.minute); + xSemaphoreGiveRecursive(xMutexAccessControl); + } + else + { + ESP_LOGE(TAG, "Unable to take mutex: findControlCurrentTemperatureEntry()"); } - - // If we reached here, current time is after all entries this week - // Use the last entry (Sunday evening) - const sControlDay *sunday = &aControlTable[6]; - currentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1]; - - // ESP_LOGI(TAG, "Using last entry of week - Time: %02d:%02d", currentControlEntry.timestamp.hour, currentControlEntry.timestamp.minute); } sControlTemperatureEntry getControlCurrentTemperatureEntry(void) { - return currentControlEntry; + sControlTemperatureEntry ret = gControlTable[0].aTemperatureEntries[0]; + if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE) + { + ret = gCurrentControlEntry; + xSemaphoreGiveRecursive(xMutexAccessControl); + } + else + { + ESP_LOGE(TAG, "Unable to take mutex: getControlCurrentTemperatureEntry()"); + } + + return ret; } -- 2.50.1 From df3825df3a22efeea1e94159cdd310d492a910f2294bca4e925d79abce8ddb76 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 11:33:37 +0100 Subject: [PATCH 07/17] Non-thread-safe function --- main/control.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main/control.c b/main/control.c index 911421d..955b624 100644 --- a/main/control.c +++ b/main/control.c @@ -295,13 +295,13 @@ eControlState getControlState(void) eControlWeekday getControlCurrentWeekday(void) { + // Get current time time_t now; - struct tm *timeinfo; - + struct tm timeinfo; time(&now); - timeinfo = localtime(&now); + localtime_r(&now, &timeinfo); - int day = timeinfo->tm_wday; + int day = timeinfo.tm_wday; return (eControlWeekday)((day == 0) ? 6 : day - 1); } -- 2.50.1 From 10f9645580d890be641933b57ffdf5a9a5f2ed0d27680273d08e823000e8bf2b Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 11:39:37 +0100 Subject: [PATCH 08/17] Unchecked gpio_config returns --- main/inputs.c | 7 ++++++- main/outputs.c | 23 ++++++++++++++++++++--- main/sntp.c | 2 +- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/main/inputs.c b/main/inputs.c index ad6be27..93b2adf 100644 --- a/main/inputs.c +++ b/main/inputs.c @@ -49,7 +49,12 @@ void initInputs(void) .intr_type = GPIO_INTR_DISABLE // Disable interrupts }; - gpio_config(&ioConfBurnerFault); + esp_err_t ret = gpio_config(&ioConfBurnerFault); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret)); + return; + } xMutexAccessInputs = xSemaphoreCreateRecursiveMutex(); if (xMutexAccessInputs == NULL) diff --git a/main/outputs.c b/main/outputs.c index c96ff41..6644413 100644 --- a/main/outputs.c +++ b/main/outputs.c @@ -41,9 +41,26 @@ void initOutputs(void) .intr_type = GPIO_INTR_DISABLE // Disable interrupts }; - gpio_config(&ioConfCirculationPump); - gpio_config(&ioConfBurner); - gpio_config(&ioConfSafetyContact); + esp_err_t ret = gpio_config(&ioConfCirculationPump); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret)); + return; + } + + ret = gpio_config(&ioConfBurner); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret)); + return; + } + + ret = gpio_config(&ioConfSafetyContact); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret)); + return; + } xMutexAccessOutputs = xSemaphoreCreateRecursiveMutex(); if (xMutexAccessOutputs == NULL) diff --git a/main/sntp.c b/main/sntp.c index 638bfa4..fab2d6f 100644 --- a/main/sntp.c +++ b/main/sntp.c @@ -6,7 +6,7 @@ #include "sntp.h" static const char *TAG = "smart-oil-heater-control-system-sntp"; -static eSntpState sntpState = SYNC_NOT_STARTED; +static volatile eSntpState sntpState = SYNC_NOT_STARTED; void time_sync_notification_cb(struct timeval *tv); void initSntp(void) -- 2.50.1 From 67929580d5f430ba28279fb7b70ca9f506bedd3d840c09270c009c326bec0537 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 11:42:27 +0100 Subject: [PATCH 09/17] Unchecked xEventGroupCreate --- main/wifi.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/main/wifi.c b/main/wifi.c index 6a4b806..29378ef 100644 --- a/main/wifi.c +++ b/main/wifi.c @@ -28,6 +28,12 @@ static void event_handler(void *arg, esp_event_base_t event_base, void initWifi(void) { s_wifi_event_group = xEventGroupCreate(); + if (s_wifi_event_group == NULL) + { + ESP_LOGE(TAG, "xEventGroupCreate() failed!"); + return; + } + ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); -- 2.50.1 From 020eb63e05b8851fcca304cff6ed8bf07dce16b0a4a3f63a703f68764b4ec579 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 11:43:26 +0100 Subject: [PATCH 10/17] Unchecked network configuration --- main/wifi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/wifi.c b/main/wifi.c index 29378ef..2b628d9 100644 --- a/main/wifi.c +++ b/main/wifi.c @@ -38,12 +38,12 @@ void initWifi(void) ESP_ERROR_CHECK(esp_event_loop_create_default()); esp_netif_t *my_sta = esp_netif_create_default_wifi_sta(); - esp_netif_dhcpc_stop(my_sta); + ESP_ERROR_CHECK(esp_netif_dhcpc_stop(my_sta)); esp_netif_ip_info_t ip_info; ip_info.ip.addr = ipaddr_addr(CONFIG_STATIC_IP_ADDR); ip_info.gw.addr = ipaddr_addr(CONFIG_STATIC_GATEWAY_IP_ADDR); ip_info.netmask.addr = ipaddr_addr(CONFIG_STATIC_IP_NETMASK); - esp_netif_set_ip_info(my_sta, &ip_info); + ESP_ERROR_CHECK(esp_netif_set_ip_info(my_sta, &ip_info)); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); -- 2.50.1 From 05757a503834b429979f26087099af85242df554e194fa06f96c9be2b67e3a62 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 11:45:49 +0100 Subject: [PATCH 11/17] Unchecked WiFi API call --- main/metrics.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/metrics.c b/main/metrics.c index 2aabf7a..5c10247 100644 --- a/main/metrics.c +++ b/main/metrics.c @@ -301,7 +301,8 @@ void taskMetrics(void *pvParameters) // Wifi RSSI wifi_ap_record_t ap; - esp_wifi_sta_get_ap_info(&ap); + ap.rssi = 0U; + ESP_ERROR_CHECK(esp_wifi_sta_get_ap_info(&ap)); strcpy(aMetrics[u16MetricCounter].caMetricName, "wifi_rssi"); aMetrics[u16MetricCounter].type = INTEGER_64; aMetrics[u16MetricCounter].i64MetricValue = ap.rssi; @@ -314,7 +315,6 @@ void taskMetrics(void *pvParameters) void vSetMetrics(sMetric *paMetrics, uint16_t u16Size) { - if (xSemaphoreTakeRecursive(xMutexAccessMetricResponse, pdMS_TO_TICKS(5000)) == pdTRUE) { memset(caHtmlResponse, 0U, HTML_RESPONSE_SIZE); -- 2.50.1 From 0236ebcdd1cba119bc35299e32fa80960806cd6f7fb3f92efd9c1eee4a12aee9 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 11:47:04 +0100 Subject: [PATCH 12/17] Unsafe strcpy --- main/safety.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/safety.c b/main/safety.c index db60a87..53ca250 100644 --- a/main/safety.c +++ b/main/safety.c @@ -143,7 +143,7 @@ void getSensorSanityStates(sSensorSanityCheck *pSensorSanityChecks) { // Copy only the needed attributes pSensorSanityChecks[i].state = sanityChecks[i].state; - strcpy(pSensorSanityChecks[i].name, sanityChecks[i].name); + strncpy(pSensorSanityChecks[i].name, sanityChecks[i].name, MAX_ERROR_STRING_SIZE); } xSemaphoreGiveRecursive(xMutexAccessSafety); } -- 2.50.1 From a9ec101bc61dc8e0d7fa6def51a43c25035e254deb3c050b04101583b30cb3f7 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 11:52:08 +0100 Subject: [PATCH 13/17] Floating-point equality comparison --- main/safety.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main/safety.c b/main/safety.c index 53ca250..94c0d04 100644 --- a/main/safety.c +++ b/main/safety.c @@ -2,10 +2,12 @@ #include "freertos/task.h" #include "esp_log.h" #include +#include #include "safety.h" #define PERIODIC_INTERVAL 1U // run safety checks every 1sec #define SENSOR_GRACE_PERIOD (60U * 30U) // period that a sensor can report the same reading in seconds +#define FLOAT_EPSILON 0.0001f static const char *TAG = "smart-oil-heater-control-system-safety"; static SemaphoreHandle_t xMutexAccessSafety = NULL; @@ -91,7 +93,7 @@ void checkSensorSanity(void) } else { - if (sCurrentMeasurement.fCurrentValue == sanityChecks[i].fSensorTemperatureLast) + if (fabsf(sCurrentMeasurement.fCurrentValue - sanityChecks[i].fSensorTemperatureLast) < FLOAT_EPSILON) { sanityChecks[i].uUnchangedCounter++; if (sanityChecks[i].uUnchangedCounter >= (SENSOR_GRACE_PERIOD / PERIODIC_INTERVAL)) -- 2.50.1 From 40f757b7d1e1625dd2f0c80e9f3e433cc37a7d5bcba47e49efb464279154bd9b Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 11:54:18 +0100 Subject: [PATCH 14/17] uUnchangedCounter reset logic flaw --- main/safety.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main/safety.c b/main/safety.c index 94c0d04..f592a86 100644 --- a/main/safety.c +++ b/main/safety.c @@ -105,6 +105,7 @@ void checkSensorSanity(void) } else { + sanityChecks[i].uUnchangedCounter = 0U; sanityChecks[i].fSensorTemperatureLast = sCurrentMeasurement.fCurrentValue; if (sCurrentMeasurement.fCurrentValue > sanityChecks[i].sSensorLimit.max) @@ -121,12 +122,10 @@ void checkSensorSanity(void) } else { - sanityChecks[i].uUnchangedCounter = 0U; sanityChecks[i].state = SENSOR_NO_ERROR; } } } - // printf(" state: %u\n", sanityChecks[i].state); } } -- 2.50.1 From d36b91a0fd6dfef5bc14366b5cbcf908db0ef6cab6f055387c4694d7f98f8dab Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 11:57:15 +0100 Subject: [PATCH 15/17] Variable name shadows type name --- main/control.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/main/control.c b/main/control.c index 955b624..8b31b0e 100644 --- a/main/control.c +++ b/main/control.c @@ -127,7 +127,7 @@ void taskControl(void *pvParameters) { bool bHeatingInAction = false; bool bSummerMode = false; - eBurnerState eBurnerState = BURNER_UNKNOWN; + eBurnerState burnerState = BURNER_UNKNOWN; int64_t i64BurnerEnableTimestamp = esp_timer_get_time(); while (1) @@ -179,7 +179,7 @@ void taskControl(void *pvParameters) // Enable burner if outdoor temperature is low and return flow temperature // is cooled down - if (!bHeatingInAction && (eBurnerState != BURNER_FAULT)) + if (!bHeatingInAction && (burnerState != BURNER_FAULT)) { if (bSummerMode) { @@ -195,7 +195,7 @@ void taskControl(void *pvParameters) { ESP_LOGI(TAG, "Enabling burner: Return flow temperature target reached"); - eBurnerState = BURNER_UNKNOWN; + burnerState = BURNER_UNKNOWN; bHeatingInAction = true; setBurnerState(ENABLED); setSafetyControlState(ENABLED); @@ -225,13 +225,13 @@ void taskControl(void *pvParameters) else if (esp_timer_get_time() - i64BurnerEnableTimestamp >= BURNER_FAULT_DETECTION_THRESHOLD * 1000000U) { - if (eBurnerState == BURNER_UNKNOWN) + if (burnerState == BURNER_UNKNOWN) { if (getBurnerError() == FAULT) { // ESP_LOGW(TAG, "Burner fault detected: Disabling burner"); bHeatingInAction = false; - eBurnerState = BURNER_FAULT; + burnerState = BURNER_FAULT; setControlState(CONTROL_FAULT_BURNER); setBurnerState(DISABLED); setSafetyControlState(ENABLED); @@ -240,7 +240,7 @@ void taskControl(void *pvParameters) { // ESP_LOGI(TAG, "No burner fault detected: Marking burner as // fired"); - eBurnerState = BURNER_FIRED; + burnerState = BURNER_FIRED; } } } -- 2.50.1 From b7180739077e5c37ac0bb6264dc36453664f30c048a5cad9ee6cea97d1d09fdf Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 11:58:46 +0100 Subject: [PATCH 16/17] Missing break before default --- main/outputs.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main/outputs.c b/main/outputs.c index 6644413..9861b60 100644 --- a/main/outputs.c +++ b/main/outputs.c @@ -97,6 +97,7 @@ void setCirculationPumpState(eOutput in) break; case DISABLED: gpio_set_level(uCirculationPumpGpioPin, 1U); // Switch off Circulation Pump + break; default: break; } @@ -135,6 +136,7 @@ void setBurnerState(eOutput in) break; case DISABLED: gpio_set_level(uBurnerGpioPin, 1U); // Switch off Burner + break; default: break; } @@ -173,6 +175,7 @@ void setSafetyControlState(eOutput in) break; case DISABLED: gpio_set_level(uSafetyContactGpioPin, 1U); // Switch off power for Burner + break; default: break; } -- 2.50.1 From f3f6f1bc5f6bd5d73ca33204fa7127b52be0f1d6ab7b5a32435a211bb79c8b7d Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 12:01:22 +0100 Subject: [PATCH 17/17] Potential division by zero --- main/inputs.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/main/inputs.c b/main/inputs.c index 93b2adf..8df4fb8 100644 --- a/main/inputs.c +++ b/main/inputs.c @@ -132,7 +132,14 @@ void updateAverage(sMeasurement *pMeasurement) sum += pMeasurement->average10s.samples[i]; } - pMeasurement->average10s.fValue = sum / pMeasurement->average10s.bufferCount; + if (pMeasurement->average10s.bufferCount == 0U) + { + pMeasurement->average10s.fValue = 0.0f; + } + else + { + pMeasurement->average10s.fValue = sum / pMeasurement->average10s.bufferCount; + } // Average form the last 60sec pMeasurement->average60s.samples[pMeasurement->average60s.bufferIndex] = pMeasurement->fCurrentValue; @@ -149,7 +156,14 @@ void updateAverage(sMeasurement *pMeasurement) sum += pMeasurement->average60s.samples[i]; } - pMeasurement->average60s.fValue = sum / pMeasurement->average60s.bufferCount; + if (pMeasurement->average60s.bufferCount == 0U) + { + pMeasurement->average60s.fValue = 0.0f; + } + else + { + pMeasurement->average60s.fValue = sum / pMeasurement->average60s.bufferCount; + } // Damped current value if (pMeasurement->fDampedValue == INITIALISATION_VALUE) -- 2.50.1