From 8be444a25e8abf4851414471cb002b8230042c2e Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 15 May 2019 10:55:35 +0200 Subject: [PATCH] Moar Custom platforms --- esphome/components/bang_bang/climate.py | 2 +- esphome/components/ccs811/ccs811.cpp | 2 +- esphome/components/ccs811/sensor.py | 7 ++- esphome/components/climate/__init__.py | 6 +- esphome/components/custom/climate/__init__.py | 26 +++++++++ .../custom/climate/custom_climate.h | 20 +++++++ esphome/components/custom/cover/__init__.py | 26 +++++++++ .../components/custom/cover/custom_cover.h | 19 +++++++ esphome/components/custom/light/__init__.py | 26 +++++++++ .../custom/light/custom_light_output.h | 22 ++++++++ esphome/components/custom/output/__init__.py | 55 +++++++------------ .../components/template/output/__init__.py | 35 ++++++++++++ .../template/output/template_output.h | 31 +++++++++++ esphome/config_validation.py | 20 ++++++- 14 files changed, 253 insertions(+), 44 deletions(-) create mode 100644 esphome/components/custom/climate/__init__.py create mode 100644 esphome/components/custom/climate/custom_climate.h create mode 100644 esphome/components/custom/cover/__init__.py create mode 100644 esphome/components/custom/cover/custom_cover.h create mode 100644 esphome/components/custom/light/__init__.py create mode 100644 esphome/components/custom/light/custom_light_output.h create mode 100644 esphome/components/template/output/__init__.py create mode 100644 esphome/components/template/output/template_output.h diff --git a/esphome/components/bang_bang/climate.py b/esphome/components/bang_bang/climate.py index 7837749ba7..88828ef44f 100644 --- a/esphome/components/bang_bang/climate.py +++ b/esphome/components/bang_bang/climate.py @@ -7,7 +7,7 @@ from esphome.const import CONF_AWAY_CONFIG, CONF_COOL_ACTION, \ CONF_ID, CONF_IDLE_ACTION, CONF_SENSOR bang_bang_ns = cg.esphome_ns.namespace('bang_bang') -BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.ClimateDevice) +BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.Climate) BangBangClimateTargetTempConfig = bang_bang_ns.struct('BangBangClimateTargetTempConfig') CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp index 4eba09ec47..538d7fe1f5 100644 --- a/esphome/components/ccs811/ccs811.cpp +++ b/esphome/components/ccs811/ccs811.cpp @@ -111,7 +111,7 @@ void CCS811Component::dump_config() { ESP_LOGCONFIG(TAG, "CCS811"); LOG_I2C_DEVICE(this) LOG_UPDATE_INTERVAL(this) - LOG_SENSOR(" ", "CO2 Sesnor", this->co2_) + LOG_SENSOR(" ", "CO2 Sensor", this->co2_) LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_) if (this->baseline_) { ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_); diff --git a/esphome/components/ccs811/sensor.py b/esphome/components/ccs811/sensor.py index ebc2b0e363..a8020c77f7 100644 --- a/esphome/components/ccs811/sensor.py +++ b/esphome/components/ccs811/sensor.py @@ -1,8 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_ID, ICON_GAS_CYLINDER, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \ - UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY +from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \ + UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_PERIODIC_TABLE_CO2 DEPENDENCIES = ['i2c'] @@ -15,7 +15,8 @@ CONF_BASELINE = 'baseline' CONFIG_SCHEMA = cv.Schema({ cv.GenerateID(): cv.declare_id(CCS811Component), - cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_GAS_CYLINDER, 0), + cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_PERIODIC_TABLE_CO2, + 0), cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0), cv.Optional(CONF_BASELINE): cv.hex_uint16_t, diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index ba0b37ed8c..8c9db58694 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -12,7 +12,7 @@ IS_PLATFORM_COMPONENT = True climate_ns = cg.esphome_ns.namespace('climate') -ClimateDevice = climate_ns.class_('Climate', cg.Nameable) +Climate = climate_ns.class_('Climate', cg.Nameable) ClimateCall = climate_ns.class_('ClimateCall') ClimateTraits = climate_ns.class_('ClimateTraits') @@ -30,7 +30,7 @@ validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True) ControlAction = climate_ns.class_('ControlAction', automation.Action) CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(ClimateDevice), + cv.GenerateID(): cv.declare_id(Climate), cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTClimateComponent), cv.Optional(CONF_VISUAL, default={}): cv.Schema({ cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature, @@ -68,7 +68,7 @@ def register_climate(var, config): CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.use_id(ClimateDevice), + cv.Required(CONF_ID): cv.use_id(Climate), cv.Optional(CONF_MODE): cv.templatable(validate_climate_mode), cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature), cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature), diff --git a/esphome/components/custom/climate/__init__.py b/esphome/components/custom/climate/__init__.py new file mode 100644 index 0000000000..ed19452dee --- /dev/null +++ b/esphome/components/custom/climate/__init__.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate +from esphome.const import CONF_ID, CONF_LAMBDA +from .. import custom_ns + +CustomClimateConstructor = custom_ns.class_('CustomClimateConstructor') +CONF_CLIMATES = 'climates' + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(CustomClimateConstructor), + cv.Required(CONF_LAMBDA): cv.returning_lambda, + cv.Required(CONF_CLIMATES): cv.ensure_list(climate.CLIMATE_SCHEMA), +}) + + +def to_code(config): + template_ = yield cg.process_lambda( + config[CONF_LAMBDA], [], + return_type=cg.std_vector.template(climate.Climate.operator('ptr'))) + + rhs = CustomClimateConstructor(template_) + custom = cg.variable(config[CONF_ID], rhs) + for i, conf in enumerate(config[CONF_CLIMATES]): + rhs = custom.Pget_climate(i) + yield climate.register_climate(rhs, conf) diff --git a/esphome/components/custom/climate/custom_climate.h b/esphome/components/custom/climate/custom_climate.h new file mode 100644 index 0000000000..250d83f69f --- /dev/null +++ b/esphome/components/custom/climate/custom_climate.h @@ -0,0 +1,20 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/climate/climate.h" + +namespace esphome { +namespace custom { + +class CustomClimateConstructor { + public: + CustomClimateConstructor(const std::function()> &init) { this->climates_ = init(); } + + climate::Climate *get_climate(int i) { return this->climates_[i]; } + + protected: + std::vector climates_; +}; + +} // namespace custom +} // namespace esphome diff --git a/esphome/components/custom/cover/__init__.py b/esphome/components/custom/cover/__init__.py new file mode 100644 index 0000000000..3ab3ec1d04 --- /dev/null +++ b/esphome/components/custom/cover/__init__.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import cover +from esphome.const import CONF_ID, CONF_LAMBDA +from .. import custom_ns + +CustomCoverConstructor = custom_ns.class_('CustomCoverConstructor') +CONF_COVERS = 'covers' + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(CustomCoverConstructor), + cv.Required(CONF_LAMBDA): cv.returning_lambda, + cv.Required(CONF_COVERS): cv.ensure_list(cover.COVER_SCHEMA), +}) + + +def to_code(config): + template_ = yield cg.process_lambda( + config[CONF_LAMBDA], [], + return_type=cg.std_vector.template(cover.Cover.operator('ptr'))) + + rhs = CustomCoverConstructor(template_) + custom = cg.variable(config[CONF_ID], rhs) + for i, conf in enumerate(config[CONF_COVERS]): + rhs = custom.Pget_cover(i) + yield cover.register_cover(rhs, conf) diff --git a/esphome/components/custom/cover/custom_cover.h b/esphome/components/custom/cover/custom_cover.h new file mode 100644 index 0000000000..71f271bc86 --- /dev/null +++ b/esphome/components/custom/cover/custom_cover.h @@ -0,0 +1,19 @@ +#pragma once + +#include "esphome/components/cover/cover.h" + +namespace esphome { +namespace custom { + +class CustomCoverConstructor { + public: + CustomCoverConstructor(const std::function()> &init) { this->covers_ = init(); } + + cover::Cover *get_cover(int i) { return this->covers_[i]; } + + protected: + std::vector covers_; +}; + +} // namespace custom +} // namespace esphome diff --git a/esphome/components/custom/light/__init__.py b/esphome/components/custom/light/__init__.py new file mode 100644 index 0000000000..24b284941e --- /dev/null +++ b/esphome/components/custom/light/__init__.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import light +from esphome.const import CONF_ID, CONF_LAMBDA +from .. import custom_ns + +CustomLightOutputConstructor = custom_ns.class_('CustomLightOutputConstructor') +CONF_LIGHTS = 'lights' + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(CustomLightOutputConstructor), + cv.Required(CONF_LAMBDA): cv.returning_lambda, + cv.Required(CONF_LIGHTS): cv.ensure_list(light.RGB_LIGHT_SCHEMA), +}) + + +def to_code(config): + template_ = yield cg.process_lambda( + config[CONF_LAMBDA], [], + return_type=cg.std_vector.template(light.LightOutput.operator('ptr'))) + + rhs = CustomLightOutputConstructor(template_) + custom = cg.variable(config[CONF_ID], rhs) + for i, conf in enumerate(config[CONF_LIGHTS]): + rhs = custom.Pget_light(i) + yield light.register_light(rhs, conf) diff --git a/esphome/components/custom/light/custom_light_output.h b/esphome/components/custom/light/custom_light_output.h new file mode 100644 index 0000000000..744e99b889 --- /dev/null +++ b/esphome/components/custom/light/custom_light_output.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/light/light_output.h" + +namespace esphome { +namespace custom { + +class CustomLightOutputConstructor { + public: + CustomLightOutputConstructor(const std::function()> &init) { + this->outputs_ = init(); + } + + light::LightOutput *get_light(int i) { return this->outputs_[i]; } + + protected: + std::vector outputs_; +}; + +} // namespace custom +} // namespace esphome diff --git a/esphome/components/custom/output/__init__.py b/esphome/components/custom/output/__init__.py index 46abee91c8..6042863872 100644 --- a/esphome/components/custom/output/__init__.py +++ b/esphome/components/custom/output/__init__.py @@ -7,42 +7,27 @@ from .. import custom_ns CustomBinaryOutputConstructor = custom_ns.class_('CustomBinaryOutputConstructor') CustomFloatOutputConstructor = custom_ns.class_('CustomFloatOutputConstructor') -BINARY_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(CustomBinaryOutputConstructor), - cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Required(CONF_TYPE): 'binary', - cv.Required(CONF_OUTPUTS): - cv.ensure_list(output.BINARY_OUTPUT_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(output.BinaryOutput), - })), -}) +CONF_BINARY = 'binary' +CONF_FLOAT = 'float' -FLOAT_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(CustomFloatOutputConstructor), - cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Required(CONF_TYPE): 'float', - cv.Required(CONF_OUTPUTS): - cv.ensure_list(output.FLOAT_OUTPUT_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(output.FloatOutput), - })), -}) - - -def validate_custom_output(value): - if not isinstance(value, dict): - raise cv.Invalid("Value must be dict") - if CONF_TYPE not in value: - raise cv.Invalid("type not specified!") - type = cv.string_strict(value[CONF_TYPE]).lower() - value[CONF_TYPE] = type - if type == 'binary': - return BINARY_SCHEMA(value) - if type == 'float': - return FLOAT_SCHEMA(value) - raise cv.Invalid("type must either be binary or float, not {}!".format(type)) - - -CONFIG_SCHEMA = validate_custom_output +CONFIG_SCHEMA = cv.typed_schema({ + CONF_BINARY: cv.Schema({ + cv.GenerateID(): cv.declare_id(CustomBinaryOutputConstructor), + cv.Required(CONF_LAMBDA): cv.returning_lambda, + cv.Required(CONF_OUTPUTS): + cv.ensure_list(output.BINARY_OUTPUT_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(output.BinaryOutput), + })), + }), + CONF_FLOAT: cv.Schema({ + cv.GenerateID(): cv.declare_id(CustomFloatOutputConstructor), + cv.Required(CONF_LAMBDA): cv.returning_lambda, + cv.Required(CONF_OUTPUTS): + cv.ensure_list(output.FLOAT_OUTPUT_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(output.FloatOutput), + })), + }) +}, lower=True) def to_code(config): diff --git a/esphome/components/template/output/__init__.py b/esphome/components/template/output/__init__.py new file mode 100644 index 0000000000..5cc9e089bd --- /dev/null +++ b/esphome/components/template/output/__init__.py @@ -0,0 +1,35 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import output +from esphome.const import CONF_ID, CONF_TYPE +from .. import template_ns + +TemplateBinaryOutput = template_ns.class_('TemplateBinaryOutput', output.BinaryOutput) +TemplateFloatOutput = template_ns.class_('TemplateFloatOutput', output.FloatOutput) + +CONF_BINARY = 'binary' +CONF_FLOAT = 'float' +CONF_WRITE_ACTION = 'write_action' + +CONFIG_SCHEMA = cv.typed_schema({ + CONF_BINARY: output.BINARY_OUTPUT_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(TemplateBinaryOutput), + cv.Required(CONF_WRITE_ACTION): automation.validate_automation(single=True), + }), + CONF_FLOAT: output.FLOAT_OUTPUT_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(TemplateFloatOutput), + cv.Required(CONF_WRITE_ACTION): automation.validate_automation(single=True), + }), +}, lower=True) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + if config[CONF_TYPE] == CONF_BINARY: + yield automation.build_automation(var.get_trigger(), [(bool, 'state')], + config[CONF_WRITE_ACTION]) + else: + yield automation.build_automation(var.get_trigger(), [(float, 'state')], + config[CONF_WRITE_ACTION]) + yield output.register_output(var, config) diff --git a/esphome/components/template/output/template_output.h b/esphome/components/template/output/template_output.h new file mode 100644 index 0000000000..90de801a5c --- /dev/null +++ b/esphome/components/template/output/template_output.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/components/output/binary_output.h" +#include "esphome/components/output/float_output.h" + +namespace esphome { +namespace template_ { + +class TemplateBinaryOutput : public output::BinaryOutput { + public: + Trigger *get_trigger() const { return trigger_; } + + protected: + void write_state(bool state) override { this->trigger_->trigger(state); } + + Trigger *trigger_ = new Trigger(); +}; + +class TemplateFloatOutput : public output::FloatOutput { + public: + Trigger *get_trigger() const { return trigger_; } + + protected: + void write_state(float state) override { this->trigger_->trigger(state); } + + Trigger *trigger_ = new Trigger(); +}; + +} // namespace template_ +} // namespace esphome diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 117391e361..ad0d90fcd3 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -16,7 +16,7 @@ from esphome import core from esphome.const import CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISCOVERY, CONF_ID, \ CONF_INTERNAL, CONF_NAME, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, \ CONF_RETAIN, CONF_SETUP_PRIORITY, CONF_STATE_TOPIC, CONF_TOPIC, \ - CONF_HOUR, CONF_MINUTE, CONF_SECOND, CONF_VALUE, CONF_UPDATE_INTERVAL, CONF_TYPE_ID + CONF_HOUR, CONF_MINUTE, CONF_SECOND, CONF_VALUE, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, CONF_TYPE from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \ TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes from esphome.helpers import list_starts_with @@ -1078,6 +1078,24 @@ def extract_keys(schema): return keys +def typed_schema(schemas, **kwargs): + """Create a schema that has a key to distinguish between schemas""" + key = kwargs.pop('key', CONF_TYPE) + key_validator = one_of(*schemas, **kwargs) + + def validator(value): + if not isinstance(value, dict): + raise Invalid("Value must be dict") + if CONF_TYPE not in value: + raise Invalid("type not specified!") + value = value.copy() + key_v = key_validator(value.pop(key)) + value = schemas[key_v](value) + value[key] = key_v + + return validator + + class GenerateID(Optional): """Mark this key as being an auto-generated ID key.""" def __init__(self, key=CONF_ID):