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);