19 Commits

Author SHA256 Message Date
localhorst 6a78f302c5 Merge branch 'main' into feature/config 2026-05-10 10:53:08 +02:00
localhorst f8f6af53bd implement config 2026-01-10 12:50:06 +01:00
localhorst f3f6f1bc5f Potential division by zero 2026-01-10 12:01:22 +01:00
localhorst b718073907 Missing break before default 2026-01-10 11:58:46 +01:00
localhorst d36b91a0fd Variable name shadows type name 2026-01-10 11:57:15 +01:00
localhorst 40f757b7d1 uUnchangedCounter reset logic flaw 2026-01-10 11:54:18 +01:00
localhorst a9ec101bc6 Floating-point equality comparison 2026-01-10 11:52:08 +01:00
localhorst 0236ebcdd1 Unsafe strcpy 2026-01-10 11:47:04 +01:00
localhorst 05757a5038 Unchecked WiFi API call 2026-01-10 11:45:49 +01:00
localhorst 020eb63e05 Unchecked network configuration 2026-01-10 11:43:26 +01:00
localhorst 67929580d5 Unchecked xEventGroupCreate 2026-01-10 11:42:27 +01:00
localhorst 10f9645580 Unchecked gpio_config returns 2026-01-10 11:39:37 +01:00
localhorst df3825df3a Non-thread-safe function 2026-01-10 11:33:37 +01:00
localhorst 8c3dbc2886 Unprotected shared state access 2026-01-10 11:31:34 +01:00
localhorst 267197ec20 Missing mutex protection 2026-01-10 11:06:10 +01:00
localhorst 781f9a1445 ncorrect memset with strlen 2026-01-10 11:02:31 +01:00
localhorst 09a3c3a22d Misuse of ESP_ERROR_CHECK 2026-01-10 10:58:12 +01:00
localhorst 0775fda0ca Off-by-one error (buffer overread) 2026-01-10 10:55:15 +01:00
localhorst cd73985740 Wrong memset size 2026-01-10 10:54:32 +01:00
17 changed files with 398 additions and 943 deletions
+97 -71
View File
@@ -1,41 +1,83 @@
# Smart Oil Heating Control System # smart-oil-heating-control-system
ESP32-based control system for oil-fired central heating with schedule-based temperature management, safety monitoring, and Prometheus metrics export. ## Software
### Design
## 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 ```mermaid
flowchart TB classDiagram
subgraph OUTSIDE[" "] Inputs <|-- Control
OT[/"🌡️ Outdoor Temp<br/>DS18B20"/] Outputs <|-- Control
end Sntp <|-- Control
Inputs <|-- Safety
Outputs <|--|> Safety
subgraph BURNER["OIL BURNER"] Inputs <|-- Metrics
CT[/"🌡️ Chamber Temp<br/>DS18B20"/] Outputs <|-- Metrics
BF[["⚠️ Burner Fault<br/>GPIO19 INPUT"]] Control <|-- Metrics
BR(["🔥 Burner Relay<br/>GPIO14"]) Safety <|-- Metrics
SC(["🔌 Safety Contact<br/>GPIO12"]) Sntp <|-- Metrics
end
subgraph CIRCUIT["HEATING CIRCUIT"] class Inputs{
IT[/"🌡️ Inlet Temp<br/>DS18B20"/] +initInputs()
CP(["💧 Circulation Pump<br/>GPIO27"]) -initMeasurement()
RT[/"🌡️ Return Temp<br/>DS18B20"/] -updateAverage()
end -updatePrediction()
-taskInput()
-linearRegressionPredict()
+getChamberTemperature()
+getOutdoorTemperature()
+getInletFlowTemperature()
+getReturnFlowTemperature()
+getBurnerError()
}
RAD["🏠 Radiators"] class Outputs{
+initOutputs()
+getCirculationPumpState()
+setCirculationPumpState()
+getBurnerState()
+setBurnerState()
+getSafetyControlState()
+setSafetyControlState()
}
BURNER -->|"hot water"| IT class Control{
IT --> CP initControl()
CP --> RAD +taskControl()
RAD --> RT +getControlCurrentWeekday()
RT -->|"cold water"| BURNER -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()
}
``` ```
### Prometheus Metrics ### Prometheus Metrics
@@ -46,26 +88,26 @@ burner_fault_pending 1
circulation_pump_enabled 1 circulation_pump_enabled 1
burner_enabled 0 burner_enabled 0
safety_contact_enabled 1 safety_contact_enabled 1
chamber_temperature 37.312500 chamber_temperature 37.250000
chamber_temperature_avg10 37.393749 chamber_temperature_avg10 37.237499
chamber_temperature_avg60 37.689583 chamber_temperature_avg60 37.438541
chamber_temperature_damped 38.058098 chamber_temperature_damped 42.185040
chamber_temperature_pred60 36.697266 chamber_temperature_pred60 36.638443
inlet_flow_temperature 34.562500 inlet_flow_temperature 35.625000
inlet_flow_temperature_avg10 34.587502 inlet_flow_temperature_avg10 35.618752
inlet_flow_temperature_avg60 34.880207 inlet_flow_temperature_avg60 35.415627
inlet_flow_temperature_damped 35.255993 inlet_flow_temperature_damped 39.431259
inlet_flow_temperature_pred60 33.910374 inlet_flow_temperature_pred60 36.078678
outdoor_temperature 1.812500 outdoor_temperature 14.687500
outdoor_temperature_avg10 1.825000 outdoor_temperature_avg10 14.662500
outdoor_temperature_avg60 1.821875 outdoor_temperature_avg60 14.646875
outdoor_temperature_damped 2.390663 outdoor_temperature_damped 9.169084
outdoor_temperature_pred60 1.840263 outdoor_temperature_pred60 14.660233
return_flow_temperature 34.125000 return_flow_temperature 39.937500
return_flow_temperature_avg10 34.162498 return_flow_temperature_avg10 40.087502
return_flow_temperature_avg60 34.304165 return_flow_temperature_avg60 41.146873
return_flow_temperature_damped 31.430506 return_flow_temperature_damped 32.385151
return_flow_temperature_pred60 33.858772 return_flow_temperature_pred60 37.311958
chamber_temperature_state 0 chamber_temperature_state 0
outdoor_temperature_state 0 outdoor_temperature_state 0
inlet_flow_temperature_state 0 inlet_flow_temperature_state 0
@@ -73,13 +115,13 @@ return_flow_temperature_state 0
safety_state 0 safety_state 0
control_state 3 control_state 3
control_current_weekday 5 control_current_weekday 5
control_current_entry_time 24300 control_current_entry_time 17100
control_current_entry_chamber_temperature 80.000000 control_current_entry_chamber_temperature 80.000000
control_current_entry_return_flow_temperature 30.000000 control_current_entry_return_flow_temperature 30.000000
sntp_state 0 sntp_state 0
system_unixtime 1768067412 system_unixtime 1762012743
uptime_seconds 344878 uptime_seconds 465229
wifi_rssi -59 wifi_rssi -72
``` ```
#### Status Encoding #### Status Encoding
@@ -138,20 +180,4 @@ wifi_rssi -59
| Input Burner Fault | IO19 | Digital Input IN1 | | Input Burner Fault | IO19 | Digital Input IN1 |
| Input Temperature DS10B20 | IO04 | 1-Wire | | 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
```
-14
View File
@@ -36,20 +36,6 @@ menu "Smart Oil Heating Control System"
default "192.168.0.1" default "192.168.0.1"
help help
NTP server address for time synchronization. NTP server address for time synchronization.
config ENV_WIFI_BSSID_LOCK
bool "Lock to specific Access Point (BSSID)"
default n
help
When enabled, the device will only connect to the access point
with the specified MAC address (BSSID). Useful when multiple APs
share the same SSID.
config ENV_WIFI_BSSID
string "Access Point MAC Address (BSSID)"
default "00:00:00:00:00:00"
depends on ENV_WIFI_BSSID_LOCK
help
MAC address of the access point to connect to.
Format: XX:XX:XX:XX:XX:XX (uppercase or lowercase)
endmenu endmenu
menu "GPIO Configuration" menu "GPIO Configuration"
+59 -56
View File
@@ -1,8 +1,3 @@
/**
* @file control.c
* @brief Implementation of heating control module.
*/
#include "control.h" #include "control.h"
#include "inputs.h" #include "inputs.h"
#include "outputs.h" #include "outputs.h"
@@ -14,16 +9,11 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include <time.h> #define PERIODIC_INTERVAL 1U // Run control loop every 1 second
/** @brief Task interval in seconds. */
#define PERIODIC_INTERVAL 1U
static const char *TAG = "control";
static const char *TAG = "smart-oil-heater-control-system-control";
static eControlState gControlState = CONTROL_STARTING; static eControlState gControlState = CONTROL_STARTING;
// Control table for daily schedules
/** @brief Weekly schedule table (from Kconfig). */
static const sControlDay gControlTable[] = { static const sControlDay gControlTable[] = {
{MONDAY, {MONDAY,
2U, 2U,
@@ -82,49 +72,45 @@ static const sControlDay gControlTable[] = {
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}}, CHAMBER_TEMPERATURE_TARGET}}},
}; };
static sControlTemperatureEntry gCurrentControlEntry = static sControlTemperatureEntry gCurrentControlEntry =
gControlTable[0].aTemperatureEntries[0]; gControlTable[0].aTemperatureEntries[0];
static SemaphoreHandle_t xMutexAccessControl = NULL; static SemaphoreHandle_t xMutexAccessControl = NULL;
/* Private function prototypes */ // Function prototypes
static void taskControl(void *pvParameters); void taskControl(void *pvParameters);
static void findControlCurrentTemperatureEntry(void); void findControlCurrentTemperatureEntry(void);
static void setControlState(eControlState state); void setControlState(eControlState state);
esp_err_t initControl(void) void initControl(void)
{ {
xMutexAccessControl = xSemaphoreCreateRecursiveMutex(); xMutexAccessControl = xSemaphoreCreateRecursiveMutex();
if (xMutexAccessControl == NULL) if (xMutexAccessControl == NULL)
{ {
ESP_LOGE(TAG, "Failed to create mutex"); ESP_LOGE(TAG, "Unable to create mutex");
return ESP_FAIL;
} }
xSemaphoreGiveRecursive(xMutexAccessControl); xSemaphoreGiveRecursive(xMutexAccessControl);
BaseType_t taskCreated = xTaskCreate( BaseType_t taskCreated =
taskControl, xTaskCreate(taskControl, // Function to implement the task
"taskControl", "taskControl", // Task name
8192, 8192, // Stack size (in words, not bytes)
NULL, NULL, // Parameters to the task function (none in this case)
5, 5, // Task priority (higher number = higher priority)
NULL); NULL // Task handle (optional)
);
if (taskCreated != pdPASS) if (taskCreated == pdPASS)
{
ESP_LOGI(TAG, "Task created successfully!");
}
else
{ {
ESP_LOGE(TAG, "Failed to create task"); 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 bHeatingInAction = false;
bool bSummerMode = false; bool bSummerMode = false;
@@ -135,7 +121,7 @@ static void taskControl(void *pvParameters)
{ {
vTaskDelay(PERIODIC_INTERVAL * 1000U / portTICK_PERIOD_MS); vTaskDelay(PERIODIC_INTERVAL * 1000U / portTICK_PERIOD_MS);
/* Check for safety faults */ // Check for safety faults
if (getSafetyState() != SAFETY_NO_ERROR) if (getSafetyState() != SAFETY_NO_ERROR)
{ {
ESP_LOGW(TAG, "Control not possible due to safety fault!"); ESP_LOGW(TAG, "Control not possible due to safety fault!");
@@ -150,7 +136,7 @@ static void taskControl(void *pvParameters)
continue; continue;
} }
/* Check for SNTP faults */ // Check for SNTP faults
if (getSntpState() != SYNC_SUCCESSFUL) if (getSntpState() != SYNC_SUCCESSFUL)
{ {
ESP_LOGW(TAG, "Control not possible due to SNTP fault!"); ESP_LOGW(TAG, "Control not possible due to SNTP fault!");
@@ -167,30 +153,35 @@ static void taskControl(void *pvParameters)
findControlCurrentTemperatureEntry(); findControlCurrentTemperatureEntry();
/* Summer mode hysteresis */ if (getOutdoorTemperature().fDampedValue >=
if (getOutdoorTemperature().fDampedValue >= SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH) SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH)
{ {
bSummerMode = true; bSummerMode = true;
} }
else if (getOutdoorTemperature().fDampedValue <= SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW) else if (getOutdoorTemperature().fDampedValue <=
SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW)
{ {
bSummerMode = false; bSummerMode = false;
} }
/* Enable burner if needed */ // Enable burner if outdoor temperature is low and return flow temperature
// is cooled down
if (!bHeatingInAction && (burnerState != BURNER_FAULT)) if (!bHeatingInAction && (burnerState != BURNER_FAULT))
{ {
if (bSummerMode) if (bSummerMode)
{ {
// ESP_LOGI(TAG, "Outdoor temperature too warm: Disabling heating");
setBurnerState(DISABLED); setBurnerState(DISABLED);
setSafetyControlState(DISABLED); setSafetyControlState(DISABLED);
setControlState(CONTROL_OUTDOOR_TOO_WARM); setControlState(CONTROL_OUTDOOR_TOO_WARM);
} }
else if ((getReturnFlowTemperature().average60s.fValue <= else if ((getReturnFlowTemperature().average60s.fValue <=
getControlCurrentTemperatureEntry().fReturnFlowTemperature) && getControlCurrentTemperatureEntry().fReturnFlowTemperature) &&
(getChamberTemperature().fCurrentValue <= CHAMBER_TEMPERATURE_THRESHOLD)) (getChamberTemperature().fCurrentValue <=
CHAMBER_TEMPERATURE_THRESHOLD))
{ {
ESP_LOGI(TAG, "Enabling burner: Return flow temperature target reached"); ESP_LOGI(TAG,
"Enabling burner: Return flow temperature target reached");
burnerState = BURNER_UNKNOWN; burnerState = BURNER_UNKNOWN;
bHeatingInAction = true; bHeatingInAction = true;
setBurnerState(ENABLED); setBurnerState(ENABLED);
@@ -200,11 +191,12 @@ static void taskControl(void *pvParameters)
} }
else else
{ {
// ESP_LOGI(TAG, "Return flow temperature too warm: Disabling heating");
setControlState(CONTROL_RETURN_FLOW_TOO_WARM); setControlState(CONTROL_RETURN_FLOW_TOO_WARM);
} }
} }
/* Disable burner if target reached or fault */ // Disable burner if target temperature is reached or a fault occurred
if (bHeatingInAction) if (bHeatingInAction)
{ {
if ((getChamberTemperature().fCurrentValue >= if ((getChamberTemperature().fCurrentValue >=
@@ -241,8 +233,9 @@ static void taskControl(void *pvParameters)
} }
} }
/* Manage circulation pump */ // Manage circulation pump
if (getChamberTemperature().fCurrentValue <= CIRCULATION_PUMP_TEMPERATURE_THRESHOLD) if (getChamberTemperature().fCurrentValue <=
CIRCULATION_PUMP_TEMPERATURE_THRESHOLD)
{ {
// ESP_LOGI(TAG, "Burner cooled down: Disabling circulation pump"); // ESP_LOGI(TAG, "Burner cooled down: Disabling circulation pump");
setCirculationPumpState(DISABLED); setCirculationPumpState(DISABLED);
@@ -255,12 +248,9 @@ static void taskControl(void *pvParameters)
} // End of while(1) } // End of while(1)
} }
/** void setControlState(eControlState state)
* @brief Set the control state with mutex protection.
* @param state New control state.
*/
static void setControlState(eControlState state)
{ {
if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE) if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE)
{ {
gControlState = state; gControlState = state;
@@ -274,6 +264,7 @@ static void setControlState(eControlState state)
eControlState getControlState(void) eControlState getControlState(void)
{ {
eControlState ret = CONTROL_FAULT_SAFETY; eControlState ret = CONTROL_FAULT_SAFETY;
if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE) if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE)
@@ -302,12 +293,24 @@ eControlWeekday getControlCurrentWeekday(void)
} }
/** /**
* @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.
*/ */
static void findControlCurrentTemperatureEntry(void) /**
* @brief Finds the active temperature control entry for the current time.
*
* Searches through the weekly schedule to find the most recent entry
* that should be active at the current date/time. Falls back to the
* last entry in the week if no suitable entry is found.
*/
void findControlCurrentTemperatureEntry(void)
{ {
eControlWeekday currentDay = getControlCurrentWeekday(); eControlWeekday currentDay = getControlCurrentWeekday();
// Get current time
time_t now; time_t now;
struct tm timeinfo; struct tm timeinfo;
time(&now); time(&now);
+20 -87
View File
@@ -1,74 +1,38 @@
/**
* @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 #pragma once
#include "sdkconfig.h" #include "sdkconfig.h"
#include "esp_err.h"
#include <stdint.h> #include <time.h>
#include <stddef.h>
/** @brief Maximum number of temperature entries per day. */
#define MAX_TEMPERATURE_ENTRIES_PER_DAY 24U #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) #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) #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) #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) #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) #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) #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) #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 #define BURNER_FAULT_DETECTION_THRESHOLD CONFIG_BURNER_FAULT_DETECTION_SECONDS
/**
* @brief Control state enumeration.
*/
typedef enum _ControlState typedef enum _ControlState
{ {
CONTROL_STARTING, /**< System starting up. */ CONTROL_STARTING,
CONTROL_HEATING, /**< Burner running. */ CONTROL_HEATING,
CONTROL_OUTDOOR_TOO_WARM, /**< Summer mode active. */ CONTROL_OUTDOOR_TOO_WARM,
CONTROL_RETURN_FLOW_TOO_WARM, /**< Target temperature reached. */ CONTROL_RETURN_FLOW_TOO_WARM,
CONTROL_FAULT_BURNER, /**< Burner fault detected. */ CONTROL_FAULT_BURNER,
CONTROL_FAULT_SAFETY, /**< Safety fault detected. */ CONTROL_FAULT_SAFETY,
CONTROL_FAULT_SNTP, /**< SNTP sync failed. */ CONTROL_FAULT_SNTP,
} eControlState; } eControlState;
/**
* @brief Burner operational state enumeration.
*/
typedef enum _BurnerState typedef enum _BurnerState
{ {
BURNER_UNKNOWN, /**< Burner state unknown after enable. */ BURNER_UNKNOWN, // Burner is disabled or state after enabling is still unkown
BURNER_FIRED, /**< Burner fired successfully. */ BURNER_FIRED, // Burner fired successfully
BURNER_FAULT /**< Burner failed to fire. */ BURNER_FAULT // Burner was unable to fire successfully
} eBurnerState; } eBurnerState;
/**
* @brief Weekday enumeration (Monday = 0).
*/
typedef enum _ControlWeekday typedef enum _ControlWeekday
{ {
MONDAY, MONDAY,
@@ -80,58 +44,27 @@ typedef enum _ControlWeekday
SUNDAY, SUNDAY,
} eControlWeekday; } eControlWeekday;
/**
* @brief Time of day structure.
*/
typedef struct _ControlTimestamp typedef struct _ControlTimestamp
{ {
uint8_t hour; /**< Hour (0-23). */ uint8_t hour;
uint8_t minute; /**< Minute (0-59). */ uint8_t minute;
} sControlTimestamp; } sControlTimestamp;
/**
* @brief Temperature schedule entry.
*/
typedef struct _ControlTemperatureEntry typedef struct _ControlTemperatureEntry
{ {
sControlTimestamp timestamp; /**< Time when entry becomes active. */ sControlTimestamp timestamp;
float fReturnFlowTemperature; /**< Target return flow temperature. */ float fReturnFlowTemperature;
float fChamberTemperature; /**< Target chamber temperature. */ float fChamberTemperature;
} sControlTemperatureEntry; } sControlTemperatureEntry;
/**
* @brief Daily schedule structure.
*/
typedef struct _ControlDay typedef struct _ControlDay
{ {
eControlWeekday day; /**< Day of week. */ eControlWeekday day;
size_t entryCount; /**< Number of entries for this day. */ size_t entryCount; // number of entries for each day
sControlTemperatureEntry aTemperatureEntries[MAX_TEMPERATURE_ENTRIES_PER_DAY]; /**< Schedule entries. */ sControlTemperatureEntry aTemperatureEntries[MAX_TEMPERATURE_ENTRIES_PER_DAY];
} sControlDay; } 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); eControlState getControlState(void);
/**
* @brief Get the current weekday.
* @return eControlWeekday (Monday = 0, Sunday = 6).
*/
eControlWeekday getControlCurrentWeekday(void); eControlWeekday getControlCurrentWeekday(void);
/**
* @brief Get the currently active temperature entry.
* @return sControlTemperatureEntry with current targets.
*/
sControlTemperatureEntry getControlCurrentTemperatureEntry(void); sControlTemperatureEntry getControlCurrentTemperatureEntry(void);
+55 -96
View File
@@ -1,8 +1,3 @@
/**
* @file inputs.c
* @brief Implementation of input handling module.
*/
#include "inputs.h" #include "inputs.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
@@ -14,38 +9,22 @@
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
/** @brief Maximum number of DS18B20 sensors supported. */
#define MAX_DN18B20_SENSORS 4U #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
/** @brief Number of retry attempts for 1-Wire read. */ static const char *TAG = "smart-oil-heater-control-system-inputs";
#define ONE_WIRE_LOOPS 4U const uint8_t uBurnerFaultPin = CONFIG_GPIO_BURNER_FAULT;
const uint8_t uDS18B20Pin = CONFIG_GPIO_DS18B20_ONEWIRE;
/** @brief Task interval in seconds. */ const onewire_addr_t uChamperTempSensorAddr = CONFIG_ONEWIRE_ADDR_CHAMBER_TEMP;
#define PERIODIC_INTERVAL 1U const onewire_addr_t uOutdoorTempSensorAddr = CONFIG_ONEWIRE_ADDR_OUTDOOR_TEMP;
const onewire_addr_t uInletFlowTempSensorAddr = CONFIG_ONEWIRE_ADDR_INLET_FLOW_TEMP;
const onewire_addr_t uReturnFlowTempSensorAddr = CONFIG_ONEWIRE_ADDR_RETURN_FLOW_TEMP;
static const char *TAG = "inputs"; onewire_addr_t uOneWireAddresses[MAX_DN18B20_SENSORS];
float fDS18B20Temps[MAX_DN18B20_SENSORS];
/** @brief Burner fault GPIO pin (from Kconfig). */ size_t sSensorCount = 0U;
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 SemaphoreHandle_t xMutexAccessInputs = NULL;
static eBurnerErrorState sBurnerErrorState; static eBurnerErrorState sBurnerErrorState;
@@ -54,34 +33,34 @@ static sMeasurement sOutdoorTemperature;
static sMeasurement sInletFlowTemperature; static sMeasurement sInletFlowTemperature;
static sMeasurement sReturnFlowTemperature; static sMeasurement sReturnFlowTemperature;
/* Private function prototypes */ void taskInput(void *pvParameters);
static void taskInput(void *pvParameters); void initMeasurement(sMeasurement *pMeasurement);
static void initMeasurement(sMeasurement *pMeasurement); void updateAverage(sMeasurement *pMeasurement);
static void updateAverage(sMeasurement *pMeasurement); void updatePrediction(sMeasurement *pMeasurement);
static void updatePrediction(sMeasurement *pMeasurement); float linearRegressionPredict(const float *samples, size_t count, size_t bufferIndex, float futureIndex);
static float linearRegressionPredict(const float *samples, size_t count, size_t bufferIndex, float futureIndex);
esp_err_t initInputs(void) void initInputs(void)
{ {
gpio_config_t ioConfBurnerFault = { gpio_config_t ioConfBurnerFault = {
.pin_bit_mask = (1ULL << uBurnerFaultPin), .pin_bit_mask = (1ULL << uBurnerFaultPin), // Pin mask
.mode = GPIO_MODE_INPUT, .mode = GPIO_MODE_INPUT, // Set as inout
.pull_up_en = GPIO_PULLUP_ENABLE, .pull_up_en = GPIO_PULLUP_ENABLE, // Enable pull-up
.pull_down_en = GPIO_PULLDOWN_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down
.intr_type = GPIO_INTR_DISABLE}; .intr_type = GPIO_INTR_DISABLE // Disable interrupts
};
esp_err_t ret = gpio_config(&ioConfBurnerFault); esp_err_t ret = gpio_config(&ioConfBurnerFault);
if (ret != ESP_OK) if (ret != ESP_OK)
{ {
ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret)); ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret));
return ESP_FAIL; return;
} }
xMutexAccessInputs = xSemaphoreCreateRecursiveMutex(); xMutexAccessInputs = xSemaphoreCreateRecursiveMutex();
if (xMutexAccessInputs == NULL) if (xMutexAccessInputs == NULL)
{ {
ESP_LOGE(TAG, "Failed to create mutex"); ESP_LOGE(TAG, "Unable to create mutex");
return ESP_FAIL;
} }
xSemaphoreGiveRecursive(xMutexAccessInputs); xSemaphoreGiveRecursive(xMutexAccessInputs);
@@ -91,28 +70,25 @@ esp_err_t initInputs(void)
initMeasurement(&sReturnFlowTemperature); initMeasurement(&sReturnFlowTemperature);
BaseType_t taskCreated = xTaskCreate( BaseType_t taskCreated = xTaskCreate(
taskInput, taskInput, // Function to implement the task
"taskInput", "taskInput", // Task name
4096, 4096, // Stack size (in words, not bytes)
NULL, NULL, // Parameters to the task function (none in this case)
5, 5, // Task priority (higher number = higher priority)
NULL); NULL // Task handle (optional)
);
if (taskCreated != pdPASS) if (taskCreated == pdPASS)
{
ESP_LOGI(TAG, "Task created successfully!");
}
else
{ {
ESP_LOGE(TAG, "Failed to create task"); 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) if (!pMeasurement)
return; return;
@@ -137,16 +113,12 @@ static void initMeasurement(sMeasurement *pMeasurement)
memset(pMeasurement->predict60s.samples, 0U, sizeof(float) * 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) if (!pMeasurement)
return; return;
/* 10-second average */ // Average form the last 10sec
pMeasurement->average10s.samples[pMeasurement->average10s.bufferIndex] = pMeasurement->fCurrentValue; pMeasurement->average10s.samples[pMeasurement->average10s.bufferIndex] = pMeasurement->fCurrentValue;
pMeasurement->average10s.bufferIndex = (pMeasurement->average10s.bufferIndex + 1) % AVG10S_SAMPLE_SIZE; pMeasurement->average10s.bufferIndex = (pMeasurement->average10s.bufferIndex + 1) % AVG10S_SAMPLE_SIZE;
@@ -170,7 +142,7 @@ static void updateAverage(sMeasurement *pMeasurement)
pMeasurement->average10s.fValue = sum / pMeasurement->average10s.bufferCount; pMeasurement->average10s.fValue = sum / pMeasurement->average10s.bufferCount;
} }
/* 60-second average */ // Average form the last 60sec
pMeasurement->average60s.samples[pMeasurement->average60s.bufferIndex] = pMeasurement->fCurrentValue; pMeasurement->average60s.samples[pMeasurement->average60s.bufferIndex] = pMeasurement->fCurrentValue;
pMeasurement->average60s.bufferIndex = (pMeasurement->average60s.bufferIndex + 1) % AVG60S_SAMPLE_SIZE; pMeasurement->average60s.bufferIndex = (pMeasurement->average60s.bufferIndex + 1) % AVG60S_SAMPLE_SIZE;
@@ -194,7 +166,7 @@ static void updateAverage(sMeasurement *pMeasurement)
pMeasurement->average60s.fValue = sum / pMeasurement->average60s.bufferCount; pMeasurement->average60s.fValue = sum / pMeasurement->average60s.bufferCount;
} }
/* Damped current value */ // Damped current value
if (pMeasurement->fDampedValue == INITIALISATION_VALUE) if (pMeasurement->fDampedValue == INITIALISATION_VALUE)
{ {
pMeasurement->fDampedValue = pMeasurement->fCurrentValue; pMeasurement->fDampedValue = pMeasurement->fCurrentValue;
@@ -213,11 +185,7 @@ static 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) if (!pMeasurement)
return; return;
@@ -237,11 +205,7 @@ static void updatePrediction(sMeasurement *pMeasurement)
predict60s->bufferCount + 60.0f); 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) while (1)
{ {
@@ -343,27 +307,20 @@ static 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) if (count == 0)
return INITIALISATION_VALUE; return INITIALISATION_VALUE; // No prediction possible with no data
float sumX = INITIALISATION_VALUE, sumY = INITIALISATION_VALUE, sumXY = INITIALISATION_VALUE, sumX2 = INITIALISATION_VALUE; float sumX = INITIALISATION_VALUE, sumY = INITIALISATION_VALUE, sumXY = INITIALISATION_VALUE, sumX2 = INITIALISATION_VALUE;
for (size_t i = 0; i < count; i++) for (size_t i = 0; i < count; i++)
{ {
// Calculate the circular buffer index for the current sample
size_t circularIndex = (bufferIndex + i + 1) % count; size_t circularIndex = (bufferIndex + i + 1) % count;
float x = (float)i; float x = (float)i; // Time index
float y = samples[circularIndex]; float y = samples[circularIndex]; // Sample value
sumX += x; sumX += x;
sumY += y; sumY += y;
@@ -371,13 +328,15 @@ static float linearRegressionPredict(const float *samples, size_t count, size_t
sumX2 += x * x; sumX2 += x * x;
} }
// Calculate slope (m) and intercept (b) of the line: y = mx + b
float denominator = (count * sumX2 - sumX * sumX); float denominator = (count * sumX2 - sumX * sumX);
if (fabs(denominator) < 1e-6) if (fabs(denominator) < 1e-6) // Avoid division by zero
return samples[bufferIndex]; return samples[bufferIndex]; // Return the latest value as prediction
float m = (count * sumXY - sumX * sumY) / denominator; float m = (count * sumXY - sumX * sumY) / denominator;
float b = (sumY - m * sumX) / count; float b = (sumY - m * sumX) / count;
// Predict value at futureIndex
return m * futureIndex + b; return m * futureIndex + b;
} }
+19 -93
View File
@@ -1,133 +1,59 @@
/**
* @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 #pragma once
#include "sdkconfig.h" #include "sdkconfig.h"
#include "esp_err.h"
#include <stddef.h> #include <stddef.h>
/** @brief Returns the maximum of two values. */
#define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b))
/** @brief Initial value for measurements before first reading. */
#define INITIALISATION_VALUE 0.0f #define INITIALISATION_VALUE 0.0f
/** @brief Sample buffer size for 10-second average. */
#define AVG10S_SAMPLE_SIZE 10U #define AVG10S_SAMPLE_SIZE 10U
/** @brief Sample buffer size for 60-second average. */
#define AVG60S_SAMPLE_SIZE 60U #define AVG60S_SAMPLE_SIZE 60U
/** @brief Sample buffer size for 24-hour average. */
#define AVG24H_SAMPLE_SIZE 24U #define AVG24H_SAMPLE_SIZE 24U
/** @brief Sample buffer size for 60-second prediction. */
#define PRED60S_SAMPLE_SIZE 60U #define PRED60S_SAMPLE_SIZE 60U
/** @brief Damping factor for rising temperatures (from Kconfig). */
#define DAMPING_FACTOR_WARMER (CONFIG_DAMPING_FACTOR_WARMER * 0.00001f) #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) #define DAMPING_FACTOR_COLDER (CONFIG_DAMPING_FACTOR_COLDER * 0.00001f)
/**
* @brief Burner error state enumeration.
*/
typedef enum _BurnerErrorState typedef enum _BurnerErrorState
{ {
NO_ERROR, /**< No burner fault detected. */ NO_ERROR,
FAULT /**< Burner fault signal active. */ FAULT
} eBurnerErrorState; } eBurnerErrorState;
/**
* @brief Measurement error state enumeration.
*/
typedef enum _MeasurementErrorState typedef enum _MeasurementErrorState
{ {
MEASUREMENT_NO_ERROR, /**< Measurement valid. */ MEASUREMENT_NO_ERROR,
MEASUREMENT_FAULT /**< Measurement failed or sensor not found. */ MEASUREMENT_FAULT
} eMeasurementErrorState; } eMeasurementErrorState;
/**
* @brief Circular buffer for averaging temperature values.
*/
typedef struct _Average typedef struct _Average
{ {
float fValue; /**< Current average value. */ float fValue;
float samples[MAX(AVG10S_SAMPLE_SIZE, MAX(AVG60S_SAMPLE_SIZE, AVG24H_SAMPLE_SIZE))]; /**< Sample buffer. */ float samples[MAX(AVG10S_SAMPLE_SIZE, MAX(AVG60S_SAMPLE_SIZE, AVG24H_SAMPLE_SIZE))];
size_t bufferIndex; /**< Current write index. */ size_t bufferIndex;
size_t bufferCount; /**< Number of valid samples. */ size_t bufferCount;
} sAverage; } sAverage;
/**
* @brief Circular buffer for temperature prediction.
*/
typedef struct _Predict typedef struct _Predict
{ {
float fValue; /**< Predicted value. */ float fValue;
float samples[PRED60S_SAMPLE_SIZE]; /**< Sample buffer. */ float samples[PRED60S_SAMPLE_SIZE];
size_t bufferIndex; /**< Current write index. */ size_t bufferIndex;
size_t bufferCount; /**< Number of valid samples. */ size_t bufferCount;
} sPredict; } sPredict;
/**
* @brief Complete measurement data structure.
*/
typedef struct _Measurement typedef struct _Measurement
{ {
float fCurrentValue; /**< Current raw temperature value. */ float fCurrentValue;
float fDampedValue; /**< Damped temperature value. */ float fDampedValue;
sAverage average10s; /**< 10-second rolling average. */ sAverage average10s;
sAverage average60s; /**< 60-second rolling average. */ sAverage average60s;
sPredict predict60s; /**< 60-second prediction. */ sPredict predict60s;
eMeasurementErrorState state; /**< Measurement state. */ eMeasurementErrorState state;
} sMeasurement; } 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); sMeasurement getChamberTemperature(void);
/**
* @brief Get the current outdoor temperature measurement.
* @return sMeasurement structure with current values and state.
*/
sMeasurement getOutdoorTemperature(void); sMeasurement getOutdoorTemperature(void);
/**
* @brief Get the current inlet flow temperature measurement.
* @return sMeasurement structure with current values and state.
*/
sMeasurement getInletFlowTemperature(void); sMeasurement getInletFlowTemperature(void);
/**
* @brief Get the current return flow temperature measurement.
* @return sMeasurement structure with current values and state.
*/
sMeasurement getReturnFlowTemperature(void); sMeasurement getReturnFlowTemperature(void);
/**
* @brief Get the current burner error state.
* @return eBurnerErrorState indicating fault status.
*/
eBurnerErrorState getBurnerError(void); eBurnerErrorState getBurnerError(void);
+12 -97
View File
@@ -1,11 +1,3 @@
/**
* @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 "safety.h"
#include "metrics.h" #include "metrics.h"
#include "outputs.h" #include "outputs.h"
@@ -17,107 +9,30 @@
#include "esp_log.h" #include "esp_log.h"
#include "esp_system.h" #include "esp_system.h"
#include "nvs_flash.h" #include "nvs_flash.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "main"; static const char *TAG = "smart-oil-heater-control-system";
/** @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) void app_main(void)
{ {
ESP_LOGI(TAG, "========================================"); ESP_LOGI(TAG, "starting ...");
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(); esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) 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()); ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init(); ret = nvs_flash_init();
} }
if (ret != ESP_OK) ESP_ERROR_CHECK(ret);
{
ESP_LOGE(TAG, "NVS init failed: %s", esp_err_to_name(ret));
reboot_on_error("NVS");
}
ESP_LOGI(TAG, "[OK] NVS initialized");
/* Initialize Outputs */ // TODO: Error handling!
if (initOutputs() != ESP_OK) initOutputs();
{ initInputs();
reboot_on_error("Outputs"); initSafety();
} initWifi();
ESP_LOGI(TAG, "[OK] Outputs initialized"); initSntp();
initControl();
/* Initialize Inputs */ initMetrics();
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) while (1)
{ {
+27 -51
View File
@@ -1,8 +1,3 @@
/**
* @file metrics.c
* @brief Implementation of Prometheus metrics endpoint.
*/
#include "metrics.h" #include "metrics.h"
#include "outputs.h" #include "outputs.h"
#include "inputs.h" #include "inputs.h"
@@ -20,50 +15,41 @@
#include <time.h> #include <time.h>
#include <sys/time.h> #include <sys/time.h>
static const char *TAG = "metrics"; static const char *TAG = "smart-oil-heater-control-system-metrics";
static char caHtmlResponse[HTML_RESPONSE_SIZE]; char caHtmlResponse[HTML_RESPONSE_SIZE];
static SemaphoreHandle_t xMutexAccessMetricResponse = NULL; SemaphoreHandle_t xMutexAccessMetricResponse = NULL;
static sMetric aMetrics[METRIC_MAX_COUNT]; static sMetric aMetrics[METRIC_MAX_COUNT];
static uint16_t u16MetricCounter = 0U; static uint16_t u16MetricCounter = 0U;
/* Private function prototypes */ void taskMetrics(void *pvParameters);
static void taskMetrics(void *pvParameters); httpd_handle_t setup_server(void);
static httpd_handle_t setup_server(void); esp_err_t get_metrics_handler(httpd_req_t *req);
static esp_err_t get_metrics_handler(httpd_req_t *req);
esp_err_t initMetrics(void) void initMetrics(void)
{ {
httpd_handle_t server = setup_server(); setup_server();
if (server == NULL)
{
ESP_LOGE(TAG, "Failed to start HTTP server");
return ESP_FAIL;
}
BaseType_t taskCreated = xTaskCreate( BaseType_t taskCreated = xTaskCreate(
taskMetrics, taskMetrics, // Function to implement the task
"taskMetrics", "taskMetrics", // Task name
32768, 32768, // Stack size (in words, not bytes)
NULL, NULL, // Parameters to the task function (none in this case)
5, 5, // Task priority (higher number = higher priority)
NULL); NULL // Task handle (optional)
);
if (taskCreated != pdPASS) if (taskCreated == pdPASS)
{
ESP_LOGI(TAG, "Task created successfully!");
}
else
{ {
ESP_LOGE(TAG, "Failed to create task"); 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) while (1)
{ {
@@ -352,6 +338,8 @@ void vSetMetrics(sMetric *paMetrics, uint16_t u16Size)
break; break;
} }
// printf("%s\n", paMetrics[u16Index].caMetricName);
// printf("%s\n", caValueBuffer);
strcat(caHtmlResponse, paMetrics[u16Index].caMetricName); strcat(caHtmlResponse, paMetrics[u16Index].caMetricName);
strcat(caHtmlResponse, caValueBuffer); strcat(caHtmlResponse, caValueBuffer);
strcat(caHtmlResponse, "\n"); strcat(caHtmlResponse, "\n");
@@ -364,12 +352,7 @@ 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) if (xSemaphoreTakeRecursive(xMutexAccessMetricResponse, pdMS_TO_TICKS(5000)) == pdTRUE)
{ {
@@ -384,11 +367,7 @@ static 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(); httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = 9100; config.server_port = 9100;
@@ -403,17 +382,14 @@ static httpd_handle_t setup_server(void)
xMutexAccessMetricResponse = xSemaphoreCreateRecursiveMutex(); xMutexAccessMetricResponse = xSemaphoreCreateRecursiveMutex();
if (xMutexAccessMetricResponse == NULL) if (xMutexAccessMetricResponse == NULL)
{ {
ESP_LOGE(TAG, "Failed to create mutex"); ESP_LOGE(TAG, "Unable to create mutex for metric response");
return NULL;
} }
xSemaphoreGiveRecursive(xMutexAccessMetricResponse); xSemaphoreGiveRecursive(xMutexAccessMetricResponse);
if (httpd_start(&server, &config) == ESP_OK) if (httpd_start(&server, &config) == ESP_OK)
{ {
httpd_register_uri_handler(server, &uri_get); httpd_register_uri_handler(server, &uri_get);
return server;
} }
ESP_LOGE(TAG, "Failed to start HTTP server"); return server;
return NULL;
} }
+10 -45
View File
@@ -1,61 +1,26 @@
/**
* @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 #pragma once
#include "esp_err.h" #include <esp_http_server.h>
#include "esp_http_server.h"
#include <stdint.h>
/** @brief Maximum size of HTTP response buffer. */
#define HTML_RESPONSE_SIZE 4096U #define HTML_RESPONSE_SIZE 4096U
/** @brief Maximum length of metric name. */
#define METRIC_NAME_MAX_SIZE 64U #define METRIC_NAME_MAX_SIZE 64U
/** @brief Maximum number of metrics. */
#define METRIC_MAX_COUNT 38U #define METRIC_MAX_COUNT 38U
/**
* @brief Metric value type enumeration.
*/
typedef enum _MetricValueType typedef enum _MetricValueType
{ {
FLOAT, /**< Floating point value. */ FLOAT,
INTEGER_U8, /**< 8-bit unsigned integer. */ INTEGER_U8,
INTEGER_64, /**< 64-bit signed integer. */ INTEGER_64,
} eMetricValueType; } eMetricValueType;
/**
* @brief Metric data structure.
*/
typedef struct _metric typedef struct _metric
{ {
char caMetricName[METRIC_NAME_MAX_SIZE]; /**< Metric name. */ char caMetricName[METRIC_NAME_MAX_SIZE];
eMetricValueType type; /**< Value type. */ eMetricValueType type;
float fMetricValue; /**< Float value (if type is FLOAT). */ float fMetricValue;
uint8_t u8MetricValue; /**< U8 value (if type is INTEGER_U8). */ uint8_t u8MetricValue;
int64_t i64MetricValue; /**< I64 value (if type is INTEGER_64). */ int64_t i64MetricValue;
} sMetric; } sMetric;
/** void initMetrics(void);
* @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); void vSetMetrics(sMetric *paMetrics, uint16_t u16Size);
+30 -42
View File
@@ -1,8 +1,3 @@
/**
* @file outputs.c
* @brief Implementation of output control module.
*/
#include "outputs.h" #include "outputs.h"
#include "sdkconfig.h" #include "sdkconfig.h"
@@ -11,76 +6,69 @@
#include "driver/gpio.h" #include "driver/gpio.h"
#include "esp_log.h" #include "esp_log.h"
static const char *TAG = "outputs"; static const char *TAG = "smart-oil-heater-control-system-outputs";
const uint8_t uCirculationPumpGpioPin = CONFIG_GPIO_CIRCULATION_PUMP;
/** @brief Circulation pump GPIO pin (from Kconfig). */ const uint8_t uBurnerGpioPin = CONFIG_GPIO_BURNER;
static const uint8_t uCirculationPumpGpioPin = CONFIG_GPIO_CIRCULATION_PUMP; const uint8_t uSafetyContactGpioPin = CONFIG_GPIO_SAFETY_CONTACT;
/** @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 SemaphoreHandle_t xMutexAccessOutputs = NULL;
static eOutput sCirculationPumpState; static eOutput sCirculationPumpState;
static eOutput sBurnerState; static eOutput sBurnerState;
static eOutput sSafetyContactState; static eOutput sSafetyContactState;
esp_err_t initOutputs(void) void initOutputs(void)
{ {
gpio_config_t ioConfCirculationPump = { gpio_config_t ioConfCirculationPump = {
.pin_bit_mask = (1ULL << uCirculationPumpGpioPin), .pin_bit_mask = (1ULL << uCirculationPumpGpioPin), // Pin mask
.mode = GPIO_MODE_OUTPUT, .mode = GPIO_MODE_OUTPUT, // Set as output
.pull_up_en = GPIO_PULLUP_DISABLE, .pull_up_en = GPIO_PULLUP_DISABLE, // Disable pull-up
.pull_down_en = GPIO_PULLDOWN_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down
.intr_type = GPIO_INTR_DISABLE}; .intr_type = GPIO_INTR_DISABLE // Disable interrupts
};
gpio_config_t ioConfBurner = { gpio_config_t ioConfBurner = {
.pin_bit_mask = (1ULL << uBurnerGpioPin), .pin_bit_mask = (1ULL << uBurnerGpioPin), // Pin mask
.mode = GPIO_MODE_OUTPUT, .mode = GPIO_MODE_OUTPUT, // Set as output
.pull_up_en = GPIO_PULLUP_DISABLE, .pull_up_en = GPIO_PULLUP_DISABLE, // Disable pull-up
.pull_down_en = GPIO_PULLDOWN_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down
.intr_type = GPIO_INTR_DISABLE}; .intr_type = GPIO_INTR_DISABLE // Disable interrupts
};
gpio_config_t ioConfSafetyContact = { gpio_config_t ioConfSafetyContact = {
.pin_bit_mask = (1ULL << uSafetyContactGpioPin), .pin_bit_mask = (1ULL << uSafetyContactGpioPin), // Pin mask
.mode = GPIO_MODE_OUTPUT, .mode = GPIO_MODE_OUTPUT, // Set as output
.pull_up_en = GPIO_PULLUP_DISABLE, .pull_up_en = GPIO_PULLUP_DISABLE, // Disable pull-up
.pull_down_en = GPIO_PULLDOWN_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down
.intr_type = GPIO_INTR_DISABLE}; .intr_type = GPIO_INTR_DISABLE // Disable interrupts
};
esp_err_t ret = gpio_config(&ioConfCirculationPump); esp_err_t ret = gpio_config(&ioConfCirculationPump);
if (ret != ESP_OK) if (ret != ESP_OK)
{ {
ESP_LOGE(TAG, "GPIO config failed for circulation pump: %s", esp_err_to_name(ret)); ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret));
return ESP_FAIL; return;
} }
ret = gpio_config(&ioConfBurner); ret = gpio_config(&ioConfBurner);
if (ret != ESP_OK) if (ret != ESP_OK)
{ {
ESP_LOGE(TAG, "GPIO config failed for burner: %s", esp_err_to_name(ret)); ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret));
return ESP_FAIL; return;
} }
ret = gpio_config(&ioConfSafetyContact); ret = gpio_config(&ioConfSafetyContact);
if (ret != ESP_OK) if (ret != ESP_OK)
{ {
ESP_LOGE(TAG, "GPIO config failed for safety contact: %s", esp_err_to_name(ret)); ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret));
return ESP_FAIL; return;
} }
xMutexAccessOutputs = xSemaphoreCreateRecursiveMutex(); xMutexAccessOutputs = xSemaphoreCreateRecursiveMutex();
if (xMutexAccessOutputs == NULL) if (xMutexAccessOutputs == NULL)
{ {
ESP_LOGE(TAG, "Failed to create mutex"); ESP_LOGE(TAG, "Unable to create mutex");
return ESP_FAIL;
} }
xSemaphoreGiveRecursive(xMutexAccessOutputs); xSemaphoreGiveRecursive(xMutexAccessOutputs);
ESP_LOGI(TAG, "Initialized successfully");
return ESP_OK;
} }
eOutput getCirculationPumpState(void) eOutput getCirculationPumpState(void)
+3 -58
View File
@@ -1,70 +1,15 @@
/**
* @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 #pragma once
#include "esp_err.h"
/**
* @brief Output state enumeration.
*/
typedef enum _Output typedef enum _Output
{ {
ENABLED, /**< Output active (relay energized, GPIO low). */ ENABLED,
DISABLED /**< Output inactive (relay de-energized, GPIO high). */ DISABLED
} eOutput; } 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); eOutput getCirculationPumpState(void);
/**
* @brief Set the circulation pump state.
* @param in Desired state (ENABLED or DISABLED).
*/
void setCirculationPumpState(eOutput in); void setCirculationPumpState(eOutput in);
/**
* @brief Get the current burner state.
* @return eOutput state (ENABLED or DISABLED).
*/
eOutput getBurnerState(void); eOutput getBurnerState(void);
/**
* @brief Set the burner state.
* @param in Desired state (ENABLED or DISABLED).
*/
void setBurnerState(eOutput in); void setBurnerState(eOutput in);
/**
* @brief Get the current safety contact state.
* @return eOutput state (ENABLED or DISABLED).
*/
eOutput getSafetyControlState(void); eOutput getSafetyControlState(void);
/**
* @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); void setSafetyControlState(eOutput in);
+25 -49
View File
@@ -1,8 +1,3 @@
/**
* @file safety.c
* @brief Implementation of safety monitoring module.
*/
#include "safety.h" #include "safety.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
@@ -12,20 +7,11 @@
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
/** @brief Task interval in seconds. */ #define PERIODIC_INTERVAL 1U // run safety checks every 1sec
#define PERIODIC_INTERVAL 1U #define SENSOR_GRACE_PERIOD (CONFIG_SENSOR_GRACE_PERIOD_MINUTES * 60U) // period that a sensor can report the same reading in seconds
/** @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 #define FLOAT_EPSILON 0.0001f
static const char *TAG = "smart-oil-heater-control-system-safety";
static const char *TAG = "safety";
static SemaphoreHandle_t xMutexAccessSafety = NULL; static SemaphoreHandle_t xMutexAccessSafety = NULL;
/** @brief Sensor sanity check configurations. */
static sSensorSanityCheck sanityChecks[NUMBER_OF_SENSOR_SANITY_CHECKS] = { static sSensorSanityCheck sanityChecks[NUMBER_OF_SENSOR_SANITY_CHECKS] = {
{SENSOR_NO_ERROR, "chamber_temperature", {SENSOR_LIMIT_CHAMBER_MAX, SENSOR_LIMIT_CHAMBER_MIN}, 0.0f, 0U, getChamberTemperature}, {SENSOR_NO_ERROR, "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, "outdoor_temperature", {SENSOR_LIMIT_OUTDOOR_MAX, SENSOR_LIMIT_OUTDOOR_MIN}, 0.0f, 0U, getOutdoorTemperature},
@@ -33,46 +19,41 @@ static sSensorSanityCheck sanityChecks[NUMBER_OF_SENSOR_SANITY_CHECKS] = {
{SENSOR_NO_ERROR, "return_flow_temperature", {SENSOR_LIMIT_RETURN_MAX, SENSOR_LIMIT_RETURN_MIN}, 0.0f, 0U, getReturnFlowTemperature}}; {SENSOR_NO_ERROR, "return_flow_temperature", {SENSOR_LIMIT_RETURN_MAX, SENSOR_LIMIT_RETURN_MIN}, 0.0f, 0U, getReturnFlowTemperature}};
static eSafetyState sSafetyState = SAFETY_NO_ERROR; static eSafetyState sSafetyState = SAFETY_NO_ERROR;
/* Private function prototypes */ void taskSafety(void *pvParameters);
static void taskSafety(void *pvParameters); void checkSensorSanity(void);
static void checkSensorSanity(void); void setSafeState(void);
static void setSafeState(void);
esp_err_t initSafety(void) void initSafety(void)
{ {
xMutexAccessSafety = xSemaphoreCreateRecursiveMutex(); xMutexAccessSafety = xSemaphoreCreateRecursiveMutex();
if (xMutexAccessSafety == NULL) if (xMutexAccessSafety == NULL)
{ {
ESP_LOGE(TAG, "Failed to create mutex"); ESP_LOGE(TAG, "Unable to create mutex");
return ESP_FAIL;
} }
xSemaphoreGiveRecursive(xMutexAccessSafety); xSemaphoreGiveRecursive(xMutexAccessSafety);
BaseType_t taskCreated = xTaskCreate( BaseType_t taskCreated = xTaskCreate(
taskSafety, taskSafety, // Function to implement the task
"taskSafety", "taskSafety", // Task name
4096, 4096, // Stack size (in words, not bytes)
NULL, NULL, // Parameters to the task function (none in this case)
5, 5, // Task priority (higher number = higher priority)
NULL); NULL // Task handle (optional)
);
if (taskCreated != pdPASS) if (taskCreated == pdPASS)
{
ESP_LOGI(TAG, "Task created successfully!");
}
else
{ {
ESP_LOGE(TAG, "Failed to create task"); ESP_LOGE(TAG, "Failed to create task");
return ESP_FAIL;
} }
setSafeState(); setSafeState(); // Set inital state
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) while (1)
{ {
@@ -80,6 +61,7 @@ static void taskSafety(void *pvParameters)
if (xSemaphoreTakeRecursive(xMutexAccessSafety, portMAX_DELAY) == pdTRUE) if (xSemaphoreTakeRecursive(xMutexAccessSafety, portMAX_DELAY) == pdTRUE)
{ {
checkSensorSanity(); checkSensorSanity();
if (sSafetyState != SAFETY_NO_ERROR) if (sSafetyState != SAFETY_NO_ERROR)
@@ -92,10 +74,7 @@ static void taskSafety(void *pvParameters)
} }
} }
/** void checkSensorSanity(void)
* @brief Check all sensor readings for sanity.
*/
static void checkSensorSanity(void)
{ {
sSafetyState = SAFETY_NO_ERROR; sSafetyState = SAFETY_NO_ERROR;
for (int i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++) for (int i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++)
@@ -151,10 +130,7 @@ static void checkSensorSanity(void)
} }
} }
/** void setSafeState(void)
* @brief Set system to safe state (burner off, pump on).
*/
static void setSafeState(void)
{ {
setCirculationPumpState(ENABLED); // To cool down system setCirculationPumpState(ENABLED); // To cool down system
setBurnerState(DISABLED); // Deactivate burner setBurnerState(DISABLED); // Deactivate burner
+17 -78
View File
@@ -1,117 +1,56 @@
/**
* @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 #pragma once
#include "outputs.h" #include "outputs.h"
#include "inputs.h" #include "inputs.h"
#include "sdkconfig.h" #include "sdkconfig.h"
#include "esp_err.h"
#include <stdint.h> #include <stdint.h>
/** @brief Maximum length of sensor name string. */
#define MAX_ERROR_STRING_SIZE 64U #define MAX_ERROR_STRING_SIZE 64U
/** @brief Number of sensors to monitor. */
#define NUMBER_OF_SENSOR_SANITY_CHECKS 4U #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) #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) #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) #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) #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) #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) #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) #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) #define SENSOR_LIMIT_RETURN_MIN (CONFIG_SENSOR_LIMIT_RETURN_MIN / 10.0f)
/**
* @brief Sensor error state enumeration.
*/
typedef enum _SensorErrorState typedef enum _SensorErrorState
{ {
SENSOR_NO_ERROR, /**< Sensor operating normally. */ SENSOR_NO_ERROR,
SENSOR_TOO_HIGH, /**< Temperature above maximum limit. */ SENSOR_TOO_HIGH,
SENSOR_TOO_LOW, /**< Temperature below minimum limit. */ SENSOR_TOO_LOW,
SENSOR_UNCHANGED, /**< Temperature unchanged for too long. */ SENSOR_UNCHANGED,
SENSOR_NOT_FOUND /**< Sensor not responding. */ SENSOR_NOT_FOUND
} eSensorErrorState; } eSensorErrorState;
/**
* @brief Overall safety state enumeration.
*/
typedef enum _SafetyState typedef enum _SafetyState
{ {
SAFETY_NO_ERROR, /**< All sensors OK. */ SAFETY_NO_ERROR,
SAFETY_SENSOR_ERROR, /**< At least one sensor failed. */ SAFETY_SENSOR_ERROR,
SAFETY_INTERNAL_ERROR /**< Internal module error. */ SAFETY_INTERNAL_ERROR
} eSafetyState; } eSafetyState;
/**
* @brief Function pointer type for sensor getter functions.
*/
typedef sMeasurement (*GetSensorValue)(); typedef sMeasurement (*GetSensorValue)();
/**
* @brief Temperature sensor limits.
*/
typedef struct _TemperatureSensorLimit typedef struct _TemperatureSensorLimit
{ {
float max; /**< Maximum temperature limit. */ float max; // Maximum temperature limit
float min; /**< Minimum temperature limit. */ float min; // Minimum temperature limit
} sTemperatureSensorLimit; } sTemperatureSensorLimit;
/**
* @brief Sensor sanity check state structure.
*/
typedef struct _SensorSanityCheck typedef struct _SensorSanityCheck
{ {
eSensorErrorState state; /**< Current error state. */ eSensorErrorState state;
char name[MAX_ERROR_STRING_SIZE]; /**< Sensor name for logging. */ char name[MAX_ERROR_STRING_SIZE];
sTemperatureSensorLimit sSensorLimit; /**< Temperature limits. */ sTemperatureSensorLimit sSensorLimit;
float fSensorTemperatureLast; /**< Last temperature reading. */ float fSensorTemperatureLast;
uint32_t uUnchangedCounter; /**< Counter for unchanged readings. */ uint32_t uUnchangedCounter;
GetSensorValue getSensor; /**< Function to get sensor value. */ GetSensorValue getSensor;
} sSensorSanityCheck; } 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); void getSensorSanityStates(sSensorSanityCheck *pSensorSanityChecks);
/**
* @brief Get the overall safety state.
* @return eSafetyState indicating current safety status.
*/
eSafetyState getSafetyState(void); eSafetyState getSafetyState(void);
+5 -19
View File
@@ -1,8 +1,3 @@
/**
* @file sntp.c
* @brief Implementation of SNTP client module.
*/
#include "sntp.h" #include "sntp.h"
#include "esp_sntp.h" #include "esp_sntp.h"
@@ -11,22 +6,17 @@
#include <time.h> #include <time.h>
#include <sys/time.h> #include <sys/time.h>
static const char *TAG = "sntp"; static const char *TAG = "smart-oil-heater-control-system-sntp";
static volatile eSntpState sntpState = SYNC_NOT_STARTED; static volatile eSntpState sntpState = SYNC_NOT_STARTED;
void time_sync_notification_cb(struct timeval *tv);
static void time_sync_notification_cb(struct timeval *tv); void initSntp(void)
esp_err_t initSntp(void)
{ {
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL); esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
esp_sntp_setservername(0, CONFIG_SNTP_SERVER_IP_ADDR); esp_sntp_setservername(0, CONFIG_SNTP_SERVER_IP_ADDR);
sntp_set_time_sync_notification_cb(time_sync_notification_cb); sntp_set_time_sync_notification_cb(time_sync_notification_cb);
esp_sntp_init(); esp_sntp_init();
ESP_LOGI(TAG, "Initialized successfully, server: %s", CONFIG_SNTP_SERVER_IP_ADDR);
return ESP_OK;
} }
eSntpState getSntpState(void) eSntpState getSntpState(void)
@@ -34,12 +24,8 @@ eSntpState getSntpState(void)
return sntpState; 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, "Time synchronized! Unix Time: %lld", tv->tv_sec); ESP_LOGI(TAG, "SNTP synchronization! Unix Time: %lld", tv->tv_sec);
sntpState = SYNC_SUCCESSFUL; sntpState = SYNC_SUCCESSFUL;
} }
+4 -30
View File
@@ -1,37 +1,11 @@
/**
* @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 #pragma once
#include "esp_err.h"
/**
* @brief SNTP synchronization state enumeration.
*/
typedef enum _SntpState typedef enum _SntpState
{ {
SYNC_SUCCESSFUL, /**< Time synchronized successfully. */ SYNC_SUCCESSFUL,
SYNC_NOT_STARTED, /**< Synchronization not yet attempted. */ SYNC_NOT_STARTED,
SYNC_FAILED, /**< Synchronization failed. */ SYNC_FAILED,
} eSntpState; } eSntpState;
/** void initSntp(void);
* @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); eSntpState getSntpState(void);
+10 -33
View File
@@ -1,8 +1,3 @@
/**
* @file wifi.c
* @brief Implementation of WiFi station mode module.
*/
#include "wifi.h" #include "wifi.h"
#include "esp_timer.h" #include "esp_timer.h"
@@ -17,19 +12,12 @@
#include <string.h> #include <string.h>
/** @brief Event bit for successful connection. */
#define WIFI_CONNECTED_BIT BIT0 #define WIFI_CONNECTED_BIT BIT0
/** @brief Event bit for connection failure. */
#define WIFI_FAIL_BIT BIT1 #define WIFI_FAIL_BIT BIT1
/** @brief Maximum connection retry attempts. */
#define MAX_RETRY_COUNT 10 #define MAX_RETRY_COUNT 10
/** @brief Delay between retries in milliseconds. */
#define RETRY_DELAY_MS 1000 #define RETRY_DELAY_MS 1000
static const char *TAG = "wifi"; static const char *TAG = "smart-oil-heater-control-system-wifi";
static EventGroupHandle_t s_wifi_event_group; static EventGroupHandle_t s_wifi_event_group;
static int s_retry_num = 0; static int s_retry_num = 0;
@@ -39,13 +27,13 @@ static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data); int32_t event_id, void *event_data);
static bool parse_bssid(const char *bssid_str, uint8_t *bssid); static bool parse_bssid(const char *bssid_str, uint8_t *bssid);
esp_err_t initWifi(void) void initWifi(void)
{ {
s_wifi_event_group = xEventGroupCreate(); s_wifi_event_group = xEventGroupCreate();
if (s_wifi_event_group == NULL) if (s_wifi_event_group == NULL)
{ {
ESP_LOGE(TAG, "Failed to create event group"); ESP_LOGE(TAG, "xEventGroupCreate() failed!");
return ESP_FAIL; return;
} }
ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_netif_init());
@@ -101,10 +89,10 @@ esp_err_t initWifi(void)
ESP_ERROR_CHECK(esp_wifi_start()); ESP_ERROR_CHECK(esp_wifi_start());
ESP_ERROR_CHECK(esp_wifi_set_max_tx_power(78)); 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)); ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_MIN_MODEM)); // Use power-saving mode
ESP_LOGI(TAG, "WiFi init finished, waiting for connection..."); ESP_LOGI(TAG, "wifi_init_sta finished.");
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
@@ -114,32 +102,21 @@ esp_err_t initWifi(void)
if (bits & WIFI_CONNECTED_BIT) 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) else if (bits & WIFI_FAIL_BIT)
{ {
ESP_LOGE(TAG, "Failed to connect to SSID:%s", CONFIG_SSID); ESP_LOGI(TAG, "Failed to connect to SSID:%s", CONFIG_SSID);
return ESP_FAIL;
} }
else else
{ {
ESP_LOGE(TAG, "Unexpected event"); ESP_LOGE(TAG, "Unexpected event");
return ESP_FAIL;
} }
// Mark initial connection phase complete - do NOT delete the event group
s_initial_connect = false; 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, static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) int32_t event_id, void *event_data)
{ {
+1 -20
View File
@@ -1,22 +1,3 @@
/**
* @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 #pragma once
#include "esp_err.h" void initWifi(void);
/**
* @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);