Allow specifying target and current visual steps for climate (#4440)

* Allow specifying target and current visual steps for climate

* Fixes

* format

* format
This commit is contained in:
Jesse Hills 2023-02-21 11:22:43 +13:00 committed by GitHub
parent 50fbbf2d3b
commit 0e1d018ce3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 111 additions and 40 deletions

View File

@ -829,7 +829,7 @@ message ListEntitiesClimateResponse {
repeated ClimateMode supported_modes = 7; repeated ClimateMode supported_modes = 7;
float visual_min_temperature = 8; float visual_min_temperature = 8;
float visual_max_temperature = 9; float visual_max_temperature = 9;
float visual_temperature_step = 10; float visual_target_temperature_step = 10;
// for older peer versions - in new system this // for older peer versions - in new system this
// is if CLIMATE_PRESET_AWAY exists is supported_presets // is if CLIMATE_PRESET_AWAY exists is supported_presets
bool legacy_supports_away = 11; bool legacy_supports_away = 11;
@ -842,6 +842,7 @@ message ListEntitiesClimateResponse {
bool disabled_by_default = 18; bool disabled_by_default = 18;
string icon = 19; string icon = 19;
EntityCategory entity_category = 20; EntityCategory entity_category = 20;
float visual_current_temperature_step = 21;
} }
message ClimateStateResponse { message ClimateStateResponse {
option (id) = 47; option (id) = 47;

View File

@ -548,7 +548,9 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
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();
msg.visual_temperature_step = traits.get_visual_temperature_step(); msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY); msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
msg.supports_action = traits.get_supports_action(); msg.supports_action = traits.get_supports_action();

View File

@ -3451,7 +3451,11 @@ bool ListEntitiesClimateResponse::decode_32bit(uint32_t field_id, Proto32Bit val
return true; return true;
} }
case 10: { case 10: {
this->visual_temperature_step = value.as_float(); this->visual_target_temperature_step = value.as_float();
return true;
}
case 21: {
this->visual_current_temperature_step = value.as_float();
return true; return true;
} }
default: default:
@ -3470,7 +3474,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
} }
buffer.encode_float(8, this->visual_min_temperature); buffer.encode_float(8, this->visual_min_temperature);
buffer.encode_float(9, this->visual_max_temperature); buffer.encode_float(9, this->visual_max_temperature);
buffer.encode_float(10, this->visual_temperature_step); buffer.encode_float(10, this->visual_target_temperature_step);
buffer.encode_bool(11, this->legacy_supports_away); buffer.encode_bool(11, this->legacy_supports_away);
buffer.encode_bool(12, this->supports_action); buffer.encode_bool(12, this->supports_action);
for (auto &it : this->supported_fan_modes) { for (auto &it : this->supported_fan_modes) {
@ -3491,6 +3495,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(18, this->disabled_by_default); buffer.encode_bool(18, this->disabled_by_default);
buffer.encode_string(19, this->icon); buffer.encode_string(19, this->icon);
buffer.encode_enum<enums::EntityCategory>(20, this->entity_category); buffer.encode_enum<enums::EntityCategory>(20, this->entity_category);
buffer.encode_float(21, this->visual_current_temperature_step);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesClimateResponse::dump_to(std::string &out) const { void ListEntitiesClimateResponse::dump_to(std::string &out) const {
@ -3537,8 +3542,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" visual_temperature_step: "); out.append(" visual_target_temperature_step: ");
sprintf(buffer, "%g", this->visual_temperature_step); sprintf(buffer, "%g", this->visual_target_temperature_step);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -3591,6 +3596,11 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append(" entity_category: "); out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n"); out.append("\n");
out.append(" visual_current_temperature_step: ");
sprintf(buffer, "%g", this->visual_current_temperature_step);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif

View File

@ -915,7 +915,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
std::vector<enums::ClimateMode> supported_modes{}; std::vector<enums::ClimateMode> supported_modes{};
float visual_min_temperature{0.0f}; float visual_min_temperature{0.0f};
float visual_max_temperature{0.0f}; float visual_max_temperature{0.0f};
float visual_temperature_step{0.0f}; float visual_target_temperature_step{0.0f};
bool legacy_supports_away{false}; bool legacy_supports_away{false};
bool supports_action{false}; bool supports_action{false};
std::vector<enums::ClimateFanMode> supported_fan_modes{}; std::vector<enums::ClimateFanMode> supported_fan_modes{};
@ -926,6 +926,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{}; std::string icon{};
enums::EntityCategory entity_category{}; enums::EntityCategory entity_category{};
float visual_current_temperature_step{0.0f};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;

View File

@ -104,10 +104,40 @@ CLIMATE_SWING_MODES = {
validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
CONF_CURRENT_TEMPERATURE = "current_temperature"
visual_temperature = cv.float_with_unit(
"visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?"
)
def single_visual_temperature(value):
if isinstance(value, dict):
return value
value = visual_temperature(value)
return VISUAL_TEMPERATURE_STEP_SCHEMA(
{
CONF_TARGET_TEMPERATURE: value,
CONF_CURRENT_TEMPERATURE: value,
}
)
# Actions # Actions
ControlAction = climate_ns.class_("ControlAction", automation.Action) ControlAction = climate_ns.class_("ControlAction", automation.Action)
StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template()) StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template())
VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any(
single_visual_temperature,
cv.Schema(
{
cv.Required(CONF_TARGET_TEMPERATURE): visual_temperature,
cv.Required(CONF_CURRENT_TEMPERATURE): visual_temperature,
}
),
)
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{ {
cv.GenerateID(): cv.declare_id(Climate), cv.GenerateID(): cv.declare_id(Climate),
@ -116,9 +146,7 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
{ {
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature, cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
cv.Optional(CONF_TEMPERATURE_STEP): cv.float_with_unit( cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA,
"visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?"
),
} }
), ),
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All( cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
@ -193,7 +221,12 @@ async def setup_climate_core_(var, config):
if CONF_MAX_TEMPERATURE in visual: if CONF_MAX_TEMPERATURE in visual:
cg.add(var.set_visual_max_temperature_override(visual[CONF_MAX_TEMPERATURE])) cg.add(var.set_visual_max_temperature_override(visual[CONF_MAX_TEMPERATURE]))
if CONF_TEMPERATURE_STEP in visual: if CONF_TEMPERATURE_STEP in visual:
cg.add(var.set_visual_temperature_step_override(visual[CONF_TEMPERATURE_STEP])) cg.add(
var.set_visual_temperature_step_override(
visual[CONF_TEMPERATURE_STEP][CONF_TARGET_TEMPERATURE],
visual[CONF_TEMPERATURE_STEP][CONF_CURRENT_TEMPERATURE],
)
)
if CONF_MQTT_ID in config: if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)

View File

@ -430,9 +430,11 @@ ClimateTraits Climate::get_traits() {
if (this->visual_max_temperature_override_.has_value()) { if (this->visual_max_temperature_override_.has_value()) {
traits.set_visual_max_temperature(*this->visual_max_temperature_override_); traits.set_visual_max_temperature(*this->visual_max_temperature_override_);
} }
if (this->visual_temperature_step_override_.has_value()) { if (this->visual_target_temperature_step_override_.has_value()) {
traits.set_visual_temperature_step(*this->visual_temperature_step_override_); traits.set_visual_target_temperature_step(*this->visual_target_temperature_step_override_);
traits.set_visual_current_temperature_step(*this->visual_current_temperature_step_override_);
} }
return traits; return traits;
} }
@ -442,8 +444,9 @@ void Climate::set_visual_min_temperature_override(float visual_min_temperature_o
void Climate::set_visual_max_temperature_override(float visual_max_temperature_override) { void Climate::set_visual_max_temperature_override(float visual_max_temperature_override) {
this->visual_max_temperature_override_ = visual_max_temperature_override; this->visual_max_temperature_override_ = visual_max_temperature_override;
} }
void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) { void Climate::set_visual_temperature_step_override(float target, float current) {
this->visual_temperature_step_override_ = visual_temperature_step_override; this->visual_target_temperature_step_override_ = target;
this->visual_current_temperature_step_override_ = current;
} }
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
@ -541,7 +544,9 @@ void Climate::dump_traits_(const char *tag) {
ESP_LOGCONFIG(tag, " [x] Visual settings:"); ESP_LOGCONFIG(tag, " [x] Visual settings:");
ESP_LOGCONFIG(tag, " - Min: %.1f", traits.get_visual_min_temperature()); ESP_LOGCONFIG(tag, " - Min: %.1f", traits.get_visual_min_temperature());
ESP_LOGCONFIG(tag, " - Max: %.1f", traits.get_visual_max_temperature()); ESP_LOGCONFIG(tag, " - Max: %.1f", traits.get_visual_max_temperature());
ESP_LOGCONFIG(tag, " - Step: %.1f", traits.get_visual_temperature_step()); ESP_LOGCONFIG(tag, " - Step:");
ESP_LOGCONFIG(tag, " Target: %.1f", traits.get_visual_target_temperature_step());
ESP_LOGCONFIG(tag, " Current: %.1f", traits.get_visual_current_temperature_step());
if (traits.get_supports_current_temperature()) { if (traits.get_supports_current_temperature()) {
ESP_LOGCONFIG(tag, " [x] Supports current temperature"); ESP_LOGCONFIG(tag, " [x] Supports current temperature");
} }

View File

@ -241,7 +241,7 @@ class Climate : public EntityBase {
void set_visual_min_temperature_override(float visual_min_temperature_override); void set_visual_min_temperature_override(float visual_min_temperature_override);
void set_visual_max_temperature_override(float visual_max_temperature_override); void set_visual_max_temperature_override(float visual_max_temperature_override);
void set_visual_temperature_step_override(float visual_temperature_step_override); void set_visual_temperature_step_override(float target, float current);
protected: protected:
friend ClimateCall; friend ClimateCall;
@ -288,7 +288,8 @@ class Climate : public EntityBase {
ESPPreferenceObject rtc_; ESPPreferenceObject rtc_;
optional<float> visual_min_temperature_override_{}; optional<float> visual_min_temperature_override_{};
optional<float> visual_max_temperature_override_{}; optional<float> visual_max_temperature_override_{};
optional<float> visual_temperature_step_override_{}; optional<float> visual_target_temperature_step_override_{};
optional<float> visual_current_temperature_step_override_{};
}; };
} // namespace climate } // namespace climate

View File

@ -3,8 +3,12 @@
namespace esphome { namespace esphome {
namespace climate { namespace climate {
int8_t ClimateTraits::get_temperature_accuracy_decimals() const { int8_t ClimateTraits::get_target_temperature_accuracy_decimals() const {
return step_to_accuracy_decimals(this->visual_temperature_step_); return step_to_accuracy_decimals(this->visual_target_temperature_step_);
}
int8_t ClimateTraits::get_current_temperature_accuracy_decimals() const {
return step_to_accuracy_decimals(this->visual_current_temperature_step_);
} }
} // namespace climate } // namespace climate

View File

@ -147,9 +147,20 @@ class ClimateTraits {
void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; } void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }
float get_visual_max_temperature() const { return visual_max_temperature_; } float get_visual_max_temperature() const { return visual_max_temperature_; }
void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; } void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; }
float get_visual_temperature_step() const { return visual_temperature_step_; } float get_visual_target_temperature_step() const { return visual_target_temperature_step_; }
int8_t get_temperature_accuracy_decimals() const; float get_visual_current_temperature_step() const { return visual_current_temperature_step_; }
void set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; } void set_visual_target_temperature_step(float temperature_step) {
visual_target_temperature_step_ = temperature_step;
}
void set_visual_current_temperature_step(float temperature_step) {
visual_current_temperature_step_ = temperature_step;
}
void set_visual_temperature_step(float temperature_step) {
visual_target_temperature_step_ = temperature_step;
visual_current_temperature_step_ = temperature_step;
}
int8_t get_target_temperature_accuracy_decimals() const;
int8_t get_current_temperature_accuracy_decimals() const;
protected: protected:
void set_mode_support_(climate::ClimateMode mode, bool supported) { void set_mode_support_(climate::ClimateMode mode, bool supported) {
@ -186,7 +197,8 @@ class ClimateTraits {
float visual_min_temperature_{10}; float visual_min_temperature_{10};
float visual_max_temperature_{30}; float visual_max_temperature_{30};
float visual_temperature_step_{0.1}; float visual_target_temperature_step_{0.1};
float visual_current_temperature_step_{0.1};
}; };
} // namespace climate } // namespace climate

View File

@ -62,7 +62,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
// max_temp // max_temp
root[MQTT_MAX_TEMP] = traits.get_visual_max_temperature(); root[MQTT_MAX_TEMP] = traits.get_visual_max_temperature();
// temp_step // temp_step
root["temp_step"] = traits.get_visual_temperature_step(); root["temp_step"] = traits.get_visual_target_temperature_step();
// temperature units are always coerced to Celsius internally // temperature units are always coerced to Celsius internally
root[MQTT_TEMPERATURE_UNIT] = "C"; root[MQTT_TEMPERATURE_UNIT] = "C";
@ -281,21 +281,22 @@ bool MQTTClimateComponent::publish_state_() {
bool success = true; bool success = true;
if (!this->publish(this->get_mode_state_topic(), mode_s)) if (!this->publish(this->get_mode_state_topic(), mode_s))
success = false; success = false;
int8_t accuracy = traits.get_temperature_accuracy_decimals(); int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
if (traits.get_supports_current_temperature() && !std::isnan(this->device_->current_temperature)) { if (traits.get_supports_current_temperature() && !std::isnan(this->device_->current_temperature)) {
std::string payload = value_accuracy_to_string(this->device_->current_temperature, accuracy); std::string payload = value_accuracy_to_string(this->device_->current_temperature, current_accuracy);
if (!this->publish(this->get_current_temperature_state_topic(), payload)) if (!this->publish(this->get_current_temperature_state_topic(), payload))
success = false; success = false;
} }
if (traits.get_supports_two_point_target_temperature()) { if (traits.get_supports_two_point_target_temperature()) {
std::string payload = value_accuracy_to_string(this->device_->target_temperature_low, accuracy); std::string payload = value_accuracy_to_string(this->device_->target_temperature_low, target_accuracy);
if (!this->publish(this->get_target_temperature_low_state_topic(), payload)) if (!this->publish(this->get_target_temperature_low_state_topic(), payload))
success = false; success = false;
payload = value_accuracy_to_string(this->device_->target_temperature_high, accuracy); payload = value_accuracy_to_string(this->device_->target_temperature_high, target_accuracy);
if (!this->publish(this->get_target_temperature_high_state_topic(), payload)) if (!this->publish(this->get_target_temperature_high_state_topic(), payload))
success = false; success = false;
} else { } else {
std::string payload = value_accuracy_to_string(this->device_->target_temperature, accuracy); std::string payload = value_accuracy_to_string(this->device_->target_temperature, target_accuracy);
if (!this->publish(this->get_target_temperature_state_topic(), payload)) if (!this->publish(this->get_target_temperature_state_topic(), payload))
success = false; success = false;
} }

View File

@ -873,7 +873,8 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf
return json::build_json([obj, start_config](JsonObject root) { return json::build_json([obj, start_config](JsonObject root) {
set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config); set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config);
const auto traits = obj->get_traits(); const auto traits = obj->get_traits();
int8_t accuracy = traits.get_temperature_accuracy_decimals(); int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
char __buf[16]; char __buf[16];
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
@ -910,9 +911,9 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf
bool has_state = false; bool has_state = false;
root["mode"] = PSTR_LOCAL(climate_mode_to_string(obj->mode)); root["mode"] = PSTR_LOCAL(climate_mode_to_string(obj->mode));
root["max_temp"] = value_accuracy_to_string(traits.get_visual_max_temperature(), accuracy); root["max_temp"] = value_accuracy_to_string(traits.get_visual_max_temperature(), target_accuracy);
root["min_temp"] = value_accuracy_to_string(traits.get_visual_min_temperature(), accuracy); root["min_temp"] = value_accuracy_to_string(traits.get_visual_min_temperature(), target_accuracy);
root["step"] = traits.get_visual_temperature_step(); root["step"] = traits.get_visual_target_temperature_step();
if (traits.get_supports_action()) { if (traits.get_supports_action()) {
root["action"] = PSTR_LOCAL(climate_action_to_string(obj->action)); root["action"] = PSTR_LOCAL(climate_action_to_string(obj->action));
root["state"] = root["action"]; root["state"] = root["action"];
@ -935,20 +936,20 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf
} }
if (traits.get_supports_current_temperature()) { if (traits.get_supports_current_temperature()) {
if (!std::isnan(obj->current_temperature)) { if (!std::isnan(obj->current_temperature)) {
root["current_temperature"] = value_accuracy_to_string(obj->current_temperature, accuracy); root["current_temperature"] = value_accuracy_to_string(obj->current_temperature, current_accuracy);
} else { } else {
root["current_temperature"] = "NA"; root["current_temperature"] = "NA";
} }
} }
if (traits.get_supports_two_point_target_temperature()) { if (traits.get_supports_two_point_target_temperature()) {
root["target_temperature_low"] = value_accuracy_to_string(obj->target_temperature_low, accuracy); root["target_temperature_low"] = value_accuracy_to_string(obj->target_temperature_low, target_accuracy);
root["target_temperature_high"] = value_accuracy_to_string(obj->target_temperature_high, accuracy); root["target_temperature_high"] = value_accuracy_to_string(obj->target_temperature_high, target_accuracy);
if (!has_state) { if (!has_state) {
root["state"] = root["state"] = value_accuracy_to_string((obj->target_temperature_high + obj->target_temperature_low) / 2.0f,
value_accuracy_to_string((obj->target_temperature_high + obj->target_temperature_low) / 2.0f, accuracy); target_accuracy);
} }
} else { } else {
root["target_temperature"] = value_accuracy_to_string(obj->target_temperature, accuracy); root["target_temperature"] = value_accuracy_to_string(obj->target_temperature, target_accuracy);
if (!has_state) if (!has_state)
root["state"] = root["target_temperature"]; root["state"] = root["target_temperature"];
} }