[web_server] add entity sorting for v3 (#6445)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
RFDarter 2024-05-30 03:54:20 +02:00 committed by GitHub
parent 854d3f2e4a
commit a7fc1a6298
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 802 additions and 503 deletions

View file

@ -1,5 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import web_server
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.core import CORE, coroutine_with_priority
@ -8,6 +9,7 @@ from esphome.const import (
CONF_ON_STATE,
CONF_TRIGGER_ID,
CONF_CODE,
CONF_WEB_SERVER_ID,
)
from esphome.cpp_helpers import setup_entity
@ -76,6 +78,8 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_(
)
ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
web_server.WEBSERVER_SORTING_SCHEMA
).extend(
{
cv.GenerateID(): cv.declare_id(AlarmControlPanel),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
@ -185,6 +189,9 @@ async def setup_alarm_control_panel_core_(var, config):
for conf in config.get(CONF_ON_READY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_alarm_control_panel(var, config):

View file

@ -4,7 +4,7 @@ from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
from esphome import automation, core
from esphome.automation import Condition, maybe_simple_id
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_DELAY,
CONF_DEVICE_CLASS,
@ -27,6 +27,7 @@ from esphome.const import (
CONF_TIMING,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_BATTERY_CHARGING,
DEVICE_CLASS_CARBON_MONOXIDE,
@ -385,70 +386,76 @@ def validate_click_timing(value):
return value
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(BinarySensor),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt.MQTTBinarySensorComponent
),
cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean,
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
}
),
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
}
),
cv.Optional(CONF_ON_CLICK): cv.All(
automation.validate_automation(
BINARY_SENSOR_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(BinarySensor),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt.MQTTBinarySensorComponent
),
cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean,
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
}
),
validate_click_timing,
),
cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
automation.validate_automation(
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
}
),
validate_click_timing,
),
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
cv.Required(CONF_TIMING): cv.All(
[parse_multi_click_timing_str], validate_multi_click_timing
cv.Optional(CONF_ON_CLICK): cv.All(
automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
}
),
cv.Optional(
CONF_INVALID_COOLDOWN, default="1s"
): cv.positive_time_period_milliseconds,
}
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
}
validate_click_timing,
),
cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
DoubleClickTrigger
),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
}
),
validate_click_timing,
),
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
cv.Required(CONF_TIMING): cv.All(
[parse_multi_click_timing_str], validate_multi_click_timing
),
cv.Optional(
CONF_INVALID_COOLDOWN, default="1s"
): cv.positive_time_period_milliseconds,
}
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
}
)
)
_UNDEF = object()
@ -536,6 +543,10 @@ async def setup_binary_sensor_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_binary_sensor(var, config):
if not CORE.has_id(config[CONF_ID]):

View file

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
@ -11,6 +11,7 @@ from esphome.const import (
CONF_ON_PRESS,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_IDENTIFY,
DEVICE_CLASS_RESTART,
@ -43,16 +44,20 @@ ButtonPressTrigger = button_ns.class_(
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
}
),
}
BUTTON_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
}
),
}
)
)
_UNDEF = object()
@ -92,6 +97,10 @@ async def setup_button_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_button(var, config):
if not CORE.has_id(config[CONF_ID]):

View file

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.cpp_helpers import setup_entity
from esphome import automation
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_ACTION_STATE_TOPIC,
CONF_AWAY,
@ -44,6 +44,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_VISUAL,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
)
from esphome.core import CORE, coroutine_with_priority
@ -150,93 +151,97 @@ VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any(
),
)
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
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,
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA,
cv.Optional(CONF_MIN_HUMIDITY): cv.percentage_int,
cv.Optional(CONF_MAX_HUMIDITY): cv.percentage_int,
}
),
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_CURRENT_HUMIDITY_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_HUMIDITY_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_ON_CONTROL): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger),
}
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
}
CLIMATE_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
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,
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA,
cv.Optional(CONF_MIN_HUMIDITY): cv.percentage_int,
cv.Optional(CONF_MAX_HUMIDITY): cv.percentage_int,
}
),
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_CURRENT_HUMIDITY_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_HUMIDITY_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_ON_CONTROL): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger),
}
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
}
)
)
@ -403,6 +408,10 @@ async def setup_climate_core_(var, config):
trigger, [(ClimateCall.operator("ref"), "x")], conf
)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_climate(var, config):
if not CORE.has_id(config[CONF_ID]):

View file

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id, Condition
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_ID,
CONF_DEVICE_CLASS,
@ -16,6 +16,7 @@ from esphome.const import (
CONF_TILT_STATE_TOPIC,
CONF_STOP,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_TRIGGER_ID,
DEVICE_CLASS_AWNING,
DEVICE_CLASS_BLIND,
@ -88,34 +89,38 @@ CoverClosedTrigger = cover_ns.class_(
CONF_ON_CLOSED = "on_closed"
COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(Cover),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_TILT_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger),
}
),
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger),
}
),
}
COVER_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(Cover),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_TILT_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger),
}
),
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger),
}
),
}
)
)
@ -132,6 +137,10 @@ async def setup_cover_core_(var, config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)

View file

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt, time
from esphome.components import mqtt, web_server, time
from esphome.const import (
CONF_ID,
CONF_ON_TIME,
@ -11,6 +11,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_TYPE,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_DATE,
CONF_DATETIME,
CONF_TIME,
@ -63,16 +64,20 @@ DATETIME_MODES = [
]
_DATETIME_SCHEMA = cv.Schema(
{
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
}
),
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
}
).extend(cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA))
_DATETIME_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
}
),
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
}
)
)
def date_schema(class_: MockObjClass) -> cv.Schema:
@ -128,6 +133,9 @@ async def setup_datetime_core_(var, config):
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
for conf in config.get(CONF_ON_VALUE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)

View file

@ -2,10 +2,11 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_OSCILLATING,
CONF_OSCILLATION_COMMAND_TOPIC,
CONF_OSCILLATION_STATE_TOPIC,
@ -79,67 +80,75 @@ FanPresetSetTrigger = fan_ns.class_(
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template())
FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(Fan),
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
RESTORE_MODES, upper=True, space="_"
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent),
cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_SPEED_LEVEL_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SPEED_LEVEL_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanStateTrigger),
}
),
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOnTrigger),
}
),
cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger),
}
),
cv.Optional(CONF_ON_DIRECTION_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanDirectionSetTrigger),
}
),
cv.Optional(CONF_ON_OSCILLATING_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanOscillatingSetTrigger),
}
),
cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger),
}
),
cv.Optional(CONF_ON_PRESET_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanPresetSetTrigger),
}
),
}
FAN_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(Fan),
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
RESTORE_MODES, upper=True, space="_"
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent),
cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_SPEED_LEVEL_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SPEED_LEVEL_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanStateTrigger),
}
),
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOnTrigger),
}
),
cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger),
}
),
cv.Optional(CONF_ON_DIRECTION_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FanDirectionSetTrigger
),
}
),
cv.Optional(CONF_ON_OSCILLATING_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FanOscillatingSetTrigger
),
}
),
cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger),
}
),
cv.Optional(CONF_ON_PRESET_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanPresetSetTrigger),
}
),
}
)
)
_PRESET_MODES_SCHEMA = cv.All(
@ -209,6 +218,10 @@ async def setup_fan_core_(var, config):
if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None:
cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic))
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(Fan.operator("ptr"), "x")], conf)

View file

@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.automation as auto
from esphome.components import mqtt, power_supply
from esphome.components import mqtt, power_supply, web_server
from esphome.const import (
CONF_COLOR_CORRECT,
CONF_DEFAULT_TRANSITION_LENGTH,
@ -10,6 +10,7 @@ from esphome.const import (
CONF_GAMMA_CORRECT,
CONF_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_POWER_SUPPLY,
CONF_RESTORE_MODE,
CONF_ON_TURN_OFF,
@ -56,29 +57,35 @@ RESTORE_MODES = {
"RESTORE_AND_ON": LightRestoreMode.LIGHT_RESTORE_AND_ON,
}
LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(LightState),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTJSONLightComponent),
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
RESTORE_MODES, upper=True, space="_"
),
cv.Optional(CONF_ON_TURN_ON): auto.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOnTrigger),
}
),
cv.Optional(CONF_ON_TURN_OFF): auto.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOffTrigger),
}
),
cv.Optional(CONF_ON_STATE): auto.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightStateTrigger),
}
),
}
LIGHT_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(LightState),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt.MQTTJSONLightComponent
),
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
RESTORE_MODES, upper=True, space="_"
),
cv.Optional(CONF_ON_TURN_ON): auto.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOnTrigger),
}
),
cv.Optional(CONF_ON_TURN_OFF): auto.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOffTrigger),
}
),
cv.Optional(CONF_ON_STATE): auto.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightStateTrigger),
}
),
}
)
)
BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend(
@ -173,6 +180,10 @@ async def setup_light_core_(light_var, output_var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, light_var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, light_var, config)
async def register_light(output_var, config):
light_var = cg.new_Pvariable(config[CONF_ID], output_var)

View file

@ -2,13 +2,14 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import Condition, maybe_simple_id
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_ID,
CONF_ON_LOCK,
CONF_ON_UNLOCK,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
@ -30,20 +31,24 @@ LockCondition = lock_ns.class_("LockCondition", Condition)
LockLockTrigger = lock_ns.class_("LockLockTrigger", automation.Trigger.template())
LockUnlockTrigger = lock_ns.class_("LockUnlockTrigger", automation.Trigger.template())
LOCK_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTLockComponent),
cv.Optional(CONF_ON_LOCK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockLockTrigger),
}
),
cv.Optional(CONF_ON_UNLOCK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockUnlockTrigger),
}
),
}
LOCK_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTLockComponent),
cv.Optional(CONF_ON_LOCK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockLockTrigger),
}
),
cv.Optional(CONF_ON_UNLOCK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockUnlockTrigger),
}
),
}
)
)
@ -61,6 +66,10 @@ async def setup_lock_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_lock(var, config):
if not CORE.has_id(config[CONF_ID]):

View file

@ -2,6 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt
from esphome.components import web_server
from esphome.const import (
CONF_ABOVE,
CONF_BELOW,
@ -18,6 +19,7 @@ from esphome.const import (
CONF_VALUE,
CONF_OPERATION,
CONF_CYCLE,
CONF_WEB_SERVER_ID,
DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_AQI,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
@ -167,26 +169,30 @@ NUMBER_OPERATION_OPTIONS = {
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
validate_unit_of_measurement = cv.string_strict
NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent),
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(NumberStateTrigger),
}
),
cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger),
cv.Optional(CONF_ABOVE): cv.templatable(cv.float_),
cv.Optional(CONF_BELOW): cv.templatable(cv.float_),
},
cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW),
),
cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement,
cv.Optional(CONF_MODE, default="AUTO"): cv.enum(NUMBER_MODES, upper=True),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
}
NUMBER_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent),
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(NumberStateTrigger),
}
),
cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger),
cv.Optional(CONF_ABOVE): cv.templatable(cv.float_),
cv.Optional(CONF_BELOW): cv.templatable(cv.float_),
},
cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW),
),
cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement,
cv.Optional(CONF_MODE, default="AUTO"): cv.enum(NUMBER_MODES, upper=True),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
}
)
)
_UNDEF = object()
@ -248,6 +254,10 @@ async def setup_number_core_(
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_number(
var, config, *, min_value: float, max_value: float, step: float

View file

@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_ENTITY_CATEGORY,
CONF_ICON,
@ -10,6 +10,7 @@ from esphome.const import (
CONF_OPTION,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_CYCLE,
CONF_MODE,
CONF_OPERATION,
@ -47,16 +48,20 @@ SELECT_OPERATION_OPTIONS = {
}
SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent),
cv.GenerateID(): cv.declare_id(Select),
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SelectStateTrigger),
}
),
}
SELECT_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent),
cv.GenerateID(): cv.declare_id(Select),
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SelectStateTrigger),
}
),
}
)
)
_UNDEF = object()
@ -99,6 +104,10 @@ async def setup_select_core_(var, config, *, options: list[str]):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_select(var, config, *, options: list[str]):
if not CORE.has_id(config[CONF_ID]):

View file

@ -3,7 +3,7 @@ import math
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ABOVE,
@ -31,6 +31,7 @@ from esphome.const import (
CONF_UNIT_OF_MEASUREMENT,
CONF_WINDOW_SIZE,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_FORCE_UPDATE,
CONF_VALUE,
CONF_MIN_VALUE,
@ -252,43 +253,49 @@ validate_accuracy_decimals = cv.int_
validate_icon = cv.icon
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent),
cv.GenerateID(): cv.declare_id(Sensor),
cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement,
cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals,
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_STATE_CLASS): validate_state_class,
cv.Optional(CONF_ENTITY_CATEGORY): sensor_entity_category,
cv.Optional("last_reset_type"): cv.invalid(
"last_reset_type has been removed since 2021.9.0. state_class: total_increasing should be used for total values."
),
cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean,
cv.Optional(CONF_EXPIRE_AFTER): cv.All(
cv.requires_component("mqtt"),
cv.Any(None, cv.positive_time_period_milliseconds),
),
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SensorStateTrigger),
}
),
cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SensorRawStateTrigger),
}
),
cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger),
cv.Optional(CONF_ABOVE): cv.templatable(cv.float_),
cv.Optional(CONF_BELOW): cv.templatable(cv.float_),
},
cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW),
),
}
SENSOR_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent),
cv.GenerateID(): cv.declare_id(Sensor),
cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement,
cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals,
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_STATE_CLASS): validate_state_class,
cv.Optional(CONF_ENTITY_CATEGORY): sensor_entity_category,
cv.Optional("last_reset_type"): cv.invalid(
"last_reset_type has been removed since 2021.9.0. state_class: total_increasing should be used for total values."
),
cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean,
cv.Optional(CONF_EXPIRE_AFTER): cv.All(
cv.requires_component("mqtt"),
cv.Any(None, cv.positive_time_period_milliseconds),
),
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SensorStateTrigger),
}
),
cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
SensorRawStateTrigger
),
}
),
cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger),
cv.Optional(CONF_ABOVE): cv.templatable(cv.float_),
cv.Optional(CONF_BELOW): cv.templatable(cv.float_),
},
cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW),
),
}
)
)
_UNDEF = object()
@ -772,6 +779,10 @@ async def setup_sensor_core_(var, config):
else:
cg.add(mqtt_.set_expire_after(expire_after))
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_sensor(var, config):
if not CORE.has_id(config[CONF_ID]):

View file

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import Condition, maybe_simple_id
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
@ -10,6 +10,7 @@ from esphome.const import (
CONF_ID,
CONF_INVERTED,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_ON_TURN_OFF,
CONF_ON_TURN_ON,
CONF_RESTORE_MODE,
@ -64,22 +65,26 @@ SwitchTurnOffTrigger = switch_ns.class_(
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True)
_SWITCH_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent),
cv.Optional(CONF_INVERTED): cv.boolean,
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOnTrigger),
}
),
cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOffTrigger),
}
),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
}
_SWITCH_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent),
cv.Optional(CONF_INVERTED): cv.boolean,
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOnTrigger),
}
),
cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOffTrigger),
}
),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
}
)
)
_UNDEF = object()
@ -151,6 +156,10 @@ async def setup_switch_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(device_class))

View file

@ -2,13 +2,14 @@ from typing import Optional
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_ID,
CONF_MODE,
CONF_ON_VALUE,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_VALUE,
)
@ -38,17 +39,21 @@ TEXT_MODES = {
"PASSWORD": TextMode.TEXT_MODE_PASSWORD, # to be implemented for keys, passwords, etc.
}
TEXT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextComponent),
cv.GenerateID(): cv.declare_id(Text),
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextStateTrigger),
}
),
cv.Required(CONF_MODE): cv.enum(TEXT_MODES, upper=True),
}
TEXT_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextComponent),
cv.GenerateID(): cv.declare_id(Text),
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextStateTrigger),
}
),
cv.Required(CONF_MODE): cv.enum(TEXT_MODES, upper=True),
}
)
)
@ -77,6 +82,10 @@ async def setup_text_core_(
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_text(
var,

View file

@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
@ -12,6 +12,7 @@ from esphome.const import (
CONF_ON_RAW_VALUE,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_STATE,
CONF_FROM,
CONF_TO,
@ -124,25 +125,31 @@ async def map_filter_to_code(config, filter_id):
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor),
cv.GenerateID(): cv.declare_id(TextSensor),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextSensorStateTrigger),
}
),
cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
TextSensorStateRawTrigger
),
}
),
}
TEXT_SENSOR_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor),
cv.GenerateID(): cv.declare_id(TextSensor),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
TextSensorStateTrigger
),
}
),
cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
TextSensorStateRawTrigger
),
}
),
}
)
)
_UNDEF = object()
@ -205,6 +212,10 @@ async def setup_text_sensor_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_text_sensor(var, config):
if not CORE.has_id(config[CONF_ID]):

View file

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id, Condition
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ID,
@ -14,6 +14,7 @@ from esphome.const import (
CONF_STATE,
CONF_STOP,
CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_GAS,
DEVICE_CLASS_WATER,
@ -70,28 +71,32 @@ ValveClosedTrigger = valve_ns.class_(
CONF_ON_CLOSED = "on_closed"
VALVE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(Valve),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTValveComponent),
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveOpenTrigger),
}
),
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveClosedTrigger),
}
),
}
VALVE_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(Valve),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTValveComponent),
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveOpenTrigger),
}
),
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveClosedTrigger),
}
),
}
)
)
@ -119,6 +124,10 @@ async def setup_valve_core_(var, config):
mqtt_.set_custom_position_command_topic(position_command_topic_config)
)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_valve(var, config):
if not CORE.has_id(config[CONF_ID]):

View file

@ -1,6 +1,9 @@
from __future__ import annotations
import gzip
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome.components import web_server_base
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
from esphome.const import (
@ -19,6 +22,8 @@ from esphome.const import (
CONF_LOG,
CONF_VERSION,
CONF_LOCAL,
CONF_WEB_SERVER_ID,
CONF_WEB_SERVER_SORTING_WEIGHT,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_BK72XX,
@ -64,6 +69,46 @@ def validate_ota(config):
return config
def _validate_no_sorting_weight(
webserver_version: int, config: dict, path: list[str] | None = None
) -> None:
if path is None:
path = []
if CONF_WEB_SERVER_SORTING_WEIGHT in config:
raise cv.FinalExternalInvalid(
f"Sorting weight on entities is not supported in web_server version {webserver_version}",
path=path + [CONF_WEB_SERVER_SORTING_WEIGHT],
)
for p, value in config.items():
if isinstance(value, dict):
_validate_no_sorting_weight(webserver_version, value, path + [p])
elif isinstance(value, list):
for i, item in enumerate(value):
if isinstance(item, dict):
_validate_no_sorting_weight(webserver_version, item, path + [p, i])
def _final_validate_sorting_weight(config):
if (webserver_version := config.get(CONF_VERSION)) != 3:
_validate_no_sorting_weight(webserver_version, fv.full_config.get())
return config
FINAL_VALIDATE_SCHEMA = _final_validate_sorting_weight
WEBSERVER_SORTING_SCHEMA = cv.Schema(
{
cv.OnlyWith(CONF_WEB_SERVER_ID, "web_server"): cv.use_id(WebServer),
cv.Optional(CONF_WEB_SERVER_SORTING_WEIGHT): cv.All(
cv.requires_component("web_server"),
cv.float_,
),
}
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@ -108,6 +153,19 @@ CONFIG_SCHEMA = cv.All(
)
def add_entity_to_sorting_list(web_server, entity, config):
sorting_weight = 50
if CONF_WEB_SERVER_SORTING_WEIGHT in config:
sorting_weight = config[CONF_WEB_SERVER_SORTING_WEIGHT]
cg.add(
web_server.add_entity_to_sorting_list(
entity,
sorting_weight,
)
)
def build_index_html(config) -> str:
html = "<!DOCTYPE html><html><head><meta charset=UTF-8><link rel=icon href=data:>"
css_include = config.get(CONF_CSS_INCLUDE)

View file

@ -435,7 +435,7 @@ void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlM
request->send(404);
}
std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
std::string state;
if (std::isnan(value)) {
state = "NA";
@ -446,6 +446,9 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail
}
set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config);
if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
if (!obj->get_unit_of_measurement().empty())
root["uom"] = obj->get_unit_of_measurement();
}
@ -471,8 +474,13 @@ void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const
}
std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value,
JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "text_sensor-" + obj->get_object_id(), value, value, start_config);
if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
#endif
@ -483,14 +491,6 @@ void WebServer::on_switch_update(switch_::Switch *obj, bool state) {
return;
this->events_.send(this->switch_json(obj, state, DETAIL_STATE).c_str(), "state");
}
std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
if (start_config == DETAIL_ALL) {
root["assumed_state"] = obj->assumed_state();
}
});
}
void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (switch_::Switch *obj : App.get_switches()) {
if (obj->get_object_id() != match.id)
@ -515,14 +515,20 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
}
request->send(404);
}
std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
if (start_config == DETAIL_ALL) {
root["assumed_state"] = obj->assumed_state();
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
#endif
#ifdef USE_BUTTON
std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) {
return json::build_json(
[obj, start_config](JsonObject root) { set_json_id(root, obj, "button-" + obj->get_object_id(), start_config); });
}
void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (button::Button *obj : App.get_buttons()) {
if (obj->get_object_id() != match.id)
@ -538,6 +544,16 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM
}
request->send(404);
}
std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "button-" + obj->get_object_id(), start_config);
if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
#endif
#ifdef USE_BINARY_SENSOR
@ -546,12 +562,6 @@ void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool s
return;
this->events_.send(this->binary_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
}
std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value,
start_config);
});
}
void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) {
if (obj->get_object_id() != match.id)
@ -562,6 +572,17 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con
}
request->send(404);
}
std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value,
start_config);
if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
#endif
#ifdef USE_FAN
@ -570,19 +591,6 @@ void WebServer::on_fan_update(fan::Fan *obj) {
return;
this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state");
}
std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state,
start_config);
const auto traits = obj->get_traits();
if (traits.supports_speed()) {
root["speed_level"] = obj->speed;
root["speed_count"] = traits.supported_speed_count();
}
if (obj->get_traits().supports_oscillation())
root["oscillation"] = obj->oscillating;
});
}
void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (fan::Fan *obj : App.get_fans()) {
if (obj->get_object_id() != match.id)
@ -635,6 +643,24 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
}
request->send(404);
}
std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state,
start_config);
const auto traits = obj->get_traits();
if (traits.supports_speed()) {
root["speed_level"] = obj->speed;
root["speed_count"] = traits.supported_speed_count();
}
if (obj->get_traits().supports_oscillation())
root["oscillation"] = obj->oscillating;
if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
#endif
#ifdef USE_LIGHT
@ -729,7 +755,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
request->send(404);
}
std::string WebServer::light_json(light::LightState *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "light-" + obj->get_object_id(), start_config);
root["state"] = obj->remote_values.is_on() ? "ON" : "OFF";
@ -740,6 +766,9 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi
for (auto const &option : obj->get_effects()) {
opt.add(option->get_name());
}
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
@ -803,7 +832,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
request->send(404);
}
std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN",
obj->position, start_config);
root["current_operation"] = cover::cover_operation_to_str(obj->current_operation);
@ -812,6 +841,11 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
root["position"] = obj->position;
if (obj->get_traits().get_supports_tilt())
root["tilt"] = obj->tilt;
if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
#endif
@ -852,7 +886,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
}
std::string WebServer::number_json(number::Number *obj, float value, JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
set_json_id(root, obj, "number-" + obj->get_object_id(), start_config);
if (start_config == DETAIL_ALL) {
root["min_value"] =
@ -864,6 +898,9 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
root["mode"] = (int) obj->traits.get_mode();
if (!obj->traits.get_unit_of_measurement().empty())
root["uom"] = obj->traits.get_unit_of_measurement();
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
if (std::isnan(value)) {
root["value"] = "\"NaN\"";
@ -919,11 +956,16 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat
}
std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "date-" + obj->get_object_id(), start_config);
std::string value = str_sprintf("%d-%02d-%02d", obj->year, obj->month, obj->day);
root["value"] = value;
root["state"] = value;
if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
#endif // USE_DATETIME_DATE
@ -967,11 +1009,16 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat
request->send(404);
}
std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "time-" + obj->get_object_id(), start_config);
std::string value = str_sprintf("%02d:%02d:%02d", obj->hour, obj->minute, obj->second);
root["value"] = value;
root["state"] = value;
if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
#endif // USE_DATETIME_TIME
@ -1015,12 +1062,17 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur
request->send(404);
}
std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "datetime-" + obj->get_object_id(), start_config);
std::string value = str_sprintf("%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour,
obj->minute, obj->second);
root["value"] = value;
root["state"] = value;
if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
#endif // USE_DATETIME_DATETIME
@ -1060,11 +1112,8 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat
}
std::string WebServer::text_json(text::Text *obj, const std::string &value, JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
set_json_id(root, obj, "text-" + obj->get_object_id(), start_config);
if (start_config == DETAIL_ALL) {
root["mode"] = (int) obj->traits.get_mode();
}
root["min_length"] = obj->traits.get_min_length();
root["max_length"] = obj->traits.get_max_length();
root["pattern"] = obj->traits.get_pattern();
@ -1074,6 +1123,12 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json
root["state"] = value;
}
root["value"] = value;
if (start_config == DETAIL_ALL) {
root["mode"] = (int) obj->traits.get_mode();
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
#endif
@ -1119,13 +1174,16 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
request->send(404);
}
std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config);
if (start_config == DETAIL_ALL) {
JsonArray opt = root.createNestedArray("option");
for (auto &option : obj->traits.get_options()) {
opt.add(option);
}
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
@ -1140,7 +1198,6 @@ void WebServer::on_climate_update(climate::Climate *obj) {
return;
this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state");
}
void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_climates()) {
if (obj->get_object_id() != match.id)
@ -1188,9 +1245,8 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url
}
request->send(404);
}
std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config);
const auto traits = obj->get_traits();
int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
@ -1227,6 +1283,9 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf
for (auto const &custom_preset : traits.get_supported_custom_presets())
opt.add(custom_preset);
}
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
bool has_state = false;
@ -1283,12 +1342,6 @@ void WebServer::on_lock_update(lock::Lock *obj) {
return;
this->events_.send(this->lock_json(obj, obj->state, DETAIL_STATE).c_str(), "state");
}
std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value,
start_config);
});
}
void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (lock::Lock *obj : App.get_locks()) {
if (obj->get_object_id() != match.id)
@ -1313,6 +1366,17 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
}
request->send(404);
}
std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value,
start_config);
if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
#endif
#ifdef USE_VALVE
@ -1366,13 +1430,16 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa
request->send(404);
}
std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "valve-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN",
obj->position, start_config);
root["current_operation"] = valve::valve_operation_to_str(obj->current_operation);
if (obj->get_traits().get_supports_position())
root["position"] = obj->position;
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
});
}
#endif
@ -1383,15 +1450,6 @@ void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP
return;
this->events_.send(this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE).c_str(), "state");
}
std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj,
alarm_control_panel::AlarmControlPanelState value,
JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
char buf[16];
set_json_icon_state_value(root, obj, "alarm-control-panel-" + obj->get_object_id(),
PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config);
});
}
void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (alarm_control_panel::AlarmControlPanel *obj : App.get_alarm_control_panels()) {
if (obj->get_object_id() != match.id)
@ -1405,6 +1463,20 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques
}
request->send(404);
}
std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj,
alarm_control_panel::AlarmControlPanelState value,
JsonDetail start_config) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
char buf[16];
set_json_icon_state_value(root, obj, "alarm-control-panel-" + obj->get_object_id(),
PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config);
if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
#endif
#ifdef USE_EVENT
@ -1709,6 +1781,10 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
bool WebServer::isRequestHandlerTrivial() { return false; }
void WebServer::add_entity_to_sorting_list(EntityBase *entity, float weight) {
this->sorting_entitys_[entity] = SortingComponents{weight};
}
void WebServer::schedule_(std::function<void()> &&f) {
#ifdef USE_ESP32
xSemaphoreTake(this->to_schedule_lock_, portMAX_DELAY);

View file

@ -5,8 +5,10 @@
#include "esphome/components/web_server_base/web_server_base.h"
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/entity_base.h"
#include <vector>
#include <map>
#ifdef USE_ESP32
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
@ -39,6 +41,10 @@ struct UrlMatch {
bool valid; ///< Whether this match is valid
};
struct SortingComponents {
float weight;
};
enum JsonDetail { DETAIL_ALL, DETAIL_STATE };
/** This class allows users to create a web server with their ESP nodes.
@ -320,12 +326,15 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// This web handle is not trivial.
bool isRequestHandlerTrivial() override;
void add_entity_to_sorting_list(EntityBase *entity, float weight);
protected:
void schedule_(std::function<void()> &&f);
friend ListEntitiesIterator;
web_server_base::WebServerBase *base_;
AsyncEventSource events_{"/events"};
ListEntitiesIterator entities_iterator_;
std::map<EntityBase *, SortingComponents> sorting_entitys_;
#if USE_WEBSERVER_VERSION == 1
const char *css_url_{nullptr};
const char *js_url_{nullptr};

View file

@ -904,6 +904,8 @@ CONF_WARM_WHITE = "warm_white"
CONF_WARM_WHITE_COLOR_TEMPERATURE = "warm_white_color_temperature"
CONF_WATCHDOG_THRESHOLD = "watchdog_threshold"
CONF_WEB_SERVER = "web_server"
CONF_WEB_SERVER_ID = "web_server_id"
CONF_WEB_SERVER_SORTING_WEIGHT = "web_server_sorting_weight"
CONF_WEIGHT = "weight"
CONF_WHILE = "while"
CONF_WHITE = "white"