From de943908bd248c8c803fffb339e3a6e16d16b303 Mon Sep 17 00:00:00 2001
From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
Date: Wed, 16 Oct 2024 14:23:43 +1100
Subject: [PATCH] [automation] Implement all and any condition shortcuts
 (#7565)

---
 esphome/automation.py | 31 +++++++++++++++++++++++++++++--
 esphome/const.py      |  2 ++
 2 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/esphome/automation.py b/esphome/automation.py
index 0bd6cf0af0..34159561c2 100644
--- a/esphome/automation.py
+++ b/esphome/automation.py
@@ -1,6 +1,8 @@
 import esphome.codegen as cg
 import esphome.config_validation as cv
 from esphome.const import (
+    CONF_ALL,
+    CONF_ANY,
     CONF_AUTOMATION_ID,
     CONF_CONDITION,
     CONF_COUNT,
@@ -73,6 +75,13 @@ def validate_potentially_and_condition(value):
     return validate_condition(value)
 
 
+def validate_potentially_or_condition(value):
+    if isinstance(value, list):
+        with cv.remove_prepend_path(["or"]):
+            return validate_condition({"or": value})
+    return validate_condition(value)
+
+
 DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
 LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
 IfAction = cg.esphome_ns.class_("IfAction", Action)
@@ -166,6 +175,18 @@ async def or_condition_to_code(config, condition_id, template_arg, args):
     return cg.new_Pvariable(condition_id, template_arg, conditions)
 
 
+@register_condition("all", AndCondition, validate_condition_list)
+async def all_condition_to_code(config, condition_id, template_arg, args):
+    conditions = await build_condition_list(config, template_arg, args)
+    return cg.new_Pvariable(condition_id, template_arg, conditions)
+
+
+@register_condition("any", OrCondition, validate_condition_list)
+async def any_condition_to_code(config, condition_id, template_arg, args):
+    conditions = await build_condition_list(config, template_arg, args)
+    return cg.new_Pvariable(condition_id, template_arg, conditions)
+
+
 @register_condition("not", NotCondition, validate_potentially_and_condition)
 async def not_condition_to_code(config, condition_id, template_arg, args):
     condition = await build_condition(config, template_arg, args)
@@ -223,15 +244,21 @@ async def delay_action_to_code(config, action_id, template_arg, args):
     IfAction,
     cv.All(
         {
-            cv.Required(CONF_CONDITION): validate_potentially_and_condition,
+            cv.Exclusive(
+                CONF_CONDITION, CONF_CONDITION
+            ): validate_potentially_and_condition,
+            cv.Exclusive(CONF_ANY, CONF_CONDITION): validate_potentially_or_condition,
+            cv.Exclusive(CONF_ALL, CONF_CONDITION): validate_potentially_and_condition,
             cv.Optional(CONF_THEN): validate_action_list,
             cv.Optional(CONF_ELSE): validate_action_list,
         },
         cv.has_at_least_one_key(CONF_THEN, CONF_ELSE),
+        cv.has_at_least_one_key(CONF_CONDITION, CONF_ANY, CONF_ALL),
     ),
 )
 async def if_action_to_code(config, action_id, template_arg, args):
-    conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
+    cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION))
+    conditions = await build_condition(config[cond_conf], template_arg, args)
     var = cg.new_Pvariable(action_id, template_arg, conditions)
     if CONF_THEN in config:
         actions = await build_action_list(config[CONF_THEN], template_arg, args)
diff --git a/esphome/const.py b/esphome/const.py
index e02a1506cb..7665c77a32 100644
--- a/esphome/const.py
+++ b/esphome/const.py
@@ -49,6 +49,7 @@ CONF_ADDRESS = "address"
 CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id"
 CONF_ADVANCED = "advanced"
 CONF_AFTER = "after"
+CONF_ALL = "all"
 CONF_ALLOW_OTHER_USES = "allow_other_uses"
 CONF_ALPHA = "alpha"
 CONF_ALTITUDE = "altitude"
@@ -57,6 +58,7 @@ CONF_AMMONIA = "ammonia"
 CONF_ANALOG = "analog"
 CONF_AND = "and"
 CONF_ANGLE = "angle"
+CONF_ANY = "any"
 CONF_AP = "ap"
 CONF_APPARENT_POWER = "apparent_power"
 CONF_ARDUINO_VERSION = "arduino_version"