1 Commits

Author SHA256 Message Date
618a6974bf enable ap lock 2026-01-09 23:26:46 +01:00
12 changed files with 252 additions and 744 deletions

View File

@ -1,391 +1,36 @@
menu "Smart Oil Heating Control System" menu "Smart Oil Heating Control System"
menu "WiFi Configuration"
config SSID config SSID
string "WiFi SSID" string "SSID"
default "my WiFi SSID" default "my WiFi SSID"
help
The SSID of the WiFi network to connect to.
config WIFI_PASSWORD config WIFI_PASSWORD
string "WiFi Password" string "WIFI_PASSWORD"
default "my WIFI Password" default "my WIFI Password"
help
The password for the WiFi network.
config STATIC_IP_ADDR config STATIC_IP_ADDR
string "Static IPv4 address" string "Static IPv4 address"
default "192.168.0.42" default "192.168.0.42"
help
Static IP address for the ESP32.
config STATIC_IP_NETMASK config STATIC_IP_NETMASK
string "Static IPv4 netmask" string "Static IPv4 netmask"
default "255.255.0.0" default "255.255.0.0"
help
Network mask for the static IP configuration.
config STATIC_GATEWAY_IP_ADDR config STATIC_GATEWAY_IP_ADDR
string "Static IPv4 gateway address" string "Static IPv4 gateway address"
default "192.168.0.1" default "192.168.0.1"
help
Gateway IP address for network routing.
config SNTP_SERVER_IP_ADDR config SNTP_SERVER_IP_ADDR
string "SNTP server address" string "SNTP IPv4 server address"
default "192.168.0.1" default "192.168.0.1"
config ENV_WIFI_BSSID_LOCK
bool "Lock to specific Access Point (BSSID)"
default n
help help
NTP server address for time synchronization. When enabled, the device will only connect to the access point
endmenu with the specified MAC address (BSSID). Useful when multiple APs
share the same SSID.
menu "GPIO Configuration" config ENV_WIFI_BSSID
menu "Input GPIOs" string "Access Point MAC Address (BSSID)"
config GPIO_BURNER_FAULT default "00:00:00:00:00:00"
int "Burner fault input GPIO" depends on ENV_WIFI_BSSID_LOCK
range 0 39
default 19
help help
GPIO pin connected to the burner fault signal. MAC address of the access point to connect to.
Format: XX:XX:XX:XX:XX:XX (uppercase or lowercase)
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 endmenu

View File

@ -1,96 +1,99 @@
#include "control.h" #include "control.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "inputs.h" #include "inputs.h"
#include "outputs.h" #include "outputs.h"
#include "safety.h" #include "safety.h"
#include "sntp.h" #include "sntp.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define PERIODIC_INTERVAL 1U // Run control loop every 1 second #define PERIODIC_INTERVAL 1U // Run control loop every 1 second
// 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
static const char *TAG = "smart-oil-heater-control-system-control"; static const char *TAG = "smart-oil-heater-control-system-control";
static eControlState gControlState = CONTROL_STARTING; static eControlState sControlState = CONTROL_STARTING;
// Control table for daily schedules // Control table for daily schedules
static const sControlDay gControlTable[] = { static const sControlDay aControlTable[] = {
{MONDAY, {MONDAY,
2U, 2U,
{{{CONFIG_SCHEDULE_WEEKDAY_DAY_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_DAY_START_MINUTE}, {{{4, 45},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
CHAMBER_TEMPERATURE_TARGET}, CHAMBER_TEMPERATURE_TARGET},
{{CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_MINUTE}, {{22, 0},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}}, CHAMBER_TEMPERATURE_TARGET}}},
{TUESDAY, {TUESDAY,
2U, 2U,
{{{CONFIG_SCHEDULE_WEEKDAY_DAY_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_DAY_START_MINUTE}, {{{4, 45},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
CHAMBER_TEMPERATURE_TARGET}, CHAMBER_TEMPERATURE_TARGET},
{{CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_MINUTE}, {{22, 0},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}}, CHAMBER_TEMPERATURE_TARGET}}},
{WEDNESDAY, {WEDNESDAY,
2U, 2U,
{{{CONFIG_SCHEDULE_WEEKDAY_DAY_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_DAY_START_MINUTE}, {{{4, 45},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
CHAMBER_TEMPERATURE_TARGET}, CHAMBER_TEMPERATURE_TARGET},
{{CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_MINUTE}, {{22, 0},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}}, CHAMBER_TEMPERATURE_TARGET}}},
{THURSDAY, {THURSDAY,
2U, 2U,
{{{CONFIG_SCHEDULE_WEEKDAY_DAY_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_DAY_START_MINUTE}, {{{4, 45},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
CHAMBER_TEMPERATURE_TARGET}, CHAMBER_TEMPERATURE_TARGET},
{{CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_MINUTE}, {{22, 0},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}}, CHAMBER_TEMPERATURE_TARGET}}},
{FRIDAY, {FRIDAY,
2U, 2U,
{{{CONFIG_SCHEDULE_FRIDAY_DAY_START_HOUR, CONFIG_SCHEDULE_FRIDAY_DAY_START_MINUTE}, {{{4, 45},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
CHAMBER_TEMPERATURE_TARGET}, CHAMBER_TEMPERATURE_TARGET},
{{CONFIG_SCHEDULE_FRIDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_FRIDAY_NIGHT_START_MINUTE}, {{23, 0},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}}, CHAMBER_TEMPERATURE_TARGET}}},
{SATURDAY, {SATURDAY,
2U, 2U,
{{{CONFIG_SCHEDULE_SATURDAY_DAY_START_HOUR, CONFIG_SCHEDULE_SATURDAY_DAY_START_MINUTE}, {{{6, 45},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
CHAMBER_TEMPERATURE_TARGET}, CHAMBER_TEMPERATURE_TARGET},
{{CONFIG_SCHEDULE_SATURDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_SATURDAY_NIGHT_START_MINUTE}, {{23, 30},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}}, CHAMBER_TEMPERATURE_TARGET}}},
{SUNDAY, {SUNDAY,
2U, 2U,
{{{CONFIG_SCHEDULE_SUNDAY_DAY_START_HOUR, CONFIG_SCHEDULE_SUNDAY_DAY_START_MINUTE}, {{{6, 45},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
CHAMBER_TEMPERATURE_TARGET}, CHAMBER_TEMPERATURE_TARGET},
{{CONFIG_SCHEDULE_SUNDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_SUNDAY_NIGHT_START_MINUTE}, {{22, 30},
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
CHAMBER_TEMPERATURE_TARGET}}}, CHAMBER_TEMPERATURE_TARGET}}},
}; };
static sControlTemperatureEntry gCurrentControlEntry = static sControlTemperatureEntry currentControlEntry =
gControlTable[0].aTemperatureEntries[0]; aControlTable[0].aTemperatureEntries[0];
static SemaphoreHandle_t xMutexAccessControl = NULL;
// Function prototypes // Function prototypes
void taskControl(void *pvParameters); void taskControl(void *pvParameters);
void findControlCurrentTemperatureEntry(void); void findControlCurrentTemperatureEntry(void);
void setControlState(eControlState state);
void initControl(void) void initControl(void)
{ {
xMutexAccessControl = xSemaphoreCreateRecursiveMutex();
if (xMutexAccessControl == NULL)
{
ESP_LOGE(TAG, "Unable to create mutex");
}
xSemaphoreGiveRecursive(xMutexAccessControl);
BaseType_t taskCreated = BaseType_t taskCreated =
xTaskCreate(taskControl, // Function to implement the task xTaskCreate(taskControl, // Function to implement the task
"taskControl", // Task name "taskControl", // Task name
@ -114,7 +117,7 @@ void taskControl(void *pvParameters)
{ {
bool bHeatingInAction = false; bool bHeatingInAction = false;
bool bSummerMode = false; bool bSummerMode = false;
eBurnerState burnerState = BURNER_UNKNOWN; eBurnerState eBurnerState = BURNER_UNKNOWN;
int64_t i64BurnerEnableTimestamp = esp_timer_get_time(); int64_t i64BurnerEnableTimestamp = esp_timer_get_time();
while (1) while (1)
@ -125,7 +128,7 @@ void taskControl(void *pvParameters)
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!");
setControlState(CONTROL_FAULT_SAFETY); sControlState = CONTROL_FAULT_SAFETY;
if (bHeatingInAction) if (bHeatingInAction)
{ {
ESP_LOGW(TAG, "Disabling burner due to safety fault"); ESP_LOGW(TAG, "Disabling burner due to safety fault");
@ -140,7 +143,7 @@ void taskControl(void *pvParameters)
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!");
setControlState(CONTROL_FAULT_SNTP); sControlState = CONTROL_FAULT_SNTP;
if (bHeatingInAction) if (bHeatingInAction)
{ {
ESP_LOGW(TAG, "Disabling burner due to SNTP fault"); ESP_LOGW(TAG, "Disabling burner due to SNTP fault");
@ -152,6 +155,8 @@ void taskControl(void *pvParameters)
} }
findControlCurrentTemperatureEntry(); findControlCurrentTemperatureEntry();
sControlTemperatureEntry currentControlEntry =
getControlCurrentTemperatureEntry();
if (getOutdoorTemperature().fDampedValue >= if (getOutdoorTemperature().fDampedValue >=
SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH) SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH)
@ -166,33 +171,33 @@ void taskControl(void *pvParameters)
// Enable burner if outdoor temperature is low and return flow temperature // Enable burner if outdoor temperature is low and return flow temperature
// is cooled down // is cooled down
if (!bHeatingInAction && (burnerState != BURNER_FAULT)) if (!bHeatingInAction && (eBurnerState != BURNER_FAULT))
{ {
if (bSummerMode) if (bSummerMode)
{ {
// ESP_LOGI(TAG, "Outdoor temperature too warm: Disabling heating"); // ESP_LOGI(TAG, "Outdoor temperature too warm: Disabling heating");
setBurnerState(DISABLED); setBurnerState(DISABLED);
setSafetyControlState(DISABLED); setSafetyControlState(DISABLED);
setControlState(CONTROL_OUTDOOR_TOO_WARM); sControlState = CONTROL_OUTDOOR_TOO_WARM;
} }
else if ((getReturnFlowTemperature().average60s.fValue <= else if ((getReturnFlowTemperature().average60s.fValue <=
getControlCurrentTemperatureEntry().fReturnFlowTemperature) && currentControlEntry.fReturnFlowTemperature) &&
(getChamberTemperature().fCurrentValue <= (getChamberTemperature().fCurrentValue <=
CHAMBER_TEMPERATURE_THRESHOLD)) CHAMBER_TEMPERATURE_THRESHOLD))
{ {
ESP_LOGI(TAG, ESP_LOGI(TAG,
"Enabling burner: Return flow temperature target reached"); "Enabling burner: Return flow temperature target reached");
burnerState = BURNER_UNKNOWN; eBurnerState = BURNER_UNKNOWN;
bHeatingInAction = true; bHeatingInAction = true;
setBurnerState(ENABLED); setBurnerState(ENABLED);
setSafetyControlState(ENABLED); setSafetyControlState(ENABLED);
i64BurnerEnableTimestamp = esp_timer_get_time(); i64BurnerEnableTimestamp = esp_timer_get_time();
setControlState(CONTROL_HEATING); sControlState = CONTROL_HEATING;
} }
else else
{ {
// ESP_LOGI(TAG, "Return flow temperature too warm: Disabling heating"); // ESP_LOGI(TAG, "Return flow temperature too warm: Disabling heating");
setControlState(CONTROL_RETURN_FLOW_TOO_WARM); sControlState = CONTROL_RETURN_FLOW_TOO_WARM;
} }
} }
@ -200,9 +205,9 @@ void taskControl(void *pvParameters)
if (bHeatingInAction) if (bHeatingInAction)
{ {
if ((getChamberTemperature().fCurrentValue >= if ((getChamberTemperature().fCurrentValue >=
getControlCurrentTemperatureEntry().fChamberTemperature) || currentControlEntry.fChamberTemperature) ||
(getChamberTemperature().predict60s.fValue >= (getChamberTemperature().predict60s.fValue >=
getControlCurrentTemperatureEntry().fChamberTemperature)) currentControlEntry.fChamberTemperature))
{ {
ESP_LOGI(TAG, "Chamber target temperature reached: Disabling burner"); ESP_LOGI(TAG, "Chamber target temperature reached: Disabling burner");
bHeatingInAction = false; bHeatingInAction = false;
@ -212,14 +217,14 @@ void taskControl(void *pvParameters)
else if (esp_timer_get_time() - i64BurnerEnableTimestamp >= else if (esp_timer_get_time() - i64BurnerEnableTimestamp >=
BURNER_FAULT_DETECTION_THRESHOLD * 1000000U) BURNER_FAULT_DETECTION_THRESHOLD * 1000000U)
{ {
if (burnerState == BURNER_UNKNOWN) if (eBurnerState == BURNER_UNKNOWN)
{ {
if (getBurnerError() == FAULT) if (getBurnerError() == FAULT)
{ {
// ESP_LOGW(TAG, "Burner fault detected: Disabling burner"); // ESP_LOGW(TAG, "Burner fault detected: Disabling burner");
bHeatingInAction = false; bHeatingInAction = false;
burnerState = BURNER_FAULT; eBurnerState = BURNER_FAULT;
setControlState(CONTROL_FAULT_BURNER); sControlState = CONTROL_FAULT_BURNER;
setBurnerState(DISABLED); setBurnerState(DISABLED);
setSafetyControlState(ENABLED); setSafetyControlState(ENABLED);
} }
@ -227,7 +232,7 @@ void taskControl(void *pvParameters)
{ {
// ESP_LOGI(TAG, "No burner fault detected: Marking burner as // ESP_LOGI(TAG, "No burner fault detected: Marking burner as
// fired"); // fired");
burnerState = BURNER_FIRED; eBurnerState = BURNER_FIRED;
} }
} }
} }
@ -248,47 +253,17 @@ void taskControl(void *pvParameters)
} // End of while(1) } // End of while(1)
} }
void setControlState(eControlState state) eControlState getControlState(void) { return sControlState; }
{
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) eControlWeekday getControlCurrentWeekday(void)
{ {
// Get current time
time_t now; time_t now;
struct tm timeinfo; struct tm *timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
int day = timeinfo.tm_wday; time(&now);
timeinfo = localtime(&now);
int day = timeinfo->tm_wday;
return (eControlWeekday)((day == 0) ? 6 : day - 1); return (eControlWeekday)((day == 0) ? 6 : day - 1);
} }
@ -319,15 +294,12 @@ void findControlCurrentTemperatureEntry(void)
int currentHour = timeinfo.tm_hour; int currentHour = timeinfo.tm_hour;
int currentMinute = timeinfo.tm_min; int currentMinute = timeinfo.tm_min;
if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE)
{
// ESP_LOGI(TAG, "Searching for control entry - Day: %d, Time: %02d:%02d", currentDay, currentHour, currentMinute); // ESP_LOGI(TAG, "Searching for control entry - Day: %d, Time: %02d:%02d", currentDay, currentHour, currentMinute);
// Search through all days and entries // Search through all days and entries
for (int dayIndex = 0; dayIndex < 7; dayIndex++) for (int dayIndex = 0; dayIndex < 7; dayIndex++)
{ {
const sControlDay *day = &gControlTable[dayIndex]; const sControlDay *day = &aControlTable[dayIndex];
for (int entryIndex = 0; entryIndex < day->entryCount; entryIndex++) for (int entryIndex = 0; entryIndex < day->entryCount; entryIndex++)
{ {
@ -342,32 +314,31 @@ void findControlCurrentTemperatureEntry(void)
if (isFutureDay || isTodayFutureTime) if (isFutureDay || isTodayFutureTime)
{ {
// Found next scheduled entry, so determine the previous (active) one // Found next scheduled entry, so determine the previous (active) one
if (entryIndex > 0) if (entryIndex > 0)
{ {
// Use previous entry from same day // Use previous entry from same day
gCurrentControlEntry = day->aTemperatureEntries[entryIndex - 1]; currentControlEntry = day->aTemperatureEntries[entryIndex - 1];
} }
else if (dayIndex > 0) else if (dayIndex > 0)
{ {
// Use last entry from previous day // Use last entry from previous day
const sControlDay *previousDay = &gControlTable[dayIndex - 1]; const sControlDay *previousDay = &aControlTable[dayIndex - 1];
gCurrentControlEntry = previousDay->aTemperatureEntries[previousDay->entryCount - 1]; currentControlEntry = previousDay->aTemperatureEntries[previousDay->entryCount - 1];
} }
else else
{ {
// First entry of the week - wrap to last entry of Sunday // First entry of the week - wrap to last entry of Sunday
const sControlDay *sunday = &gControlTable[6]; const sControlDay *sunday = &aControlTable[6];
gCurrentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1]; currentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1];
} }
/* /*
ESP_LOGI(TAG, "Active entry found - Time: %02d:%02d, " ESP_LOGI(TAG, "Active entry found - Time: %02d:%02d, "
"Return Temp: %lf, Chamber Temp: %lf", "Return Temp: %lf, Chamber Temp: %lf",
gCurrentControlEntry.timestamp.hour, currentControlEntry.timestamp.hour,
gCurrentControlEntry.timestamp.minute, currentControlEntry.timestamp.minute,
gCurrentControlEntry.fReturnFlowTemperature, currentControlEntry.fReturnFlowTemperature,
gCurrentControlEntry.fChamberTemperature); currentControlEntry.fChamberTemperature);
*/ */
return; return;
} }
@ -376,30 +347,13 @@ void findControlCurrentTemperatureEntry(void)
// If we reached here, current time is after all entries this week // If we reached here, current time is after all entries this week
// Use the last entry (Sunday evening) // Use the last entry (Sunday evening)
const sControlDay *sunday = &gControlTable[6]; const sControlDay *sunday = &aControlTable[6];
gCurrentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1]; currentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1];
// ESP_LOGI(TAG, "Using last entry of week - Time: %02d:%02d", gCurrentControlEntry.timestamp.hour, gCurrentControlEntry.timestamp.minute); // ESP_LOGI(TAG, "Using last entry of week - Time: %02d:%02d", currentControlEntry.timestamp.hour, currentControlEntry.timestamp.minute);
xSemaphoreGiveRecursive(xMutexAccessControl);
}
else
{
ESP_LOGE(TAG, "Unable to take mutex: findControlCurrentTemperatureEntry()");
}
} }
sControlTemperatureEntry getControlCurrentTemperatureEntry(void) sControlTemperatureEntry getControlCurrentTemperatureEntry(void)
{ {
sControlTemperatureEntry ret = gControlTable[0].aTemperatureEntries[0]; return currentControlEntry;
if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE)
{
ret = gCurrentControlEntry;
xSemaphoreGiveRecursive(xMutexAccessControl);
}
else
{
ESP_LOGE(TAG, "Unable to take mutex: getControlCurrentTemperatureEntry()");
}
return ret;
} }

View File

@ -1,20 +1,8 @@
#pragma once #pragma once
#include "sdkconfig.h"
#include <time.h> #include <time.h>
#define MAX_TEMPERATURE_ENTRIES_PER_DAY 24U #define MAX_TEMPERATURE_ENTRIES_PER_DAY 24U
#define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY (CONFIG_TEMP_RETURN_FLOW_LOWER_LIMIT_DAY / 10.0f)
#define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT (CONFIG_TEMP_RETURN_FLOW_LOWER_LIMIT_NIGHT / 10.0f)
#define CHAMBER_TEMPERATURE_TARGET (CONFIG_TEMP_CHAMBER_TARGET / 10.0f)
#define CHAMBER_TEMPERATURE_THRESHOLD (CONFIG_TEMP_CHAMBER_THRESHOLD / 10.0f)
#define SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH (CONFIG_TEMP_SUMMER_MODE_HIGH / 10.0f)
#define SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW (CONFIG_TEMP_SUMMER_MODE_LOW / 10.0f)
#define CIRCULATION_PUMP_TEMPERATURE_THRESHOLD (CONFIG_TEMP_CIRCULATION_PUMP_THRESHOLD / 10.0f)
#define BURNER_FAULT_DETECTION_THRESHOLD CONFIG_BURNER_FAULT_DETECTION_SECONDS
typedef enum _ControlState typedef enum _ControlState
{ {
CONTROL_STARTING, CONTROL_STARTING,

View File

@ -1,26 +1,25 @@
#include "inputs.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "driver/gpio.h" #include "driver/gpio.h"
#include <string.h>
#include <math.h>
#include "esp_log.h" #include "esp_log.h"
#include <ds18x20.h> #include <ds18x20.h>
#include <string.h> #include "inputs.h"
#include <math.h>
#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 ONE_WIRE_LOOPS 4U // try to read the 1-Wire sensors that often
#define PERIODIC_INTERVAL 1U // read and compute the inputs every 1sec #define PERIODIC_INTERVAL 1U // read and compute the inputs every 1sec
static const char *TAG = "smart-oil-heater-control-system-inputs"; static const char *TAG = "smart-oil-heater-control-system-inputs";
const uint8_t uBurnerFaultPin = CONFIG_GPIO_BURNER_FAULT; const uint8_t uBurnerFaultPin = 19U;
const uint8_t uDS18B20Pin = CONFIG_GPIO_DS18B20_ONEWIRE; const uint8_t uDS18B20Pin = 4U;
const onewire_addr_t uChamperTempSensorAddr = CONFIG_ONEWIRE_ADDR_CHAMBER_TEMP; const onewire_addr_t uChamperTempSensorAddr = 0xd00000108cd01d28;
const onewire_addr_t uOutdoorTempSensorAddr = CONFIG_ONEWIRE_ADDR_OUTDOOR_TEMP; const onewire_addr_t uOutdoorTempSensorAddr = 0xd70000108a9b9128;
const onewire_addr_t uInletFlowTempSensorAddr = CONFIG_ONEWIRE_ADDR_INLET_FLOW_TEMP; const onewire_addr_t uInletFlowTempSensorAddr = 0x410000108b8c0628;
const onewire_addr_t uReturnFlowTempSensorAddr = CONFIG_ONEWIRE_ADDR_RETURN_FLOW_TEMP; const onewire_addr_t uReturnFlowTempSensorAddr = 0x90000108cc77c28;
onewire_addr_t uOneWireAddresses[MAX_DN18B20_SENSORS]; onewire_addr_t uOneWireAddresses[MAX_DN18B20_SENSORS];
float fDS18B20Temps[MAX_DN18B20_SENSORS]; float fDS18B20Temps[MAX_DN18B20_SENSORS];
@ -50,12 +49,7 @@ void initInputs(void)
.intr_type = GPIO_INTR_DISABLE // Disable interrupts .intr_type = GPIO_INTR_DISABLE // Disable interrupts
}; };
esp_err_t ret = gpio_config(&ioConfBurnerFault); gpio_config(&ioConfBurnerFault);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret));
return;
}
xMutexAccessInputs = xSemaphoreCreateRecursiveMutex(); xMutexAccessInputs = xSemaphoreCreateRecursiveMutex();
if (xMutexAccessInputs == NULL) if (xMutexAccessInputs == NULL)
@ -100,17 +94,17 @@ void initMeasurement(sMeasurement *pMeasurement)
pMeasurement->average10s.fValue = INITIALISATION_VALUE; pMeasurement->average10s.fValue = INITIALISATION_VALUE;
pMeasurement->average10s.bufferCount = 0U; pMeasurement->average10s.bufferCount = 0U;
pMeasurement->average10s.bufferIndex = 0U; pMeasurement->average10s.bufferIndex = 0U;
memset(pMeasurement->average10s.samples, 0U, sizeof(float) * AVG10S_SAMPLE_SIZE); memset(pMeasurement->average10s.samples, 0U, AVG10S_SAMPLE_SIZE);
pMeasurement->average60s.fValue = INITIALISATION_VALUE; pMeasurement->average60s.fValue = INITIALISATION_VALUE;
pMeasurement->average60s.bufferCount = 0U; pMeasurement->average60s.bufferCount = 0U;
pMeasurement->average60s.bufferIndex = 0U; pMeasurement->average60s.bufferIndex = 0U;
memset(pMeasurement->average60s.samples, 0U, sizeof(float) * AVG60S_SAMPLE_SIZE); memset(pMeasurement->average60s.samples, 0U, AVG60S_SAMPLE_SIZE);
pMeasurement->predict60s.fValue = INITIALISATION_VALUE; pMeasurement->predict60s.fValue = INITIALISATION_VALUE;
pMeasurement->predict60s.bufferCount = 0U; pMeasurement->predict60s.bufferCount = 0U;
pMeasurement->predict60s.bufferIndex = 0U; pMeasurement->predict60s.bufferIndex = 0U;
memset(pMeasurement->predict60s.samples, 0U, sizeof(float) * PRED60S_SAMPLE_SIZE); memset(pMeasurement->predict60s.samples, 0U, PRED60S_SAMPLE_SIZE);
} }
void updateAverage(sMeasurement *pMeasurement) void updateAverage(sMeasurement *pMeasurement)
@ -128,19 +122,12 @@ void updateAverage(sMeasurement *pMeasurement)
} }
float sum = 0.0; 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]; sum += pMeasurement->average10s.samples[i];
} }
if (pMeasurement->average10s.bufferCount == 0U)
{
pMeasurement->average10s.fValue = 0.0f;
}
else
{
pMeasurement->average10s.fValue = sum / pMeasurement->average10s.bufferCount; pMeasurement->average10s.fValue = sum / pMeasurement->average10s.bufferCount;
}
// Average form the last 60sec // Average form the last 60sec
pMeasurement->average60s.samples[pMeasurement->average60s.bufferIndex] = pMeasurement->fCurrentValue; pMeasurement->average60s.samples[pMeasurement->average60s.bufferIndex] = pMeasurement->fCurrentValue;
@ -157,14 +144,7 @@ void updateAverage(sMeasurement *pMeasurement)
sum += pMeasurement->average60s.samples[i]; sum += pMeasurement->average60s.samples[i];
} }
if (pMeasurement->average60s.bufferCount == 0U)
{
pMeasurement->average60s.fValue = 0.0f;
}
else
{
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)

View File

@ -1,17 +1,13 @@
#pragma once #pragma once
#include "sdkconfig.h"
#include <stddef.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b))
#define INITIALISATION_VALUE 0.0f #define INITIALISATION_VALUE 0.0f
#define AVG10S_SAMPLE_SIZE 10U #define AVG10S_SAMPLE_SIZE 10U
#define AVG60S_SAMPLE_SIZE 60U #define AVG60S_SAMPLE_SIZE 60U
#define AVG24H_SAMPLE_SIZE 24U #define AVG24H_SAMPLE_SIZE 24U
#define PRED60S_SAMPLE_SIZE 60U #define PRED60S_SAMPLE_SIZE 60U
#define DAMPING_FACTOR_WARMER (CONFIG_DAMPING_FACTOR_WARMER * 0.00001f) #define DAMPING_FACTOR_WARMER 0.00001f // 0.001%
#define DAMPING_FACTOR_COLDER (CONFIG_DAMPING_FACTOR_COLDER * 0.00001f) #define DAMPING_FACTOR_COLDER 0.00005f // 0.005%
typedef enum _BurnerErrorState typedef enum _BurnerErrorState
{ {

View File

@ -1,3 +1,7 @@
#include "esp_log.h"
#include <esp_system.h>
#include "nvs_flash.h"
#include "safety.h" #include "safety.h"
#include "metrics.h" #include "metrics.h"
#include "outputs.h" #include "outputs.h"
@ -6,10 +10,6 @@
#include "wifi.h" #include "wifi.h"
#include "sntp.h" #include "sntp.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
static const char *TAG = "smart-oil-heater-control-system"; static const char *TAG = "smart-oil-heater-control-system";
void app_main(void) void app_main(void)

View File

@ -1,3 +1,12 @@
#include <string.h>
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include <time.h>
#include <sys/time.h>
#include "metrics.h" #include "metrics.h"
#include "outputs.h" #include "outputs.h"
#include "inputs.h" #include "inputs.h"
@ -5,16 +14,6 @@
#include "sntp.h" #include "sntp.h"
#include "control.h" #include "control.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include <string.h>
#include <time.h>
#include <sys/time.h>
static const char *TAG = "smart-oil-heater-control-system-metrics"; static const char *TAG = "smart-oil-heater-control-system-metrics";
char caHtmlResponse[HTML_RESPONSE_SIZE]; char caHtmlResponse[HTML_RESPONSE_SIZE];
@ -302,23 +301,23 @@ void taskMetrics(void *pvParameters)
// Wifi RSSI // Wifi RSSI
wifi_ap_record_t ap; wifi_ap_record_t ap;
ap.rssi = 0U; esp_wifi_sta_get_ap_info(&ap);
ESP_ERROR_CHECK(esp_wifi_sta_get_ap_info(&ap));
strcpy(aMetrics[u16MetricCounter].caMetricName, "wifi_rssi"); strcpy(aMetrics[u16MetricCounter].caMetricName, "wifi_rssi");
aMetrics[u16MetricCounter].type = INTEGER_64; aMetrics[u16MetricCounter].type = INTEGER_64;
aMetrics[u16MetricCounter].i64MetricValue = ap.rssi; aMetrics[u16MetricCounter].i64MetricValue = ap.rssi;
u16MetricCounter++; u16MetricCounter++;
configASSERT(!(u16MetricCounter > METRIC_MAX_COUNT)); ESP_ERROR_CHECK(u16MetricCounter > METRIC_MAX_COUNT);
vSetMetrics(aMetrics, u16MetricCounter); vSetMetrics(aMetrics, u16MetricCounter);
} }
} }
void vSetMetrics(sMetric *paMetrics, uint16_t u16Size) void vSetMetrics(sMetric *paMetrics, uint16_t u16Size)
{ {
if (xSemaphoreTakeRecursive(xMutexAccessMetricResponse, pdMS_TO_TICKS(5000)) == pdTRUE) if (xSemaphoreTakeRecursive(xMutexAccessMetricResponse, pdMS_TO_TICKS(5000)) == pdTRUE)
{ {
memset(caHtmlResponse, 0U, HTML_RESPONSE_SIZE); memset(caHtmlResponse, 0U, strlen(caHtmlResponse));
for (uint16_t u16Index = 0U; u16Index < u16Size; u16Index++) for (uint16_t u16Index = 0U; u16Index < u16Size; u16Index++)
{ {
char caValueBuffer[64]; char caValueBuffer[64];

View File

@ -1,15 +1,14 @@
#include "outputs.h"
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "driver/gpio.h" #include "driver/gpio.h"
#include "esp_log.h" #include "esp_log.h"
#include "outputs.h"
static const char *TAG = "smart-oil-heater-control-system-outputs"; static const char *TAG = "smart-oil-heater-control-system-outputs";
const uint8_t uCirculationPumpGpioPin = CONFIG_GPIO_CIRCULATION_PUMP; const uint8_t uCirculationPumpGpioPin = 27U;
const uint8_t uBurnerGpioPin = CONFIG_GPIO_BURNER; const uint8_t uBurnerGpioPin = 14U;
const uint8_t uSafetyContactGpioPin = CONFIG_GPIO_SAFETY_CONTACT; const uint8_t uSafetyContactGpioPin = 12U;
static SemaphoreHandle_t xMutexAccessOutputs = NULL; static SemaphoreHandle_t xMutexAccessOutputs = NULL;
static eOutput sCirculationPumpState; static eOutput sCirculationPumpState;
@ -42,26 +41,9 @@ void initOutputs(void)
.intr_type = GPIO_INTR_DISABLE // Disable interrupts .intr_type = GPIO_INTR_DISABLE // Disable interrupts
}; };
esp_err_t ret = gpio_config(&ioConfCirculationPump); gpio_config(&ioConfCirculationPump);
if (ret != ESP_OK) gpio_config(&ioConfBurner);
{ gpio_config(&ioConfSafetyContact);
ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret));
return;
}
ret = gpio_config(&ioConfBurner);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret));
return;
}
ret = gpio_config(&ioConfSafetyContact);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret));
return;
}
xMutexAccessOutputs = xSemaphoreCreateRecursiveMutex(); xMutexAccessOutputs = xSemaphoreCreateRecursiveMutex();
if (xMutexAccessOutputs == NULL) if (xMutexAccessOutputs == NULL)
@ -73,17 +55,7 @@ void initOutputs(void)
eOutput getCirculationPumpState(void) eOutput getCirculationPumpState(void)
{ {
eOutput ret = ENABLED; return sCirculationPumpState;
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) void setCirculationPumpState(eOutput in)
@ -98,7 +70,6 @@ void setCirculationPumpState(eOutput in)
break; break;
case DISABLED: case DISABLED:
gpio_set_level(uCirculationPumpGpioPin, 1U); // Switch off Circulation Pump gpio_set_level(uCirculationPumpGpioPin, 1U); // Switch off Circulation Pump
break;
default: default:
break; break;
} }
@ -137,7 +108,6 @@ void setBurnerState(eOutput in)
break; break;
case DISABLED: case DISABLED:
gpio_set_level(uBurnerGpioPin, 1U); // Switch off Burner gpio_set_level(uBurnerGpioPin, 1U); // Switch off Burner
break;
default: default:
break; break;
} }
@ -176,7 +146,6 @@ void setSafetyControlState(eOutput in)
break; break;
case DISABLED: case DISABLED:
gpio_set_level(uSafetyContactGpioPin, 1U); // Switch off power for Burner gpio_set_level(uSafetyContactGpioPin, 1U); // Switch off power for Burner
break;
default: default:
break; break;
} }

View File

@ -1,22 +1,19 @@
#include "safety.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "esp_log.h" #include "esp_log.h"
#include <string.h> #include <string.h>
#include <math.h> #include "safety.h"
#define PERIODIC_INTERVAL 1U // run safety checks every 1sec #define PERIODIC_INTERVAL 1U // run safety checks every 1sec
#define SENSOR_GRACE_PERIOD (CONFIG_SENSOR_GRACE_PERIOD_MINUTES * 60U) // period that a sensor can report the same reading in seconds #define SENSOR_GRACE_PERIOD (60U * 30U) // period that a sensor can report the same reading in seconds
#define FLOAT_EPSILON 0.0001f
static const char *TAG = "smart-oil-heater-control-system-safety"; static const char *TAG = "smart-oil-heater-control-system-safety";
static SemaphoreHandle_t xMutexAccessSafety = NULL; static SemaphoreHandle_t xMutexAccessSafety = NULL;
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", {95.0f, -10.0f}, 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", {45.0f, -20.0f}, 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, "inlet_flow_temperature", {95.0f, -10.0f}, 0.0f, 0U, getInletFlowTemperature},
{SENSOR_NO_ERROR, "return_flow_temperature", {SENSOR_LIMIT_RETURN_MAX, SENSOR_LIMIT_RETURN_MIN}, 0.0f, 0U, getReturnFlowTemperature}}; {SENSOR_NO_ERROR, "return_flow_temperature", {95.0f, -10.0f}, 0.0f, 0U, getReturnFlowTemperature}};
static eSafetyState sSafetyState = SAFETY_NO_ERROR; static eSafetyState sSafetyState = SAFETY_NO_ERROR;
void taskSafety(void *pvParameters); void taskSafety(void *pvParameters);
@ -94,7 +91,7 @@ void checkSensorSanity(void)
} }
else else
{ {
if (fabsf(sCurrentMeasurement.fCurrentValue - sanityChecks[i].fSensorTemperatureLast) < FLOAT_EPSILON) if (sCurrentMeasurement.fCurrentValue == sanityChecks[i].fSensorTemperatureLast)
{ {
sanityChecks[i].uUnchangedCounter++; sanityChecks[i].uUnchangedCounter++;
if (sanityChecks[i].uUnchangedCounter >= (SENSOR_GRACE_PERIOD / PERIODIC_INTERVAL)) if (sanityChecks[i].uUnchangedCounter >= (SENSOR_GRACE_PERIOD / PERIODIC_INTERVAL))
@ -106,7 +103,6 @@ void checkSensorSanity(void)
} }
else else
{ {
sanityChecks[i].uUnchangedCounter = 0U;
sanityChecks[i].fSensorTemperatureLast = sCurrentMeasurement.fCurrentValue; sanityChecks[i].fSensorTemperatureLast = sCurrentMeasurement.fCurrentValue;
if (sCurrentMeasurement.fCurrentValue > sanityChecks[i].sSensorLimit.max) if (sCurrentMeasurement.fCurrentValue > sanityChecks[i].sSensorLimit.max)
@ -123,10 +119,12 @@ void checkSensorSanity(void)
} }
else else
{ {
sanityChecks[i].uUnchangedCounter = 0U;
sanityChecks[i].state = SENSOR_NO_ERROR; sanityChecks[i].state = SENSOR_NO_ERROR;
} }
} }
} }
// printf(" state: %u\n", sanityChecks[i].state);
} }
} }
@ -145,7 +143,7 @@ void getSensorSanityStates(sSensorSanityCheck *pSensorSanityChecks)
{ {
// Copy only the needed attributes // Copy only the needed attributes
pSensorSanityChecks[i].state = sanityChecks[i].state; pSensorSanityChecks[i].state = sanityChecks[i].state;
strncpy(pSensorSanityChecks[i].name, sanityChecks[i].name, MAX_ERROR_STRING_SIZE); strcpy(pSensorSanityChecks[i].name, sanityChecks[i].name);
} }
xSemaphoreGiveRecursive(xMutexAccessSafety); xSemaphoreGiveRecursive(xMutexAccessSafety);
} }

View File

@ -3,22 +3,9 @@
#include "outputs.h" #include "outputs.h"
#include "inputs.h" #include "inputs.h"
#include "sdkconfig.h"
#include <stdint.h>
#define MAX_ERROR_STRING_SIZE 64U #define MAX_ERROR_STRING_SIZE 64U
#define NUMBER_OF_SENSOR_SANITY_CHECKS 4U #define NUMBER_OF_SENSOR_SANITY_CHECKS 4U
#define SENSOR_LIMIT_CHAMBER_MAX (CONFIG_SENSOR_LIMIT_CHAMBER_MAX / 10.0f)
#define SENSOR_LIMIT_CHAMBER_MIN (CONFIG_SENSOR_LIMIT_CHAMBER_MIN / 10.0f)
#define SENSOR_LIMIT_OUTDOOR_MAX (CONFIG_SENSOR_LIMIT_OUTDOOR_MAX / 10.0f)
#define SENSOR_LIMIT_OUTDOOR_MIN (CONFIG_SENSOR_LIMIT_OUTDOOR_MIN / 10.0f)
#define SENSOR_LIMIT_INLET_MAX (CONFIG_SENSOR_LIMIT_INLET_MAX / 10.0f)
#define SENSOR_LIMIT_INLET_MIN (CONFIG_SENSOR_LIMIT_INLET_MIN / 10.0f)
#define SENSOR_LIMIT_RETURN_MAX (CONFIG_SENSOR_LIMIT_RETURN_MAX / 10.0f)
#define SENSOR_LIMIT_RETURN_MIN (CONFIG_SENSOR_LIMIT_RETURN_MIN / 10.0f)
typedef enum _SensorErrorState typedef enum _SensorErrorState
{ {
SENSOR_NO_ERROR, SENSOR_NO_ERROR,

View File

@ -1,13 +1,12 @@
#include "sntp.h"
#include "esp_sntp.h"
#include "esp_log.h"
#include <time.h> #include <time.h>
#include <sys/time.h> #include <sys/time.h>
#include <esp_sntp.h>
#include "esp_log.h"
#include "sntp.h"
static const char *TAG = "smart-oil-heater-control-system-sntp"; static const char *TAG = "smart-oil-heater-control-system-sntp";
static volatile eSntpState sntpState = SYNC_NOT_STARTED; static eSntpState sntpState = SYNC_NOT_STARTED;
void time_sync_notification_cb(struct timeval *tv); void time_sync_notification_cb(struct timeval *tv);
void initSntp(void) void initSntp(void)

View File

@ -1,50 +1,39 @@
#include "wifi.h" #include <string.h>
#include "esp_timer.h" #include "esp_timer.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h" #include "esp_wifi.h"
#include "esp_event.h" #include "esp_event.h"
#include "freertos/event_groups.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_netif.h" #include "esp_netif.h"
#include <lwip/sockets.h> #include <lwip/sockets.h>
#include <string.h> #include "wifi.h"
#define WIFI_CONNECTED_BIT BIT0 #define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1 #define WIFI_FAIL_BIT BIT1
#define MAX_RETRY_COUNT 10
#define RETRY_DELAY_MS 1000
static const char *TAG = "smart-oil-heater-control-system-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 bool s_initial_connect = true;
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);
static bool parse_bssid(const char *bssid_str, uint8_t *bssid);
void initWifi(void) void initWifi(void)
{ {
s_wifi_event_group = xEventGroupCreate(); s_wifi_event_group = xEventGroupCreate();
if (s_wifi_event_group == NULL)
{
ESP_LOGE(TAG, "xEventGroupCreate() failed!");
return;
}
ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default()); ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_t *my_sta = esp_netif_create_default_wifi_sta(); esp_netif_t *my_sta = esp_netif_create_default_wifi_sta();
ESP_ERROR_CHECK(esp_netif_dhcpc_stop(my_sta)); esp_netif_dhcpc_stop(my_sta);
esp_netif_ip_info_t ip_info; esp_netif_ip_info_t ip_info;
ip_info.ip.addr = ipaddr_addr(CONFIG_STATIC_IP_ADDR); ip_info.ip.addr = ipaddr_addr(CONFIG_STATIC_IP_ADDR);
ip_info.gw.addr = ipaddr_addr(CONFIG_STATIC_GATEWAY_IP_ADDR); ip_info.gw.addr = ipaddr_addr(CONFIG_STATIC_GATEWAY_IP_ADDR);
ip_info.netmask.addr = ipaddr_addr(CONFIG_STATIC_IP_NETMASK); ip_info.netmask.addr = ipaddr_addr(CONFIG_STATIC_IP_NETMASK);
ESP_ERROR_CHECK(esp_netif_set_ip_info(my_sta, &ip_info)); esp_netif_set_ip_info(my_sta, &ip_info);
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg)); ESP_ERROR_CHECK(esp_wifi_init(&cfg));
@ -68,6 +57,21 @@ void initWifi(void)
.threshold.authmode = WIFI_AUTH_WPA2_PSK, .threshold.authmode = WIFI_AUTH_WPA2_PSK,
}, },
}; };
#if CONFIG_ENV_WIFI_BSSID_LOCK
/* Lock to specific AP by BSSID */
if (parse_bssid(CONFIG_ENV_WIFI_BSSID, wifi_config.sta.bssid))
{
wifi_config.sta.bssid_set = true;
ESP_LOGI(TAG, "BSSID lock enabled: %s", CONFIG_ENV_WIFI_BSSID);
}
else
{
ESP_LOGE(TAG, "Invalid BSSID format: %s", CONFIG_ENV_WIFI_BSSID);
wifi_config.sta.bssid_set = false;
}
#endif
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
@ -96,9 +100,7 @@ void initWifi(void)
{ {
ESP_LOGE(TAG, "Unexpected event"); ESP_LOGE(TAG, "Unexpected event");
} }
vEventGroupDelete(s_wifi_event_group);
// Mark initial connection phase complete - do NOT delete the event group
s_initial_connect = false;
} }
static void event_handler(void *arg, esp_event_base_t event_base, static void event_handler(void *arg, esp_event_base_t event_base,
@ -110,46 +112,37 @@ static void event_handler(void *arg, esp_event_base_t event_base,
} }
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{ {
wifi_event_sta_disconnected_t *event = (wifi_event_sta_disconnected_t *)event_data;
ESP_LOGW(TAG, "Disconnected from AP (reason: %d)", event->reason);
if (s_initial_connect)
{
// During initial connection phase, use retry limit
if (s_retry_num < MAX_RETRY_COUNT)
{
vTaskDelay(pdMS_TO_TICKS(RETRY_DELAY_MS));
esp_wifi_connect(); esp_wifi_connect();
s_retry_num++; ESP_LOGI(TAG, "Retry to connect to the AP");
ESP_LOGI(TAG, "Retry to connect to the AP (%d/%d)", s_retry_num, MAX_RETRY_COUNT);
}
else
{
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
ESP_LOGE(TAG, "Failed to connect after %d attempts", MAX_RETRY_COUNT);
}
}
else
{
// After initial connection, always try to reconnect with delay
vTaskDelay(pdMS_TO_TICKS(RETRY_DELAY_MS));
esp_wifi_connect();
ESP_LOGI(TAG, "Attempting to reconnect to the AP...");
}
} }
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{ {
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "Got ip:" IPSTR, IP2STR(&event->ip_info.ip)); ESP_LOGI(TAG, "Got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
if (s_initial_connect)
{
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
} }
else }
/**
* @brief Parse BSSID string to byte array
*
* @param bssid_str BSSID string in format "XX:XX:XX:XX:XX:XX"
* @param bssid Output byte array (6 bytes)
* @return true on success, false on parse error
*/
static bool parse_bssid(const char *bssid_str, uint8_t *bssid)
{ {
ESP_LOGI(TAG, "Successfully reconnected to AP"); unsigned int tmp[6];
int parsed = sscanf(bssid_str, "%x:%x:%x:%x:%x:%x",
&tmp[0], &tmp[1], &tmp[2],
&tmp[3], &tmp[4], &tmp[5]);
if (parsed != 6)
{
return false;
} }
for (int i = 0; i < 6; i++)
{
bssid[i] = (uint8_t)tmp[i];
} }
return true;
} }