diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 2e01856a3b..4bb7d1b555 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -678,6 +678,9 @@ enum ClimateAction { // values same as mode for readability CLIMATE_ACTION_COOLING = 2; CLIMATE_ACTION_HEATING = 3; + CLIMATE_ACTION_IDLE = 4; + CLIMATE_ACTION_DRYING = 5; + CLIMATE_ACTION_FAN = 6; } message ListEntitiesClimateResponse { option (id) = 46; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index cca488decf..6b98f95f53 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -158,6 +158,12 @@ template<> const char *proto_enum_to_string(enums::Climate return "CLIMATE_ACTION_COOLING"; case enums::CLIMATE_ACTION_HEATING: return "CLIMATE_ACTION_HEATING"; + case enums::CLIMATE_ACTION_IDLE: + return "CLIMATE_ACTION_IDLE"; + case enums::CLIMATE_ACTION_DRYING: + return "CLIMATE_ACTION_DRYING"; + case enums::CLIMATE_ACTION_FAN: + return "CLIMATE_ACTION_FAN"; default: return "UNKNOWN"; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index fc855a889a..8be89f0365 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -76,6 +76,9 @@ enum ClimateAction : uint32_t { CLIMATE_ACTION_OFF = 0, CLIMATE_ACTION_COOLING = 2, CLIMATE_ACTION_HEATING = 3, + CLIMATE_ACTION_IDLE = 4, + CLIMATE_ACTION_DRYING = 5, + CLIMATE_ACTION_FAN = 6, }; } // namespace enums diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index 978abae52a..cf527988fe 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -51,12 +51,15 @@ climate::ClimateTraits BangBangClimate::traits() { } void BangBangClimate::compute_state_() { if (this->mode != climate::CLIMATE_MODE_AUTO) { - // in non-auto mode + // in non-auto mode, switch directly to appropriate action + // - HEAT mode -> HEATING action + // - COOL mode -> COOLING action + // - OFF mode -> OFF action (not IDLE!) this->switch_to_action_(static_cast(this->mode)); return; } if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) { - // if any control values are nan, go to OFF (idle) mode + // if any control parameters are nan, go to OFF action (not IDLE!) this->switch_to_action_(climate::CLIMATE_ACTION_OFF); return; } @@ -69,18 +72,18 @@ void BangBangClimate::compute_state_() { if (this->supports_heat_) target_action = climate::CLIMATE_ACTION_HEATING; else - target_action = climate::CLIMATE_ACTION_OFF; + target_action = climate::CLIMATE_ACTION_IDLE; } else if (too_hot) { // too hot -> enable cooling if possible, else idle if (this->supports_cool_) target_action = climate::CLIMATE_ACTION_COOLING; else - target_action = climate::CLIMATE_ACTION_OFF; + target_action = climate::CLIMATE_ACTION_IDLE; } else { // neither too hot nor too cold -> in range if (this->supports_cool_ && this->supports_heat_) { - // if supports both ends, go to idle mode - target_action = climate::CLIMATE_ACTION_OFF; + // if supports both ends, go to idle action + target_action = climate::CLIMATE_ACTION_IDLE; } else { // else use current mode and don't change (hysteresis) target_action = this->action; @@ -94,6 +97,16 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction 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(); this->prev_trigger_ = nullptr; @@ -101,6 +114,7 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) { Trigger<> *trig; switch (action) { case climate::CLIMATE_ACTION_OFF: + case climate::CLIMATE_ACTION_IDLE: trig = this->idle_trigger_; break; case climate::CLIMATE_ACTION_COOLING: @@ -112,13 +126,11 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) { default: trig = nullptr; } - if (trig != nullptr) { - // trig should never be null, but still check so that we don't crash - trig->trigger(); - this->action = action; - this->prev_trigger_ = trig; - this->publish_state(); - } + assert(trig != nullptr); + trig->trigger(); + this->action = action; + this->prev_trigger_ = trig; + this->publish_state(); } void BangBangClimate::change_away_(bool away) { if (!away) { diff --git a/esphome/components/climate/climate_mode.cpp b/esphome/components/climate/climate_mode.cpp index aa06ce87f0..ddcc4af4d9 100644 --- a/esphome/components/climate/climate_mode.cpp +++ b/esphome/components/climate/climate_mode.cpp @@ -29,6 +29,12 @@ const char *climate_action_to_string(ClimateAction action) { return "COOLING"; case CLIMATE_ACTION_HEATING: return "HEATING"; + case CLIMATE_ACTION_IDLE: + return "IDLE"; + case CLIMATE_ACTION_DRYING: + return "DRYING"; + case CLIMATE_ACTION_FAN: + return "FAN"; default: return "UNKNOWN"; } diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index 83ef715402..8037ea2196 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -29,6 +29,12 @@ enum ClimateAction : uint8_t { CLIMATE_ACTION_COOLING = 2, /// The climate device is actively heating (usually in heat or auto mode) CLIMATE_ACTION_HEATING = 3, + /// The climate device is idle (monitoring climate but no action needed) + CLIMATE_ACTION_IDLE = 4, + /// The climate device is drying (either mode DRY or AUTO) + CLIMATE_ACTION_DRYING = 5, + /// The climate device is in fan only mode (either mode FAN_ONLY or AUTO) + CLIMATE_ACTION_FAN = 6, }; /// Enum for all modes a climate fan can be in