diff --git a/CODEOWNERS b/CODEOWNERS index 61469c53fa..4edfde7711 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -44,6 +44,7 @@ esphome/components/climate/* @esphome/core esphome/components/climate_ir/* @glmnet esphome/components/color_temperature/* @jesserockz esphome/components/coolix/* @glmnet +esphome/components/copy/* @OttoWinter esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun esphome/components/cse7761/* @berfenger diff --git a/esphome/components/copy/__init__.py b/esphome/components/copy/__init__.py new file mode 100644 index 0000000000..7594894650 --- /dev/null +++ b/esphome/components/copy/__init__.py @@ -0,0 +1,5 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@OttoWinter"] + +copy_ns = cg.esphome_ns.namespace("copy") diff --git a/esphome/components/copy/binary_sensor/__init__.py b/esphome/components/copy/binary_sensor/__init__.py new file mode 100644 index 0000000000..1b6836fae7 --- /dev/null +++ b/esphome/components/copy/binary_sensor/__init__.py @@ -0,0 +1,41 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyBinarySensor = copy_ns.class_( + "CopyBinarySensor", binary_sensor.BinarySensor, cg.Component +) + + +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(CopyBinarySensor) + .extend( + { + cv.Required(CONF_SOURCE_ID): cv.use_id(binary_sensor.BinarySensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/binary_sensor/copy_binary_sensor.cpp b/esphome/components/copy/binary_sensor/copy_binary_sensor.cpp new file mode 100644 index 0000000000..0d96f58750 --- /dev/null +++ b/esphome/components/copy/binary_sensor/copy_binary_sensor.cpp @@ -0,0 +1,18 @@ +#include "copy_binary_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.binary_sensor"; + +void CopyBinarySensor::setup() { + source_->add_on_state_callback([this](bool value) { this->publish_state(value); }); + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopyBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Copy Binary Sensor", this); } + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/binary_sensor/copy_binary_sensor.h b/esphome/components/copy/binary_sensor/copy_binary_sensor.h new file mode 100644 index 0000000000..d62ed13c76 --- /dev/null +++ b/esphome/components/copy/binary_sensor/copy_binary_sensor.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" + +namespace esphome { +namespace copy { + +class CopyBinarySensor : public binary_sensor::BinarySensor, public Component { + public: + void set_source(binary_sensor::BinarySensor *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + binary_sensor::BinarySensor *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/button/__init__.py b/esphome/components/copy/button/__init__.py new file mode 100644 index 0000000000..65d956601a --- /dev/null +++ b/esphome/components/copy/button/__init__.py @@ -0,0 +1,42 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyButton = copy_ns.class_("CopyButton", button.Button, cg.Component) + + +CONFIG_SCHEMA = ( + button.button_schema() + .extend( + { + cv.GenerateID(): cv.declare_id(CopyButton), + cv.Required(CONF_SOURCE_ID): cv.use_id(button.Button), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await button.register_button(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/button/copy_button.cpp b/esphome/components/copy/button/copy_button.cpp new file mode 100644 index 0000000000..595388775c --- /dev/null +++ b/esphome/components/copy/button/copy_button.cpp @@ -0,0 +1,14 @@ +#include "copy_button.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.button"; + +void CopyButton::dump_config() { LOG_BUTTON("", "Copy Button", this); } + +void CopyButton::press_action() { source_->press(); } + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/button/copy_button.h b/esphome/components/copy/button/copy_button.h new file mode 100644 index 0000000000..9996ca0c65 --- /dev/null +++ b/esphome/components/copy/button/copy_button.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/button/button.h" + +namespace esphome { +namespace copy { + +class CopyButton : public button::Button, public Component { + public: + void set_source(button::Button *source) { source_ = source; } + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void press_action() override; + + button::Button *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/cover/__init__.py b/esphome/components/copy/cover/__init__.py new file mode 100644 index 0000000000..155e22883b --- /dev/null +++ b/esphome/components/copy/cover/__init__.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import cover +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyCover = copy_ns.class_("CopyCover", cover.Cover, cg.Component) + + +CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopyCover), + cv.Required(CONF_SOURCE_ID): cv.use_id(cover.Cover), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cover.register_cover(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/cover/copy_cover.cpp b/esphome/components/copy/cover/copy_cover.cpp new file mode 100644 index 0000000000..cf50473018 --- /dev/null +++ b/esphome/components/copy/cover/copy_cover.cpp @@ -0,0 +1,50 @@ +#include "copy_cover.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.cover"; + +void CopyCover::setup() { + source_->add_on_state_callback([this]() { + this->current_operation = this->source_->current_operation; + this->position = this->source_->position; + this->tilt = this->source_->tilt; + this->publish_state(); + }); + + this->current_operation = this->source_->current_operation; + this->position = this->source_->position; + this->tilt = this->source_->tilt; + this->publish_state(); +} + +void CopyCover::dump_config() { LOG_COVER("", "Copy Cover", this); } + +cover::CoverTraits CopyCover::get_traits() { + auto base = source_->get_traits(); + cover::CoverTraits traits{}; + // copy traits manually so it doesn't break when new options are added + // but the control() method hasn't implemented them yet. + traits.set_is_assumed_state(base.get_is_assumed_state()); + traits.set_supports_position(base.get_supports_position()); + traits.set_supports_tilt(base.get_supports_tilt()); + traits.set_supports_toggle(base.get_supports_toggle()); + return traits; +} + +void CopyCover::control(const cover::CoverCall &call) { + auto call2 = source_->make_call(); + call2.set_stop(call.get_stop()); + if (call.get_tilt().has_value()) + call2.set_tilt(*call.get_tilt()); + if (call.get_position().has_value()) + call2.set_position(*call.get_position()); + if (call.get_tilt().has_value()) + call2.set_tilt(*call.get_tilt()); + call2.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/cover/copy_cover.h b/esphome/components/copy/cover/copy_cover.h new file mode 100644 index 0000000000..fb278523ff --- /dev/null +++ b/esphome/components/copy/cover/copy_cover.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/cover/cover.h" + +namespace esphome { +namespace copy { + +class CopyCover : public cover::Cover, public Component { + public: + void set_source(cover::Cover *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + cover::CoverTraits get_traits() override; + + protected: + void control(const cover::CoverCall &call) override; + + cover::Cover *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/fan/__init__.py b/esphome/components/copy/fan/__init__.py new file mode 100644 index 0000000000..22672c02d8 --- /dev/null +++ b/esphome/components/copy/fan/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import fan +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyFan = copy_ns.class_("CopyFan", fan.Fan, cg.Component) + + +CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopyFan), + cv.Required(CONF_SOURCE_ID): cv.use_id(fan.Fan), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await fan.register_fan(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/fan/copy_fan.cpp b/esphome/components/copy/fan/copy_fan.cpp new file mode 100644 index 0000000000..74d9da279f --- /dev/null +++ b/esphome/components/copy/fan/copy_fan.cpp @@ -0,0 +1,53 @@ +#include "copy_fan.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.fan"; + +void CopyFan::setup() { + source_->add_on_state_callback([this]() { + this->state = source_->state; + this->oscillating = source_->oscillating; + this->speed = source_->speed; + this->direction = source_->direction; + this->publish_state(); + }); + + this->state = source_->state; + this->oscillating = source_->oscillating; + this->speed = source_->speed; + this->direction = source_->direction; + this->publish_state(); +} + +void CopyFan::dump_config() { LOG_FAN("", "Copy Fan", this); } + +fan::FanTraits CopyFan::get_traits() { + fan::FanTraits traits; + auto base = source_->get_traits(); + // copy traits manually so it doesn't break when new options are added + // but the control() method hasn't implemented them yet. + traits.set_oscillation(base.supports_oscillation()); + traits.set_speed(base.supports_speed()); + traits.set_supported_speed_count(base.supported_speed_count()); + traits.set_direction(base.supports_direction()); + return traits; +} + +void CopyFan::control(const fan::FanCall &call) { + auto call2 = source_->make_call(); + if (call.get_state().has_value()) + call2.set_state(*call.get_state()); + if (call.get_oscillating().has_value()) + call2.set_oscillating(*call.get_oscillating()); + if (call.get_speed().has_value()) + call2.set_speed(*call.get_speed()); + if (call.get_direction().has_value()) + call2.set_direction(*call.get_direction()); + call2.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/fan/copy_fan.h b/esphome/components/copy/fan/copy_fan.h new file mode 100644 index 0000000000..1a69810510 --- /dev/null +++ b/esphome/components/copy/fan/copy_fan.h @@ -0,0 +1,26 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/fan/fan.h" + +namespace esphome { +namespace copy { + +class CopyFan : public fan::Fan, public Component { + public: + void set_source(fan::Fan *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + fan::FanTraits get_traits() override; + + protected: + void control(const fan::FanCall &call) override; + ; + + fan::Fan *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/lock/__init__.py b/esphome/components/copy/lock/__init__.py new file mode 100644 index 0000000000..d19e4a5807 --- /dev/null +++ b/esphome/components/copy/lock/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import lock +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyLock = copy_ns.class_("CopyLock", lock.Lock, cg.Component) + + +CONFIG_SCHEMA = lock.LOCK_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopyLock), + cv.Required(CONF_SOURCE_ID): cv.use_id(lock.Lock), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await lock.register_lock(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/lock/copy_lock.cpp b/esphome/components/copy/lock/copy_lock.cpp new file mode 100644 index 0000000000..67a8acffec --- /dev/null +++ b/esphome/components/copy/lock/copy_lock.cpp @@ -0,0 +1,29 @@ +#include "copy_lock.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.lock"; + +void CopyLock::setup() { + source_->add_on_state_callback([this]() { this->publish_state(source_->state); }); + + traits.set_assumed_state(source_->traits.get_assumed_state()); + traits.set_requires_code(source_->traits.get_requires_code()); + traits.set_supported_states(source_->traits.get_supported_states()); + traits.set_supports_open(source_->traits.get_supports_open()); + + this->publish_state(source_->state); +} + +void CopyLock::dump_config() { LOG_LOCK("", "Copy Lock", this); } + +void CopyLock::control(const lock::LockCall &call) { + auto call2 = source_->make_call(); + call2.set_state(call.get_state()); + call2.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/lock/copy_lock.h b/esphome/components/copy/lock/copy_lock.h new file mode 100644 index 0000000000..0554013674 --- /dev/null +++ b/esphome/components/copy/lock/copy_lock.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/lock/lock.h" + +namespace esphome { +namespace copy { + +class CopyLock : public lock::Lock, public Component { + public: + void set_source(lock::Lock *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void control(const lock::LockCall &call) override; + + lock::Lock *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/number/__init__.py b/esphome/components/copy/number/__init__.py new file mode 100644 index 0000000000..4e78627a1f --- /dev/null +++ b/esphome/components/copy/number/__init__.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import number +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_MODE, + CONF_SOURCE_ID, + CONF_UNIT_OF_MEASUREMENT, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyNumber = copy_ns.class_("CopyNumber", number.Number, cg.Component) + + +CONFIG_SCHEMA = number.NUMBER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopyNumber), + cv.Required(CONF_SOURCE_ID): cv.use_id(number.Number), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_UNIT_OF_MEASUREMENT, CONF_SOURCE_ID), + inherit_property_from(CONF_MODE, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = await number.new_number(config, min_value=0, max_value=0, step=0) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/number/copy_number.cpp b/esphome/components/copy/number/copy_number.cpp new file mode 100644 index 0000000000..46dc200b73 --- /dev/null +++ b/esphome/components/copy/number/copy_number.cpp @@ -0,0 +1,29 @@ +#include "copy_number.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.number"; + +void CopyNumber::setup() { + source_->add_on_state_callback([this](float value) { this->publish_state(value); }); + + traits.set_min_value(source_->traits.get_min_value()); + traits.set_max_value(source_->traits.get_max_value()); + traits.set_step(source_->traits.get_step()); + + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopyNumber::dump_config() { LOG_NUMBER("", "Copy Number", this); } + +void CopyNumber::control(float value) { + auto call2 = source_->make_call(); + call2.set_value(value); + call2.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/number/copy_number.h b/esphome/components/copy/number/copy_number.h new file mode 100644 index 0000000000..1ad956fec4 --- /dev/null +++ b/esphome/components/copy/number/copy_number.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/number/number.h" + +namespace esphome { +namespace copy { + +class CopyNumber : public number::Number, public Component { + public: + void set_source(number::Number *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void control(float value) override; + + number::Number *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/select/__init__.py b/esphome/components/copy/select/__init__.py new file mode 100644 index 0000000000..7d4c1c7705 --- /dev/null +++ b/esphome/components/copy/select/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import select +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopySelect = copy_ns.class_("CopySelect", select.Select, cg.Component) + + +CONFIG_SCHEMA = select.SELECT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopySelect), + cv.Required(CONF_SOURCE_ID): cv.use_id(select.Select), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await select.register_select(var, config, options=[]) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/select/copy_select.cpp b/esphome/components/copy/select/copy_select.cpp new file mode 100644 index 0000000000..0f01c2692c --- /dev/null +++ b/esphome/components/copy/select/copy_select.cpp @@ -0,0 +1,27 @@ +#include "copy_select.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.select"; + +void CopySelect::setup() { + source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); }); + + traits.set_options(source_->traits.get_options()); + + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopySelect::dump_config() { LOG_SELECT("", "Copy Select", this); } + +void CopySelect::control(const std::string &value) { + auto call = source_->make_call(); + call.set_option(value); + call.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/select/copy_select.h b/esphome/components/copy/select/copy_select.h new file mode 100644 index 0000000000..c8666cd394 --- /dev/null +++ b/esphome/components/copy/select/copy_select.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/select/select.h" + +namespace esphome { +namespace copy { + +class CopySelect : public select::Select, public Component { + public: + void set_source(select::Select *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void control(const std::string &value) override; + + select::Select *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/sensor/__init__.py b/esphome/components/copy/sensor/__init__.py new file mode 100644 index 0000000000..8e78cda7c7 --- /dev/null +++ b/esphome/components/copy/sensor/__init__.py @@ -0,0 +1,45 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_SOURCE_ID, + CONF_STATE_CLASS, + CONF_UNIT_OF_MEASUREMENT, + CONF_ACCURACY_DECIMALS, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopySensor = copy_ns.class_("CopySensor", sensor.Sensor, cg.Component) + + +CONFIG_SCHEMA = ( + sensor.sensor_schema(CopySensor) + .extend( + { + cv.Required(CONF_SOURCE_ID): cv.use_id(sensor.Sensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_UNIT_OF_MEASUREMENT, CONF_SOURCE_ID), + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ACCURACY_DECIMALS, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), + inherit_property_from(CONF_STATE_CLASS, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/sensor/copy_sensor.cpp b/esphome/components/copy/sensor/copy_sensor.cpp new file mode 100644 index 0000000000..a47a0cf22b --- /dev/null +++ b/esphome/components/copy/sensor/copy_sensor.cpp @@ -0,0 +1,18 @@ +#include "copy_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.sensor"; + +void CopySensor::setup() { + source_->add_on_state_callback([this](float value) { this->publish_state(value); }); + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopySensor::dump_config() { LOG_SENSOR("", "Copy Sensor", this); } + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/sensor/copy_sensor.h b/esphome/components/copy/sensor/copy_sensor.h new file mode 100644 index 0000000000..1ae790ada3 --- /dev/null +++ b/esphome/components/copy/sensor/copy_sensor.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace copy { + +class CopySensor : public sensor::Sensor, public Component { + public: + void set_source(sensor::Sensor *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + sensor::Sensor *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/switch/__init__.py b/esphome/components/copy/switch/__init__.py new file mode 100644 index 0000000000..6622412123 --- /dev/null +++ b/esphome/components/copy/switch/__init__.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopySwitch = copy_ns.class_("CopySwitch", switch.Switch, cg.Component) + + +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopySwitch), + cv.Required(CONF_SOURCE_ID): cv.use_id(switch.Switch), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await switch.register_switch(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/switch/copy_switch.cpp b/esphome/components/copy/switch/copy_switch.cpp new file mode 100644 index 0000000000..8a9fbb03dd --- /dev/null +++ b/esphome/components/copy/switch/copy_switch.cpp @@ -0,0 +1,26 @@ +#include "copy_switch.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.switch"; + +void CopySwitch::setup() { + source_->add_on_state_callback([this](float value) { this->publish_state(value); }); + + this->publish_state(source_->state); +} + +void CopySwitch::dump_config() { LOG_SWITCH("", "Copy Switch", this); } + +void CopySwitch::write_state(bool state) { + if (state) { + source_->turn_on(); + } else { + source_->turn_off(); + } +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/switch/copy_switch.h b/esphome/components/copy/switch/copy_switch.h new file mode 100644 index 0000000000..26cb254ab3 --- /dev/null +++ b/esphome/components/copy/switch/copy_switch.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" + +namespace esphome { +namespace copy { + +class CopySwitch : public switch_::Switch, public Component { + public: + void set_source(switch_::Switch *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void write_state(bool state) override; + + switch_::Switch *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/text_sensor/__init__.py b/esphome/components/copy/text_sensor/__init__.py new file mode 100644 index 0000000000..5b59f21319 --- /dev/null +++ b/esphome/components/copy/text_sensor/__init__.py @@ -0,0 +1,37 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyTextSensor = copy_ns.class_("CopyTextSensor", text_sensor.TextSensor, cg.Component) + + +CONFIG_SCHEMA = ( + text_sensor.text_sensor_schema(CopyTextSensor) + .extend( + { + cv.Required(CONF_SOURCE_ID): cv.use_id(text_sensor.TextSensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = await text_sensor.new_text_sensor(config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/text_sensor/copy_text_sensor.cpp b/esphome/components/copy/text_sensor/copy_text_sensor.cpp new file mode 100644 index 0000000000..95fa6d7a1b --- /dev/null +++ b/esphome/components/copy/text_sensor/copy_text_sensor.cpp @@ -0,0 +1,18 @@ +#include "copy_text_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.text_sensor"; + +void CopyTextSensor::setup() { + source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); }); + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopyTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Copy Sensor", this); } + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/text_sensor/copy_text_sensor.h b/esphome/components/copy/text_sensor/copy_text_sensor.h new file mode 100644 index 0000000000..fe91fe948b --- /dev/null +++ b/esphome/components/copy/text_sensor/copy_text_sensor.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/text_sensor/text_sensor.h" + +namespace esphome { +namespace copy { + +class CopyTextSensor : public text_sensor::TextSensor, public Component { + public: + void set_source(text_sensor::TextSensor *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + text_sensor::TextSensor *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index dec68e0701..00cc90ad6a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -621,6 +621,7 @@ CONF_SLEEP_PIN = "sleep_pin" CONF_SLEEP_WHEN_DONE = "sleep_when_done" CONF_SONY = "sony" CONF_SOURCE = "source" +CONF_SOURCE_ID = "source_id" CONF_SPEED = "speed" CONF_SPEED_COMMAND_TOPIC = "speed_command_topic" CONF_SPEED_COUNT = "speed_count" diff --git a/tests/test1.yaml b/tests/test1.yaml index 2acb420fb5..c4398498c1 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2104,6 +2104,9 @@ fan: on_speed_set: then: - logger.log: "Fan speed was changed!" + - platform: copy + source_id: fan_speed + name: "Fan Speed Copy" interval: - interval: 10s @@ -2671,6 +2674,9 @@ select: - one - two optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy qr_code: - id: homepage_qr @@ -2700,3 +2706,6 @@ lock: name: "Generic Output Lock" id: test_lock2 output: pca_6 + - platform: copy + source_id: test_lock2 + name: Generic Output Lock Copy diff --git a/tests/test4.yaml b/tests/test4.yaml index 998db8ed2d..54412222b5 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -221,6 +221,10 @@ sensor: - platform: mcp3204 name: "MCP3204 Pin 1" number: 1 + id: mcp_sensor + - platform: copy + source_id: mcp_sensor + name: "MCP binary sensor copy" # # platform sensor.apds9960 requires component apds9960 @@ -364,6 +368,9 @@ switch: name: inverter0_pv_ok_condition_for_parallel pv_power_balance: name: inverter0_pv_power_balance + - platform: copy + source_id: tuya_switch + name: Tuya Switch Copy light: - platform: fastled_clockless @@ -391,6 +398,9 @@ cover: - platform: tuya id: tuya_cover position_datapoint: 2 + - platform: copy + source_id: tuya_cover + name: "Tuya Cover copy" display: - platform: addressable_light @@ -465,6 +475,9 @@ number: min_value: 0 max_value: 17 step: 1 + - platform: copy + source_id: tuya_number + name: Tuya Number Copy text_sensor: - platform: pipsolar @@ -484,6 +497,9 @@ text_sensor: last_qflag: id: inverter0_last_qflag name: inverter0_last_qflag + - platform: copy + source_id: inverter0_device_mode + name: "Inverter Text Sensor Copy" output: - platform: pipsolar @@ -552,6 +568,11 @@ button: name: Safe Mode Button - platform: shutdown name: Shutdown Button + id: shutdown_btn + - platform: copy + source_id: shutdown_btn + name: Shutdown Button Copy + touchscreen: - platform: ektf2232