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 diff --git a/main/control.c b/main/control.c index 7ddcab4..1098bc7 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,26 +13,80 @@ // 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); @@ -40,14 +94,14 @@ 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) - ); + 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) { @@ -100,18 +154,23 @@ void taskControl(void *pvParameters) continue; } - sControlTemperatureEntry currentControlEntry = getControlCurrentTemperatureEntry(); + findControlCurrentTemperatureEntry(); + sControlTemperatureEntry currentControlEntry = + getControlCurrentTemperatureEntry(); - if (getOutdoorTemperature().fDampedValue >= SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH) + if (getOutdoorTemperature().fDampedValue >= + SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH) { bSummerMode = true; } - else if (getOutdoorTemperature().fDampedValue <= SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW) + 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 + // Enable burner if outdoor temperature is low and return flow temperature + // is cooled down if (!bHeatingInAction && (eBurnerState != BURNER_FAULT)) { if (bSummerMode) @@ -121,10 +180,13 @@ void taskControl(void *pvParameters) setSafetyControlState(DISABLED); sControlState = CONTROL_OUTDOOR_TOO_WARM; } - else if ((getReturnFlowTemperature().average60s.fValue <= currentControlEntry.fReturnFlowTemperature) && - (getChamberTemperature().fCurrentValue <= CHAMBER_TEMPERATURE_THRESHOLD)) + else if ((getReturnFlowTemperature().average60s.fValue <= + currentControlEntry.fReturnFlowTemperature) && + (getChamberTemperature().fCurrentValue <= + CHAMBER_TEMPERATURE_THRESHOLD)) { - ESP_LOGI(TAG, "Enabling burner: Return flow temperature target reached"); + ESP_LOGI(TAG, + "Enabling burner: Return flow temperature target reached"); eBurnerState = BURNER_UNKNOWN; bHeatingInAction = true; setBurnerState(ENABLED); @@ -142,15 +204,18 @@ void taskControl(void *pvParameters) // Disable burner if target temperature is reached or a fault occurred if (bHeatingInAction) { - if ((getChamberTemperature().fCurrentValue >= currentControlEntry.fChamberTemperature) || - (getChamberTemperature().predict60s.fValue >= currentControlEntry.fChamberTemperature)) + 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) + else if (esp_timer_get_time() - i64BurnerEnableTimestamp >= + BURNER_FAULT_DETECTION_THRESHOLD * 1000000U) { if (eBurnerState == BURNER_UNKNOWN) { @@ -165,7 +230,8 @@ void taskControl(void *pvParameters) } else { - // ESP_LOGI(TAG, "No burner fault detected: Marking burner as fired"); + // ESP_LOGI(TAG, "No burner fault detected: Marking burner as + // fired"); eBurnerState = BURNER_FIRED; } } @@ -173,7 +239,8 @@ void taskControl(void *pvParameters) } // Manage circulation pump - if (getChamberTemperature().fCurrentValue <= CIRCULATION_PUMP_TEMPERATURE_THRESHOLD) + if (getChamberTemperature().fCurrentValue <= + CIRCULATION_PUMP_TEMPERATURE_THRESHOLD) { // ESP_LOGI(TAG, "Burner cooled down: Disabling circulation pump"); setCirculationPumpState(DISABLED); @@ -186,10 +253,7 @@ void taskControl(void *pvParameters) } // End of while(1) } -eControlState getControlState(void) -{ - return sControlState; -} +eControlState getControlState(void) { return sControlState; } eControlWeekday getControlCurrentWeekday(void) { @@ -203,31 +267,90 @@ eControlWeekday getControlCurrentWeekday(void) return (eControlWeekday)((day == 0) ? 6 : day - 1); } +/** + * @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 hour = timeinfo.tm_hour; - int minute = timeinfo.tm_min; + int currentHour = timeinfo.tm_hour; + int currentMinute = timeinfo.tm_min; - for (int i = 0; i < sizeof(aControlTable) / sizeof(aControlTable[0]); i++) + // 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++) { - for (int j = 0; j < aControlTable[i].entryCount; j++) + const sControlDay *day = &aControlTable[dayIndex]; + + for (int entryIndex = 0; entryIndex < day->entryCount; entryIndex++) { - 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)) + 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) { - currentControlEntry = aControlTable[i].aTemperatureEntries[j]; + // 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)