From aeb9e04413a3df667d52c7c8cbe2125728b8b64214d453eea877b2f6851da603 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 1 Nov 2025 16:59:52 +0100 Subject: [PATCH 1/4] update control entry --- main/control.c | 1 + 1 file changed, 1 insertion(+) diff --git a/main/control.c b/main/control.c index 7ddcab4..6f8d81e 100644 --- a/main/control.c +++ b/main/control.c @@ -100,6 +100,7 @@ void taskControl(void *pvParameters) continue; } + findControlCurrentTemperatureEntry(); sControlTemperatureEntry currentControlEntry = getControlCurrentTemperatureEntry(); if (getOutdoorTemperature().fDampedValue >= SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH) -- 2.50.1 From 55b62d74384dcc6512fb157127c1337bfe45b7f3446c1860c39cf03ac7ef1b2c Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 1 Nov 2025 17:00:00 +0100 Subject: [PATCH 2/4] update readme --- README.md | 56 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 3f0f299..07d79a3 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,11 @@ Sntp <|-- Metrics class Inputs{ +initInputs() + -initMeasurement() + -updateAverage() + -updatePrediction() -taskInput() + -linearRegressionPredict() +getChamberTemperature() +getOutdoorTemperature() +getInletFlowTemperature() @@ -38,7 +42,11 @@ Sntp <|-- Metrics } class Control{ + initControl() +taskControl() + +getControlCurrentWeekday() + -findControlCurrentTemperatureEntry() + +getControlCurrentTemperatureEntry() -controlTable +getControlState() } @@ -79,33 +87,41 @@ Sntp <|-- Metrics burner_fault_pending 1 circulation_pump_enabled 1 burner_enabled 0 -safety_contact_enabled 0 -chamber_temperature 58.750000 -chamber_temperature_avg10 58.931252 -chamber_temperature_avg60 59.190475 -chamber_temperature_pred60 55.870998 -inlet_flow_temperature 53.875000 -inlet_flow_temperature_avg10 53.900002 -inlet_flow_temperature_avg60 53.994320 -inlet_flow_temperature_pred60 52.848743 -outdoor_temperature 18.000000 -outdoor_temperature_avg10 18.006250 -outdoor_temperature_avg60 18.002840 -outdoor_temperature_pred60 18.050785 -return_flow_temperature 48.625000 -return_flow_temperature_avg10 48.718750 -return_flow_temperature_avg60 48.846592 -return_flow_temperature_pred60 47.383083 +safety_contact_enabled 1 +chamber_temperature 37.250000 +chamber_temperature_avg10 37.237499 +chamber_temperature_avg60 37.438541 +chamber_temperature_damped 42.185040 +chamber_temperature_pred60 36.638443 +inlet_flow_temperature 35.625000 +inlet_flow_temperature_avg10 35.618752 +inlet_flow_temperature_avg60 35.415627 +inlet_flow_temperature_damped 39.431259 +inlet_flow_temperature_pred60 36.078678 +outdoor_temperature 14.687500 +outdoor_temperature_avg10 14.662500 +outdoor_temperature_avg60 14.646875 +outdoor_temperature_damped 9.169084 +outdoor_temperature_pred60 14.660233 +return_flow_temperature 39.937500 +return_flow_temperature_avg10 40.087502 +return_flow_temperature_avg60 41.146873 +return_flow_temperature_damped 32.385151 +return_flow_temperature_pred60 37.311958 chamber_temperature_state 0 outdoor_temperature_state 0 inlet_flow_temperature_state 0 return_flow_temperature_state 0 safety_state 0 control_state 3 +control_current_weekday 5 +control_current_entry_time 17100 +control_current_entry_chamber_temperature 80.000000 +control_current_entry_return_flow_temperature 30.000000 sntp_state 0 -system_unixtime 1735242392 -uptime_seconds 40 -wifi_rssi -74 +system_unixtime 1762012743 +uptime_seconds 465229 +wifi_rssi -72 ``` #### Status Encoding -- 2.50.1 From d992218a7d73bdb300b0d48dd1eb4340bd5354b118acb6fcf3e442aadd46f81e Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 1 Nov 2025 17:43:25 +0100 Subject: [PATCH 3/4] fix search algo --- main/control.c | 465 +++++++++++++++++++++++++++++-------------------- 1 file changed, 274 insertions(+), 191 deletions(-) diff --git a/main/control.c b/main/control.c index 6f8d81e..6c2ede5 100644 --- a/main/control.c +++ b/main/control.c @@ -1,10 +1,10 @@ +#include "control.h" +#include "esp_log.h" +#include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "esp_timer.h" -#include "esp_log.h" -#include "control.h" -#include "outputs.h" #include "inputs.h" +#include "outputs.h" #include "safety.h" #include "sntp.h" @@ -13,225 +13,308 @@ // Temperature thresholds #define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY 30.0f #define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT 25.0f -#define CHAMBER_TEMPERATURE_TARGET 80.0f // Max cutoff temperature -#define CHAMBER_TEMPERATURE_THRESHOLD 45.0f // Min threshold for burner enable -#define SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH 20.0f // Summer mode will be activated -#define SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW 15.0f // Summer mode will be deactivated --> Heating starts -#define CIRCULATION_PUMP_TEMPERATURE_THRESHOLD 30.0f // Min threshold of chamber for circulation pump enable -#define BURNER_FAULT_DETECTION_THRESHOLD (60U * 4U) // Burner fault detection after 4 minutes +#define CHAMBER_TEMPERATURE_TARGET 80.0f // Max cutoff temperature +#define CHAMBER_TEMPERATURE_THRESHOLD 45.0f // Min threshold for burner enable +#define SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH \ + 20.0f // Summer mode will be activated +#define SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW \ + 15.0f // Summer mode will be deactivated --> Heating starts +#define CIRCULATION_PUMP_TEMPERATURE_THRESHOLD \ + 30.0f // Min threshold of chamber for circulation pump enable +#define BURNER_FAULT_DETECTION_THRESHOLD \ + (60U * 4U) // Burner fault detection after 4 minutes static const char *TAG = "smart-oil-heater-control-system-control"; static eControlState sControlState = CONTROL_STARTING; // Control table for daily schedules static const sControlDay aControlTable[] = { - {MONDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMBER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMBER_TEMPERATURE_TARGET}}}, - {TUESDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMBER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMBER_TEMPERATURE_TARGET}}}, - {WEDNESDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMBER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMBER_TEMPERATURE_TARGET}}}, - {THURSDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMBER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMBER_TEMPERATURE_TARGET}}}, - {FRIDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMBER_TEMPERATURE_TARGET}, {{23, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMBER_TEMPERATURE_TARGET}}}, - {SATURDAY, 2U, {{{6, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMBER_TEMPERATURE_TARGET}, {{23, 30}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMBER_TEMPERATURE_TARGET}}}, - {SUNDAY, 2U, {{{6, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMBER_TEMPERATURE_TARGET}, {{22, 30}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMBER_TEMPERATURE_TARGET}}}, + {MONDAY, + 2U, + {{{4, 45}, + RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, + CHAMBER_TEMPERATURE_TARGET}, + {{22, 0}, + RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, + CHAMBER_TEMPERATURE_TARGET}}}, + {TUESDAY, + 2U, + {{{4, 45}, + RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, + CHAMBER_TEMPERATURE_TARGET}, + {{22, 0}, + RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, + CHAMBER_TEMPERATURE_TARGET}}}, + {WEDNESDAY, + 2U, + {{{4, 45}, + RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, + CHAMBER_TEMPERATURE_TARGET}, + {{22, 0}, + RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, + CHAMBER_TEMPERATURE_TARGET}}}, + {THURSDAY, + 2U, + {{{4, 45}, + RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, + CHAMBER_TEMPERATURE_TARGET}, + {{22, 0}, + RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, + CHAMBER_TEMPERATURE_TARGET}}}, + {FRIDAY, + 2U, + {{{4, 45}, + RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, + CHAMBER_TEMPERATURE_TARGET}, + {{23, 0}, + RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, + CHAMBER_TEMPERATURE_TARGET}}}, + {SATURDAY, + 2U, + {{{6, 45}, + RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, + CHAMBER_TEMPERATURE_TARGET}, + {{23, 30}, + RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, + CHAMBER_TEMPERATURE_TARGET}}}, + {SUNDAY, + 2U, + {{{6, 45}, + RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, + CHAMBER_TEMPERATURE_TARGET}, + {{22, 30}, + RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, + CHAMBER_TEMPERATURE_TARGET}}}, }; -static sControlTemperatureEntry currentControlEntry = aControlTable[0].aTemperatureEntries[0]; +static sControlTemperatureEntry currentControlEntry = + aControlTable[0].aTemperatureEntries[0]; // Function prototypes void taskControl(void *pvParameters); void findControlCurrentTemperatureEntry(void); -void initControl(void) -{ - BaseType_t taskCreated = xTaskCreate( - taskControl, // Function to implement the task - "taskControl", // Task name - 8192, // 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) - ); +void initControl(void) { + BaseType_t taskCreated = + xTaskCreate(taskControl, // Function to implement the task + "taskControl", // Task name + 8192, // 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) + ); - if (taskCreated == pdPASS) - { - ESP_LOGI(TAG, "Task created successfully!"); + if (taskCreated == pdPASS) { + ESP_LOGI(TAG, "Task created successfully!"); + } else { + ESP_LOGE(TAG, "Failed to create task"); + } +} + +void taskControl(void *pvParameters) { + bool bHeatingInAction = false; + bool bSummerMode = false; + eBurnerState eBurnerState = BURNER_UNKNOWN; + int64_t i64BurnerEnableTimestamp = esp_timer_get_time(); + + while (1) { + vTaskDelay(PERIODIC_INTERVAL * 1000U / portTICK_PERIOD_MS); + + // Check for safety faults + if (getSafetyState() != SAFETY_NO_ERROR) { + ESP_LOGW(TAG, "Control not possible due to safety fault!"); + sControlState = CONTROL_FAULT_SAFETY; + if (bHeatingInAction) { + ESP_LOGW(TAG, "Disabling burner due to safety fault"); + bHeatingInAction = false; + setBurnerState(DISABLED); + setSafetyControlState(ENABLED); + } + continue; } - else - { - ESP_LOGE(TAG, "Failed to create task"); + + // Check for SNTP faults + if (getSntpState() != SYNC_SUCCESSFUL) { + ESP_LOGW(TAG, "Control not possible due to SNTP fault!"); + sControlState = CONTROL_FAULT_SNTP; + if (bHeatingInAction) { + ESP_LOGW(TAG, "Disabling burner due to SNTP fault"); + bHeatingInAction = false; + setBurnerState(DISABLED); + setSafetyControlState(ENABLED); + } + continue; } + + findControlCurrentTemperatureEntry(); + sControlTemperatureEntry currentControlEntry = + getControlCurrentTemperatureEntry(); + + if (getOutdoorTemperature().fDampedValue >= + SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH) { + bSummerMode = true; + } else if (getOutdoorTemperature().fDampedValue <= + SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW) { + bSummerMode = false; + } + + // Enable burner if outdoor temperature is low and return flow temperature + // is cooled down + if (!bHeatingInAction && (eBurnerState != BURNER_FAULT)) { + if (bSummerMode) { + // ESP_LOGI(TAG, "Outdoor temperature too warm: Disabling heating"); + setBurnerState(DISABLED); + setSafetyControlState(DISABLED); + sControlState = CONTROL_OUTDOOR_TOO_WARM; + } else if ((getReturnFlowTemperature().average60s.fValue <= + currentControlEntry.fReturnFlowTemperature) && + (getChamberTemperature().fCurrentValue <= + CHAMBER_TEMPERATURE_THRESHOLD)) { + ESP_LOGI(TAG, + "Enabling burner: Return flow temperature target reached"); + eBurnerState = BURNER_UNKNOWN; + bHeatingInAction = true; + setBurnerState(ENABLED); + setSafetyControlState(ENABLED); + i64BurnerEnableTimestamp = esp_timer_get_time(); + sControlState = CONTROL_HEATING; + } else { + // ESP_LOGI(TAG, "Return flow temperature too warm: Disabling heating"); + sControlState = CONTROL_RETURN_FLOW_TOO_WARM; + } + } + + // Disable burner if target temperature is reached or a fault occurred + if (bHeatingInAction) { + if ((getChamberTemperature().fCurrentValue >= + currentControlEntry.fChamberTemperature) || + (getChamberTemperature().predict60s.fValue >= + currentControlEntry.fChamberTemperature)) { + ESP_LOGI(TAG, "Chamber target temperature reached: Disabling burner"); + bHeatingInAction = false; + setBurnerState(DISABLED); + setSafetyControlState(ENABLED); + } else if (esp_timer_get_time() - i64BurnerEnableTimestamp >= + BURNER_FAULT_DETECTION_THRESHOLD * 1000000U) { + if (eBurnerState == BURNER_UNKNOWN) { + if (getBurnerError() == FAULT) { + // ESP_LOGW(TAG, "Burner fault detected: Disabling burner"); + bHeatingInAction = false; + eBurnerState = BURNER_FAULT; + sControlState = CONTROL_FAULT_BURNER; + setBurnerState(DISABLED); + setSafetyControlState(ENABLED); + } else { + // ESP_LOGI(TAG, "No burner fault detected: Marking burner as + // fired"); + eBurnerState = BURNER_FIRED; + } + } + } + } + + // Manage circulation pump + if (getChamberTemperature().fCurrentValue <= + CIRCULATION_PUMP_TEMPERATURE_THRESHOLD) { + // ESP_LOGI(TAG, "Burner cooled down: Disabling circulation pump"); + setCirculationPumpState(DISABLED); + } else { + // ESP_LOGI(TAG, "Burner heated: Enabling circulation pump"); + setCirculationPumpState(ENABLED); + } + } // End of while(1) } -void taskControl(void *pvParameters) -{ - bool bHeatingInAction = false; - bool bSummerMode = false; - eBurnerState eBurnerState = BURNER_UNKNOWN; - int64_t i64BurnerEnableTimestamp = esp_timer_get_time(); +eControlState getControlState(void) { return sControlState; } - while (1) - { - vTaskDelay(PERIODIC_INTERVAL * 1000U / portTICK_PERIOD_MS); +eControlWeekday getControlCurrentWeekday(void) { + time_t now; + struct tm *timeinfo; - // Check for safety faults - if (getSafetyState() != SAFETY_NO_ERROR) - { - ESP_LOGW(TAG, "Control not possible due to safety fault!"); - sControlState = CONTROL_FAULT_SAFETY; - if (bHeatingInAction) - { - ESP_LOGW(TAG, "Disabling burner due to safety fault"); - bHeatingInAction = false; - setBurnerState(DISABLED); - setSafetyControlState(ENABLED); - } - continue; - } + time(&now); + timeinfo = localtime(&now); - // Check for SNTP faults - if (getSntpState() != SYNC_SUCCESSFUL) - { - ESP_LOGW(TAG, "Control not possible due to SNTP fault!"); - sControlState = CONTROL_FAULT_SNTP; - if (bHeatingInAction) - { - ESP_LOGW(TAG, "Disabling burner due to SNTP fault"); - bHeatingInAction = false; - setBurnerState(DISABLED); - setSafetyControlState(ENABLED); - } - continue; - } - - findControlCurrentTemperatureEntry(); - sControlTemperatureEntry currentControlEntry = getControlCurrentTemperatureEntry(); - - if (getOutdoorTemperature().fDampedValue >= SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH) - { - bSummerMode = true; - } - else if (getOutdoorTemperature().fDampedValue <= SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW) - { - bSummerMode = false; - } - - // Enable burner if outdoor temperature is low and return flow temperature is cooled down - if (!bHeatingInAction && (eBurnerState != BURNER_FAULT)) - { - if (bSummerMode) - { - // ESP_LOGI(TAG, "Outdoor temperature too warm: Disabling heating"); - setBurnerState(DISABLED); - setSafetyControlState(DISABLED); - sControlState = CONTROL_OUTDOOR_TOO_WARM; - } - else if ((getReturnFlowTemperature().average60s.fValue <= currentControlEntry.fReturnFlowTemperature) && - (getChamberTemperature().fCurrentValue <= CHAMBER_TEMPERATURE_THRESHOLD)) - { - ESP_LOGI(TAG, "Enabling burner: Return flow temperature target reached"); - eBurnerState = BURNER_UNKNOWN; - bHeatingInAction = true; - setBurnerState(ENABLED); - setSafetyControlState(ENABLED); - i64BurnerEnableTimestamp = esp_timer_get_time(); - sControlState = CONTROL_HEATING; - } - else - { - // ESP_LOGI(TAG, "Return flow temperature too warm: Disabling heating"); - sControlState = CONTROL_RETURN_FLOW_TOO_WARM; - } - } - - // Disable burner if target temperature is reached or a fault occurred - if (bHeatingInAction) - { - if ((getChamberTemperature().fCurrentValue >= currentControlEntry.fChamberTemperature) || - (getChamberTemperature().predict60s.fValue >= currentControlEntry.fChamberTemperature)) - { - ESP_LOGI(TAG, "Chamber target temperature reached: Disabling burner"); - bHeatingInAction = false; - setBurnerState(DISABLED); - setSafetyControlState(ENABLED); - } - else if (esp_timer_get_time() - i64BurnerEnableTimestamp >= BURNER_FAULT_DETECTION_THRESHOLD * 1000000U) - { - if (eBurnerState == BURNER_UNKNOWN) - { - if (getBurnerError() == FAULT) - { - // ESP_LOGW(TAG, "Burner fault detected: Disabling burner"); - bHeatingInAction = false; - eBurnerState = BURNER_FAULT; - sControlState = CONTROL_FAULT_BURNER; - setBurnerState(DISABLED); - setSafetyControlState(ENABLED); - } - else - { - // ESP_LOGI(TAG, "No burner fault detected: Marking burner as fired"); - eBurnerState = BURNER_FIRED; - } - } - } - } - - // Manage circulation pump - if (getChamberTemperature().fCurrentValue <= CIRCULATION_PUMP_TEMPERATURE_THRESHOLD) - { - // ESP_LOGI(TAG, "Burner cooled down: Disabling circulation pump"); - setCirculationPumpState(DISABLED); - } - else - { - // ESP_LOGI(TAG, "Burner heated: Enabling circulation pump"); - setCirculationPumpState(ENABLED); - } - } // End of while(1) + int day = timeinfo->tm_wday; + return (eControlWeekday)((day == 0) ? 6 : day - 1); } -eControlState getControlState(void) -{ - return sControlState; -} - -eControlWeekday getControlCurrentWeekday(void) -{ - time_t now; - struct tm *timeinfo; - - time(&now); - timeinfo = localtime(&now); - - int day = timeinfo->tm_wday; - return (eControlWeekday)((day == 0) ? 6 : day - 1); -} - -void findControlCurrentTemperatureEntry(void) -{ +/** + * @brief Finds the active temperature control entry for the current time. + * + * Searches through the weekly schedule to find the most recent entry + * that should be active at the current date/time. Falls back to the + * last entry in the week if no suitable entry is found. + */ +/** + * @brief Finds the active temperature control entry for the current time. + * + * Searches through the weekly schedule to find the most recent entry + * that should be active at the current date/time. Falls back to the + * last entry in the week if no suitable entry is found. + */ +void findControlCurrentTemperatureEntry(void) { eControlWeekday currentDay = getControlCurrentWeekday(); + // Get current time time_t now; struct tm timeinfo; time(&now); localtime_r(&now, &timeinfo); + + int currentHour = timeinfo.tm_hour; + int currentMinute = timeinfo.tm_min; - int hour = timeinfo.tm_hour; - int minute = timeinfo.tm_min; + //ESP_LOGI(TAG, "Searching for control entry - Day: %d, Time: %02d:%02d", currentDay, currentHour, currentMinute); - for (int i = 0; i < sizeof(aControlTable) / sizeof(aControlTable[0]); i++) - { - for (int j = 0; j < aControlTable[i].entryCount; j++) - { - if ((aControlTable[i].day > currentDay) || - (aControlTable[i].day == currentDay && aControlTable[i].aTemperatureEntries[j].timestamp.hour > hour) || - (aControlTable[i].day == currentDay && aControlTable[i].aTemperatureEntries[j].timestamp.hour == hour && aControlTable[i].aTemperatureEntries[j].timestamp.minute >= minute)) - { - currentControlEntry = aControlTable[i].aTemperatureEntries[j]; + // Search through all days and entries + for (int dayIndex = 0; dayIndex < 7; dayIndex++) { + const sControlDay* day = &aControlTable[dayIndex]; + + for (int entryIndex = 0; entryIndex < day->entryCount; entryIndex++) { + 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) { + // Found next scheduled entry, so determine the previous (active) one + if (entryIndex > 0) { + // Use previous entry from same day + currentControlEntry = day->aTemperatureEntries[entryIndex - 1]; + } 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; } - currentControlEntry = aControlTable[i].aTemperatureEntries[j]; } } + + // 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 getControlCurrentTemperatureEntry(void) { + return currentControlEntry; } -- 2.50.1 From b6150ad452cc31a1865e016128fbc2cc05d80f5174c700ba8a3b66b665fb9e5a Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 1 Nov 2025 17:46:33 +0100 Subject: [PATCH 4/4] formatting --- main/control.c | 359 +++++++++++++++++++++++++++---------------------- 1 file changed, 199 insertions(+), 160 deletions(-) diff --git a/main/control.c b/main/control.c index 6c2ede5..1098bc7 100644 --- a/main/control.c +++ b/main/control.c @@ -15,14 +15,14 @@ #define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT 25.0f #define CHAMBER_TEMPERATURE_TARGET 80.0f // Max cutoff temperature #define CHAMBER_TEMPERATURE_THRESHOLD 45.0f // Min threshold for burner enable -#define SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH \ - 20.0f // Summer mode will be activated -#define SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW \ - 15.0f // Summer mode will be deactivated --> Heating starts -#define CIRCULATION_PUMP_TEMPERATURE_THRESHOLD \ - 30.0f // Min threshold of chamber for circulation pump enable -#define BURNER_FAULT_DETECTION_THRESHOLD \ - (60U * 4U) // Burner fault detection after 4 minutes +#define SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH \ + 20.0f // Summer mode will be activated +#define SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW \ + 15.0f // Summer mode will be deactivated --> Heating starts +#define CIRCULATION_PUMP_TEMPERATURE_THRESHOLD \ + 30.0f // Min threshold of chamber for circulation pump enable +#define BURNER_FAULT_DETECTION_THRESHOLD \ + (60U * 4U) // Burner fault detection after 4 minutes static const char *TAG = "smart-oil-heater-control-system-control"; static eControlState sControlState = CONTROL_STARTING; @@ -92,148 +92,179 @@ static sControlTemperatureEntry currentControlEntry = void taskControl(void *pvParameters); void findControlCurrentTemperatureEntry(void); -void initControl(void) { - BaseType_t taskCreated = - xTaskCreate(taskControl, // Function to implement the task - "taskControl", // Task name - 8192, // 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) - ); +void initControl(void) +{ + BaseType_t taskCreated = + xTaskCreate(taskControl, // Function to implement the task + "taskControl", // Task name + 8192, // 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) + ); - if (taskCreated == pdPASS) { - ESP_LOGI(TAG, "Task created successfully!"); - } else { - ESP_LOGE(TAG, "Failed to create task"); - } + if (taskCreated == pdPASS) + { + ESP_LOGI(TAG, "Task created successfully!"); + } + else + { + ESP_LOGE(TAG, "Failed to create task"); + } } -void taskControl(void *pvParameters) { - bool bHeatingInAction = false; - bool bSummerMode = false; - eBurnerState eBurnerState = BURNER_UNKNOWN; - int64_t i64BurnerEnableTimestamp = esp_timer_get_time(); +void taskControl(void *pvParameters) +{ + bool bHeatingInAction = false; + bool bSummerMode = false; + eBurnerState eBurnerState = BURNER_UNKNOWN; + int64_t i64BurnerEnableTimestamp = esp_timer_get_time(); - while (1) { - vTaskDelay(PERIODIC_INTERVAL * 1000U / portTICK_PERIOD_MS); + while (1) + { + vTaskDelay(PERIODIC_INTERVAL * 1000U / portTICK_PERIOD_MS); - // Check for safety faults - if (getSafetyState() != SAFETY_NO_ERROR) { - ESP_LOGW(TAG, "Control not possible due to safety fault!"); - sControlState = CONTROL_FAULT_SAFETY; - if (bHeatingInAction) { - ESP_LOGW(TAG, "Disabling burner due to safety fault"); - bHeatingInAction = false; - setBurnerState(DISABLED); - setSafetyControlState(ENABLED); - } - continue; - } - - // Check for SNTP faults - if (getSntpState() != SYNC_SUCCESSFUL) { - ESP_LOGW(TAG, "Control not possible due to SNTP fault!"); - sControlState = CONTROL_FAULT_SNTP; - if (bHeatingInAction) { - ESP_LOGW(TAG, "Disabling burner due to SNTP fault"); - bHeatingInAction = false; - setBurnerState(DISABLED); - setSafetyControlState(ENABLED); - } - continue; - } - - findControlCurrentTemperatureEntry(); - sControlTemperatureEntry currentControlEntry = - getControlCurrentTemperatureEntry(); - - if (getOutdoorTemperature().fDampedValue >= - SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH) { - bSummerMode = true; - } else if (getOutdoorTemperature().fDampedValue <= - SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW) { - bSummerMode = false; - } - - // Enable burner if outdoor temperature is low and return flow temperature - // is cooled down - if (!bHeatingInAction && (eBurnerState != BURNER_FAULT)) { - if (bSummerMode) { - // ESP_LOGI(TAG, "Outdoor temperature too warm: Disabling heating"); - setBurnerState(DISABLED); - setSafetyControlState(DISABLED); - sControlState = CONTROL_OUTDOOR_TOO_WARM; - } else if ((getReturnFlowTemperature().average60s.fValue <= - currentControlEntry.fReturnFlowTemperature) && - (getChamberTemperature().fCurrentValue <= - CHAMBER_TEMPERATURE_THRESHOLD)) { - ESP_LOGI(TAG, - "Enabling burner: Return flow temperature target reached"); - eBurnerState = BURNER_UNKNOWN; - bHeatingInAction = true; - setBurnerState(ENABLED); - setSafetyControlState(ENABLED); - i64BurnerEnableTimestamp = esp_timer_get_time(); - sControlState = CONTROL_HEATING; - } else { - // ESP_LOGI(TAG, "Return flow temperature too warm: Disabling heating"); - sControlState = CONTROL_RETURN_FLOW_TOO_WARM; - } - } - - // Disable burner if target temperature is reached or a fault occurred - if (bHeatingInAction) { - if ((getChamberTemperature().fCurrentValue >= - currentControlEntry.fChamberTemperature) || - (getChamberTemperature().predict60s.fValue >= - currentControlEntry.fChamberTemperature)) { - ESP_LOGI(TAG, "Chamber target temperature reached: Disabling burner"); - bHeatingInAction = false; - setBurnerState(DISABLED); - setSafetyControlState(ENABLED); - } else if (esp_timer_get_time() - i64BurnerEnableTimestamp >= - BURNER_FAULT_DETECTION_THRESHOLD * 1000000U) { - if (eBurnerState == BURNER_UNKNOWN) { - if (getBurnerError() == FAULT) { - // ESP_LOGW(TAG, "Burner fault detected: Disabling burner"); - bHeatingInAction = false; - eBurnerState = BURNER_FAULT; - sControlState = CONTROL_FAULT_BURNER; - setBurnerState(DISABLED); - setSafetyControlState(ENABLED); - } else { - // ESP_LOGI(TAG, "No burner fault detected: Marking burner as - // fired"); - eBurnerState = BURNER_FIRED; - } + // Check for safety faults + if (getSafetyState() != SAFETY_NO_ERROR) + { + ESP_LOGW(TAG, "Control not possible due to safety fault!"); + sControlState = CONTROL_FAULT_SAFETY; + if (bHeatingInAction) + { + ESP_LOGW(TAG, "Disabling burner due to safety fault"); + bHeatingInAction = false; + setBurnerState(DISABLED); + setSafetyControlState(ENABLED); + } + continue; } - } - } - // Manage circulation pump - if (getChamberTemperature().fCurrentValue <= - CIRCULATION_PUMP_TEMPERATURE_THRESHOLD) { - // ESP_LOGI(TAG, "Burner cooled down: Disabling circulation pump"); - setCirculationPumpState(DISABLED); - } else { - // ESP_LOGI(TAG, "Burner heated: Enabling circulation pump"); - setCirculationPumpState(ENABLED); - } - } // End of while(1) + // Check for SNTP faults + if (getSntpState() != SYNC_SUCCESSFUL) + { + ESP_LOGW(TAG, "Control not possible due to SNTP fault!"); + sControlState = CONTROL_FAULT_SNTP; + if (bHeatingInAction) + { + ESP_LOGW(TAG, "Disabling burner due to SNTP fault"); + bHeatingInAction = false; + setBurnerState(DISABLED); + setSafetyControlState(ENABLED); + } + continue; + } + + findControlCurrentTemperatureEntry(); + sControlTemperatureEntry currentControlEntry = + getControlCurrentTemperatureEntry(); + + if (getOutdoorTemperature().fDampedValue >= + SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH) + { + bSummerMode = true; + } + else if (getOutdoorTemperature().fDampedValue <= + SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW) + { + bSummerMode = false; + } + + // Enable burner if outdoor temperature is low and return flow temperature + // is cooled down + if (!bHeatingInAction && (eBurnerState != BURNER_FAULT)) + { + if (bSummerMode) + { + // ESP_LOGI(TAG, "Outdoor temperature too warm: Disabling heating"); + setBurnerState(DISABLED); + setSafetyControlState(DISABLED); + sControlState = CONTROL_OUTDOOR_TOO_WARM; + } + else if ((getReturnFlowTemperature().average60s.fValue <= + currentControlEntry.fReturnFlowTemperature) && + (getChamberTemperature().fCurrentValue <= + CHAMBER_TEMPERATURE_THRESHOLD)) + { + ESP_LOGI(TAG, + "Enabling burner: Return flow temperature target reached"); + eBurnerState = BURNER_UNKNOWN; + bHeatingInAction = true; + setBurnerState(ENABLED); + setSafetyControlState(ENABLED); + i64BurnerEnableTimestamp = esp_timer_get_time(); + sControlState = CONTROL_HEATING; + } + else + { + // ESP_LOGI(TAG, "Return flow temperature too warm: Disabling heating"); + sControlState = CONTROL_RETURN_FLOW_TOO_WARM; + } + } + + // Disable burner if target temperature is reached or a fault occurred + if (bHeatingInAction) + { + if ((getChamberTemperature().fCurrentValue >= + currentControlEntry.fChamberTemperature) || + (getChamberTemperature().predict60s.fValue >= + currentControlEntry.fChamberTemperature)) + { + ESP_LOGI(TAG, "Chamber target temperature reached: Disabling burner"); + bHeatingInAction = false; + setBurnerState(DISABLED); + setSafetyControlState(ENABLED); + } + else if (esp_timer_get_time() - i64BurnerEnableTimestamp >= + BURNER_FAULT_DETECTION_THRESHOLD * 1000000U) + { + if (eBurnerState == BURNER_UNKNOWN) + { + if (getBurnerError() == FAULT) + { + // ESP_LOGW(TAG, "Burner fault detected: Disabling burner"); + bHeatingInAction = false; + eBurnerState = BURNER_FAULT; + sControlState = CONTROL_FAULT_BURNER; + setBurnerState(DISABLED); + setSafetyControlState(ENABLED); + } + else + { + // ESP_LOGI(TAG, "No burner fault detected: Marking burner as + // fired"); + eBurnerState = BURNER_FIRED; + } + } + } + } + + // Manage circulation pump + if (getChamberTemperature().fCurrentValue <= + CIRCULATION_PUMP_TEMPERATURE_THRESHOLD) + { + // ESP_LOGI(TAG, "Burner cooled down: Disabling circulation pump"); + setCirculationPumpState(DISABLED); + } + else + { + // ESP_LOGI(TAG, "Burner heated: Enabling circulation pump"); + setCirculationPumpState(ENABLED); + } + } // End of while(1) } eControlState getControlState(void) { return sControlState; } -eControlWeekday getControlCurrentWeekday(void) { - time_t now; - struct tm *timeinfo; +eControlWeekday getControlCurrentWeekday(void) +{ + time_t now; + struct tm *timeinfo; - time(&now); - timeinfo = localtime(&now); + time(&now); + timeinfo = localtime(&now); - int day = timeinfo->tm_wday; - return (eControlWeekday)((day == 0) ? 6 : day - 1); + int day = timeinfo->tm_wday; + return (eControlWeekday)((day == 0) ? 6 : day - 1); } /** @@ -245,12 +276,13 @@ eControlWeekday getControlCurrentWeekday(void) { */ /** * @brief Finds the active temperature control entry for the current time. - * + * * Searches through the weekly schedule to find the most recent entry * that should be active at the current date/time. Falls back to the * last entry in the week if no suitable entry is found. */ -void findControlCurrentTemperatureEntry(void) { +void findControlCurrentTemperatureEntry(void) +{ eControlWeekday currentDay = getControlCurrentWeekday(); // Get current time @@ -258,38 +290,46 @@ void findControlCurrentTemperatureEntry(void) { struct tm timeinfo; time(&now); localtime_r(&now, &timeinfo); - + 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); + // 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 sControlDay* day = &aControlTable[dayIndex]; + for (int dayIndex = 0; dayIndex < 7; dayIndex++) + { + const sControlDay *day = &aControlTable[dayIndex]; - for (int entryIndex = 0; entryIndex < day->entryCount; entryIndex++) { - const sControlTemperatureEntry* entry = &day->aTemperatureEntries[entryIndex]; + for (int entryIndex = 0; entryIndex < day->entryCount; entryIndex++) + { + 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) && + bool isTodayFutureTime = (day->day == currentDay) && ((entry->timestamp.hour > currentHour) || - (entry->timestamp.hour == currentHour && + (entry->timestamp.hour == currentHour && entry->timestamp.minute > currentMinute)); - if (isFutureDay || isTodayFutureTime) { + if (isFutureDay || isTodayFutureTime) + { // Found next scheduled entry, so determine the previous (active) one - if (entryIndex > 0) { + if (entryIndex > 0) + { // Use previous entry from same day currentControlEntry = day->aTemperatureEntries[entryIndex - 1]; - } else if (dayIndex > 0) { + } + else if (dayIndex > 0) + { // Use last entry from previous day - const sControlDay* previousDay = &aControlTable[dayIndex - 1]; + const sControlDay *previousDay = &aControlTable[dayIndex - 1]; currentControlEntry = previousDay->aTemperatureEntries[previousDay->entryCount - 1]; - } else { + } + else + { // First entry of the week - wrap to last entry of Sunday - const sControlDay* sunday = &aControlTable[6]; + const sControlDay *sunday = &aControlTable[6]; currentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1]; } /* @@ -307,14 +347,13 @@ void findControlCurrentTemperatureEntry(void) { // If we reached here, current time is after all entries this week // Use the last entry (Sunday evening) - const sControlDay* sunday = &aControlTable[6]; + 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); + + // ESP_LOGI(TAG, "Using last entry of week - Time: %02d:%02d", currentControlEntry.timestamp.hour, currentControlEntry.timestamp.minute); } -sControlTemperatureEntry getControlCurrentTemperatureEntry(void) { - return currentControlEntry; +sControlTemperatureEntry getControlCurrentTemperatureEntry(void) +{ + return currentControlEntry; } -- 2.50.1