mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01:00
add support for climate action (#720)
* add support for climate action: Following hass implementation of climate, action represents the current action the climate device is perfoming, e.g. cooling or heating fix bang_bang climate: make sure that the thresholds are always respected. fixes the issue where the component would just keep on heating, regardless of the temperature range * Updates - Use dedicated enum for action (otherwise it gets confusing because "auto" is not a valid action) - Add field to tell HA that action is supported - Revert semantic changes in bang_bang * Conditional print Co-authored-by: Otto Winter <otto@otto-winter.com>
This commit is contained in:
parent
22aecdfc6f
commit
72d6471ab8
12 changed files with 101 additions and 25 deletions
|
@ -644,6 +644,12 @@ enum ClimateMode {
|
|||
CLIMATE_MODE_COOL = 2;
|
||||
CLIMATE_MODE_HEAT = 3;
|
||||
}
|
||||
enum ClimateAction {
|
||||
CLIMATE_ACTION_OFF = 0;
|
||||
// values same as mode for readability
|
||||
CLIMATE_ACTION_COOLING = 2;
|
||||
CLIMATE_ACTION_HEATING = 3;
|
||||
}
|
||||
message ListEntitiesClimateResponse {
|
||||
option (id) = 46;
|
||||
option (source) = SOURCE_SERVER;
|
||||
|
@ -661,6 +667,7 @@ message ListEntitiesClimateResponse {
|
|||
float visual_max_temperature = 9;
|
||||
float visual_temperature_step = 10;
|
||||
bool supports_away = 11;
|
||||
bool supports_action = 12;
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
|
@ -675,6 +682,7 @@ message ClimateStateResponse {
|
|||
float target_temperature_low = 5;
|
||||
float target_temperature_high = 6;
|
||||
bool away = 7;
|
||||
ClimateAction action = 8;
|
||||
}
|
||||
message ClimateCommandRequest {
|
||||
option (id) = 48;
|
||||
|
|
|
@ -442,6 +442,7 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
|
|||
ClimateStateResponse resp{};
|
||||
resp.key = climate->get_object_id_hash();
|
||||
resp.mode = static_cast<EnumClimateMode>(climate->mode);
|
||||
resp.action = static_cast<EnumClimateAction>(climate->action);
|
||||
if (traits.get_supports_current_temperature())
|
||||
resp.current_temperature = climate->current_temperature;
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
|
@ -472,6 +473,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
|||
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
||||
msg.visual_temperature_step = traits.get_visual_temperature_step();
|
||||
msg.supports_away = traits.get_supports_away();
|
||||
msg.supports_action = traits.get_supports_action();
|
||||
return this->send_list_entities_climate_response(msg);
|
||||
}
|
||||
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||
|
|
|
@ -106,6 +106,18 @@ template<> const char *proto_enum_to_string<EnumClimateMode>(EnumClimateMode val
|
|||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<EnumClimateAction>(EnumClimateAction value) {
|
||||
switch (value) {
|
||||
case CLIMATE_ACTION_OFF:
|
||||
return "CLIMATE_ACTION_OFF";
|
||||
case CLIMATE_ACTION_COOLING:
|
||||
return "CLIMATE_ACTION_COOLING";
|
||||
case CLIMATE_ACTION_HEATING:
|
||||
return "CLIMATE_ACTION_HEATING";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
|
@ -2394,6 +2406,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
|
|||
this->supports_away = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
this->supports_action = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -2452,6 +2468,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_float(9, this->visual_max_temperature);
|
||||
buffer.encode_float(10, this->visual_temperature_step);
|
||||
buffer.encode_bool(11, this->supports_away);
|
||||
buffer.encode_bool(12, this->supports_action);
|
||||
}
|
||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
|
@ -2505,6 +2522,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
|||
out.append(" supports_away: ");
|
||||
out.append(YESNO(this->supports_away));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_action: ");
|
||||
out.append(YESNO(this->supports_action));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
|
@ -2517,6 +2538,10 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
|||
this->away = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->action = value.as_enum<EnumClimateAction>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -2555,6 +2580,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_float(5, this->target_temperature_low);
|
||||
buffer.encode_float(6, this->target_temperature_high);
|
||||
buffer.encode_bool(7, this->away);
|
||||
buffer.encode_enum<EnumClimateAction>(8, this->action);
|
||||
}
|
||||
void ClimateStateResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
|
@ -2591,6 +2617,10 @@ void ClimateStateResponse::dump_to(std::string &out) const {
|
|||
out.append(" away: ");
|
||||
out.append(YESNO(this->away));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" action: ");
|
||||
out.append(proto_enum_to_string<EnumClimateAction>(this->action));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
|
|
|
@ -49,6 +49,11 @@ enum EnumClimateMode : uint32_t {
|
|||
CLIMATE_MODE_COOL = 2,
|
||||
CLIMATE_MODE_HEAT = 3,
|
||||
};
|
||||
enum EnumClimateAction : uint32_t {
|
||||
CLIMATE_ACTION_OFF = 0,
|
||||
CLIMATE_ACTION_COOLING = 2,
|
||||
CLIMATE_ACTION_HEATING = 3,
|
||||
};
|
||||
class HelloRequest : public ProtoMessage {
|
||||
public:
|
||||
std::string client_info{}; // NOLINT
|
||||
|
@ -638,6 +643,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
|||
float visual_max_temperature{0.0f}; // NOLINT
|
||||
float visual_temperature_step{0.0f}; // NOLINT
|
||||
bool supports_away{false}; // NOLINT
|
||||
bool supports_action{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
|
@ -655,6 +661,7 @@ class ClimateStateResponse : public ProtoMessage {
|
|||
float target_temperature_low{0.0f}; // NOLINT
|
||||
float target_temperature_high{0.0f}; // NOLINT
|
||||
bool away{false}; // NOLINT
|
||||
EnumClimateAction action{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
|
|
|
@ -46,52 +46,51 @@ climate::ClimateTraits BangBangClimate::traits() {
|
|||
traits.set_supports_heat_mode(this->supports_heat_);
|
||||
traits.set_supports_two_point_target_temperature(true);
|
||||
traits.set_supports_away(this->supports_away_);
|
||||
traits.set_supports_action(true);
|
||||
return traits;
|
||||
}
|
||||
void BangBangClimate::compute_state_() {
|
||||
if (this->mode != climate::CLIMATE_MODE_AUTO) {
|
||||
// in non-auto mode
|
||||
this->switch_to_mode_(this->mode);
|
||||
this->switch_to_action_(static_cast<climate::ClimateAction>(this->mode));
|
||||
return;
|
||||
}
|
||||
|
||||
// auto mode, compute target mode
|
||||
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
|
||||
this->switch_to_mode_(climate::CLIMATE_MODE_OFF);
|
||||
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::ClimateMode target_mode;
|
||||
climate::ClimateAction target_action;
|
||||
if (too_cold) {
|
||||
// too cold -> enable heating if possible, else idle
|
||||
if (this->supports_heat_)
|
||||
target_mode = climate::CLIMATE_MODE_HEAT;
|
||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||
else
|
||||
target_mode = climate::CLIMATE_MODE_OFF;
|
||||
target_action = climate::CLIMATE_ACTION_OFF;
|
||||
} else if (too_hot) {
|
||||
// too hot -> enable cooling if possible, else idle
|
||||
if (this->supports_cool_)
|
||||
target_mode = climate::CLIMATE_MODE_COOL;
|
||||
target_action = climate::CLIMATE_ACTION_COOLING;
|
||||
else
|
||||
target_mode = climate::CLIMATE_MODE_OFF;
|
||||
target_action = climate::CLIMATE_ACTION_OFF;
|
||||
} 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_mode = climate::CLIMATE_MODE_OFF;
|
||||
target_action = climate::CLIMATE_ACTION_OFF;
|
||||
} else {
|
||||
// else use current mode and don't change (hysteresis)
|
||||
target_mode = this->internal_mode_;
|
||||
target_action = this->action;
|
||||
}
|
||||
}
|
||||
|
||||
this->switch_to_mode_(target_mode);
|
||||
this->switch_to_action_(target_action);
|
||||
}
|
||||
void BangBangClimate::switch_to_mode_(climate::ClimateMode mode) {
|
||||
if (mode == this->internal_mode_)
|
||||
void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
|
||||
if (action == this->action)
|
||||
// already in target mode
|
||||
return;
|
||||
|
||||
|
@ -100,14 +99,14 @@ void BangBangClimate::switch_to_mode_(climate::ClimateMode mode) {
|
|||
this->prev_trigger_ = nullptr;
|
||||
}
|
||||
Trigger<> *trig;
|
||||
switch (mode) {
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
switch (action) {
|
||||
case climate::CLIMATE_ACTION_OFF:
|
||||
trig = this->idle_trigger_;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
case climate::CLIMATE_ACTION_COOLING:
|
||||
trig = this->cool_trigger_;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
case climate::CLIMATE_ACTION_HEATING:
|
||||
trig = this->heat_trigger_;
|
||||
break;
|
||||
default:
|
||||
|
@ -116,7 +115,7 @@ void BangBangClimate::switch_to_mode_(climate::ClimateMode mode) {
|
|||
if (trig != nullptr) {
|
||||
// trig should never be null, but still check so that we don't crash
|
||||
trig->trigger();
|
||||
this->internal_mode_ = mode;
|
||||
this->action = action;
|
||||
this->prev_trigger_ = trig;
|
||||
this->publish_state();
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ class BangBangClimate : public climate::Climate, public Component {
|
|||
void compute_state_();
|
||||
|
||||
/// Switch the climate device to the given climate mode.
|
||||
void switch_to_mode_(climate::ClimateMode mode);
|
||||
void switch_to_action_(climate::ClimateAction action);
|
||||
|
||||
/// The sensor used for getting the current temperature
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
|
@ -74,11 +74,6 @@ class BangBangClimate : public climate::Climate, public Component {
|
|||
* This is so that the previous trigger can be stopped before enabling a new one.
|
||||
*/
|
||||
Trigger<> *prev_trigger_{nullptr};
|
||||
/** The climate mode that is currently active - for a `.mode = AUTO` this will
|
||||
* contain the actual mode the device
|
||||
*
|
||||
*/
|
||||
climate::ClimateMode internal_mode_{climate::CLIMATE_MODE_OFF};
|
||||
|
||||
BangBangClimateTargetTempConfig normal_config_{};
|
||||
bool supports_away_{false};
|
||||
|
|
|
@ -173,6 +173,9 @@ void Climate::publish_state() {
|
|||
auto traits = this->get_traits();
|
||||
|
||||
ESP_LOGD(TAG, " Mode: %s", climate_mode_to_string(this->mode));
|
||||
if (traits.get_supports_action()) {
|
||||
ESP_LOGD(TAG, " Action: %s", climate_action_to_string(this->action));
|
||||
}
|
||||
if (traits.get_supports_current_temperature()) {
|
||||
ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature);
|
||||
}
|
||||
|
|
|
@ -121,6 +121,8 @@ class Climate : public Nameable {
|
|||
|
||||
/// The active mode of the climate device.
|
||||
ClimateMode mode{CLIMATE_MODE_OFF};
|
||||
/// The active state of the climate device.
|
||||
ClimateAction action{CLIMATE_ACTION_OFF};
|
||||
/// The current temperature of the climate device, as reported from the integration.
|
||||
float current_temperature{NAN};
|
||||
|
||||
|
|
|
@ -17,6 +17,18 @@ const char *climate_mode_to_string(ClimateMode mode) {
|
|||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
const char *climate_action_to_string(ClimateAction action) {
|
||||
switch (action) {
|
||||
case CLIMATE_ACTION_OFF:
|
||||
return "OFF";
|
||||
case CLIMATE_ACTION_COOLING:
|
||||
return "COOLING";
|
||||
case CLIMATE_ACTION_HEATING:
|
||||
return "HEATING";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
|
|
|
@ -17,8 +17,19 @@ enum ClimateMode : uint8_t {
|
|||
CLIMATE_MODE_HEAT = 3,
|
||||
};
|
||||
|
||||
/// Enum for the current action of the climate device. Values match those of ClimateMode.
|
||||
enum ClimateAction : uint8_t {
|
||||
/// The climate device is off (inactive or no power)
|
||||
CLIMATE_ACTION_OFF = 0,
|
||||
/// The climate device is actively cooling (usually in cool or auto mode)
|
||||
CLIMATE_ACTION_COOLING = 2,
|
||||
/// The climate device is actively heating (usually in heat or auto mode)
|
||||
CLIMATE_ACTION_HEATING = 3,
|
||||
};
|
||||
|
||||
/// Convert the given ClimateMode to a human-readable string.
|
||||
const char *climate_mode_to_string(ClimateMode mode);
|
||||
const char *climate_action_to_string(ClimateAction action);
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
|
|
|
@ -30,6 +30,7 @@ void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_a
|
|||
void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; }
|
||||
void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; }
|
||||
void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; }
|
||||
void ClimateTraits::set_supports_action(bool supports_action) { supports_action_ = supports_action; }
|
||||
float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; }
|
||||
void ClimateTraits::set_visual_min_temperature(float visual_min_temperature) {
|
||||
visual_min_temperature_ = visual_min_temperature;
|
||||
|
@ -52,6 +53,7 @@ int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
|
|||
}
|
||||
void ClimateTraits::set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; }
|
||||
bool ClimateTraits::get_supports_away() const { return supports_away_; }
|
||||
bool ClimateTraits::get_supports_action() const { return supports_action_; }
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
|
|
|
@ -23,6 +23,8 @@ namespace climate {
|
|||
* - heat mode (increases current temperature)
|
||||
* - supports away - away mode means that the climate device supports two different
|
||||
* target temperature settings: one target temp setting for "away" mode and one for non-away mode.
|
||||
* - supports action - if the climate device supports reporting the active
|
||||
* current action of the device with the action property.
|
||||
*
|
||||
* This class also contains static data for the climate device display:
|
||||
* - visual min/max temperature - tells the frontend what range of temperatures the climate device
|
||||
|
@ -41,6 +43,8 @@ class ClimateTraits {
|
|||
void set_supports_heat_mode(bool supports_heat_mode);
|
||||
void set_supports_away(bool supports_away);
|
||||
bool get_supports_away() const;
|
||||
void set_supports_action(bool supports_action);
|
||||
bool get_supports_action() const;
|
||||
bool supports_mode(ClimateMode mode) const;
|
||||
|
||||
float get_visual_min_temperature() const;
|
||||
|
@ -58,6 +62,7 @@ class ClimateTraits {
|
|||
bool supports_cool_mode_{false};
|
||||
bool supports_heat_mode_{false};
|
||||
bool supports_away_{false};
|
||||
bool supports_action_{false};
|
||||
|
||||
float visual_min_temperature_{10};
|
||||
float visual_max_temperature_{30};
|
||||
|
|
Loading…
Reference in a new issue