mirror of
https://github.com/esphome/esphome.git
synced 2025-10-02 01:52:21 +01:00
Sprinkler "v2" updates (#4159)
* Add standby switch * Add support for arbitrary run duration in start_single_valve action * Add divider feature * Allow zero multiplier * Fixes for #3740, misc. cleanup and polishing * Integrate number components for multiplier, repeat and run duration * Add various methods to get time remaining * Add next_prev_ignore_disabled flag * Optimize next/previous valve selection methods * Add numbers_use_minutes flag * Initialize switch states as they are set up * Ensure SprinklerControllerSwitch has state if it's not restored * Add repeat validation * Misc. clean-up and tweaking * Fix bugprone-integer-division * More clean-up * Set entity_category for standby_switch * Set default entity_category for numbers * More housekeeping * Add run request tracking * Fix time remaining calculation * Use native unit_of_measurement for run duration numbers * Unstack some ifs
This commit is contained in:
@@ -2,23 +2,36 @@ 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 number
|
||||
from esphome.components import switch
|
||||
from esphome.const import (
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_ID,
|
||||
CONF_INITIAL_VALUE,
|
||||
CONF_MAX_VALUE,
|
||||
CONF_MIN_VALUE,
|
||||
CONF_NAME,
|
||||
CONF_REPEAT,
|
||||
CONF_RESTORE_VALUE,
|
||||
CONF_RUN_DURATION,
|
||||
CONF_STEP,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
UNIT_MINUTE,
|
||||
UNIT_SECOND,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["switch"]
|
||||
AUTO_LOAD = ["number", "switch"]
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
|
||||
CONF_AUTO_ADVANCE_SWITCH = "auto_advance_switch"
|
||||
CONF_DIVIDER = "divider"
|
||||
CONF_ENABLE_SWITCH = "enable_switch"
|
||||
CONF_MAIN_SWITCH = "main_switch"
|
||||
CONF_MANUAL_SELECTION_DELAY = "manual_selection_delay"
|
||||
CONF_MULTIPLIER = "multiplier"
|
||||
CONF_MULTIPLIER_NUMBER = "multiplier_number"
|
||||
CONF_NEXT_PREV_IGNORE_DISABLED = "next_prev_ignore_disabled"
|
||||
CONF_PUMP_OFF_SWITCH_ID = "pump_off_switch_id"
|
||||
CONF_PUMP_ON_SWITCH_ID = "pump_on_switch_id"
|
||||
CONF_PUMP_PULSE_DURATION = "pump_pulse_duration"
|
||||
@@ -30,7 +43,11 @@ CONF_PUMP_SWITCH = "pump_switch"
|
||||
CONF_PUMP_SWITCH_ID = "pump_switch_id"
|
||||
CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY = "pump_switch_off_during_valve_open_delay"
|
||||
CONF_QUEUE_ENABLE_SWITCH = "queue_enable_switch"
|
||||
CONF_REPEAT_NUMBER = "repeat_number"
|
||||
CONF_REVERSE_SWITCH = "reverse_switch"
|
||||
CONF_RUN_DURATION_NUMBER = "run_duration_number"
|
||||
CONF_SET_ACTION = "set_action"
|
||||
CONF_STANDBY_SWITCH = "standby_switch"
|
||||
CONF_VALVE_NUMBER = "valve_number"
|
||||
CONF_VALVE_OPEN_DELAY = "valve_open_delay"
|
||||
CONF_VALVE_OVERLAP = "valve_overlap"
|
||||
@@ -43,10 +60,14 @@ CONF_VALVES = "valves"
|
||||
|
||||
sprinkler_ns = cg.esphome_ns.namespace("sprinkler")
|
||||
Sprinkler = sprinkler_ns.class_("Sprinkler", cg.Component)
|
||||
SprinklerControllerNumber = sprinkler_ns.class_(
|
||||
"SprinklerControllerNumber", number.Number, cg.Component
|
||||
)
|
||||
SprinklerControllerSwitch = sprinkler_ns.class_(
|
||||
"SprinklerControllerSwitch", switch.Switch, cg.Component
|
||||
)
|
||||
|
||||
SetDividerAction = sprinkler_ns.class_("SetDividerAction", automation.Action)
|
||||
SetMultiplierAction = sprinkler_ns.class_("SetMultiplierAction", automation.Action)
|
||||
QueueValveAction = sprinkler_ns.class_("QueueValveAction", automation.Action)
|
||||
ClearQueuedValvesAction = sprinkler_ns.class_(
|
||||
@@ -67,6 +88,19 @@ ResumeAction = sprinkler_ns.class_("ResumeAction", automation.Action)
|
||||
ResumeOrStartAction = sprinkler_ns.class_("ResumeOrStartAction", automation.Action)
|
||||
|
||||
|
||||
def validate_min_max(config):
|
||||
if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]:
|
||||
raise cv.Invalid(f"{CONF_MAX_VALUE} must be greater than {CONF_MIN_VALUE}")
|
||||
|
||||
if (config[CONF_INITIAL_VALUE] > config[CONF_MAX_VALUE]) or (
|
||||
config[CONF_INITIAL_VALUE] < config[CONF_MIN_VALUE]
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_INITIAL_VALUE} must be a value between {CONF_MAX_VALUE} and {CONF_MIN_VALUE}"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def validate_sprinkler(config):
|
||||
for sprinkler_controller_index, sprinkler_controller in enumerate(config):
|
||||
if len(sprinkler_controller[CONF_VALVES]) <= 1:
|
||||
@@ -104,9 +138,18 @@ def validate_sprinkler(config):
|
||||
f"{CONF_VALVE_OPEN_DELAY} must be defined when {CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY} is enabled"
|
||||
)
|
||||
|
||||
if (
|
||||
CONF_REPEAT in sprinkler_controller
|
||||
and CONF_REPEAT_NUMBER in sprinkler_controller
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"Do not specify {CONF_REPEAT} when using {CONF_REPEAT_NUMBER}; use number component's {CONF_INITIAL_VALUE} instead"
|
||||
)
|
||||
|
||||
for valve in sprinkler_controller[CONF_VALVES]:
|
||||
if (
|
||||
CONF_VALVE_OVERLAP in sprinkler_controller
|
||||
and CONF_RUN_DURATION in valve
|
||||
and valve[CONF_RUN_DURATION] <= sprinkler_controller[CONF_VALVE_OVERLAP]
|
||||
):
|
||||
raise cv.Invalid(
|
||||
@@ -114,6 +157,7 @@ def validate_sprinkler(config):
|
||||
)
|
||||
if (
|
||||
CONF_VALVE_OPEN_DELAY in sprinkler_controller
|
||||
and CONF_RUN_DURATION in valve
|
||||
and valve[CONF_RUN_DURATION]
|
||||
<= sprinkler_controller[CONF_VALVE_OPEN_DELAY]
|
||||
):
|
||||
@@ -170,6 +214,14 @@ def validate_sprinkler(config):
|
||||
raise cv.Invalid(
|
||||
f"Either {CONF_VALVE_SWITCH_ID} or {CONF_VALVE_OFF_SWITCH_ID} and {CONF_VALVE_ON_SWITCH_ID} must be specified in valve configuration"
|
||||
)
|
||||
if CONF_RUN_DURATION not in valve and CONF_RUN_DURATION_NUMBER not in valve:
|
||||
raise cv.Invalid(
|
||||
f"Either {CONF_RUN_DURATION} or {CONF_RUN_DURATION_NUMBER} must be specified for each valve"
|
||||
)
|
||||
if CONF_RUN_DURATION in valve and CONF_RUN_DURATION_NUMBER in valve:
|
||||
raise cv.Invalid(
|
||||
f"Do not specify {CONF_RUN_DURATION} when using {CONF_RUN_DURATION_NUMBER}; use number component's {CONF_INITIAL_VALUE} instead"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
@@ -190,11 +242,20 @@ SPRINKLER_ACTION_REPEAT_SCHEMA = cv.maybe_simple_value(
|
||||
SPRINKLER_ACTION_SINGLE_VALVE_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(Sprinkler),
|
||||
cv.Optional(CONF_RUN_DURATION): cv.templatable(cv.positive_time_period_seconds),
|
||||
cv.Required(CONF_VALVE_NUMBER): cv.templatable(cv.positive_int),
|
||||
},
|
||||
key=CONF_VALVE_NUMBER,
|
||||
)
|
||||
|
||||
SPRINKLER_ACTION_SET_DIVIDER_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(Sprinkler),
|
||||
cv.Required(CONF_DIVIDER): cv.templatable(cv.positive_int),
|
||||
},
|
||||
key=CONF_DIVIDER,
|
||||
)
|
||||
|
||||
SPRINKLER_ACTION_SET_MULTIPLIER_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(Sprinkler),
|
||||
@@ -232,7 +293,30 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_PUMP_OFF_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
cv.Optional(CONF_PUMP_ON_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
cv.Optional(CONF_PUMP_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
cv.Required(CONF_RUN_DURATION): cv.positive_time_period_seconds,
|
||||
cv.Optional(CONF_RUN_DURATION): cv.positive_time_period_seconds,
|
||||
cv.Optional(CONF_RUN_DURATION_NUMBER): cv.maybe_simple_value(
|
||||
number.NUMBER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SprinklerControllerNumber),
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG
|
||||
): cv.entity_category,
|
||||
cv.Optional(CONF_INITIAL_VALUE, default=900): cv.positive_int,
|
||||
cv.Optional(CONF_MAX_VALUE, default=86400): cv.positive_int,
|
||||
cv.Optional(CONF_MIN_VALUE, default=1): cv.positive_int,
|
||||
cv.Optional(CONF_RESTORE_VALUE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_STEP, default=1): cv.positive_int,
|
||||
cv.Optional(CONF_SET_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_UNIT_OF_MEASUREMENT, default=UNIT_SECOND
|
||||
): cv.one_of(UNIT_MINUTE, UNIT_SECOND, lower="True"),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate_min_max,
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Required(CONF_VALVE_SWITCH): cv.maybe_simple_value(
|
||||
switch.switch_schema(SprinklerControllerSwitch),
|
||||
key=CONF_NAME,
|
||||
@@ -268,8 +352,55 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema(
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_STANDBY_SWITCH): cv.maybe_simple_value(
|
||||
switch.switch_schema(
|
||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_NEXT_PREV_IGNORE_DISABLED, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MANUAL_SELECTION_DELAY): cv.positive_time_period_seconds,
|
||||
cv.Optional(CONF_MULTIPLIER_NUMBER): cv.maybe_simple_value(
|
||||
number.NUMBER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SprinklerControllerNumber),
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG
|
||||
): cv.entity_category,
|
||||
cv.Optional(CONF_INITIAL_VALUE, default=1): cv.positive_float,
|
||||
cv.Optional(CONF_MAX_VALUE, default=10): cv.positive_float,
|
||||
cv.Optional(CONF_MIN_VALUE, default=0): cv.positive_float,
|
||||
cv.Optional(CONF_RESTORE_VALUE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_STEP, default=0.1): cv.positive_float,
|
||||
cv.Optional(CONF_SET_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate_min_max,
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_REPEAT): cv.positive_int,
|
||||
cv.Optional(CONF_REPEAT_NUMBER): cv.maybe_simple_value(
|
||||
number.NUMBER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SprinklerControllerNumber),
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG
|
||||
): cv.entity_category,
|
||||
cv.Optional(CONF_INITIAL_VALUE, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_MAX_VALUE, default=10): cv.positive_int,
|
||||
cv.Optional(CONF_MIN_VALUE, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_RESTORE_VALUE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_STEP, default=1): cv.positive_int,
|
||||
cv.Optional(CONF_SET_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate_min_max,
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_PUMP_PULSE_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_VALVE_PULSE_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Exclusive(
|
||||
@@ -301,6 +432,19 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"sprinkler.set_divider",
|
||||
SetDividerAction,
|
||||
SPRINKLER_ACTION_SET_DIVIDER_SCHEMA,
|
||||
)
|
||||
async def sprinkler_set_divider_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_DIVIDER], args, cg.float_)
|
||||
cg.add(var.set_divider(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"sprinkler.set_multiplier",
|
||||
SetMultiplierAction,
|
||||
@@ -385,6 +529,9 @@ async def sprinkler_start_single_valve_to_code(config, action_id, template_arg,
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_VALVE_NUMBER], args, cg.uint8)
|
||||
cg.add(var.set_valve_to_start(template_))
|
||||
if CONF_RUN_DURATION in config:
|
||||
template_ = await cg.templatable(config[CONF_RUN_DURATION], args, cg.uint32)
|
||||
cg.add(var.set_valve_run_duration(template_))
|
||||
return var
|
||||
|
||||
|
||||
@@ -455,6 +602,79 @@ async def to_code(config):
|
||||
)
|
||||
cg.add(var.set_controller_reverse_switch(sw_rev_var))
|
||||
|
||||
if CONF_STANDBY_SWITCH in sprinkler_controller:
|
||||
sw_stb_var = await switch.new_switch(
|
||||
sprinkler_controller[CONF_STANDBY_SWITCH]
|
||||
)
|
||||
await cg.register_component(
|
||||
sw_stb_var, sprinkler_controller[CONF_STANDBY_SWITCH]
|
||||
)
|
||||
cg.add(var.set_controller_standby_switch(sw_stb_var))
|
||||
|
||||
if CONF_MULTIPLIER_NUMBER in sprinkler_controller:
|
||||
num_mult_var = await number.new_number(
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER],
|
||||
min_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][
|
||||
CONF_MIN_VALUE
|
||||
],
|
||||
max_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][
|
||||
CONF_MAX_VALUE
|
||||
],
|
||||
step=sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_STEP],
|
||||
)
|
||||
await cg.register_component(
|
||||
num_mult_var, sprinkler_controller[CONF_MULTIPLIER_NUMBER]
|
||||
)
|
||||
cg.add(
|
||||
num_mult_var.set_initial_value(
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_INITIAL_VALUE]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
num_mult_var.set_restore_value(
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_RESTORE_VALUE]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_SET_ACTION in sprinkler_controller[CONF_MULTIPLIER_NUMBER]:
|
||||
await automation.build_automation(
|
||||
num_mult_var.get_set_trigger(),
|
||||
[(float, "x")],
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_SET_ACTION],
|
||||
)
|
||||
|
||||
cg.add(var.set_controller_multiplier_number(num_mult_var))
|
||||
|
||||
if CONF_REPEAT_NUMBER in sprinkler_controller:
|
||||
num_repeat_var = await number.new_number(
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER],
|
||||
min_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MIN_VALUE],
|
||||
max_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MAX_VALUE],
|
||||
step=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_STEP],
|
||||
)
|
||||
await cg.register_component(
|
||||
num_repeat_var, sprinkler_controller[CONF_REPEAT_NUMBER]
|
||||
)
|
||||
cg.add(
|
||||
num_repeat_var.set_initial_value(
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER][CONF_INITIAL_VALUE]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
num_repeat_var.set_restore_value(
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER][CONF_RESTORE_VALUE]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_SET_ACTION in sprinkler_controller[CONF_REPEAT_NUMBER]:
|
||||
await automation.build_automation(
|
||||
num_repeat_var.get_set_trigger(),
|
||||
[(float, "x")],
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER][CONF_SET_ACTION],
|
||||
)
|
||||
|
||||
cg.add(var.set_controller_repeat_number(num_repeat_var))
|
||||
|
||||
for valve in sprinkler_controller[CONF_VALVES]:
|
||||
sw_valve_var = await switch.new_switch(valve[CONF_VALVE_SWITCH])
|
||||
await cg.register_component(sw_valve_var, valve[CONF_VALVE_SWITCH])
|
||||
@@ -470,6 +690,12 @@ async def to_code(config):
|
||||
else:
|
||||
cg.add(var.add_valve(sw_valve_var))
|
||||
|
||||
cg.add(
|
||||
var.set_next_prev_ignore_disabled_valves(
|
||||
sprinkler_controller[CONF_NEXT_PREV_IGNORE_DISABLED]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_MANUAL_SELECTION_DELAY in sprinkler_controller:
|
||||
cg.add(
|
||||
var.set_manual_selection_delay(
|
||||
@@ -524,6 +750,11 @@ async def to_code(config):
|
||||
for sprinkler_controller in config:
|
||||
var = await cg.get_variable(sprinkler_controller[CONF_ID])
|
||||
for valve_index, valve in enumerate(sprinkler_controller[CONF_VALVES]):
|
||||
if CONF_RUN_DURATION not in valve:
|
||||
valve[CONF_RUN_DURATION] = valve[CONF_RUN_DURATION_NUMBER][
|
||||
CONF_INITIAL_VALUE
|
||||
]
|
||||
|
||||
if CONF_VALVE_SWITCH_ID in valve:
|
||||
valve_switch = await cg.get_variable(valve[CONF_VALVE_SWITCH_ID])
|
||||
cg.add(
|
||||
@@ -561,6 +792,35 @@ async def to_code(config):
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_RUN_DURATION_NUMBER in valve:
|
||||
num_rd_var = await number.new_number(
|
||||
valve[CONF_RUN_DURATION_NUMBER],
|
||||
min_value=valve[CONF_RUN_DURATION_NUMBER][CONF_MIN_VALUE],
|
||||
max_value=valve[CONF_RUN_DURATION_NUMBER][CONF_MAX_VALUE],
|
||||
step=valve[CONF_RUN_DURATION_NUMBER][CONF_STEP],
|
||||
)
|
||||
await cg.register_component(num_rd_var, valve[CONF_RUN_DURATION_NUMBER])
|
||||
|
||||
cg.add(
|
||||
num_rd_var.set_initial_value(
|
||||
valve[CONF_RUN_DURATION_NUMBER][CONF_INITIAL_VALUE]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
num_rd_var.set_restore_value(
|
||||
valve[CONF_RUN_DURATION_NUMBER][CONF_RESTORE_VALUE]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_SET_ACTION in valve[CONF_RUN_DURATION_NUMBER]:
|
||||
await automation.build_automation(
|
||||
num_rd_var.get_set_trigger(),
|
||||
[(float, "x")],
|
||||
valve[CONF_RUN_DURATION_NUMBER][CONF_SET_ACTION],
|
||||
)
|
||||
|
||||
cg.add(var.configure_valve_run_duration_number(valve_index, num_rd_var))
|
||||
|
||||
for sprinkler_controller in config:
|
||||
var = await cg.get_variable(sprinkler_controller[CONF_ID])
|
||||
for controller_to_add in config:
|
||||
|
Reference in New Issue
Block a user