mirror of
https://github.com/esphome/esphome.git
synced 2025-01-18 20:10:55 +00:00
[climate/template]: New template climate component
This commit is contained in:
parent
9e40d4cf45
commit
43188a4d53
126
esphome/components/template/climate/__init__.py
Normal file
126
esphome/components/template/climate/__init__.py
Normal file
@ -0,0 +1,126 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import climate
|
||||
from esphome.components.climate import (
|
||||
CLIMATE_FAN_MODES,
|
||||
CLIMATE_MODES,
|
||||
CLIMATE_PRESETS,
|
||||
CLIMATE_SWING_MODES,
|
||||
)
|
||||
from esphome.components.number import Number
|
||||
from esphome.components.select import Select
|
||||
from esphome.components.sensor import Sensor
|
||||
from esphome.components.text_sensor import TextSensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACTION_ID,
|
||||
CONF_ID,
|
||||
CONF_SUPPORTED_FAN_MODES,
|
||||
CONF_SUPPORTED_MODES,
|
||||
CONF_SUPPORTED_PRESETS,
|
||||
CONF_SUPPORTED_SWING_MODES,
|
||||
)
|
||||
|
||||
from .. import template_ns
|
||||
|
||||
DEPENDENCIES = ["climate"]
|
||||
|
||||
CONF_AUTO_ACTION = "auto_action"
|
||||
CONF_CURRENT_TEMPERATURE_ID = "current_temperature_id"
|
||||
CONF_TARGET_TEMPERATURE_ID = "target_temperature_id"
|
||||
CONF_MODE_ID = "mode_id"
|
||||
CONF_FAN_MODE_ID = "fan_mode_id"
|
||||
CONF_SWING_MODE_ID = "swing_mode_id"
|
||||
CONF_PRESET_ID = "preset_id"
|
||||
|
||||
TemplateClimate = template_ns.class_("TemplateClimate", climate.Climate, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(TemplateClimate),
|
||||
cv.Required(CONF_CURRENT_TEMPERATURE_ID): cv.use_id(Sensor),
|
||||
cv.Required(CONF_TARGET_TEMPERATURE_ID): cv.use_id(Number),
|
||||
cv.Required(CONF_MODE_ID): cv.use_id(Select),
|
||||
cv.Optional(CONF_FAN_MODE_ID): cv.use_id(Select),
|
||||
cv.Optional(CONF_SWING_MODE_ID): cv.use_id(Select),
|
||||
cv.Optional(CONF_PRESET_ID): cv.use_id(Select),
|
||||
cv.Required(CONF_SUPPORTED_MODES): cv.ensure_list(cv.enum(CLIMATE_MODES)),
|
||||
cv.Optional(CONF_SUPPORTED_FAN_MODES): cv.ensure_list(
|
||||
cv.enum(CLIMATE_FAN_MODES)
|
||||
),
|
||||
cv.Optional(CONF_SUPPORTED_SWING_MODES): cv.ensure_list(
|
||||
cv.enum(CLIMATE_SWING_MODES)
|
||||
),
|
||||
cv.Optional(CONF_SUPPORTED_PRESETS): cv.ensure_list(cv.enum(CLIMATE_PRESETS)),
|
||||
cv.Optional(CONF_ACTION_ID): cv.use_id(TextSensor),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def FINAL_VALIDATE_SCHEMA(config):
|
||||
if bool(CONF_FAN_MODE_ID in config) != bool(CONF_SUPPORTED_FAN_MODES in config):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_FAN_MODE_ID} requires {CONF_SUPPORTED_FAN_MODES} and vice versa"
|
||||
)
|
||||
if bool(CONF_SWING_MODE_ID in config) != bool(CONF_SUPPORTED_SWING_MODES in config):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_SWING_MODE_ID} requires {CONF_SUPPORTED_SWING_MODES} and vice versa"
|
||||
)
|
||||
if bool(CONF_PRESET_ID in config) != bool(CONF_SUPPORTED_PRESETS in config):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_PRESET_ID} requires {CONF_SUPPORTED_PRESETS} and vice versa"
|
||||
)
|
||||
|
||||
# TODO: It would be nice to be able to get the set of options for the mode selectors if defined,
|
||||
# that way we can validate those as well. But how can they be accessed?
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await climate.register_climate(var, config)
|
||||
|
||||
cg.add(
|
||||
var.set_current_temperature(
|
||||
await cg.get_variable(config[CONF_CURRENT_TEMPERATURE_ID])
|
||||
)
|
||||
)
|
||||
|
||||
cg.add(
|
||||
var.set_target_temperature(
|
||||
await cg.get_variable(config[CONF_TARGET_TEMPERATURE_ID])
|
||||
)
|
||||
)
|
||||
|
||||
cg.add(var.set_mode(await cg.get_variable(config[CONF_MODE_ID])))
|
||||
cg.add(
|
||||
var.traits_.set_supported_modes(
|
||||
[CLIMATE_MODES[m] for m in config[CONF_SUPPORTED_MODES]]
|
||||
)
|
||||
)
|
||||
|
||||
if v := config.get(CONF_FAN_MODE_ID):
|
||||
cg.add(var.set_fan_mode(await cg.get_variable(v)))
|
||||
cg.add(
|
||||
var.traits_.set_supported_fan_modes(
|
||||
[CLIMATE_FAN_MODES[m] for m in config[CONF_SUPPORTED_FAN_MODES]]
|
||||
)
|
||||
)
|
||||
|
||||
if v := config.get(CONF_SWING_MODE_ID):
|
||||
cg.add(var.set_swing_mode(await cg.get_variable(v)))
|
||||
cg.add(
|
||||
var.traits_.set_supported_swing_modes(
|
||||
[CLIMATE_SWING_MODES[m] for m in config[CONF_SUPPORTED_SWING_MODES]]
|
||||
)
|
||||
)
|
||||
|
||||
if v := config.get(CONF_PRESET_ID):
|
||||
cg.add(var.set_preset(await cg.get_variable(v)))
|
||||
cg.add(
|
||||
var.traits_.set_supported_presets(
|
||||
[CLIMATE_PRESETS[m] for m in config[CONF_SUPPORTED_PRESETS]]
|
||||
)
|
||||
)
|
||||
|
||||
if v := config.get(CONF_ACTION_ID):
|
||||
cg.add(var.set_action(await cg.get_variable(v)))
|
197
esphome/components/template/climate/template_climate.cpp
Normal file
197
esphome/components/template/climate/template_climate.cpp
Normal file
@ -0,0 +1,197 @@
|
||||
#include "template_climate.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
|
||||
static const char *const TAG = "template_climate";
|
||||
|
||||
ClimateMode mode_from_string(const std::string &s) {
|
||||
if (str_equals_case_insensitive(s, "OFF")) {
|
||||
return CLIMATE_MODE_OFF;
|
||||
} else if (str_equals_case_insensitive(s, "HEAT_COOL")) {
|
||||
return CLIMATE_MODE_HEAT_COOL;
|
||||
} else if (str_equals_case_insensitive(s, "COOL")) {
|
||||
return CLIMATE_MODE_COOL;
|
||||
} else if (str_equals_case_insensitive(s, "HEAT")) {
|
||||
return CLIMATE_MODE_HEAT;
|
||||
} else if (str_equals_case_insensitive(s, "FAN_ONLY")) {
|
||||
return CLIMATE_MODE_FAN_ONLY;
|
||||
} else if (str_equals_case_insensitive(s, "DRY")) {
|
||||
return CLIMATE_MODE_DRY;
|
||||
} else if (str_equals_case_insensitive(s, "AUTO")) {
|
||||
return CLIMATE_MODE_AUTO;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unrecognized mode %s", s.c_str());
|
||||
return CLIMATE_MODE_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
ClimateFanMode fan_mode_from_string(const std::string &s) {
|
||||
if (str_equals_case_insensitive(s, "ON")) {
|
||||
return CLIMATE_FAN_ON;
|
||||
} else if (str_equals_case_insensitive(s, "OFF")) {
|
||||
return CLIMATE_FAN_OFF;
|
||||
} else if (str_equals_case_insensitive(s, "AUTO")) {
|
||||
return CLIMATE_FAN_AUTO;
|
||||
} else if (str_equals_case_insensitive(s, "LOW")) {
|
||||
return CLIMATE_FAN_LOW;
|
||||
} else if (str_equals_case_insensitive(s, "MEDIUM")) {
|
||||
return CLIMATE_FAN_MEDIUM;
|
||||
} else if (str_equals_case_insensitive(s, "HIGH")) {
|
||||
return CLIMATE_FAN_HIGH;
|
||||
} else if (str_equals_case_insensitive(s, "MIDDLE")) {
|
||||
return CLIMATE_FAN_MIDDLE;
|
||||
} else if (str_equals_case_insensitive(s, "FOCUS")) {
|
||||
return CLIMATE_FAN_FOCUS;
|
||||
} else if (str_equals_case_insensitive(s, "DIFFUSE")) {
|
||||
return CLIMATE_FAN_DIFFUSE;
|
||||
} else if (str_equals_case_insensitive(s, "QUIET")) {
|
||||
return CLIMATE_FAN_QUIET;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unrecognized fan mode %s", s.c_str());
|
||||
return CLIMATE_FAN_AUTO;
|
||||
}
|
||||
}
|
||||
|
||||
ClimateSwingMode swing_mode_from_string(const std::string &s) {
|
||||
if (str_equals_case_insensitive(s, "OFF")) {
|
||||
return CLIMATE_SWING_OFF;
|
||||
} else if (str_equals_case_insensitive(s, "BOTH")) {
|
||||
return CLIMATE_SWING_BOTH;
|
||||
} else if (str_equals_case_insensitive(s, "VERTICAL")) {
|
||||
return CLIMATE_SWING_VERTICAL;
|
||||
} else if (str_equals_case_insensitive(s, "HORIZONTAL")) {
|
||||
return CLIMATE_SWING_HORIZONTAL;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unrecognized swing mode %s", s.c_str());
|
||||
return CLIMATE_SWING_BOTH;
|
||||
}
|
||||
}
|
||||
|
||||
ClimatePreset preset_from_string(const std::string &s) {
|
||||
if (str_equals_case_insensitive(s, "NONE")) {
|
||||
return CLIMATE_PRESET_NONE;
|
||||
} else if (str_equals_case_insensitive(s, "HOME")) {
|
||||
return CLIMATE_PRESET_HOME;
|
||||
} else if (str_equals_case_insensitive(s, "AWAY")) {
|
||||
return CLIMATE_PRESET_AWAY;
|
||||
} else if (str_equals_case_insensitive(s, "BOOST")) {
|
||||
return CLIMATE_PRESET_BOOST;
|
||||
} else if (str_equals_case_insensitive(s, "COMFORT")) {
|
||||
return CLIMATE_PRESET_COMFORT;
|
||||
} else if (str_equals_case_insensitive(s, "ECO")) {
|
||||
return CLIMATE_PRESET_ECO;
|
||||
} else if (str_equals_case_insensitive(s, "SLEEP")) {
|
||||
return CLIMATE_PRESET_SLEEP;
|
||||
} else if (str_equals_case_insensitive(s, "ACTIVITY")) {
|
||||
return CLIMATE_PRESET_ACTIVITY;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unrecognized preset %s", s.c_str());
|
||||
return CLIMATE_PRESET_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
ClimateAction action_from_string(const std::string &s) {
|
||||
if (str_equals_case_insensitive(s, "OFF")) {
|
||||
return CLIMATE_ACTION_OFF;
|
||||
} else if (str_equals_case_insensitive(s, "COOLING")) {
|
||||
return CLIMATE_ACTION_COOLING;
|
||||
} else if (str_equals_case_insensitive(s, "HEATING")) {
|
||||
return CLIMATE_ACTION_HEATING;
|
||||
} else if (str_equals_case_insensitive(s, "IDLE")) {
|
||||
return CLIMATE_ACTION_IDLE;
|
||||
} else if (str_equals_case_insensitive(s, "DRYING")) {
|
||||
return CLIMATE_ACTION_DRYING;
|
||||
} else if (str_equals_case_insensitive(s, "FAN")) {
|
||||
return CLIMATE_ACTION_FAN;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unrecognized action %s", s.c_str());
|
||||
return CLIMATE_ACTION_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
void TemplateClimate::setup() {
|
||||
this->current_temperature_->add_on_state_callback([this](float x) {
|
||||
this->current_temperature = x;
|
||||
this->publish_state();
|
||||
});
|
||||
this->current_temperature = this->current_temperature_->state;
|
||||
|
||||
this->target_temperature_->add_on_state_callback([this](float x) {
|
||||
this->target_temperature = x;
|
||||
this->publish_state();
|
||||
});
|
||||
this->current_temperature = this->target_temperature_->state;
|
||||
|
||||
this->mode_->add_on_state_callback([this](const std::string &x, size_t i) {
|
||||
this->mode = mode_from_string(x);
|
||||
this->publish_state();
|
||||
});
|
||||
this->mode = mode_from_string(this->mode_->state);
|
||||
|
||||
if (this->fan_mode_ != nullptr) {
|
||||
this->fan_mode_->add_on_state_callback([this](const std::string &x, size_t i) {
|
||||
this->fan_mode = fan_mode_from_string(x);
|
||||
this->publish_state();
|
||||
});
|
||||
this->fan_mode = fan_mode_from_string(this->fan_mode_->state);
|
||||
}
|
||||
|
||||
if (this->swing_mode_ != nullptr) {
|
||||
this->swing_mode_->add_on_state_callback([this](const std::string &x, size_t i) {
|
||||
this->swing_mode = swing_mode_from_string(x);
|
||||
this->publish_state();
|
||||
});
|
||||
this->swing_mode = swing_mode_from_string(this->swing_mode_->state);
|
||||
}
|
||||
|
||||
if (this->preset_ != nullptr) {
|
||||
this->preset_->add_on_state_callback([this](const std::string &x, size_t i) {
|
||||
this->preset = preset_from_string(x);
|
||||
this->publish_state();
|
||||
});
|
||||
this->preset = preset_from_string(this->preset_->state);
|
||||
}
|
||||
|
||||
if (this->action_ != nullptr) {
|
||||
this->action_->add_on_state_callback([this](const std::string &x) {
|
||||
this->action = action_from_string(x);
|
||||
this->publish_state();
|
||||
});
|
||||
this->action = action_from_string(this->action_->state);
|
||||
}
|
||||
}
|
||||
|
||||
void TemplateClimate::control(const ClimateCall &call) {
|
||||
auto mode = call.get_mode();
|
||||
if (mode) {
|
||||
std::string opt = LOG_STR_ARG(climate_mode_to_string(mode.value()));
|
||||
this->mode_->make_call().set_option(opt).perform();
|
||||
}
|
||||
|
||||
auto fan_mode = call.get_fan_mode();
|
||||
if (fan_mode && this->fan_mode_ != nullptr) {
|
||||
std::string opt = LOG_STR_ARG(climate_fan_mode_to_string(fan_mode.value()));
|
||||
this->fan_mode_->make_call().set_option(opt).perform();
|
||||
}
|
||||
|
||||
auto swing_mode = call.get_swing_mode();
|
||||
if (swing_mode && this->swing_mode_ != nullptr) {
|
||||
std::string opt = LOG_STR_ARG(climate_swing_mode_to_string(swing_mode.value()));
|
||||
this->swing_mode_->make_call().set_option(opt).perform();
|
||||
}
|
||||
|
||||
auto preset = call.get_preset();
|
||||
if (preset && this->preset_ != nullptr) {
|
||||
std::string opt = LOG_STR_ARG(climate_preset_to_string(preset.value()));
|
||||
this->preset_->make_call().set_option(opt).perform();
|
||||
}
|
||||
|
||||
auto target_temperature = call.get_target_temperature();
|
||||
if (target_temperature) {
|
||||
this->target_temperature_->make_call().set_value(target_temperature.value()).perform();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace template_
|
||||
} // namespace esphome
|
62
esphome/components/template/climate/template_climate.h
Normal file
62
esphome/components/template/climate/template_climate.h
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "esphome/components/select/select.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
|
||||
using namespace climate;
|
||||
using namespace number;
|
||||
using namespace select;
|
||||
using namespace sensor;
|
||||
using namespace text_sensor;
|
||||
|
||||
class TemplateClimate : public Climate, public Component {
|
||||
public:
|
||||
ClimateTraits traits_;
|
||||
|
||||
void setup() override;
|
||||
|
||||
ClimateTraits traits() override { return this->traits_; }
|
||||
|
||||
void set_current_temperature(Sensor *sensor) {
|
||||
this->current_temperature_ = sensor;
|
||||
this->traits_.set_supports_current_temperature(true);
|
||||
}
|
||||
|
||||
void set_target_temperature(Number *num) { this->target_temperature_ = num; }
|
||||
|
||||
void set_mode(Select *s) { this->mode_ = s; }
|
||||
|
||||
void set_fan_mode(Select *s) { this->fan_mode_ = s; }
|
||||
|
||||
void set_swing_mode(Select *s) { this->swing_mode_ = s; }
|
||||
|
||||
void set_preset(Select *s) { this->preset_ = s; }
|
||||
|
||||
void set_action(TextSensor *s) {
|
||||
this->action_ = s;
|
||||
this->traits_.set_supports_action(true);
|
||||
}
|
||||
|
||||
protected:
|
||||
void control(const ClimateCall &call) override;
|
||||
|
||||
Sensor *current_temperature_{nullptr};
|
||||
Number *target_temperature_{nullptr};
|
||||
Select *mode_{nullptr};
|
||||
Select *fan_mode_{nullptr};
|
||||
Select *swing_mode_{nullptr};
|
||||
Select *preset_{nullptr};
|
||||
TextSensor *action_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace template_
|
||||
} // namespace esphome
|
@ -29,6 +29,11 @@ sensor:
|
||||
- timeout:
|
||||
timeout: 1d
|
||||
|
||||
- platform: template
|
||||
id: climate_current_temperature
|
||||
lambda: |-
|
||||
return 42;
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
- sensor.template.publish:
|
||||
@ -125,6 +130,13 @@ number:
|
||||
max_value: 100
|
||||
step: 1
|
||||
|
||||
- platform: template
|
||||
id: climate_target_temperature
|
||||
optimistic: true
|
||||
min_value: 10
|
||||
max_value: 30
|
||||
step: 0.1
|
||||
|
||||
select:
|
||||
- platform: template
|
||||
name: "Template select"
|
||||
@ -135,6 +147,34 @@ select:
|
||||
- three
|
||||
initial_option: two
|
||||
|
||||
- platform: template
|
||||
id: climate_mode
|
||||
optimistic: true
|
||||
options:
|
||||
- "OFF"
|
||||
- "HEAT"
|
||||
|
||||
- platform: template
|
||||
id: climate_fan_mode
|
||||
optimistic: true
|
||||
options:
|
||||
- "ON"
|
||||
- "AUTO"
|
||||
|
||||
- platform: template
|
||||
id: climate_swing_mode
|
||||
optimistic: true
|
||||
options:
|
||||
- "OFF"
|
||||
- "HORIZONTAL"
|
||||
|
||||
- platform: template
|
||||
id: climate_preset
|
||||
optimistic: true
|
||||
options:
|
||||
- "NONE"
|
||||
- "ECO"
|
||||
|
||||
lock:
|
||||
- platform: template
|
||||
name: "Template Lock"
|
||||
@ -187,6 +227,10 @@ text:
|
||||
format: Template Text set to %s
|
||||
args: ["x.c_str()"]
|
||||
|
||||
text_sensor:
|
||||
- platform: template
|
||||
id: climate_action
|
||||
|
||||
alarm_control_panel:
|
||||
- platform: template
|
||||
name: Alarm Panel
|
||||
@ -240,6 +284,33 @@ datetime:
|
||||
- x.minute
|
||||
- x.second
|
||||
|
||||
climate:
|
||||
- platform: template
|
||||
name: Nice Climate Template Component
|
||||
target_temperature_id: climate_target_temperature
|
||||
current_temperature_id: climate_current_temperature
|
||||
mode_id: climate_mode
|
||||
fan_mode_id: climate_fan_mode
|
||||
swing_mode_id: climate_swing_mode
|
||||
preset_id: climate_preset
|
||||
action_id: climate_action
|
||||
supported_modes:
|
||||
- "OFF"
|
||||
- "COOL"
|
||||
- "HEAT"
|
||||
- "AUTO"
|
||||
supported_fan_modes:
|
||||
- "ON"
|
||||
- "QUIET"
|
||||
supported_swing_modes:
|
||||
- "OFF"
|
||||
- "HORIZONTAL"
|
||||
supported_presets:
|
||||
- "NONE"
|
||||
- "ECO"
|
||||
visual:
|
||||
temperature_step: 0.1C
|
||||
|
||||
time:
|
||||
- platform: sntp # Required for datetime
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user