// // 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) // 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; 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 (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; } if (end_temp_meas) { if (gState.t_aggr_cnt > 0) { int16_t t1 = (int16_t) (gState.t1_aggr / (int32_t)gState.t_aggr_cnt); int16_t t2 = (int16_t) (gState.t2_aggr / (int32_t)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_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 = 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_inflow = false; } break; } } gState.t1_aggr = gState.t2_aggr = 0; 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 = act_temp_in(); int16_t t2 = act_temp_out(); gState.t_actual_in = t1; gState.t_actual_out = t2; gState.valid_t_actual_in = gState.valid_t_actual_out = 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_in = gState.valid_t_actual_out = 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_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) { 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; } }