4 Commits

Author SHA256 Message Date
b6150ad452 formatting 2025-11-01 17:46:33 +01:00
d992218a7d fix search algo 2025-11-01 17:43:25 +01:00
55b62d7438 update readme 2025-11-01 17:00:00 +01:00
aeb9e04413 update control entry 2025-11-01 16:59:52 +01:00
2 changed files with 210 additions and 71 deletions

View File

@ -19,7 +19,11 @@ Sntp <|-- Metrics
class Inputs{ class Inputs{
+initInputs() +initInputs()
-initMeasurement()
-updateAverage()
-updatePrediction()
-taskInput() -taskInput()
-linearRegressionPredict()
+getChamberTemperature() +getChamberTemperature()
+getOutdoorTemperature() +getOutdoorTemperature()
+getInletFlowTemperature() +getInletFlowTemperature()
@ -38,7 +42,11 @@ Sntp <|-- Metrics
} }
class Control{ class Control{
initControl()
+taskControl() +taskControl()
+getControlCurrentWeekday()
-findControlCurrentTemperatureEntry()
+getControlCurrentTemperatureEntry()
-controlTable -controlTable
+getControlState() +getControlState()
} }
@ -79,33 +87,41 @@ Sntp <|-- Metrics
burner_fault_pending 1 burner_fault_pending 1
circulation_pump_enabled 1 circulation_pump_enabled 1
burner_enabled 0 burner_enabled 0
safety_contact_enabled 0 safety_contact_enabled 1
chamber_temperature 58.750000 chamber_temperature 37.250000
chamber_temperature_avg10 58.931252 chamber_temperature_avg10 37.237499
chamber_temperature_avg60 59.190475 chamber_temperature_avg60 37.438541
chamber_temperature_pred60 55.870998 chamber_temperature_damped 42.185040
inlet_flow_temperature 53.875000 chamber_temperature_pred60 36.638443
inlet_flow_temperature_avg10 53.900002 inlet_flow_temperature 35.625000
inlet_flow_temperature_avg60 53.994320 inlet_flow_temperature_avg10 35.618752
inlet_flow_temperature_pred60 52.848743 inlet_flow_temperature_avg60 35.415627
outdoor_temperature 18.000000 inlet_flow_temperature_damped 39.431259
outdoor_temperature_avg10 18.006250 inlet_flow_temperature_pred60 36.078678
outdoor_temperature_avg60 18.002840 outdoor_temperature 14.687500
outdoor_temperature_pred60 18.050785 outdoor_temperature_avg10 14.662500
return_flow_temperature 48.625000 outdoor_temperature_avg60 14.646875
return_flow_temperature_avg10 48.718750 outdoor_temperature_damped 9.169084
return_flow_temperature_avg60 48.846592 outdoor_temperature_pred60 14.660233
return_flow_temperature_pred60 47.383083 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 chamber_temperature_state 0
outdoor_temperature_state 0 outdoor_temperature_state 0
inlet_flow_temperature_state 0 inlet_flow_temperature_state 0
return_flow_temperature_state 0 return_flow_temperature_state 0
safety_state 0 safety_state 0
control_state 3 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 sntp_state 0
system_unixtime 1735242392 system_unixtime 1762012743
uptime_seconds 40 uptime_seconds 465229
wifi_rssi -74 wifi_rssi -72
``` ```
#### Status Encoding #### Status Encoding

View File

@ -1,10 +1,10 @@
#include "control.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "control.h"
#include "outputs.h"
#include "inputs.h" #include "inputs.h"
#include "outputs.h"
#include "safety.h" #include "safety.h"
#include "sntp.h" #include "sntp.h"
@ -15,24 +15,78 @@
#define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT 25.0f #define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT 25.0f
#define CHAMBER_TEMPERATURE_TARGET 80.0f // Max cutoff temperature #define CHAMBER_TEMPERATURE_TARGET 80.0f // Max cutoff temperature
#define CHAMBER_TEMPERATURE_THRESHOLD 45.0f // Min threshold for burner enable #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_HIGH \
#define SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW 15.0f // Summer mode will be deactivated --> Heating starts 20.0f // Summer mode will be activated
#define CIRCULATION_PUMP_TEMPERATURE_THRESHOLD 30.0f // Min threshold of chamber for circulation pump enable #define SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW \
#define BURNER_FAULT_DETECTION_THRESHOLD (60U * 4U) // Burner fault detection after 4 minutes 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 const char *TAG = "smart-oil-heater-control-system-control";
static eControlState sControlState = CONTROL_STARTING; static eControlState sControlState = CONTROL_STARTING;
// Control table for daily schedules // Control table for daily schedules
static const sControlDay aControlTable[] = { 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}}}, {MONDAY,
{TUESDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMBER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMBER_TEMPERATURE_TARGET}}}, 2U,
{WEDNESDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMBER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMBER_TEMPERATURE_TARGET}}}, {{{4, 45},
{THURSDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMBER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMBER_TEMPERATURE_TARGET}}}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
{FRIDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMBER_TEMPERATURE_TARGET}, {{23, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMBER_TEMPERATURE_TARGET}}}, 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}}}, {{22, 0},
{SUNDAY, 2U, {{{6, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMBER_TEMPERATURE_TARGET}, {{22, 30}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMBER_TEMPERATURE_TARGET}}}, 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 // Function prototypes
void taskControl(void *pvParameters); void taskControl(void *pvParameters);
@ -40,8 +94,8 @@ void findControlCurrentTemperatureEntry(void);
void initControl(void) void initControl(void)
{ {
BaseType_t taskCreated = xTaskCreate( BaseType_t taskCreated =
taskControl, // Function to implement the task xTaskCreate(taskControl, // Function to implement the task
"taskControl", // Task name "taskControl", // Task name
8192, // Stack size (in words, not bytes) 8192, // Stack size (in words, not bytes)
NULL, // Parameters to the task function (none in this case) NULL, // Parameters to the task function (none in this case)
@ -100,18 +154,23 @@ void taskControl(void *pvParameters)
continue; 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; bSummerMode = true;
} }
else if (getOutdoorTemperature().fDampedValue <= SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW) else if (getOutdoorTemperature().fDampedValue <=
SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW)
{ {
bSummerMode = false; 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 (!bHeatingInAction && (eBurnerState != BURNER_FAULT))
{ {
if (bSummerMode) if (bSummerMode)
@ -121,10 +180,13 @@ void taskControl(void *pvParameters)
setSafetyControlState(DISABLED); setSafetyControlState(DISABLED);
sControlState = CONTROL_OUTDOOR_TOO_WARM; sControlState = CONTROL_OUTDOOR_TOO_WARM;
} }
else if ((getReturnFlowTemperature().average60s.fValue <= currentControlEntry.fReturnFlowTemperature) && else if ((getReturnFlowTemperature().average60s.fValue <=
(getChamberTemperature().fCurrentValue <= CHAMBER_TEMPERATURE_THRESHOLD)) 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; eBurnerState = BURNER_UNKNOWN;
bHeatingInAction = true; bHeatingInAction = true;
setBurnerState(ENABLED); setBurnerState(ENABLED);
@ -142,15 +204,18 @@ void taskControl(void *pvParameters)
// Disable burner if target temperature is reached or a fault occurred // Disable burner if target temperature is reached or a fault occurred
if (bHeatingInAction) if (bHeatingInAction)
{ {
if ((getChamberTemperature().fCurrentValue >= currentControlEntry.fChamberTemperature) || if ((getChamberTemperature().fCurrentValue >=
(getChamberTemperature().predict60s.fValue >= currentControlEntry.fChamberTemperature)) currentControlEntry.fChamberTemperature) ||
(getChamberTemperature().predict60s.fValue >=
currentControlEntry.fChamberTemperature))
{ {
ESP_LOGI(TAG, "Chamber target temperature reached: Disabling burner"); ESP_LOGI(TAG, "Chamber target temperature reached: Disabling burner");
bHeatingInAction = false; bHeatingInAction = false;
setBurnerState(DISABLED); setBurnerState(DISABLED);
setSafetyControlState(ENABLED); 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) if (eBurnerState == BURNER_UNKNOWN)
{ {
@ -165,7 +230,8 @@ void taskControl(void *pvParameters)
} }
else 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; eBurnerState = BURNER_FIRED;
} }
} }
@ -173,7 +239,8 @@ void taskControl(void *pvParameters)
} }
// Manage circulation pump // 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"); // ESP_LOGI(TAG, "Burner cooled down: Disabling circulation pump");
setCirculationPumpState(DISABLED); setCirculationPumpState(DISABLED);
@ -186,10 +253,7 @@ void taskControl(void *pvParameters)
} // End of while(1) } // End of while(1)
} }
eControlState getControlState(void) eControlState getControlState(void) { return sControlState; }
{
return sControlState;
}
eControlWeekday getControlCurrentWeekday(void) eControlWeekday getControlCurrentWeekday(void)
{ {
@ -203,31 +267,90 @@ eControlWeekday getControlCurrentWeekday(void)
return (eControlWeekday)((day == 0) ? 6 : day - 1); 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) void findControlCurrentTemperatureEntry(void)
{ {
eControlWeekday currentDay = getControlCurrentWeekday(); eControlWeekday currentDay = getControlCurrentWeekday();
// Get current time
time_t now; time_t now;
struct tm timeinfo; struct tm timeinfo;
time(&now); time(&now);
localtime_r(&now, &timeinfo); localtime_r(&now, &timeinfo);
int hour = timeinfo.tm_hour; int currentHour = timeinfo.tm_hour;
int minute = timeinfo.tm_min; 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) || const sControlTemperatureEntry *entry = &day->aTemperatureEntries[entryIndex];
(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)) // 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];
} }
currentControlEntry = aControlTable[i].aTemperatureEntries[j]; 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 = &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) sControlTemperatureEntry getControlCurrentTemperatureEntry(void)