diff --git a/esphome/components/demo/__init__.py b/esphome/components/demo/__init__.py index 96ffb58b82..33bdc3dee2 100644 --- a/esphome/components/demo/__init__.py +++ b/esphome/components/demo/__init__.py @@ -1,14 +1,22 @@ import esphome.codegen as cg from esphome.components import ( + alarm_control_panel, binary_sensor, + button, climate, cover, + datetime, + event, fan, light, + lock, number, + select, sensor, switch, + text, text_sensor, + valve, ) import esphome.config_validation as cv from esphome.const import ( @@ -20,7 +28,9 @@ from esphome.const import ( CONF_INVERTED, CONF_MAX_VALUE, CONF_MIN_VALUE, + CONF_MODE, CONF_NAME, + CONF_OPTIONS, CONF_OUTPUT_ID, CONF_SENSORS, CONF_STATE_CLASS, @@ -31,9 +41,11 @@ from esphome.const import ( CONF_UNIT_OF_MEASUREMENT, DEVICE_CLASS_ENERGY, DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_IDENTIFY, DEVICE_CLASS_MOISTURE, DEVICE_CLASS_MOTION, DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_UPDATE, ICON_BLUETOOTH, ICON_BLUR, ICON_THERMOMETER, @@ -45,38 +57,68 @@ from esphome.const import ( ) AUTO_LOAD = [ + "alarm_control_panel", "binary_sensor", + "button", "climate", "cover", + "datetime", + "event", "fan", "light", + "lock", "number", + "select", "sensor", "switch", + "text", "text_sensor", + "valve", ] demo_ns = cg.esphome_ns.namespace("demo") +DemoAlarmControlPanel = demo_ns.class_( + "DemoAlarmControlPanel", alarm_control_panel.AlarmControlPanel, cg.Component +) +DemoAlarmControlPanelType = demo_ns.enum("DemoAlarmControlPanelType", is_class=True) DemoBinarySensor = demo_ns.class_( "DemoBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent ) +DemoButton = demo_ns.class_("DemoButton", button.Button) DemoClimate = demo_ns.class_("DemoClimate", climate.Climate, cg.Component) DemoClimateType = demo_ns.enum("DemoClimateType", is_class=True) DemoCover = demo_ns.class_("DemoCover", cover.Cover, cg.Component) DemoCoverType = demo_ns.enum("DemoCoverType", is_class=True) +DemoDate = demo_ns.class_("DemoDate", datetime.DateEntity, cg.Component) +DemoDateTime = demo_ns.class_("DemoDateTime", datetime.DateTimeEntity, cg.Component) +DemoTime = demo_ns.class_("DemoTime", datetime.TimeEntity, cg.Component) +DemoEvent = demo_ns.class_("DemoEvent", event.Event, cg.Component) DemoFan = demo_ns.class_("DemoFan", fan.Fan, cg.Component) DemoFanType = demo_ns.enum("DemoFanType", is_class=True) DemoLight = demo_ns.class_("DemoLight", light.LightOutput, cg.Component) DemoLightType = demo_ns.enum("DemoLightType", is_class=True) +DemoLock = demo_ns.class_("DemoLock", lock.Lock, cg.Component) +DemoLockType = demo_ns.enum("DemoLockType", is_class=True) DemoNumber = demo_ns.class_("DemoNumber", number.Number, cg.Component) DemoNumberType = demo_ns.enum("DemoNumberType", is_class=True) +DemoSelect = demo_ns.class_("DemoSelect", select.Select, cg.Component) +DemoSelectType = demo_ns.enum("DemoSelectType", is_class=True) DemoSensor = demo_ns.class_("DemoSensor", sensor.Sensor, cg.PollingComponent) DemoSwitch = demo_ns.class_("DemoSwitch", switch.Switch, cg.Component) +DemoText = demo_ns.class_("DemoText", text.Text, cg.Component) +DemoTextType = demo_ns.enum("DemoTextType", is_class=True) DemoTextSensor = demo_ns.class_( "DemoTextSensor", text_sensor.TextSensor, cg.PollingComponent ) +DemoValve = demo_ns.class_("DemoValve", valve.Valve) +DemoValveType = demo_ns.enum("DemoValveType", is_class=True) +ALARM_CONTROL_PANEL_TYPES = { + 1: DemoAlarmControlPanelType.TYPE_1, + 2: DemoAlarmControlPanelType.TYPE_2, + 3: DemoAlarmControlPanelType.TYPE_3, +} CLIMATE_TYPES = { 1: DemoClimateType.TYPE_1, 2: DemoClimateType.TYPE_2, @@ -103,21 +145,71 @@ LIGHT_TYPES = { 6: DemoLightType.TYPE_6, 7: DemoLightType.TYPE_7, } +LOCK_TYPES = { + 1: DemoLockType.TYPE_1, + 2: DemoLockType.TYPE_2, +} NUMBER_TYPES = { 1: DemoNumberType.TYPE_1, 2: DemoNumberType.TYPE_2, 3: DemoNumberType.TYPE_3, } +SELECT_TYPES = { + 1: DemoSelectType.TYPE_1, + 2: DemoSelectType.TYPE_2, +} +TEXT_TYPES = { + 1: DemoTextType.TYPE_1, + 2: DemoTextType.TYPE_2, +} +VALVE_TYPES = { + 1: DemoValveType.TYPE_1, + 2: DemoValveType.TYPE_2, +} +CONF_ALARM_CONTROL_PANELS = "alarm_control_panels" +CONF_BUTTONS = "buttons" CONF_CLIMATES = "climates" CONF_COVERS = "covers" +CONF_DATETIMES = "datetimes" CONF_FANS = "fans" CONF_LIGHTS = "lights" +CONF_LOCKS = "locks" CONF_NUMBERS = "numbers" +CONF_SELECTS = "selects" +CONF_TEXTS = "texts" +CONF_VALVES = "valves" CONFIG_SCHEMA = cv.Schema( { + cv.Optional( + CONF_ALARM_CONTROL_PANELS, + default=[ + { + CONF_NAME: "Demo Alarm Control Panel", + CONF_TYPE: 1, + }, + { + CONF_NAME: "Demo Alarm Control Panel Code", + CONF_TYPE: 2, + }, + { + CONF_NAME: "Demo Alarm Control Panel Code to Arm", + CONF_TYPE: 3, + }, + ], + ): [ + alarm_control_panel.alarm_control_panel_schema( + DemoAlarmControlPanel + ).extend( + { + cv.Required(CONF_TYPE): cv.enum( + ALARM_CONTROL_PANEL_TYPES, int=True + ), + } + ) + ], cv.Optional( CONF_BINARY_SENSORS, default=[ @@ -135,6 +227,21 @@ CONFIG_SCHEMA = cv.Schema( cv.polling_component_schema("60s") ) ], + cv.Optional( + CONF_BUTTONS, + default=[ + { + CONF_NAME: "Demo Update Button", + CONF_DEVICE_CLASS: DEVICE_CLASS_UPDATE, + }, + { + CONF_NAME: "Demo Button Identify", + CONF_DEVICE_CLASS: DEVICE_CLASS_IDENTIFY, + }, + ], + ): [ + button.button_schema(DemoButton), + ], cv.Optional( CONF_CLIMATES, default=[ @@ -191,6 +298,20 @@ CONFIG_SCHEMA = cv.Schema( } ) ], + cv.Optional( + CONF_DATETIMES, + default=[ + {CONF_NAME: "Demo DateTime", CONF_TYPE: "DATETIME"}, + {CONF_NAME: "Demo Date", CONF_TYPE: "DATE"}, + {CONF_NAME: "Demo Time", CONF_TYPE: "TIME"}, + ], + ): [ + cv.Any( + datetime.date_schema(DemoDate), + datetime.datetime_schema(DemoDateTime), + datetime.time_schema(DemoTime), + ) + ], cv.Optional( CONF_FANS, default=[ @@ -262,6 +383,19 @@ CONFIG_SCHEMA = cv.Schema( } ) ], + cv.Optional( + CONF_LOCKS, + default=[ + {CONF_NAME: "Demo Lock", CONF_TYPE: 1}, + {CONF_NAME: "Demo Lock and Open", CONF_TYPE: 2}, + ], + ): [ + lock.lock_schema(DemoLock).extend( + { + cv.Required(CONF_TYPE): cv.enum(LOCK_TYPES, int=True), + } + ) + ], cv.Optional( CONF_NUMBERS, default=[ @@ -299,6 +433,28 @@ CONFIG_SCHEMA = cv.Schema( } ) ], + cv.Optional( + CONF_SELECTS, + default=[ + { + CONF_NAME: "Demo Select 1", + CONF_OPTIONS: ["Option 1", "Option 2", "Option 3"], + CONF_TYPE: 1, + }, + { + CONF_NAME: "Demo Select 2", + CONF_OPTIONS: ["Option A", "Option B", "Option C"], + CONF_TYPE: 2, + }, + ], + ): [ + select.select_schema(DemoSelect).extend( + { + cv.Required(CONF_TYPE): cv.enum(SELECT_TYPES, int=True), + cv.Required(CONF_OPTIONS): cv.ensure_list(cv.string_strict), + } + ) + ], cv.Optional( CONF_SENSORS, default=[ @@ -355,6 +511,19 @@ CONFIG_SCHEMA = cv.Schema( }, ], ): [switch.switch_schema(DemoSwitch).extend(cv.COMPONENT_SCHEMA)], + cv.Optional( + CONF_TEXTS, + default=[ + {CONF_NAME: "Demo Text 1", CONF_MODE: "TEXT", CONF_TYPE: 1}, + {CONF_NAME: "Demo Text 2", CONF_MODE: "PASSWORD", CONF_TYPE: 2}, + ], + ): [ + text.text_schema(DemoText).extend( + { + cv.Required(CONF_TYPE): cv.enum(TEXT_TYPES, int=True), + } + ) + ], cv.Optional( CONF_TEXT_SENSORS, default=[ @@ -371,15 +540,35 @@ CONFIG_SCHEMA = cv.Schema( cv.polling_component_schema("60s") ) ], + cv.Optional( + CONF_VALVES, + default=[ + {CONF_NAME: "Demo Valve 1", CONF_TYPE: 1}, + {CONF_NAME: "Demo Valve 2", CONF_TYPE: 2}, + ], + ): [ + valve.valve_schema(DemoValve).extend( + { + cv.Required(CONF_TYPE): cv.enum(VALVE_TYPES, int=True), + } + ) + ], } ) async def to_code(config): + for conf in config[CONF_ALARM_CONTROL_PANELS]: + var = await alarm_control_panel.new_alarm_control_panel(conf) + await cg.register_component(var, conf) + for conf in config[CONF_BINARY_SENSORS]: var = await binary_sensor.new_binary_sensor(conf) await cg.register_component(var, conf) + for conf in config[CONF_BUTTONS]: + await button.new_button(conf) + for conf in config[CONF_CLIMATES]: var = await climate.new_climate(conf) await cg.register_component(var, conf) @@ -390,6 +579,10 @@ async def to_code(config): await cg.register_component(var, conf) cg.add(var.set_type(conf[CONF_TYPE])) + for conf in config[CONF_DATETIMES]: + var = await datetime.new_datetime(conf) + await cg.register_component(var, conf) + for conf in config[CONF_FANS]: var = await fan.new_fan(conf) await cg.register_component(var, conf) @@ -400,6 +593,11 @@ async def to_code(config): await cg.register_component(var, conf) cg.add(var.set_type(conf[CONF_TYPE])) + for conf in config[CONF_LOCKS]: + var = await lock.new_lock(conf) + if conf[CONF_TYPE] == 2: + cg.add(var.traits.set_supports_open(True)) + for conf in config[CONF_NUMBERS]: var = await number.new_number( conf, @@ -410,6 +608,10 @@ async def to_code(config): await cg.register_component(var, conf) cg.add(var.set_type(conf[CONF_TYPE])) + for conf in config[CONF_SELECTS]: + var = await select.new_select(conf, options=conf[CONF_OPTIONS]) + await cg.register_component(var, conf) + for conf in config[CONF_SENSORS]: var = await sensor.new_sensor(conf) await cg.register_component(var, conf) @@ -418,6 +620,16 @@ async def to_code(config): var = await switch.new_switch(conf) await cg.register_component(var, conf) + for conf in config[CONF_TEXTS]: + var = await text.new_text(conf) + await cg.register_component(var, conf) + if conf[CONF_TYPE] == 2: + cg.add(var.traits.set_mode(text.TextMode.TEXT_MODE_PASSWORD)) + for conf in config[CONF_TEXT_SENSORS]: var = await text_sensor.new_text_sensor(conf) await cg.register_component(var, conf) + + for conf in config[CONF_VALVES]: + var = await valve.new_valve(conf) + cg.add(var.set_type(conf[CONF_TYPE])) diff --git a/esphome/components/demo/demo_alarm_control_panel.h b/esphome/components/demo/demo_alarm_control_panel.h new file mode 100644 index 0000000000..9902d27882 --- /dev/null +++ b/esphome/components/demo/demo_alarm_control_panel.h @@ -0,0 +1,65 @@ +#pragma once + +#include "esphome/components/alarm_control_panel/alarm_control_panel.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace demo { + +using namespace alarm_control_panel; + +enum class DemoAlarmControlPanelType { + TYPE_1, + TYPE_2, + TYPE_3, +}; + +class DemoAlarmControlPanel : public AlarmControlPanel, public Component { + public: + void setup() override {} + + uint32_t get_supported_features() const override { return ACP_FEAT_ARM_AWAY | ACP_FEAT_TRIGGER; } + + bool get_requires_code() const override { return this->type_ != DemoAlarmControlPanelType::TYPE_1; } + + bool get_requires_code_to_arm() const override { return this->type_ == DemoAlarmControlPanelType::TYPE_3; } + + void set_type(DemoAlarmControlPanelType type) { this->type_ = type; } + + protected: + void control(const AlarmControlPanelCall &call) override { + auto state = call.get_state().value_or(ACP_STATE_DISARMED); + switch (state) { + case ACP_STATE_ARMED_AWAY: + if (this->get_requires_code_to_arm() && call.get_code().has_value()) { + if (call.get_code().value() != "1234") { + this->status_momentary_error("Invalid code", 5000); + return; + } + } + this->publish_state(ACP_STATE_ARMED_AWAY); + break; + case ACP_STATE_DISARMED: + if (this->get_requires_code() && call.get_code().has_value()) { + if (call.get_code().value() != "1234") { + this->status_momentary_error("Invalid code", 5000); + return; + } + } + this->publish_state(ACP_STATE_DISARMED); + return; + case ACP_STATE_TRIGGERED: + this->publish_state(ACP_STATE_TRIGGERED); + return; + case ACP_STATE_PENDING: + this->publish_state(ACP_STATE_PENDING); + return; + default: + break; + } + } + DemoAlarmControlPanelType type_{}; +}; + +} // namespace demo +} // namespace esphome diff --git a/esphome/components/demo/demo_button.h b/esphome/components/demo/demo_button.h new file mode 100644 index 0000000000..be80d26a8a --- /dev/null +++ b/esphome/components/demo/demo_button.h @@ -0,0 +1,15 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace demo { + +class DemoButton : public button::Button { + protected: + void press_action() override {} +}; + +} // namespace demo +} // namespace esphome diff --git a/esphome/components/demo/demo_date.h b/esphome/components/demo/demo_date.h new file mode 100644 index 0000000000..e09ab5f887 --- /dev/null +++ b/esphome/components/demo/demo_date.h @@ -0,0 +1,34 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_DATETIME_DATE + +#include "esphome/components/datetime/date_entity.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace demo { + +class DemoDate : public datetime::DateEntity, public Component { + public: + void setup() override { + this->year_ = 2038; + this->month_ = 01; + this->day_ = 19; + this->publish_state(); + } + + protected: + void control(const datetime::DateCall &call) override { + this->year_ = call.get_year().value_or(this->year_); + this->month_ = call.get_month().value_or(this->month_); + this->day_ = call.get_day().value_or(this->day_); + this->publish_state(); + } +}; + +} // namespace demo +} // namespace esphome + +#endif diff --git a/esphome/components/demo/demo_datetime.h b/esphome/components/demo/demo_datetime.h new file mode 100644 index 0000000000..5ebcc3e64e --- /dev/null +++ b/esphome/components/demo/demo_datetime.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_DATETIME_DATETIME + +#include "esphome/components/datetime/datetime_entity.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace demo { + +class DemoDateTime : public datetime::DateTimeEntity, public Component { + public: + void setup() override { + this->year_ = 2038; + this->month_ = 01; + this->day_ = 19; + this->hour_ = 3; + this->minute_ = 14; + this->second_ = 8; + this->publish_state(); + } + + protected: + void control(const datetime::DateTimeCall &call) override { + this->year_ = call.get_year().value_or(this->year_); + this->month_ = call.get_month().value_or(this->month_); + this->day_ = call.get_day().value_or(this->day_); + this->hour_ = call.get_hour().value_or(this->hour_); + this->minute_ = call.get_minute().value_or(this->minute_); + this->second_ = call.get_second().value_or(this->second_); + this->publish_state(); + } +}; + +} // namespace demo +} // namespace esphome + +#endif diff --git a/esphome/components/demo/demo_lock.h b/esphome/components/demo/demo_lock.h new file mode 100644 index 0000000000..94d0f70a14 --- /dev/null +++ b/esphome/components/demo/demo_lock.h @@ -0,0 +1,17 @@ +#pragma once + +#include "esphome/components/lock/lock.h" + +namespace esphome { +namespace demo { + +class DemoLock : public lock::Lock { + protected: + void control(const lock::LockCall &call) override { + auto state = *call.get_state(); + this->publish_state(state); + } +}; + +} // namespace demo +} // namespace esphome diff --git a/esphome/components/demo/demo_select.h b/esphome/components/demo/demo_select.h new file mode 100644 index 0000000000..1951a684a2 --- /dev/null +++ b/esphome/components/demo/demo_select.h @@ -0,0 +1,15 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace demo { + +class DemoSelect : public select::Select, public Component { + protected: + void control(const std::string &value) override { this->publish_state(value); } +}; + +} // namespace demo +} // namespace esphome diff --git a/esphome/components/demo/demo_text.h b/esphome/components/demo/demo_text.h new file mode 100644 index 0000000000..a753175062 --- /dev/null +++ b/esphome/components/demo/demo_text.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/text/text.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace demo { + +class DemoText : public text::Text, public Component { + public: + void setup() override { this->publish_state("I am a text entity"); } + + protected: + void control(const std::string &value) override { this->publish_state(value); } +}; + +} // namespace demo +} // namespace esphome diff --git a/esphome/components/demo/demo_time.h b/esphome/components/demo/demo_time.h new file mode 100644 index 0000000000..42788504bb --- /dev/null +++ b/esphome/components/demo/demo_time.h @@ -0,0 +1,34 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_DATETIME_TIME + +#include "esphome/components/datetime/time_entity.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace demo { + +class DemoTime : public datetime::TimeEntity, public Component { + public: + void setup() override { + this->hour_ = 3; + this->minute_ = 14; + this->second_ = 8; + this->publish_state(); + } + + protected: + void control(const datetime::TimeCall &call) override { + this->hour_ = call.get_hour().value_or(this->hour_); + this->minute_ = call.get_minute().value_or(this->minute_); + this->second_ = call.get_second().value_or(this->second_); + this->publish_state(); + } +}; + +} // namespace demo +} // namespace esphome + +#endif diff --git a/esphome/components/demo/demo_valve.h b/esphome/components/demo/demo_valve.h new file mode 100644 index 0000000000..4cdf9dea67 --- /dev/null +++ b/esphome/components/demo/demo_valve.h @@ -0,0 +1,50 @@ +#pragma once + +#include "esphome/components/valve/valve.h" + +namespace esphome { +namespace demo { + +enum class DemoValveType { + TYPE_1, + TYPE_2, +}; + +class DemoValve : public valve::Valve { + public: + valve::ValveTraits get_traits() override { + valve::ValveTraits traits; + if (this->type_ == DemoValveType::TYPE_2) { + traits.set_supports_position(true); + traits.set_supports_toggle(true); + traits.set_supports_stop(true); + } + return traits; + } + + void set_type(DemoValveType type) { this->type_ = type; } + + protected: + void control(const valve::ValveCall &call) override { + if (call.get_position().has_value()) { + this->publish_state(*call.get_position()); + return; + } else if (call.get_toggle().has_value()) { + if (call.get_toggle().value()) { + if (this->position == valve::VALVE_OPEN) { + this->publish_state(valve::VALVE_CLOSED); + } else { + this->publish_state(valve::VALVE_OPEN); + } + } + return; + } else if (call.get_stop()) { + this->publish_state(this->position); // Keep the current position + return; + } + } + DemoValveType type_{}; +}; + +} // namespace demo +} // namespace esphome