mirror of
https://github.com/esphome/esphome.git
synced 2025-10-21 19:23:45 +01:00
[api, climate, thermostat] Implement feature_flags for climate
(#10987)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
@@ -987,8 +987,8 @@ message ListEntitiesClimateResponse {
|
|||||||
string name = 3;
|
string name = 3;
|
||||||
reserved 4; // Deprecated: was string unique_id
|
reserved 4; // Deprecated: was string unique_id
|
||||||
|
|
||||||
bool supports_current_temperature = 5;
|
bool supports_current_temperature = 5; // Deprecated: use feature_flags
|
||||||
bool supports_two_point_target_temperature = 6;
|
bool supports_two_point_target_temperature = 6; // Deprecated: use feature_flags
|
||||||
repeated ClimateMode supported_modes = 7 [(container_pointer) = "std::set<climate::ClimateMode>"];
|
repeated ClimateMode supported_modes = 7 [(container_pointer) = "std::set<climate::ClimateMode>"];
|
||||||
float visual_min_temperature = 8;
|
float visual_min_temperature = 8;
|
||||||
float visual_max_temperature = 9;
|
float visual_max_temperature = 9;
|
||||||
@@ -997,7 +997,7 @@ message ListEntitiesClimateResponse {
|
|||||||
// is if CLIMATE_PRESET_AWAY exists is supported_presets
|
// is if CLIMATE_PRESET_AWAY exists is supported_presets
|
||||||
// Deprecated in API version 1.5
|
// Deprecated in API version 1.5
|
||||||
bool legacy_supports_away = 11 [deprecated=true];
|
bool legacy_supports_away = 11 [deprecated=true];
|
||||||
bool supports_action = 12;
|
bool supports_action = 12; // Deprecated: use feature_flags
|
||||||
repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer) = "std::set<climate::ClimateFanMode>"];
|
repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer) = "std::set<climate::ClimateFanMode>"];
|
||||||
repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer) = "std::set<climate::ClimateSwingMode>"];
|
repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer) = "std::set<climate::ClimateSwingMode>"];
|
||||||
repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::set"];
|
repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::set"];
|
||||||
@@ -1007,11 +1007,12 @@ message ListEntitiesClimateResponse {
|
|||||||
string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"];
|
string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||||
EntityCategory entity_category = 20;
|
EntityCategory entity_category = 20;
|
||||||
float visual_current_temperature_step = 21;
|
float visual_current_temperature_step = 21;
|
||||||
bool supports_current_humidity = 22;
|
bool supports_current_humidity = 22; // Deprecated: use feature_flags
|
||||||
bool supports_target_humidity = 23;
|
bool supports_target_humidity = 23; // Deprecated: use feature_flags
|
||||||
float visual_min_humidity = 24;
|
float visual_min_humidity = 24;
|
||||||
float visual_max_humidity = 25;
|
float visual_max_humidity = 25;
|
||||||
uint32 device_id = 26 [(field_ifdef) = "USE_DEVICES"];
|
uint32 device_id = 26 [(field_ifdef) = "USE_DEVICES"];
|
||||||
|
uint32 feature_flags = 27;
|
||||||
}
|
}
|
||||||
message ClimateStateResponse {
|
message ClimateStateResponse {
|
||||||
option (id) = 47;
|
option (id) = 47;
|
||||||
|
@@ -27,6 +27,9 @@
|
|||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
|
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
|
#include "esphome/components/climate/climate_mode.h"
|
||||||
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
#include "esphome/components/voice_assistant/voice_assistant.h"
|
#include "esphome/components/voice_assistant/voice_assistant.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -623,9 +626,10 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
|
|||||||
auto traits = climate->get_traits();
|
auto traits = climate->get_traits();
|
||||||
resp.mode = static_cast<enums::ClimateMode>(climate->mode);
|
resp.mode = static_cast<enums::ClimateMode>(climate->mode);
|
||||||
resp.action = static_cast<enums::ClimateAction>(climate->action);
|
resp.action = static_cast<enums::ClimateAction>(climate->action);
|
||||||
if (traits.get_supports_current_temperature())
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE))
|
||||||
resp.current_temperature = climate->current_temperature;
|
resp.current_temperature = climate->current_temperature;
|
||||||
if (traits.get_supports_two_point_target_temperature()) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||||
|
climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||||
resp.target_temperature_low = climate->target_temperature_low;
|
resp.target_temperature_low = climate->target_temperature_low;
|
||||||
resp.target_temperature_high = climate->target_temperature_high;
|
resp.target_temperature_high = climate->target_temperature_high;
|
||||||
} else {
|
} else {
|
||||||
@@ -644,9 +648,9 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
|
|||||||
}
|
}
|
||||||
if (traits.get_supports_swing_modes())
|
if (traits.get_supports_swing_modes())
|
||||||
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
||||||
if (traits.get_supports_current_humidity())
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY))
|
||||||
resp.current_humidity = climate->current_humidity;
|
resp.current_humidity = climate->current_humidity;
|
||||||
if (traits.get_supports_target_humidity())
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY))
|
||||||
resp.target_humidity = climate->target_humidity;
|
resp.target_humidity = climate->target_humidity;
|
||||||
return fill_and_encode_entity_state(climate, resp, ClimateStateResponse::MESSAGE_TYPE, conn, remaining_size,
|
return fill_and_encode_entity_state(climate, resp, ClimateStateResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
is_single);
|
is_single);
|
||||||
@@ -656,10 +660,14 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
|
|||||||
auto *climate = static_cast<climate::Climate *>(entity);
|
auto *climate = static_cast<climate::Climate *>(entity);
|
||||||
ListEntitiesClimateResponse msg;
|
ListEntitiesClimateResponse msg;
|
||||||
auto traits = climate->get_traits();
|
auto traits = climate->get_traits();
|
||||||
|
// Flags set for backward compatibility, deprecated in 2025.11.0
|
||||||
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||||
msg.supports_current_humidity = traits.get_supports_current_humidity();
|
msg.supports_current_humidity = traits.get_supports_current_humidity();
|
||||||
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
||||||
msg.supports_target_humidity = traits.get_supports_target_humidity();
|
msg.supports_target_humidity = traits.get_supports_target_humidity();
|
||||||
|
msg.supports_action = traits.get_supports_action();
|
||||||
|
// Current feature flags and other supported parameters
|
||||||
|
msg.feature_flags = traits.get_feature_flags();
|
||||||
msg.supported_modes = &traits.get_supported_modes_for_api_();
|
msg.supported_modes = &traits.get_supported_modes_for_api_();
|
||||||
msg.visual_min_temperature = traits.get_visual_min_temperature();
|
msg.visual_min_temperature = traits.get_visual_min_temperature();
|
||||||
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
||||||
@@ -667,7 +675,6 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
|
|||||||
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
|
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
|
||||||
msg.visual_min_humidity = traits.get_visual_min_humidity();
|
msg.visual_min_humidity = traits.get_visual_min_humidity();
|
||||||
msg.visual_max_humidity = traits.get_visual_max_humidity();
|
msg.visual_max_humidity = traits.get_visual_max_humidity();
|
||||||
msg.supports_action = traits.get_supports_action();
|
|
||||||
msg.supported_fan_modes = &traits.get_supported_fan_modes_for_api_();
|
msg.supported_fan_modes = &traits.get_supported_fan_modes_for_api_();
|
||||||
msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes_for_api_();
|
msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes_for_api_();
|
||||||
msg.supported_presets = &traits.get_supported_presets_for_api_();
|
msg.supported_presets = &traits.get_supported_presets_for_api_();
|
||||||
@@ -1406,7 +1413,7 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
|
|||||||
|
|
||||||
HelloResponse resp;
|
HelloResponse resp;
|
||||||
resp.api_version_major = 1;
|
resp.api_version_major = 1;
|
||||||
resp.api_version_minor = 12;
|
resp.api_version_minor = 13;
|
||||||
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
|
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
|
||||||
resp.set_server_info(ESPHOME_VERSION_REF);
|
resp.set_server_info(ESPHOME_VERSION_REF);
|
||||||
resp.set_name(StringRef(App.get_name()));
|
resp.set_name(StringRef(App.get_name()));
|
||||||
|
@@ -1185,6 +1185,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
buffer.encode_uint32(26, this->device_id);
|
buffer.encode_uint32(26, this->device_id);
|
||||||
#endif
|
#endif
|
||||||
|
buffer.encode_uint32(27, this->feature_flags);
|
||||||
}
|
}
|
||||||
void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const {
|
void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const {
|
||||||
size.add_length(1, this->object_id_ref_.size());
|
size.add_length(1, this->object_id_ref_.size());
|
||||||
@@ -1239,6 +1240,7 @@ void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const {
|
|||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
size.add_uint32(2, this->device_id);
|
size.add_uint32(2, this->device_id);
|
||||||
#endif
|
#endif
|
||||||
|
size.add_uint32(2, this->feature_flags);
|
||||||
}
|
}
|
||||||
void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_fixed32(1, this->key);
|
buffer.encode_fixed32(1, this->key);
|
||||||
|
@@ -1369,7 +1369,7 @@ class CameraImageRequest final : public ProtoDecodableMessage {
|
|||||||
class ListEntitiesClimateResponse final : public InfoResponseProtoMessage {
|
class ListEntitiesClimateResponse final : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 46;
|
static constexpr uint8_t MESSAGE_TYPE = 46;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 145;
|
static constexpr uint8_t ESTIMATED_SIZE = 150;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "list_entities_climate_response"; }
|
const char *message_name() const override { return "list_entities_climate_response"; }
|
||||||
#endif
|
#endif
|
||||||
@@ -1390,6 +1390,7 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage {
|
|||||||
bool supports_target_humidity{false};
|
bool supports_target_humidity{false};
|
||||||
float visual_min_humidity{0.0f};
|
float visual_min_humidity{0.0f};
|
||||||
float visual_max_humidity{0.0f};
|
float visual_max_humidity{0.0f};
|
||||||
|
uint32_t feature_flags{0};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(ProtoSize &size) const override;
|
void calculate_size(ProtoSize &size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
@@ -1292,6 +1292,7 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
|||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
dump_field(out, "device_id", this->device_id);
|
dump_field(out, "device_id", this->device_id);
|
||||||
#endif
|
#endif
|
||||||
|
dump_field(out, "feature_flags", this->feature_flags);
|
||||||
}
|
}
|
||||||
void ClimateStateResponse::dump_to(std::string &out) const {
|
void ClimateStateResponse::dump_to(std::string &out) const {
|
||||||
MessageDumpHelper helper(out, "ClimateStateResponse");
|
MessageDumpHelper helper(out, "ClimateStateResponse");
|
||||||
|
@@ -96,7 +96,8 @@ void ClimateCall::validate_() {
|
|||||||
}
|
}
|
||||||
if (this->target_temperature_.has_value()) {
|
if (this->target_temperature_.has_value()) {
|
||||||
auto target = *this->target_temperature_;
|
auto target = *this->target_temperature_;
|
||||||
if (traits.get_supports_two_point_target_temperature()) {
|
if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||||
|
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||||
ESP_LOGW(TAG, " Cannot set target temperature for climate device "
|
ESP_LOGW(TAG, " Cannot set target temperature for climate device "
|
||||||
"with two-point target temperature!");
|
"with two-point target temperature!");
|
||||||
this->target_temperature_.reset();
|
this->target_temperature_.reset();
|
||||||
@@ -106,7 +107,8 @@ void ClimateCall::validate_() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->target_temperature_low_.has_value() || this->target_temperature_high_.has_value()) {
|
if (this->target_temperature_low_.has_value() || this->target_temperature_high_.has_value()) {
|
||||||
if (!traits.get_supports_two_point_target_temperature()) {
|
if (!traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||||
|
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||||
ESP_LOGW(TAG, " Cannot set low/high target temperature for this device!");
|
ESP_LOGW(TAG, " Cannot set low/high target temperature for this device!");
|
||||||
this->target_temperature_low_.reset();
|
this->target_temperature_low_.reset();
|
||||||
this->target_temperature_high_.reset();
|
this->target_temperature_high_.reset();
|
||||||
@@ -350,13 +352,14 @@ void Climate::save_state_() {
|
|||||||
|
|
||||||
state.mode = this->mode;
|
state.mode = this->mode;
|
||||||
auto traits = this->get_traits();
|
auto traits = this->get_traits();
|
||||||
if (traits.get_supports_two_point_target_temperature()) {
|
if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||||
|
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||||
state.target_temperature_low = this->target_temperature_low;
|
state.target_temperature_low = this->target_temperature_low;
|
||||||
state.target_temperature_high = this->target_temperature_high;
|
state.target_temperature_high = this->target_temperature_high;
|
||||||
} else {
|
} else {
|
||||||
state.target_temperature = this->target_temperature;
|
state.target_temperature = this->target_temperature;
|
||||||
}
|
}
|
||||||
if (traits.get_supports_target_humidity()) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
|
||||||
state.target_humidity = this->target_humidity;
|
state.target_humidity = this->target_humidity;
|
||||||
}
|
}
|
||||||
if (traits.get_supports_fan_modes() && fan_mode.has_value()) {
|
if (traits.get_supports_fan_modes() && fan_mode.has_value()) {
|
||||||
@@ -400,7 +403,7 @@ void Climate::publish_state() {
|
|||||||
auto traits = this->get_traits();
|
auto traits = this->get_traits();
|
||||||
|
|
||||||
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));
|
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));
|
||||||
if (traits.get_supports_action()) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) {
|
||||||
ESP_LOGD(TAG, " Action: %s", LOG_STR_ARG(climate_action_to_string(this->action)));
|
ESP_LOGD(TAG, " Action: %s", LOG_STR_ARG(climate_action_to_string(this->action)));
|
||||||
}
|
}
|
||||||
if (traits.get_supports_fan_modes() && this->fan_mode.has_value()) {
|
if (traits.get_supports_fan_modes() && this->fan_mode.has_value()) {
|
||||||
@@ -418,19 +421,20 @@ void Climate::publish_state() {
|
|||||||
if (traits.get_supports_swing_modes()) {
|
if (traits.get_supports_swing_modes()) {
|
||||||
ESP_LOGD(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode)));
|
ESP_LOGD(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode)));
|
||||||
}
|
}
|
||||||
if (traits.get_supports_current_temperature()) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) {
|
||||||
ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature);
|
ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature);
|
||||||
}
|
}
|
||||||
if (traits.get_supports_two_point_target_temperature()) {
|
if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||||
|
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||||
ESP_LOGD(TAG, " Target Temperature: Low: %.2f°C High: %.2f°C", this->target_temperature_low,
|
ESP_LOGD(TAG, " Target Temperature: Low: %.2f°C High: %.2f°C", this->target_temperature_low,
|
||||||
this->target_temperature_high);
|
this->target_temperature_high);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature);
|
ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature);
|
||||||
}
|
}
|
||||||
if (traits.get_supports_current_humidity()) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)) {
|
||||||
ESP_LOGD(TAG, " Current Humidity: %.0f%%", this->current_humidity);
|
ESP_LOGD(TAG, " Current Humidity: %.0f%%", this->current_humidity);
|
||||||
}
|
}
|
||||||
if (traits.get_supports_target_humidity()) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
|
||||||
ESP_LOGD(TAG, " Target Humidity: %.0f%%", this->target_humidity);
|
ESP_LOGD(TAG, " Target Humidity: %.0f%%", this->target_humidity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,13 +489,14 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
|
|||||||
auto call = climate->make_call();
|
auto call = climate->make_call();
|
||||||
auto traits = climate->get_traits();
|
auto traits = climate->get_traits();
|
||||||
call.set_mode(this->mode);
|
call.set_mode(this->mode);
|
||||||
if (traits.get_supports_two_point_target_temperature()) {
|
if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||||
|
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||||
call.set_target_temperature_low(this->target_temperature_low);
|
call.set_target_temperature_low(this->target_temperature_low);
|
||||||
call.set_target_temperature_high(this->target_temperature_high);
|
call.set_target_temperature_high(this->target_temperature_high);
|
||||||
} else {
|
} else {
|
||||||
call.set_target_temperature(this->target_temperature);
|
call.set_target_temperature(this->target_temperature);
|
||||||
}
|
}
|
||||||
if (traits.get_supports_target_humidity()) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
|
||||||
call.set_target_humidity(this->target_humidity);
|
call.set_target_humidity(this->target_humidity);
|
||||||
}
|
}
|
||||||
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
|
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
|
||||||
@@ -508,13 +513,14 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
|
|||||||
void ClimateDeviceRestoreState::apply(Climate *climate) {
|
void ClimateDeviceRestoreState::apply(Climate *climate) {
|
||||||
auto traits = climate->get_traits();
|
auto traits = climate->get_traits();
|
||||||
climate->mode = this->mode;
|
climate->mode = this->mode;
|
||||||
if (traits.get_supports_two_point_target_temperature()) {
|
if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||||
|
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||||
climate->target_temperature_low = this->target_temperature_low;
|
climate->target_temperature_low = this->target_temperature_low;
|
||||||
climate->target_temperature_high = this->target_temperature_high;
|
climate->target_temperature_high = this->target_temperature_high;
|
||||||
} else {
|
} else {
|
||||||
climate->target_temperature = this->target_temperature;
|
climate->target_temperature = this->target_temperature;
|
||||||
}
|
}
|
||||||
if (traits.get_supports_target_humidity()) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
|
||||||
climate->target_humidity = this->target_humidity;
|
climate->target_humidity = this->target_humidity;
|
||||||
}
|
}
|
||||||
if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) {
|
if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) {
|
||||||
@@ -580,28 +586,30 @@ void Climate::dump_traits_(const char *tag) {
|
|||||||
" Target: %.1f",
|
" Target: %.1f",
|
||||||
traits.get_visual_min_temperature(), traits.get_visual_max_temperature(),
|
traits.get_visual_min_temperature(), traits.get_visual_max_temperature(),
|
||||||
traits.get_visual_target_temperature_step());
|
traits.get_visual_target_temperature_step());
|
||||||
if (traits.get_supports_current_temperature()) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) {
|
||||||
ESP_LOGCONFIG(tag, " Current: %.1f", traits.get_visual_current_temperature_step());
|
ESP_LOGCONFIG(tag, " Current: %.1f", traits.get_visual_current_temperature_step());
|
||||||
}
|
}
|
||||||
if (traits.get_supports_target_humidity() || traits.get_supports_current_humidity()) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY |
|
||||||
|
climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)) {
|
||||||
ESP_LOGCONFIG(tag,
|
ESP_LOGCONFIG(tag,
|
||||||
" - Min humidity: %.0f\n"
|
" - Min humidity: %.0f\n"
|
||||||
" - Max humidity: %.0f",
|
" - Max humidity: %.0f",
|
||||||
traits.get_visual_min_humidity(), traits.get_visual_max_humidity());
|
traits.get_visual_min_humidity(), traits.get_visual_max_humidity());
|
||||||
}
|
}
|
||||||
if (traits.get_supports_two_point_target_temperature()) {
|
if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||||
|
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||||
ESP_LOGCONFIG(tag, " [x] Supports two-point target temperature");
|
ESP_LOGCONFIG(tag, " [x] Supports two-point target temperature");
|
||||||
}
|
}
|
||||||
if (traits.get_supports_current_temperature()) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) {
|
||||||
ESP_LOGCONFIG(tag, " [x] Supports current temperature");
|
ESP_LOGCONFIG(tag, " [x] Supports current temperature");
|
||||||
}
|
}
|
||||||
if (traits.get_supports_target_humidity()) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
|
||||||
ESP_LOGCONFIG(tag, " [x] Supports target humidity");
|
ESP_LOGCONFIG(tag, " [x] Supports target humidity");
|
||||||
}
|
}
|
||||||
if (traits.get_supports_current_humidity()) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)) {
|
||||||
ESP_LOGCONFIG(tag, " [x] Supports current humidity");
|
ESP_LOGCONFIG(tag, " [x] Supports current humidity");
|
||||||
}
|
}
|
||||||
if (traits.get_supports_action()) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) {
|
||||||
ESP_LOGCONFIG(tag, " [x] Supports action");
|
ESP_LOGCONFIG(tag, " [x] Supports action");
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_modes().empty()) {
|
if (!traits.get_supported_modes().empty()) {
|
||||||
|
@@ -98,6 +98,21 @@ enum ClimatePreset : uint8_t {
|
|||||||
CLIMATE_PRESET_ACTIVITY = 7,
|
CLIMATE_PRESET_ACTIVITY = 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum ClimateFeature : uint32_t {
|
||||||
|
// Reporting current temperature is supported
|
||||||
|
CLIMATE_SUPPORTS_CURRENT_TEMPERATURE = 1 << 0,
|
||||||
|
// Setting two target temperatures is supported (used in conjunction with CLIMATE_MODE_HEAT_COOL)
|
||||||
|
CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE = 1 << 1,
|
||||||
|
// Single-point mode is NOT supported (UI always displays two handles, setting 'target_temperature' is not supported)
|
||||||
|
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE = 1 << 2,
|
||||||
|
// Reporting current humidity is supported
|
||||||
|
CLIMATE_SUPPORTS_CURRENT_HUMIDITY = 1 << 3,
|
||||||
|
// Setting a target humidity is supported
|
||||||
|
CLIMATE_SUPPORTS_TARGET_HUMIDITY = 1 << 4,
|
||||||
|
// Reporting current climate action is supported
|
||||||
|
CLIMATE_SUPPORTS_ACTION = 1 << 5,
|
||||||
|
};
|
||||||
|
|
||||||
/// Convert the given ClimateMode to a human-readable string.
|
/// Convert the given ClimateMode to a human-readable string.
|
||||||
const LogString *climate_mode_to_string(ClimateMode mode);
|
const LogString *climate_mode_to_string(ClimateMode mode);
|
||||||
|
|
||||||
|
@@ -21,48 +21,92 @@ namespace climate {
|
|||||||
* - Target Temperature
|
* - Target Temperature
|
||||||
*
|
*
|
||||||
* All other properties and modes are optional and the integration must mark
|
* All other properties and modes are optional and the integration must mark
|
||||||
* each of them as supported by setting the appropriate flag here.
|
* each of them as supported by setting the appropriate flag(s) here.
|
||||||
*
|
*
|
||||||
* - supports current temperature - if the climate device supports reporting a current temperature
|
* - feature flags: see ClimateFeatures enum in climate_mode.h
|
||||||
* - supports two point target temperature - if the climate device's target temperature should be
|
|
||||||
* split in target_temperature_low and target_temperature_high instead of just the single target_temperature
|
|
||||||
* - supports modes:
|
* - supports modes:
|
||||||
* - auto mode (automatic control)
|
* - auto mode (automatic control)
|
||||||
* - cool mode (lowers current temperature)
|
* - cool mode (lowers current temperature)
|
||||||
* - heat mode (increases current temperature)
|
* - heat mode (increases current temperature)
|
||||||
* - dry mode (removes humidity from air)
|
* - dry mode (removes humidity from air)
|
||||||
* - fan mode (only turns on fan)
|
* - fan mode (only turns on fan)
|
||||||
* - supports action - if the climate device supports reporting the active
|
|
||||||
* current action of the device with the action property.
|
|
||||||
* - supports fan modes - optionally, if it has a fan which can be configured in different ways:
|
* - supports fan modes - optionally, if it has a fan which can be configured in different ways:
|
||||||
* - on, off, auto, high, medium, low, middle, focus, diffuse, quiet
|
* - on, off, auto, high, medium, low, middle, focus, diffuse, quiet
|
||||||
* - supports swing modes - optionally, if it has a swing which can be configured in different ways:
|
* - supports swing modes - optionally, if it has a swing which can be configured in different ways:
|
||||||
* - off, both, vertical, horizontal
|
* - off, both, vertical, horizontal
|
||||||
*
|
*
|
||||||
* This class also contains static data for the climate device display:
|
* 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
|
* - visual min/max temperature/humidity - tells the frontend what range of temperature/humidity the
|
||||||
* should display (gauge min/max values)
|
* climate device should display (gauge min/max values)
|
||||||
* - temperature step - the step with which to increase/decrease target temperature.
|
* - temperature step - the step with which to increase/decrease target temperature.
|
||||||
* This also affects with how many decimal places the temperature is shown
|
* This also affects with how many decimal places the temperature is shown
|
||||||
*/
|
*/
|
||||||
class ClimateTraits {
|
class ClimateTraits {
|
||||||
public:
|
public:
|
||||||
bool get_supports_current_temperature() const { return this->supports_current_temperature_; }
|
/// Get/set feature flags (see ClimateFeatures enum in climate_mode.h)
|
||||||
|
uint32_t get_feature_flags() const { return this->feature_flags_; }
|
||||||
|
void add_feature_flags(uint32_t feature_flags) { this->feature_flags_ |= feature_flags; }
|
||||||
|
void clear_feature_flags(uint32_t feature_flags) { this->feature_flags_ &= ~feature_flags; }
|
||||||
|
bool has_feature_flags(uint32_t feature_flags) const { return this->feature_flags_ & feature_flags; }
|
||||||
|
void set_feature_flags(uint32_t feature_flags) { this->feature_flags_ = feature_flags; }
|
||||||
|
|
||||||
|
ESPDEPRECATED("This method is deprecated, use get_feature_flags() instead", "2025.11.0")
|
||||||
|
bool get_supports_current_temperature() const {
|
||||||
|
return this->has_feature_flags(CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);
|
||||||
|
}
|
||||||
|
ESPDEPRECATED("This method is deprecated, use add_feature_flags() instead", "2025.11.0")
|
||||||
void set_supports_current_temperature(bool supports_current_temperature) {
|
void set_supports_current_temperature(bool supports_current_temperature) {
|
||||||
this->supports_current_temperature_ = supports_current_temperature;
|
if (supports_current_temperature) {
|
||||||
|
this->add_feature_flags(CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);
|
||||||
|
} else {
|
||||||
|
this->clear_feature_flags(CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bool get_supports_current_humidity() const { return this->supports_current_humidity_; }
|
ESPDEPRECATED("This method is deprecated, use get_feature_flags() instead", "2025.11.0")
|
||||||
|
bool get_supports_current_humidity() const { return this->has_feature_flags(CLIMATE_SUPPORTS_CURRENT_HUMIDITY); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use add_feature_flags() instead", "2025.11.0")
|
||||||
void set_supports_current_humidity(bool supports_current_humidity) {
|
void set_supports_current_humidity(bool supports_current_humidity) {
|
||||||
this->supports_current_humidity_ = supports_current_humidity;
|
if (supports_current_humidity) {
|
||||||
|
this->add_feature_flags(CLIMATE_SUPPORTS_CURRENT_HUMIDITY);
|
||||||
|
} else {
|
||||||
|
this->clear_feature_flags(CLIMATE_SUPPORTS_CURRENT_HUMIDITY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bool get_supports_two_point_target_temperature() const { return this->supports_two_point_target_temperature_; }
|
ESPDEPRECATED("This method is deprecated, use get_feature_flags() instead", "2025.11.0")
|
||||||
|
bool get_supports_two_point_target_temperature() const {
|
||||||
|
return this->has_feature_flags(CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE);
|
||||||
|
}
|
||||||
|
ESPDEPRECATED("This method is deprecated, use add_feature_flags() instead", "2025.11.0")
|
||||||
void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) {
|
void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) {
|
||||||
this->supports_two_point_target_temperature_ = supports_two_point_target_temperature;
|
if (supports_two_point_target_temperature)
|
||||||
|
// Use CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE to mimic previous behavior
|
||||||
|
{
|
||||||
|
this->add_feature_flags(CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE);
|
||||||
|
} else {
|
||||||
|
this->clear_feature_flags(CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bool get_supports_target_humidity() const { return this->supports_target_humidity_; }
|
ESPDEPRECATED("This method is deprecated, use get_feature_flags() instead", "2025.11.0")
|
||||||
|
bool get_supports_target_humidity() const { return this->has_feature_flags(CLIMATE_SUPPORTS_TARGET_HUMIDITY); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use add_feature_flags() instead", "2025.11.0")
|
||||||
void set_supports_target_humidity(bool supports_target_humidity) {
|
void set_supports_target_humidity(bool supports_target_humidity) {
|
||||||
this->supports_target_humidity_ = supports_target_humidity;
|
if (supports_target_humidity) {
|
||||||
|
this->add_feature_flags(CLIMATE_SUPPORTS_TARGET_HUMIDITY);
|
||||||
|
} else {
|
||||||
|
this->clear_feature_flags(CLIMATE_SUPPORTS_TARGET_HUMIDITY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
ESPDEPRECATED("This method is deprecated, use get_feature_flags() instead", "2025.11.0")
|
||||||
|
bool get_supports_action() const { return this->has_feature_flags(CLIMATE_SUPPORTS_ACTION); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use add_feature_flags() instead", "2025.11.0")
|
||||||
|
void set_supports_action(bool supports_action) {
|
||||||
|
if (supports_action) {
|
||||||
|
this->add_feature_flags(CLIMATE_SUPPORTS_ACTION);
|
||||||
|
} else {
|
||||||
|
this->clear_feature_flags(CLIMATE_SUPPORTS_ACTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void set_supported_modes(std::set<ClimateMode> modes) { this->supported_modes_ = std::move(modes); }
|
void set_supported_modes(std::set<ClimateMode> modes) { this->supported_modes_ = std::move(modes); }
|
||||||
void add_supported_mode(ClimateMode mode) { this->supported_modes_.insert(mode); }
|
void add_supported_mode(ClimateMode mode) { this->supported_modes_.insert(mode); }
|
||||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||||
@@ -82,9 +126,6 @@ class ClimateTraits {
|
|||||||
bool supports_mode(ClimateMode mode) const { return this->supported_modes_.count(mode); }
|
bool supports_mode(ClimateMode mode) const { return this->supported_modes_.count(mode); }
|
||||||
const std::set<ClimateMode> &get_supported_modes() const { return this->supported_modes_; }
|
const std::set<ClimateMode> &get_supported_modes() const { return this->supported_modes_; }
|
||||||
|
|
||||||
void set_supports_action(bool supports_action) { this->supports_action_ = supports_action; }
|
|
||||||
bool get_supports_action() const { return this->supports_action_; }
|
|
||||||
|
|
||||||
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { this->supported_fan_modes_ = std::move(modes); }
|
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { this->supported_fan_modes_ = std::move(modes); }
|
||||||
void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); }
|
void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); }
|
||||||
void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.insert(mode); }
|
void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.insert(mode); }
|
||||||
@@ -219,24 +260,20 @@ class ClimateTraits {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool supports_current_temperature_{false};
|
uint32_t feature_flags_{0};
|
||||||
bool supports_current_humidity_{false};
|
|
||||||
bool supports_two_point_target_temperature_{false};
|
|
||||||
bool supports_target_humidity_{false};
|
|
||||||
std::set<climate::ClimateMode> supported_modes_ = {climate::CLIMATE_MODE_OFF};
|
|
||||||
bool supports_action_{false};
|
|
||||||
std::set<climate::ClimateFanMode> supported_fan_modes_;
|
|
||||||
std::set<climate::ClimateSwingMode> supported_swing_modes_;
|
|
||||||
std::set<climate::ClimatePreset> supported_presets_;
|
|
||||||
std::set<std::string> supported_custom_fan_modes_;
|
|
||||||
std::set<std::string> supported_custom_presets_;
|
|
||||||
|
|
||||||
float visual_min_temperature_{10};
|
float visual_min_temperature_{10};
|
||||||
float visual_max_temperature_{30};
|
float visual_max_temperature_{30};
|
||||||
float visual_target_temperature_step_{0.1};
|
float visual_target_temperature_step_{0.1};
|
||||||
float visual_current_temperature_step_{0.1};
|
float visual_current_temperature_step_{0.1};
|
||||||
float visual_min_humidity_{30};
|
float visual_min_humidity_{30};
|
||||||
float visual_max_humidity_{99};
|
float visual_max_humidity_{99};
|
||||||
|
|
||||||
|
std::set<climate::ClimateMode> supported_modes_ = {climate::CLIMATE_MODE_OFF};
|
||||||
|
std::set<climate::ClimateFanMode> supported_fan_modes_;
|
||||||
|
std::set<climate::ClimateSwingMode> supported_swing_modes_;
|
||||||
|
std::set<climate::ClimatePreset> supported_presets_;
|
||||||
|
std::set<std::string> supported_custom_fan_modes_;
|
||||||
|
std::set<std::string> supported_custom_presets_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace climate
|
} // namespace climate
|
||||||
|
@@ -241,9 +241,14 @@ void ThermostatClimate::control(const climate::ClimateCall &call) {
|
|||||||
|
|
||||||
climate::ClimateTraits ThermostatClimate::traits() {
|
climate::ClimateTraits ThermostatClimate::traits() {
|
||||||
auto traits = climate::ClimateTraits();
|
auto traits = climate::ClimateTraits();
|
||||||
traits.set_supports_current_temperature(true);
|
|
||||||
|
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_ACTION | climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);
|
||||||
|
|
||||||
|
if (this->supports_two_points_)
|
||||||
|
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE);
|
||||||
|
|
||||||
if (this->humidity_sensor_ != nullptr)
|
if (this->humidity_sensor_ != nullptr)
|
||||||
traits.set_supports_current_humidity(true);
|
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY);
|
||||||
|
|
||||||
if (this->supports_auto_)
|
if (this->supports_auto_)
|
||||||
traits.add_supported_mode(climate::CLIMATE_MODE_AUTO);
|
traits.add_supported_mode(climate::CLIMATE_MODE_AUTO);
|
||||||
@@ -294,9 +299,6 @@ climate::ClimateTraits ThermostatClimate::traits() {
|
|||||||
for (auto &it : this->custom_preset_config_) {
|
for (auto &it : this->custom_preset_config_) {
|
||||||
traits.add_supported_custom_preset(it.first);
|
traits.add_supported_custom_preset(it.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
traits.set_supports_two_point_target_temperature(this->supports_two_points_);
|
|
||||||
traits.set_supports_action(true);
|
|
||||||
return traits;
|
return traits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user