Compare commits
1 Commits
feature/co
...
feature/wi
| Author | SHA256 | Date | |
|---|---|---|---|
| d14ae528c0 |
56
README.md
56
README.md
@ -19,11 +19,7 @@ Sntp <|-- Metrics
|
|||||||
|
|
||||||
class Inputs{
|
class Inputs{
|
||||||
+initInputs()
|
+initInputs()
|
||||||
-initMeasurement()
|
|
||||||
-updateAverage()
|
|
||||||
-updatePrediction()
|
|
||||||
-taskInput()
|
-taskInput()
|
||||||
-linearRegressionPredict()
|
|
||||||
+getChamberTemperature()
|
+getChamberTemperature()
|
||||||
+getOutdoorTemperature()
|
+getOutdoorTemperature()
|
||||||
+getInletFlowTemperature()
|
+getInletFlowTemperature()
|
||||||
@ -42,11 +38,7 @@ Sntp <|-- Metrics
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Control{
|
class Control{
|
||||||
initControl()
|
|
||||||
+taskControl()
|
+taskControl()
|
||||||
+getControlCurrentWeekday()
|
|
||||||
-findControlCurrentTemperatureEntry()
|
|
||||||
+getControlCurrentTemperatureEntry()
|
|
||||||
-controlTable
|
-controlTable
|
||||||
+getControlState()
|
+getControlState()
|
||||||
}
|
}
|
||||||
@ -87,41 +79,33 @@ Sntp <|-- Metrics
|
|||||||
burner_fault_pending 1
|
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 0
|
||||||
chamber_temperature 37.250000
|
chamber_temperature 58.750000
|
||||||
chamber_temperature_avg10 37.237499
|
chamber_temperature_avg10 58.931252
|
||||||
chamber_temperature_avg60 37.438541
|
chamber_temperature_avg60 59.190475
|
||||||
chamber_temperature_damped 42.185040
|
chamber_temperature_pred60 55.870998
|
||||||
chamber_temperature_pred60 36.638443
|
inlet_flow_temperature 53.875000
|
||||||
inlet_flow_temperature 35.625000
|
inlet_flow_temperature_avg10 53.900002
|
||||||
inlet_flow_temperature_avg10 35.618752
|
inlet_flow_temperature_avg60 53.994320
|
||||||
inlet_flow_temperature_avg60 35.415627
|
inlet_flow_temperature_pred60 52.848743
|
||||||
inlet_flow_temperature_damped 39.431259
|
outdoor_temperature 18.000000
|
||||||
inlet_flow_temperature_pred60 36.078678
|
outdoor_temperature_avg10 18.006250
|
||||||
outdoor_temperature 14.687500
|
outdoor_temperature_avg60 18.002840
|
||||||
outdoor_temperature_avg10 14.662500
|
outdoor_temperature_pred60 18.050785
|
||||||
outdoor_temperature_avg60 14.646875
|
return_flow_temperature 48.625000
|
||||||
outdoor_temperature_damped 9.169084
|
return_flow_temperature_avg10 48.718750
|
||||||
outdoor_temperature_pred60 14.660233
|
return_flow_temperature_avg60 48.846592
|
||||||
return_flow_temperature 39.937500
|
return_flow_temperature_pred60 47.383083
|
||||||
return_flow_temperature_avg10 40.087502
|
|
||||||
return_flow_temperature_avg60 41.146873
|
|
||||||
return_flow_temperature_damped 32.385151
|
|
||||||
return_flow_temperature_pred60 37.311958
|
|
||||||
chamber_temperature_state 0
|
chamber_temperature_state 0
|
||||||
outdoor_temperature_state 0
|
outdoor_temperature_state 0
|
||||||
inlet_flow_temperature_state 0
|
inlet_flow_temperature_state 0
|
||||||
return_flow_temperature_state 0
|
return_flow_temperature_state 0
|
||||||
safety_state 0
|
safety_state 0
|
||||||
control_state 3
|
control_state 3
|
||||||
control_current_weekday 5
|
|
||||||
control_current_entry_time 17100
|
|
||||||
control_current_entry_chamber_temperature 80.000000
|
|
||||||
control_current_entry_return_flow_temperature 30.000000
|
|
||||||
sntp_state 0
|
sntp_state 0
|
||||||
system_unixtime 1762012743
|
system_unixtime 1735242392
|
||||||
uptime_seconds 465229
|
uptime_seconds 40
|
||||||
wifi_rssi -72
|
wifi_rssi -74
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Status Encoding
|
#### Status Encoding
|
||||||
|
|||||||
@ -1,391 +1,22 @@
|
|||||||
menu "Smart Oil Heating Control System"
|
menu "Smart Oil Heating Control System"
|
||||||
|
|
||||||
menu "WiFi Configuration"
|
config SSID
|
||||||
config SSID
|
string "SSID"
|
||||||
string "WiFi SSID"
|
default "my WiFi SSID"
|
||||||
default "my WiFi SSID"
|
config WIFI_PASSWORD
|
||||||
help
|
string "WIFI_PASSWORD"
|
||||||
The SSID of the WiFi network to connect to.
|
default "my WIFI Password"
|
||||||
|
config STATIC_IP_ADDR
|
||||||
config WIFI_PASSWORD
|
string "Static IPv4 address"
|
||||||
string "WiFi Password"
|
default "192.168.0.42"
|
||||||
default "my WIFI Password"
|
config STATIC_IP_NETMASK
|
||||||
help
|
string "Static IPv4 netmask"
|
||||||
The password for the WiFi network.
|
default "255.255.0.0"
|
||||||
|
config STATIC_GATEWAY_IP_ADDR
|
||||||
config STATIC_IP_ADDR
|
string "Static IPv4 gateway address"
|
||||||
string "Static IPv4 address"
|
default "192.168.0.1"
|
||||||
default "192.168.0.42"
|
config SNTP_SERVER_IP_ADDR
|
||||||
help
|
string "SNTP IPv4 server address"
|
||||||
Static IP address for the ESP32.
|
default "192.168.0.1"
|
||||||
|
|
||||||
config STATIC_IP_NETMASK
|
|
||||||
string "Static IPv4 netmask"
|
|
||||||
default "255.255.0.0"
|
|
||||||
help
|
|
||||||
Network mask for the static IP configuration.
|
|
||||||
|
|
||||||
config STATIC_GATEWAY_IP_ADDR
|
|
||||||
string "Static IPv4 gateway address"
|
|
||||||
default "192.168.0.1"
|
|
||||||
help
|
|
||||||
Gateway IP address for network routing.
|
|
||||||
|
|
||||||
config SNTP_SERVER_IP_ADDR
|
|
||||||
string "SNTP server address"
|
|
||||||
default "192.168.0.1"
|
|
||||||
help
|
|
||||||
NTP server address for time synchronization.
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
menu "GPIO Configuration"
|
|
||||||
menu "Input GPIOs"
|
|
||||||
config GPIO_BURNER_FAULT
|
|
||||||
int "Burner fault input GPIO"
|
|
||||||
range 0 39
|
|
||||||
default 19
|
|
||||||
help
|
|
||||||
GPIO pin connected to the burner fault signal.
|
|
||||||
|
|
||||||
config GPIO_DS18B20_ONEWIRE
|
|
||||||
int "DS18B20 1-Wire bus GPIO"
|
|
||||||
range 0 39
|
|
||||||
default 4
|
|
||||||
help
|
|
||||||
GPIO pin for the 1-Wire bus (DS18B20 temperature sensors).
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
menu "Output GPIOs"
|
|
||||||
config GPIO_CIRCULATION_PUMP
|
|
||||||
int "Circulation pump output GPIO"
|
|
||||||
range 0 39
|
|
||||||
default 27
|
|
||||||
help
|
|
||||||
GPIO pin to control the circulation pump relay.
|
|
||||||
|
|
||||||
config GPIO_BURNER
|
|
||||||
int "Burner control output GPIO"
|
|
||||||
range 0 39
|
|
||||||
default 14
|
|
||||||
help
|
|
||||||
GPIO pin to control the burner relay.
|
|
||||||
|
|
||||||
config GPIO_SAFETY_CONTACT
|
|
||||||
int "Safety contact output GPIO"
|
|
||||||
range 0 39
|
|
||||||
default 12
|
|
||||||
help
|
|
||||||
GPIO pin for the safety contact relay (main power to burner).
|
|
||||||
endmenu
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
menu "1-Wire Sensor Addresses"
|
|
||||||
config ONEWIRE_ADDR_CHAMBER_TEMP
|
|
||||||
hex "Chamber temperature sensor address"
|
|
||||||
default 0xd00000108cd01d28
|
|
||||||
help
|
|
||||||
64-bit 1-Wire address of the chamber temperature sensor.
|
|
||||||
|
|
||||||
config ONEWIRE_ADDR_OUTDOOR_TEMP
|
|
||||||
hex "Outdoor temperature sensor address"
|
|
||||||
default 0xd70000108a9b9128
|
|
||||||
help
|
|
||||||
64-bit 1-Wire address of the outdoor temperature sensor.
|
|
||||||
|
|
||||||
config ONEWIRE_ADDR_INLET_FLOW_TEMP
|
|
||||||
hex "Inlet flow temperature sensor address"
|
|
||||||
default 0x410000108b8c0628
|
|
||||||
help
|
|
||||||
64-bit 1-Wire address of the inlet flow temperature sensor.
|
|
||||||
|
|
||||||
config ONEWIRE_ADDR_RETURN_FLOW_TEMP
|
|
||||||
hex "Return flow temperature sensor address"
|
|
||||||
default 0x90000108cc77c28
|
|
||||||
help
|
|
||||||
64-bit 1-Wire address of the return flow temperature sensor.
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
menu "Temperature Control Settings"
|
|
||||||
menu "Target Temperatures"
|
|
||||||
config TEMP_RETURN_FLOW_LOWER_LIMIT_DAY
|
|
||||||
int "Return flow lower limit (day) [°C x 10]"
|
|
||||||
range 150 500
|
|
||||||
default 300
|
|
||||||
help
|
|
||||||
Minimum return flow temperature during day mode in 0.1°C units.
|
|
||||||
Example: 300 = 30.0°C
|
|
||||||
|
|
||||||
config TEMP_RETURN_FLOW_LOWER_LIMIT_NIGHT
|
|
||||||
int "Return flow lower limit (night) [°C x 10]"
|
|
||||||
range 150 500
|
|
||||||
default 250
|
|
||||||
help
|
|
||||||
Minimum return flow temperature during night mode in 0.1°C units.
|
|
||||||
Example: 250 = 25.0°C
|
|
||||||
|
|
||||||
config TEMP_CHAMBER_TARGET
|
|
||||||
int "Chamber target temperature [°C x 10]"
|
|
||||||
range 500 950
|
|
||||||
default 800
|
|
||||||
help
|
|
||||||
Maximum chamber temperature target in 0.1°C units.
|
|
||||||
Example: 800 = 80.0°C
|
|
||||||
|
|
||||||
config TEMP_CHAMBER_THRESHOLD
|
|
||||||
int "Chamber temperature threshold [°C x 10]"
|
|
||||||
range 300 700
|
|
||||||
default 450
|
|
||||||
help
|
|
||||||
Minimum chamber temperature to enable burner in 0.1°C units.
|
|
||||||
Example: 450 = 45.0°C
|
|
||||||
|
|
||||||
config TEMP_CIRCULATION_PUMP_THRESHOLD
|
|
||||||
int "Circulation pump threshold [°C x 10]"
|
|
||||||
range 200 500
|
|
||||||
default 300
|
|
||||||
help
|
|
||||||
Minimum chamber temperature to enable circulation pump in 0.1°C units.
|
|
||||||
Example: 300 = 30.0°C
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
menu "Summer Mode Settings"
|
|
||||||
config TEMP_SUMMER_MODE_HIGH
|
|
||||||
int "Summer mode activation threshold [°C x 10]"
|
|
||||||
range 150 300
|
|
||||||
default 200
|
|
||||||
help
|
|
||||||
Outdoor temperature above which summer mode activates in 0.1°C units.
|
|
||||||
Example: 200 = 20.0°C
|
|
||||||
|
|
||||||
config TEMP_SUMMER_MODE_LOW
|
|
||||||
int "Summer mode deactivation threshold [°C x 10]"
|
|
||||||
range 100 250
|
|
||||||
default 150
|
|
||||||
help
|
|
||||||
Outdoor temperature below which summer mode deactivates in 0.1°C units.
|
|
||||||
Example: 150 = 15.0°C
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
config BURNER_FAULT_DETECTION_SECONDS
|
|
||||||
int "Burner fault detection timeout (seconds)"
|
|
||||||
range 60 600
|
|
||||||
default 240
|
|
||||||
help
|
|
||||||
Time in seconds to wait before checking for burner fault after enabling.
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
menu "Sensor Limits"
|
|
||||||
menu "Chamber Temperature Limits"
|
|
||||||
config SENSOR_LIMIT_CHAMBER_MAX
|
|
||||||
int "Chamber sensor maximum [°C x 10]"
|
|
||||||
range 500 1200
|
|
||||||
default 950
|
|
||||||
help
|
|
||||||
Maximum valid chamber temperature reading in 0.1°C units.
|
|
||||||
|
|
||||||
config SENSOR_LIMIT_CHAMBER_MIN
|
|
||||||
int "Chamber sensor minimum [°C x 10]"
|
|
||||||
range -400 100
|
|
||||||
default -100
|
|
||||||
help
|
|
||||||
Minimum valid chamber temperature reading in 0.1°C units.
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
menu "Outdoor Temperature Limits"
|
|
||||||
config SENSOR_LIMIT_OUTDOOR_MAX
|
|
||||||
int "Outdoor sensor maximum [°C x 10]"
|
|
||||||
range 300 600
|
|
||||||
default 450
|
|
||||||
help
|
|
||||||
Maximum valid outdoor temperature reading in 0.1°C units.
|
|
||||||
|
|
||||||
config SENSOR_LIMIT_OUTDOOR_MIN
|
|
||||||
int "Outdoor sensor minimum [°C x 10]"
|
|
||||||
range -500 0
|
|
||||||
default -200
|
|
||||||
help
|
|
||||||
Minimum valid outdoor temperature reading in 0.1°C units.
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
menu "Inlet Flow Temperature Limits"
|
|
||||||
config SENSOR_LIMIT_INLET_MAX
|
|
||||||
int "Inlet flow sensor maximum [°C x 10]"
|
|
||||||
range 500 1200
|
|
||||||
default 950
|
|
||||||
help
|
|
||||||
Maximum valid inlet flow temperature reading in 0.1°C units.
|
|
||||||
|
|
||||||
config SENSOR_LIMIT_INLET_MIN
|
|
||||||
int "Inlet flow sensor minimum [°C x 10]"
|
|
||||||
range -400 100
|
|
||||||
default -100
|
|
||||||
help
|
|
||||||
Minimum valid inlet flow temperature reading in 0.1°C units.
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
menu "Return Flow Temperature Limits"
|
|
||||||
config SENSOR_LIMIT_RETURN_MAX
|
|
||||||
int "Return flow sensor maximum [°C x 10]"
|
|
||||||
range 500 1200
|
|
||||||
default 950
|
|
||||||
help
|
|
||||||
Maximum valid return flow temperature reading in 0.1°C units.
|
|
||||||
|
|
||||||
config SENSOR_LIMIT_RETURN_MIN
|
|
||||||
int "Return flow sensor minimum [°C x 10]"
|
|
||||||
range -400 100
|
|
||||||
default -100
|
|
||||||
help
|
|
||||||
Minimum valid return flow temperature reading in 0.1°C units.
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
config SENSOR_GRACE_PERIOD_MINUTES
|
|
||||||
int "Sensor unchanged grace period (minutes)"
|
|
||||||
range 1 120
|
|
||||||
default 30
|
|
||||||
help
|
|
||||||
Maximum time in minutes a sensor can report unchanged values
|
|
||||||
before being flagged as faulty.
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
menu "Damping Factors"
|
|
||||||
config DAMPING_FACTOR_WARMER
|
|
||||||
int "Damping factor warmer [x 0.00001]"
|
|
||||||
range 1 100
|
|
||||||
default 1
|
|
||||||
help
|
|
||||||
Damping factor for rising temperatures in units of 0.00001.
|
|
||||||
Example: 1 = 0.00001 (0.001%)
|
|
||||||
|
|
||||||
config DAMPING_FACTOR_COLDER
|
|
||||||
int "Damping factor colder [x 0.00001]"
|
|
||||||
range 1 100
|
|
||||||
default 5
|
|
||||||
help
|
|
||||||
Damping factor for falling temperatures in units of 0.00001.
|
|
||||||
Example: 5 = 0.00005 (0.005%)
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
menu "Heating Schedule"
|
|
||||||
menu "Weekday Schedule (Monday-Thursday)"
|
|
||||||
config SCHEDULE_WEEKDAY_DAY_START_HOUR
|
|
||||||
int "Day mode start hour"
|
|
||||||
range 0 23
|
|
||||||
default 4
|
|
||||||
help
|
|
||||||
Hour when day mode starts on weekdays (24h format).
|
|
||||||
|
|
||||||
config SCHEDULE_WEEKDAY_DAY_START_MINUTE
|
|
||||||
int "Day mode start minute"
|
|
||||||
range 0 59
|
|
||||||
default 45
|
|
||||||
help
|
|
||||||
Minute when day mode starts on weekdays.
|
|
||||||
|
|
||||||
config SCHEDULE_WEEKDAY_NIGHT_START_HOUR
|
|
||||||
int "Night mode start hour"
|
|
||||||
range 0 23
|
|
||||||
default 22
|
|
||||||
help
|
|
||||||
Hour when night mode starts on weekdays (24h format).
|
|
||||||
|
|
||||||
config SCHEDULE_WEEKDAY_NIGHT_START_MINUTE
|
|
||||||
int "Night mode start minute"
|
|
||||||
range 0 59
|
|
||||||
default 0
|
|
||||||
help
|
|
||||||
Minute when night mode starts on weekdays.
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
menu "Friday Schedule"
|
|
||||||
config SCHEDULE_FRIDAY_DAY_START_HOUR
|
|
||||||
int "Day mode start hour"
|
|
||||||
range 0 23
|
|
||||||
default 4
|
|
||||||
help
|
|
||||||
Hour when day mode starts on Friday (24h format).
|
|
||||||
|
|
||||||
config SCHEDULE_FRIDAY_DAY_START_MINUTE
|
|
||||||
int "Day mode start minute"
|
|
||||||
range 0 59
|
|
||||||
default 45
|
|
||||||
help
|
|
||||||
Minute when day mode starts on Friday.
|
|
||||||
|
|
||||||
config SCHEDULE_FRIDAY_NIGHT_START_HOUR
|
|
||||||
int "Night mode start hour"
|
|
||||||
range 0 23
|
|
||||||
default 23
|
|
||||||
help
|
|
||||||
Hour when night mode starts on Friday (24h format).
|
|
||||||
|
|
||||||
config SCHEDULE_FRIDAY_NIGHT_START_MINUTE
|
|
||||||
int "Night mode start minute"
|
|
||||||
range 0 59
|
|
||||||
default 0
|
|
||||||
help
|
|
||||||
Minute when night mode starts on Friday.
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
menu "Saturday Schedule"
|
|
||||||
config SCHEDULE_SATURDAY_DAY_START_HOUR
|
|
||||||
int "Day mode start hour"
|
|
||||||
range 0 23
|
|
||||||
default 6
|
|
||||||
help
|
|
||||||
Hour when day mode starts on Saturday (24h format).
|
|
||||||
|
|
||||||
config SCHEDULE_SATURDAY_DAY_START_MINUTE
|
|
||||||
int "Day mode start minute"
|
|
||||||
range 0 59
|
|
||||||
default 45
|
|
||||||
help
|
|
||||||
Minute when day mode starts on Saturday.
|
|
||||||
|
|
||||||
config SCHEDULE_SATURDAY_NIGHT_START_HOUR
|
|
||||||
int "Night mode start hour"
|
|
||||||
range 0 23
|
|
||||||
default 23
|
|
||||||
help
|
|
||||||
Hour when night mode starts on Saturday (24h format).
|
|
||||||
|
|
||||||
config SCHEDULE_SATURDAY_NIGHT_START_MINUTE
|
|
||||||
int "Night mode start minute"
|
|
||||||
range 0 59
|
|
||||||
default 30
|
|
||||||
help
|
|
||||||
Minute when night mode starts on Saturday.
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
menu "Sunday Schedule"
|
|
||||||
config SCHEDULE_SUNDAY_DAY_START_HOUR
|
|
||||||
int "Day mode start hour"
|
|
||||||
range 0 23
|
|
||||||
default 6
|
|
||||||
help
|
|
||||||
Hour when day mode starts on Sunday (24h format).
|
|
||||||
|
|
||||||
config SCHEDULE_SUNDAY_DAY_START_MINUTE
|
|
||||||
int "Day mode start minute"
|
|
||||||
range 0 59
|
|
||||||
default 45
|
|
||||||
help
|
|
||||||
Minute when day mode starts on Sunday.
|
|
||||||
|
|
||||||
config SCHEDULE_SUNDAY_NIGHT_START_HOUR
|
|
||||||
int "Night mode start hour"
|
|
||||||
range 0 23
|
|
||||||
default 22
|
|
||||||
help
|
|
||||||
Hour when night mode starts on Sunday (24h format).
|
|
||||||
|
|
||||||
config SCHEDULE_SUNDAY_NIGHT_START_MINUTE
|
|
||||||
int "Night mode start minute"
|
|
||||||
range 0 59
|
|
||||||
default 30
|
|
||||||
help
|
|
||||||
Minute when night mode starts on Sunday.
|
|
||||||
endmenu
|
|
||||||
endmenu
|
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|||||||
444
main/control.c
444
main/control.c
@ -1,104 +1,47 @@
|
|||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "esp_log.h"
|
||||||
#include "control.h"
|
#include "control.h"
|
||||||
#include "inputs.h"
|
|
||||||
#include "outputs.h"
|
#include "outputs.h"
|
||||||
|
#include "inputs.h"
|
||||||
#include "safety.h"
|
#include "safety.h"
|
||||||
#include "sntp.h"
|
#include "sntp.h"
|
||||||
|
|
||||||
#include "esp_log.h"
|
#define PERIODIC_INTERVAL 1U // run control loop every 1sec
|
||||||
#include "esp_timer.h"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
|
|
||||||
#define PERIODIC_INTERVAL 1U // Run control loop every 1 second
|
#define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY 30.0
|
||||||
|
#define RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT 25.0
|
||||||
|
#define CHAMPER_TEMPERATURE_TARGET 80.0
|
||||||
|
#define BURNER_FAULT_DETECTION_THRESHOLD (60U * 3U) // Detect burner fault if after 3 minutes no burner start detected
|
||||||
|
|
||||||
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
|
|
||||||
static const sControlDay gControlTable[] = {
|
static sControlDay aControlTable[] = {
|
||||||
{MONDAY,
|
{MONDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMPER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMPER_TEMPERATURE_TARGET}}},
|
||||||
2U,
|
{TUESDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMPER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMPER_TEMPERATURE_TARGET}}},
|
||||||
{{{CONFIG_SCHEDULE_WEEKDAY_DAY_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_DAY_START_MINUTE},
|
{WEDNESDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMPER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMPER_TEMPERATURE_TARGET}}},
|
||||||
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
|
{THURSDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMPER_TEMPERATURE_TARGET}, {{22, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMPER_TEMPERATURE_TARGET}}},
|
||||||
CHAMBER_TEMPERATURE_TARGET},
|
{FRIDAY, 2U, {{{4, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMPER_TEMPERATURE_TARGET}, {{23, 0}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMPER_TEMPERATURE_TARGET}}},
|
||||||
{{CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_MINUTE},
|
{SATURDAY, 2U, {{{6, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMPER_TEMPERATURE_TARGET}, {{23, 30}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMPER_TEMPERATURE_TARGET}}},
|
||||||
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
|
{SUNDAY, 2U, {{{6, 45}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY, CHAMPER_TEMPERATURE_TARGET}, {{22, 30}, RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT, CHAMPER_TEMPERATURE_TARGET}}},
|
||||||
CHAMBER_TEMPERATURE_TARGET}}},
|
};
|
||||||
{TUESDAY,
|
|
||||||
2U,
|
|
||||||
{{{CONFIG_SCHEDULE_WEEKDAY_DAY_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_DAY_START_MINUTE},
|
|
||||||
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
|
|
||||||
CHAMBER_TEMPERATURE_TARGET},
|
|
||||||
{{CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_MINUTE},
|
|
||||||
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
|
|
||||||
CHAMBER_TEMPERATURE_TARGET}}},
|
|
||||||
{WEDNESDAY,
|
|
||||||
2U,
|
|
||||||
{{{CONFIG_SCHEDULE_WEEKDAY_DAY_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_DAY_START_MINUTE},
|
|
||||||
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
|
|
||||||
CHAMBER_TEMPERATURE_TARGET},
|
|
||||||
{{CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_MINUTE},
|
|
||||||
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
|
|
||||||
CHAMBER_TEMPERATURE_TARGET}}},
|
|
||||||
{THURSDAY,
|
|
||||||
2U,
|
|
||||||
{{{CONFIG_SCHEDULE_WEEKDAY_DAY_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_DAY_START_MINUTE},
|
|
||||||
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
|
|
||||||
CHAMBER_TEMPERATURE_TARGET},
|
|
||||||
{{CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_WEEKDAY_NIGHT_START_MINUTE},
|
|
||||||
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
|
|
||||||
CHAMBER_TEMPERATURE_TARGET}}},
|
|
||||||
{FRIDAY,
|
|
||||||
2U,
|
|
||||||
{{{CONFIG_SCHEDULE_FRIDAY_DAY_START_HOUR, CONFIG_SCHEDULE_FRIDAY_DAY_START_MINUTE},
|
|
||||||
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
|
|
||||||
CHAMBER_TEMPERATURE_TARGET},
|
|
||||||
{{CONFIG_SCHEDULE_FRIDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_FRIDAY_NIGHT_START_MINUTE},
|
|
||||||
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
|
|
||||||
CHAMBER_TEMPERATURE_TARGET}}},
|
|
||||||
{SATURDAY,
|
|
||||||
2U,
|
|
||||||
{{{CONFIG_SCHEDULE_SATURDAY_DAY_START_HOUR, CONFIG_SCHEDULE_SATURDAY_DAY_START_MINUTE},
|
|
||||||
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
|
|
||||||
CHAMBER_TEMPERATURE_TARGET},
|
|
||||||
{{CONFIG_SCHEDULE_SATURDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_SATURDAY_NIGHT_START_MINUTE},
|
|
||||||
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
|
|
||||||
CHAMBER_TEMPERATURE_TARGET}}},
|
|
||||||
{SUNDAY,
|
|
||||||
2U,
|
|
||||||
{{{CONFIG_SCHEDULE_SUNDAY_DAY_START_HOUR, CONFIG_SCHEDULE_SUNDAY_DAY_START_MINUTE},
|
|
||||||
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_DAY,
|
|
||||||
CHAMBER_TEMPERATURE_TARGET},
|
|
||||||
{{CONFIG_SCHEDULE_SUNDAY_NIGHT_START_HOUR, CONFIG_SCHEDULE_SUNDAY_NIGHT_START_MINUTE},
|
|
||||||
RETURN_FLOW_TEMPERATURE_LOWER_LIMIT_NIGHT,
|
|
||||||
CHAMBER_TEMPERATURE_TARGET}}},
|
|
||||||
};
|
|
||||||
static sControlTemperatureEntry gCurrentControlEntry =
|
|
||||||
gControlTable[0].aTemperatureEntries[0];
|
|
||||||
static SemaphoreHandle_t xMutexAccessControl = NULL;
|
|
||||||
|
|
||||||
// Function prototypes
|
|
||||||
void taskControl(void *pvParameters);
|
void taskControl(void *pvParameters);
|
||||||
void findControlCurrentTemperatureEntry(void);
|
eControlWeekday getCurrentWeekday(void);
|
||||||
void setControlState(eControlState state);
|
sControlTemperatureEntry getCurrentTemperatureEntry(void);
|
||||||
|
|
||||||
void initControl(void)
|
void initControl(void)
|
||||||
{
|
{
|
||||||
|
BaseType_t taskCreated = xTaskCreate(
|
||||||
xMutexAccessControl = xSemaphoreCreateRecursiveMutex();
|
taskControl, // Function to implement the task
|
||||||
if (xMutexAccessControl == NULL)
|
"taskControl", // Task name
|
||||||
{
|
8192, // Stack size (in words, not bytes)
|
||||||
ESP_LOGE(TAG, "Unable to create mutex");
|
NULL, // Parameters to the task function (none in this case)
|
||||||
}
|
5, // Task priority (higher number = higher priority)
|
||||||
xSemaphoreGiveRecursive(xMutexAccessControl);
|
NULL // Task handle (optional)
|
||||||
|
);
|
||||||
BaseType_t taskCreated =
|
|
||||||
xTaskCreate(taskControl, // Function to implement the task
|
|
||||||
"taskControl", // Task name
|
|
||||||
8192, // Stack size (in words, not bytes)
|
|
||||||
NULL, // Parameters to the task function (none in this case)
|
|
||||||
5, // Task priority (higher number = higher priority)
|
|
||||||
NULL // Task handle (optional)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (taskCreated == pdPASS)
|
if (taskCreated == pdPASS)
|
||||||
{
|
{
|
||||||
@ -113,293 +56,182 @@ void initControl(void)
|
|||||||
void taskControl(void *pvParameters)
|
void taskControl(void *pvParameters)
|
||||||
{
|
{
|
||||||
bool bHeatingInAction = false;
|
bool bHeatingInAction = false;
|
||||||
bool bSummerMode = false;
|
bool bBurnerFaultDetected = false;
|
||||||
eBurnerState burnerState = BURNER_UNKNOWN;
|
|
||||||
int64_t i64BurnerEnableTimestamp = esp_timer_get_time();
|
int64_t i64BurnerEnableTimestamp = esp_timer_get_time();
|
||||||
|
|
||||||
|
time_t now;
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
|
// Get the current time
|
||||||
|
time(&now);
|
||||||
|
ESP_LOGW(TAG, "Control loop time: %lli", now);
|
||||||
vTaskDelay(PERIODIC_INTERVAL * 1000U / portTICK_PERIOD_MS);
|
vTaskDelay(PERIODIC_INTERVAL * 1000U / portTICK_PERIOD_MS);
|
||||||
|
|
||||||
// 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!");
|
||||||
setControlState(CONTROL_FAULT_SAFETY);
|
sControlState = CONTROL_FAULT_SAFETY;
|
||||||
if (bHeatingInAction)
|
if (bHeatingInAction == true)
|
||||||
{
|
{
|
||||||
ESP_LOGW(TAG, "Disabling burner due to safety fault");
|
ESP_LOGW(TAG, "Control not possible due to safety fault: Disable burner");
|
||||||
bHeatingInAction = false;
|
bHeatingInAction = false;
|
||||||
|
setCirculationPumpState(ENABLED);
|
||||||
setBurnerState(DISABLED);
|
setBurnerState(DISABLED);
|
||||||
setSafetyControlState(ENABLED);
|
setSafetyControlState(ENABLED);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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!");
|
||||||
setControlState(CONTROL_FAULT_SNTP);
|
sControlState = CONTROL_FAULT_SNTP;
|
||||||
if (bHeatingInAction)
|
if (bHeatingInAction == true)
|
||||||
{
|
{
|
||||||
ESP_LOGW(TAG, "Disabling burner due to SNTP fault");
|
ESP_LOGW(TAG, "Control not possible due to sntp fault: Disable burner");
|
||||||
bHeatingInAction = false;
|
bHeatingInAction = false;
|
||||||
|
setCirculationPumpState(ENABLED);
|
||||||
setBurnerState(DISABLED);
|
setBurnerState(DISABLED);
|
||||||
setSafetyControlState(ENABLED);
|
setSafetyControlState(ENABLED);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
findControlCurrentTemperatureEntry();
|
sControlTemperatureEntry currentControlEntry = getCurrentTemperatureEntry();
|
||||||
|
// ESP_LOGI(TAG, "Control Entry Hour: %i Minute: %i ChamberTemp: %lf ReturnFlowTemp: %lf", currentControlEntry.timestamp.hour, currentControlEntry.timestamp.minute, currentControlEntry.fChamberTemperature, currentControlEntry.fReturnFlowTemperature);
|
||||||
|
|
||||||
if (getOutdoorTemperature().fDampedValue >=
|
if (bHeatingInAction == true)
|
||||||
SUMMER_MODE_TEMPERATURE_THRESHOLD_HIGH)
|
|
||||||
{
|
{
|
||||||
bSummerMode = true;
|
if ((getChamberTemperature().fCurrentValue >= currentControlEntry.fChamberTemperature) || (getChamberTemperature().predict60s.fValue >= currentControlEntry.fChamberTemperature))
|
||||||
}
|
|
||||||
else if (getOutdoorTemperature().fDampedValue <=
|
|
||||||
SUMMER_MODE_TEMPERATURE_THRESHOLD_LOW)
|
|
||||||
{
|
|
||||||
bSummerMode = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable burner if outdoor temperature is low and return flow temperature
|
|
||||||
// is cooled down
|
|
||||||
if (!bHeatingInAction && (burnerState != BURNER_FAULT))
|
|
||||||
{
|
|
||||||
if (bSummerMode)
|
|
||||||
{
|
{
|
||||||
// ESP_LOGI(TAG, "Outdoor temperature too warm: Disabling heating");
|
ESP_LOGI(TAG, "Chamber Target Temperature reached: Disable burner");
|
||||||
|
bHeatingInAction = false;
|
||||||
|
setCirculationPumpState(ENABLED);
|
||||||
setBurnerState(DISABLED);
|
setBurnerState(DISABLED);
|
||||||
setSafetyControlState(DISABLED);
|
|
||||||
setControlState(CONTROL_OUTDOOR_TOO_WARM);
|
|
||||||
}
|
|
||||||
else if ((getReturnFlowTemperature().average60s.fValue <=
|
|
||||||
getControlCurrentTemperatureEntry().fReturnFlowTemperature) &&
|
|
||||||
(getChamberTemperature().fCurrentValue <=
|
|
||||||
CHAMBER_TEMPERATURE_THRESHOLD))
|
|
||||||
{
|
|
||||||
ESP_LOGI(TAG,
|
|
||||||
"Enabling burner: Return flow temperature target reached");
|
|
||||||
burnerState = BURNER_UNKNOWN;
|
|
||||||
bHeatingInAction = true;
|
|
||||||
setBurnerState(ENABLED);
|
|
||||||
setSafetyControlState(ENABLED);
|
setSafetyControlState(ENABLED);
|
||||||
i64BurnerEnableTimestamp = esp_timer_get_time();
|
|
||||||
setControlState(CONTROL_HEATING);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// ESP_LOGI(TAG, "Return flow temperature too warm: Disabling heating");
|
if (bHeatingInAction)
|
||||||
setControlState(CONTROL_RETURN_FLOW_TOO_WARM);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable burner if target temperature is reached or a fault occurred
|
|
||||||
if (bHeatingInAction)
|
|
||||||
{
|
|
||||||
if ((getChamberTemperature().fCurrentValue >=
|
|
||||||
getControlCurrentTemperatureEntry().fChamberTemperature) ||
|
|
||||||
(getChamberTemperature().predict60s.fValue >=
|
|
||||||
getControlCurrentTemperatureEntry().fChamberTemperature))
|
|
||||||
{
|
|
||||||
ESP_LOGI(TAG, "Chamber target temperature reached: Disabling burner");
|
|
||||||
bHeatingInAction = false;
|
|
||||||
setBurnerState(DISABLED);
|
|
||||||
setSafetyControlState(ENABLED);
|
|
||||||
}
|
|
||||||
else if (esp_timer_get_time() - i64BurnerEnableTimestamp >=
|
|
||||||
BURNER_FAULT_DETECTION_THRESHOLD * 1000000U)
|
|
||||||
{
|
|
||||||
if (burnerState == BURNER_UNKNOWN)
|
|
||||||
{
|
{
|
||||||
if (getBurnerError() == FAULT)
|
int64_t i64Delta = esp_timer_get_time() - i64BurnerEnableTimestamp;
|
||||||
|
|
||||||
|
if ((i64Delta / 1000000U) >= BURNER_FAULT_DETECTION_THRESHOLD)
|
||||||
{
|
{
|
||||||
// ESP_LOGW(TAG, "Burner fault detected: Disabling burner");
|
if (getBurnerError() == FAULT)
|
||||||
bHeatingInAction = false;
|
{
|
||||||
burnerState = BURNER_FAULT;
|
ESP_LOGW(TAG, "Detected burner fault after %lli seconds!", (i64Delta / 1000000U));
|
||||||
setControlState(CONTROL_FAULT_BURNER);
|
ESP_LOGW(TAG, "Control not possible due to burner fault: Disable burner");
|
||||||
setBurnerState(DISABLED);
|
sControlState = CONTROL_FAULT_BURNER;
|
||||||
setSafetyControlState(ENABLED);
|
bHeatingInAction = false;
|
||||||
}
|
bBurnerFaultDetected = true;
|
||||||
else
|
setCirculationPumpState(ENABLED);
|
||||||
{
|
setBurnerState(DISABLED);
|
||||||
// ESP_LOGI(TAG, "No burner fault detected: Marking burner as
|
setSafetyControlState(ENABLED);
|
||||||
// fired");
|
}
|
||||||
burnerState = BURNER_FIRED;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manage circulation pump
|
if ((bHeatingInAction == false) && (bBurnerFaultDetected == false))
|
||||||
if (getChamberTemperature().fCurrentValue <=
|
|
||||||
CIRCULATION_PUMP_TEMPERATURE_THRESHOLD)
|
|
||||||
{
|
{
|
||||||
// ESP_LOGI(TAG, "Burner cooled down: Disabling circulation pump");
|
if ((getReturnFlowTemperature().average60s.fValue <= currentControlEntry.fReturnFlowTemperature) && (getChamberTemperature().fCurrentValue <= 45.0))
|
||||||
setCirculationPumpState(DISABLED);
|
{
|
||||||
|
ESP_LOGI(TAG, "Return Flow Target Temperature reached: Enable Burner");
|
||||||
|
bHeatingInAction = true;
|
||||||
|
setCirculationPumpState(ENABLED);
|
||||||
|
setBurnerState(ENABLED);
|
||||||
|
setSafetyControlState(ENABLED);
|
||||||
|
i64BurnerEnableTimestamp = esp_timer_get_time();
|
||||||
|
sControlState = CONTROL_HEATING;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sControlState = CONTROL_RETURN_FLOW_TOO_WARM;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// ESP_LOGI(TAG, "Burner heated: Enabling circulation pump");
|
|
||||||
setCirculationPumpState(ENABLED);
|
|
||||||
}
|
|
||||||
} // End of while(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 getControlState(void)
|
||||||
{
|
{
|
||||||
|
return sControlState;
|
||||||
|
}
|
||||||
|
|
||||||
eControlState ret = CONTROL_FAULT_SAFETY;
|
eControlWeekday getCurrentWeekday(void)
|
||||||
|
{
|
||||||
|
time_t now;
|
||||||
|
struct tm *timeinfo;
|
||||||
|
|
||||||
if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE)
|
// Get the current time
|
||||||
|
time(&now);
|
||||||
|
timeinfo = localtime(&now); // Convert to local time
|
||||||
|
|
||||||
|
// Get the day of the week (0 = Sunday, 1 = Monday, ..., 6 = Saturday)
|
||||||
|
int day = timeinfo->tm_wday;
|
||||||
|
|
||||||
|
// Adjust so that Monday = 0, Sunday = 6
|
||||||
|
if (day == 0)
|
||||||
{
|
{
|
||||||
ret = gControlState;
|
day = 6; // Sunday becomes 6
|
||||||
xSemaphoreGiveRecursive(xMutexAccessControl);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "Unable to take mutex: getControlState()");
|
day -= 1; // Shift other days to make Monday = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return (eControlWeekday)day;
|
||||||
}
|
}
|
||||||
|
|
||||||
eControlWeekday getControlCurrentWeekday(void)
|
sControlTemperatureEntry getCurrentTemperatureEntry(void)
|
||||||
{
|
{
|
||||||
// Get current time
|
sControlTemperatureEntry result = aControlTable[0].aTemperatureEntries[0];
|
||||||
|
eControlWeekday currentDay = getCurrentWeekday();
|
||||||
time_t now;
|
time_t now;
|
||||||
struct tm timeinfo;
|
struct tm timeinfo;
|
||||||
|
|
||||||
|
// Get the current time
|
||||||
time(&now);
|
time(&now);
|
||||||
|
// Convert to local time structure
|
||||||
localtime_r(&now, &timeinfo);
|
localtime_r(&now, &timeinfo);
|
||||||
|
// Extract hour and minute
|
||||||
|
int hour = timeinfo.tm_hour; // Hour (0-23)
|
||||||
|
int minute = timeinfo.tm_min; // Minute (0-59)u
|
||||||
|
|
||||||
int day = timeinfo.tm_wday;
|
// ESP_LOGI(TAG, "Current Day: %i Hour: %i Minute: %i", currentDay, hour, minute);
|
||||||
return (eControlWeekday)((day == 0) ? 6 : day - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
for (int i = 0; i < sizeof(aControlTable) / sizeof(aControlTable[0]); i++)
|
||||||
* @brief Finds the active temperature control entry for the current time.
|
|
||||||
*
|
|
||||||
* Searches through the weekly schedule to find the most recent entry
|
|
||||||
* that should be active at the current date/time. Falls back to the
|
|
||||||
* last entry in the week if no suitable entry is found.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @brief 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();
|
|
||||||
|
|
||||||
// Get current time
|
|
||||||
time_t now;
|
|
||||||
struct tm timeinfo;
|
|
||||||
time(&now);
|
|
||||||
localtime_r(&now, &timeinfo);
|
|
||||||
|
|
||||||
int currentHour = timeinfo.tm_hour;
|
|
||||||
int currentMinute = timeinfo.tm_min;
|
|
||||||
|
|
||||||
if (xSemaphoreTakeRecursive(xMutexAccessControl, pdMS_TO_TICKS(5000)) == pdTRUE)
|
|
||||||
{
|
{
|
||||||
|
/// loops through days
|
||||||
|
// ESP_LOGI(TAG, "Day %d: %d", i + 1, aControlTable[i].day);
|
||||||
|
// int numberOfEntries = aControlTable[i].entryCount;
|
||||||
|
// ESP_LOGI(TAG, "Number of entries: %i", numberOfEntries);
|
||||||
|
|
||||||
// ESP_LOGI(TAG, "Searching for control entry - Day: %d, Time: %02d:%02d", currentDay, currentHour, currentMinute);
|
for (int j = 0; j < aControlTable[i].entryCount; j++)
|
||||||
|
|
||||||
// Search through all days and entries
|
|
||||||
for (int dayIndex = 0; dayIndex < 7; dayIndex++)
|
|
||||||
{
|
{
|
||||||
const sControlDay *day = &gControlTable[dayIndex];
|
if ((aControlTable[i].day) > currentDay)
|
||||||
|
|
||||||
for (int entryIndex = 0; entryIndex < day->entryCount; entryIndex++)
|
|
||||||
{
|
{
|
||||||
const sControlTemperatureEntry *entry = &day->aTemperatureEntries[entryIndex];
|
// ESP_LOGI(TAG, "DAY Return Control Entry Day: %i Hour: %i Minute: %i ChamberTemp: %lf ReturnFlowTemp: %lf", aControlTable[i].day, aControlTable[i].aTemperatureEntries[j].timestamp.hour, aControlTable[i].aTemperatureEntries[j].timestamp.minute, aControlTable[i].aTemperatureEntries[j].fChamberTemperature, aControlTable[i].aTemperatureEntries[j].fReturnFlowTemperature);
|
||||||
|
return result;
|
||||||
// Check if this entry is in the future (next active entry)
|
|
||||||
bool isFutureDay = (day->day > currentDay);
|
|
||||||
bool isTodayFutureTime = (day->day == currentDay) &&
|
|
||||||
((entry->timestamp.hour > currentHour) ||
|
|
||||||
(entry->timestamp.hour == currentHour &&
|
|
||||||
entry->timestamp.minute > currentMinute));
|
|
||||||
|
|
||||||
if (isFutureDay || isTodayFutureTime)
|
|
||||||
{
|
|
||||||
|
|
||||||
// Found next scheduled entry, so determine the previous (active) one
|
|
||||||
if (entryIndex > 0)
|
|
||||||
{
|
|
||||||
// Use previous entry from same day
|
|
||||||
gCurrentControlEntry = day->aTemperatureEntries[entryIndex - 1];
|
|
||||||
}
|
|
||||||
else if (dayIndex > 0)
|
|
||||||
{
|
|
||||||
// Use last entry from previous day
|
|
||||||
const sControlDay *previousDay = &gControlTable[dayIndex - 1];
|
|
||||||
gCurrentControlEntry = previousDay->aTemperatureEntries[previousDay->entryCount - 1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// First entry of the week - wrap to last entry of Sunday
|
|
||||||
const sControlDay *sunday = &gControlTable[6];
|
|
||||||
gCurrentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1];
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
ESP_LOGI(TAG, "Active entry found - Time: %02d:%02d, "
|
|
||||||
"Return Temp: %lf, Chamber Temp: %lf",
|
|
||||||
gCurrentControlEntry.timestamp.hour,
|
|
||||||
gCurrentControlEntry.timestamp.minute,
|
|
||||||
gCurrentControlEntry.fReturnFlowTemperature,
|
|
||||||
gCurrentControlEntry.fChamberTemperature);
|
|
||||||
*/
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((aControlTable[i].day == currentDay) && (aControlTable[i].aTemperatureEntries[j].timestamp.hour > hour))
|
||||||
|
{
|
||||||
|
// ESP_LOGI(TAG, "HOUR Return Control Entry Day: %i Hour: %i Minute: %i ChamberTemp: %lf ReturnFlowTemp: %lf", aControlTable[i].day, aControlTable[i].aTemperatureEntries[j].timestamp.hour, aControlTable[i].aTemperatureEntries[j].timestamp.minute, aControlTable[i].aTemperatureEntries[j].fChamberTemperature, aControlTable[i].aTemperatureEntries[j].fReturnFlowTemperature);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((aControlTable[i].day == currentDay) && (aControlTable[i].aTemperatureEntries[j].timestamp.hour == hour) && (aControlTable[i].aTemperatureEntries[j].timestamp.minute == minute))
|
||||||
|
{
|
||||||
|
// ESP_LOGI(TAG, "MINUTE Return Control Entry Day: %i Hour: %i Minute: %i ChamberTemp: %lf ReturnFlowTemp: %lf", aControlTable[i].day, aControlTable[i].aTemperatureEntries[j].timestamp.hour, aControlTable[i].aTemperatureEntries[j].timestamp.minute, aControlTable[i].aTemperatureEntries[j].fChamberTemperature, aControlTable[i].aTemperatureEntries[j].fReturnFlowTemperature);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ESP_LOGI(TAG, "SET Return Control Entry Day: %i Hour: %i Minute: %i ChamberTemp: %lf ReturnFlowTemp: %lf", aControlTable[i].day, aControlTable[i].aTemperatureEntries[j].timestamp.hour, aControlTable[i].aTemperatureEntries[j].timestamp.minute, aControlTable[i].aTemperatureEntries[j].fChamberTemperature, aControlTable[i].aTemperatureEntries[j].fReturnFlowTemperature);
|
||||||
|
result = aControlTable[i].aTemperatureEntries[j];
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we reached here, current time is after all entries this week
|
|
||||||
// Use the last entry (Sunday evening)
|
|
||||||
const sControlDay *sunday = &gControlTable[6];
|
|
||||||
gCurrentControlEntry = sunday->aTemperatureEntries[sunday->entryCount - 1];
|
|
||||||
|
|
||||||
// ESP_LOGI(TAG, "Using last entry of week - Time: %02d:%02d", gCurrentControlEntry.timestamp.hour, gCurrentControlEntry.timestamp.minute);
|
|
||||||
xSemaphoreGiveRecursive(xMutexAccessControl);
|
|
||||||
}
|
}
|
||||||
else
|
return result;
|
||||||
{
|
}
|
||||||
ESP_LOGE(TAG, "Unable to take mutex: findControlCurrentTemperatureEntry()");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sControlTemperatureEntry getControlCurrentTemperatureEntry(void)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@ -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,
|
||||||
@ -26,13 +14,6 @@ typedef enum _ControlState
|
|||||||
CONTROL_FAULT_SNTP,
|
CONTROL_FAULT_SNTP,
|
||||||
} eControlState;
|
} eControlState;
|
||||||
|
|
||||||
typedef enum _BurnerState
|
|
||||||
{
|
|
||||||
BURNER_UNKNOWN, // Burner is disabled or state after enabling is still unkown
|
|
||||||
BURNER_FIRED, // Burner fired successfully
|
|
||||||
BURNER_FAULT // Burner was unable to fire successfully
|
|
||||||
} eBurnerState;
|
|
||||||
|
|
||||||
typedef enum _ControlWeekday
|
typedef enum _ControlWeekday
|
||||||
{
|
{
|
||||||
MONDAY,
|
MONDAY,
|
||||||
@ -66,5 +47,3 @@ typedef struct _ControlDay
|
|||||||
|
|
||||||
void initControl(void);
|
void initControl(void);
|
||||||
eControlState getControlState(void);
|
eControlState getControlState(void);
|
||||||
eControlWeekday getControlCurrentWeekday(void);
|
|
||||||
sControlTemperatureEntry getControlCurrentTemperatureEntry(void);
|
|
||||||
|
|||||||
105
main/inputs.c
105
main/inputs.c
@ -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 = 0x78000000c6c2f728;
|
||||||
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)
|
||||||
@ -94,23 +88,22 @@ void initMeasurement(sMeasurement *pMeasurement)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
pMeasurement->state = MEASUREMENT_FAULT;
|
pMeasurement->state = MEASUREMENT_FAULT;
|
||||||
pMeasurement->fCurrentValue = INITIALISATION_VALUE;
|
pMeasurement->fCurrentValue = 0.0f;
|
||||||
pMeasurement->fDampedValue = INITIALISATION_VALUE;
|
|
||||||
|
|
||||||
pMeasurement->average10s.fValue = INITIALISATION_VALUE;
|
pMeasurement->average10s.fValue = 0.0f;
|
||||||
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, AVG10_SAMPLE_SIZE);
|
||||||
|
|
||||||
pMeasurement->average60s.fValue = INITIALISATION_VALUE;
|
pMeasurement->average60s.fValue = 0.0f;
|
||||||
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, AVG60_SAMPLE_SIZE);
|
||||||
|
|
||||||
pMeasurement->predict60s.fValue = INITIALISATION_VALUE;
|
pMeasurement->predict60s.fValue = 0.0f;
|
||||||
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, PRED60_SAMPLE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateAverage(sMeasurement *pMeasurement)
|
void updateAverage(sMeasurement *pMeasurement)
|
||||||
@ -120,33 +113,26 @@ void updateAverage(sMeasurement *pMeasurement)
|
|||||||
|
|
||||||
// Average form the last 10sec
|
// 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) % AVG10_SAMPLE_SIZE;
|
||||||
|
|
||||||
if (pMeasurement->average10s.bufferCount < AVG10S_SAMPLE_SIZE)
|
if (pMeasurement->average10s.bufferCount < AVG10_SAMPLE_SIZE)
|
||||||
{
|
{
|
||||||
pMeasurement->average10s.bufferCount++;
|
pMeasurement->average10s.bufferCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = sum / pMeasurement->average10s.bufferCount;
|
||||||
{
|
|
||||||
pMeasurement->average10s.fValue = 0.0f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
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;
|
||||||
pMeasurement->average60s.bufferIndex = (pMeasurement->average60s.bufferIndex + 1) % AVG60S_SAMPLE_SIZE;
|
pMeasurement->average60s.bufferIndex = (pMeasurement->average60s.bufferIndex + 1) % AVG60_SAMPLE_SIZE;
|
||||||
|
|
||||||
if (pMeasurement->average60s.bufferCount < AVG60S_SAMPLE_SIZE)
|
if (pMeasurement->average60s.bufferCount < AVG60_SAMPLE_SIZE)
|
||||||
{
|
{
|
||||||
pMeasurement->average60s.bufferCount++;
|
pMeasurement->average60s.bufferCount++;
|
||||||
}
|
}
|
||||||
@ -157,32 +143,7 @@ void updateAverage(sMeasurement *pMeasurement)
|
|||||||
sum += pMeasurement->average60s.samples[i];
|
sum += pMeasurement->average60s.samples[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pMeasurement->average60s.bufferCount == 0U)
|
pMeasurement->average60s.fValue = sum / pMeasurement->average60s.bufferCount;
|
||||||
{
|
|
||||||
pMeasurement->average60s.fValue = 0.0f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pMeasurement->average60s.fValue = sum / pMeasurement->average60s.bufferCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Damped current value
|
|
||||||
if (pMeasurement->fDampedValue == INITIALISATION_VALUE)
|
|
||||||
{
|
|
||||||
pMeasurement->fDampedValue = pMeasurement->fCurrentValue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (pMeasurement->fCurrentValue > pMeasurement->fDampedValue)
|
|
||||||
{
|
|
||||||
pMeasurement->fDampedValue = pMeasurement->fDampedValue + (DAMPING_FACTOR_WARMER * (pMeasurement->fCurrentValue - pMeasurement->fDampedValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pMeasurement->fCurrentValue < pMeasurement->fDampedValue)
|
|
||||||
{
|
|
||||||
pMeasurement->fDampedValue = pMeasurement->fDampedValue - (DAMPING_FACTOR_COLDER * (pMeasurement->fDampedValue - pMeasurement->fCurrentValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void updatePrediction(sMeasurement *pMeasurement)
|
void updatePrediction(sMeasurement *pMeasurement)
|
||||||
@ -193,8 +154,8 @@ void updatePrediction(sMeasurement *pMeasurement)
|
|||||||
// Update predict60s buffer
|
// Update predict60s buffer
|
||||||
sPredict *predict60s = &pMeasurement->predict60s;
|
sPredict *predict60s = &pMeasurement->predict60s;
|
||||||
predict60s->samples[predict60s->bufferIndex] = pMeasurement->fCurrentValue;
|
predict60s->samples[predict60s->bufferIndex] = pMeasurement->fCurrentValue;
|
||||||
predict60s->bufferIndex = (predict60s->bufferIndex + 1) % PRED60S_SAMPLE_SIZE;
|
predict60s->bufferIndex = (predict60s->bufferIndex + 1) % PRED60_SAMPLE_SIZE;
|
||||||
if (predict60s->bufferCount < PRED60S_SAMPLE_SIZE)
|
if (predict60s->bufferCount < PRED60_SAMPLE_SIZE)
|
||||||
predict60s->bufferCount++;
|
predict60s->bufferCount++;
|
||||||
|
|
||||||
// Predict 60s future value using linear regression
|
// Predict 60s future value using linear regression
|
||||||
@ -228,12 +189,12 @@ void taskInput(void *pvParameters)
|
|||||||
|
|
||||||
if (ds18x20_scan_devices(uDS18B20Pin, uOneWireAddresses, MAX_DN18B20_SENSORS, &sSensorCount) != ESP_OK)
|
if (ds18x20_scan_devices(uDS18B20Pin, uOneWireAddresses, MAX_DN18B20_SENSORS, &sSensorCount) != ESP_OK)
|
||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "1-Wire device scan error!");
|
// ESP_LOGE(TAG, "1-Wire device scan error!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sSensorCount)
|
if (!sSensorCount)
|
||||||
{
|
{
|
||||||
ESP_LOGW(TAG, "No 1-Wire devices detected!");
|
// ESP_LOGW(TAG, "No 1-Wire devices detected!");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -242,14 +203,14 @@ void taskInput(void *pvParameters)
|
|||||||
if (sSensorCount > MAX_DN18B20_SENSORS)
|
if (sSensorCount > MAX_DN18B20_SENSORS)
|
||||||
{
|
{
|
||||||
sSensorCount = MAX_DN18B20_SENSORS;
|
sSensorCount = MAX_DN18B20_SENSORS;
|
||||||
ESP_LOGW(TAG, "More 1-Wire devices found than expected!");
|
// ESP_LOGW(TAG, "More 1-Wire devices found than expected!");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t iReadLoop = 0; iReadLoop < ONE_WIRE_LOOPS; iReadLoop++)
|
for (size_t iReadLoop = 0; iReadLoop < ONE_WIRE_LOOPS; iReadLoop++)
|
||||||
{
|
{
|
||||||
if (ds18x20_measure_and_read_multi(uDS18B20Pin, uOneWireAddresses, sSensorCount, fDS18B20Temps) != ESP_OK)
|
if (ds18x20_measure_and_read_multi(uDS18B20Pin, uOneWireAddresses, sSensorCount, fDS18B20Temps) != ESP_OK)
|
||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "1-Wire devices read error");
|
// ESP_LOGE(TAG, "1-Wire devices read error");
|
||||||
vTaskDelay(PERIODIC_INTERVAL * 100U / portTICK_PERIOD_MS); // Wait 100ms if bus error occurred
|
vTaskDelay(PERIODIC_INTERVAL * 100U / portTICK_PERIOD_MS); // Wait 100ms if bus error occurred
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -310,9 +271,9 @@ void taskInput(void *pvParameters)
|
|||||||
float linearRegressionPredict(const float *samples, size_t count, size_t bufferIndex, float futureIndex)
|
float linearRegressionPredict(const float *samples, size_t count, size_t bufferIndex, float futureIndex)
|
||||||
{
|
{
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
return INITIALISATION_VALUE; // No prediction possible with no data
|
return 0.0f; // No prediction possible with no data
|
||||||
|
|
||||||
float sumX = INITIALISATION_VALUE, sumY = INITIALISATION_VALUE, sumXY = INITIALISATION_VALUE, sumX2 = INITIALISATION_VALUE;
|
float sumX = 0.0f, sumY = 0.0f, sumXY = 0.0f, sumX2 = 0.0f;
|
||||||
|
|
||||||
for (size_t i = 0; i < count; i++)
|
for (size_t i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
@ -417,4 +378,4 @@ eBurnerErrorState getBurnerError(void)
|
|||||||
ESP_LOGE(TAG, "Unable to take mutex: getBurnerError()");
|
ESP_LOGE(TAG, "Unable to take mutex: getBurnerError()");
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -1,17 +1,9 @@
|
|||||||
#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 AVG10_SAMPLE_SIZE 10U
|
||||||
#define AVG10S_SAMPLE_SIZE 10U
|
#define AVG60_SAMPLE_SIZE 60U
|
||||||
#define AVG60S_SAMPLE_SIZE 60U
|
#define PRED60_SAMPLE_SIZE 60U
|
||||||
#define AVG24H_SAMPLE_SIZE 24U
|
|
||||||
#define PRED60S_SAMPLE_SIZE 60U
|
|
||||||
#define DAMPING_FACTOR_WARMER (CONFIG_DAMPING_FACTOR_WARMER * 0.00001f)
|
|
||||||
#define DAMPING_FACTOR_COLDER (CONFIG_DAMPING_FACTOR_COLDER * 0.00001f)
|
|
||||||
|
|
||||||
typedef enum _BurnerErrorState
|
typedef enum _BurnerErrorState
|
||||||
{
|
{
|
||||||
@ -28,7 +20,7 @@ typedef enum _MeasurementErrorState
|
|||||||
typedef struct _Average
|
typedef struct _Average
|
||||||
{
|
{
|
||||||
float fValue;
|
float fValue;
|
||||||
float samples[MAX(AVG10S_SAMPLE_SIZE, MAX(AVG60S_SAMPLE_SIZE, AVG24H_SAMPLE_SIZE))];
|
float samples[MAX(AVG10_SAMPLE_SIZE, AVG60_SAMPLE_SIZE)];
|
||||||
size_t bufferIndex;
|
size_t bufferIndex;
|
||||||
size_t bufferCount;
|
size_t bufferCount;
|
||||||
} sAverage;
|
} sAverage;
|
||||||
@ -36,7 +28,7 @@ typedef struct _Average
|
|||||||
typedef struct _Predict
|
typedef struct _Predict
|
||||||
{
|
{
|
||||||
float fValue;
|
float fValue;
|
||||||
float samples[PRED60S_SAMPLE_SIZE];
|
float samples[PRED60_SAMPLE_SIZE];
|
||||||
size_t bufferIndex;
|
size_t bufferIndex;
|
||||||
size_t bufferCount;
|
size_t bufferCount;
|
||||||
} sPredict;
|
} sPredict;
|
||||||
@ -44,7 +36,6 @@ typedef struct _Predict
|
|||||||
typedef struct _Measurement
|
typedef struct _Measurement
|
||||||
{
|
{
|
||||||
float fCurrentValue;
|
float fCurrentValue;
|
||||||
float fDampedValue;
|
|
||||||
sAverage average10s;
|
sAverage average10s;
|
||||||
sAverage average60s;
|
sAverage average60s;
|
||||||
sPredict predict60s;
|
sPredict predict60s;
|
||||||
@ -56,4 +47,4 @@ sMeasurement getChamberTemperature(void);
|
|||||||
sMeasurement getOutdoorTemperature(void);
|
sMeasurement getOutdoorTemperature(void);
|
||||||
sMeasurement getInletFlowTemperature(void);
|
sMeasurement getInletFlowTemperature(void);
|
||||||
sMeasurement getReturnFlowTemperature(void);
|
sMeasurement getReturnFlowTemperature(void);
|
||||||
eBurnerErrorState getBurnerError(void);
|
eBurnerErrorState getBurnerError(void);
|
||||||
10
main/main.c
10
main/main.c
@ -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)
|
||||||
@ -39,4 +39,4 @@ void app_main(void)
|
|||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
// Do nothing ;-)
|
// Do nothing ;-)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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];
|
||||||
@ -129,12 +128,6 @@ void taskMetrics(void *pvParameters)
|
|||||||
aMetrics[u16MetricCounter].fMetricValue = getChamberTemperature().average60s.fValue;
|
aMetrics[u16MetricCounter].fMetricValue = getChamberTemperature().average60s.fValue;
|
||||||
u16MetricCounter++;
|
u16MetricCounter++;
|
||||||
|
|
||||||
// Chamber Temperature Damped
|
|
||||||
strcpy(aMetrics[u16MetricCounter].caMetricName, "chamber_temperature_damped");
|
|
||||||
aMetrics[u16MetricCounter].type = FLOAT;
|
|
||||||
aMetrics[u16MetricCounter].fMetricValue = getChamberTemperature().fDampedValue;
|
|
||||||
u16MetricCounter++;
|
|
||||||
|
|
||||||
// Chamber Temperature Predict 60s
|
// Chamber Temperature Predict 60s
|
||||||
strcpy(aMetrics[u16MetricCounter].caMetricName, "chamber_temperature_pred60");
|
strcpy(aMetrics[u16MetricCounter].caMetricName, "chamber_temperature_pred60");
|
||||||
aMetrics[u16MetricCounter].type = FLOAT;
|
aMetrics[u16MetricCounter].type = FLOAT;
|
||||||
@ -159,12 +152,6 @@ void taskMetrics(void *pvParameters)
|
|||||||
aMetrics[u16MetricCounter].fMetricValue = getInletFlowTemperature().average60s.fValue;
|
aMetrics[u16MetricCounter].fMetricValue = getInletFlowTemperature().average60s.fValue;
|
||||||
u16MetricCounter++;
|
u16MetricCounter++;
|
||||||
|
|
||||||
// Inlet Flow Temperature Damped
|
|
||||||
strcpy(aMetrics[u16MetricCounter].caMetricName, "inlet_flow_temperature_damped");
|
|
||||||
aMetrics[u16MetricCounter].type = FLOAT;
|
|
||||||
aMetrics[u16MetricCounter].fMetricValue = getInletFlowTemperature().fDampedValue;
|
|
||||||
u16MetricCounter++;
|
|
||||||
|
|
||||||
// Inlet Flow Temperature Predict 60s
|
// Inlet Flow Temperature Predict 60s
|
||||||
strcpy(aMetrics[u16MetricCounter].caMetricName, "inlet_flow_temperature_pred60");
|
strcpy(aMetrics[u16MetricCounter].caMetricName, "inlet_flow_temperature_pred60");
|
||||||
aMetrics[u16MetricCounter].type = FLOAT;
|
aMetrics[u16MetricCounter].type = FLOAT;
|
||||||
@ -189,12 +176,6 @@ void taskMetrics(void *pvParameters)
|
|||||||
aMetrics[u16MetricCounter].fMetricValue = getOutdoorTemperature().average60s.fValue;
|
aMetrics[u16MetricCounter].fMetricValue = getOutdoorTemperature().average60s.fValue;
|
||||||
u16MetricCounter++;
|
u16MetricCounter++;
|
||||||
|
|
||||||
// Outdoor Temperature Average Damped
|
|
||||||
strcpy(aMetrics[u16MetricCounter].caMetricName, "outdoor_temperature_damped");
|
|
||||||
aMetrics[u16MetricCounter].type = FLOAT;
|
|
||||||
aMetrics[u16MetricCounter].fMetricValue = getOutdoorTemperature().fDampedValue;
|
|
||||||
u16MetricCounter++;
|
|
||||||
|
|
||||||
// Outdoor Temperature Predict 60s
|
// Outdoor Temperature Predict 60s
|
||||||
strcpy(aMetrics[u16MetricCounter].caMetricName, "outdoor_temperature_pred60");
|
strcpy(aMetrics[u16MetricCounter].caMetricName, "outdoor_temperature_pred60");
|
||||||
aMetrics[u16MetricCounter].type = FLOAT;
|
aMetrics[u16MetricCounter].type = FLOAT;
|
||||||
@ -219,12 +200,6 @@ void taskMetrics(void *pvParameters)
|
|||||||
aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature().average60s.fValue;
|
aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature().average60s.fValue;
|
||||||
u16MetricCounter++;
|
u16MetricCounter++;
|
||||||
|
|
||||||
// Return Flow Temperature Damped
|
|
||||||
strcpy(aMetrics[u16MetricCounter].caMetricName, "return_flow_temperature_damped");
|
|
||||||
aMetrics[u16MetricCounter].type = FLOAT;
|
|
||||||
aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature().fDampedValue;
|
|
||||||
u16MetricCounter++;
|
|
||||||
|
|
||||||
// Return Flow Temperature Predict 60s
|
// Return Flow Temperature Predict 60s
|
||||||
strcpy(aMetrics[u16MetricCounter].caMetricName, "return_flow_temperature_pred60");
|
strcpy(aMetrics[u16MetricCounter].caMetricName, "return_flow_temperature_pred60");
|
||||||
aMetrics[u16MetricCounter].type = FLOAT;
|
aMetrics[u16MetricCounter].type = FLOAT;
|
||||||
@ -255,31 +230,6 @@ void taskMetrics(void *pvParameters)
|
|||||||
aMetrics[u16MetricCounter].u8MetricValue = getControlState();
|
aMetrics[u16MetricCounter].u8MetricValue = getControlState();
|
||||||
u16MetricCounter++;
|
u16MetricCounter++;
|
||||||
|
|
||||||
// Control Current Weekday
|
|
||||||
strcpy(aMetrics[u16MetricCounter].caMetricName, "control_current_weekday");
|
|
||||||
aMetrics[u16MetricCounter].type = INTEGER_U8;
|
|
||||||
aMetrics[u16MetricCounter].u8MetricValue = getControlCurrentWeekday();
|
|
||||||
u16MetricCounter++;
|
|
||||||
|
|
||||||
// Control Current Entry Time
|
|
||||||
strcpy(aMetrics[u16MetricCounter].caMetricName, "control_current_entry_time");
|
|
||||||
aMetrics[u16MetricCounter].type = INTEGER_64;
|
|
||||||
int64_t i64SecondsSinceMidnight = (getControlCurrentTemperatureEntry().timestamp.hour * 60U * 60U) + (getControlCurrentTemperatureEntry().timestamp.minute * 60U);
|
|
||||||
aMetrics[u16MetricCounter].i64MetricValue = i64SecondsSinceMidnight;
|
|
||||||
u16MetricCounter++;
|
|
||||||
|
|
||||||
// Control Current Entry Chamber Temperature
|
|
||||||
strcpy(aMetrics[u16MetricCounter].caMetricName, "control_current_entry_chamber_temperature");
|
|
||||||
aMetrics[u16MetricCounter].type = FLOAT;
|
|
||||||
aMetrics[u16MetricCounter].fMetricValue = getControlCurrentTemperatureEntry().fChamberTemperature;
|
|
||||||
u16MetricCounter++;
|
|
||||||
|
|
||||||
// Control Current Entry Return Flow Temperature
|
|
||||||
strcpy(aMetrics[u16MetricCounter].caMetricName, "control_current_entry_return_flow_temperature");
|
|
||||||
aMetrics[u16MetricCounter].type = FLOAT;
|
|
||||||
aMetrics[u16MetricCounter].fMetricValue = getControlCurrentTemperatureEntry().fReturnFlowTemperature;
|
|
||||||
u16MetricCounter++;
|
|
||||||
|
|
||||||
// SNTP State
|
// SNTP State
|
||||||
strcpy(aMetrics[u16MetricCounter].caMetricName, "sntp_state");
|
strcpy(aMetrics[u16MetricCounter].caMetricName, "sntp_state");
|
||||||
aMetrics[u16MetricCounter].type = INTEGER_U8;
|
aMetrics[u16MetricCounter].type = INTEGER_U8;
|
||||||
@ -302,23 +252,22 @@ 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));
|
|
||||||
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];
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#define HTML_RESPONSE_SIZE 4096U
|
#define HTML_RESPONSE_SIZE 4096U
|
||||||
#define METRIC_NAME_MAX_SIZE 64U
|
#define METRIC_NAME_MAX_SIZE 64U
|
||||||
#define METRIC_MAX_COUNT 38U
|
#define METRIC_MAX_COUNT 32U
|
||||||
|
|
||||||
typedef enum _MetricValueType
|
typedef enum _MetricValueType
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 SENSOR_GRACE_PERIOD (60U * 30U) // period that a sensor can report the same reading in seconds
|
||||||
|
|
||||||
#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 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);
|
||||||
@ -88,13 +85,13 @@ void checkSensorSanity(void)
|
|||||||
|
|
||||||
if (sCurrentMeasurement.state == MEASUREMENT_FAULT)
|
if (sCurrentMeasurement.state == MEASUREMENT_FAULT)
|
||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "%s Sensor not found!", sanityChecks[i].name);
|
//ESP_LOGE(TAG, "%s Sensor not found!", sanityChecks[i].name);
|
||||||
sanityChecks[i].state = SENSOR_NOT_FOUND;
|
sanityChecks[i].state = SENSOR_NOT_FOUND;
|
||||||
sSafetyState = SAFETY_SENSOR_ERROR;
|
sSafetyState = SAFETY_SENSOR_ERROR;
|
||||||
}
|
}
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
11
main/sntp.c
11
main/sntp.c
@ -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)
|
||||||
|
|||||||
67
main/wifi.c
67
main/wifi.c
@ -1,50 +1,38 @@
|
|||||||
#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);
|
||||||
|
|
||||||
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));
|
||||||
@ -96,9 +84,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 +96,13 @@ 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_wifi_connect();
|
||||||
ESP_LOGW(TAG, "Disconnected from AP (reason: %d)", event->reason);
|
ESP_LOGI(TAG, "Retry to connect to the AP");
|
||||||
|
|
||||||
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();
|
|
||||||
s_retry_num++;
|
|
||||||
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;
|
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
||||||
|
|
||||||
if (s_initial_connect)
|
|
||||||
{
|
|
||||||
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ESP_LOGI(TAG, "Successfully reconnected to AP");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user