mirror of
https://github.com/esphome/esphome.git
synced 2024-12-02 11:44:13 +01:00
189 lines
7.6 KiB
C++
189 lines
7.6 KiB
C++
#include "bang_bang_climate.h"
|
|
#include "esphome/core/log.h"
|
|
|
|
namespace esphome {
|
|
namespace bang_bang {
|
|
|
|
static const char *const TAG = "bang_bang.climate";
|
|
|
|
void BangBangClimate::setup() {
|
|
this->sensor_->add_on_state_callback([this](float state) {
|
|
this->current_temperature = state;
|
|
// control may have changed, recompute
|
|
this->compute_state_();
|
|
// current temperature changed, publish state
|
|
this->publish_state();
|
|
});
|
|
this->current_temperature = this->sensor_->state;
|
|
// restore set points
|
|
auto restore = this->restore_state_();
|
|
if (restore.has_value()) {
|
|
restore->to_call(this).perform();
|
|
} else {
|
|
// restore from defaults, change_away handles those for us
|
|
if (supports_cool_ && supports_heat_)
|
|
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
|
else if (supports_cool_)
|
|
this->mode = climate::CLIMATE_MODE_COOL;
|
|
else if (supports_heat_)
|
|
this->mode = climate::CLIMATE_MODE_HEAT;
|
|
this->change_away_(false);
|
|
}
|
|
}
|
|
void BangBangClimate::control(const climate::ClimateCall &call) {
|
|
if (call.get_mode().has_value())
|
|
this->mode = *call.get_mode();
|
|
if (call.get_target_temperature_low().has_value())
|
|
this->target_temperature_low = *call.get_target_temperature_low();
|
|
if (call.get_target_temperature_high().has_value())
|
|
this->target_temperature_high = *call.get_target_temperature_high();
|
|
if (call.get_preset().has_value())
|
|
this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY);
|
|
|
|
this->compute_state_();
|
|
this->publish_state();
|
|
}
|
|
climate::ClimateTraits BangBangClimate::traits() {
|
|
auto traits = climate::ClimateTraits();
|
|
traits.set_supports_current_temperature(true);
|
|
traits.set_supported_modes({
|
|
climate::CLIMATE_MODE_OFF,
|
|
});
|
|
if (supports_cool_)
|
|
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
|
if (supports_heat_)
|
|
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
|
if (supports_cool_ && supports_heat_)
|
|
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
|
|
traits.set_supports_two_point_target_temperature(true);
|
|
if (supports_away_)
|
|
traits.set_supported_presets({
|
|
climate::CLIMATE_PRESET_HOME,
|
|
climate::CLIMATE_PRESET_AWAY,
|
|
});
|
|
traits.set_supports_action(true);
|
|
return traits;
|
|
}
|
|
void BangBangClimate::compute_state_() {
|
|
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
|
this->switch_to_action_(climate::CLIMATE_ACTION_OFF);
|
|
return;
|
|
}
|
|
if (std::isnan(this->current_temperature) || std::isnan(this->target_temperature_low) ||
|
|
std::isnan(this->target_temperature_high)) {
|
|
// if any control parameters are nan, go to OFF action (not IDLE!)
|
|
this->switch_to_action_(climate::CLIMATE_ACTION_OFF);
|
|
return;
|
|
}
|
|
const bool too_cold = this->current_temperature < this->target_temperature_low;
|
|
const bool too_hot = this->current_temperature > this->target_temperature_high;
|
|
|
|
climate::ClimateAction target_action;
|
|
if (too_cold) {
|
|
// too cold -> enable heating if possible and enabled, else idle
|
|
if (this->supports_heat_ &&
|
|
(this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_HEAT))
|
|
target_action = climate::CLIMATE_ACTION_HEATING;
|
|
else
|
|
target_action = climate::CLIMATE_ACTION_IDLE;
|
|
} else if (too_hot) {
|
|
// too hot -> enable cooling if possible and enabled, else idle
|
|
if (this->supports_cool_ &&
|
|
(this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_COOL))
|
|
target_action = climate::CLIMATE_ACTION_COOLING;
|
|
else
|
|
target_action = climate::CLIMATE_ACTION_IDLE;
|
|
} else {
|
|
// neither too hot nor too cold -> in range
|
|
if (this->supports_cool_ && this->supports_heat_ && this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
|
|
// if supports both ends and both cooling and heating enabled, go to idle action
|
|
target_action = climate::CLIMATE_ACTION_IDLE;
|
|
} else {
|
|
// else use current mode and don't change (hysteresis)
|
|
target_action = this->action;
|
|
}
|
|
}
|
|
|
|
this->switch_to_action_(target_action);
|
|
}
|
|
void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
|
|
if (action == this->action)
|
|
// already in target mode
|
|
return;
|
|
|
|
if ((action == climate::CLIMATE_ACTION_OFF && this->action == climate::CLIMATE_ACTION_IDLE) ||
|
|
(action == climate::CLIMATE_ACTION_IDLE && this->action == climate::CLIMATE_ACTION_OFF)) {
|
|
// switching from OFF to IDLE or vice-versa
|
|
// these only have visual difference. OFF means user manually disabled,
|
|
// IDLE means it's in auto mode but value is in target range.
|
|
this->action = action;
|
|
this->publish_state();
|
|
return;
|
|
}
|
|
|
|
if (this->prev_trigger_ != nullptr) {
|
|
this->prev_trigger_->stop_action();
|
|
this->prev_trigger_ = nullptr;
|
|
}
|
|
Trigger<> *trig;
|
|
switch (action) {
|
|
case climate::CLIMATE_ACTION_OFF:
|
|
case climate::CLIMATE_ACTION_IDLE:
|
|
trig = this->idle_trigger_;
|
|
break;
|
|
case climate::CLIMATE_ACTION_COOLING:
|
|
trig = this->cool_trigger_;
|
|
break;
|
|
case climate::CLIMATE_ACTION_HEATING:
|
|
trig = this->heat_trigger_;
|
|
break;
|
|
default:
|
|
trig = nullptr;
|
|
}
|
|
assert(trig != nullptr);
|
|
trig->trigger();
|
|
this->action = action;
|
|
this->prev_trigger_ = trig;
|
|
this->publish_state();
|
|
}
|
|
void BangBangClimate::change_away_(bool away) {
|
|
if (!away) {
|
|
this->target_temperature_low = this->normal_config_.default_temperature_low;
|
|
this->target_temperature_high = this->normal_config_.default_temperature_high;
|
|
} else {
|
|
this->target_temperature_low = this->away_config_.default_temperature_low;
|
|
this->target_temperature_high = this->away_config_.default_temperature_high;
|
|
}
|
|
this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME;
|
|
}
|
|
void BangBangClimate::set_normal_config(const BangBangClimateTargetTempConfig &normal_config) {
|
|
this->normal_config_ = normal_config;
|
|
}
|
|
void BangBangClimate::set_away_config(const BangBangClimateTargetTempConfig &away_config) {
|
|
this->supports_away_ = true;
|
|
this->away_config_ = away_config;
|
|
}
|
|
BangBangClimate::BangBangClimate()
|
|
: idle_trigger_(new Trigger<>()), cool_trigger_(new Trigger<>()), heat_trigger_(new Trigger<>()) {}
|
|
void BangBangClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
|
Trigger<> *BangBangClimate::get_idle_trigger() const { return this->idle_trigger_; }
|
|
Trigger<> *BangBangClimate::get_cool_trigger() const { return this->cool_trigger_; }
|
|
void BangBangClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
|
Trigger<> *BangBangClimate::get_heat_trigger() const { return this->heat_trigger_; }
|
|
void BangBangClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
|
void BangBangClimate::dump_config() {
|
|
LOG_CLIMATE("", "Bang Bang Climate", this);
|
|
ESP_LOGCONFIG(TAG, " Supports HEAT: %s", YESNO(this->supports_heat_));
|
|
ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_));
|
|
ESP_LOGCONFIG(TAG, " Supports AWAY mode: %s", YESNO(this->supports_away_));
|
|
ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C", this->normal_config_.default_temperature_low);
|
|
ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature_high);
|
|
}
|
|
|
|
BangBangClimateTargetTempConfig::BangBangClimateTargetTempConfig() = default;
|
|
BangBangClimateTargetTempConfig::BangBangClimateTargetTempConfig(float default_temperature_low,
|
|
float default_temperature_high)
|
|
: default_temperature_low(default_temperature_low), default_temperature_high(default_temperature_high) {}
|
|
|
|
} // namespace bang_bang
|
|
} // namespace esphome
|