|
|
|
@ -13,13 +13,15 @@ |
|
|
|
|
|
|
|
|
|
const char *TAG = "fc"; |
|
|
|
|
|
|
|
|
|
#define UNIDIR_T_MEAS_PERIOD 15 /* s */ |
|
|
|
|
#define UNIDIR_T_MEAS_PERIOD 60 |
|
|
|
|
struct FanControlState gState = {}; |
|
|
|
|
|
|
|
|
|
static void timerCallback(); |
|
|
|
|
|
|
|
|
|
static void invalidate_temps(); |
|
|
|
|
|
|
|
|
|
static float absf(float f); |
|
|
|
|
|
|
|
|
|
void settings_blind_time_set(uint16_t blind_time) |
|
|
|
|
{ |
|
|
|
|
// if the blind is surely at the end
|
|
|
|
@ -85,13 +87,39 @@ static const char * vent_mode_labels[] = { |
|
|
|
|
[VENT_MODE_RECUP] = "RECUP", |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
static const char * recup_mode_labels[] = { |
|
|
|
|
[RECUP_MODE_TIME] = "TIME", |
|
|
|
|
[RECUP_MODE_TEMP] = "TEMP", |
|
|
|
|
}; |
|
|
|
|
//static const char * recup_mode_labels[] = {
|
|
|
|
|
// [RECUP_MODE_TIME] = "TIME",
|
|
|
|
|
// [RECUP_MODE_TEMP] = "TEMP",
|
|
|
|
|
//};
|
|
|
|
|
|
|
|
|
|
static void timerCallback() |
|
|
|
|
{ |
|
|
|
|
// Measure temperatures
|
|
|
|
|
cels_t tin = act_temp1(); |
|
|
|
|
cels_t tout = act_temp2(); |
|
|
|
|
|
|
|
|
|
uint16_t old_tempSerial = gState.tempSerial; |
|
|
|
|
// float old_tin = gState.t_actual_in;
|
|
|
|
|
// float old_tout = gState.t_actual_out;
|
|
|
|
|
|
|
|
|
|
gState.tempSerial = act_temps_serial(); |
|
|
|
|
gState.t_actual_in = tin; |
|
|
|
|
gState.t_actual_out = tout; |
|
|
|
|
|
|
|
|
|
gState.valid_t_actual_in = gState.valid_t_actual_out = tempSensorsOk; |
|
|
|
|
|
|
|
|
|
if (gState.tempSerial != old_tempSerial) { |
|
|
|
|
// measuring speed of temp change
|
|
|
|
|
for (int i = NUM_PREVIOUS_T_INS - 1; i > 0; i--) { |
|
|
|
|
gState.previous_t_ins[i] = gState.previous_t_ins[i - 1]; |
|
|
|
|
} |
|
|
|
|
if (gState.real_direction == MOTOR_DIR_IN) { |
|
|
|
|
gState.previous_t_ins[0] = tin; |
|
|
|
|
} else { |
|
|
|
|
gState.previous_t_ins[0] = tout; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// posun rolety
|
|
|
|
|
if (gAct.blind) { |
|
|
|
|
if (gState.blind_position < gSettings.blind_time) { |
|
|
|
@ -115,7 +143,9 @@ static void timerCallback() |
|
|
|
|
gState.ramp++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
gState.run_time++; |
|
|
|
|
if (gState.run_time < 0xFFFF) { |
|
|
|
|
gState.run_time++; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
if (gState.ramp > 0) { |
|
|
|
|
gState.ramp--; |
|
|
|
@ -141,7 +171,8 @@ static void timerCallback() |
|
|
|
|
if (gState.blind_position >= gSettings.blind_time) { |
|
|
|
|
act_motor_power_set(gState.set_power); |
|
|
|
|
} |
|
|
|
|
if (gState.ramp >= gSettings.ramp_time * 2) { |
|
|
|
|
// some time is needed before the temperature means anything
|
|
|
|
|
if (gState.run_time >= UNIDIR_T_MEAS_PERIOD) { |
|
|
|
|
end_temp_meas = true; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
@ -151,7 +182,7 @@ static void timerCallback() |
|
|
|
|
if (gState.blind_position >= gSettings.blind_time) { |
|
|
|
|
act_motor_power_set(gState.set_power); |
|
|
|
|
} |
|
|
|
|
if (gState.ramp >= gSettings.ramp_time * 2) { |
|
|
|
|
if (gState.run_time >= UNIDIR_T_MEAS_PERIOD) { |
|
|
|
|
end_temp_meas = true; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
@ -164,53 +195,49 @@ static void timerCallback() |
|
|
|
|
// Stop condition
|
|
|
|
|
bool do_switch = false; |
|
|
|
|
|
|
|
|
|
// TODO questionable logic, verify
|
|
|
|
|
// vyfukovaci strana trubice, libovona strana
|
|
|
|
|
if (gState.real_direction == MOTOR_DIR_IN) { |
|
|
|
|
if (gState.t_actual_in <= gSettings.t_in_min || gState.t_actual_in >= gSettings.t_in_max) { |
|
|
|
|
ESP_LOGW(TAG, "Temp limit reached, change dir!"); |
|
|
|
|
do_switch = true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
float t_output = gState.real_direction == MOTOR_DIR_OUT ? gState.t_actual_out : gState.t_actual_in; |
|
|
|
|
float t_input = gState.real_direction == MOTOR_DIR_IN ? gState.t_actual_out : gState.t_actual_in; |
|
|
|
|
float delta = absf(t_input - t_output); |
|
|
|
|
|
|
|
|
|
if (gState.real_direction == MOTOR_DIR_IN) { |
|
|
|
|
if (delta < gSettings.t_stopdelta_in) { |
|
|
|
|
ESP_LOGW(TAG, "IN stop delta reached, change dir!"); |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
if (delta < gSettings.t_stopdelta_out) { |
|
|
|
|
ESP_LOGW(TAG, "OUT stop delta reached, change dir!"); |
|
|
|
|
do_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 (gSettings.recup_mode == RECUP_MODE_TIME) { |
|
|
|
|
if (gState.run_time >= gSettings.recup_time) { |
|
|
|
|
ESP_LOGW(TAG, "Recup time elapsed, change dir!"); |
|
|
|
|
do_switch = true; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
if (gState.run_time >= gSettings.max_recup_time) { |
|
|
|
|
ESP_LOGW(TAG, "Max time elapsed, change dir!"); |
|
|
|
|
// Max time elapsed, switch even if the condition was not reached
|
|
|
|
|
do_switch = true; |
|
|
|
|
} else { |
|
|
|
|
if (!gSettings.summer_mode) { |
|
|
|
|
// expecting some change in temps
|
|
|
|
|
float speed = absf(gState.previous_t_ins[NUM_PREVIOUS_T_INS - 1] - gState.previous_t_ins[0]) / (float)NUM_PREVIOUS_T_INS; |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
if (speed < gSettings.t_stop_speed) { |
|
|
|
|
ESP_LOGW(TAG, "Near-equilibrium reached, change dir!"); |
|
|
|
|
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)
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -242,15 +269,6 @@ static void timerCallback() |
|
|
|
|
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: |
|
|
|
@ -273,29 +291,33 @@ static void timerCallback() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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], |
|
|
|
|
"%s (%s), B%ds, M%ds, %d%%, Tid %.2f%s, Tod %.2f%s, Tit %.2f%s, Teh %.2f%s, T1 %.2f%s, T2 %.2f%s C", |
|
|
|
|
vent_mode_labels[gState.set_vent_mode], vent_mode_labels[gState.instantaneous_vent_mode], |
|
|
|
|
gState.blind_position, |
|
|
|
|
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 ? "" : "!" |
|
|
|
|
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 ? "" : "!" |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static float absf(float f) |
|
|
|
|
{ |
|
|
|
|
if (f < 0) { |
|
|
|
|
return -f; |
|
|
|
|
} else { |
|
|
|
|
return f; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void fan_set_vent_mode(enum ventilation_mode mode) |
|
|
|
|
{ |
|
|
|
|
ESP_LOGI(TAG, "Set vent mode = %d", mode); |
|
|
|
|
ESP_LOGI(TAG, "Set vent mode = %s", vent_mode_labels[mode]); |
|
|
|
|
|
|
|
|
|
if (mode == gState.set_vent_mode) { |
|
|
|
|
return; |
|
|
|
@ -323,7 +345,7 @@ static void invalidate_temps() |
|
|
|
|
gState.valid_t_actual_out = false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void fan_set_power(uint16_t power) |
|
|
|
|
void fan_set_power(perc_t power) |
|
|
|
|
{ |
|
|
|
|
ESP_LOGI(TAG, "Set power = %d%%", power); |
|
|
|
|
|
|
|
|
|