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/Kconfig.projbuild b/main/Kconfig.projbuild
index c855f47..5c0bcff 100644
--- a/main/Kconfig.projbuild
+++ b/main/Kconfig.projbuild
@@ -1,22 +1,391 @@
menu "Smart Oil Heating Control System"
- config SSID
- string "SSID"
- default "my WiFi SSID"
- config WIFI_PASSWORD
- string "WIFI_PASSWORD"
- default "my WIFI Password"
- config STATIC_IP_ADDR
- string "Static IPv4 address"
- default "192.168.0.42"
- config STATIC_IP_NETMASK
- string "Static IPv4 netmask"
- default "255.255.0.0"
- config STATIC_GATEWAY_IP_ADDR
- string "Static IPv4 gateway address"
- default "192.168.0.1"
- config SNTP_SERVER_IP_ADDR
- string "SNTP IPv4 server address"
- default "192.168.0.1"
+ menu "WiFi Configuration"
+ config SSID
+ string "WiFi SSID"
+ default "my WiFi SSID"
+ help
+ The SSID of the WiFi network to connect to.
+
+ config WIFI_PASSWORD
+ string "WiFi Password"
+ default "my WIFI Password"
+ help
+ The password for the WiFi network.
+
+ config STATIC_IP_ADDR
+ string "Static IPv4 address"
+ default "192.168.0.42"
+ help
+ Static IP address for the ESP32.
+
+ config STATIC_IP_NETMASK
+ string "Static IPv4 netmask"
+ default "255.255.0.0"
+ help
+ Network mask for the static IP configuration.
+
+ config STATIC_GATEWAY_IP_ADDR
+ string "Static IPv4 gateway address"
+ default "192.168.0.1"
+ help
+ Gateway IP address for network routing.
+
+ config SNTP_SERVER_IP_ADDR
+ string "SNTP server address"
+ default "192.168.0.1"
+ help
+ NTP server address for time synchronization.
+ endmenu
+
+ menu "GPIO Configuration"
+ menu "Input GPIOs"
+ config GPIO_BURNER_FAULT
+ int "Burner fault input GPIO"
+ range 0 39
+ default 19
+ help
+ GPIO pin connected to the burner fault signal.
+
+ config GPIO_DS18B20_ONEWIRE
+ int "DS18B20 1-Wire bus GPIO"
+ range 0 39
+ default 4
+ help
+ GPIO pin for the 1-Wire bus (DS18B20 temperature sensors).
+ endmenu
+
+ menu "Output GPIOs"
+ config GPIO_CIRCULATION_PUMP
+ int "Circulation pump output GPIO"
+ range 0 39
+ default 27
+ help
+ GPIO pin to control the circulation pump relay.
+
+ config GPIO_BURNER
+ int "Burner control output GPIO"
+ range 0 39
+ default 14
+ help
+ GPIO pin to control the burner relay.
+
+ config GPIO_SAFETY_CONTACT
+ int "Safety contact output GPIO"
+ range 0 39
+ default 12
+ help
+ GPIO pin for the safety contact relay (main power to burner).
+ endmenu
+ endmenu
+
+ menu "1-Wire Sensor Addresses"
+ config ONEWIRE_ADDR_CHAMBER_TEMP
+ hex "Chamber temperature sensor address"
+ default 0xd00000108cd01d28
+ help
+ 64-bit 1-Wire address of the chamber temperature sensor.
+
+ config ONEWIRE_ADDR_OUTDOOR_TEMP
+ hex "Outdoor temperature sensor address"
+ default 0xd70000108a9b9128
+ help
+ 64-bit 1-Wire address of the outdoor temperature sensor.
+
+ config ONEWIRE_ADDR_INLET_FLOW_TEMP
+ hex "Inlet flow temperature sensor address"
+ default 0x410000108b8c0628
+ help
+ 64-bit 1-Wire address of the inlet flow temperature sensor.
+
+ config ONEWIRE_ADDR_RETURN_FLOW_TEMP
+ hex "Return flow temperature sensor address"
+ default 0x90000108cc77c28
+ help
+ 64-bit 1-Wire address of the return flow temperature sensor.
+ endmenu
+
+ menu "Temperature Control Settings"
+ menu "Target Temperatures"
+ config TEMP_RETURN_FLOW_LOWER_LIMIT_DAY
+ int "Return flow lower limit (day) [°C x 10]"
+ range 150 500
+ default 300
+ help
+ Minimum return flow temperature during day mode in 0.1°C units.
+ Example: 300 = 30.0°C
+
+ config TEMP_RETURN_FLOW_LOWER_LIMIT_NIGHT
+ int "Return flow lower limit (night) [°C x 10]"
+ range 150 500
+ default 250
+ help
+ Minimum return flow temperature during night mode in 0.1°C units.
+ Example: 250 = 25.0°C
+
+ config TEMP_CHAMBER_TARGET
+ int "Chamber target temperature [°C x 10]"
+ range 500 950
+ default 800
+ help
+ Maximum chamber temperature target in 0.1°C units.
+ Example: 800 = 80.0°C
+
+ config TEMP_CHAMBER_THRESHOLD
+ int "Chamber temperature threshold [°C x 10]"
+ range 300 700
+ default 450
+ help
+ Minimum chamber temperature to enable burner in 0.1°C units.
+ Example: 450 = 45.0°C
+
+ config TEMP_CIRCULATION_PUMP_THRESHOLD
+ int "Circulation pump threshold [°C x 10]"
+ range 200 500
+ default 300
+ help
+ Minimum chamber temperature to enable circulation pump in 0.1°C units.
+ Example: 300 = 30.0°C
+ endmenu
+
+ menu "Summer Mode Settings"
+ config TEMP_SUMMER_MODE_HIGH
+ int "Summer mode activation threshold [°C x 10]"
+ range 150 300
+ default 200
+ help
+ Outdoor temperature above which summer mode activates in 0.1°C units.
+ Example: 200 = 20.0°C
+
+ config TEMP_SUMMER_MODE_LOW
+ int "Summer mode deactivation threshold [°C x 10]"
+ range 100 250
+ default 150
+ help
+ Outdoor temperature below which summer mode deactivates in 0.1°C units.
+ Example: 150 = 15.0°C
+ endmenu
+
+ config BURNER_FAULT_DETECTION_SECONDS
+ int "Burner fault detection timeout (seconds)"
+ range 60 600
+ default 240
+ help
+ Time in seconds to wait before checking for burner fault after enabling.
+ endmenu
+
+ menu "Sensor Limits"
+ menu "Chamber Temperature Limits"
+ config SENSOR_LIMIT_CHAMBER_MAX
+ int "Chamber sensor maximum [°C x 10]"
+ range 500 1200
+ default 950
+ help
+ Maximum valid chamber temperature reading in 0.1°C units.
+
+ config SENSOR_LIMIT_CHAMBER_MIN
+ int "Chamber sensor minimum [°C x 10]"
+ range -400 100
+ default -100
+ help
+ Minimum valid chamber temperature reading in 0.1°C units.
+ endmenu
+
+ menu "Outdoor Temperature Limits"
+ config SENSOR_LIMIT_OUTDOOR_MAX
+ int "Outdoor sensor maximum [°C x 10]"
+ range 300 600
+ default 450
+ help
+ Maximum valid outdoor temperature reading in 0.1°C units.
+
+ config SENSOR_LIMIT_OUTDOOR_MIN
+ int "Outdoor sensor minimum [°C x 10]"
+ range -500 0
+ default -200
+ help
+ Minimum valid outdoor temperature reading in 0.1°C units.
+ endmenu
+
+ menu "Inlet Flow Temperature Limits"
+ config SENSOR_LIMIT_INLET_MAX
+ int "Inlet flow sensor maximum [°C x 10]"
+ range 500 1200
+ default 950
+ help
+ Maximum valid inlet flow temperature reading in 0.1°C units.
+
+ config SENSOR_LIMIT_INLET_MIN
+ int "Inlet flow sensor minimum [°C x 10]"
+ range -400 100
+ default -100
+ help
+ Minimum valid inlet flow temperature reading in 0.1°C units.
+ endmenu
+
+ menu "Return Flow Temperature Limits"
+ config SENSOR_LIMIT_RETURN_MAX
+ int "Return flow sensor maximum [°C x 10]"
+ range 500 1200
+ default 950
+ help
+ Maximum valid return flow temperature reading in 0.1°C units.
+
+ config SENSOR_LIMIT_RETURN_MIN
+ int "Return flow sensor minimum [°C x 10]"
+ range -400 100
+ default -100
+ help
+ Minimum valid return flow temperature reading in 0.1°C units.
+ endmenu
+
+ config SENSOR_GRACE_PERIOD_MINUTES
+ int "Sensor unchanged grace period (minutes)"
+ range 1 120
+ default 30
+ help
+ Maximum time in minutes a sensor can report unchanged values
+ before being flagged as faulty.
+ endmenu
+
+ menu "Damping Factors"
+ config DAMPING_FACTOR_WARMER
+ int "Damping factor warmer [x 0.00001]"
+ range 1 100
+ default 1
+ help
+ Damping factor for rising temperatures in units of 0.00001.
+ Example: 1 = 0.00001 (0.001%)
+
+ config DAMPING_FACTOR_COLDER
+ int "Damping factor colder [x 0.00001]"
+ range 1 100
+ default 5
+ help
+ Damping factor for falling temperatures in units of 0.00001.
+ Example: 5 = 0.00005 (0.005%)
+ endmenu
+
+ menu "Heating Schedule"
+ menu "Weekday Schedule (Monday-Thursday)"
+ config SCHEDULE_WEEKDAY_DAY_START_HOUR
+ int "Day mode start hour"
+ range 0 23
+ default 4
+ help
+ Hour when day mode starts on weekdays (24h format).
+
+ config SCHEDULE_WEEKDAY_DAY_START_MINUTE
+ int "Day mode start minute"
+ range 0 59
+ default 45
+ help
+ Minute when day mode starts on weekdays.
+
+ config SCHEDULE_WEEKDAY_NIGHT_START_HOUR
+ int "Night mode start hour"
+ range 0 23
+ default 22
+ help
+ Hour when night mode starts on weekdays (24h format).
+
+ config SCHEDULE_WEEKDAY_NIGHT_START_MINUTE
+ int "Night mode start minute"
+ range 0 59
+ default 0
+ help
+ Minute when night mode starts on weekdays.
+ endmenu
+
+ menu "Friday Schedule"
+ config SCHEDULE_FRIDAY_DAY_START_HOUR
+ int "Day mode start hour"
+ range 0 23
+ default 4
+ help
+ Hour when day mode starts on Friday (24h format).
+
+ config SCHEDULE_FRIDAY_DAY_START_MINUTE
+ int "Day mode start minute"
+ range 0 59
+ default 45
+ help
+ Minute when day mode starts on Friday.
+
+ config SCHEDULE_FRIDAY_NIGHT_START_HOUR
+ int "Night mode start hour"
+ range 0 23
+ default 23
+ help
+ Hour when night mode starts on Friday (24h format).
+
+ config SCHEDULE_FRIDAY_NIGHT_START_MINUTE
+ int "Night mode start minute"
+ range 0 59
+ default 0
+ help
+ Minute when night mode starts on Friday.
+ endmenu
+
+ menu "Saturday Schedule"
+ config SCHEDULE_SATURDAY_DAY_START_HOUR
+ int "Day mode start hour"
+ range 0 23
+ default 6
+ help
+ Hour when day mode starts on Saturday (24h format).
+
+ config SCHEDULE_SATURDAY_DAY_START_MINUTE
+ int "Day mode start minute"
+ range 0 59
+ default 45
+ help
+ Minute when day mode starts on Saturday.
+
+ config SCHEDULE_SATURDAY_NIGHT_START_HOUR
+ int "Night mode start hour"
+ range 0 23
+ default 23
+ help
+ Hour when night mode starts on Saturday (24h format).
+
+ config SCHEDULE_SATURDAY_NIGHT_START_MINUTE
+ int "Night mode start minute"
+ range 0 59
+ default 30
+ help
+ Minute when night mode starts on Saturday.
+ endmenu
+
+ menu "Sunday Schedule"
+ config SCHEDULE_SUNDAY_DAY_START_HOUR
+ int "Day mode start hour"
+ range 0 23
+ default 6
+ help
+ Hour when day mode starts on Sunday (24h format).
+
+ config SCHEDULE_SUNDAY_DAY_START_MINUTE
+ int "Day mode start minute"
+ range 0 59
+ default 45
+ help
+ Minute when day mode starts on Sunday.
+
+ config SCHEDULE_SUNDAY_NIGHT_START_HOUR
+ int "Night mode start hour"
+ range 0 23
+ default 22
+ help
+ Hour when night mode starts on Sunday (24h format).
+
+ config SCHEDULE_SUNDAY_NIGHT_START_MINUTE
+ int "Night mode start minute"
+ range 0 59
+ default 30
+ help
+ Minute when night mode starts on Sunday.
+ endmenu
+ endmenu
endmenu
diff --git a/main/control.c b/main/control.c
index 1098bc7..7a80cf6 100644
--- a/main/control.c
+++ b/main/control.c
@@ -1,134 +1,145 @@
+/**
+ * @file control.c
+ * @brief Implementation of heating control module.
+ */
+
#include "control.h"
-#include "esp_log.h"
-#include "esp_timer.h"
-#include "freertos/FreeRTOS.h"
-#include "freertos/task.h"
#include "inputs.h"
#include "outputs.h"
#include "safety.h"
#include "sntp.h"
-#define PERIODIC_INTERVAL 1U // Run control loop every 1 second
+#include "esp_log.h"
+#include "esp_timer.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
-// Temperature thresholds
-#define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY 30.0f
-#define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT 25.0f
-#define CHAMBER_TEMPERATURE_TARGET 80.0f // Max cutoff temperature
-#define CHAMBER_TEMPERATURE_THRESHOLD 45.0f // Min threshold for burner enable
-#define SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH \
- 20.0f // Summer mode will be activated
-#define SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW \
- 15.0f // Summer mode will be deactivated --> Heating starts
-#define CIRCULATION_PUMP_TEMPERATURE_THRESHOLD \
- 30.0f // Min threshold of chamber for circulation pump enable
-#define BURNER_FAULT_DETECTION_THRESHOLD \
- (60U * 4U) // Burner fault detection after 4 minutes
+#include
-static const char *TAG = "smart-oil-heater-control-system-control";
-static eControlState sControlState = CONTROL_STARTING;
-// Control table for daily schedules
-static const sControlDay aControlTable[] = {
+/** @brief Task interval in seconds. */
+#define PERIODIC_INTERVAL 1U
+
+static const char *TAG = "control";
+
+static eControlState gControlState = CONTROL_STARTING;
+
+/** @brief Weekly schedule table (from Kconfig). */
+static const sControlDay gControlTable[] = {
{MONDAY,
2U,
- {{{4, 45},
+ {{{CONFIG_SCHEDULE_WEEKDAY_DAY_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_DAY_START_MINUTE},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
CHAMBER_TEMPERATURE_TARGET},
- {{22, 0},
+ {{CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_MINUTE},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}},
{TUESDAY,
2U,
- {{{4, 45},
+ {{{CONFIG_SCHEDULE_WEEKDAY_DAY_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_DAY_START_MINUTE},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
CHAMBER_TEMPERATURE_TARGET},
- {{22, 0},
+ {{CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_MINUTE},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}},
{WEDNESDAY,
2U,
- {{{4, 45},
+ {{{CONFIG_SCHEDULE_WEEKDAY_DAY_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_DAY_START_MINUTE},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
CHAMBER_TEMPERATURE_TARGET},
- {{22, 0},
+ {{CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_MINUTE},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}},
{THURSDAY,
2U,
- {{{4, 45},
+ {{{CONFIG_SCHEDULE_WEEKDAY_DAY_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_DAY_START_MINUTE},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
CHAMBER_TEMPERATURE_TARGET},
- {{22, 0},
+ {{CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_MINUTE},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}},
{FRIDAY,
2U,
- {{{4, 45},
+ {{{CONFIG_SCHEDULE_FRIDAY_DAY_START_HOUR, CONFIG_SCHEDULE_FRIDAY_DAY_START_MINUTE},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
CHAMBER_TEMPERATURE_TARGET},
- {{23, 0},
+ {{CONFIG_SCHEDULE_FRIDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_FRIDAY_NIGHT_START_MINUTE},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}},
{SATURDAY,
2U,
- {{{6, 45},
+ {{{CONFIG_SCHEDULE_SATURDAY_DAY_START_HOUR, CONFIG_SCHEDULE_SATURDAY_DAY_START_MINUTE},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
CHAMBER_TEMPERATURE_TARGET},
- {{23, 30},
+ {{CONFIG_SCHEDULE_SATURDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_SATURDAY_NIGHT_START_MINUTE},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}},
{SUNDAY,
2U,
- {{{6, 45},
+ {{{CONFIG_SCHEDULE_SUNDAY_DAY_START_HOUR, CONFIG_SCHEDULE_SUNDAY_DAY_START_MINUTE},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
CHAMBER_TEMPERATURE_TARGET},
- {{22, 30},
+ {{CONFIG_SCHEDULE_SUNDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_SUNDAY_NIGHT_START_MINUTE},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}},
};
-static sControlTemperatureEntry currentControlEntry =
- aControlTable[0].aTemperatureEntries[0];
-// Function prototypes
-void taskControl(void *pvParameters);
-void findControlCurrentTemperatureEntry(void);
+static sControlTemperatureEntry gCurrentControlEntry =
+ gControlTable[0].aTemperatureEntries[0];
+static SemaphoreHandle_t xMutexAccessControl = NULL;
-void initControl(void)
+/* Private function prototypes */
+static void taskControl(void *pvParameters);
+static void findControlCurrentTemperatureEntry(void);
+static void setControlState(eControlState state);
+
+esp_err_t initControl(void)
{
- BaseType_t taskCreated =
- xTaskCreate(taskControl, // Function to implement the task
- "taskControl", // Task name
- 8192, // Stack size (in words, not bytes)
- NULL, // Parameters to the task function (none in this case)
- 5, // Task priority (higher number = higher priority)
- NULL // Task handle (optional)
- );
-
- if (taskCreated == pdPASS)
+ xMutexAccessControl = xSemaphoreCreateRecursiveMutex();
+ if (xMutexAccessControl == NULL)
{
- ESP_LOGI(TAG, "Task created successfully!");
+ ESP_LOGE(TAG, "Failed to create mutex");
+ return ESP_FAIL;
}
- else
+ xSemaphoreGiveRecursive(xMutexAccessControl);
+
+ BaseType_t taskCreated = xTaskCreate(
+ taskControl,
+ "taskControl",
+ 8192,
+ NULL,
+ 5,
+ NULL);
+
+ 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;
- eBurnerState eBurnerState = BURNER_UNKNOWN;
+ eBurnerState burnerState = BURNER_UNKNOWN;
int64_t i64BurnerEnableTimestamp = esp_timer_get_time();
while (1)
{
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!");
- sControlState = CONTROL_FAULT_SAFETY;
+ setControlState(CONTROL_FAULT_SAFETY);
if (bHeatingInAction)
{
ESP_LOGW(TAG, "Disabling burner due to safety fault");
@@ -139,11 +150,11 @@ 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!");
- sControlState = CONTROL_FAULT_SNTP;
+ setControlState(CONTROL_FAULT_SNTP);
if (bHeatingInAction)
{
ESP_LOGW(TAG, "Disabling burner due to SNTP fault");
@@ -155,59 +166,51 @@ void taskControl(void *pvParameters)
}
findControlCurrentTemperatureEntry();
- sControlTemperatureEntry currentControlEntry =
- getControlCurrentTemperatureEntry();
- 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
- if (!bHeatingInAction && (eBurnerState != BURNER_FAULT))
+ /* Enable burner if needed */
+ if (!bHeatingInAction && (burnerState != BURNER_FAULT))
{
if (bSummerMode)
{
- // ESP_LOGI(TAG, "Outdoor temperature too warm: Disabling heating");
setBurnerState(DISABLED);
setSafetyControlState(DISABLED);
- sControlState = CONTROL_OUTDOOR_TOO_WARM;
+ setControlState(CONTROL_OUTDOOR_TOO_WARM);
}
else if ((getReturnFlowTemperature().average60s.fValue <=
- currentControlEntry.fReturnFlowTemperature) &&
- (getChamberTemperature().fCurrentValue <=
- CHAMBER_TEMPERATURE_THRESHOLD))
+ getControlCurrentTemperatureEntry().fReturnFlowTemperature) &&
+ (getChamberTemperature().fCurrentValue <= CHAMBER_TEMPERATURE_THRESHOLD))
{
- ESP_LOGI(TAG,
- "Enabling burner: Return flow temperature target reached");
- eBurnerState = BURNER_UNKNOWN;
+ ESP_LOGI(TAG, "Enabling burner: Return flow temperature target reached");
+ burnerState = BURNER_UNKNOWN;
bHeatingInAction = true;
setBurnerState(ENABLED);
setSafetyControlState(ENABLED);
i64BurnerEnableTimestamp = esp_timer_get_time();
- sControlState = CONTROL_HEATING;
+ setControlState(CONTROL_HEATING);
}
else
{
- // ESP_LOGI(TAG, "Return flow temperature too warm: Disabling heating");
- sControlState = CONTROL_RETURN_FLOW_TOO_WARM;
+ 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 >=
- currentControlEntry.fChamberTemperature) ||
+ getControlCurrentTemperatureEntry().fChamberTemperature) ||
(getChamberTemperature().predict60s.fValue >=
- currentControlEntry.fChamberTemperature))
+ getControlCurrentTemperatureEntry().fChamberTemperature))
{
ESP_LOGI(TAG, "Chamber target temperature reached: Disabling burner");
bHeatingInAction = false;
@@ -217,14 +220,14 @@ void taskControl(void *pvParameters)
else if (esp_timer_get_time() - i64BurnerEnableTimestamp >=
BURNER_FAULT_DETECTION_THRESHOLD * 1000000U)
{
- if (eBurnerState == BURNER_UNKNOWN)
+ if (burnerState == BURNER_UNKNOWN)
{
if (getBurnerError() == FAULT)
{
// ESP_LOGW(TAG, "Burner fault detected: Disabling burner");
bHeatingInAction = false;
- eBurnerState = BURNER_FAULT;
- sControlState = CONTROL_FAULT_BURNER;
+ burnerState = BURNER_FAULT;
+ setControlState(CONTROL_FAULT_BURNER);
setBurnerState(DISABLED);
setSafetyControlState(ENABLED);
}
@@ -232,15 +235,14 @@ void taskControl(void *pvParameters)
{
// ESP_LOGI(TAG, "No burner fault detected: Marking burner as
// fired");
- eBurnerState = BURNER_FIRED;
+ burnerState = BURNER_FIRED;
}
}
}
}
- // 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);
@@ -253,39 +255,58 @@ void taskControl(void *pvParameters)
} // End of while(1)
}
-eControlState getControlState(void) { return sControlState; }
+/**
+ * @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;
+ xSemaphoreGiveRecursive(xMutexAccessControl);
+ }
+ else
+ {
+ ESP_LOGE(TAG, "Unable to take mutex: setControlState()");
+ }
+}
+
+eControlState getControlState(void)
+{
+ eControlState ret = CONTROL_FAULT_SAFETY;
+
+ if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE)
+ {
+ ret = gControlState;
+ xSemaphoreGiveRecursive(xMutexAccessControl);
+ }
+ else
+ {
+ ESP_LOGE(TAG, "Unable to take mutex: getControlState()");
+ }
+
+ return ret;
+}
eControlWeekday getControlCurrentWeekday(void)
{
time_t now;
- struct tm *timeinfo;
-
+ struct tm timeinfo;
time(&now);
- timeinfo = localtime(&now);
+ localtime_r(&now, &timeinfo);
- int day = timeinfo->tm_wday;
+ int day = timeinfo.tm_wday;
return (eControlWeekday)((day == 0) ? 6 : day - 1);
}
/**
- * @brief Finds the active temperature control entry for the current time.
- *
- * Searches through the weekly schedule to find the most recent entry
- * that should be active at the current date/time. Falls back to the
- * last entry in the week if no suitable entry is found.
+ * @brief 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);
@@ -294,66 +315,88 @@ void findControlCurrentTemperatureEntry(void)
int currentHour = timeinfo.tm_hour;
int currentMinute = timeinfo.tm_min;
- // ESP_LOGI(TAG, "Searching for control entry - Day: %d, Time: %02d:%02d", currentDay, currentHour, currentMinute);
-
- // Search through all days and entries
- for (int dayIndex = 0; dayIndex < 7; dayIndex++)
+ if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE)
{
- const sControlDay *day = &aControlTable[dayIndex];
- for (int entryIndex = 0; entryIndex < day->entryCount; entryIndex++)
+ // ESP_LOGI(TAG, "Searching for control entry - Day: %d, Time: %02d:%02d", currentDay, currentHour, currentMinute);
+
+ // Search through all days and entries
+ for (int dayIndex = 0; dayIndex < 7; dayIndex++)
{
- const sControlTemperatureEntry *entry = &day->aTemperatureEntries[entryIndex];
+ const sControlDay *day = &gControlTable[dayIndex];
- // Check if this entry is in the future (next active entry)
- bool isFutureDay = (day->day > currentDay);
- bool isTodayFutureTime = (day->day == currentDay) &&
- ((entry->timestamp.hour > currentHour) ||
- (entry->timestamp.hour == currentHour &&
- entry->timestamp.minute > currentMinute));
-
- if (isFutureDay || isTodayFutureTime)
+ for (int entryIndex = 0; entryIndex < day->entryCount; entryIndex++)
{
- // Found next scheduled entry, so determine the previous (active) one
- if (entryIndex > 0)
+ const sControlTemperatureEntry *entry = &day->aTemperatureEntries[entryIndex];
+
+ // Check if this entry is in the future (next active entry)
+ bool isFutureDay = (day->day > currentDay);
+ bool isTodayFutureTime = (day->day == currentDay) &&
+ ((entry->timestamp.hour > currentHour) ||
+ (entry->timestamp.hour == currentHour &&
+ entry->timestamp.minute > currentMinute));
+
+ if (isFutureDay || isTodayFutureTime)
{
- // Use previous entry from same day
- currentControlEntry = day->aTemperatureEntries[entryIndex - 1];
+
+ // Found next scheduled entry, so determine the previous (active) one
+ if (entryIndex > 0)
+ {
+ // Use previous entry from same day
+ gCurrentControlEntry = day->aTemperatureEntries[entryIndex - 1];
+ }
+ else if (dayIndex > 0)
+ {
+ // Use last entry from previous day
+ const sControlDay *previousDay = &gControlTable[dayIndex - 1];
+ gCurrentControlEntry = previousDay->aTemperatureEntries[previousDay->entryCount - 1];
+ }
+ else
+ {
+ // First entry of the week - wrap to last entry of Sunday
+ 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;
}
- else if (dayIndex > 0)
- {
- // Use last entry from previous day
- const sControlDay *previousDay = &aControlTable[dayIndex - 1];
- currentControlEntry = previousDay->aTemperatureEntries[previousDay->entryCount - 1];
- }
- else
- {
- // First entry of the week - wrap to last entry of Sunday
- const sControlDay *sunday = &aControlTable[6];
- currentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1];
- }
- /*
- ESP_LOGI(TAG, "Active entry found - Time: %02d:%02d, "
- "Return Temp: %lf, Chamber Temp: %lf",
- currentControlEntry.timestamp.hour,
- currentControlEntry.timestamp.minute,
- currentControlEntry.fReturnFlowTemperature,
- currentControlEntry.fChamberTemperature);
- */
- return;
}
}
+
+ // If we reached here, current time is after all entries this week
+ // Use the last entry (Sunday evening)
+ const sControlDay *sunday = &gControlTable[6];
+ gCurrentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1];
+
+ // ESP_LOGI(TAG, "Using last entry of week - Time: %02d:%02d", gCurrentControlEntry.timestamp.hour, gCurrentControlEntry.timestamp.minute);
+ xSemaphoreGiveRecursive(xMutexAccessControl);
+ }
+ else
+ {
+ ESP_LOGE(TAG, "Unable to take mutex: findControlCurrentTemperatureEntry()");
}
-
- // If we reached here, current time is after all entries this week
- // Use the last entry (Sunday evening)
- const sControlDay *sunday = &aControlTable[6];
- currentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1];
-
- // ESP_LOGI(TAG, "Using last entry of week - Time: %02d:%02d", currentControlEntry.timestamp.hour, currentControlEntry.timestamp.minute);
}
sControlTemperatureEntry getControlCurrentTemperatureEntry(void)
{
- return currentControlEntry;
+ sControlTemperatureEntry ret = gControlTable[0].aTemperatureEntries[0];
+ if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE)
+ {
+ ret = gCurrentControlEntry;
+ xSemaphoreGiveRecursive(xMutexAccessControl);
+ }
+ else
+ {
+ ESP_LOGE(TAG, "Unable to take mutex: getControlCurrentTemperatureEntry()");
+ }
+
+ return ret;
}
diff --git a/main/control.h b/main/control.h
index 7690bd1..4507f95 100644
--- a/main/control.h
+++ b/main/control.h
@@ -1,26 +1,74 @@
-#pragma once
-#include
+/**
+ * @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
+
+/** @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,
@@ -32,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 a9ef723..9289e9c 100644
--- a/main/inputs.c
+++ b/main/inputs.c
@@ -1,29 +1,51 @@
-#include "freertos/FreeRTOS.h"
-#include "freertos/task.h"
-#include "driver/gpio.h"
-#include
-#include
-#include "esp_log.h"
-#include
+/**
+ * @file inputs.c
+ * @brief Implementation of input handling module.
+ */
#include "inputs.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "driver/gpio.h"
+#include "esp_log.h"
+#include
+
+#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 = 19U;
-const uint8_t uDS18B20Pin = 4U;
+/** @brief Number of retry attempts for 1-Wire read. */
+#define ONE_WIRE_LOOPS 4U
-const onewire_addr_t uChamperTempSensorAddr = 0xd00000108cd01d28;
-const onewire_addr_t uOutdoorTempSensorAddr = 0xd70000108a9b9128;
-const onewire_addr_t uInletFlowTempSensorAddr = 0x410000108b8c0628;
-const onewire_addr_t uReturnFlowTempSensorAddr = 0x90000108cc77c28;
+/** @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;
@@ -32,29 +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};
- gpio_config(&ioConfBurnerFault);
+ esp_err_t ret = gpio_config(&ioConfBurnerFault);
+ if (ret != ESP_OK)
+ {
+ ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret));
+ 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);
@@ -64,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;
@@ -94,25 +124,29 @@ void initMeasurement(sMeasurement *pMeasurement)
pMeasurement->average10s.fValue = INITIALISATION_VALUE;
pMeasurement->average10s.bufferCount = 0U;
pMeasurement->average10s.bufferIndex = 0U;
- memset(pMeasurement->average10s.samples, 0U, AVG10S_SAMPLE_SIZE);
+ memset(pMeasurement->average10s.samples, 0U, sizeof(float) * AVG10S_SAMPLE_SIZE);
pMeasurement->average60s.fValue = INITIALISATION_VALUE;
pMeasurement->average60s.bufferCount = 0U;
pMeasurement->average60s.bufferIndex = 0U;
- memset(pMeasurement->average60s.samples, 0U, AVG60S_SAMPLE_SIZE);
+ memset(pMeasurement->average60s.samples, 0U, sizeof(float) * AVG60S_SAMPLE_SIZE);
pMeasurement->predict60s.fValue = INITIALISATION_VALUE;
pMeasurement->predict60s.bufferCount = 0U;
pMeasurement->predict60s.bufferIndex = 0U;
- memset(pMeasurement->predict60s.samples, 0U, PRED60S_SAMPLE_SIZE);
+ 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;
@@ -122,14 +156,21 @@ void updateAverage(sMeasurement *pMeasurement)
}
float sum = 0.0;
- for (int i = 0; i <= pMeasurement->average10s.bufferCount; i++)
+ for (int i = 0; i < pMeasurement->average10s.bufferCount; i++)
{
sum += pMeasurement->average10s.samples[i];
}
- pMeasurement->average10s.fValue = sum / pMeasurement->average10s.bufferCount;
+ if (pMeasurement->average10s.bufferCount == 0U)
+ {
+ pMeasurement->average10s.fValue = 0.0f;
+ }
+ else
+ {
+ 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;
@@ -144,9 +185,16 @@ void updateAverage(sMeasurement *pMeasurement)
sum += pMeasurement->average60s.samples[i];
}
- pMeasurement->average60s.fValue = sum / pMeasurement->average60s.bufferCount;
+ if (pMeasurement->average60s.bufferCount == 0U)
+ {
+ pMeasurement->average60s.fValue = 0.0f;
+ }
+ else
+ {
+ pMeasurement->average60s.fValue = sum / pMeasurement->average60s.bufferCount;
+ }
- // Damped current value
+ /* Damped current value */
if (pMeasurement->fDampedValue == INITIALISATION_VALUE)
{
pMeasurement->fDampedValue = pMeasurement->fCurrentValue;
@@ -165,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;
@@ -185,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)
{
@@ -287,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;
@@ -308,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;
}
@@ -397,4 +458,4 @@ eBurnerErrorState getBurnerError(void)
ESP_LOGE(TAG, "Unable to take mutex: getBurnerError()");
}
return ret;
-}
\ No newline at end of file
+}
diff --git a/main/inputs.h b/main/inputs.h
index 17e095b..7d8fb59 100644
--- a/main/inputs.h
+++ b/main/inputs.h
@@ -1,55 +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
-#define MAX(a, b) ((a) > (b) ? (a) : (b))
-#define INITIALISATION_VALUE 0.0f
-#define AVG10S_SAMPLE_SIZE 10U
-#define AVG60S_SAMPLE_SIZE 60U
-#define AVG24H_SAMPLE_SIZE 24U
-#define PRED60S_SAMPLE_SIZE 60U
-#define DAMPING_FACTOR_WARMER 0.00001f // 0.001%
-#define DAMPING_FACTOR_COLDER 0.00005f // 0.005%
+#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);
-eBurnerErrorState getBurnerError(void);
\ No newline at end of file
+
+/**
+ * @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 82c2d63..ba373de 100644
--- a/main/main.c
+++ b/main/main.c
@@ -1,6 +1,10 @@
-#include "esp_log.h"
-#include
-#include "nvs_flash.h"
+/**
+ * @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"
@@ -10,33 +14,114 @@
#include "wifi.h"
#include "sntp.h"
-static const char *TAG = "smart-oil-heater-control-system";
+#include "esp_log.h"
+#include "esp_system.h"
+#include "nvs_flash.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+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)
{
vTaskDelay(pdMS_TO_TICKS(1000));
// Do nothing ;-)
}
-}
\ No newline at end of file
+}
diff --git a/main/metrics.c b/main/metrics.c
index 6f1b2db..d24d45f 100644
--- a/main/metrics.c
+++ b/main/metrics.c
@@ -1,11 +1,7 @@
-#include
-#include "esp_timer.h"
-#include "freertos/FreeRTOS.h"
-#include "freertos/task.h"
-#include "esp_wifi.h"
-#include "esp_log.h"
-#include
-#include
+/**
+ * @file metrics.c
+ * @brief Implementation of Prometheus metrics endpoint.
+ */
#include "metrics.h"
#include "outputs.h"
@@ -14,41 +10,60 @@
#include "sntp.h"
#include "control.h"
-static const char *TAG = "smart-oil-heater-control-system-metrics";
+#include "esp_timer.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
-char caHtmlResponse[HTML_RESPONSE_SIZE];
-SemaphoreHandle_t xMutexAccessMetricResponse = NULL;
+#include
+#include
+#include
+
+static const char *TAG = "metrics";
+
+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)
{
@@ -301,23 +316,23 @@ void taskMetrics(void *pvParameters)
// Wifi RSSI
wifi_ap_record_t ap;
- esp_wifi_sta_get_ap_info(&ap);
+ ap.rssi = 0U;
+ ESP_ERROR_CHECK(esp_wifi_sta_get_ap_info(&ap));
strcpy(aMetrics[u16MetricCounter].caMetricName, "wifi_rssi");
aMetrics[u16MetricCounter].type = INTEGER_64;
aMetrics[u16MetricCounter].i64MetricValue = ap.rssi;
u16MetricCounter++;
- ESP_ERROR_CHECK(u16MetricCounter > METRIC_MAX_COUNT);
+ configASSERT(!(u16MetricCounter > METRIC_MAX_COUNT));
vSetMetrics(aMetrics, u16MetricCounter);
}
}
void vSetMetrics(sMetric *paMetrics, uint16_t u16Size)
{
-
if (xSemaphoreTakeRecursive(xMutexAccessMetricResponse, pdMS_TO_TICKS(5000)) == pdTRUE)
{
- memset(caHtmlResponse, 0U, strlen(caHtmlResponse));
+ memset(caHtmlResponse, 0U, HTML_RESPONSE_SIZE);
for (uint16_t u16Index = 0U; u16Index < u16Size; u16Index++)
{
char caValueBuffer[64];
@@ -337,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");
@@ -351,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)
{
@@ -366,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;
@@ -381,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 7536e76..9475d89 100644
--- a/main/outputs.c
+++ b/main/outputs.c
@@ -1,61 +1,101 @@
+/**
+ * @file outputs.c
+ * @brief Implementation of output control module.
+ */
+
+#include "outputs.h"
+
+#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
-#include "outputs.h"
+static const char *TAG = "outputs";
-static const char *TAG = "smart-oil-heater-control-system-outputs";
-const uint8_t uCirculationPumpGpioPin = 27U;
-const uint8_t uBurnerGpioPin = 14U;
-const uint8_t uSafetyContactGpioPin = 12U;
+/** @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};
- gpio_config(&ioConfCirculationPump);
- gpio_config(&ioConfBurner);
- gpio_config(&ioConfSafetyContact);
+ esp_err_t ret = gpio_config(&ioConfCirculationPump);
+ if (ret != ESP_OK)
+ {
+ 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 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 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)
{
- return sCirculationPumpState;
+ eOutput ret = ENABLED;
+ if (xSemaphoreTakeRecursive(xMutexAccessOutputs, pdMS_TO_TICKS(5000)) == pdTRUE)
+ {
+ ret = sCirculationPumpState;
+ xSemaphoreGiveRecursive(xMutexAccessOutputs);
+ }
+ else
+ {
+ ESP_LOGE(TAG, "Unable to take mutex: getCirculationPumpState()");
+ }
+ return ret;
}
void setCirculationPumpState(eOutput in)
@@ -70,6 +110,7 @@ void setCirculationPumpState(eOutput in)
break;
case DISABLED:
gpio_set_level(uCirculationPumpGpioPin, 1U); // Switch off Circulation Pump
+ break;
default:
break;
}
@@ -108,6 +149,7 @@ void setBurnerState(eOutput in)
break;
case DISABLED:
gpio_set_level(uBurnerGpioPin, 1U); // Switch off Burner
+ break;
default:
break;
}
@@ -146,6 +188,7 @@ void setSafetyControlState(eOutput in)
break;
case DISABLED:
gpio_set_level(uSafetyContactGpioPin, 1U); // Switch off power for Burner
+ break;
default:
break;
}
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 db60a87..fbf3d27 100644
--- a/main/safety.c
+++ b/main/safety.c
@@ -1,56 +1,79 @@
+/**
+ * @file safety.c
+ * @brief Implementation of safety monitoring module.
+ */
+
+#include "safety.h"
+
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
+
#include
-#include "safety.h"
+#include
-#define PERIODIC_INTERVAL 1U // run safety checks every 1sec
-#define SENSOR_GRACE_PERIOD (60U * 30U) // 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 = "safety";
-static const char *TAG = "smart-oil-heater-control-system-safety";
static SemaphoreHandle_t xMutexAccessSafety = NULL;
+
+/** @brief Sensor sanity check configurations. */
static sSensorSanityCheck sanityChecks[NUMBER_OF_SENSOR_SANITY_CHECKS] = {
- {SENSOR_NO_ERROR, "chamber_temperature", {95.0f, -10.0f}, 0.0f, 0U, getChamberTemperature},
- {SENSOR_NO_ERROR, "outdoor_temperature", {45.0f, -20.0f}, 0.0f, 0U, getOutdoorTemperature},
- {SENSOR_NO_ERROR, "inlet_flow_temperature", {95.0f, -10.0f}, 0.0f, 0U, getInletFlowTemperature},
- {SENSOR_NO_ERROR, "return_flow_temperature", {95.0f, -10.0f}, 0.0f, 0U, getReturnFlowTemperature}};
+ {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)
{
@@ -58,7 +81,6 @@ void taskSafety(void *pvParameters)
if (xSemaphoreTakeRecursive(xMutexAccessSafety, portMAX_DELAY) == pdTRUE)
{
-
checkSensorSanity();
if (sSafetyState != SAFETY_NO_ERROR)
@@ -71,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++)
@@ -91,7 +116,7 @@ void checkSensorSanity(void)
}
else
{
- if (sCurrentMeasurement.fCurrentValue == sanityChecks[i].fSensorTemperatureLast)
+ if (fabsf(sCurrentMeasurement.fCurrentValue - sanityChecks[i].fSensorTemperatureLast) < FLOAT_EPSILON)
{
sanityChecks[i].uUnchangedCounter++;
if (sanityChecks[i].uUnchangedCounter >= (SENSOR_GRACE_PERIOD / PERIODIC_INTERVAL))
@@ -103,6 +128,7 @@ void checkSensorSanity(void)
}
else
{
+ sanityChecks[i].uUnchangedCounter = 0U;
sanityChecks[i].fSensorTemperatureLast = sCurrentMeasurement.fCurrentValue;
if (sCurrentMeasurement.fCurrentValue > sanityChecks[i].sSensorLimit.max)
@@ -119,16 +145,17 @@ void checkSensorSanity(void)
}
else
{
- sanityChecks[i].uUnchangedCounter = 0U;
sanityChecks[i].state = SENSOR_NO_ERROR;
}
}
}
- // printf(" state: %u\n", sanityChecks[i].state);
}
}
-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
@@ -143,7 +170,7 @@ void getSensorSanityStates(sSensorSanityCheck *pSensorSanityChecks)
{
// Copy only the needed attributes
pSensorSanityChecks[i].state = sanityChecks[i].state;
- strcpy(pSensorSanityChecks[i].name, sanityChecks[i].name);
+ strncpy(pSensorSanityChecks[i].name, sanityChecks[i].name, MAX_ERROR_STRING_SIZE);
}
xSemaphoreGiveRecursive(xMutexAccessSafety);
}
diff --git a/main/safety.h b/main/safety.h
index 825e7cd..ad32cc5 100644
--- a/main/safety.h
+++ b/main/safety.h
@@ -1,43 +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 638bfa4..e9c60ca 100644
--- a/main/sntp.c
+++ b/main/sntp.c
@@ -1,21 +1,32 @@
-#include
-#include
-#include
-#include "esp_log.h"
+/**
+ * @file sntp.c
+ * @brief Implementation of SNTP client module.
+ */
#include "sntp.h"
-static const char *TAG = "smart-oil-heater-control-system-sntp";
-static eSntpState sntpState = SYNC_NOT_STARTED;
-void time_sync_notification_cb(struct timeval *tv);
+#include "esp_sntp.h"
+#include "esp_log.h"
-void initSntp(void)
+#include
+#include
+
+static const char *TAG = "sntp";
+
+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)
@@ -23,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 6a4b806..ace3e96 100644
--- a/main/wifi.c
+++ b/main/wifi.c
@@ -1,22 +1,35 @@
-#include
+/**
+ * @file wifi.c
+ * @brief Implementation of WiFi station mode module.
+ */
+
+#include "wifi.h"
+
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
+#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event.h"
-#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_netif.h"
#include
-#include "wifi.h"
+#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;
@@ -25,19 +38,25 @@ 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, "Failed to create event group");
+ return ESP_FAIL;
+ }
+
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_t *my_sta = esp_netif_create_default_wifi_sta();
- esp_netif_dhcpc_stop(my_sta);
+ ESP_ERROR_CHECK(esp_netif_dhcpc_stop(my_sta));
esp_netif_ip_info_t ip_info;
ip_info.ip.addr = ipaddr_addr(CONFIG_STATIC_IP_ADDR);
ip_info.gw.addr = ipaddr_addr(CONFIG_STATIC_GATEWAY_IP_ADDR);
ip_info.netmask.addr = ipaddr_addr(CONFIG_STATIC_IP_NETMASK);
- esp_netif_set_ip_info(my_sta, &ip_info);
+ ESP_ERROR_CHECK(esp_netif_set_ip_info(my_sta, &ip_info));
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
@@ -66,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,
@@ -79,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);