From 1d4e272d800fa1b9309d8576a4d89d4c3db7bc9444a4e04f692270328e4b3a1e Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 10 Jan 2026 13:32:49 +0100 Subject: [PATCH] error handling and cleanup --- main/control.c | 129 +++++++++++++++++++++--------------------- main/control.h | 107 ++++++++++++++++++++++++++++------- main/inputs.c | 151 +++++++++++++++++++++++++++++++------------------ main/inputs.h | 112 +++++++++++++++++++++++++++++------- main/main.c | 109 +++++++++++++++++++++++++++++++---- main/metrics.c | 78 ++++++++++++++++--------- main/metrics.h | 57 +++++++++++++++---- main/outputs.c | 72 +++++++++++++---------- main/outputs.h | 63 +++++++++++++++++++-- main/safety.c | 75 ++++++++++++++++-------- main/safety.h | 97 +++++++++++++++++++++++++------ main/sntp.c | 26 +++++++-- main/sntp.h | 36 ++++++++++-- main/wifi.c | 43 ++++++++++---- main/wifi.h | 21 ++++++- 15 files changed, 867 insertions(+), 309 deletions(-) diff --git a/main/control.c b/main/control.c index 8061cc8..7a80cf6 100644 --- a/main/control.c +++ b/main/control.c @@ -1,3 +1,8 @@ +/** + * @file control.c + * @brief Implementation of heating control module. + */ + #include "control.h" #include "inputs.h" #include "outputs.h" @@ -9,11 +14,16 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#define PERIODIC_INTERVAL 1U // Run control loop every 1 second +#include + +/** @brief Task interval in seconds. */ +#define PERIODIC_INTERVAL 1U + +static const char *TAG = "control"; -static const char *TAG = "smart-oil-heater-control-system-control"; static eControlState gControlState = CONTROL_STARTING; -// Control table for daily schedules + +/** @brief Weekly schedule table (from Kconfig). */ static const sControlDay gControlTable[] = { {MONDAY, 2U, @@ -72,45 +82,49 @@ static const sControlDay gControlTable[] = { RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMBER_TEMPERATURE_TARGET}}}, }; + static sControlTemperatureEntry gCurrentControlEntry = gControlTable[0].aTemperatureEntries[0]; static SemaphoreHandle_t xMutexAccessControl = NULL; -// Function prototypes -void taskControl(void *pvParameters); -void findControlCurrentTemperatureEntry(void); -void setControlState(eControlState state); +/* Private function prototypes */ +static void taskControl(void *pvParameters); +static void findControlCurrentTemperatureEntry(void); +static void setControlState(eControlState state); -void initControl(void) +esp_err_t initControl(void) { - xMutexAccessControl = xSemaphoreCreateRecursiveMutex(); if (xMutexAccessControl == NULL) { - ESP_LOGE(TAG, "Unable to create mutex"); + ESP_LOGE(TAG, "Failed to create mutex"); + return ESP_FAIL; } xSemaphoreGiveRecursive(xMutexAccessControl); - BaseType_t taskCreated = - xTaskCreate(taskControl, // Function to implement the task - "taskControl", // Task name - 8192, // Stack size (in words, not bytes) - NULL, // Parameters to the task function (none in this case) - 5, // Task priority (higher number = higher priority) - NULL // Task handle (optional) - ); + BaseType_t taskCreated = xTaskCreate( + taskControl, + "taskControl", + 8192, + NULL, + 5, + NULL); - if (taskCreated == pdPASS) - { - ESP_LOGI(TAG, "Task created successfully!"); - } - else + if (taskCreated != pdPASS) { ESP_LOGE(TAG, "Failed to create task"); + return ESP_FAIL; } + + ESP_LOGI(TAG, "Initialized successfully"); + return ESP_OK; } -void taskControl(void *pvParameters) +/** + * @brief Main control task. + * @param pvParameters Task parameters (unused). + */ +static void taskControl(void *pvParameters) { bool bHeatingInAction = false; bool bSummerMode = false; @@ -121,7 +135,7 @@ void taskControl(void *pvParameters) { vTaskDelay(PERIODIC_INTERVAL * 1000U / portTICK_PERIOD_MS); - // Check for safety faults + /* Check for safety faults */ if (getSafetyState() != SAFETY_NO_ERROR) { ESP_LOGW(TAG, "Control not possible due to safety fault!"); @@ -136,7 +150,7 @@ void taskControl(void *pvParameters) continue; } - // Check for SNTP faults + /* Check for SNTP faults */ if (getSntpState() != SYNC_SUCCESSFUL) { ESP_LOGW(TAG, "Control not possible due to SNTP fault!"); @@ -153,35 +167,30 @@ void taskControl(void *pvParameters) findControlCurrentTemperatureEntry(); - if (getOutdoorTemperature().fDampedValue >= - SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH) + /* Summer mode hysteresis */ + if (getOutdoorTemperature().fDampedValue >= SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH) { bSummerMode = true; } - else if (getOutdoorTemperature().fDampedValue <= - SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW) + else if (getOutdoorTemperature().fDampedValue <= SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW) { bSummerMode = false; } - // Enable burner if outdoor temperature is low and return flow temperature - // is cooled down + /* Enable burner if needed */ if (!bHeatingInAction && (burnerState != BURNER_FAULT)) { if (bSummerMode) { - // ESP_LOGI(TAG, "Outdoor temperature too warm: Disabling heating"); setBurnerState(DISABLED); setSafetyControlState(DISABLED); setControlState(CONTROL_OUTDOOR_TOO_WARM); } else if ((getReturnFlowTemperature().average60s.fValue <= getControlCurrentTemperatureEntry().fReturnFlowTemperature) && - (getChamberTemperature().fCurrentValue <= - CHAMBER_TEMPERATURE_THRESHOLD)) + (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"); burnerState = BURNER_UNKNOWN; bHeatingInAction = true; setBurnerState(ENABLED); @@ -191,12 +200,11 @@ void taskControl(void *pvParameters) } else { - // ESP_LOGI(TAG, "Return flow temperature too warm: Disabling heating"); setControlState(CONTROL_RETURN_FLOW_TOO_WARM); } } - // Disable burner if target temperature is reached or a fault occurred + /* Disable burner if target reached or fault */ if (bHeatingInAction) { if ((getChamberTemperature().fCurrentValue >= @@ -233,9 +241,8 @@ void taskControl(void *pvParameters) } } - // Manage circulation pump - if (getChamberTemperature().fCurrentValue <= - CIRCULATION_PUMP_TEMPERATURE_THRESHOLD) + /* Manage circulation pump */ + if (getChamberTemperature().fCurrentValue <= CIRCULATION_PUMP_TEMPERATURE_THRESHOLD) { // ESP_LOGI(TAG, "Burner cooled down: Disabling circulation pump"); setCirculationPumpState(DISABLED); @@ -248,9 +255,12 @@ void taskControl(void *pvParameters) } // End of while(1) } -void setControlState(eControlState state) +/** + * @brief Set the control state with mutex protection. + * @param state New control state. + */ +static void setControlState(eControlState state) { - if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE) { gControlState = state; @@ -264,7 +274,6 @@ void setControlState(eControlState state) eControlState getControlState(void) { - eControlState ret = CONTROL_FAULT_SAFETY; if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE) @@ -282,7 +291,6 @@ eControlState getControlState(void) eControlWeekday getControlCurrentWeekday(void) { - // Get current time time_t now; struct tm timeinfo; time(&now); @@ -293,24 +301,12 @@ eControlWeekday getControlCurrentWeekday(void) } /** - * @brief Finds the active temperature control entry for the current time. - * - * Searches through the weekly schedule to find the most recent entry - * that should be active at the current date/time. Falls back to the - * last entry in the week if no suitable entry is found. + * @brief Find the currently active temperature entry based on time. */ -/** - * @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) +static void findControlCurrentTemperatureEntry(void) { eControlWeekday currentDay = getControlCurrentWeekday(); - // Get current time time_t now; struct tm timeinfo; time(&now); @@ -361,14 +357,15 @@ void findControlCurrentTemperatureEntry(void) const sControlDay *sunday = &gControlTable[6]; gCurrentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1]; } + xSemaphoreGiveRecursive(xMutexAccessControl); /* ESP_LOGI(TAG, "Active entry found - Time: %02d:%02d, " - "Return Temp: %lf, Chamber Temp: %lf", - gCurrentControlEntry.timestamp.hour, - gCurrentControlEntry.timestamp.minute, - gCurrentControlEntry.fReturnFlowTemperature, - gCurrentControlEntry.fChamberTemperature); - */ + "Return Temp: %lf, Chamber Temp: %lf", + gCurrentControlEntry.timestamp.hour, + gCurrentControlEntry.timestamp.minute, + gCurrentControlEntry.fReturnFlowTemperature, + gCurrentControlEntry.fChamberTemperature); + */ return; } } diff --git a/main/control.h b/main/control.h index 0096038..4507f95 100644 --- a/main/control.h +++ b/main/control.h @@ -1,38 +1,74 @@ +/** + * @file control.h + * @brief Heating control logic with schedule-based temperature management. + * + * This module implements the main heating control loop. It manages + * burner operation based on time schedules, temperature targets, + * and summer mode detection. + */ + #pragma once #include "sdkconfig.h" +#include "esp_err.h" -#include +#include +#include +/** @brief Maximum number of temperature entries per day. */ #define MAX_TEMPERATURE_ENTRIES_PER_DAY 24U +/** @brief Return flow target temperature for day mode (°C). */ #define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY (CONFIG_TEMP_RETURN_FLOW_LOWER_LIMIT_DAY / 10.0f) + +/** @brief Return flow target temperature for night mode (°C). */ #define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT (CONFIG_TEMP_RETURN_FLOW_LOWER_LIMIT_NIGHT / 10.0f) + +/** @brief Chamber target temperature (°C). */ #define CHAMBER_TEMPERATURE_TARGET (CONFIG_TEMP_CHAMBER_TARGET / 10.0f) + +/** @brief Chamber temperature threshold to enable burner (°C). */ #define CHAMBER_TEMPERATURE_THRESHOLD (CONFIG_TEMP_CHAMBER_THRESHOLD / 10.0f) + +/** @brief Outdoor temperature to activate summer mode (°C). */ #define SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH (CONFIG_TEMP_SUMMER_MODE_HIGH / 10.0f) + +/** @brief Outdoor temperature to deactivate summer mode (°C). */ #define SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW (CONFIG_TEMP_SUMMER_MODE_LOW / 10.0f) + +/** @brief Chamber temperature threshold for circulation pump (°C). */ #define CIRCULATION_PUMP_TEMPERATURE_THRESHOLD (CONFIG_TEMP_CIRCULATION_PUMP_THRESHOLD / 10.0f) + +/** @brief Time to wait before checking burner fault (seconds). */ #define BURNER_FAULT_DETECTION_THRESHOLD CONFIG_BURNER_FAULT_DETECTION_SECONDS +/** + * @brief Control state enumeration. + */ typedef enum _ControlState { - CONTROL_STARTING, - CONTROL_HEATING, - CONTROL_OUTDOOR_TOO_WARM, - CONTROL_RETURN_FLOW_TOO_WARM, - CONTROL_FAULT_BURNER, - CONTROL_FAULT_SAFETY, - CONTROL_FAULT_SNTP, + CONTROL_STARTING, /**< System starting up. */ + CONTROL_HEATING, /**< Burner running. */ + CONTROL_OUTDOOR_TOO_WARM, /**< Summer mode active. */ + CONTROL_RETURN_FLOW_TOO_WARM, /**< Target temperature reached. */ + CONTROL_FAULT_BURNER, /**< Burner fault detected. */ + CONTROL_FAULT_SAFETY, /**< Safety fault detected. */ + CONTROL_FAULT_SNTP, /**< SNTP sync failed. */ } eControlState; +/** + * @brief Burner operational state enumeration. + */ 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 + BURNER_UNKNOWN, /**< Burner state unknown after enable. */ + BURNER_FIRED, /**< Burner fired successfully. */ + BURNER_FAULT /**< Burner failed to fire. */ } eBurnerState; +/** + * @brief Weekday enumeration (Monday = 0). + */ typedef enum _ControlWeekday { MONDAY, @@ -44,27 +80,58 @@ typedef enum _ControlWeekday SUNDAY, } eControlWeekday; +/** + * @brief Time of day structure. + */ typedef struct _ControlTimestamp { - uint8_t hour; - uint8_t minute; + uint8_t hour; /**< Hour (0-23). */ + uint8_t minute; /**< Minute (0-59). */ } sControlTimestamp; +/** + * @brief Temperature schedule entry. + */ typedef struct _ControlTemperatureEntry { - sControlTimestamp timestamp; - float fReturnFlowTemperature; - float fChamberTemperature; + sControlTimestamp timestamp; /**< Time when entry becomes active. */ + float fReturnFlowTemperature; /**< Target return flow temperature. */ + float fChamberTemperature; /**< Target chamber temperature. */ } sControlTemperatureEntry; +/** + * @brief Daily schedule structure. + */ typedef struct _ControlDay { - eControlWeekday day; - size_t entryCount; // number of entries for each day - sControlTemperatureEntry aTemperatureEntries[MAX_TEMPERATURE_ENTRIES_PER_DAY]; + eControlWeekday day; /**< Day of week. */ + size_t entryCount; /**< Number of entries for this day. */ + sControlTemperatureEntry aTemperatureEntries[MAX_TEMPERATURE_ENTRIES_PER_DAY]; /**< Schedule entries. */ } sControlDay; -void initControl(void); +/** + * @brief Initialize the control module. + * + * Creates the control task that manages heating operation. + * + * @return ESP_OK on success, ESP_FAIL on error. + */ +esp_err_t initControl(void); + +/** + * @brief Get the current control state. + * @return eControlState indicating current operation mode. + */ eControlState getControlState(void); + +/** + * @brief Get the current weekday. + * @return eControlWeekday (Monday = 0, Sunday = 6). + */ eControlWeekday getControlCurrentWeekday(void); + +/** + * @brief Get the currently active temperature entry. + * @return sControlTemperatureEntry with current targets. + */ sControlTemperatureEntry getControlCurrentTemperatureEntry(void); diff --git a/main/inputs.c b/main/inputs.c index 56c04b2..9289e9c 100644 --- a/main/inputs.c +++ b/main/inputs.c @@ -1,3 +1,8 @@ +/** + * @file inputs.c + * @brief Implementation of input handling module. + */ + #include "inputs.h" #include "freertos/FreeRTOS.h" @@ -9,22 +14,38 @@ #include #include +/** @brief Maximum number of DS18B20 sensors supported. */ #define MAX_DN18B20_SENSORS 4U -#define ONE_WIRE_LOOPS 4U // try to read the 1-Wire sensors that often -#define PERIODIC_INTERVAL 1U // read and compute the inputs every 1sec -static const char *TAG = "smart-oil-heater-control-system-inputs"; -const uint8_t uBurnerFaultPin = CONFIG_GPIO_BURNER_FAULT; -const uint8_t uDS18B20Pin = CONFIG_GPIO_DS18B20_ONEWIRE; +/** @brief Number of retry attempts for 1-Wire read. */ +#define ONE_WIRE_LOOPS 4U -const onewire_addr_t uChamperTempSensorAddr = CONFIG_ONEWIRE_ADDR_CHAMBER_TEMP; -const onewire_addr_t uOutdoorTempSensorAddr = CONFIG_ONEWIRE_ADDR_OUTDOOR_TEMP; -const onewire_addr_t uInletFlowTempSensorAddr = CONFIG_ONEWIRE_ADDR_INLET_FLOW_TEMP; -const onewire_addr_t uReturnFlowTempSensorAddr = CONFIG_ONEWIRE_ADDR_RETURN_FLOW_TEMP; +/** @brief Task interval in seconds. */ +#define PERIODIC_INTERVAL 1U -onewire_addr_t uOneWireAddresses[MAX_DN18B20_SENSORS]; -float fDS18B20Temps[MAX_DN18B20_SENSORS]; -size_t sSensorCount = 0U; +static const char *TAG = "inputs"; + +/** @brief Burner fault GPIO pin (from Kconfig). */ +static const uint8_t uBurnerFaultPin = CONFIG_GPIO_BURNER_FAULT; + +/** @brief DS18B20 1-Wire GPIO pin (from Kconfig). */ +static const uint8_t uDS18B20Pin = CONFIG_GPIO_DS18B20_ONEWIRE; + +/** @brief Chamber temperature sensor address (from Kconfig). */ +static const onewire_addr_t uChamperTempSensorAddr = CONFIG_ONEWIRE_ADDR_CHAMBER_TEMP; + +/** @brief Outdoor temperature sensor address (from Kconfig). */ +static const onewire_addr_t uOutdoorTempSensorAddr = CONFIG_ONEWIRE_ADDR_OUTDOOR_TEMP; + +/** @brief Inlet flow temperature sensor address (from Kconfig). */ +static const onewire_addr_t uInletFlowTempSensorAddr = CONFIG_ONEWIRE_ADDR_INLET_FLOW_TEMP; + +/** @brief Return flow temperature sensor address (from Kconfig). */ +static const onewire_addr_t uReturnFlowTempSensorAddr = CONFIG_ONEWIRE_ADDR_RETURN_FLOW_TEMP; + +static onewire_addr_t uOneWireAddresses[MAX_DN18B20_SENSORS]; +static float fDS18B20Temps[MAX_DN18B20_SENSORS]; +static size_t sSensorCount = 0U; static SemaphoreHandle_t xMutexAccessInputs = NULL; static eBurnerErrorState sBurnerErrorState; @@ -33,34 +54,34 @@ static sMeasurement sOutdoorTemperature; 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); +/* Private function prototypes */ +static void taskInput(void *pvParameters); +static void initMeasurement(sMeasurement *pMeasurement); +static void updateAverage(sMeasurement *pMeasurement); +static void updatePrediction(sMeasurement *pMeasurement); +static float linearRegressionPredict(const float *samples, size_t count, size_t bufferIndex, float futureIndex); -void initInputs(void) +esp_err_t initInputs(void) { - gpio_config_t ioConfBurnerFault = { - .pin_bit_mask = (1ULL << uBurnerFaultPin), // Pin mask - .mode = GPIO_MODE_INPUT, // Set as inout - .pull_up_en = GPIO_PULLUP_ENABLE, // Enable pull-up - .pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down - .intr_type = GPIO_INTR_DISABLE // Disable interrupts - }; + .pin_bit_mask = (1ULL << uBurnerFaultPin), + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE}; esp_err_t ret = gpio_config(&ioConfBurnerFault); if (ret != ESP_OK) { ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret)); - return; + return ESP_FAIL; } xMutexAccessInputs = xSemaphoreCreateRecursiveMutex(); if (xMutexAccessInputs == NULL) { - ESP_LOGE(TAG, "Unable to create mutex"); + ESP_LOGE(TAG, "Failed to create mutex"); + return ESP_FAIL; } xSemaphoreGiveRecursive(xMutexAccessInputs); @@ -70,25 +91,28 @@ void initInputs(void) initMeasurement(&sReturnFlowTemperature); BaseType_t taskCreated = xTaskCreate( - taskInput, // Function to implement the task - "taskInput", // 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) - ); + taskInput, + "taskInput", + 4096, + NULL, + 5, + NULL); - if (taskCreated == pdPASS) - { - ESP_LOGI(TAG, "Task created successfully!"); - } - else + if (taskCreated != pdPASS) { ESP_LOGE(TAG, "Failed to create task"); + return ESP_FAIL; } + + ESP_LOGI(TAG, "Initialized successfully"); + return ESP_OK; } -void initMeasurement(sMeasurement *pMeasurement) +/** + * @brief Initialize a measurement structure to default values. + * @param pMeasurement Pointer to measurement structure. + */ +static void initMeasurement(sMeasurement *pMeasurement) { if (!pMeasurement) return; @@ -113,12 +137,16 @@ void initMeasurement(sMeasurement *pMeasurement) memset(pMeasurement->predict60s.samples, 0U, sizeof(float) * PRED60S_SAMPLE_SIZE); } -void updateAverage(sMeasurement *pMeasurement) +/** + * @brief Update average values and damped value for a measurement. + * @param pMeasurement Pointer to measurement structure. + */ +static void updateAverage(sMeasurement *pMeasurement) { if (!pMeasurement) return; - // Average form the last 10sec + /* 10-second average */ pMeasurement->average10s.samples[pMeasurement->average10s.bufferIndex] = pMeasurement->fCurrentValue; pMeasurement->average10s.bufferIndex = (pMeasurement->average10s.bufferIndex + 1) % AVG10S_SAMPLE_SIZE; @@ -142,7 +170,7 @@ void updateAverage(sMeasurement *pMeasurement) pMeasurement->average10s.fValue = sum / pMeasurement->average10s.bufferCount; } - // Average form the last 60sec + /* 60-second average */ pMeasurement->average60s.samples[pMeasurement->average60s.bufferIndex] = pMeasurement->fCurrentValue; pMeasurement->average60s.bufferIndex = (pMeasurement->average60s.bufferIndex + 1) % AVG60S_SAMPLE_SIZE; @@ -166,7 +194,7 @@ void updateAverage(sMeasurement *pMeasurement) pMeasurement->average60s.fValue = sum / pMeasurement->average60s.bufferCount; } - // Damped current value + /* Damped current value */ if (pMeasurement->fDampedValue == INITIALISATION_VALUE) { pMeasurement->fDampedValue = pMeasurement->fCurrentValue; @@ -185,7 +213,11 @@ void updateAverage(sMeasurement *pMeasurement) } } -void updatePrediction(sMeasurement *pMeasurement) +/** + * @brief Update 60-second prediction using linear regression. + * @param pMeasurement Pointer to measurement structure. + */ +static void updatePrediction(sMeasurement *pMeasurement) { if (!pMeasurement) return; @@ -205,7 +237,11 @@ void updatePrediction(sMeasurement *pMeasurement) predict60s->bufferCount + 60.0f); } -void taskInput(void *pvParameters) +/** + * @brief Input task - reads sensors periodically. + * @param pvParameters Task parameters (unused). + */ +static void taskInput(void *pvParameters) { while (1) { @@ -307,20 +343,27 @@ void taskInput(void *pvParameters) } } -float linearRegressionPredict(const float *samples, size_t count, size_t bufferIndex, float futureIndex) +/** + * @brief Predict future value using linear regression. + * @param samples Sample buffer. + * @param count Number of valid samples. + * @param bufferIndex Current buffer write index. + * @param futureIndex Future time index to predict. + * @return Predicted value. + */ +static 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 + return INITIALISATION_VALUE; 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 + float x = (float)i; + float y = samples[circularIndex]; sumX += x; sumY += y; @@ -328,15 +371,13 @@ float linearRegressionPredict(const float *samples, size_t count, size_t bufferI 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 + if (fabs(denominator) < 1e-6) + return samples[bufferIndex]; float m = (count * sumXY - sumX * sumY) / denominator; float b = (sumY - m * sumX) / count; - // Predict value at futureIndex return m * futureIndex + b; } diff --git a/main/inputs.h b/main/inputs.h index be68658..7d8fb59 100644 --- a/main/inputs.h +++ b/main/inputs.h @@ -1,59 +1,133 @@ +/** + * @file inputs.h + * @brief Input handling for temperature sensors and burner fault detection. + * + * This module reads DS18B20 temperature sensors via 1-Wire and monitors + * the burner fault input. It provides averaged, damped, and predicted + * temperature values. + */ + #pragma once #include "sdkconfig.h" +#include "esp_err.h" + #include +/** @brief Returns the maximum of two values. */ #define MAX(a, b) ((a) > (b) ? (a) : (b)) + +/** @brief Initial value for measurements before first reading. */ #define INITIALISATION_VALUE 0.0f + +/** @brief Sample buffer size for 10-second average. */ #define AVG10S_SAMPLE_SIZE 10U + +/** @brief Sample buffer size for 60-second average. */ #define AVG60S_SAMPLE_SIZE 60U + +/** @brief Sample buffer size for 24-hour average. */ #define AVG24H_SAMPLE_SIZE 24U + +/** @brief Sample buffer size for 60-second prediction. */ #define PRED60S_SAMPLE_SIZE 60U + +/** @brief Damping factor for rising temperatures (from Kconfig). */ #define DAMPING_FACTOR_WARMER (CONFIG_DAMPING_FACTOR_WARMER * 0.00001f) + +/** @brief Damping factor for falling temperatures (from Kconfig). */ #define DAMPING_FACTOR_COLDER (CONFIG_DAMPING_FACTOR_COLDER * 0.00001f) +/** + * @brief Burner error state enumeration. + */ typedef enum _BurnerErrorState { - NO_ERROR, - FAULT + NO_ERROR, /**< No burner fault detected. */ + FAULT /**< Burner fault signal active. */ } eBurnerErrorState; +/** + * @brief Measurement error state enumeration. + */ typedef enum _MeasurementErrorState { - MEASUREMENT_NO_ERROR, - MEASUREMENT_FAULT + MEASUREMENT_NO_ERROR, /**< Measurement valid. */ + MEASUREMENT_FAULT /**< Measurement failed or sensor not found. */ } eMeasurementErrorState; +/** + * @brief Circular buffer for averaging temperature values. + */ typedef struct _Average { - float fValue; - float samples[MAX(AVG10S_SAMPLE_SIZE, MAX(AVG60S_SAMPLE_SIZE, AVG24H_SAMPLE_SIZE))]; - size_t bufferIndex; - size_t bufferCount; + float fValue; /**< Current average value. */ + float samples[MAX(AVG10S_SAMPLE_SIZE, MAX(AVG60S_SAMPLE_SIZE, AVG24H_SAMPLE_SIZE))]; /**< Sample buffer. */ + size_t bufferIndex; /**< Current write index. */ + size_t bufferCount; /**< Number of valid samples. */ } sAverage; +/** + * @brief Circular buffer for temperature prediction. + */ typedef struct _Predict { - float fValue; - float samples[PRED60S_SAMPLE_SIZE]; - size_t bufferIndex; - size_t bufferCount; + float fValue; /**< Predicted value. */ + float samples[PRED60S_SAMPLE_SIZE]; /**< Sample buffer. */ + size_t bufferIndex; /**< Current write index. */ + size_t bufferCount; /**< Number of valid samples. */ } sPredict; +/** + * @brief Complete measurement data structure. + */ typedef struct _Measurement { - float fCurrentValue; - float fDampedValue; - sAverage average10s; - sAverage average60s; - sPredict predict60s; - eMeasurementErrorState state; + float fCurrentValue; /**< Current raw temperature value. */ + float fDampedValue; /**< Damped temperature value. */ + sAverage average10s; /**< 10-second rolling average. */ + sAverage average60s; /**< 60-second rolling average. */ + sPredict predict60s; /**< 60-second prediction. */ + eMeasurementErrorState state; /**< Measurement state. */ } sMeasurement; -void initInputs(void); +/** + * @brief Initialize the inputs module. + * + * Configures GPIO for burner fault input and starts the input task + * for reading DS18B20 temperature sensors. + * + * @return ESP_OK on success, ESP_FAIL on error. + */ +esp_err_t initInputs(void); + +/** + * @brief Get the current chamber temperature measurement. + * @return sMeasurement structure with current values and state. + */ sMeasurement getChamberTemperature(void); + +/** + * @brief Get the current outdoor temperature measurement. + * @return sMeasurement structure with current values and state. + */ sMeasurement getOutdoorTemperature(void); + +/** + * @brief Get the current inlet flow temperature measurement. + * @return sMeasurement structure with current values and state. + */ sMeasurement getInletFlowTemperature(void); + +/** + * @brief Get the current return flow temperature measurement. + * @return sMeasurement structure with current values and state. + */ sMeasurement getReturnFlowTemperature(void); + +/** + * @brief Get the current burner error state. + * @return eBurnerErrorState indicating fault status. + */ eBurnerErrorState getBurnerError(void); diff --git a/main/main.c b/main/main.c index 622cd51..ba373de 100644 --- a/main/main.c +++ b/main/main.c @@ -1,3 +1,11 @@ +/** + * @file main.c + * @brief Main entry point for Smart Oil Heating Control System. + * + * This file initializes all system modules and handles initialization + * errors by logging diagnostic information and triggering a reboot. + */ + #include "safety.h" #include "metrics.h" #include "outputs.h" @@ -9,30 +17,107 @@ #include "esp_log.h" #include "esp_system.h" #include "nvs_flash.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" -static const char *TAG = "smart-oil-heater-control-system"; +static const char *TAG = "main"; +/** @brief Delay before reboot in milliseconds. */ +#define REBOOT_DELAY_MS 5000 + +/** + * @brief Log error and trigger system reboot. + * @param module Name of the module that failed. + */ +static void reboot_on_error(const char *module) +{ + ESP_LOGE(TAG, "========================================"); + ESP_LOGE(TAG, "FATAL: %s initialization failed!", module); + ESP_LOGE(TAG, "System will reboot in %d seconds...", REBOOT_DELAY_MS / 1000); + ESP_LOGE(TAG, "========================================"); + + vTaskDelay(pdMS_TO_TICKS(REBOOT_DELAY_MS)); + esp_restart(); +} + +/** + * @brief Application main entry point. + */ void app_main(void) { - ESP_LOGI(TAG, "starting ..."); + ESP_LOGI(TAG, "========================================"); + ESP_LOGI(TAG, "Smart Oil Heating Control System"); + ESP_LOGI(TAG, "Starting initialization..."); + ESP_LOGI(TAG, "========================================"); - // Initialize NVS + /* Initialize NVS */ esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_LOGW(TAG, "NVS partition needs erase, erasing..."); ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } - ESP_ERROR_CHECK(ret); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "NVS init failed: %s", esp_err_to_name(ret)); + reboot_on_error("NVS"); + } + ESP_LOGI(TAG, "[OK] NVS initialized"); - // TODO: Error handling! - initOutputs(); - initInputs(); - initSafety(); - initWifi(); - initSntp(); - initControl(); - initMetrics(); + /* Initialize Outputs */ + if (initOutputs() != ESP_OK) + { + reboot_on_error("Outputs"); + } + ESP_LOGI(TAG, "[OK] Outputs initialized"); + + /* Initialize Inputs */ + if (initInputs() != ESP_OK) + { + reboot_on_error("Inputs"); + } + ESP_LOGI(TAG, "[OK] Inputs initialized"); + + /* Initialize Safety */ + if (initSafety() != ESP_OK) + { + reboot_on_error("Safety"); + } + ESP_LOGI(TAG, "[OK] Safety initialized"); + + /* Initialize WiFi */ + if (initWifi() != ESP_OK) + { + reboot_on_error("WiFi"); + } + ESP_LOGI(TAG, "[OK] WiFi initialized"); + + /* Initialize SNTP */ + if (initSntp() != ESP_OK) + { + reboot_on_error("SNTP"); + } + ESP_LOGI(TAG, "[OK] SNTP initialized"); + + /* Initialize Control */ + if (initControl() != ESP_OK) + { + reboot_on_error("Control"); + } + ESP_LOGI(TAG, "[OK] Control initialized"); + + /* Initialize Metrics */ + if (initMetrics() != ESP_OK) + { + reboot_on_error("Metrics"); + } + ESP_LOGI(TAG, "[OK] Metrics initialized"); + + ESP_LOGI(TAG, "========================================"); + ESP_LOGI(TAG, "All modules initialized successfully!"); + ESP_LOGI(TAG, "System is now running."); + ESP_LOGI(TAG, "========================================"); while (1) { diff --git a/main/metrics.c b/main/metrics.c index ba3c436..d24d45f 100644 --- a/main/metrics.c +++ b/main/metrics.c @@ -1,3 +1,8 @@ +/** + * @file metrics.c + * @brief Implementation of Prometheus metrics endpoint. + */ + #include "metrics.h" #include "outputs.h" #include "inputs.h" @@ -15,41 +20,50 @@ #include #include -static const char *TAG = "smart-oil-heater-control-system-metrics"; +static const char *TAG = "metrics"; -char caHtmlResponse[HTML_RESPONSE_SIZE]; -SemaphoreHandle_t xMutexAccessMetricResponse = NULL; +static char caHtmlResponse[HTML_RESPONSE_SIZE]; +static SemaphoreHandle_t xMutexAccessMetricResponse = NULL; static sMetric aMetrics[METRIC_MAX_COUNT]; static uint16_t u16MetricCounter = 0U; -void taskMetrics(void *pvParameters); -httpd_handle_t setup_server(void); -esp_err_t get_metrics_handler(httpd_req_t *req); +/* Private function prototypes */ +static void taskMetrics(void *pvParameters); +static httpd_handle_t setup_server(void); +static esp_err_t get_metrics_handler(httpd_req_t *req); -void initMetrics(void) +esp_err_t initMetrics(void) { - setup_server(); + httpd_handle_t server = setup_server(); + if (server == NULL) + { + ESP_LOGE(TAG, "Failed to start HTTP server"); + return ESP_FAIL; + } BaseType_t taskCreated = xTaskCreate( - taskMetrics, // Function to implement the task - "taskMetrics", // Task name - 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) - ); + taskMetrics, + "taskMetrics", + 32768, + NULL, + 5, + NULL); - if (taskCreated == pdPASS) - { - ESP_LOGI(TAG, "Task created successfully!"); - } - else + if (taskCreated != pdPASS) { ESP_LOGE(TAG, "Failed to create task"); + return ESP_FAIL; } + + ESP_LOGI(TAG, "Initialized successfully"); + return ESP_OK; } -void taskMetrics(void *pvParameters) +/** + * @brief Metrics collection task. + * @param pvParameters Task parameters (unused). + */ +static void taskMetrics(void *pvParameters) { while (1) { @@ -338,8 +352,6 @@ 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); strcat(caHtmlResponse, "\n"); @@ -352,7 +364,12 @@ void vSetMetrics(sMetric *paMetrics, uint16_t u16Size) } } -esp_err_t get_metrics_handler(httpd_req_t *req) +/** + * @brief HTTP GET handler for /metrics endpoint. + * @param req HTTP request. + * @return ESP_OK on success. + */ +static esp_err_t get_metrics_handler(httpd_req_t *req) { if (xSemaphoreTakeRecursive(xMutexAccessMetricResponse, pdMS_TO_TICKS(5000)) == pdTRUE) { @@ -367,7 +384,11 @@ esp_err_t get_metrics_handler(httpd_req_t *req) } } -httpd_handle_t setup_server(void) +/** + * @brief Setup HTTP server for metrics endpoint. + * @return HTTP server handle or NULL on failure. + */ +static httpd_handle_t setup_server(void) { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.server_port = 9100; @@ -382,14 +403,17 @@ httpd_handle_t setup_server(void) xMutexAccessMetricResponse = xSemaphoreCreateRecursiveMutex(); if (xMutexAccessMetricResponse == NULL) { - ESP_LOGE(TAG, "Unable to create mutex for metric response"); + ESP_LOGE(TAG, "Failed to create mutex"); + return NULL; } xSemaphoreGiveRecursive(xMutexAccessMetricResponse); if (httpd_start(&server, &config) == ESP_OK) { httpd_register_uri_handler(server, &uri_get); + return server; } - return server; + ESP_LOGE(TAG, "Failed to start HTTP server"); + return NULL; } diff --git a/main/metrics.h b/main/metrics.h index e8eafed..2515b4e 100644 --- a/main/metrics.h +++ b/main/metrics.h @@ -1,26 +1,61 @@ +/** + * @file metrics.h + * @brief Prometheus metrics HTTP endpoint. + * + * This module provides a HTTP server on port 9100 that exposes + * system metrics in Prometheus format at /metrics endpoint. + */ + #pragma once -#include +#include "esp_err.h" +#include "esp_http_server.h" +#include + +/** @brief Maximum size of HTTP response buffer. */ #define HTML_RESPONSE_SIZE 4096U + +/** @brief Maximum length of metric name. */ #define METRIC_NAME_MAX_SIZE 64U + +/** @brief Maximum number of metrics. */ #define METRIC_MAX_COUNT 38U +/** + * @brief Metric value type enumeration. + */ typedef enum _MetricValueType { - FLOAT, - INTEGER_U8, - INTEGER_64, + FLOAT, /**< Floating point value. */ + INTEGER_U8, /**< 8-bit unsigned integer. */ + INTEGER_64, /**< 64-bit signed integer. */ } eMetricValueType; +/** + * @brief Metric data structure. + */ typedef struct _metric { - char caMetricName[METRIC_NAME_MAX_SIZE]; - eMetricValueType type; - float fMetricValue; - uint8_t u8MetricValue; - int64_t i64MetricValue; + char caMetricName[METRIC_NAME_MAX_SIZE]; /**< Metric name. */ + eMetricValueType type; /**< Value type. */ + float fMetricValue; /**< Float value (if type is FLOAT). */ + uint8_t u8MetricValue; /**< U8 value (if type is INTEGER_U8). */ + int64_t i64MetricValue; /**< I64 value (if type is INTEGER_64). */ } sMetric; -void initMetrics(void); -void vSetMetrics(sMetric *paMetrics, uint16_t u16Size); \ No newline at end of file +/** + * @brief Initialize the metrics module. + * + * Starts the HTTP server and creates the metrics collection task. + * + * @return ESP_OK on success, ESP_FAIL on error. + */ +esp_err_t initMetrics(void); + +/** + * @brief Update the metrics buffer. + * @param paMetrics Array of metrics to publish. + * @param u16Size Number of metrics in array. + */ +void vSetMetrics(sMetric *paMetrics, uint16_t u16Size); diff --git a/main/outputs.c b/main/outputs.c index 63dd0e6..9475d89 100644 --- a/main/outputs.c +++ b/main/outputs.c @@ -1,3 +1,8 @@ +/** + * @file outputs.c + * @brief Implementation of output control module. + */ + #include "outputs.h" #include "sdkconfig.h" @@ -6,69 +11,76 @@ #include "driver/gpio.h" #include "esp_log.h" -static const char *TAG = "smart-oil-heater-control-system-outputs"; -const uint8_t uCirculationPumpGpioPin = CONFIG_GPIO_CIRCULATION_PUMP; -const uint8_t uBurnerGpioPin = CONFIG_GPIO_BURNER; -const uint8_t uSafetyContactGpioPin = CONFIG_GPIO_SAFETY_CONTACT; +static const char *TAG = "outputs"; + +/** @brief Circulation pump GPIO pin (from Kconfig). */ +static const uint8_t uCirculationPumpGpioPin = CONFIG_GPIO_CIRCULATION_PUMP; + +/** @brief Burner control GPIO pin (from Kconfig). */ +static const uint8_t uBurnerGpioPin = CONFIG_GPIO_BURNER; + +/** @brief Safety contact GPIO pin (from Kconfig). */ +static const uint8_t uSafetyContactGpioPin = CONFIG_GPIO_SAFETY_CONTACT; static SemaphoreHandle_t xMutexAccessOutputs = NULL; static eOutput sCirculationPumpState; static eOutput sBurnerState; static eOutput sSafetyContactState; -void initOutputs(void) +esp_err_t initOutputs(void) { gpio_config_t ioConfCirculationPump = { - .pin_bit_mask = (1ULL << uCirculationPumpGpioPin), // Pin mask - .mode = GPIO_MODE_OUTPUT, // Set as output - .pull_up_en = GPIO_PULLUP_DISABLE, // Disable pull-up - .pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down - .intr_type = GPIO_INTR_DISABLE // Disable interrupts - }; + .pin_bit_mask = (1ULL << uCirculationPumpGpioPin), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE}; gpio_config_t ioConfBurner = { - .pin_bit_mask = (1ULL << uBurnerGpioPin), // Pin mask - .mode = GPIO_MODE_OUTPUT, // Set as output - .pull_up_en = GPIO_PULLUP_DISABLE, // Disable pull-up - .pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down - .intr_type = GPIO_INTR_DISABLE // Disable interrupts - }; + .pin_bit_mask = (1ULL << uBurnerGpioPin), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE}; gpio_config_t ioConfSafetyContact = { - .pin_bit_mask = (1ULL << uSafetyContactGpioPin), // Pin mask - .mode = GPIO_MODE_OUTPUT, // Set as output - .pull_up_en = GPIO_PULLUP_DISABLE, // Disable pull-up - .pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down - .intr_type = GPIO_INTR_DISABLE // Disable interrupts - }; + .pin_bit_mask = (1ULL << uSafetyContactGpioPin), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE}; esp_err_t ret = gpio_config(&ioConfCirculationPump); if (ret != ESP_OK) { - ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret)); - return; + ESP_LOGE(TAG, "GPIO config failed for circulation pump: %s", esp_err_to_name(ret)); + return ESP_FAIL; } ret = gpio_config(&ioConfBurner); if (ret != ESP_OK) { - ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret)); - return; + ESP_LOGE(TAG, "GPIO config failed for burner: %s", esp_err_to_name(ret)); + return ESP_FAIL; } ret = gpio_config(&ioConfSafetyContact); if (ret != ESP_OK) { - ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret)); - return; + ESP_LOGE(TAG, "GPIO config failed for safety contact: %s", esp_err_to_name(ret)); + return ESP_FAIL; } xMutexAccessOutputs = xSemaphoreCreateRecursiveMutex(); if (xMutexAccessOutputs == NULL) { - ESP_LOGE(TAG, "Unable to create mutex"); + ESP_LOGE(TAG, "Failed to create mutex"); + return ESP_FAIL; } xSemaphoreGiveRecursive(xMutexAccessOutputs); + + ESP_LOGI(TAG, "Initialized successfully"); + return ESP_OK; } eOutput getCirculationPumpState(void) diff --git a/main/outputs.h b/main/outputs.h index 12ddfe9..c064178 100644 --- a/main/outputs.h +++ b/main/outputs.h @@ -1,15 +1,70 @@ +/** + * @file outputs.h + * @brief Output control for circulation pump, burner, and safety contact. + * + * This module controls the relay outputs via GPIO pins. All outputs + * are active-low (relay energized when GPIO is low). + */ + #pragma once +#include "esp_err.h" + +/** + * @brief Output state enumeration. + */ typedef enum _Output { - ENABLED, - DISABLED + ENABLED, /**< Output active (relay energized, GPIO low). */ + DISABLED /**< Output inactive (relay de-energized, GPIO high). */ } eOutput; -void initOutputs(void); +/** + * @brief Initialize the outputs module. + * + * Configures GPIO pins for circulation pump, burner, and safety contact + * as outputs. All outputs are initialized to DISABLED (safe state). + * + * @return ESP_OK on success, ESP_FAIL on error. + */ +esp_err_t initOutputs(void); + +/** + * @brief Get the current circulation pump state. + * @return eOutput state (ENABLED or DISABLED). + */ eOutput getCirculationPumpState(void); + +/** + * @brief Set the circulation pump state. + * @param in Desired state (ENABLED or DISABLED). + */ void setCirculationPumpState(eOutput in); + +/** + * @brief Get the current burner state. + * @return eOutput state (ENABLED or DISABLED). + */ eOutput getBurnerState(void); + +/** + * @brief Set the burner state. + * @param in Desired state (ENABLED or DISABLED). + */ void setBurnerState(eOutput in); + +/** + * @brief Get the current safety contact state. + * @return eOutput state (ENABLED or DISABLED). + */ eOutput getSafetyControlState(void); -void setSafetyControlState(eOutput in); \ No newline at end of file + +/** + * @brief Set the safety contact state. + * + * The safety contact controls power to the burner. When DISABLED, + * the burner cannot operate regardless of the burner signal. + * + * @param in Desired state (ENABLED or DISABLED). + */ +void setSafetyControlState(eOutput in); diff --git a/main/safety.c b/main/safety.c index 7fb6ae4..fbf3d27 100644 --- a/main/safety.c +++ b/main/safety.c @@ -1,3 +1,8 @@ +/** + * @file safety.c + * @brief Implementation of safety monitoring module. + */ + #include "safety.h" #include "freertos/FreeRTOS.h" @@ -7,53 +12,68 @@ #include #include -#define PERIODIC_INTERVAL 1U // run safety checks every 1sec -#define SENSOR_GRACE_PERIOD (CONFIG_SENSOR_GRACE_PERIOD_MINUTES * 60U) // period that a sensor can report the same reading in seconds +/** @brief Task interval in seconds. */ +#define PERIODIC_INTERVAL 1U + +/** @brief Grace period for unchanged sensor readings (seconds). */ +#define SENSOR_GRACE_PERIOD (CONFIG_SENSOR_GRACE_PERIOD_MINUTES * 60U) + +/** @brief Epsilon for float comparison. */ #define FLOAT_EPSILON 0.0001f -static const char *TAG = "smart-oil-heater-control-system-safety"; + +static const char *TAG = "safety"; + static SemaphoreHandle_t xMutexAccessSafety = NULL; + +/** @brief Sensor sanity check configurations. */ static sSensorSanityCheck sanityChecks[NUMBER_OF_SENSOR_SANITY_CHECKS] = { {SENSOR_NO_ERROR, "chamber_temperature", {SENSOR_LIMIT_CHAMBER_MAX, SENSOR_LIMIT_CHAMBER_MIN}, 0.0f, 0U, getChamberTemperature}, {SENSOR_NO_ERROR, "outdoor_temperature", {SENSOR_LIMIT_OUTDOOR_MAX, SENSOR_LIMIT_OUTDOOR_MIN}, 0.0f, 0U, getOutdoorTemperature}, {SENSOR_NO_ERROR, "inlet_flow_temperature", {SENSOR_LIMIT_INLET_MAX, SENSOR_LIMIT_INLET_MIN}, 0.0f, 0U, getInletFlowTemperature}, {SENSOR_NO_ERROR, "return_flow_temperature", {SENSOR_LIMIT_RETURN_MAX, SENSOR_LIMIT_RETURN_MIN}, 0.0f, 0U, getReturnFlowTemperature}}; + static eSafetyState sSafetyState = SAFETY_NO_ERROR; -void taskSafety(void *pvParameters); -void checkSensorSanity(void); -void setSafeState(void); +/* Private function prototypes */ +static void taskSafety(void *pvParameters); +static void checkSensorSanity(void); +static void setSafeState(void); -void initSafety(void) +esp_err_t initSafety(void) { xMutexAccessSafety = xSemaphoreCreateRecursiveMutex(); if (xMutexAccessSafety == NULL) { - ESP_LOGE(TAG, "Unable to create mutex"); + ESP_LOGE(TAG, "Failed to create mutex"); + return ESP_FAIL; } xSemaphoreGiveRecursive(xMutexAccessSafety); BaseType_t taskCreated = xTaskCreate( - taskSafety, // Function to implement the task - "taskSafety", // 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) - ); + taskSafety, + "taskSafety", + 4096, + NULL, + 5, + NULL); - if (taskCreated == pdPASS) - { - ESP_LOGI(TAG, "Task created successfully!"); - } - else + if (taskCreated != pdPASS) { ESP_LOGE(TAG, "Failed to create task"); + return ESP_FAIL; } - setSafeState(); // Set inital state + setSafeState(); + + ESP_LOGI(TAG, "Initialized successfully"); + return ESP_OK; } -void taskSafety(void *pvParameters) +/** + * @brief Safety monitoring task. + * @param pvParameters Task parameters (unused). + */ +static void taskSafety(void *pvParameters) { while (1) { @@ -61,7 +81,6 @@ void taskSafety(void *pvParameters) if (xSemaphoreTakeRecursive(xMutexAccessSafety, portMAX_DELAY) == pdTRUE) { - checkSensorSanity(); if (sSafetyState != SAFETY_NO_ERROR) @@ -74,7 +93,10 @@ void taskSafety(void *pvParameters) } } -void checkSensorSanity(void) +/** + * @brief Check all sensor readings for sanity. + */ +static void checkSensorSanity(void) { sSafetyState = SAFETY_NO_ERROR; for (int i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++) @@ -130,7 +152,10 @@ void checkSensorSanity(void) } } -void setSafeState(void) +/** + * @brief Set system to safe state (burner off, pump on). + */ +static void setSafeState(void) { setCirculationPumpState(ENABLED); // To cool down system setBurnerState(DISABLED); // Deactivate burner diff --git a/main/safety.h b/main/safety.h index 58e6974..ad32cc5 100644 --- a/main/safety.h +++ b/main/safety.h @@ -1,56 +1,117 @@ +/** + * @file safety.h + * @brief Safety monitoring for temperature sensors. + * + * This module performs sanity checks on all temperature sensors and + * puts the system into a safe state if any sensor fails. + */ + #pragma once #include "outputs.h" #include "inputs.h" #include "sdkconfig.h" +#include "esp_err.h" #include +/** @brief Maximum length of sensor name string. */ #define MAX_ERROR_STRING_SIZE 64U + +/** @brief Number of sensors to monitor. */ #define NUMBER_OF_SENSOR_SANITY_CHECKS 4U +/** @brief Chamber sensor maximum temperature limit (°C). */ #define SENSOR_LIMIT_CHAMBER_MAX (CONFIG_SENSOR_LIMIT_CHAMBER_MAX / 10.0f) + +/** @brief Chamber sensor minimum temperature limit (°C). */ #define SENSOR_LIMIT_CHAMBER_MIN (CONFIG_SENSOR_LIMIT_CHAMBER_MIN / 10.0f) + +/** @brief Outdoor sensor maximum temperature limit (°C). */ #define SENSOR_LIMIT_OUTDOOR_MAX (CONFIG_SENSOR_LIMIT_OUTDOOR_MAX / 10.0f) + +/** @brief Outdoor sensor minimum temperature limit (°C). */ #define SENSOR_LIMIT_OUTDOOR_MIN (CONFIG_SENSOR_LIMIT_OUTDOOR_MIN / 10.0f) + +/** @brief Inlet flow sensor maximum temperature limit (°C). */ #define SENSOR_LIMIT_INLET_MAX (CONFIG_SENSOR_LIMIT_INLET_MAX / 10.0f) + +/** @brief Inlet flow sensor minimum temperature limit (°C). */ #define SENSOR_LIMIT_INLET_MIN (CONFIG_SENSOR_LIMIT_INLET_MIN / 10.0f) + +/** @brief Return flow sensor maximum temperature limit (°C). */ #define SENSOR_LIMIT_RETURN_MAX (CONFIG_SENSOR_LIMIT_RETURN_MAX / 10.0f) + +/** @brief Return flow sensor minimum temperature limit (°C). */ #define SENSOR_LIMIT_RETURN_MIN (CONFIG_SENSOR_LIMIT_RETURN_MIN / 10.0f) +/** + * @brief Sensor error state enumeration. + */ typedef enum _SensorErrorState { - SENSOR_NO_ERROR, - SENSOR_TOO_HIGH, - SENSOR_TOO_LOW, - SENSOR_UNCHANGED, - SENSOR_NOT_FOUND + SENSOR_NO_ERROR, /**< Sensor operating normally. */ + SENSOR_TOO_HIGH, /**< Temperature above maximum limit. */ + SENSOR_TOO_LOW, /**< Temperature below minimum limit. */ + SENSOR_UNCHANGED, /**< Temperature unchanged for too long. */ + SENSOR_NOT_FOUND /**< Sensor not responding. */ } eSensorErrorState; +/** + * @brief Overall safety state enumeration. + */ typedef enum _SafetyState { - SAFETY_NO_ERROR, - SAFETY_SENSOR_ERROR, - SAFETY_INTERNAL_ERROR + SAFETY_NO_ERROR, /**< All sensors OK. */ + SAFETY_SENSOR_ERROR, /**< At least one sensor failed. */ + SAFETY_INTERNAL_ERROR /**< Internal module error. */ } eSafetyState; +/** + * @brief Function pointer type for sensor getter functions. + */ typedef sMeasurement (*GetSensorValue)(); + +/** + * @brief Temperature sensor limits. + */ typedef struct _TemperatureSensorLimit { - float max; // Maximum temperature limit - float min; // Minimum temperature limit + float max; /**< Maximum temperature limit. */ + float min; /**< Minimum temperature limit. */ } sTemperatureSensorLimit; + +/** + * @brief Sensor sanity check state structure. + */ typedef struct _SensorSanityCheck { - eSensorErrorState state; - char name[MAX_ERROR_STRING_SIZE]; - sTemperatureSensorLimit sSensorLimit; - float fSensorTemperatureLast; - uint32_t uUnchangedCounter; - GetSensorValue getSensor; + eSensorErrorState state; /**< Current error state. */ + char name[MAX_ERROR_STRING_SIZE]; /**< Sensor name for logging. */ + sTemperatureSensorLimit sSensorLimit; /**< Temperature limits. */ + float fSensorTemperatureLast; /**< Last temperature reading. */ + uint32_t uUnchangedCounter; /**< Counter for unchanged readings. */ + GetSensorValue getSensor; /**< Function to get sensor value. */ } sSensorSanityCheck; -void initSafety(void); +/** + * @brief Initialize the safety module. + * + * Creates the safety monitoring task and sets initial safe state. + * + * @return ESP_OK on success, ESP_FAIL on error. + */ +esp_err_t initSafety(void); + +/** + * @brief Get the current sensor sanity states. + * @param[out] pSensorSanityChecks Array to receive sensor states. + */ void getSensorSanityStates(sSensorSanityCheck *pSensorSanityChecks); -eSafetyState getSafetyState(void); \ No newline at end of file + +/** + * @brief Get the overall safety state. + * @return eSafetyState indicating current safety status. + */ +eSafetyState getSafetyState(void); diff --git a/main/sntp.c b/main/sntp.c index f00bdd6..e9c60ca 100644 --- a/main/sntp.c +++ b/main/sntp.c @@ -1,3 +1,8 @@ +/** + * @file sntp.c + * @brief Implementation of SNTP client module. + */ + #include "sntp.h" #include "esp_sntp.h" @@ -6,17 +11,22 @@ #include #include -static const char *TAG = "smart-oil-heater-control-system-sntp"; -static volatile eSntpState sntpState = SYNC_NOT_STARTED; -void time_sync_notification_cb(struct timeval *tv); +static const char *TAG = "sntp"; -void initSntp(void) +static volatile eSntpState sntpState = SYNC_NOT_STARTED; + +static void time_sync_notification_cb(struct timeval *tv); + +esp_err_t initSntp(void) { esp_sntp_setoperatingmode(SNTP_OPMODE_POLL); esp_sntp_setservername(0, CONFIG_SNTP_SERVER_IP_ADDR); sntp_set_time_sync_notification_cb(time_sync_notification_cb); esp_sntp_init(); + + ESP_LOGI(TAG, "Initialized successfully, server: %s", CONFIG_SNTP_SERVER_IP_ADDR); + return ESP_OK; } eSntpState getSntpState(void) @@ -24,8 +34,12 @@ eSntpState getSntpState(void) return sntpState; } -void time_sync_notification_cb(struct timeval *tv) +/** + * @brief SNTP time sync callback. + * @param tv Synchronized time value. + */ +static void time_sync_notification_cb(struct timeval *tv) { - ESP_LOGI(TAG, "SNTP synchronization! Unix Time: %lld", tv->tv_sec); + ESP_LOGI(TAG, "Time synchronized! Unix Time: %lld", tv->tv_sec); sntpState = SYNC_SUCCESSFUL; } diff --git a/main/sntp.h b/main/sntp.h index 4feb88b..1967234 100644 --- a/main/sntp.h +++ b/main/sntp.h @@ -1,11 +1,37 @@ +/** + * @file sntp.h + * @brief SNTP client for time synchronization. + * + * This module synchronizes system time with an NTP server. + * Time sync is required for schedule-based heating control. + */ + #pragma once +#include "esp_err.h" + +/** + * @brief SNTP synchronization state enumeration. + */ typedef enum _SntpState { - SYNC_SUCCESSFUL, - SYNC_NOT_STARTED, - SYNC_FAILED, + SYNC_SUCCESSFUL, /**< Time synchronized successfully. */ + SYNC_NOT_STARTED, /**< Synchronization not yet attempted. */ + SYNC_FAILED, /**< Synchronization failed. */ } eSntpState; -void initSntp(void); -eSntpState getSntpState(void); \ No newline at end of file +/** + * @brief Initialize the SNTP client. + * + * Configures SNTP with the server from Kconfig and starts + * periodic time synchronization. + * + * @return ESP_OK on success, ESP_FAIL on error. + */ +esp_err_t initSntp(void); + +/** + * @brief Get the current SNTP synchronization state. + * @return eSntpState indicating sync status. + */ +eSntpState getSntpState(void); diff --git a/main/wifi.c b/main/wifi.c index bf764d4..ace3e96 100644 --- a/main/wifi.c +++ b/main/wifi.c @@ -1,3 +1,8 @@ +/** + * @file wifi.c + * @brief Implementation of WiFi station mode module. + */ + #include "wifi.h" #include "esp_timer.h" @@ -12,12 +17,19 @@ #include +/** @brief Event bit for successful connection. */ #define WIFI_CONNECTED_BIT BIT0 + +/** @brief Event bit for connection failure. */ #define WIFI_FAIL_BIT BIT1 + +/** @brief Maximum connection retry attempts. */ #define MAX_RETRY_COUNT 10 + +/** @brief Delay between retries in milliseconds. */ #define RETRY_DELAY_MS 1000 -static const char *TAG = "smart-oil-heater-control-system-wifi"; +static const char *TAG = "wifi"; static EventGroupHandle_t s_wifi_event_group; static int s_retry_num = 0; @@ -26,13 +38,13 @@ static bool s_initial_connect = true; static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); -void initWifi(void) +esp_err_t initWifi(void) { s_wifi_event_group = xEventGroupCreate(); if (s_wifi_event_group == NULL) { - ESP_LOGE(TAG, "xEventGroupCreate() failed!"); - return; + ESP_LOGE(TAG, "Failed to create event group"); + return ESP_FAIL; } ESP_ERROR_CHECK(esp_netif_init()); @@ -73,10 +85,10 @@ void initWifi(void) ESP_ERROR_CHECK(esp_wifi_start()); - ESP_ERROR_CHECK(esp_wifi_set_max_tx_power(78)); // Set max power to 19.5 dBm (78 in units of 0.25 dBm) - ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_MIN_MODEM)); // Use power-saving mode + ESP_ERROR_CHECK(esp_wifi_set_max_tx_power(78)); + ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_MIN_MODEM)); - ESP_LOGI(TAG, "wifi_init_sta finished."); + ESP_LOGI(TAG, "WiFi init finished, waiting for connection..."); EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, @@ -86,21 +98,32 @@ void initWifi(void) if (bits & WIFI_CONNECTED_BIT) { - ESP_LOGI(TAG, "Connected to ap SSID:%s", CONFIG_SSID); + ESP_LOGI(TAG, "Connected to AP SSID:%s", CONFIG_SSID); } else if (bits & WIFI_FAIL_BIT) { - ESP_LOGI(TAG, "Failed to connect to SSID:%s", CONFIG_SSID); + ESP_LOGE(TAG, "Failed to connect to SSID:%s", CONFIG_SSID); + return ESP_FAIL; } else { ESP_LOGE(TAG, "Unexpected event"); + return ESP_FAIL; } - // Mark initial connection phase complete - do NOT delete the event group s_initial_connect = false; + + ESP_LOGI(TAG, "Initialized successfully"); + return ESP_OK; } +/** + * @brief WiFi event handler. + * @param arg User argument (unused). + * @param event_base Event base. + * @param event_id Event ID. + * @param event_data Event data. + */ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { diff --git a/main/wifi.h b/main/wifi.h index 380c625..a4ad953 100644 --- a/main/wifi.h +++ b/main/wifi.h @@ -1,3 +1,22 @@ +/** + * @file wifi.h + * @brief WiFi station mode initialization and management. + * + * This module initializes WiFi in station mode with static IP + * configuration. It handles connection and automatic reconnection. + */ + #pragma once -void initWifi(void); \ No newline at end of file +#include "esp_err.h" + +/** + * @brief Initialize WiFi in station mode. + * + * Configures WiFi with static IP address from Kconfig settings + * and connects to the configured access point. Blocks until + * connected or maximum retry count is reached. + * + * @return ESP_OK on success, ESP_FAIL on error. + */ +esp_err_t initWifi(void);