|
|
|
//
|
|
|
|
// Created by MightyPork on 2022/08/20.
|
|
|
|
//
|
|
|
|
|
|
|
|
#include "fancontrol.h"
|
|
|
|
#include "freertos/FreeRTOS.h"
|
|
|
|
#include "freertos/timers.h"
|
|
|
|
#include "settings.h"
|
|
|
|
#include "actuators.h"
|
|
|
|
|
|
|
|
#define UNIDIR_T_MEAS_PERIOD 15 /* s */
|
|
|
|
struct FanControlState gState = {};
|
|
|
|
|
|
|
|
static void timerCallback(TimerHandle_t xTimer);
|
|
|
|
static void invalidate_temps();
|
|
|
|
|
|
|
|
void settings_blind_time_set(uint16_t blind_time) {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
void fancontrol_init() {
|
|
|
|
gState.set_vent_mode = gSettings.initial_mode;
|
|
|
|
gState.set_power = gSettings.initial_power;
|
|
|
|
|
|
|
|
xTimerCreate("fanctl",
|
|
|
|
pdMS_TO_TICKS(1000),
|
|
|
|
pdTRUE,
|
|
|
|
NULL,
|
|
|
|
timerCallback);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void timerCallback(TimerHandle_t xTimer) {
|
|
|
|
// 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.t_aggr_cnt > UNIDIR_T_MEAS_PERIOD) {
|
|
|
|
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.t_aggr_cnt > UNIDIR_T_MEAS_PERIOD) {
|
|
|
|
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)
|
|
|
|
const int16_t ideal_delta = gState.t_indoor - gState.t_outdoor;
|
|
|
|
int16_t delta, stop_delta;
|
|
|
|
bool allow_temp_switch = false;
|
|
|
|
if (gState.real_direction == MOTOR_DIR_OUT) {
|
|
|
|
delta = gState.t_indoor - gState.t_exhaust;
|
|
|
|
// 100% factor = nadoraz
|
|
|
|
stop_delta = ((int32_t)ideal_delta
|
|
|
|
* (int32_t)(100 - gSettings.recup_factor)) / 100;
|
|
|
|
// Delta = IN - OUT
|
|
|
|
if (gState.valid_t_indoor && gState.valid_t_exhaust && gState.valid_t_outdoor) {
|
|
|
|
allow_temp_switch = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// IN
|
|
|
|
delta = gState.t_inflow - gState.t_outdoor;
|
|
|
|
// 100% factor = nadoraz
|
|
|
|
stop_delta = ((int32_t)ideal_delta
|
|
|
|
* (int32_t)(gSettings.recup_factor)) / 100;
|
|
|
|
// Delta = IN - OUT
|
|
|
|
if (gState.valid_t_intake && gState.valid_t_indoor && gState.valid_t_outdoor) {
|
|
|
|
allow_temp_switch = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (end_temp_meas) {
|
|
|
|
if (gState.t_aggr_cnt > 0) {
|
|
|
|
uint16_t t1 = (uint16_t) (gState.t1_aggr / gState.t_aggr_cnt);
|
|
|
|
uint16_t t2 = (uint16_t) (gState.t2_aggr / gState.t_aggr_cnt);
|
|
|
|
switch (gState.real_direction) {
|
|
|
|
case MOTOR_DIR_IN:
|
|
|
|
gState.t_outdoor = t2;
|
|
|
|
gState.t_inflow = t1;
|
|
|
|
gState.valid_t_outdoor = gState.valid_t_intake = 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 = t1;
|
|
|
|
gState.t_exhaust = t2;
|
|
|
|
gState.valid_t_indoor = gState.valid_t_exhaust = true;
|
|
|
|
if (gState.effective_vent_mode == VENT_MODE_OUT) {
|
|
|
|
gState.valid_t_outdoor = gState.valid_t_intake = false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
gState.t1_aggr = gState.t2_aggr = gState.t_aggr_cnt = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gState.effective_vent_mode == VENT_MODE_IN
|
|
|
|
|| gState.effective_vent_mode == VENT_MODE_OUT
|
|
|
|
|| gState.effective_vent_mode == VENT_MODE_RECUP) {
|
|
|
|
// Measure temperatures
|
|
|
|
int16_t t1 = 200;
|
|
|
|
int16_t t2 = 190; // TODO
|
|
|
|
|
|
|
|
gState.t_actual_1 = t1;
|
|
|
|
gState.t_actual_2 = t2;
|
|
|
|
|
|
|
|
gState.valid_t_actual_1 = gState.valid_t_actual_2 = true;
|
|
|
|
|
|
|
|
if (gAct.dir == gState.real_direction
|
|
|
|
&& gState.ramp >= gSettings.ramp_time) {
|
|
|
|
gState.t1_aggr += t1;
|
|
|
|
gState.t2_aggr += t2;
|
|
|
|
gState.t_aggr_cnt++;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
gState.valid_t_actual_1 = gState.valid_t_actual_2 = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void fan_set_vent_mode(enum ventilation_mode mode) {
|
|
|
|
if (mode == gState.set_vent_mode) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
gState.set_vent_mode = mode;
|
|
|
|
gState.t1_aggr = gState.t2_aggr = gState.t_aggr_cnt = 0;
|
|
|
|
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_intake = false;
|
|
|
|
gState.valid_t_exhaust = false;
|
|
|
|
gState.valid_t_actual_1 = false;
|
|
|
|
gState.valid_t_actual_2 = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void fan_set_power(uint16_t 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;
|
|
|
|
}
|
|
|
|
}
|