Add more demo platforms (#8903)

This commit is contained in:
Jesse Hills 2025-05-29 08:23:45 +12:00 committed by GitHub
parent 3fb9577ad9
commit c4cb694d77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 500 additions and 0 deletions

View File

@ -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]))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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