18 Commits

Author SHA256 Message Date
f8f6af53bd implement config 2026-01-10 12:50:06 +01:00
f3f6f1bc5f Potential division by zero 2026-01-10 12:01:22 +01:00
b718073907 Missing break before default 2026-01-10 11:58:46 +01:00
d36b91a0fd Variable name shadows type name 2026-01-10 11:57:15 +01:00
40f757b7d1 uUnchangedCounter reset logic flaw 2026-01-10 11:54:18 +01:00
a9ec101bc6 Floating-point equality comparison 2026-01-10 11:52:08 +01:00
0236ebcdd1 Unsafe strcpy 2026-01-10 11:47:04 +01:00
05757a5038 Unchecked WiFi API call 2026-01-10 11:45:49 +01:00
020eb63e05 Unchecked network configuration 2026-01-10 11:43:26 +01:00
67929580d5 Unchecked xEventGroupCreate 2026-01-10 11:42:27 +01:00
10f9645580 Unchecked gpio_config returns 2026-01-10 11:39:37 +01:00
df3825df3a Non-thread-safe function 2026-01-10 11:33:37 +01:00
8c3dbc2886 Unprotected shared state access 2026-01-10 11:31:34 +01:00
267197ec20 Missing mutex protection 2026-01-10 11:06:10 +01:00
781f9a1445 ncorrect memset with strlen 2026-01-10 11:02:31 +01:00
09a3c3a22d Misuse of ESP_ERROR_CHECK 2026-01-10 10:58:12 +01:00
0775fda0ca Off-by-one error (buffer overread) 2026-01-10 10:55:15 +01:00
cd73985740 Wrong memset size 2026-01-10 10:54:32 +01:00
12 changed files with 700 additions and 194 deletions

View File

@ -1,22 +1,391 @@
menu "Smart Oil Heating Control System"
menu "WiFi Configuration"
config SSID
string "SSID"
string "WiFi SSID"
default "my WiFi SSID"
help
The SSID of the WiFi network to connect to.
config WIFI_PASSWORD
string "WIFI_PASSWORD"
string "WiFi Password"
default "my WIFI Password"
help
The password for the WiFi network.
config STATIC_IP_ADDR
string "Static IPv4 address"
default "192.168.0.42"
help
Static IP address for the ESP32.
config STATIC_IP_NETMASK
string "Static IPv4 netmask"
default "255.255.0.0"
help
Network mask for the static IP configuration.
config STATIC_GATEWAY_IP_ADDR
string "Static IPv4 gateway address"
default "192.168.0.1"
help
Gateway IP address for network routing.
config SNTP_SERVER_IP_ADDR
string "SNTP IPv4 server address"
string "SNTP server address"
default "192.168.0.1"
help
NTP server address for time synchronization.
endmenu
menu "GPIO Configuration"
menu "Input GPIOs"
config GPIO_BURNER_FAULT
int "Burner fault input GPIO"
range 0 39
default 19
help
GPIO pin connected to the burner fault signal.
config GPIO_DS18B20_ONEWIRE
int "DS18B20 1-Wire bus GPIO"
range 0 39
default 4
help
GPIO pin for the 1-Wire bus (DS18B20 temperature sensors).
endmenu
menu "Output GPIOs"
config GPIO_CIRCULATION_PUMP
int "Circulation pump output GPIO"
range 0 39
default 27
help
GPIO pin to control the circulation pump relay.
config GPIO_BURNER
int "Burner control output GPIO"
range 0 39
default 14
help
GPIO pin to control the burner relay.
config GPIO_SAFETY_CONTACT
int "Safety contact output GPIO"
range 0 39
default 12
help
GPIO pin for the safety contact relay (main power to burner).
endmenu
endmenu
menu "1-Wire Sensor Addresses"
config ONEWIRE_ADDR_CHAMBER_TEMP
hex "Chamber temperature sensor address"
default 0xd00000108cd01d28
help
64-bit 1-Wire address of the chamber temperature sensor.
config ONEWIRE_ADDR_OUTDOOR_TEMP
hex "Outdoor temperature sensor address"
default 0xd70000108a9b9128
help
64-bit 1-Wire address of the outdoor temperature sensor.
config ONEWIRE_ADDR_INLET_FLOW_TEMP
hex "Inlet flow temperature sensor address"
default 0x410000108b8c0628
help
64-bit 1-Wire address of the inlet flow temperature sensor.
config ONEWIRE_ADDR_RETURN_FLOW_TEMP
hex "Return flow temperature sensor address"
default 0x90000108cc77c28
help
64-bit 1-Wire address of the return flow temperature sensor.
endmenu
menu "Temperature Control Settings"
menu "Target Temperatures"
config TEMP_RETURN_FLOW_LOWER_LIMIT_DAY
int "Return flow lower limit (day) [°C x 10]"
range 150 500
default 300
help
Minimum return flow temperature during day mode in 0.1°C units.
Example: 300 = 30.0°C
config TEMP_RETURN_FLOW_LOWER_LIMIT_NIGHT
int "Return flow lower limit (night) [°C x 10]"
range 150 500
default 250
help
Minimum return flow temperature during night mode in 0.1°C units.
Example: 250 = 25.0°C
config TEMP_CHAMBER_TARGET
int "Chamber target temperature [°C x 10]"
range 500 950
default 800
help
Maximum chamber temperature target in 0.1°C units.
Example: 800 = 80.0°C
config TEMP_CHAMBER_THRESHOLD
int "Chamber temperature threshold [°C x 10]"
range 300 700
default 450
help
Minimum chamber temperature to enable burner in 0.1°C units.
Example: 450 = 45.0°C
config TEMP_CIRCULATION_PUMP_THRESHOLD
int "Circulation pump threshold [°C x 10]"
range 200 500
default 300
help
Minimum chamber temperature to enable circulation pump in 0.1°C units.
Example: 300 = 30.0°C
endmenu
menu "Summer Mode Settings"
config TEMP_SUMMER_MODE_HIGH
int "Summer mode activation threshold [°C x 10]"
range 150 300
default 200
help
Outdoor temperature above which summer mode activates in 0.1°C units.
Example: 200 = 20.0°C
config TEMP_SUMMER_MODE_LOW
int "Summer mode deactivation threshold [°C x 10]"
range 100 250
default 150
help
Outdoor temperature below which summer mode deactivates in 0.1°C units.
Example: 150 = 15.0°C
endmenu
config BURNER_FAULT_DETECTION_SECONDS
int "Burner fault detection timeout (seconds)"
range 60 600
default 240
help
Time in seconds to wait before checking for burner fault after enabling.
endmenu
menu "Sensor Limits"
menu "Chamber Temperature Limits"
config SENSOR_LIMIT_CHAMBER_MAX
int "Chamber sensor maximum [°C x 10]"
range 500 1200
default 950
help
Maximum valid chamber temperature reading in 0.1°C units.
config SENSOR_LIMIT_CHAMBER_MIN
int "Chamber sensor minimum [°C x 10]"
range -400 100
default -100
help
Minimum valid chamber temperature reading in 0.1°C units.
endmenu
menu "Outdoor Temperature Limits"
config SENSOR_LIMIT_OUTDOOR_MAX
int "Outdoor sensor maximum [°C x 10]"
range 300 600
default 450
help
Maximum valid outdoor temperature reading in 0.1°C units.
config SENSOR_LIMIT_OUTDOOR_MIN
int "Outdoor sensor minimum [°C x 10]"
range -500 0
default -200
help
Minimum valid outdoor temperature reading in 0.1°C units.
endmenu
menu "Inlet Flow Temperature Limits"
config SENSOR_LIMIT_INLET_MAX
int "Inlet flow sensor maximum [°C x 10]"
range 500 1200
default 950
help
Maximum valid inlet flow temperature reading in 0.1°C units.
config SENSOR_LIMIT_INLET_MIN
int "Inlet flow sensor minimum [°C x 10]"
range -400 100
default -100
help
Minimum valid inlet flow temperature reading in 0.1°C units.
endmenu
menu "Return Flow Temperature Limits"
config SENSOR_LIMIT_RETURN_MAX
int "Return flow sensor maximum [°C x 10]"
range 500 1200
default 950
help
Maximum valid return flow temperature reading in 0.1°C units.
config SENSOR_LIMIT_RETURN_MIN
int "Return flow sensor minimum [°C x 10]"
range -400 100
default -100
help
Minimum valid return flow temperature reading in 0.1°C units.
endmenu
config SENSOR_GRACE_PERIOD_MINUTES
int "Sensor unchanged grace period (minutes)"
range 1 120
default 30
help
Maximum time in minutes a sensor can report unchanged values
before being flagged as faulty.
endmenu
menu "Damping Factors"
config DAMPING_FACTOR_WARMER
int "Damping factor warmer [x 0.00001]"
range 1 100
default 1
help
Damping factor for rising temperatures in units of 0.00001.
Example: 1 = 0.00001 (0.001%)
config DAMPING_FACTOR_COLDER
int "Damping factor colder [x 0.00001]"
range 1 100
default 5
help
Damping factor for falling temperatures in units of 0.00001.
Example: 5 = 0.00005 (0.005%)
endmenu
menu "Heating Schedule"
menu "Weekday Schedule (Monday-Thursday)"
config SCHEDULE_WEEKDAY_DAY_START_HOUR
int "Day mode start hour"
range 0 23
default 4
help
Hour when day mode starts on weekdays (24h format).
config SCHEDULE_WEEKDAY_DAY_START_MINUTE
int "Day mode start minute"
range 0 59
default 45
help
Minute when day mode starts on weekdays.
config SCHEDULE_WEEKDAY_NIGHT_START_HOUR
int "Night mode start hour"
range 0 23
default 22
help
Hour when night mode starts on weekdays (24h format).
config SCHEDULE_WEEKDAY_NIGHT_START_MINUTE
int "Night mode start minute"
range 0 59
default 0
help
Minute when night mode starts on weekdays.
endmenu
menu "Friday Schedule"
config SCHEDULE_FRIDAY_DAY_START_HOUR
int "Day mode start hour"
range 0 23
default 4
help
Hour when day mode starts on Friday (24h format).
config SCHEDULE_FRIDAY_DAY_START_MINUTE
int "Day mode start minute"
range 0 59
default 45
help
Minute when day mode starts on Friday.
config SCHEDULE_FRIDAY_NIGHT_START_HOUR
int "Night mode start hour"
range 0 23
default 23
help
Hour when night mode starts on Friday (24h format).
config SCHEDULE_FRIDAY_NIGHT_START_MINUTE
int "Night mode start minute"
range 0 59
default 0
help
Minute when night mode starts on Friday.
endmenu
menu "Saturday Schedule"
config SCHEDULE_SATURDAY_DAY_START_HOUR
int "Day mode start hour"
range 0 23
default 6
help
Hour when day mode starts on Saturday (24h format).
config SCHEDULE_SATURDAY_DAY_START_MINUTE
int "Day mode start minute"
range 0 59
default 45
help
Minute when day mode starts on Saturday.
config SCHEDULE_SATURDAY_NIGHT_START_HOUR
int "Night mode start hour"
range 0 23
default 23
help
Hour when night mode starts on Saturday (24h format).
config SCHEDULE_SATURDAY_NIGHT_START_MINUTE
int "Night mode start minute"
range 0 59
default 30
help
Minute when night mode starts on Saturday.
endmenu
menu "Sunday Schedule"
config SCHEDULE_SUNDAY_DAY_START_HOUR
int "Day mode start hour"
range 0 23
default 6
help
Hour when day mode starts on Sunday (24h format).
config SCHEDULE_SUNDAY_DAY_START_MINUTE
int "Day mode start minute"
range 0 59
default 45
help
Minute when day mode starts on Sunday.
config SCHEDULE_SUNDAY_NIGHT_START_HOUR
int "Night mode start hour"
range 0 23
default 22
help
Hour when night mode starts on Sunday (24h format).
config SCHEDULE_SUNDAY_NIGHT_START_MINUTE
int "Night mode start minute"
range 0 59
default 30
help
Minute when night mode starts on Sunday.
endmenu
endmenu
endmenu

View File

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

View File

@ -1,8 +1,20 @@
#pragma once
#include "sdkconfig.h"
#include <time.h>
#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
{
CONTROL_STARTING,

View File

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

View File

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

View File

@ -1,7 +1,3 @@
#include "esp_log.h"
#include <esp_system.h>
#include "nvs_flash.h"
#include "safety.h"
#include "metrics.h"
#include "outputs.h"
@ -10,6 +6,10 @@
#include "wifi.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";
void app_main(void)

View File

@ -1,12 +1,3 @@
#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 "outputs.h"
#include "inputs.h"
@ -14,6 +5,16 @@
#include "sntp.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";
char caHtmlResponse[HTML_RESPONSE_SIZE];
@ -301,23 +302,23 @@ void taskMetrics(void *pvParameters)
// Wifi RSSI
wifi_ap_record_t ap;
esp_wifi_sta_get_ap_info(&ap);
ap.rssi = 0U;
ESP_ERROR_CHECK(esp_wifi_sta_get_ap_info(&ap));
strcpy(aMetrics[u16MetricCounter].caMetricName, "wifi_rssi");
aMetrics[u16MetricCounter].type = INTEGER_64;
aMetrics[u16MetricCounter].i64MetricValue = ap.rssi;
u16MetricCounter++;
ESP_ERROR_CHECK(u16MetricCounter > METRIC_MAX_COUNT);
configASSERT(!(u16MetricCounter > METRIC_MAX_COUNT));
vSetMetrics(aMetrics, u16MetricCounter);
}
}
void vSetMetrics(sMetric *paMetrics, uint16_t u16Size)
{
if (xSemaphoreTakeRecursive(xMutexAccessMetricResponse, pdMS_TO_TICKS(5000)) == pdTRUE)
{
memset(caHtmlResponse, 0U, strlen(caHtmlResponse));
memset(caHtmlResponse, 0U, HTML_RESPONSE_SIZE);
for (uint16_t u16Index = 0U; u16Index < u16Size; u16Index++)
{
char caValueBuffer[64];

View File

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

View File

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

View File

@ -3,9 +3,22 @@
#include "outputs.h"
#include "inputs.h"
#include "sdkconfig.h"
#include <stdint.h>
#define MAX_ERROR_STRING_SIZE 64U
#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
{
SENSOR_NO_ERROR,

View File

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

View File

@ -1,15 +1,16 @@
#include <string.h>
#include "wifi.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_netif.h"
#include <lwip/sockets.h>
#include "wifi.h"
#include <string.h>
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
@ -28,16 +29,22 @@ static void event_handler(void *arg, esp_event_base_t event_base,
void initWifi(void)
{
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_event_loop_create_default());
esp_netif_t *my_sta = esp_netif_create_default_wifi_sta();
esp_netif_dhcpc_stop(my_sta);
ESP_ERROR_CHECK(esp_netif_dhcpc_stop(my_sta));
esp_netif_ip_info_t ip_info;
ip_info.ip.addr = ipaddr_addr(CONFIG_STATIC_IP_ADDR);
ip_info.gw.addr = ipaddr_addr(CONFIG_STATIC_GATEWAY_IP_ADDR);
ip_info.netmask.addr = ipaddr_addr(CONFIG_STATIC_IP_NETMASK);
esp_netif_set_ip_info(my_sta, &ip_info);
ESP_ERROR_CHECK(esp_netif_set_ip_info(my_sta, &ip_info));
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));