recuperator fan control with esp32
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
esp-recuperator/main/fancontrol.c

337 lines
11 KiB

//
// Created by MightyPork on 2022/08/20.
//
#include <esp_log.h>
#include "fancontrol.h"
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"
#include "settings.h"
#include "actuators.h"
#include "onewires.h"
#include "tasks.h"
const char *TAG = "fc";
#define UNIDIR_T_MEAS_PERIOD 15 /* s */
struct FanControlState gState = {};
static void timerCallback();
static void invalidate_temps();
void settings_blind_time_set(uint16_t blind_time)
{
// if the blind is surely at the end
bool nadoraz = (gState.blind_position >= gSettings.blind_time) || (gState.blind_position >= blind_time);
gSettings.blind_time = blind_time;
if (nadoraz) {
gState.blind_position = blind_time;
}
settings_persist(SETTINGS_blind_time);
}
static void fanctltask(void *dummy) {
TickType_t last_wake_time = xTaskGetTickCount();
bool statusled = 0;
while (1) {
act_statusled_set(statusled);
statusled ^= 1;
//
timerCallback();
vTaskDelayUntil(&last_wake_time, pdMS_TO_TICKS(1000));
gState.uptime_secs += 1;
if (gState.uptime_secs >= 3600) {
gState.uptime_secs = 0;
gState.uptime_hours += 1;
}
if ((gState.effective_vent_mode & 6) != 0) {
gState.motor_secs += 1;
if (gState.motor_secs >= 3600) {
gState.motor_secs = 0;
gState.motor_hours += 1;
}
}
}
}
void fancontrol_init()
{
gState.set_vent_mode = gSettings.initial_mode;
gState.set_power = gSettings.initial_power;
// this doesnt work
// TimerHandle_t hTimer = xTimerCreate("fanctl",
// pdMS_TO_TICKS(1000),
// pdTRUE,
// NULL,
// timerCallback);
//
// xTimerStart(hTimer, pdMS_TO_TICKS(2000));
xTaskCreate(fanctltask, "fc", FCTL_TASK_STACK, NULL, FCTL_TASK_PRIO, NULL);
}
static const char * vent_mode_labels[] = {
[VENT_MODE_OFF] = "OFF",
[VENT_MODE_FREE] = "FREE",
[VENT_MODE_OUT] = "OUT",
[VENT_MODE_IN] = "IN",
[VENT_MODE_RECUP] = "RECUP",
};
static const char * recup_mode_labels[] = {
[RECUP_MODE_TIME] = "TIME",
[RECUP_MODE_TEMP] = "TEMP",
};
static void timerCallback()
{
// posun rolety
if (gAct.blind) {
if (gState.blind_position < gSettings.blind_time) {
gState.blind_position++;
}
} else {
if (gState.blind_position > 0) {
gState.blind_position--;
}
}
if (gAct.power > 0) {
if (gState.real_direction != gAct.dir) {
if (gState.ramp > 0) {
gState.ramp--;
} else {
gState.run_time = 0;
gState.real_direction = gAct.dir;
}
} else {
if (gState.ramp < gSettings.ramp_time) {
gState.ramp++;
}
}
gState.run_time++;
} else {
if (gState.ramp > 0) {
gState.ramp--;
} else {
gState.run_time = 0;
}
}
bool end_temp_meas = false;
switch (gState.effective_vent_mode) {
case VENT_MODE_OFF:
act_motor_power_set(0);
act_blind_set(0);
break;
case VENT_MODE_FREE:
act_motor_power_set(0);
act_blind_set(1);
break;
case VENT_MODE_OUT:
act_motor_direction_set(MOTOR_DIR_OUT);
act_blind_set(1);
if (gState.blind_position >= gSettings.blind_time) {
act_motor_power_set(gState.set_power);
}
if (gState.ramp >= gSettings.ramp_time * 2) {
end_temp_meas = true;
}
break;
case VENT_MODE_IN:
act_motor_direction_set(MOTOR_DIR_IN);
act_blind_set(1);
if (gState.blind_position >= gSettings.blind_time) {
act_motor_power_set(gState.set_power);
}
if (gState.ramp >= gSettings.ramp_time * 2) {
end_temp_meas = true;
}
break;
case VENT_MODE_RECUP:
act_blind_set(1);
if (gState.blind_position >= gSettings.blind_time) {
act_motor_power_set(gState.set_power);
if (gState.real_direction == gAct.dir && gState.run_time >= gSettings.min_recup_time) {
// Stop condition
bool do_switch = false;
// TODO questionable logic, verify
if (gSettings.recup_mode == RECUP_MODE_TIME) {
do_switch = gState.run_time >= gSettings.recup_time;
} else if (gState.run_time >= gSettings.max_recup_time) {
do_switch = true;
} else {
// temp-based switching - magic(tm)
// Delta = IN - OUT
const int16_t ideal_delta = gState.t_indoor - gState.t_outdoor;
int16_t stop_delta = ((int32_t) ideal_delta
* (int32_t) (100 - gSettings.recup_factor)) / 100;
int16_t delta = 0;
bool allow_temp_switch = false;
if (gState.real_direction == MOTOR_DIR_OUT) {
if (gState.valid_t_indoor && gState.valid_t_exhaust && gState.valid_t_outdoor) {
delta = gState.t_indoor - gState.t_exhaust;
allow_temp_switch = true;
}
} else {
// IN
if (gState.valid_t_inflow && gState.valid_t_indoor && gState.valid_t_outdoor) {
delta = gState.t_inflow - gState.t_outdoor;
allow_temp_switch = true;
}
}
ESP_LOGI(TAG, "Delta now %d, max %d, stop %d (RF %d%%), allow_switch %d Cx10", delta, ideal_delta, stop_delta, gSettings.recup_factor, allow_temp_switch);
if (allow_temp_switch) {
if (gSettings.summer_mode) {
if (ideal_delta < 0) {
// warmer outside, trying to keep cool in
if (delta >= stop_delta) {
do_switch = true;
}
}
// colder outside - no stopping (will run to max recup time)
} else {
if (ideal_delta > 0) {
// colder outside, trying to keep warmth in
if (delta <= stop_delta) {
do_switch = true;
}
}
// warmer outside - no stopping (will run to max recup time)
}
}
}
if (do_switch) {
// zmena smeru
if (gAct.dir == MOTOR_DIR_IN) {
gState.real_recup_time_in = gState.run_time;
} else {
gState.real_recup_time_out = gState.run_time;
}
gState.run_time = 0;
act_motor_direction_set(1 - gAct.dir);
end_temp_meas = true;
}
}
}
break;
}
if (gState.effective_vent_mode == VENT_MODE_RECUP) {
if (gState.real_direction == MOTOR_DIR_OUT) {
gState.instantaneous_vent_mode = VENT_MODE_OUT;
} else {
gState.instantaneous_vent_mode = VENT_MODE_IN;
}
} else {
gState.instantaneous_vent_mode = gState.effective_vent_mode;
}
// Measure temperatures
int16_t t1 = act_temp1();
int16_t t2 = act_temp2();
gState.t_actual_in = t1;
gState.t_actual_out = t2;
gState.valid_t_actual_in = gState.valid_t_actual_out = tempSensorsOk;
if (end_temp_meas) {
switch (gState.real_direction) {
case MOTOR_DIR_IN:
gState.t_outdoor = gState.t_actual_out;
gState.t_inflow = gState.t_actual_in;
gState.valid_t_outdoor = gState.valid_t_inflow = true;
if (gState.effective_vent_mode == VENT_MODE_IN) {
gState.valid_t_indoor = gState.valid_t_exhaust = false;
}
break;
case MOTOR_DIR_OUT:
gState.t_indoor = gState.t_actual_in;
gState.t_exhaust = gState.t_actual_out;
gState.valid_t_indoor = gState.valid_t_exhaust = true;
if (gState.effective_vent_mode == VENT_MODE_OUT) {
gState.valid_t_outdoor = gState.valid_t_inflow = false;
}
break;
}
}
ESP_LOGI(TAG,
"%s (ef %s, inst %s), rt %ds %d%%m, Tid %d%s, Tod %d%s, Tit %d%s, Teh %d%s, T1 %d%s, T2 %d%s Cx10",
vent_mode_labels[gState.set_vent_mode], vent_mode_labels[gState.effective_vent_mode], vent_mode_labels[gState.instantaneous_vent_mode],
gState.run_time,
gAct.power,
gState.t_indoor,
gState.valid_t_indoor ? "" : "!",
gState.t_outdoor,
gState.valid_t_outdoor ? "" : "!",
gState.t_inflow,
gState.valid_t_inflow ? "" : "!",
gState.t_exhaust,
gState.valid_t_exhaust ? "" : "!",
gState.t_actual_in,
gState.valid_t_actual_in ? "" : "!",
gState.t_actual_out,
gState.valid_t_actual_out ? "" : "!"
);
}
void fan_set_vent_mode(enum ventilation_mode mode)
{
ESP_LOGI(TAG, "Set vent mode = %d", mode);
if (mode == gState.set_vent_mode) {
return;
}
gState.set_vent_mode = mode;
if (gState.set_power != 0 || mode == VENT_MODE_FREE) {
gState.effective_vent_mode = mode;
} else if (gState.set_power == 0) {
gState.effective_vent_mode = VENT_MODE_OFF;
}
if (mode == VENT_MODE_OFF || mode == VENT_MODE_FREE) {
invalidate_temps();
}
}
static void invalidate_temps()
{
gState.valid_t_indoor = false;
gState.valid_t_outdoor = false;
gState.valid_t_inflow = false;
gState.valid_t_exhaust = false;
gState.valid_t_actual_in = false;
gState.valid_t_actual_out = false;
}
void fan_set_power(uint16_t power)
{
ESP_LOGI(TAG, "Set power = %d%%", power);
gState.set_power = power;
if (power == 0) {
gState.effective_vent_mode = VENT_MODE_OFF;
invalidate_temps();
} else {
gState.effective_vent_mode = gState.set_vent_mode;
}
}