mirror of
https://github.com/esphome/esphome.git
synced 2025-10-29 22:24:26 +00:00
🏗 Merge C++ into python codebase (#504)
## Description: Move esphome-core codebase into esphome (and a bunch of other refactors). See https://github.com/esphome/feature-requests/issues/97 Yes this is a shit ton of work and no there's no way to automate it :( But it will be worth it 👍 Progress: - Core support (file copy etc): 80% - Base Abstractions (light, switch): ~50% - Integrations: ~10% - Working? Yes, (but only with ported components). Other refactors: - Moves all codegen related stuff into a single class: `esphome.codegen` (imported as `cg`) - Rework coroutine syntax - Move from `component/platform.py` to `domain/component.py` structure as with HA - Move all defaults out of C++ and into config validation. - Remove `make_...` helpers from Application class. Reason: Merge conflicts with every single new integration. - Pointer Variables are stored globally instead of locally in setup(). Reason: stack size limit. Future work: - Rework const.py - Move all `CONF_...` into a conf class (usage `conf.UPDATE_INTERVAL` vs `CONF_UPDATE_INTERVAL`). Reason: Less convoluted import block - Enable loading from `custom_components` folder. **Related issue (if applicable):** https://github.com/esphome/feature-requests/issues/97 **Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here> ## Checklist: - [ ] The code change is tested and works locally. - [ ] Tests have been added to verify that the new code works (under `tests/` folder). If user exposed functionality or configuration variables are added/changed: - [ ] Documentation added/updated in [esphomedocs](https://github.com/OttoWinter/esphomedocs).
This commit is contained in:
@@ -1,28 +1,21 @@
|
||||
import voluptuous as vol
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.automation import ACTION_REGISTRY, maybe_simple_id, Condition
|
||||
from esphome.components import mqtt
|
||||
from esphome.components.mqtt import setup_mqtt_component
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_INTERNAL, CONF_MQTT_ID, CONF_DEVICE_CLASS, CONF_STATE, \
|
||||
CONF_POSITION, CONF_TILT, CONF_STOP
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable, templatable
|
||||
from esphome.cpp_types import Action, Nameable, esphome_ns, App
|
||||
from esphome.const import CONF_ID, CONF_INTERNAL, CONF_DEVICE_CLASS, CONF_STATE, \
|
||||
CONF_POSITION, CONF_TILT, CONF_STOP, CONF_MQTT_ID
|
||||
from esphome.core import CORE, coroutine
|
||||
|
||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
|
||||
|
||||
})
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
DEVICE_CLASSES = [
|
||||
'', 'awning', 'blind', 'curtain', 'damper', 'door', 'garage',
|
||||
'shade', 'shutter', 'window'
|
||||
]
|
||||
|
||||
cover_ns = esphome_ns.namespace('cover')
|
||||
cover_ns = cg.esphome_ns.namespace('cover')
|
||||
|
||||
Cover = cover_ns.class_('Cover', Nameable)
|
||||
MQTTCoverComponent = cover_ns.class_('MQTTCoverComponent', mqtt.MQTTComponent)
|
||||
Cover = cover_ns.class_('Cover', cg.Nameable)
|
||||
|
||||
COVER_OPEN = cover_ns.COVER_OPEN
|
||||
COVER_CLOSED = cover_ns.COVER_CLOSED
|
||||
@@ -42,112 +35,102 @@ COVER_OPERATIONS = {
|
||||
validate_cover_operation = cv.one_of(*COVER_OPERATIONS, upper=True)
|
||||
|
||||
# Actions
|
||||
OpenAction = cover_ns.class_('OpenAction', Action)
|
||||
CloseAction = cover_ns.class_('CloseAction', Action)
|
||||
StopAction = cover_ns.class_('StopAction', Action)
|
||||
ControlAction = cover_ns.class_('ControlAction', Action)
|
||||
OpenAction = cover_ns.class_('OpenAction', cg.Action)
|
||||
CloseAction = cover_ns.class_('CloseAction', cg.Action)
|
||||
StopAction = cover_ns.class_('StopAction', cg.Action)
|
||||
ControlAction = cover_ns.class_('ControlAction', cg.Action)
|
||||
CoverPublishAction = cover_ns.class_('CoverPublishAction', cg.Action)
|
||||
CoverIsOpenCondition = cover_ns.class_('CoverIsOpenCondition', Condition)
|
||||
CoverIsClosedCondition = cover_ns.class_('CoverIsClosedCondition', Condition)
|
||||
|
||||
COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(Cover),
|
||||
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTCoverComponent),
|
||||
vol.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
|
||||
cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_variable_id(mqtt.MQTTCoverComponent),
|
||||
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
|
||||
# TODO: MQTT topic options
|
||||
})
|
||||
|
||||
COVER_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(COVER_SCHEMA.schema)
|
||||
|
||||
|
||||
def setup_cover_core_(cover_var, config):
|
||||
@coroutine
|
||||
def setup_cover_core_(var, config):
|
||||
if CONF_INTERNAL in config:
|
||||
add(cover_var.set_internal(config[CONF_INTERNAL]))
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
add(cover_var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||
setup_mqtt_component(cover_var.Pget_mqtt(), config)
|
||||
|
||||
|
||||
def setup_cover(cover_obj, config):
|
||||
CORE.add_job(setup_cover_core_, cover_obj, config)
|
||||
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
yield mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
|
||||
@coroutine
|
||||
def register_cover(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = Pvariable(config[CONF_ID], var, has_side_effects=True)
|
||||
add(App.register_cover(var))
|
||||
CORE.add_job(setup_cover_core_, var, config)
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_cover(var))
|
||||
yield setup_cover_core_(var, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_COVER'
|
||||
|
||||
CONF_COVER_OPEN = 'cover.open'
|
||||
COVER_OPEN_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(Cover),
|
||||
COVER_ACTION_SCHEMA = maybe_simple_id({
|
||||
cv.Required(CONF_ID): cv.use_variable_id(Cover),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_COVER_OPEN, COVER_OPEN_ACTION_SCHEMA)
|
||||
@ACTION_REGISTRY.register('cover.open', COVER_ACTION_SCHEMA)
|
||||
def cover_open_to_code(config, action_id, template_arg, args):
|
||||
var = yield get_variable(config[CONF_ID])
|
||||
var = yield cg.get_variable(config[CONF_ID])
|
||||
type = OpenAction.template(template_arg)
|
||||
rhs = type.new(var)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
yield cg.Pvariable(action_id, rhs, type=type)
|
||||
|
||||
|
||||
CONF_COVER_CLOSE = 'cover.close'
|
||||
COVER_CLOSE_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(Cover),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_COVER_CLOSE, COVER_CLOSE_ACTION_SCHEMA)
|
||||
@ACTION_REGISTRY.register('cover.close', COVER_ACTION_SCHEMA)
|
||||
def cover_close_to_code(config, action_id, template_arg, args):
|
||||
var = yield get_variable(config[CONF_ID])
|
||||
var = yield cg.get_variable(config[CONF_ID])
|
||||
type = CloseAction.template(template_arg)
|
||||
rhs = type.new(var)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
yield cg.Pvariable(action_id, rhs, type=type)
|
||||
|
||||
|
||||
CONF_COVER_STOP = 'cover.stop'
|
||||
COVER_STOP_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(Cover),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_COVER_STOP, COVER_STOP_ACTION_SCHEMA)
|
||||
@ACTION_REGISTRY.register('cover.stop', COVER_ACTION_SCHEMA)
|
||||
def cover_stop_to_code(config, action_id, template_arg, args):
|
||||
var = yield get_variable(config[CONF_ID])
|
||||
var = yield cg.get_variable(config[CONF_ID])
|
||||
type = StopAction.template(template_arg)
|
||||
rhs = type.new(var)
|
||||
yield Pvariable(action_id, rhs, type=type)
|
||||
yield cg.Pvariable(action_id, rhs, type=type)
|
||||
|
||||
|
||||
CONF_COVER_CONTROL = 'cover.control'
|
||||
COVER_CONTROL_ACTION_SCHEMA = cv.Schema({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(Cover),
|
||||
vol.Optional(CONF_STOP): cv.templatable(cv.boolean),
|
||||
vol.Exclusive(CONF_STATE, 'pos'): cv.templatable(cv.one_of(*COVER_STATES)),
|
||||
vol.Exclusive(CONF_POSITION, 'pos'): cv.templatable(cv.percentage),
|
||||
vol.Optional(CONF_TILT): cv.templatable(cv.percentage),
|
||||
cv.Required(CONF_ID): cv.use_variable_id(Cover),
|
||||
cv.Optional(CONF_STOP): cv.templatable(cv.boolean),
|
||||
cv.Exclusive(CONF_STATE, 'pos'): cv.templatable(cv.one_of(*COVER_STATES)),
|
||||
cv.Exclusive(CONF_POSITION, 'pos'): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_TILT): cv.templatable(cv.percentage),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_COVER_CONTROL, COVER_CONTROL_ACTION_SCHEMA)
|
||||
@ACTION_REGISTRY.register('cover.control', COVER_CONTROL_ACTION_SCHEMA)
|
||||
def cover_control_to_code(config, action_id, template_arg, args):
|
||||
var = yield get_variable(config[CONF_ID])
|
||||
var = yield cg.get_variable(config[CONF_ID])
|
||||
type = StopAction.template(template_arg)
|
||||
rhs = type.new(var)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
action = cg.Pvariable(action_id, rhs, type=type)
|
||||
if CONF_STOP in config:
|
||||
template_ = yield templatable(config[CONF_STOP], args, bool)
|
||||
add(action.set_stop(template_))
|
||||
template_ = yield cg.templatable(config[CONF_STOP], args, bool)
|
||||
cg.add(action.set_stop(template_))
|
||||
if CONF_STATE in config:
|
||||
template_ = yield templatable(config[CONF_STATE], args, float,
|
||||
to_exp=COVER_STATES)
|
||||
add(action.set_position(template_))
|
||||
template_ = yield cg.templatable(config[CONF_STATE], args, float,
|
||||
to_exp=COVER_STATES)
|
||||
cg.add(action.set_position(template_))
|
||||
if CONF_POSITION in config:
|
||||
template_ = yield templatable(config[CONF_POSITION], args, float)
|
||||
add(action.set_position(template_))
|
||||
template_ = yield cg.templatable(config[CONF_POSITION], args, float)
|
||||
cg.add(action.set_position(template_))
|
||||
if CONF_TILT in config:
|
||||
template_ = yield templatable(config[CONF_TILT], args, float)
|
||||
add(action.set_tilt(template_))
|
||||
template_ = yield cg.templatable(config[CONF_TILT], args, float)
|
||||
cg.add(action.set_tilt(template_))
|
||||
yield action
|
||||
|
||||
|
||||
def to_code(config):
|
||||
cg.add_define('USE_COVER')
|
||||
cg.add_global(cover_ns.using)
|
||||
|
||||
113
esphome/components/cover/automation.h
Normal file
113
esphome/components/cover/automation.h
Normal file
@@ -0,0 +1,113 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "cover.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
|
||||
template<typename... Ts> class OpenAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit OpenAction(Cover *cover) : cover_(cover) {}
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->cover_->open();
|
||||
this->play_next(x...);
|
||||
}
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class CloseAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit CloseAction(Cover *cover) : cover_(cover) {}
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->cover_->close();
|
||||
this->play_next(x...);
|
||||
}
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class StopAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit StopAction(Cover *cover) : cover_(cover) {}
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->cover_->stop();
|
||||
this->play_next(x...);
|
||||
}
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ControlAction(Cover *cover) : cover_(cover) {}
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto call = this->cover_->make_call();
|
||||
if (this->stop_.has_value())
|
||||
call.set_stop(this->stop_.value(x...));
|
||||
if (this->position_.has_value())
|
||||
call.set_position(this->position_.value(x...));
|
||||
if (this->tilt_.has_value())
|
||||
call.set_tilt(this->tilt_.value(x...));
|
||||
call.perform();
|
||||
this->play_next(x...);
|
||||
}
|
||||
|
||||
TEMPLATABLE_VALUE(bool, stop)
|
||||
TEMPLATABLE_VALUE(float, position)
|
||||
TEMPLATABLE_VALUE(float, tilt)
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class CoverPublishAction : public Action<Ts...> {
|
||||
public:
|
||||
CoverPublishAction(Cover *cover) : cover_(cover) {}
|
||||
void play(Ts... x) override {
|
||||
if (this->position_.has_value())
|
||||
this->cover_->position = this->position_.value(x...);
|
||||
if (this->tilt_.has_value())
|
||||
this->cover_->tilt = this->tilt_.value(x...);
|
||||
if (this->current_operation_.has_value())
|
||||
this->cover_->current_operation = this->current_operation_.value(x...);
|
||||
this->cover_->publish_state();
|
||||
this->play_next(x...);
|
||||
}
|
||||
|
||||
TEMPLATABLE_VALUE(float, position)
|
||||
TEMPLATABLE_VALUE(float, tilt)
|
||||
TEMPLATABLE_VALUE(CoverOperation, current_operation)
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class CoverIsOpenCondition : public Condition<Ts...> {
|
||||
public:
|
||||
CoverIsOpenCondition(Cover *cover) : cover_(cover) {}
|
||||
bool check(Ts... x) override { return this->cover_->is_fully_open(); }
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
template<typename... Ts> class CoverIsClosedCondition : public Condition<Ts...> {
|
||||
public:
|
||||
CoverIsClosedCondition(Cover *cover) : cover_(cover) {}
|
||||
bool check(Ts... x) override { return this->cover_->is_fully_closed(); }
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
214
esphome/components/cover/cover.cpp
Normal file
214
esphome/components/cover/cover.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
#include "cover.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
|
||||
static const char *TAG = "cover";
|
||||
|
||||
const float COVER_OPEN = 1.0f;
|
||||
const float COVER_CLOSED = 0.0f;
|
||||
|
||||
const char *cover_command_to_str(float pos) {
|
||||
if (pos == COVER_OPEN) {
|
||||
return "OPEN";
|
||||
} else if (pos == COVER_CLOSED) {
|
||||
return "CLOSE";
|
||||
} else {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
const char *cover_operation_to_str(CoverOperation op) {
|
||||
switch (op) {
|
||||
case COVER_OPERATION_IDLE:
|
||||
return "IDLE";
|
||||
case COVER_OPERATION_OPENING:
|
||||
return "OPENING";
|
||||
case COVER_OPERATION_CLOSING:
|
||||
return "CLOSING";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
Cover::Cover(const std::string &name) : Nameable(name), position{COVER_OPEN} {}
|
||||
|
||||
uint32_t Cover::hash_base() { return 1727367479UL; }
|
||||
|
||||
CoverCall::CoverCall(Cover *parent) : parent_(parent) {}
|
||||
CoverCall &CoverCall::set_command(const char *command) {
|
||||
if (strcasecmp(command, "OPEN") == 0) {
|
||||
this->set_command_open();
|
||||
} else if (strcasecmp(command, "CLOSE") == 0) {
|
||||
this->set_command_close();
|
||||
} else if (strcasecmp(command, "STOP") == 0) {
|
||||
this->set_command_stop();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
CoverCall &CoverCall::set_command_open() {
|
||||
this->position_ = COVER_OPEN;
|
||||
return *this;
|
||||
}
|
||||
CoverCall &CoverCall::set_command_close() {
|
||||
this->position_ = COVER_CLOSED;
|
||||
return *this;
|
||||
}
|
||||
CoverCall &CoverCall::set_command_stop() {
|
||||
this->stop_ = true;
|
||||
return *this;
|
||||
}
|
||||
CoverCall &CoverCall::set_position(float position) {
|
||||
this->position_ = position;
|
||||
return *this;
|
||||
}
|
||||
CoverCall &CoverCall::set_tilt(float tilt) {
|
||||
this->tilt_ = tilt;
|
||||
return *this;
|
||||
}
|
||||
void CoverCall::perform() {
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
auto traits = this->parent_->get_traits();
|
||||
this->validate_();
|
||||
if (this->stop_) {
|
||||
ESP_LOGD(TAG, " Command: STOP");
|
||||
}
|
||||
if (this->position_.has_value()) {
|
||||
if (traits.get_supports_position()) {
|
||||
ESP_LOGD(TAG, " Position: %.0f%%", *this->position_ * 100.0f);
|
||||
} else {
|
||||
ESP_LOGD(TAG, " Command: %s", cover_command_to_str(*this->position_));
|
||||
}
|
||||
}
|
||||
if (this->tilt_.has_value()) {
|
||||
ESP_LOGD(TAG, " Tilt: %.0f%%", *this->tilt_ * 100.0f);
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
const optional<float> &CoverCall::get_position() const { return this->position_; }
|
||||
const optional<float> &CoverCall::get_tilt() const { return this->tilt_; }
|
||||
void CoverCall::validate_() {
|
||||
auto traits = this->parent_->get_traits();
|
||||
if (this->position_.has_value()) {
|
||||
auto pos = *this->position_;
|
||||
if (!traits.get_supports_position() && pos != COVER_OPEN && pos != COVER_CLOSED) {
|
||||
ESP_LOGW(TAG, "'%s' - This cover device does not support setting position!", this->parent_->get_name().c_str());
|
||||
this->position_.reset();
|
||||
} else if (pos < 0.0f || pos > 1.0f) {
|
||||
ESP_LOGW(TAG, "'%s' - Position %.2f is out of range [0.0 - 1.0]", this->parent_->get_name().c_str(), pos);
|
||||
this->position_ = clamp(pos, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
if (this->tilt_.has_value()) {
|
||||
auto tilt = *this->tilt_;
|
||||
if (!traits.get_supports_tilt()) {
|
||||
ESP_LOGW(TAG, "'%s' - This cover device does not support tilt!", this->parent_->get_name().c_str());
|
||||
this->tilt_.reset();
|
||||
} else if (tilt < 0.0f || tilt > 1.0f) {
|
||||
ESP_LOGW(TAG, "'%s' - Tilt %.2f is out of range [0.0 - 1.0]", this->parent_->get_name().c_str(), tilt);
|
||||
this->tilt_ = clamp(tilt, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
if (this->stop_) {
|
||||
if (this->position_.has_value()) {
|
||||
ESP_LOGW(TAG, "Cannot set position when stopping a cover!");
|
||||
this->position_.reset();
|
||||
}
|
||||
if (this->tilt_.has_value()) {
|
||||
ESP_LOGW(TAG, "Cannot set tilt when stopping a cover!");
|
||||
this->tilt_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
CoverCall &CoverCall::set_stop(bool stop) {
|
||||
this->stop_ = stop;
|
||||
return *this;
|
||||
}
|
||||
bool CoverCall::get_stop() const { return this->stop_; }
|
||||
void Cover::set_device_class(const std::string &device_class) { this->device_class_override_ = device_class; }
|
||||
CoverCall Cover::make_call() { return {this}; }
|
||||
void Cover::open() {
|
||||
auto call = this->make_call();
|
||||
call.set_command_open();
|
||||
call.perform();
|
||||
}
|
||||
void Cover::close() {
|
||||
auto call = this->make_call();
|
||||
call.set_command_close();
|
||||
call.perform();
|
||||
}
|
||||
void Cover::stop() {
|
||||
auto call = this->make_call();
|
||||
call.set_command_stop();
|
||||
call.perform();
|
||||
}
|
||||
void Cover::add_on_state_callback(std::function<void()> &&f) { this->state_callback_.add(std::move(f)); }
|
||||
void Cover::publish_state(bool save) {
|
||||
this->position = clamp(this->position, 0.0f, 1.0f);
|
||||
this->tilt = clamp(this->tilt, 0.0f, 1.0f);
|
||||
|
||||
ESP_LOGD(TAG, "'%s' - Publishing:", this->name_.c_str());
|
||||
auto traits = this->get_traits();
|
||||
if (traits.get_supports_position()) {
|
||||
ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f);
|
||||
} else {
|
||||
if (this->position == COVER_OPEN) {
|
||||
ESP_LOGD(TAG, " State: OPEN");
|
||||
} else if (this->position == COVER_CLOSED) {
|
||||
ESP_LOGD(TAG, " State: CLOSED");
|
||||
} else {
|
||||
ESP_LOGD(TAG, " State: UNKNOWN");
|
||||
}
|
||||
}
|
||||
if (traits.get_supports_tilt()) {
|
||||
ESP_LOGD(TAG, " Tilt: %.0f%%", this->tilt * 100.0f);
|
||||
}
|
||||
ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation));
|
||||
|
||||
this->state_callback_.call();
|
||||
|
||||
if (save) {
|
||||
CoverRestoreState restore{};
|
||||
memset(&restore, 0, sizeof(restore));
|
||||
restore.position = this->position;
|
||||
if (traits.get_supports_tilt()) {
|
||||
restore.tilt = this->tilt;
|
||||
}
|
||||
this->rtc_.save(&restore);
|
||||
}
|
||||
}
|
||||
optional<CoverRestoreState> Cover::restore_state_() {
|
||||
this->rtc_ = global_preferences.make_preference<CoverRestoreState>(this->get_object_id_hash());
|
||||
CoverRestoreState recovered{};
|
||||
if (!this->rtc_.load(&recovered))
|
||||
return {};
|
||||
return recovered;
|
||||
}
|
||||
Cover::Cover() : Cover("") {}
|
||||
std::string Cover::get_device_class() {
|
||||
if (this->device_class_override_.has_value())
|
||||
return *this->device_class_override_;
|
||||
return this->device_class();
|
||||
}
|
||||
bool Cover::is_fully_open() const { return this->position == COVER_OPEN; }
|
||||
bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; }
|
||||
std::string Cover::device_class() { return ""; }
|
||||
|
||||
CoverCall CoverRestoreState::to_call(Cover *cover) {
|
||||
auto call = cover->make_call();
|
||||
auto traits = cover->get_traits();
|
||||
call.set_position(this->position);
|
||||
if (traits.get_supports_tilt())
|
||||
call.set_tilt(this->tilt);
|
||||
return call;
|
||||
}
|
||||
void CoverRestoreState::apply(Cover *cover) {
|
||||
cover->position = this->position;
|
||||
cover->tilt = this->tilt;
|
||||
cover->publish_state();
|
||||
}
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
179
esphome/components/cover/cover.h
Normal file
179
esphome/components/cover/cover.h
Normal file
@@ -0,0 +1,179 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "cover_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
|
||||
const extern float COVER_OPEN;
|
||||
const extern float COVER_CLOSED;
|
||||
|
||||
#define LOG_COVER(prefix, type, obj) \
|
||||
if (obj != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, prefix type " '%s'", obj->get_name().c_str()); \
|
||||
auto traits_ = obj->get_traits(); \
|
||||
if (traits_.get_is_assumed_state()) { \
|
||||
ESP_LOGCONFIG(TAG, prefix " Assumed State: YES"); \
|
||||
} \
|
||||
if (!obj->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, prefix " Device Class: '%s'", obj->get_device_class().c_str()); \
|
||||
} \
|
||||
}
|
||||
|
||||
class Cover;
|
||||
|
||||
class CoverCall {
|
||||
public:
|
||||
CoverCall(Cover *parent);
|
||||
|
||||
/// Set the command as a string, "STOP", "OPEN", "CLOSE".
|
||||
CoverCall &set_command(const char *command);
|
||||
/// Set the command to open the cover.
|
||||
CoverCall &set_command_open();
|
||||
/// Set the command to close the cover.
|
||||
CoverCall &set_command_close();
|
||||
/// Set the command to stop the cover.
|
||||
CoverCall &set_command_stop();
|
||||
/// Set the call to a certain target position.
|
||||
CoverCall &set_position(float position);
|
||||
/// Set the call to a certain target tilt.
|
||||
CoverCall &set_tilt(float tilt);
|
||||
/// Set whether this cover call should stop the cover.
|
||||
CoverCall &set_stop(bool stop);
|
||||
|
||||
/// Perform the cover call.
|
||||
void perform();
|
||||
|
||||
const optional<float> &get_position() const;
|
||||
bool get_stop() const;
|
||||
const optional<float> &get_tilt() const;
|
||||
|
||||
protected:
|
||||
void validate_();
|
||||
|
||||
Cover *parent_;
|
||||
bool stop_{false};
|
||||
optional<float> position_{};
|
||||
optional<float> tilt_{};
|
||||
};
|
||||
|
||||
/// Struct used to store the restored state of a cover
|
||||
struct CoverRestoreState {
|
||||
float position;
|
||||
float tilt;
|
||||
|
||||
/// Convert this struct to a cover call that can be performed.
|
||||
CoverCall to_call(Cover *cover);
|
||||
/// Apply these settings to the cover
|
||||
void apply(Cover *cover);
|
||||
} __attribute__((packed));
|
||||
|
||||
/// Enum encoding the current operation of a cover.
|
||||
enum CoverOperation : uint8_t {
|
||||
/// The cover is currently idle (not moving)
|
||||
COVER_OPERATION_IDLE = 0,
|
||||
/// The cover is currently opening.
|
||||
COVER_OPERATION_OPENING,
|
||||
/// The cover is currently closing.
|
||||
COVER_OPERATION_CLOSING,
|
||||
};
|
||||
|
||||
const char *cover_operation_to_str(CoverOperation op);
|
||||
|
||||
/** Base class for all cover devices.
|
||||
*
|
||||
* Covers currently have three properties:
|
||||
* - position - The current position of the cover from 0.0 (fully closed) to 1.0 (fully open).
|
||||
* For covers with only binary OPEN/CLOSED position this will always be either 0.0 or 1.0
|
||||
* - tilt - The tilt value of the cover from 0.0 (closed) to 1.0 (closed)
|
||||
* - current_operation - The operation the cover is currently performing, this can
|
||||
* be one of IDLE, OPENING and CLOSING.
|
||||
*
|
||||
* For users: All cover operations must be performed over the .make_call() interface.
|
||||
* To command a cover, use .make_call() to create a call object, set all properties
|
||||
* you wish to set, and activate the command with .perform().
|
||||
* For reading out the current values of the cover, use the public .position, .tilt etc
|
||||
* properties (these are read-only for users)
|
||||
*
|
||||
* For integrations: Integrations must implement two methods: control() and get_traits().
|
||||
* Control will be called with the arguments supplied by the user and should be used
|
||||
* to control all values of the cover. Also implement get_traits() to return what operations
|
||||
* the cover supports.
|
||||
*/
|
||||
class Cover : public Nameable {
|
||||
public:
|
||||
explicit Cover(const std::string &name);
|
||||
explicit Cover();
|
||||
|
||||
/// The current operation of the cover (idle, opening, closing).
|
||||
CoverOperation current_operation{COVER_OPERATION_IDLE};
|
||||
union {
|
||||
/** The position of the cover from 0.0 (fully closed) to 1.0 (fully open).
|
||||
*
|
||||
* For binary covers this is always equals to 0.0 or 1.0 (see also COVER_OPEN and
|
||||
* COVER_CLOSED constants).
|
||||
*/
|
||||
float position;
|
||||
ESPDEPRECATED("<cover>.state is deprecated, please use .position instead") float state;
|
||||
};
|
||||
/// The current tilt value of the cover from 0.0 to 1.0.
|
||||
float tilt{COVER_OPEN};
|
||||
|
||||
/// Construct a new cover call used to control the cover.
|
||||
CoverCall make_call();
|
||||
/** Open the cover.
|
||||
*
|
||||
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||
*/
|
||||
void open();
|
||||
/** Close the cover.
|
||||
*
|
||||
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||
*/
|
||||
void close();
|
||||
/** Stop the cover.
|
||||
*
|
||||
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
void add_on_state_callback(std::function<void()> &&f);
|
||||
|
||||
/** Publish the current state of the cover.
|
||||
*
|
||||
* First set the .position, .tilt, etc values and then call this method
|
||||
* to publish the state of the cover.
|
||||
*
|
||||
* @param save Whether to save the updated values in RTC area.
|
||||
*/
|
||||
void publish_state(bool save = true);
|
||||
|
||||
virtual CoverTraits get_traits() = 0;
|
||||
void set_device_class(const std::string &device_class);
|
||||
std::string get_device_class();
|
||||
|
||||
/// Helper method to check if the cover is fully open. Equivalent to comparing .position against 1.0
|
||||
bool is_fully_open() const;
|
||||
/// Helper method to check if the cover is fully closed. Equivalent to comparing .position against 0.0
|
||||
bool is_fully_closed() const;
|
||||
|
||||
protected:
|
||||
friend CoverCall;
|
||||
|
||||
virtual void control(const CoverCall &call) = 0;
|
||||
virtual std::string device_class();
|
||||
|
||||
optional<CoverRestoreState> restore_state_();
|
||||
uint32_t hash_base() override;
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
optional<std::string> device_class_override_{};
|
||||
|
||||
ESPPreferenceObject rtc_;
|
||||
};
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
24
esphome/components/cover/cover_traits.h
Normal file
24
esphome/components/cover/cover_traits.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
|
||||
class CoverTraits {
|
||||
public:
|
||||
CoverTraits() = default;
|
||||
|
||||
bool get_is_assumed_state() const { return this->is_assumed_state_; }
|
||||
void set_is_assumed_state(bool is_assumed_state) { this->is_assumed_state_ = is_assumed_state; }
|
||||
bool get_supports_position() const { return this->supports_position_; }
|
||||
void set_supports_position(bool supports_position) { this->supports_position_ = supports_position; }
|
||||
bool get_supports_tilt() const { return this->supports_tilt_; }
|
||||
void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; }
|
||||
|
||||
protected:
|
||||
bool is_assumed_state_{false};
|
||||
bool supports_position_{false};
|
||||
bool supports_tilt_{false};
|
||||
};
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
@@ -1,55 +0,0 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import automation
|
||||
from esphome.components import binary_sensor, cover
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CLOSE_ACTION, CONF_CLOSE_DURATION, \
|
||||
CONF_CLOSE_ENDSTOP, CONF_ID, CONF_NAME, CONF_OPEN_ACTION, CONF_OPEN_DURATION, \
|
||||
CONF_OPEN_ENDSTOP, CONF_STOP_ACTION, CONF_MAX_DURATION
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Component
|
||||
|
||||
EndstopCover = cover.cover_ns.class_('EndstopCover', cover.Cover, Component)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(cover.COVER_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(EndstopCover),
|
||||
vol.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
|
||||
|
||||
vol.Required(CONF_OPEN_ENDSTOP): cv.use_variable_id(binary_sensor.BinarySensor),
|
||||
vol.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True),
|
||||
vol.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds,
|
||||
|
||||
vol.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
|
||||
vol.Required(CONF_CLOSE_ENDSTOP): cv.use_variable_id(binary_sensor.BinarySensor),
|
||||
vol.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.register_component(EndstopCover.new(config[CONF_NAME]))
|
||||
var = Pvariable(config[CONF_ID], rhs)
|
||||
cover.register_cover(var, config)
|
||||
setup_component(var, config)
|
||||
|
||||
automation.build_automations(var.get_stop_trigger(), [],
|
||||
config[CONF_STOP_ACTION])
|
||||
|
||||
bin = yield get_variable(config[CONF_OPEN_ENDSTOP])
|
||||
add(var.set_open_endstop(bin))
|
||||
add(var.set_open_duration(config[CONF_OPEN_DURATION]))
|
||||
automation.build_automations(var.get_open_trigger(), [],
|
||||
config[CONF_OPEN_ACTION])
|
||||
|
||||
bin = yield get_variable(config[CONF_CLOSE_ENDSTOP])
|
||||
add(var.set_close_endstop(bin))
|
||||
add(var.set_close_duration(config[CONF_CLOSE_DURATION]))
|
||||
automation.build_automations(var.get_close_trigger(), [],
|
||||
config[CONF_CLOSE_ACTION])
|
||||
|
||||
if CONF_MAX_DURATION in config:
|
||||
add(var.set_max_duration(config[CONF_MAX_DURATION]))
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_ENDSTOP_COVER'
|
||||
@@ -1,93 +0,0 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import ACTION_REGISTRY
|
||||
from esphome.components import cover
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ASSUMED_STATE, CONF_CLOSE_ACTION, CONF_CURRENT_OPERATION, CONF_ID, \
|
||||
CONF_LAMBDA, CONF_NAME, CONF_OPEN_ACTION, CONF_OPTIMISTIC, CONF_POSITION, CONF_RESTORE_MODE, \
|
||||
CONF_STATE, CONF_STOP_ACTION
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda, templatable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import Action, App, optional
|
||||
|
||||
TemplateCover = cover.cover_ns.class_('TemplateCover', cover.Cover)
|
||||
CoverPublishAction = cover.cover_ns.class_('CoverPublishAction', Action)
|
||||
|
||||
TemplateCoverRestoreMode = cover.cover_ns.enum('TemplateCoverRestoreMode')
|
||||
RESTORE_MODES = {
|
||||
'NO_RESTORE': TemplateCoverRestoreMode.NO_RESTORE,
|
||||
'RESTORE': TemplateCoverRestoreMode.RESTORE,
|
||||
'RESTORE_AND_CALL': TemplateCoverRestoreMode.RESTORE_AND_CALL,
|
||||
}
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(cover.COVER_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(TemplateCover),
|
||||
vol.Optional(CONF_LAMBDA): cv.lambda_,
|
||||
vol.Optional(CONF_OPTIMISTIC): cv.boolean,
|
||||
vol.Optional(CONF_ASSUMED_STATE): cv.boolean,
|
||||
vol.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True),
|
||||
vol.Optional(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
|
||||
vol.Optional(CONF_STOP_ACTION): automation.validate_automation(single=True),
|
||||
vol.Optional(CONF_RESTORE_MODE): cv.one_of(*RESTORE_MODES, upper=True),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.register_component(TemplateCover.new(config[CONF_NAME]))
|
||||
var = Pvariable(config[CONF_ID], rhs)
|
||||
cover.register_cover(var, config)
|
||||
setup_component(var, config)
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
template_ = yield process_lambda(config[CONF_LAMBDA], [],
|
||||
return_type=optional.template(float))
|
||||
add(var.set_state_lambda(template_))
|
||||
if CONF_OPEN_ACTION in config:
|
||||
automation.build_automations(var.get_open_trigger(), [],
|
||||
config[CONF_OPEN_ACTION])
|
||||
if CONF_CLOSE_ACTION in config:
|
||||
automation.build_automations(var.get_close_trigger(), [],
|
||||
config[CONF_CLOSE_ACTION])
|
||||
if CONF_STOP_ACTION in config:
|
||||
automation.build_automations(var.get_stop_trigger(), [],
|
||||
config[CONF_STOP_ACTION])
|
||||
if CONF_OPTIMISTIC in config:
|
||||
add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
if CONF_ASSUMED_STATE in config:
|
||||
add(var.set_assumed_state(config[CONF_ASSUMED_STATE]))
|
||||
if CONF_RESTORE_MODE in config:
|
||||
add(var.set_restore_mode(RESTORE_MODES[config[CONF_RESTORE_MODE]]))
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_TEMPLATE_COVER'
|
||||
|
||||
CONF_COVER_TEMPLATE_PUBLISH = 'cover.template.publish'
|
||||
COVER_TEMPLATE_PUBLISH_ACTION_SCHEMA = cv.Schema({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(cover.Cover),
|
||||
vol.Exclusive(CONF_STATE, 'pos'): cv.templatable(cover.validate_cover_state),
|
||||
vol.Exclusive(CONF_POSITION, 'pos'): cv.templatable(cv.zero_to_one_float),
|
||||
vol.Optional(CONF_CURRENT_OPERATION): cv.templatable(cover.validate_cover_operation),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_COVER_TEMPLATE_PUBLISH,
|
||||
COVER_TEMPLATE_PUBLISH_ACTION_SCHEMA)
|
||||
def cover_template_publish_to_code(config, action_id, template_arg, args):
|
||||
var = yield get_variable(config[CONF_ID])
|
||||
type = CoverPublishAction.template(template_arg)
|
||||
rhs = type.new(var)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
if CONF_STATE in config:
|
||||
template_ = yield templatable(config[CONF_STATE], args, float,
|
||||
to_exp=cover.COVER_STATES)
|
||||
add(action.set_position(template_))
|
||||
if CONF_POSITION in config:
|
||||
template_ = yield templatable(config[CONF_POSITION], args, float,
|
||||
to_exp=cover.COVER_STATES)
|
||||
add(action.set_position(template_))
|
||||
if CONF_CURRENT_OPERATION in config:
|
||||
template_ = yield templatable(config[CONF_CURRENT_OPERATION], args, cover.CoverOperation,
|
||||
to_exp=cover.COVER_OPERATIONS)
|
||||
add(action.set_current_operation(template_))
|
||||
yield action
|
||||
@@ -1,44 +0,0 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import automation
|
||||
from esphome.components import cover
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CLOSE_ACTION, CONF_CLOSE_DURATION, CONF_ID, CONF_NAME, \
|
||||
CONF_OPEN_ACTION, CONF_OPEN_DURATION, CONF_STOP_ACTION
|
||||
from esphome.cpp_generator import Pvariable, add
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Component
|
||||
|
||||
TimeBasedCover = cover.cover_ns.class_('TimeBasedCover', cover.Cover, Component)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(cover.COVER_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(TimeBasedCover),
|
||||
vol.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
|
||||
|
||||
vol.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True),
|
||||
vol.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds,
|
||||
|
||||
vol.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
|
||||
vol.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.register_component(TimeBasedCover.new(config[CONF_NAME]))
|
||||
var = Pvariable(config[CONF_ID], rhs)
|
||||
cover.register_cover(var, config)
|
||||
setup_component(var, config)
|
||||
|
||||
automation.build_automations(var.get_stop_trigger(), [],
|
||||
config[CONF_STOP_ACTION])
|
||||
|
||||
add(var.set_open_duration(config[CONF_OPEN_DURATION]))
|
||||
automation.build_automations(var.get_open_trigger(), [],
|
||||
config[CONF_OPEN_ACTION])
|
||||
|
||||
add(var.set_close_duration(config[CONF_CLOSE_DURATION]))
|
||||
automation.build_automations(var.get_close_trigger(), [],
|
||||
config[CONF_CLOSE_ACTION])
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_TIME_BASED_COVER'
|
||||
Reference in New Issue
Block a user