diff --git a/main/control.c b/main/control.c index 76ea7e7..eedc877 100644 --- a/main/control.c +++ b/main/control.c @@ -35,7 +35,7 @@ void initControl(void) BaseType_t taskCreated = xTaskCreate( taskControl, // Function to implement the task "taskControl", // Task name - 4096, // Stack size (in words, not bytes) + 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) diff --git a/main/inputs.c b/main/inputs.c index c418164..cb90d2a 100644 --- a/main/inputs.c +++ b/main/inputs.c @@ -1,6 +1,8 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" +#include +#include #include "esp_log.h" #include @@ -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, 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,8 +82,36 @@ void initInputs(void) } } +void initMeasurement(sMeasurement *pMeasurement) +{ + if (!pMeasurement) + return; + + pMeasurement->state = MEASUREMENT_FAULT; + pMeasurement->fCurrentValue = 0.0f; + + pMeasurement->average10s.fValue = 0.0f; + pMeasurement->average10s.bufferCount = 0U; + pMeasurement->average10s.bufferIndex = 0U; + memset(pMeasurement->average10s.samples, 0U, AVG10_SAMPLE_SIZE); + + pMeasurement->average60s.fValue = 0.0f; + pMeasurement->average60s.bufferCount = 0U; + pMeasurement->average60s.bufferIndex = 0U; + memset(pMeasurement->average60s.samples, 0U, AVG60_SAMPLE_SIZE); + + pMeasurement->predict60s.fValue = 0.0f; + pMeasurement->predict60s.bufferCount = 0U; + pMeasurement->predict60s.bufferIndex = 0U; + memset(pMeasurement->predict60s.samples, 0U, PRED60_SAMPLE_SIZE); +} + void updateAverage(sMeasurement *pMeasurement) -{ /* Average form the last 10sec */ +{ + if (!pMeasurement) + return; + + // Average form the last 10sec pMeasurement->average10s.samples[pMeasurement->average10s.bufferIndex] = pMeasurement->fCurrentValue; pMeasurement->average10s.bufferIndex = (pMeasurement->average10s.bufferIndex + 1) % AVG10_SAMPLE_SIZE; @@ -82,20 +120,15 @@ void updateAverage(sMeasurement *pMeasurement) 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; @@ -104,13 +137,8 @@ void updateAverage(sMeasurement *pMeasurement) 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]; } @@ -118,6 +146,25 @@ void updateAverage(sMeasurement *pMeasurement) pMeasurement->average60s.fValue = sum / pMeasurement->average60s.bufferCount; } +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) % PRED60_SAMPLE_SIZE; + if (predict60s->bufferCount < PRED60_SAMPLE_SIZE) + predict60s->bufferCount++; + + // Predict 60s future value using linear regression + predict60s->fValue = linearRegressionPredict( + predict60s->samples, + predict60s->bufferCount, + predict60s->bufferCount + 60.0f); +} + void taskInput(void *pvParameters) { while (1) @@ -163,7 +210,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 +225,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 +267,36 @@ void taskInput(void *pvParameters) } } +float linearRegressionPredict(const float *samples, size_t count, float futureIndex) +{ + if (count == 0) + return 0.0f; // No prediction possible with no data + + float sumX = 0.0f, sumY = 0.0f, sumXY = 0.0f, sumX2 = 0.0f; + + for (size_t i = 0; i < count; i++) + { + float x = (float)i; // Time index + float y = samples[i]; // 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[count - 1]; // Return last 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; diff --git a/main/inputs.h b/main/inputs.h index 2630507..9af37f5 100644 --- a/main/inputs.h +++ b/main/inputs.h @@ -3,6 +3,7 @@ #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define AVG10_SAMPLE_SIZE 10U #define AVG60_SAMPLE_SIZE 60U +#define PRED60_SAMPLE_SIZE 60U typedef enum _BurnerErrorState { @@ -24,11 +25,20 @@ typedef struct _Average size_t bufferCount; } sAverage; +typedef struct _Predict +{ + float fValue; + float samples[PRED60_SAMPLE_SIZE]; + size_t bufferIndex; + size_t bufferCount; +} sPredict; + typedef struct _Measurement { float fCurrentValue; sAverage average10s; sAverage average60s; + sPredict predict60s; eMeasurementErrorState state; } sMeasurement; diff --git a/main/metrics.c b/main/metrics.c index 278d2c2..c7aaf6f 100644 --- a/main/metrics.c +++ b/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,103 @@ 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 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 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 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 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 +218,25 @@ 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*/ + // 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 +244,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"); @@ -243,7 +267,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 +287,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); diff --git a/main/metrics.h b/main/metrics.h index b02342a..7f48067 100644 --- a/main/metrics.h +++ b/main/metrics.h @@ -2,9 +2,9 @@ #include -#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 32U typedef enum _MetricValueType {