Compare commits
	
		
			55 Commits
		
	
	
		
			a0.0.1
			...
			bugfix/con
		
	
	| Author | SHA256 | Date | |
|---|---|---|---|
| b6150ad452 | |||
| d992218a7d | |||
| 55b62d7438 | |||
| aeb9e04413 | |||
| dddf2c9bf0 | |||
| 33c7bc4007 | |||
| 524d94c515 | |||
| e8c62a1bd7 | |||
| 5b987bfd5b | |||
| b3a571da3f | |||
| 9ff3b38f70 | |||
| 067dc84afa | |||
| 9974e2d738 | |||
| 6eca00200e | |||
| ac15376f6b | |||
| dcace073d9 | |||
| da7a1be183 | |||
| 2477ccb42a | |||
| f66b831666 | |||
| 66b7f8320e | |||
| 416cda0f50 | |||
| 8ca3d97165 | |||
| c9b7313608 | |||
| fa958dd53b | |||
| 3771a83fcc | |||
| a72c0673b1 | |||
| 999af9d888 | |||
| 8a8bcd078b | |||
| 59b8c3e2b2 | |||
| 06c6612ef6 | |||
| e790660c36 | |||
| 3c972296ce | |||
| 8672241151 | |||
| 25b0a11694 | |||
| effd5c19e9 | |||
| b21dc720ed | |||
| 4ffa416f6f | |||
| 5fde319b63 | |||
| 6b38a73d77 | |||
| b5229a4082 | |||
| 856f009e7f | |||
| 1564860213 | |||
| 29223c0070 | |||
| 655d890a0f | |||
| 59eb361431 | |||
| 56f1831d8c | |||
| 80e48f632f | |||
| 26d0761aed | |||
| e267e2be58 | |||
| 795223ff66 | |||
| 5380cc9cca | |||
| 8effd730b9 | |||
| 4deed03190 | |||
| ff16e601fb | |||
| 307278e679 | 
							
								
								
									
										74
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										74
									
								
								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()
 | 
			
		||||
    }
 | 
			
		||||
@ -77,31 +85,43 @@ Sntp <|-- Metrics
 | 
			
		||||
#### Example
 | 
			
		||||
```
 | 
			
		||||
burner_fault_pending 1
 | 
			
		||||
circulation_pump_enabled 0
 | 
			
		||||
burner_enabled 1
 | 
			
		||||
circulation_pump_enabled 1
 | 
			
		||||
burner_enabled 0
 | 
			
		||||
safety_contact_enabled 1
 | 
			
		||||
chamber_temperature 21.812500
 | 
			
		||||
chamber_temperature_avg10 21.837500
 | 
			
		||||
chamber_temperature_avg60 21.825521
 | 
			
		||||
inlet_flow_temperature 22.437500
 | 
			
		||||
inlet_flow_temperature_avg10 22.437500
 | 
			
		||||
inlet_flow_temperature_avg60 22.434896
 | 
			
		||||
outdoor_temperature 21.937500
 | 
			
		||||
outdoor_temperature_avg10 21.937500
 | 
			
		||||
outdoor_temperature_avg60 21.933594
 | 
			
		||||
return_flow_temperature 22.375000
 | 
			
		||||
return_flow_temperature_avg10 22.375000
 | 
			
		||||
return_flow_temperature_avg60 22.375000
 | 
			
		||||
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 5
 | 
			
		||||
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 1734814285
 | 
			
		||||
uptime_seconds 90
 | 
			
		||||
wifi_rssi -63
 | 
			
		||||
system_unixtime 1762012743
 | 
			
		||||
uptime_seconds 465229
 | 
			
		||||
wifi_rssi -72
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Status Encoding
 | 
			
		||||
@ -131,15 +151,15 @@ wifi_rssi -63
 | 
			
		||||
##### Control Loop
 | 
			
		||||
 - control_state
 | 
			
		||||
 | 
			
		||||
| Enum eControlState in [control.h](main/control.h) | Value | Description                        |
 | 
			
		||||
|---------------------------------------------------|-------|------------------------------------|
 | 
			
		||||
| CONTROL_STARTING                                  | 0     |                                    |
 | 
			
		||||
| CONTROL_HEATING                                   | 1     | Burner running                     |
 | 
			
		||||
| CONTROL_OUTDOOR_TOO_WARM                          | 2     | Heating not needed                 |
 | 
			
		||||
| CONTROL_RETURN_FLOW_TOO_WARM                      | 3     | Heating not needed                 |
 | 
			
		||||
| CONTROL_BURNER_FAULT                              | 4     | Burner reported fault              |
 | 
			
		||||
| CONTROL_FAULT_SAFETY                              | 5     | Unable to control due safety fault |
 | 
			
		||||
| CONTROL_FAULT_SNTP                                | 6     | Unable to control due SNTP fault   |
 | 
			
		||||
| Enum eControlState in [control.h](main/control.h) | Value | Description                                      |
 | 
			
		||||
|---------------------------------------------------|-------|--------------------------------------------------|
 | 
			
		||||
| CONTROL_STARTING                                  | 0     |                                                  |
 | 
			
		||||
| CONTROL_HEATING                                   | 1     | Burner running                                   |
 | 
			
		||||
| CONTROL_OUTDOOR_TOO_WARM                          | 2     | Heating not needed                               |
 | 
			
		||||
| CONTROL_RETURN_FLOW_TOO_WARM                      | 3     | Heating not needed                               |
 | 
			
		||||
| CONTROL_FAULT_BURNER                              | 4     | Burner reported fault after threshold is reached |
 | 
			
		||||
| CONTROL_FAULT_SAFETY                              | 5     | Unable to control due safety fault               |
 | 
			
		||||
| CONTROL_FAULT_SNTP                                | 6     | Unable to control due SNTP fault                 |
 | 
			
		||||
 | 
			
		||||
##### SNTP Client
 | 
			
		||||
 - sntp_state
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										368
									
								
								main/control.c
									
									
									
									
									
								
							
							
						
						
									
										368
									
								
								main/control.c
									
									
									
									
									
								
							@ -1,45 +1,107 @@
 | 
			
		||||
#include "control.h"
 | 
			
		||||
#include "esp_log.h"
 | 
			
		||||
#include "esp_timer.h"
 | 
			
		||||
#include "freertos/FreeRTOS.h"
 | 
			
		||||
#include "freertos/task.h"
 | 
			
		||||
#include "esp_log.h"
 | 
			
		||||
#include "control.h"
 | 
			
		||||
#include "outputs.h"
 | 
			
		||||
#include "inputs.h"
 | 
			
		||||
#include "outputs.h"
 | 
			
		||||
#include "safety.h"
 | 
			
		||||
#include "sntp.h"
 | 
			
		||||
 | 
			
		||||
#define PERIODIC_INTERVAL 1U // run control loop every 1sec
 | 
			
		||||
#define PERIODIC_INTERVAL 1U // Run control loop every 1 second
 | 
			
		||||
 | 
			
		||||
#define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY 30.0
 | 
			
		||||
#define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT 25.0
 | 
			
		||||
#define CHAMPER_TEMPERATURE_TARGET 70.0
 | 
			
		||||
// 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
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "smart-oil-heater-control-system-control";
 | 
			
		||||
static eControlState sControlState = CONTROL_STARTING;
 | 
			
		||||
 | 
			
		||||
static sControlDay aControlTable[] = {
 | 
			
		||||
    {MONDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMPER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMPER_TEMPERATURE_TARGET}}},
 | 
			
		||||
    {TUESDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMPER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMPER_TEMPERATURE_TARGET}}},
 | 
			
		||||
    {WEDNESDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMPER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMPER_TEMPERATURE_TARGET}}},
 | 
			
		||||
    {THURSDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMPER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMPER_TEMPERATURE_TARGET}}},
 | 
			
		||||
    {FRIDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMPER_TEMPERATURE_TARGET}, {{23, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMPER_TEMPERATURE_TARGET}}},
 | 
			
		||||
    {SATURDAY, 2U, {{{6, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMPER_TEMPERATURE_TARGET}, {{23, 30}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMPER_TEMPERATURE_TARGET}}},
 | 
			
		||||
    {SUNDAY, 2U, {{{6, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMPER_TEMPERATURE_TARGET}, {{22, 30}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMPER_TEMPERATURE_TARGET}}},
 | 
			
		||||
// 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}}},
 | 
			
		||||
};
 | 
			
		||||
static sControlTemperatureEntry currentControlEntry =
 | 
			
		||||
    aControlTable[0].aTemperatureEntries[0];
 | 
			
		||||
 | 
			
		||||
// Function prototypes
 | 
			
		||||
void taskControl(void *pvParameters);
 | 
			
		||||
eControlWeekday getCurrentWeekday(void);
 | 
			
		||||
sControlTemperatureEntry getCurrentTemperatureEntry(void);
 | 
			
		||||
void findControlCurrentTemperatureEntry(void);
 | 
			
		||||
 | 
			
		||||
void initControl(void)
 | 
			
		||||
{
 | 
			
		||||
    BaseType_t taskCreated = xTaskCreate(
 | 
			
		||||
        taskControl,   // Function to implement the task
 | 
			
		||||
        "taskControl", // Task name
 | 
			
		||||
        4096,          // Stack size (in words, not bytes)
 | 
			
		||||
        NULL,          // Parameters to the task function (none in this case)
 | 
			
		||||
        5,             // Task priority (higher number = higher priority)
 | 
			
		||||
        NULL           // Task handle (optional)
 | 
			
		||||
    );
 | 
			
		||||
    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)
 | 
			
		||||
    {
 | 
			
		||||
@ -54,158 +116,244 @@ void initControl(void)
 | 
			
		||||
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 == true)
 | 
			
		||||
            if (bHeatingInAction)
 | 
			
		||||
            {
 | 
			
		||||
                ESP_LOGI(TAG, "Control not possible due to safety fault: Disable burner");
 | 
			
		||||
                ESP_LOGW(TAG, "Disabling burner due to safety fault");
 | 
			
		||||
                bHeatingInAction = false;
 | 
			
		||||
                setCirculationPumpState(ENABLED);
 | 
			
		||||
                setBurnerState(DISABLED);
 | 
			
		||||
                setSafetyControlState(ENABLED);
 | 
			
		||||
            }
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check for SNTP faults
 | 
			
		||||
        if (getSntpState() != SYNC_SUCCESSFUL)
 | 
			
		||||
        {
 | 
			
		||||
            ESP_LOGW(TAG, "Control not possible due to sntp fault!");
 | 
			
		||||
            ESP_LOGW(TAG, "Control not possible due to SNTP fault!");
 | 
			
		||||
            sControlState = CONTROL_FAULT_SNTP;
 | 
			
		||||
            if (bHeatingInAction == true)
 | 
			
		||||
            if (bHeatingInAction)
 | 
			
		||||
            {
 | 
			
		||||
                ESP_LOGI(TAG, "Control not possible due to sntp fault: Disable burner");
 | 
			
		||||
                ESP_LOGW(TAG, "Disabling burner due to SNTP fault");
 | 
			
		||||
                bHeatingInAction = false;
 | 
			
		||||
                setCirculationPumpState(ENABLED);
 | 
			
		||||
                setBurnerState(DISABLED);
 | 
			
		||||
                setSafetyControlState(ENABLED);
 | 
			
		||||
            }
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        sControlTemperatureEntry currentControlEntry = getCurrentTemperatureEntry();
 | 
			
		||||
        // ESP_LOGI(TAG, "Control Entry Hour: %i Minute: %i ChamberTemp: %lf ReturnFlowTemp: %lf", currentControlEntry.timestamp.hour, currentControlEntry.timestamp.minute, currentControlEntry.fChamberTemperature, currentControlEntry.fReturnFlowTemperature);
 | 
			
		||||
        findControlCurrentTemperatureEntry();
 | 
			
		||||
        sControlTemperatureEntry currentControlEntry =
 | 
			
		||||
            getControlCurrentTemperatureEntry();
 | 
			
		||||
 | 
			
		||||
        if (bHeatingInAction == true)
 | 
			
		||||
        if (getOutdoorTemperature().fDampedValue >=
 | 
			
		||||
            SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH)
 | 
			
		||||
        {
 | 
			
		||||
            if (getChamberTemperature().fCurrentValue >= currentControlEntry.fChamberTemperature)
 | 
			
		||||
            {
 | 
			
		||||
                ESP_LOGI(TAG, "Chamber Target Temperature reached: Disable burner");
 | 
			
		||||
                bHeatingInAction = false;
 | 
			
		||||
                setCirculationPumpState(ENABLED);
 | 
			
		||||
                setBurnerState(DISABLED);
 | 
			
		||||
                setSafetyControlState(ENABLED);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                if (bHeatingInAction)
 | 
			
		||||
                {
 | 
			
		||||
                    // TODO: Check burner fault signal here
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            bSummerMode = true;
 | 
			
		||||
        }
 | 
			
		||||
        else if (getOutdoorTemperature().fDampedValue <=
 | 
			
		||||
                 SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW)
 | 
			
		||||
        {
 | 
			
		||||
            bSummerMode = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (bHeatingInAction == false)
 | 
			
		||||
        // Enable burner if outdoor temperature is low and return flow temperature
 | 
			
		||||
        // is cooled down
 | 
			
		||||
        if (!bHeatingInAction && (eBurnerState != BURNER_FAULT))
 | 
			
		||||
        {
 | 
			
		||||
            if ((getReturnFlowTemperature().average60s.fValue <= currentControlEntry.fReturnFlowTemperature) && (getChamberTemperature().fCurrentValue <= 45.0))
 | 
			
		||||
            if (bSummerMode)
 | 
			
		||||
            {
 | 
			
		||||
                ESP_LOGI(TAG, "Return Flow Target Temperature reached: Enable Burner");
 | 
			
		||||
                // 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;
 | 
			
		||||
                setCirculationPumpState(ENABLED);
 | 
			
		||||
                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;
 | 
			
		||||
}
 | 
			
		||||
eControlState getControlState(void) { return sControlState; }
 | 
			
		||||
 | 
			
		||||
eControlWeekday getCurrentWeekday(void)
 | 
			
		||||
eControlWeekday getControlCurrentWeekday(void)
 | 
			
		||||
{
 | 
			
		||||
    time_t now;
 | 
			
		||||
    struct tm *timeinfo;
 | 
			
		||||
 | 
			
		||||
    // Get the current time
 | 
			
		||||
    time(&now);
 | 
			
		||||
    timeinfo = localtime(&now); // Convert to local time
 | 
			
		||||
    timeinfo = localtime(&now);
 | 
			
		||||
 | 
			
		||||
    // Get the day of the week (0 = Sunday, 1 = Monday, ..., 6 = Saturday)
 | 
			
		||||
    int day = timeinfo->tm_wday;
 | 
			
		||||
 | 
			
		||||
    // Adjust so that Monday = 0, Sunday = 6
 | 
			
		||||
    if (day == 0)
 | 
			
		||||
    {
 | 
			
		||||
        day = 6; // Sunday becomes 6
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        day -= 1; // Shift other days to make Monday = 0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (eControlWeekday)day;
 | 
			
		||||
    return (eControlWeekday)((day == 0) ? 6 : day - 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sControlTemperatureEntry getCurrentTemperatureEntry(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)
 | 
			
		||||
{
 | 
			
		||||
    sControlTemperatureEntry result = aControlTable[0].aTemperatureEntries[0];
 | 
			
		||||
    eControlWeekday currentDay = getCurrentWeekday();
 | 
			
		||||
    eControlWeekday currentDay = getControlCurrentWeekday();
 | 
			
		||||
 | 
			
		||||
    // Get current time
 | 
			
		||||
    time_t now;
 | 
			
		||||
    struct tm timeinfo;
 | 
			
		||||
 | 
			
		||||
    // Get the current time
 | 
			
		||||
    time(&now);
 | 
			
		||||
    // Convert to local time structure
 | 
			
		||||
    localtime_r(&now, &timeinfo);
 | 
			
		||||
    // Extract hour and minute
 | 
			
		||||
    int hour = timeinfo.tm_hour;  // Hour (0-23)
 | 
			
		||||
    int minute = timeinfo.tm_min; // Minute (0-59)u
 | 
			
		||||
 | 
			
		||||
    // ESP_LOGI(TAG, "Current Day: %i Hour: %i Minute: %i", currentDay, hour, minute);
 | 
			
		||||
    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++)
 | 
			
		||||
    {
 | 
			
		||||
        /// loops through days
 | 
			
		||||
        // ESP_LOGI(TAG, "Day %d: %d", i + 1, aControlTable[i].day);
 | 
			
		||||
        // int numberOfEntries = aControlTable[i].entryCount;
 | 
			
		||||
        // ESP_LOGI(TAG, "Number of entries: %i", numberOfEntries);
 | 
			
		||||
        const sControlDay *day = &aControlTable[dayIndex];
 | 
			
		||||
 | 
			
		||||
        for (int j = 0; j < aControlTable[i].entryCount; j++)
 | 
			
		||||
        for (int entryIndex = 0; entryIndex < day->entryCount; entryIndex++)
 | 
			
		||||
        {
 | 
			
		||||
            if ((aControlTable[i].day) > currentDay)
 | 
			
		||||
            {
 | 
			
		||||
                // ESP_LOGI(TAG, "DAY Return Control Entry Day: %i Hour: %i Minute: %i ChamberTemp: %lf ReturnFlowTemp: %lf", aControlTable[i].day, aControlTable[i].aTemperatureEntries[j].timestamp.hour, aControlTable[i].aTemperatureEntries[j].timestamp.minute, aControlTable[i].aTemperatureEntries[j].fChamberTemperature, aControlTable[i].aTemperatureEntries[j].fReturnFlowTemperature);
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
            const sControlTemperatureEntry *entry = &day->aTemperatureEntries[entryIndex];
 | 
			
		||||
 | 
			
		||||
            if ((aControlTable[i].day == currentDay) && (aControlTable[i].aTemperatureEntries[j].timestamp.hour > hour))
 | 
			
		||||
            {
 | 
			
		||||
                // ESP_LOGI(TAG, "HOUR Return Control Entry Day: %i Hour: %i Minute: %i ChamberTemp: %lf ReturnFlowTemp: %lf", aControlTable[i].day, aControlTable[i].aTemperatureEntries[j].timestamp.hour, aControlTable[i].aTemperatureEntries[j].timestamp.minute, aControlTable[i].aTemperatureEntries[j].fChamberTemperature, aControlTable[i].aTemperatureEntries[j].fReturnFlowTemperature);
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
            // 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 ((aControlTable[i].day == currentDay) && (aControlTable[i].aTemperatureEntries[j].timestamp.hour == hour) && (aControlTable[i].aTemperatureEntries[j].timestamp.minute == minute))
 | 
			
		||||
            if (isFutureDay || isTodayFutureTime)
 | 
			
		||||
            {
 | 
			
		||||
                // ESP_LOGI(TAG, "MINUTE Return Control Entry Day: %i Hour: %i Minute: %i ChamberTemp: %lf ReturnFlowTemp: %lf", aControlTable[i].day, aControlTable[i].aTemperatureEntries[j].timestamp.hour, aControlTable[i].aTemperatureEntries[j].timestamp.minute, aControlTable[i].aTemperatureEntries[j].fChamberTemperature, aControlTable[i].aTemperatureEntries[j].fReturnFlowTemperature);
 | 
			
		||||
                return result;
 | 
			
		||||
                // 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;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // ESP_LOGI(TAG, "SET Return Control Entry Day: %i Hour: %i Minute: %i ChamberTemp: %lf ReturnFlowTemp: %lf", aControlTable[i].day, aControlTable[i].aTemperatureEntries[j].timestamp.hour, aControlTable[i].aTemperatureEntries[j].timestamp.minute, aControlTable[i].aTemperatureEntries[j].fChamberTemperature, aControlTable[i].aTemperatureEntries[j].fReturnFlowTemperature);
 | 
			
		||||
            result = aControlTable[i].aTemperatureEntries[j];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    // 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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -9,11 +9,18 @@ typedef enum _ControlState
 | 
			
		||||
    CONTROL_HEATING,
 | 
			
		||||
    CONTROL_OUTDOOR_TOO_WARM,
 | 
			
		||||
    CONTROL_RETURN_FLOW_TOO_WARM,
 | 
			
		||||
    CONTROL_BURNER_FAULT,
 | 
			
		||||
    CONTROL_FAULT_BURNER,
 | 
			
		||||
    CONTROL_FAULT_SAFETY,
 | 
			
		||||
    CONTROL_FAULT_SNTP,
 | 
			
		||||
} eControlState;
 | 
			
		||||
 | 
			
		||||
typedef enum _BurnerState
 | 
			
		||||
{
 | 
			
		||||
    BURNER_UNKNOWN, // Burner is disabled or state after enabling is still unkown
 | 
			
		||||
    BURNER_FIRED,   // Burner fired successfully
 | 
			
		||||
    BURNER_FAULT    // Burner was unable to fire successfully
 | 
			
		||||
} eBurnerState;
 | 
			
		||||
 | 
			
		||||
typedef enum _ControlWeekday
 | 
			
		||||
{
 | 
			
		||||
    MONDAY,
 | 
			
		||||
@ -47,3 +54,5 @@ typedef struct _ControlDay
 | 
			
		||||
 | 
			
		||||
void initControl(void);
 | 
			
		||||
eControlState getControlState(void);
 | 
			
		||||
eControlWeekday getControlCurrentWeekday(void);
 | 
			
		||||
sControlTemperatureEntry getControlCurrentTemperatureEntry(void);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										154
									
								
								main/inputs.c
									
									
									
									
									
								
							
							
						
						
									
										154
									
								
								main/inputs.c
									
									
									
									
									
								
							@ -1,6 +1,8 @@
 | 
			
		||||
#include "freertos/FreeRTOS.h"
 | 
			
		||||
#include "freertos/task.h"
 | 
			
		||||
#include "driver/gpio.h"
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <math.h>
 | 
			
		||||
#include "esp_log.h"
 | 
			
		||||
#include <ds18x20.h>
 | 
			
		||||
 | 
			
		||||
@ -14,10 +16,10 @@ static const char *TAG = "smart-oil-heater-control-system-inputs";
 | 
			
		||||
const uint8_t uBurnerFaultPin = 19U;
 | 
			
		||||
const uint8_t uDS18B20Pin = 4U;
 | 
			
		||||
 | 
			
		||||
const onewire_addr_t uChamperTempSensorAddr = 0x3e0000001754be28;
 | 
			
		||||
const onewire_addr_t uOutdoorTempSensorAddr = 0x880000001648e328;
 | 
			
		||||
const onewire_addr_t uInletFlowTempSensorAddr = 0xe59cdef51e64ff28;
 | 
			
		||||
const onewire_addr_t uReturnFlowTempSensorAddr = 0xa7a8e1531f64ff28;
 | 
			
		||||
const onewire_addr_t uChamperTempSensorAddr = 0xd00000108cd01d28;
 | 
			
		||||
const onewire_addr_t uOutdoorTempSensorAddr = 0xd70000108a9b9128;
 | 
			
		||||
const onewire_addr_t uInletFlowTempSensorAddr = 0x410000108b8c0628;
 | 
			
		||||
const onewire_addr_t uReturnFlowTempSensorAddr = 0x90000108cc77c28;
 | 
			
		||||
 | 
			
		||||
onewire_addr_t uOneWireAddresses[MAX_DN18B20_SENSORS];
 | 
			
		||||
float fDS18B20Temps[MAX_DN18B20_SENSORS];
 | 
			
		||||
@ -31,7 +33,10 @@ static sMeasurement sInletFlowTemperature;
 | 
			
		||||
static sMeasurement sReturnFlowTemperature;
 | 
			
		||||
 | 
			
		||||
void taskInput(void *pvParameters);
 | 
			
		||||
void initMeasurement(sMeasurement *pMeasurement);
 | 
			
		||||
void updateAverage(sMeasurement *pMeasurement);
 | 
			
		||||
void updatePrediction(sMeasurement *pMeasurement);
 | 
			
		||||
float linearRegressionPredict(const float *samples, size_t count, size_t bufferIndex, float futureIndex);
 | 
			
		||||
 | 
			
		||||
void initInputs(void)
 | 
			
		||||
{
 | 
			
		||||
@ -53,6 +58,11 @@ void initInputs(void)
 | 
			
		||||
    }
 | 
			
		||||
    xSemaphoreGiveRecursive(xMutexAccessInputs);
 | 
			
		||||
 | 
			
		||||
    initMeasurement(&sChamperTemperature);
 | 
			
		||||
    initMeasurement(&sOutdoorTemperature);
 | 
			
		||||
    initMeasurement(&sInletFlowTemperature);
 | 
			
		||||
    initMeasurement(&sReturnFlowTemperature);
 | 
			
		||||
 | 
			
		||||
    BaseType_t taskCreated = xTaskCreate(
 | 
			
		||||
        taskInput,   // Function to implement the task
 | 
			
		||||
        "taskInput", // Task name
 | 
			
		||||
@ -72,50 +82,107 @@ void initInputs(void)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void updateAverage(sMeasurement *pMeasurement)
 | 
			
		||||
{ /* Average form the last 10sec */
 | 
			
		||||
    pMeasurement->average10s.samples[pMeasurement->average10s.bufferIndex] = pMeasurement->fCurrentValue;
 | 
			
		||||
    pMeasurement->average10s.bufferIndex = (pMeasurement->average10s.bufferIndex + 1) % AVG10_SAMPLE_SIZE;
 | 
			
		||||
void initMeasurement(sMeasurement *pMeasurement)
 | 
			
		||||
{
 | 
			
		||||
    if (!pMeasurement)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    if (pMeasurement->average10s.bufferCount < AVG10_SAMPLE_SIZE)
 | 
			
		||||
    pMeasurement->state = MEASUREMENT_FAULT;
 | 
			
		||||
    pMeasurement->fCurrentValue = INITIALISATION_VALUE;
 | 
			
		||||
    pMeasurement->fDampedValue = INITIALISATION_VALUE;
 | 
			
		||||
 | 
			
		||||
    pMeasurement->average10s.fValue = INITIALISATION_VALUE;
 | 
			
		||||
    pMeasurement->average10s.bufferCount = 0U;
 | 
			
		||||
    pMeasurement->average10s.bufferIndex = 0U;
 | 
			
		||||
    memset(pMeasurement->average10s.samples, 0U, AVG10S_SAMPLE_SIZE);
 | 
			
		||||
 | 
			
		||||
    pMeasurement->average60s.fValue = INITIALISATION_VALUE;
 | 
			
		||||
    pMeasurement->average60s.bufferCount = 0U;
 | 
			
		||||
    pMeasurement->average60s.bufferIndex = 0U;
 | 
			
		||||
    memset(pMeasurement->average60s.samples, 0U, AVG60S_SAMPLE_SIZE);
 | 
			
		||||
 | 
			
		||||
    pMeasurement->predict60s.fValue = INITIALISATION_VALUE;
 | 
			
		||||
    pMeasurement->predict60s.bufferCount = 0U;
 | 
			
		||||
    pMeasurement->predict60s.bufferIndex = 0U;
 | 
			
		||||
    memset(pMeasurement->predict60s.samples, 0U, PRED60S_SAMPLE_SIZE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void updateAverage(sMeasurement *pMeasurement)
 | 
			
		||||
{
 | 
			
		||||
    if (!pMeasurement)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    // Average form the last 10sec
 | 
			
		||||
    pMeasurement->average10s.samples[pMeasurement->average10s.bufferIndex] = pMeasurement->fCurrentValue;
 | 
			
		||||
    pMeasurement->average10s.bufferIndex = (pMeasurement->average10s.bufferIndex + 1) % AVG10S_SAMPLE_SIZE;
 | 
			
		||||
 | 
			
		||||
    if (pMeasurement->average10s.bufferCount < AVG10S_SAMPLE_SIZE)
 | 
			
		||||
    {
 | 
			
		||||
        pMeasurement->average10s.bufferCount++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (pMeasurement->average10s.bufferCount == 0U)
 | 
			
		||||
    {
 | 
			
		||||
        pMeasurement->average10s.fValue = pMeasurement->fCurrentValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pMeasurement->average10s.fValue = sum / pMeasurement->average10s.bufferCount;
 | 
			
		||||
 | 
			
		||||
    /* Average form the last 60sec */
 | 
			
		||||
    // Average form the last 60sec
 | 
			
		||||
    pMeasurement->average60s.samples[pMeasurement->average60s.bufferIndex] = pMeasurement->fCurrentValue;
 | 
			
		||||
    pMeasurement->average60s.bufferIndex = (pMeasurement->average60s.bufferIndex + 1) % AVG60_SAMPLE_SIZE;
 | 
			
		||||
    pMeasurement->average60s.bufferIndex = (pMeasurement->average60s.bufferIndex + 1) % AVG60S_SAMPLE_SIZE;
 | 
			
		||||
 | 
			
		||||
    if (pMeasurement->average60s.bufferCount < AVG60_SAMPLE_SIZE)
 | 
			
		||||
    if (pMeasurement->average60s.bufferCount < AVG60S_SAMPLE_SIZE)
 | 
			
		||||
    {
 | 
			
		||||
        pMeasurement->average60s.bufferCount++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (pMeasurement->average60s.bufferCount == 0U)
 | 
			
		||||
    {
 | 
			
		||||
        pMeasurement->average60s.fValue = pMeasurement->fCurrentValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sum = 0.0;
 | 
			
		||||
    for (int i = 0; i < pMeasurement->average60s.bufferCount; i++)
 | 
			
		||||
    for (int i = 0; i <= pMeasurement->average60s.bufferCount; i++)
 | 
			
		||||
    {
 | 
			
		||||
        sum += pMeasurement->average60s.samples[i];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pMeasurement->average60s.fValue = sum / pMeasurement->average60s.bufferCount;
 | 
			
		||||
 | 
			
		||||
    // Damped current value
 | 
			
		||||
    if (pMeasurement->fDampedValue == INITIALISATION_VALUE)
 | 
			
		||||
    {
 | 
			
		||||
        pMeasurement->fDampedValue = pMeasurement->fCurrentValue;
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        if (pMeasurement->fCurrentValue > pMeasurement->fDampedValue)
 | 
			
		||||
        {
 | 
			
		||||
            pMeasurement->fDampedValue = pMeasurement->fDampedValue + (DAMPING_FACTOR_WARMER * (pMeasurement->fCurrentValue - pMeasurement->fDampedValue));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (pMeasurement->fCurrentValue < pMeasurement->fDampedValue)
 | 
			
		||||
        {
 | 
			
		||||
            pMeasurement->fDampedValue = pMeasurement->fDampedValue - (DAMPING_FACTOR_COLDER * (pMeasurement->fDampedValue - pMeasurement->fCurrentValue));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void updatePrediction(sMeasurement *pMeasurement)
 | 
			
		||||
{
 | 
			
		||||
    if (!pMeasurement)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    // Update predict60s buffer
 | 
			
		||||
    sPredict *predict60s = &pMeasurement->predict60s;
 | 
			
		||||
    predict60s->samples[predict60s->bufferIndex] = pMeasurement->fCurrentValue;
 | 
			
		||||
    predict60s->bufferIndex = (predict60s->bufferIndex + 1) % PRED60S_SAMPLE_SIZE;
 | 
			
		||||
    if (predict60s->bufferCount < PRED60S_SAMPLE_SIZE)
 | 
			
		||||
        predict60s->bufferCount++;
 | 
			
		||||
 | 
			
		||||
    // Predict 60s future value using linear regression
 | 
			
		||||
    predict60s->fValue = linearRegressionPredict(
 | 
			
		||||
        predict60s->samples,
 | 
			
		||||
        predict60s->bufferCount,
 | 
			
		||||
        predict60s->bufferIndex,
 | 
			
		||||
        predict60s->bufferCount + 60.0f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void taskInput(void *pvParameters)
 | 
			
		||||
@ -163,7 +230,7 @@ void taskInput(void *pvParameters)
 | 
			
		||||
                    if (ds18x20_measure_and_read_multi(uDS18B20Pin, uOneWireAddresses, sSensorCount, fDS18B20Temps) != ESP_OK)
 | 
			
		||||
                    {
 | 
			
		||||
                        ESP_LOGE(TAG, "1-Wire devices read error");
 | 
			
		||||
                        vTaskDelay(PERIODIC_INTERVAL * 100U / portTICK_PERIOD_MS); //Wait 100ms if bus error occurred
 | 
			
		||||
                        vTaskDelay(PERIODIC_INTERVAL * 100U / portTICK_PERIOD_MS); // Wait 100ms if bus error occurred
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
@ -178,21 +245,25 @@ void taskInput(void *pvParameters)
 | 
			
		||||
                                sChamperTemperature.fCurrentValue = temp_c;
 | 
			
		||||
                                sChamperTemperature.state = MEASUREMENT_NO_ERROR;
 | 
			
		||||
                                updateAverage(&sChamperTemperature);
 | 
			
		||||
                                updatePrediction(&sChamperTemperature);
 | 
			
		||||
                                break;
 | 
			
		||||
                            case ((uint64_t)uOutdoorTempSensorAddr):
 | 
			
		||||
                                sOutdoorTemperature.fCurrentValue = temp_c;
 | 
			
		||||
                                sOutdoorTemperature.state = MEASUREMENT_NO_ERROR;
 | 
			
		||||
                                updateAverage(&sOutdoorTemperature);
 | 
			
		||||
                                updatePrediction(&sOutdoorTemperature);
 | 
			
		||||
                                break;
 | 
			
		||||
                            case ((uint64_t)uInletFlowTempSensorAddr):
 | 
			
		||||
                                sInletFlowTemperature.fCurrentValue = temp_c;
 | 
			
		||||
                                sInletFlowTemperature.state = MEASUREMENT_NO_ERROR;
 | 
			
		||||
                                updateAverage(&sInletFlowTemperature);
 | 
			
		||||
                                updatePrediction(&sInletFlowTemperature);
 | 
			
		||||
                                break;
 | 
			
		||||
                            case ((uint64_t)uReturnFlowTempSensorAddr):
 | 
			
		||||
                                sReturnFlowTemperature.fCurrentValue = temp_c;
 | 
			
		||||
                                sReturnFlowTemperature.state = MEASUREMENT_NO_ERROR;
 | 
			
		||||
                                updateAverage(&sReturnFlowTemperature);
 | 
			
		||||
                                updatePrediction(&sReturnFlowTemperature);
 | 
			
		||||
                                break;
 | 
			
		||||
                            default:
 | 
			
		||||
                                break;
 | 
			
		||||
@ -216,6 +287,39 @@ void taskInput(void *pvParameters)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float linearRegressionPredict(const float *samples, size_t count, size_t bufferIndex, float futureIndex)
 | 
			
		||||
{
 | 
			
		||||
    if (count == 0)
 | 
			
		||||
        return INITIALISATION_VALUE; // No prediction possible with no data
 | 
			
		||||
 | 
			
		||||
    float sumX = INITIALISATION_VALUE, sumY = INITIALISATION_VALUE, sumXY = INITIALISATION_VALUE, sumX2 = INITIALISATION_VALUE;
 | 
			
		||||
 | 
			
		||||
    for (size_t i = 0; i < count; i++)
 | 
			
		||||
    {
 | 
			
		||||
        // Calculate the circular buffer index for the current sample
 | 
			
		||||
        size_t circularIndex = (bufferIndex + i + 1) % count;
 | 
			
		||||
 | 
			
		||||
        float x = (float)i;               // Time index
 | 
			
		||||
        float y = samples[circularIndex]; // Sample value
 | 
			
		||||
 | 
			
		||||
        sumX += x;
 | 
			
		||||
        sumY += y;
 | 
			
		||||
        sumXY += x * y;
 | 
			
		||||
        sumX2 += x * x;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate slope (m) and intercept (b) of the line: y = mx + b
 | 
			
		||||
    float denominator = (count * sumX2 - sumX * sumX);
 | 
			
		||||
    if (fabs(denominator) < 1e-6)    // Avoid division by zero
 | 
			
		||||
        return samples[bufferIndex]; // Return the latest value as prediction
 | 
			
		||||
 | 
			
		||||
    float m = (count * sumXY - sumX * sumY) / denominator;
 | 
			
		||||
    float b = (sumY - m * sumX) / count;
 | 
			
		||||
 | 
			
		||||
    // Predict value at futureIndex
 | 
			
		||||
    return m * futureIndex + b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sMeasurement getChamberTemperature(void)
 | 
			
		||||
{
 | 
			
		||||
    sMeasurement ret;
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,13 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
 | 
			
		||||
#define AVG10_SAMPLE_SIZE 10U
 | 
			
		||||
#define AVG60_SAMPLE_SIZE 60U
 | 
			
		||||
#define INITIALISATION_VALUE 0.0f
 | 
			
		||||
#define AVG10S_SAMPLE_SIZE 10U
 | 
			
		||||
#define AVG60S_SAMPLE_SIZE 60U
 | 
			
		||||
#define AVG24H_SAMPLE_SIZE 24U
 | 
			
		||||
#define PRED60S_SAMPLE_SIZE 60U
 | 
			
		||||
#define DAMPING_FACTOR_WARMER 0.00001f // 0.001%
 | 
			
		||||
#define DAMPING_FACTOR_COLDER 0.00005f // 0.005%
 | 
			
		||||
 | 
			
		||||
typedef enum _BurnerErrorState
 | 
			
		||||
{
 | 
			
		||||
@ -19,16 +24,26 @@ typedef enum _MeasurementErrorState
 | 
			
		||||
typedef struct _Average
 | 
			
		||||
{
 | 
			
		||||
    float fValue;
 | 
			
		||||
    float samples[MAX(AVG10_SAMPLE_SIZE, AVG60_SAMPLE_SIZE)];
 | 
			
		||||
    float samples[MAX(AVG10S_SAMPLE_SIZE, MAX(AVG60S_SAMPLE_SIZE, AVG24H_SAMPLE_SIZE))];
 | 
			
		||||
    size_t bufferIndex;
 | 
			
		||||
    size_t bufferCount;
 | 
			
		||||
} sAverage;
 | 
			
		||||
 | 
			
		||||
typedef struct _Predict
 | 
			
		||||
{
 | 
			
		||||
    float fValue;
 | 
			
		||||
    float samples[PRED60S_SAMPLE_SIZE];
 | 
			
		||||
    size_t bufferIndex;
 | 
			
		||||
    size_t bufferCount;
 | 
			
		||||
} sPredict;
 | 
			
		||||
 | 
			
		||||
typedef struct _Measurement
 | 
			
		||||
{
 | 
			
		||||
    float fCurrentValue;
 | 
			
		||||
    float fDampedValue;
 | 
			
		||||
    sAverage average10s;
 | 
			
		||||
    sAverage average60s;
 | 
			
		||||
    sPredict predict60s;
 | 
			
		||||
    eMeasurementErrorState state;
 | 
			
		||||
} sMeasurement;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										125
									
								
								main/metrics.c
									
									
									
									
									
								
							
							
						
						
									
										125
									
								
								main/metrics.c
									
									
									
									
									
								
							@ -32,7 +32,7 @@ void initMetrics(void)
 | 
			
		||||
    BaseType_t taskCreated = xTaskCreate(
 | 
			
		||||
        taskMetrics,   // Function to implement the task
 | 
			
		||||
        "taskMetrics", // Task name
 | 
			
		||||
        16384,         // Stack size (in words, not bytes)
 | 
			
		||||
        32768,         // 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)
 | 
			
		||||
@ -56,13 +56,13 @@ void taskMetrics(void *pvParameters)
 | 
			
		||||
 | 
			
		||||
        u16MetricCounter = 0U;
 | 
			
		||||
 | 
			
		||||
        /*Burner Error State*/
 | 
			
		||||
        // Burner Error State
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "burner_fault_pending");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = INTEGER_U8;
 | 
			
		||||
        aMetrics[u16MetricCounter].u8MetricValue = getBurnerError();
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Circulation Pump State*/
 | 
			
		||||
        // Circulation Pump State
 | 
			
		||||
        if (getCirculationPumpState() == ENABLED)
 | 
			
		||||
        {
 | 
			
		||||
            strcpy(aMetrics[u16MetricCounter].caMetricName, "circulation_pump_enabled");
 | 
			
		||||
@ -78,7 +78,7 @@ void taskMetrics(void *pvParameters)
 | 
			
		||||
            u16MetricCounter++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /*Burner State*/
 | 
			
		||||
        // Burner State
 | 
			
		||||
        if (getBurnerState() == ENABLED)
 | 
			
		||||
        {
 | 
			
		||||
            strcpy(aMetrics[u16MetricCounter].caMetricName, "burner_enabled");
 | 
			
		||||
@ -94,7 +94,7 @@ void taskMetrics(void *pvParameters)
 | 
			
		||||
            u16MetricCounter++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /*Safety Contact State*/
 | 
			
		||||
        // Safety Contact State
 | 
			
		||||
        if (getSafetyControlState() == ENABLED)
 | 
			
		||||
        {
 | 
			
		||||
            strcpy(aMetrics[u16MetricCounter].caMetricName, "safety_contact_enabled");
 | 
			
		||||
@ -110,79 +110,127 @@ void taskMetrics(void *pvParameters)
 | 
			
		||||
            u16MetricCounter++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /*Chamber Temperature*/
 | 
			
		||||
        // Chamber Temperature
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "chamber_temperature");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getChamberTemperature().fCurrentValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Chamber Temperature Average 10s*/
 | 
			
		||||
        // Chamber Temperature Average 10s
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "chamber_temperature_avg10");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getChamberTemperature().average10s.fValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Chamber Temperature Average 60s*/
 | 
			
		||||
        // Chamber Temperature Average 60s
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "chamber_temperature_avg60");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getChamberTemperature().average60s.fValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Inlet Flow Temperature*/
 | 
			
		||||
        // Chamber Temperature Damped
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "chamber_temperature_damped");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getChamberTemperature().fDampedValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        // Chamber Temperature Predict 60s
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "chamber_temperature_pred60");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getChamberTemperature().predict60s.fValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        // Inlet Flow Temperature
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "inlet_flow_temperature");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getInletFlowTemperature().fCurrentValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Inlet Flow Temperature Average 10s*/
 | 
			
		||||
        // Inlet Flow Temperature Average 10s
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "inlet_flow_temperature_avg10");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getInletFlowTemperature().average10s.fValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Inlet Flow Temperature Average 60s*/
 | 
			
		||||
        // Inlet Flow Temperature Average 60s
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "inlet_flow_temperature_avg60");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getInletFlowTemperature().average60s.fValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Outdoor Temperature*/
 | 
			
		||||
        // Inlet Flow Temperature Damped
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "inlet_flow_temperature_damped");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getInletFlowTemperature().fDampedValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        // Inlet Flow Temperature Predict 60s
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "inlet_flow_temperature_pred60");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getInletFlowTemperature().predict60s.fValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        // Outdoor Temperature
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "outdoor_temperature");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getOutdoorTemperature().fCurrentValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Outdoor Temperature Average 10s*/
 | 
			
		||||
        // Outdoor Temperature Average 10s
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "outdoor_temperature_avg10");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getOutdoorTemperature().average10s.fValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Outdoor Temperature Average 60s*/
 | 
			
		||||
        // Outdoor Temperature Average 60s
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "outdoor_temperature_avg60");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getOutdoorTemperature().average60s.fValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Return Flow Temperature*/
 | 
			
		||||
        // Outdoor Temperature Average Damped
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "outdoor_temperature_damped");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getOutdoorTemperature().fDampedValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        // Outdoor Temperature Predict 60s
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "outdoor_temperature_pred60");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getOutdoorTemperature().predict60s.fValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        // Return Flow Temperature
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "return_flow_temperature");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature().fCurrentValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Return Flow Temperature Average 10s*/
 | 
			
		||||
        // Return Flow Temperature Average 10s
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "return_flow_temperature_avg10");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature().average10s.fValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Return Flow Temperature Average 60s*/
 | 
			
		||||
        // Return Flow Temperature Average 60s
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "return_flow_temperature_avg60");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature().average60s.fValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Sensor State*/
 | 
			
		||||
        // Return Flow Temperature Damped
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "return_flow_temperature_damped");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature().fDampedValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        // Return Flow Temperature Predict 60s
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "return_flow_temperature_pred60");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature().predict60s.fValue;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        // Sensor State
 | 
			
		||||
        sSensorSanityCheck aChecks[NUMBER_OF_SENSOR_SANITY_CHECKS];
 | 
			
		||||
        getSensorSanityStates(aChecks);
 | 
			
		||||
        for (size_t i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++)
 | 
			
		||||
@ -194,25 +242,50 @@ void taskMetrics(void *pvParameters)
 | 
			
		||||
            u16MetricCounter++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /*Safety State*/
 | 
			
		||||
        // Safety State
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "safety_state");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = INTEGER_U8;
 | 
			
		||||
        aMetrics[u16MetricCounter].u8MetricValue = getSafetyState();
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Control State*/
 | 
			
		||||
        // Control State
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "control_state");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = INTEGER_U8;
 | 
			
		||||
        aMetrics[u16MetricCounter].u8MetricValue = getControlState();
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*SNTP State*/
 | 
			
		||||
        // Control Current Weekday
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "control_current_weekday");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = INTEGER_U8;
 | 
			
		||||
        aMetrics[u16MetricCounter].u8MetricValue = getControlCurrentWeekday();
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        // Control Current Entry Time
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "control_current_entry_time");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = INTEGER_64;
 | 
			
		||||
        int64_t i64SecondsSinceMidnight = (getControlCurrentTemperatureEntry().timestamp.hour * 60U * 60U) + (getControlCurrentTemperatureEntry().timestamp.minute * 60U);
 | 
			
		||||
        aMetrics[u16MetricCounter].i64MetricValue = i64SecondsSinceMidnight;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        // Control Current Entry Chamber Temperature
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "control_current_entry_chamber_temperature");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getControlCurrentTemperatureEntry().fChamberTemperature;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        // Control Current Entry Return Flow Temperature
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "control_current_entry_return_flow_temperature");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = FLOAT;
 | 
			
		||||
        aMetrics[u16MetricCounter].fMetricValue = getControlCurrentTemperatureEntry().fReturnFlowTemperature;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        // SNTP State
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "sntp_state");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = INTEGER_U8;
 | 
			
		||||
        aMetrics[u16MetricCounter].u8MetricValue = getSntpState();
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*System Time*/
 | 
			
		||||
        // System Time
 | 
			
		||||
        time_t now;
 | 
			
		||||
        time(&now);
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "system_unixtime");
 | 
			
		||||
@ -220,13 +293,13 @@ void taskMetrics(void *pvParameters)
 | 
			
		||||
        aMetrics[u16MetricCounter].i64MetricValue = now;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Uptime*/
 | 
			
		||||
        // Uptime
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "uptime_seconds");
 | 
			
		||||
        aMetrics[u16MetricCounter].type = INTEGER_64;
 | 
			
		||||
        aMetrics[u16MetricCounter].i64MetricValue = (esp_timer_get_time() / 1000000U);
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        /*Wifi RSSI*/
 | 
			
		||||
        // Wifi RSSI
 | 
			
		||||
        wifi_ap_record_t ap;
 | 
			
		||||
        esp_wifi_sta_get_ap_info(&ap);
 | 
			
		||||
        strcpy(aMetrics[u16MetricCounter].caMetricName, "wifi_rssi");
 | 
			
		||||
@ -234,6 +307,7 @@ void taskMetrics(void *pvParameters)
 | 
			
		||||
        aMetrics[u16MetricCounter].i64MetricValue = ap.rssi;
 | 
			
		||||
        u16MetricCounter++;
 | 
			
		||||
 | 
			
		||||
        ESP_ERROR_CHECK(u16MetricCounter > METRIC_MAX_COUNT);
 | 
			
		||||
        vSetMetrics(aMetrics, u16MetricCounter);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -243,7 +317,7 @@ void vSetMetrics(sMetric *paMetrics, uint16_t u16Size)
 | 
			
		||||
 | 
			
		||||
    if (xSemaphoreTakeRecursive(xMutexAccessMetricResponse, pdMS_TO_TICKS(5000)) == pdTRUE)
 | 
			
		||||
    {
 | 
			
		||||
        memset(caHtmlResponse, 0, strlen(caHtmlResponse));
 | 
			
		||||
        memset(caHtmlResponse, 0U, strlen(caHtmlResponse));
 | 
			
		||||
        for (uint16_t u16Index = 0U; u16Index < u16Size; u16Index++)
 | 
			
		||||
        {
 | 
			
		||||
            char caValueBuffer[64];
 | 
			
		||||
@ -263,6 +337,7 @@ void vSetMetrics(sMetric *paMetrics, uint16_t u16Size)
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // printf("%s\n", paMetrics[u16Index].caMetricName);
 | 
			
		||||
            // printf("%s\n", caValueBuffer);
 | 
			
		||||
            strcat(caHtmlResponse, paMetrics[u16Index].caMetricName);
 | 
			
		||||
            strcat(caHtmlResponse, caValueBuffer);
 | 
			
		||||
 | 
			
		||||
@ -2,9 +2,9 @@
 | 
			
		||||
 | 
			
		||||
#include <esp_http_server.h>
 | 
			
		||||
 | 
			
		||||
#define HTML_RESPONSE_SIZE 1024U
 | 
			
		||||
#define METRIC_NAME_MAX_SIZE 256U
 | 
			
		||||
#define METRIC_MAX_COUNT 64U
 | 
			
		||||
#define HTML_RESPONSE_SIZE 4096U
 | 
			
		||||
#define METRIC_NAME_MAX_SIZE 64U
 | 
			
		||||
#define METRIC_MAX_COUNT 38U
 | 
			
		||||
 | 
			
		||||
typedef enum _MetricValueType
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
@ -155,5 +155,16 @@ void getSensorSanityStates(sSensorSanityCheck *pSensorSanityChecks)
 | 
			
		||||
 | 
			
		||||
eSafetyState getSafetyState(void)
 | 
			
		||||
{
 | 
			
		||||
    return sSafetyState;
 | 
			
		||||
    eSafetyState state = SAFETY_NO_ERROR;
 | 
			
		||||
    if (xSemaphoreTakeRecursive(xMutexAccessSafety, pdMS_TO_TICKS(5000)) == pdTRUE)
 | 
			
		||||
    {
 | 
			
		||||
        state = sSafetyState;
 | 
			
		||||
        xSemaphoreGiveRecursive(xMutexAccessSafety);
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        state = SAFETY_INTERNAL_ERROR;
 | 
			
		||||
        ESP_LOGE(TAG, "Unable to take mutex: getSafetyState()");
 | 
			
		||||
    }
 | 
			
		||||
    return state;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user