// // Created by MightyPork on 2022/08/20. // #include #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 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 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() { // 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; // 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++; } } if (gState.run_time < 0xFFFF) { 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); } // some time is needed before the temperature means anything if (gState.run_time >= 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.run_time >= 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; // 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; } } 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 { if (delta < gSettings.t_stopdelta_out) { ESP_LOGW(TAG, "OUT stop delta reached, change dir!"); do_switch = true; } } 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 && gState.tempSerial != old_tempSerial) { // expecting some change in temps float speed; if (gState.real_direction == MOTOR_DIR_IN) { speed = absf(tin - old_tin); } else { speed = absf(tout - old_tout); } if (speed < gSettings.t_stop_speed) { ESP_LOGW(TAG, "Near-equilibrium reached, change dir!"); do_switch = true; } } } } 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) { 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 (%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 ? "" : "!" ); } 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 = %s", vent_mode_labels[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(perc_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; } }