diff --git a/README.md b/README.md
index 07d79a3..845fde9 100644
--- a/README.md
+++ b/README.md
@@ -1,83 +1,41 @@
-# smart-oil-heating-control-system
+# Smart Oil Heating Control System
-## Software
-### Design
+ESP32-based control system for oil-fired central heating with schedule-based temperature management, safety monitoring, and Prometheus metrics export.
+## Features
+
+- **Schedule Control**: Day/night temperature targets per weekday
+- **Summer Mode**: Automatic heating disable based on outdoor temperature
+- **Safety Monitoring**: Sensor sanity checks with automatic safe-state fallback
+- **Prometheus Metrics**: HTTP endpoint at port 9100
+
+## System Overview
```mermaid
-classDiagram
-Inputs <|-- Control
-Outputs <|-- Control
-Sntp <|-- Control
-Inputs <|-- Safety
-Outputs <|--|> Safety
+flowchart TB
+ subgraph OUTSIDE[" "]
+ OT[/"🌡️ Outdoor Temp
DS18B20"/]
+ end
-Inputs <|-- Metrics
-Outputs <|-- Metrics
-Control <|-- Metrics
-Safety <|-- Metrics
-Sntp <|-- Metrics
+ subgraph BURNER["OIL BURNER"]
+ CT[/"🌡️ Chamber Temp
DS18B20"/]
+ BF[["⚠️ Burner Fault
GPIO19 INPUT"]]
+ BR(["🔥 Burner Relay
GPIO14"])
+ SC(["🔌 Safety Contact
GPIO12"])
+ end
- class Inputs{
- +initInputs()
- -initMeasurement()
- -updateAverage()
- -updatePrediction()
- -taskInput()
- -linearRegressionPredict()
- +getChamberTemperature()
- +getOutdoorTemperature()
- +getInletFlowTemperature()
- +getReturnFlowTemperature()
- +getBurnerError()
- }
+ subgraph CIRCUIT["HEATING CIRCUIT"]
+ IT[/"🌡️ Inlet Temp
DS18B20"/]
+ CP(["💧 Circulation Pump
GPIO27"])
+ RT[/"🌡️ Return Temp
DS18B20"/]
+ end
- class Outputs{
- +initOutputs()
- +getCirculationPumpState()
- +setCirculationPumpState()
- +getBurnerState()
- +setBurnerState()
- +getSafetyControlState()
- +setSafetyControlState()
- }
+ RAD["🏠 Radiators"]
- class Control{
- initControl()
- +taskControl()
- +getControlCurrentWeekday()
- -findControlCurrentTemperatureEntry()
- +getControlCurrentTemperatureEntry()
- -controlTable
- +getControlState()
- }
-
- class Safety{
- +initSafety()
- -taskSafety()
- -setSafeState()
- -checkSensorSanity()
- +getSensorSanityStates()
- +getSafetyState()
- }
-
-
- class Wifi{
- +initWifi()
- }
-
- class Sntp{
- +initSntp()
- +getSntpState()
- }
-
- class Metrics{
- +initMetrics()
- -taskMetrics()
- -metrics
- +event_handler()
- +connect_wifi()
- +setMetrics()
- }
+ BURNER -->|"hot water"| IT
+ IT --> CP
+ CP --> RAD
+ RAD --> RT
+ RT -->|"cold water"| BURNER
```
### Prometheus Metrics
@@ -88,26 +46,26 @@ burner_fault_pending 1
circulation_pump_enabled 1
burner_enabled 0
safety_contact_enabled 1
-chamber_temperature 37.250000
-chamber_temperature_avg10 37.237499
-chamber_temperature_avg60 37.438541
-chamber_temperature_damped 42.185040
-chamber_temperature_pred60 36.638443
-inlet_flow_temperature 35.625000
-inlet_flow_temperature_avg10 35.618752
-inlet_flow_temperature_avg60 35.415627
-inlet_flow_temperature_damped 39.431259
-inlet_flow_temperature_pred60 36.078678
-outdoor_temperature 14.687500
-outdoor_temperature_avg10 14.662500
-outdoor_temperature_avg60 14.646875
-outdoor_temperature_damped 9.169084
-outdoor_temperature_pred60 14.660233
-return_flow_temperature 39.937500
-return_flow_temperature_avg10 40.087502
-return_flow_temperature_avg60 41.146873
-return_flow_temperature_damped 32.385151
-return_flow_temperature_pred60 37.311958
+chamber_temperature 37.312500
+chamber_temperature_avg10 37.393749
+chamber_temperature_avg60 37.689583
+chamber_temperature_damped 38.058098
+chamber_temperature_pred60 36.697266
+inlet_flow_temperature 34.562500
+inlet_flow_temperature_avg10 34.587502
+inlet_flow_temperature_avg60 34.880207
+inlet_flow_temperature_damped 35.255993
+inlet_flow_temperature_pred60 33.910374
+outdoor_temperature 1.812500
+outdoor_temperature_avg10 1.825000
+outdoor_temperature_avg60 1.821875
+outdoor_temperature_damped 2.390663
+outdoor_temperature_pred60 1.840263
+return_flow_temperature 34.125000
+return_flow_temperature_avg10 34.162498
+return_flow_temperature_avg60 34.304165
+return_flow_temperature_damped 31.430506
+return_flow_temperature_pred60 33.858772
chamber_temperature_state 0
outdoor_temperature_state 0
inlet_flow_temperature_state 0
@@ -115,13 +73,13 @@ return_flow_temperature_state 0
safety_state 0
control_state 3
control_current_weekday 5
-control_current_entry_time 17100
+control_current_entry_time 24300
control_current_entry_chamber_temperature 80.000000
control_current_entry_return_flow_temperature 30.000000
sntp_state 0
-system_unixtime 1762012743
-uptime_seconds 465229
-wifi_rssi -72
+system_unixtime 1768067412
+uptime_seconds 344878
+wifi_rssi -59
```
#### Status Encoding
@@ -180,4 +138,20 @@ wifi_rssi -72
| Input Burner Fault | IO19 | Digital Input IN1 |
| Input Temperature DS10B20 | IO04 | 1-Wire |
+## Configuration
+All parameters configurable via `idf.py menuconfig`:
+- WiFi credentials and static IP
+- GPIO pin assignments
+- 1-Wire sensor addresses
+- Temperature thresholds and limits
+- Heating schedule (day/night per weekday)
+- Damping factors
+
+## Building
+
+```bash
+idf.py set-target esp32
+idf.py menuconfig # Configure settings
+idf.py build flash monitor
+```
\ No newline at end of file
diff --git a/main/control.c b/main/control.c
index 9280c71..e852fcc 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)
@@ -293,24 +302,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);
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..2045df4 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,11 +12,20 @@
#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},
@@ -19,41 +33,46 @@ static sSensorSanityCheck sanityChecks[NUMBER_OF_SENSOR_SANITY_CHECKS] = {
{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 +80,6 @@ void taskSafety(void *pvParameters)
if (xSemaphoreTakeRecursive(xMutexAccessSafety, portMAX_DELAY) == pdTRUE)
{
-
checkSensorSanity();
if (sSafetyState != SAFETY_NO_ERROR)
@@ -74,7 +92,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 +151,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 c4938c6..afcd013 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;
@@ -27,13 +39,13 @@ static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data);
static bool parse_bssid(const char *bssid_str, uint8_t *bssid);
-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());
@@ -89,10 +101,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,
@@ -102,21 +114,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);