mirror of
https://github.com/esphome/esphome.git
synced 2026-03-21 10:46:44 +01:00
Sprinkler controller component (#2249)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
ced423748e
commit
e2cddf1005
@ -193,6 +193,7 @@ esphome/components/smt100/* @piechade
|
||||
esphome/components/socket/* @esphome/core
|
||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
|
||||
esphome/components/spi/* @esphome/core
|
||||
esphome/components/sprinkler/* @kbx81
|
||||
esphome/components/sps30/* @martgras
|
||||
esphome/components/ssd1322_base/* @kbx81
|
||||
esphome/components/ssd1322_spi/* @kbx81
|
||||
|
||||
599
esphome/components/sprinkler/__init__.py
Normal file
599
esphome/components/sprinkler/__init__.py
Normal file
@ -0,0 +1,599 @@
|
||||
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 switch
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_NAME,
|
||||
CONF_REPEAT,
|
||||
CONF_RUN_DURATION,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["switch"]
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
|
||||
CONF_AUTO_ADVANCE_SWITCH = "auto_advance_switch"
|
||||
CONF_ENABLE_SWITCH = "enable_switch"
|
||||
CONF_MAIN_SWITCH = "main_switch"
|
||||
CONF_MANUAL_SELECTION_DELAY = "manual_selection_delay"
|
||||
CONF_MULTIPLIER = "multiplier"
|
||||
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"
|
||||
CONF_PUMP_START_PUMP_DELAY = "pump_start_pump_delay"
|
||||
CONF_PUMP_START_VALVE_DELAY = "pump_start_valve_delay"
|
||||
CONF_PUMP_STOP_PUMP_DELAY = "pump_stop_pump_delay"
|
||||
CONF_PUMP_STOP_VALVE_DELAY = "pump_stop_valve_delay"
|
||||
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_REVERSE_SWITCH = "reverse_switch"
|
||||
CONF_VALVE_NUMBER = "valve_number"
|
||||
CONF_VALVE_OPEN_DELAY = "valve_open_delay"
|
||||
CONF_VALVE_OVERLAP = "valve_overlap"
|
||||
CONF_VALVE_PULSE_DURATION = "valve_pulse_duration"
|
||||
CONF_VALVE_OFF_SWITCH_ID = "valve_off_switch_id"
|
||||
CONF_VALVE_ON_SWITCH_ID = "valve_on_switch_id"
|
||||
CONF_VALVE_SWITCH = "valve_switch"
|
||||
CONF_VALVE_SWITCH_ID = "valve_switch_id"
|
||||
CONF_VALVES = "valves"
|
||||
|
||||
sprinkler_ns = cg.esphome_ns.namespace("sprinkler")
|
||||
Sprinkler = sprinkler_ns.class_("Sprinkler", cg.Component)
|
||||
SprinklerControllerSwitch = sprinkler_ns.class_(
|
||||
"SprinklerControllerSwitch", switch.Switch, cg.Component
|
||||
)
|
||||
|
||||
SetMultiplierAction = sprinkler_ns.class_("SetMultiplierAction", automation.Action)
|
||||
QueueValveAction = sprinkler_ns.class_("QueueValveAction", automation.Action)
|
||||
ClearQueuedValvesAction = sprinkler_ns.class_(
|
||||
"ClearQueuedValvesAction", automation.Action
|
||||
)
|
||||
SetRepeatAction = sprinkler_ns.class_("SetRepeatAction", automation.Action)
|
||||
SetRunDurationAction = sprinkler_ns.class_("SetRunDurationAction", automation.Action)
|
||||
StartFromQueueAction = sprinkler_ns.class_("StartFromQueueAction", automation.Action)
|
||||
StartFullCycleAction = sprinkler_ns.class_("StartFullCycleAction", automation.Action)
|
||||
StartSingleValveAction = sprinkler_ns.class_(
|
||||
"StartSingleValveAction", automation.Action
|
||||
)
|
||||
ShutdownAction = sprinkler_ns.class_("ShutdownAction", automation.Action)
|
||||
NextValveAction = sprinkler_ns.class_("NextValveAction", automation.Action)
|
||||
PreviousValveAction = sprinkler_ns.class_("PreviousValveAction", automation.Action)
|
||||
PauseAction = sprinkler_ns.class_("PauseAction", automation.Action)
|
||||
ResumeAction = sprinkler_ns.class_("ResumeAction", automation.Action)
|
||||
ResumeOrStartAction = sprinkler_ns.class_("ResumeOrStartAction", automation.Action)
|
||||
|
||||
|
||||
def validate_sprinkler(config):
|
||||
for sprinkler_controller_index, sprinkler_controller in enumerate(config):
|
||||
if len(sprinkler_controller[CONF_VALVES]) <= 1:
|
||||
exclusions = [
|
||||
CONF_VALVE_OPEN_DELAY,
|
||||
CONF_VALVE_OVERLAP,
|
||||
CONF_AUTO_ADVANCE_SWITCH,
|
||||
CONF_MAIN_SWITCH,
|
||||
CONF_REVERSE_SWITCH,
|
||||
]
|
||||
for config_item in exclusions:
|
||||
if config_item in sprinkler_controller:
|
||||
raise cv.Invalid(f"Do not define {config_item} with only one valve")
|
||||
if CONF_ENABLE_SWITCH in sprinkler_controller[CONF_VALVES][0]:
|
||||
raise cv.Invalid(
|
||||
f"Do not define {CONF_ENABLE_SWITCH} with only one valve"
|
||||
)
|
||||
else:
|
||||
requirements = [
|
||||
CONF_AUTO_ADVANCE_SWITCH,
|
||||
CONF_MAIN_SWITCH,
|
||||
]
|
||||
for config_item in requirements:
|
||||
if config_item not in sprinkler_controller:
|
||||
raise cv.Invalid(
|
||||
f"{config_item} is a required option for {sprinkler_controller_index}"
|
||||
)
|
||||
|
||||
if (
|
||||
CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY in sprinkler_controller
|
||||
and CONF_VALVE_OPEN_DELAY not in sprinkler_controller
|
||||
):
|
||||
if sprinkler_controller[CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY]:
|
||||
raise cv.Invalid(
|
||||
f"{CONF_VALVE_OPEN_DELAY} must be defined when {CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY} is enabled"
|
||||
)
|
||||
|
||||
for valve in sprinkler_controller[CONF_VALVES]:
|
||||
if (
|
||||
CONF_VALVE_OVERLAP in sprinkler_controller
|
||||
and valve[CONF_RUN_DURATION] <= sprinkler_controller[CONF_VALVE_OVERLAP]
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_RUN_DURATION} must be greater than {CONF_VALVE_OVERLAP}"
|
||||
)
|
||||
if (
|
||||
CONF_VALVE_OPEN_DELAY in sprinkler_controller
|
||||
and valve[CONF_RUN_DURATION]
|
||||
<= sprinkler_controller[CONF_VALVE_OPEN_DELAY]
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_RUN_DURATION} must be greater than {CONF_VALVE_OPEN_DELAY}"
|
||||
)
|
||||
if (
|
||||
CONF_PUMP_OFF_SWITCH_ID in valve and CONF_PUMP_ON_SWITCH_ID not in valve
|
||||
) or (
|
||||
CONF_PUMP_ON_SWITCH_ID in valve and CONF_PUMP_OFF_SWITCH_ID not in valve
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"Both {CONF_PUMP_OFF_SWITCH_ID} and {CONF_PUMP_ON_SWITCH_ID} must be specified for latching pump configuration"
|
||||
)
|
||||
if CONF_PUMP_SWITCH_ID in valve and (
|
||||
CONF_PUMP_OFF_SWITCH_ID in valve or CONF_PUMP_ON_SWITCH_ID in valve
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"Do not specify {CONF_PUMP_OFF_SWITCH_ID} or {CONF_PUMP_ON_SWITCH_ID} when using {CONF_PUMP_SWITCH_ID}"
|
||||
)
|
||||
if CONF_PUMP_PULSE_DURATION not in sprinkler_controller and (
|
||||
CONF_PUMP_OFF_SWITCH_ID in valve or CONF_PUMP_ON_SWITCH_ID in valve
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_PUMP_PULSE_DURATION} must be specified when using {CONF_PUMP_OFF_SWITCH_ID} and {CONF_PUMP_ON_SWITCH_ID}"
|
||||
)
|
||||
if (
|
||||
CONF_VALVE_OFF_SWITCH_ID in valve
|
||||
and CONF_VALVE_ON_SWITCH_ID not in valve
|
||||
) or (
|
||||
CONF_VALVE_ON_SWITCH_ID in valve
|
||||
and CONF_VALVE_OFF_SWITCH_ID not in valve
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"Both {CONF_VALVE_OFF_SWITCH_ID} and {CONF_VALVE_ON_SWITCH_ID} must be specified for latching valve configuration"
|
||||
)
|
||||
if CONF_VALVE_SWITCH_ID in valve and (
|
||||
CONF_VALVE_OFF_SWITCH_ID in valve or CONF_VALVE_ON_SWITCH_ID in valve
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"Do not specify {CONF_VALVE_OFF_SWITCH_ID} or {CONF_VALVE_ON_SWITCH_ID} when using {CONF_VALVE_SWITCH_ID}"
|
||||
)
|
||||
if CONF_VALVE_PULSE_DURATION not in sprinkler_controller and (
|
||||
CONF_VALVE_OFF_SWITCH_ID in valve or CONF_VALVE_ON_SWITCH_ID in valve
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_VALVE_PULSE_DURATION} must be specified when using {CONF_VALVE_OFF_SWITCH_ID} and {CONF_VALVE_ON_SWITCH_ID}"
|
||||
)
|
||||
if (
|
||||
CONF_VALVE_SWITCH_ID not in valve
|
||||
and CONF_VALVE_OFF_SWITCH_ID not in valve
|
||||
and CONF_VALVE_ON_SWITCH_ID not in valve
|
||||
):
|
||||
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"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
SPRINKLER_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Sprinkler),
|
||||
}
|
||||
)
|
||||
|
||||
SPRINKLER_ACTION_REPEAT_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(Sprinkler),
|
||||
cv.Required(CONF_REPEAT): cv.templatable(cv.positive_int),
|
||||
},
|
||||
key=CONF_REPEAT,
|
||||
)
|
||||
|
||||
SPRINKLER_ACTION_SINGLE_VALVE_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(Sprinkler),
|
||||
cv.Required(CONF_VALVE_NUMBER): cv.templatable(cv.positive_int),
|
||||
},
|
||||
key=CONF_VALVE_NUMBER,
|
||||
)
|
||||
|
||||
SPRINKLER_ACTION_SET_MULTIPLIER_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(Sprinkler),
|
||||
cv.Required(CONF_MULTIPLIER): cv.templatable(cv.positive_float),
|
||||
},
|
||||
key=CONF_MULTIPLIER,
|
||||
)
|
||||
|
||||
SPRINKLER_ACTION_SET_RUN_DURATION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Sprinkler),
|
||||
cv.Required(CONF_RUN_DURATION): cv.templatable(cv.positive_time_period_seconds),
|
||||
cv.Required(CONF_VALVE_NUMBER): cv.templatable(cv.positive_int),
|
||||
}
|
||||
)
|
||||
|
||||
SPRINKLER_ACTION_QUEUE_VALVE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Sprinkler),
|
||||
cv.Optional(CONF_RUN_DURATION, default=0): cv.templatable(
|
||||
cv.positive_time_period_seconds
|
||||
),
|
||||
cv.Required(CONF_VALVE_NUMBER): cv.templatable(cv.positive_int),
|
||||
}
|
||||
)
|
||||
|
||||
SPRINKLER_VALVE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ENABLE_SWITCH): cv.maybe_simple_value(
|
||||
switch.SWITCH_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SprinklerControllerSwitch),
|
||||
}
|
||||
)
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
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.Required(CONF_VALVE_SWITCH): cv.maybe_simple_value(
|
||||
switch.SWITCH_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SprinklerControllerSwitch),
|
||||
}
|
||||
)
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_VALVE_OFF_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
cv.Optional(CONF_VALVE_ON_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
cv.Optional(CONF_VALVE_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
}
|
||||
)
|
||||
|
||||
SPRINKLER_CONTROLLER_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Sprinkler),
|
||||
cv.Optional(CONF_AUTO_ADVANCE_SWITCH): cv.maybe_simple_value(
|
||||
switch.SWITCH_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SprinklerControllerSwitch),
|
||||
}
|
||||
)
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_MAIN_SWITCH): cv.maybe_simple_value(
|
||||
switch.SWITCH_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SprinklerControllerSwitch),
|
||||
}
|
||||
)
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_QUEUE_ENABLE_SWITCH): cv.maybe_simple_value(
|
||||
switch.SWITCH_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SprinklerControllerSwitch),
|
||||
}
|
||||
)
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_REVERSE_SWITCH): cv.maybe_simple_value(
|
||||
switch.SWITCH_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SprinklerControllerSwitch),
|
||||
}
|
||||
)
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_MANUAL_SELECTION_DELAY): cv.positive_time_period_seconds,
|
||||
cv.Optional(CONF_REPEAT): cv.positive_int,
|
||||
cv.Optional(CONF_PUMP_PULSE_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_VALVE_PULSE_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Exclusive(
|
||||
CONF_PUMP_START_PUMP_DELAY, "pump_start_xxxx_delay"
|
||||
): cv.positive_time_period_seconds,
|
||||
cv.Exclusive(
|
||||
CONF_PUMP_STOP_PUMP_DELAY, "pump_stop_xxxx_delay"
|
||||
): cv.positive_time_period_seconds,
|
||||
cv.Optional(CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY): cv.boolean,
|
||||
cv.Exclusive(
|
||||
CONF_PUMP_START_VALVE_DELAY, "pump_start_xxxx_delay"
|
||||
): cv.positive_time_period_seconds,
|
||||
cv.Exclusive(
|
||||
CONF_PUMP_STOP_VALVE_DELAY, "pump_stop_xxxx_delay"
|
||||
): cv.positive_time_period_seconds,
|
||||
cv.Exclusive(
|
||||
CONF_VALVE_OVERLAP, "open_delay/overlap"
|
||||
): cv.positive_time_period_seconds,
|
||||
cv.Exclusive(
|
||||
CONF_VALVE_OPEN_DELAY, "open_delay/overlap"
|
||||
): cv.positive_time_period_seconds,
|
||||
cv.Required(CONF_VALVES): cv.ensure_list(SPRINKLER_VALVE_SCHEMA),
|
||||
}
|
||||
).extend(cv.ENTITY_BASE_SCHEMA)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.ensure_list(SPRINKLER_CONTROLLER_SCHEMA),
|
||||
validate_sprinkler,
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"sprinkler.set_multiplier",
|
||||
SetMultiplierAction,
|
||||
SPRINKLER_ACTION_SET_MULTIPLIER_SCHEMA,
|
||||
)
|
||||
async def sprinkler_set_multiplier_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_MULTIPLIER], args, cg.float_)
|
||||
cg.add(var.set_multiplier(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"sprinkler.queue_valve",
|
||||
QueueValveAction,
|
||||
SPRINKLER_ACTION_QUEUE_VALVE_SCHEMA,
|
||||
)
|
||||
async def sprinkler_set_queued_valve_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_VALVE_NUMBER], args, cg.uint8)
|
||||
cg.add(var.set_valve_number(template_))
|
||||
template_ = await cg.templatable(config[CONF_RUN_DURATION], args, cg.uint32)
|
||||
cg.add(var.set_valve_run_duration(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"sprinkler.set_repeat",
|
||||
SetRepeatAction,
|
||||
SPRINKLER_ACTION_REPEAT_SCHEMA,
|
||||
)
|
||||
async def sprinkler_set_repeat_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_REPEAT], args, cg.float_)
|
||||
cg.add(var.set_repeat(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"sprinkler.set_valve_run_duration",
|
||||
SetRunDurationAction,
|
||||
SPRINKLER_ACTION_SET_RUN_DURATION_SCHEMA,
|
||||
)
|
||||
async def sprinkler_set_valve_run_duration_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_VALVE_NUMBER], args, cg.uint8)
|
||||
cg.add(var.set_valve_number(template_))
|
||||
template_ = await cg.templatable(config[CONF_RUN_DURATION], args, cg.uint32)
|
||||
cg.add(var.set_valve_run_duration(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"sprinkler.start_from_queue", StartFromQueueAction, SPRINKLER_ACTION_SCHEMA
|
||||
)
|
||||
async def sprinkler_start_from_queue_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"sprinkler.start_full_cycle", StartFullCycleAction, SPRINKLER_ACTION_SCHEMA
|
||||
)
|
||||
async def sprinkler_start_full_cycle_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"sprinkler.start_single_valve",
|
||||
StartSingleValveAction,
|
||||
SPRINKLER_ACTION_SINGLE_VALVE_SCHEMA,
|
||||
)
|
||||
async def sprinkler_start_single_valve_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_VALVE_NUMBER], args, cg.uint8)
|
||||
cg.add(var.set_valve_to_start(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"sprinkler.clear_queued_valves", ClearQueuedValvesAction, SPRINKLER_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"sprinkler.next_valve", NextValveAction, SPRINKLER_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"sprinkler.previous_valve", PreviousValveAction, SPRINKLER_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action("sprinkler.pause", PauseAction, SPRINKLER_ACTION_SCHEMA)
|
||||
@automation.register_action("sprinkler.resume", ResumeAction, SPRINKLER_ACTION_SCHEMA)
|
||||
@automation.register_action(
|
||||
"sprinkler.resume_or_start_full_cycle", ResumeOrStartAction, SPRINKLER_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"sprinkler.shutdown", ShutdownAction, SPRINKLER_ACTION_SCHEMA
|
||||
)
|
||||
async def sprinkler_simple_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
for sprinkler_controller in config:
|
||||
if len(sprinkler_controller[CONF_VALVES]) > 1:
|
||||
var = cg.new_Pvariable(
|
||||
sprinkler_controller[CONF_ID],
|
||||
sprinkler_controller[CONF_MAIN_SWITCH][CONF_NAME],
|
||||
)
|
||||
else:
|
||||
var = cg.new_Pvariable(
|
||||
sprinkler_controller[CONF_ID],
|
||||
sprinkler_controller[CONF_VALVES][0][CONF_VALVE_SWITCH][CONF_NAME],
|
||||
)
|
||||
await cg.register_component(var, sprinkler_controller)
|
||||
|
||||
if len(sprinkler_controller[CONF_VALVES]) > 1:
|
||||
sw_var = await switch.new_switch(sprinkler_controller[CONF_MAIN_SWITCH])
|
||||
await cg.register_component(sw_var, sprinkler_controller[CONF_MAIN_SWITCH])
|
||||
cg.add(var.set_controller_main_switch(sw_var))
|
||||
|
||||
sw_aa_var = await switch.new_switch(
|
||||
sprinkler_controller[CONF_AUTO_ADVANCE_SWITCH]
|
||||
)
|
||||
await cg.register_component(
|
||||
sw_aa_var, sprinkler_controller[CONF_AUTO_ADVANCE_SWITCH]
|
||||
)
|
||||
cg.add(var.set_controller_auto_adv_switch(sw_aa_var))
|
||||
|
||||
if CONF_QUEUE_ENABLE_SWITCH in sprinkler_controller:
|
||||
sw_qen_var = await switch.new_switch(
|
||||
sprinkler_controller[CONF_QUEUE_ENABLE_SWITCH]
|
||||
)
|
||||
await cg.register_component(
|
||||
sw_qen_var, sprinkler_controller[CONF_QUEUE_ENABLE_SWITCH]
|
||||
)
|
||||
cg.add(var.set_controller_queue_enable_switch(sw_qen_var))
|
||||
|
||||
if CONF_REVERSE_SWITCH in sprinkler_controller:
|
||||
sw_rev_var = await switch.new_switch(
|
||||
sprinkler_controller[CONF_REVERSE_SWITCH]
|
||||
)
|
||||
await cg.register_component(
|
||||
sw_rev_var, sprinkler_controller[CONF_REVERSE_SWITCH]
|
||||
)
|
||||
cg.add(var.set_controller_reverse_switch(sw_rev_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])
|
||||
|
||||
if (
|
||||
CONF_ENABLE_SWITCH in valve
|
||||
and len(sprinkler_controller[CONF_VALVES]) > 1
|
||||
):
|
||||
sw_en_var = await switch.new_switch(valve[CONF_ENABLE_SWITCH])
|
||||
await cg.register_component(sw_en_var, valve[CONF_ENABLE_SWITCH])
|
||||
|
||||
cg.add(var.add_valve(sw_valve_var, sw_en_var))
|
||||
else:
|
||||
cg.add(var.add_valve(sw_valve_var))
|
||||
|
||||
if CONF_MANUAL_SELECTION_DELAY in sprinkler_controller:
|
||||
cg.add(
|
||||
var.set_manual_selection_delay(
|
||||
sprinkler_controller[CONF_MANUAL_SELECTION_DELAY]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_REPEAT in sprinkler_controller:
|
||||
cg.add(var.set_repeat(sprinkler_controller[CONF_REPEAT]))
|
||||
|
||||
if CONF_VALVE_OVERLAP in sprinkler_controller:
|
||||
cg.add(var.set_valve_overlap(sprinkler_controller[CONF_VALVE_OVERLAP]))
|
||||
|
||||
if CONF_VALVE_OPEN_DELAY in sprinkler_controller:
|
||||
cg.add(
|
||||
var.set_valve_open_delay(sprinkler_controller[CONF_VALVE_OPEN_DELAY])
|
||||
)
|
||||
|
||||
if CONF_PUMP_START_PUMP_DELAY in sprinkler_controller:
|
||||
cg.add(
|
||||
var.set_pump_start_delay(
|
||||
sprinkler_controller[CONF_PUMP_START_PUMP_DELAY]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_PUMP_STOP_PUMP_DELAY in sprinkler_controller:
|
||||
cg.add(
|
||||
var.set_pump_stop_delay(sprinkler_controller[CONF_PUMP_STOP_PUMP_DELAY])
|
||||
)
|
||||
|
||||
if CONF_PUMP_START_VALVE_DELAY in sprinkler_controller:
|
||||
cg.add(
|
||||
var.set_valve_start_delay(
|
||||
sprinkler_controller[CONF_PUMP_START_VALVE_DELAY]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_PUMP_STOP_VALVE_DELAY in sprinkler_controller:
|
||||
cg.add(
|
||||
var.set_valve_stop_delay(
|
||||
sprinkler_controller[CONF_PUMP_STOP_VALVE_DELAY]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY in sprinkler_controller:
|
||||
cg.add(
|
||||
var.set_pump_switch_off_during_valve_open_delay(
|
||||
sprinkler_controller[CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY]
|
||||
)
|
||||
)
|
||||
|
||||
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_VALVE_SWITCH_ID in valve:
|
||||
valve_switch = await cg.get_variable(valve[CONF_VALVE_SWITCH_ID])
|
||||
cg.add(
|
||||
var.configure_valve_switch(
|
||||
valve_index, valve_switch, valve[CONF_RUN_DURATION]
|
||||
)
|
||||
)
|
||||
elif CONF_VALVE_OFF_SWITCH_ID in valve and CONF_VALVE_ON_SWITCH_ID in valve:
|
||||
valve_switch_off = await cg.get_variable(
|
||||
valve[CONF_VALVE_OFF_SWITCH_ID]
|
||||
)
|
||||
valve_switch_on = await cg.get_variable(valve[CONF_VALVE_ON_SWITCH_ID])
|
||||
cg.add(
|
||||
var.configure_valve_switch_pulsed(
|
||||
valve_index,
|
||||
valve_switch_off,
|
||||
valve_switch_on,
|
||||
sprinkler_controller[CONF_VALVE_PULSE_DURATION],
|
||||
valve[CONF_RUN_DURATION],
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_PUMP_SWITCH_ID in valve:
|
||||
pump = await cg.get_variable(valve[CONF_PUMP_SWITCH_ID])
|
||||
cg.add(var.configure_valve_pump_switch(valve_index, pump))
|
||||
elif CONF_PUMP_OFF_SWITCH_ID in valve and CONF_PUMP_ON_SWITCH_ID in valve:
|
||||
pump_off = await cg.get_variable(valve[CONF_PUMP_OFF_SWITCH_ID])
|
||||
pump_on = await cg.get_variable(valve[CONF_PUMP_ON_SWITCH_ID])
|
||||
cg.add(
|
||||
var.configure_valve_pump_switch_pulsed(
|
||||
valve_index,
|
||||
pump_off,
|
||||
pump_on,
|
||||
sprinkler_controller[CONF_PUMP_PULSE_DURATION],
|
||||
)
|
||||
)
|
||||
|
||||
for sprinkler_controller in config:
|
||||
var = await cg.get_variable(sprinkler_controller[CONF_ID])
|
||||
for controller_to_add in config:
|
||||
if sprinkler_controller[CONF_ID] != controller_to_add[CONF_ID]:
|
||||
cg.add(
|
||||
var.add_controller(
|
||||
await cg.get_variable(controller_to_add[CONF_ID])
|
||||
)
|
||||
)
|
||||
169
esphome/components/sprinkler/automation.h
Normal file
169
esphome/components/sprinkler/automation.h
Normal file
@ -0,0 +1,169 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sprinkler/sprinkler.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sprinkler {
|
||||
|
||||
template<typename... Ts> class SetMultiplierAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetMultiplierAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
TEMPLATABLE_VALUE(float, multiplier)
|
||||
|
||||
void play(Ts... x) override { this->sprinkler_->set_multiplier(this->multiplier_.optional_value(x...)); }
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class QueueValveAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit QueueValveAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
TEMPLATABLE_VALUE(size_t, valve_number)
|
||||
TEMPLATABLE_VALUE(uint32_t, valve_run_duration)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->sprinkler_->queue_valve(this->valve_number_.optional_value(x...),
|
||||
this->valve_run_duration_.optional_value(x...));
|
||||
}
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ClearQueuedValvesAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ClearQueuedValvesAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
void play(Ts... x) override { this->sprinkler_->clear_queued_valves(); }
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetRepeatAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetRepeatAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
TEMPLATABLE_VALUE(uint32_t, repeat)
|
||||
|
||||
void play(Ts... x) override { this->sprinkler_->set_repeat(this->repeat_.optional_value(x...)); }
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetRunDurationAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetRunDurationAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
TEMPLATABLE_VALUE(size_t, valve_number)
|
||||
TEMPLATABLE_VALUE(uint32_t, valve_run_duration)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->sprinkler_->set_valve_run_duration(this->valve_number_.optional_value(x...),
|
||||
this->valve_run_duration_.optional_value(x...));
|
||||
}
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class StartFromQueueAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit StartFromQueueAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
void play(Ts... x) override { this->sprinkler_->start_from_queue(); }
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class StartFullCycleAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit StartFullCycleAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
void play(Ts... x) override { this->sprinkler_->start_full_cycle(); }
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class StartSingleValveAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit StartSingleValveAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
TEMPLATABLE_VALUE(size_t, valve_to_start)
|
||||
|
||||
void play(Ts... x) override { this->sprinkler_->start_single_valve(this->valve_to_start_.optional_value(x...)); }
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ShutdownAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ShutdownAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
void play(Ts... x) override { this->sprinkler_->shutdown(); }
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class NextValveAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit NextValveAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
void play(Ts... x) override { this->sprinkler_->next_valve(); }
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class PreviousValveAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit PreviousValveAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
void play(Ts... x) override { this->sprinkler_->previous_valve(); }
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class PauseAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit PauseAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
void play(Ts... x) override { this->sprinkler_->pause(); }
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ResumeAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ResumeAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
void play(Ts... x) override { this->sprinkler_->resume(); }
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ResumeOrStartAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ResumeOrStartAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
void play(Ts... x) override { this->sprinkler_->resume_or_start_full_cycle(); }
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
} // namespace sprinkler
|
||||
} // namespace esphome
|
||||
1347
esphome/components/sprinkler/sprinkler.cpp
Normal file
1347
esphome/components/sprinkler/sprinkler.cpp
Normal file
@ -0,0 +1,1347 @@
|
||||
#include "automation.h"
|
||||
#include "sprinkler.h"
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <utility>
|
||||
|
||||
namespace esphome {
|
||||
namespace sprinkler {
|
||||
|
||||
static const char *const TAG = "sprinkler";
|
||||
|
||||
SprinklerSwitch::SprinklerSwitch() {}
|
||||
SprinklerSwitch::SprinklerSwitch(switch_::Switch *sprinkler_switch) : on_switch_(sprinkler_switch) {}
|
||||
SprinklerSwitch::SprinklerSwitch(switch_::Switch *off_switch, switch_::Switch *on_switch, uint32_t pulse_duration)
|
||||
: pulse_duration_(pulse_duration), off_switch_(off_switch), on_switch_(on_switch) {}
|
||||
|
||||
bool SprinklerSwitch::is_latching_valve() { return (this->off_switch_ != nullptr) && (this->on_switch_ != nullptr); }
|
||||
|
||||
void SprinklerSwitch::loop() {
|
||||
if ((this->pinned_millis_) && (millis() > this->pinned_millis_ + this->pulse_duration_)) {
|
||||
this->pinned_millis_ = 0; // reset tracker
|
||||
if (this->off_switch_->state) {
|
||||
this->off_switch_->turn_off();
|
||||
}
|
||||
if (this->on_switch_->state) {
|
||||
this->on_switch_->turn_off();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerSwitch::turn_off() {
|
||||
if (!this->state()) { // do nothing if we're already in the requested state
|
||||
return;
|
||||
}
|
||||
if (this->off_switch_ != nullptr) { // latching valve, start a pulse
|
||||
if (!this->off_switch_->state) {
|
||||
this->off_switch_->turn_on();
|
||||
}
|
||||
this->pinned_millis_ = millis();
|
||||
} else if (this->on_switch_ != nullptr) { // non-latching valve
|
||||
this->on_switch_->turn_off();
|
||||
}
|
||||
this->state_ = false;
|
||||
}
|
||||
|
||||
void SprinklerSwitch::turn_on() {
|
||||
if (this->state()) { // do nothing if we're already in the requested state
|
||||
return;
|
||||
}
|
||||
if (this->off_switch_ != nullptr) { // latching valve, start a pulse
|
||||
if (!this->on_switch_->state) {
|
||||
this->on_switch_->turn_on();
|
||||
}
|
||||
this->pinned_millis_ = millis();
|
||||
} else if (this->on_switch_ != nullptr) { // non-latching valve
|
||||
this->on_switch_->turn_on();
|
||||
}
|
||||
this->state_ = true;
|
||||
}
|
||||
|
||||
bool SprinklerSwitch::state() {
|
||||
if ((this->off_switch_ == nullptr) && (this->on_switch_ != nullptr)) { // latching valve is not configured...
|
||||
return this->on_switch_->state; // ...so just return the pump switch state
|
||||
}
|
||||
return this->state_;
|
||||
}
|
||||
|
||||
void SprinklerSwitch::sync_valve_state(bool latch_state) {
|
||||
if (this->is_latching_valve()) {
|
||||
this->state_ = latch_state;
|
||||
} else if (this->on_switch_ != nullptr) {
|
||||
this->state_ = this->on_switch_->state;
|
||||
}
|
||||
}
|
||||
|
||||
SprinklerControllerSwitch::SprinklerControllerSwitch()
|
||||
: turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
|
||||
|
||||
void SprinklerControllerSwitch::loop() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
auto s = (*this->f_)();
|
||||
if (!s.has_value())
|
||||
return;
|
||||
|
||||
this->publish_state(*s);
|
||||
}
|
||||
|
||||
void SprinklerControllerSwitch::write_state(bool state) {
|
||||
if (this->prev_trigger_ != nullptr) {
|
||||
this->prev_trigger_->stop_action();
|
||||
}
|
||||
|
||||
if (state) {
|
||||
this->prev_trigger_ = this->turn_on_trigger_;
|
||||
this->turn_on_trigger_->trigger();
|
||||
} else {
|
||||
this->prev_trigger_ = this->turn_off_trigger_;
|
||||
this->turn_off_trigger_->trigger();
|
||||
}
|
||||
|
||||
if (this->optimistic_)
|
||||
this->publish_state(state);
|
||||
}
|
||||
|
||||
void SprinklerControllerSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
bool SprinklerControllerSwitch::assumed_state() { return this->assumed_state_; }
|
||||
void SprinklerControllerSwitch::set_state_lambda(std::function<optional<bool>()> &&f) { this->f_ = f; }
|
||||
float SprinklerControllerSwitch::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
Trigger<> *SprinklerControllerSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; }
|
||||
Trigger<> *SprinklerControllerSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; }
|
||||
|
||||
void SprinklerControllerSwitch::setup() {
|
||||
if (!this->restore_state_)
|
||||
return;
|
||||
|
||||
auto restored = this->get_initial_state();
|
||||
if (!restored.has_value())
|
||||
return;
|
||||
|
||||
ESP_LOGD(TAG, " Restored state %s", ONOFF(*restored));
|
||||
if (*restored) {
|
||||
this->turn_on();
|
||||
} else {
|
||||
this->turn_off();
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerControllerSwitch::dump_config() {
|
||||
LOG_SWITCH("", "Sprinkler Switch", this);
|
||||
ESP_LOGCONFIG(TAG, " Restore State: %s", YESNO(this->restore_state_));
|
||||
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
||||
}
|
||||
|
||||
void SprinklerControllerSwitch::set_restore_state(bool restore_state) { this->restore_state_ = restore_state; }
|
||||
|
||||
void SprinklerControllerSwitch::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
|
||||
|
||||
SprinklerValveOperator::SprinklerValveOperator() {}
|
||||
SprinklerValveOperator::SprinklerValveOperator(SprinklerValve *valve, Sprinkler *controller)
|
||||
: controller_(controller), valve_(valve) {}
|
||||
|
||||
void SprinklerValveOperator::loop() {
|
||||
if (millis() >= this->pinned_millis_) { // dummy check
|
||||
switch (this->state_) {
|
||||
case STARTING:
|
||||
if (millis() > (this->pinned_millis_ + this->start_delay_)) {
|
||||
this->run_(); // start_delay_ has been exceeded, so ensure both valves are on and update the state
|
||||
}
|
||||
break;
|
||||
|
||||
case ACTIVE:
|
||||
if (millis() > (this->pinned_millis_ + this->start_delay_ + this->run_duration_)) {
|
||||
this->stop(); // start_delay_ + run_duration_ has been exceeded, start shutting down
|
||||
}
|
||||
break;
|
||||
|
||||
case STOPPING:
|
||||
if (millis() > (this->pinned_millis_ + this->stop_delay_)) {
|
||||
this->kill_(); // stop_delay_has been exceeded, ensure all valves are off
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else { // perhaps millis() rolled over...or something else is horribly wrong!
|
||||
this->stop(); // bail out (TODO: handle this highly unlikely situation better...)
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::set_controller(Sprinkler *controller) {
|
||||
if (controller != nullptr) {
|
||||
this->controller_ = controller;
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::set_valve(SprinklerValve *valve) {
|
||||
if (valve != nullptr) {
|
||||
this->state_ = IDLE; // reset state
|
||||
this->run_duration_ = 0; // reset to ensure the valve isn't started without updating it
|
||||
this->pinned_millis_ = 0; // reset because (new) valve has not been started yet
|
||||
this->kill_(); // ensure everything is off before we let go!
|
||||
this->valve_ = valve; // finally, set the pointer to the new valve
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::set_run_duration(uint32_t run_duration) {
|
||||
if (run_duration) {
|
||||
this->run_duration_ = run_duration * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::set_start_delay(uint32_t start_delay, bool start_delay_is_valve_delay) {
|
||||
this->start_delay_is_valve_delay_ = start_delay_is_valve_delay;
|
||||
this->start_delay_ = start_delay * 1000; // because 1000 milliseconds is one second
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::set_stop_delay(uint32_t stop_delay, bool stop_delay_is_valve_delay) {
|
||||
this->stop_delay_is_valve_delay_ = stop_delay_is_valve_delay;
|
||||
this->stop_delay_ = stop_delay * 1000; // because 1000 milliseconds is one second
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::start() {
|
||||
if (!this->run_duration_) { // can't start if zero run duration
|
||||
return;
|
||||
}
|
||||
if (this->start_delay_ && (this->pump_switch() != nullptr)) {
|
||||
this->state_ = STARTING; // STARTING state requires both a pump and a start_delay_
|
||||
if (this->start_delay_is_valve_delay_) {
|
||||
this->pump_on_();
|
||||
} else if (!this->pump_switch()->state()) { // if the pump is already on, wait to switch on the valve
|
||||
this->valve_on_(); // to ensure consistent run time
|
||||
}
|
||||
} else {
|
||||
this->run_(); // there is no start_delay_, so just start the pump and valve
|
||||
}
|
||||
this->pinned_millis_ = millis(); // save the time the start request was made
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::stop() {
|
||||
if ((this->state_ == IDLE) || (this->state_ == STOPPING)) { // can't stop if already stopped or stopping
|
||||
return;
|
||||
}
|
||||
if (this->stop_delay_ && (this->pump_switch() != nullptr)) {
|
||||
this->state_ = STOPPING; // STOPPING state requires both a pump and a stop_delay_
|
||||
if (this->stop_delay_is_valve_delay_) {
|
||||
this->pump_off_();
|
||||
} else {
|
||||
this->valve_off_();
|
||||
}
|
||||
if (this->pump_switch()->state()) { // if the pump is still on at this point, it may be in use...
|
||||
this->valve_off_(); // ...so just switch the valve off now to ensure consistent run time
|
||||
}
|
||||
this->pinned_millis_ = millis(); // save the time the stop request was made
|
||||
} else {
|
||||
this->kill_(); // there is no stop_delay_, so just stop the pump and valve
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t SprinklerValveOperator::run_duration() { return this->run_duration_; }
|
||||
|
||||
uint32_t SprinklerValveOperator::time_remaining() {
|
||||
if ((this->state_ == STARTING) || (this->state_ == ACTIVE)) {
|
||||
return (this->pinned_millis_ + this->start_delay_ + this->run_duration_ - millis()) / 1000;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SprinklerState SprinklerValveOperator::state() { return this->state_; }
|
||||
|
||||
SprinklerSwitch *SprinklerValveOperator::pump_switch() {
|
||||
if ((this->controller_ == nullptr) || (this->valve_ == nullptr)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (this->valve_->pump_switch_index.has_value()) {
|
||||
return this->controller_->valve_pump_switch_by_pump_index(this->valve_->pump_switch_index.value());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::pump_off_() {
|
||||
if ((this->valve_ == nullptr) || (this->pump_switch() == nullptr)) { // safety first!
|
||||
return;
|
||||
}
|
||||
if (this->controller_ == nullptr) { // safety first!
|
||||
this->pump_switch()->turn_off(); // if no controller was set, just switch off the pump
|
||||
} else { // ...otherwise, do it "safely"
|
||||
auto state = this->state_; // this is silly, but...
|
||||
this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does
|
||||
this->controller_->set_pump_state(this->pump_switch(), false);
|
||||
this->state_ = state;
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::pump_on_() {
|
||||
if ((this->valve_ == nullptr) || (this->pump_switch() == nullptr)) { // safety first!
|
||||
return;
|
||||
}
|
||||
if (this->controller_ == nullptr) { // safety first!
|
||||
this->pump_switch()->turn_on(); // if no controller was set, just switch on the pump
|
||||
} else { // ...otherwise, do it "safely"
|
||||
auto state = this->state_; // this is silly, but...
|
||||
this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does
|
||||
this->controller_->set_pump_state(this->pump_switch(), true);
|
||||
this->state_ = state;
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::valve_off_() {
|
||||
if (this->valve_ == nullptr) { // safety first!
|
||||
return;
|
||||
}
|
||||
if (this->valve_->valve_switch.state()) {
|
||||
this->valve_->valve_switch.turn_off();
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::valve_on_() {
|
||||
if (this->valve_ == nullptr) { // safety first!
|
||||
return;
|
||||
}
|
||||
if (!this->valve_->valve_switch.state()) {
|
||||
this->valve_->valve_switch.turn_on();
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::kill_() {
|
||||
this->state_ = IDLE;
|
||||
this->valve_off_();
|
||||
this->pump_off_();
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::run_() {
|
||||
this->state_ = ACTIVE;
|
||||
this->valve_on_();
|
||||
this->pump_on_();
|
||||
}
|
||||
|
||||
SprinklerValveRunRequest::SprinklerValveRunRequest() {}
|
||||
SprinklerValveRunRequest::SprinklerValveRunRequest(size_t valve_number, uint32_t run_duration,
|
||||
SprinklerValveOperator *valve_op)
|
||||
: valve_number_(valve_number), run_duration_(run_duration), valve_op_(valve_op) {}
|
||||
|
||||
bool SprinklerValveRunRequest::has_request() { return this->has_valve_; }
|
||||
bool SprinklerValveRunRequest::has_valve_operator() { return !(this->valve_op_ == nullptr); }
|
||||
|
||||
void SprinklerValveRunRequest::set_run_duration(uint32_t run_duration) { this->run_duration_ = run_duration; }
|
||||
|
||||
void SprinklerValveRunRequest::set_valve(size_t valve_number) {
|
||||
this->valve_number_ = valve_number;
|
||||
this->run_duration_ = 0;
|
||||
this->valve_op_ = nullptr;
|
||||
this->has_valve_ = true;
|
||||
}
|
||||
|
||||
void SprinklerValveRunRequest::set_valve_operator(SprinklerValveOperator *valve_op) {
|
||||
if (valve_op != nullptr) {
|
||||
this->valve_op_ = valve_op;
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerValveRunRequest::reset() {
|
||||
this->has_valve_ = false;
|
||||
this->run_duration_ = 0;
|
||||
this->valve_op_ = nullptr;
|
||||
}
|
||||
|
||||
uint32_t SprinklerValveRunRequest::run_duration() { return this->run_duration_; }
|
||||
|
||||
size_t SprinklerValveRunRequest::valve() { return this->valve_number_; }
|
||||
|
||||
optional<size_t> SprinklerValveRunRequest::valve_as_opt() {
|
||||
if (this->has_valve_) {
|
||||
return this->valve_number_;
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
SprinklerValveOperator *SprinklerValveRunRequest::valve_operator() { return this->valve_op_; }
|
||||
|
||||
Sprinkler::Sprinkler() {}
|
||||
Sprinkler::Sprinkler(const std::string &name) : EntityBase(name) {}
|
||||
|
||||
void Sprinkler::setup() { this->all_valves_off_(true); }
|
||||
|
||||
void Sprinkler::loop() {
|
||||
for (auto &p : this->pump_) {
|
||||
p.loop();
|
||||
}
|
||||
for (auto &v : this->valve_) {
|
||||
v.valve_switch.loop();
|
||||
}
|
||||
for (auto &vo : this->valve_op_) {
|
||||
vo.loop();
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControllerSwitch *enable_sw) {
|
||||
auto new_valve_number = this->number_of_valves();
|
||||
this->valve_.resize(new_valve_number + 1);
|
||||
SprinklerValve *new_valve = &this->valve_[new_valve_number];
|
||||
|
||||
new_valve->controller_switch = valve_sw;
|
||||
new_valve->controller_switch->set_state_lambda([=]() -> optional<bool> {
|
||||
if (this->valve_pump_switch(new_valve_number) != nullptr) {
|
||||
return this->valve_switch(new_valve_number)->state() && this->valve_pump_switch(new_valve_number)->state();
|
||||
}
|
||||
return this->valve_switch(new_valve_number)->state();
|
||||
});
|
||||
|
||||
new_valve->valve_turn_off_automation =
|
||||
make_unique<Automation<>>(new_valve->controller_switch->get_turn_off_trigger());
|
||||
new_valve->valve_shutdown_action = make_unique<sprinkler::ShutdownAction<>>(this);
|
||||
new_valve->valve_turn_off_automation->add_actions({new_valve->valve_shutdown_action.get()});
|
||||
|
||||
new_valve->valve_turn_on_automation = make_unique<Automation<>>(new_valve->controller_switch->get_turn_on_trigger());
|
||||
new_valve->valve_resumeorstart_action = make_unique<sprinkler::StartSingleValveAction<>>(this);
|
||||
new_valve->valve_resumeorstart_action->set_valve_to_start(new_valve_number);
|
||||
new_valve->valve_turn_on_automation->add_actions({new_valve->valve_resumeorstart_action.get()});
|
||||
|
||||
if (enable_sw != nullptr) {
|
||||
new_valve->enable_switch = enable_sw;
|
||||
new_valve->enable_switch->set_optimistic(true);
|
||||
new_valve->enable_switch->set_restore_state(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::add_controller(Sprinkler *other_controller) { this->other_controllers_.push_back(other_controller); }
|
||||
|
||||
void Sprinkler::set_controller_main_switch(SprinklerControllerSwitch *controller_switch) {
|
||||
this->controller_sw_ = controller_switch;
|
||||
controller_switch->set_state_lambda([=]() -> optional<bool> {
|
||||
for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
|
||||
if (this->valve_[valve_number].controller_switch->state) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return this->active_req_.has_request();
|
||||
});
|
||||
|
||||
this->sprinkler_turn_off_automation_ = make_unique<Automation<>>(controller_switch->get_turn_off_trigger());
|
||||
this->sprinkler_shutdown_action_ = make_unique<sprinkler::ShutdownAction<>>(this);
|
||||
this->sprinkler_turn_off_automation_->add_actions({sprinkler_shutdown_action_.get()});
|
||||
|
||||
this->sprinkler_turn_on_automation_ = make_unique<Automation<>>(controller_switch->get_turn_on_trigger());
|
||||
this->sprinkler_resumeorstart_action_ = make_unique<sprinkler::ResumeOrStartAction<>>(this);
|
||||
this->sprinkler_turn_on_automation_->add_actions({sprinkler_resumeorstart_action_.get()});
|
||||
}
|
||||
|
||||
void Sprinkler::set_controller_auto_adv_switch(SprinklerControllerSwitch *auto_adv_switch) {
|
||||
this->auto_adv_sw_ = auto_adv_switch;
|
||||
auto_adv_switch->set_optimistic(true);
|
||||
auto_adv_switch->set_restore_state(true);
|
||||
}
|
||||
|
||||
void Sprinkler::set_controller_queue_enable_switch(SprinklerControllerSwitch *queue_enable_switch) {
|
||||
this->queue_enable_sw_ = queue_enable_switch;
|
||||
queue_enable_switch->set_optimistic(true);
|
||||
queue_enable_switch->set_restore_state(true);
|
||||
}
|
||||
|
||||
void Sprinkler::set_controller_reverse_switch(SprinklerControllerSwitch *reverse_switch) {
|
||||
this->reverse_sw_ = reverse_switch;
|
||||
reverse_switch->set_optimistic(true);
|
||||
reverse_switch->set_restore_state(true);
|
||||
}
|
||||
|
||||
void Sprinkler::configure_valve_switch(size_t valve_number, switch_::Switch *valve_switch, uint32_t run_duration) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
this->valve_[valve_number].valve_switch.set_on_switch(valve_switch);
|
||||
this->valve_[valve_number].run_duration = run_duration;
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::configure_valve_switch_pulsed(size_t valve_number, switch_::Switch *valve_switch_off,
|
||||
switch_::Switch *valve_switch_on, uint32_t pulse_duration,
|
||||
uint32_t run_duration) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
this->valve_[valve_number].valve_switch.set_off_switch(valve_switch_off);
|
||||
this->valve_[valve_number].valve_switch.set_on_switch(valve_switch_on);
|
||||
this->valve_[valve_number].valve_switch.set_pulse_duration(pulse_duration);
|
||||
this->valve_[valve_number].run_duration = run_duration;
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::configure_valve_pump_switch(size_t valve_number, switch_::Switch *pump_switch) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
for (size_t i = 0; i < this->pump_.size(); i++) { // check each existing registered pump
|
||||
if (this->pump_[i].on_switch() == pump_switch) { // if the "new" pump matches one we already have...
|
||||
this->valve_[valve_number].pump_switch_index = i; // ...save its index in the SprinklerSwitch vector pump_...
|
||||
return; // ...and we are done
|
||||
}
|
||||
} // if we end up here, no pumps matched, so add a new one and set the valve's SprinklerSwitch at it
|
||||
this->pump_.resize(this->pump_.size() + 1);
|
||||
this->pump_.back().set_on_switch(pump_switch);
|
||||
this->valve_[valve_number].pump_switch_index = this->pump_.size() - 1; // save the index to the new pump
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::configure_valve_pump_switch_pulsed(size_t valve_number, switch_::Switch *pump_switch_off,
|
||||
switch_::Switch *pump_switch_on, uint32_t pulse_duration) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
for (size_t i = 0; i < this->pump_.size(); i++) { // check each existing registered pump
|
||||
if ((this->pump_[i].off_switch() == pump_switch_off) &&
|
||||
(this->pump_[i].on_switch() == pump_switch_on)) { // if the "new" pump matches one we already have...
|
||||
this->valve_[valve_number].pump_switch_index = i; // ...save its index in the SprinklerSwitch vector pump_...
|
||||
return; // ...and we are done
|
||||
}
|
||||
} // if we end up here, no pumps matched, so add a new one and set the valve's SprinklerSwitch at it
|
||||
this->pump_.resize(this->pump_.size() + 1);
|
||||
this->pump_.back().set_off_switch(pump_switch_off);
|
||||
this->pump_.back().set_on_switch(pump_switch_on);
|
||||
this->pump_.back().set_pulse_duration(pulse_duration);
|
||||
this->valve_[valve_number].pump_switch_index = this->pump_.size() - 1; // save the index to the new pump
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::set_multiplier(const optional<float> multiplier) {
|
||||
if (multiplier.has_value()) {
|
||||
if (multiplier.value() > 0) {
|
||||
this->multiplier_ = multiplier.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::set_pump_start_delay(uint32_t start_delay) {
|
||||
this->start_delay_is_valve_delay_ = false;
|
||||
this->start_delay_ = start_delay;
|
||||
}
|
||||
|
||||
void Sprinkler::set_pump_stop_delay(uint32_t stop_delay) {
|
||||
this->stop_delay_is_valve_delay_ = false;
|
||||
this->stop_delay_ = stop_delay;
|
||||
}
|
||||
|
||||
void Sprinkler::set_valve_start_delay(uint32_t start_delay) {
|
||||
this->start_delay_is_valve_delay_ = true;
|
||||
this->start_delay_ = start_delay;
|
||||
}
|
||||
|
||||
void Sprinkler::set_valve_stop_delay(uint32_t stop_delay) {
|
||||
this->stop_delay_is_valve_delay_ = true;
|
||||
this->stop_delay_ = stop_delay;
|
||||
}
|
||||
|
||||
void Sprinkler::set_pump_switch_off_during_valve_open_delay(bool pump_switch_off_during_valve_open_delay) {
|
||||
this->pump_switch_off_during_valve_open_delay_ = pump_switch_off_during_valve_open_delay;
|
||||
}
|
||||
|
||||
void Sprinkler::set_valve_open_delay(const uint32_t valve_open_delay) {
|
||||
if (valve_open_delay > 0) {
|
||||
this->valve_overlap_ = false;
|
||||
this->switching_delay_ = valve_open_delay;
|
||||
} else {
|
||||
this->switching_delay_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::set_valve_overlap(uint32_t valve_overlap) {
|
||||
if (valve_overlap > 0) {
|
||||
this->valve_overlap_ = true;
|
||||
this->switching_delay_ = valve_overlap;
|
||||
} else {
|
||||
this->switching_delay_.reset();
|
||||
}
|
||||
this->pump_switch_off_during_valve_open_delay_ = false; // incompatible option
|
||||
}
|
||||
|
||||
void Sprinkler::set_manual_selection_delay(uint32_t manual_selection_delay) {
|
||||
if (manual_selection_delay > 0) {
|
||||
this->manual_selection_delay_ = manual_selection_delay;
|
||||
} else {
|
||||
this->manual_selection_delay_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::set_valve_run_duration(const optional<size_t> valve_number, const optional<uint32_t> run_duration) {
|
||||
if (valve_number.has_value() && run_duration.has_value()) {
|
||||
if (this->is_a_valid_valve(valve_number.value())) {
|
||||
this->valve_[valve_number.value()].run_duration = run_duration.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::set_auto_advance(const bool auto_advance) {
|
||||
if (this->auto_adv_sw_ != nullptr) {
|
||||
this->auto_adv_sw_->publish_state(auto_advance);
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::set_repeat(optional<uint32_t> repeat) { this->target_repeats_ = repeat; }
|
||||
|
||||
void Sprinkler::set_queue_enable(bool queue_enable) {
|
||||
if (this->queue_enable_sw_ != nullptr) {
|
||||
this->queue_enable_sw_->publish_state(queue_enable);
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::set_reverse(const bool reverse) {
|
||||
if (this->reverse_sw_ != nullptr) {
|
||||
this->reverse_sw_->publish_state(reverse);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Sprinkler::valve_run_duration(const size_t valve_number) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
return this->valve_[valve_number].run_duration;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t Sprinkler::valve_run_duration_adjusted(const size_t valve_number) {
|
||||
uint32_t run_duration = 0;
|
||||
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
run_duration = this->valve_[valve_number].run_duration;
|
||||
}
|
||||
run_duration = static_cast<uint32_t>(roundf(run_duration * this->multiplier_));
|
||||
// run_duration must not be less than any of these
|
||||
if ((run_duration < this->start_delay_) || (run_duration < this->stop_delay_) ||
|
||||
(run_duration < this->switching_delay_.value_or(0) * 2)) {
|
||||
return std::max(this->switching_delay_.value_or(0) * 2, std::max(this->start_delay_, this->stop_delay_));
|
||||
}
|
||||
return run_duration;
|
||||
}
|
||||
|
||||
bool Sprinkler::auto_advance() {
|
||||
if (this->auto_adv_sw_ != nullptr) {
|
||||
return this->auto_adv_sw_->state;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
float Sprinkler::multiplier() { return this->multiplier_; }
|
||||
|
||||
optional<uint32_t> Sprinkler::repeat() { return this->target_repeats_; }
|
||||
|
||||
optional<uint32_t> Sprinkler::repeat_count() {
|
||||
// if there is an active valve and auto-advance is enabled, we may be repeating, so return the count
|
||||
if (this->auto_adv_sw_ != nullptr) {
|
||||
if (this->active_req_.has_request() && this->auto_adv_sw_->state) {
|
||||
return this->repeat_count_;
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
bool Sprinkler::queue_enabled() {
|
||||
if (this->queue_enable_sw_ != nullptr) {
|
||||
return this->queue_enable_sw_->state;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Sprinkler::reverse() {
|
||||
if (this->reverse_sw_ != nullptr) {
|
||||
return this->reverse_sw_->state;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Sprinkler::start_from_queue() {
|
||||
if (this->queued_valves_.empty()) {
|
||||
return; // if there is nothing in the queue, don't do anything
|
||||
}
|
||||
if (this->queue_enabled() && this->active_valve().has_value()) {
|
||||
return; // if there is already a valve running from the queue, do nothing
|
||||
}
|
||||
|
||||
if (this->auto_adv_sw_ != nullptr) {
|
||||
this->auto_adv_sw_->publish_state(false);
|
||||
}
|
||||
if (this->queue_enable_sw_ != nullptr) {
|
||||
this->queue_enable_sw_->publish_state(true);
|
||||
}
|
||||
this->reset_cycle_states_(); // just in case auto-advance is switched on later
|
||||
this->repeat_count_ = 0;
|
||||
this->fsm_kick_(); // will automagically pick up from the queue (it has priority)
|
||||
}
|
||||
|
||||
void Sprinkler::start_full_cycle() {
|
||||
if (this->auto_advance() && this->active_valve().has_value()) {
|
||||
return; // if auto-advance is already enabled and there is already a valve running, do nothing
|
||||
}
|
||||
|
||||
if (this->queue_enable_sw_ != nullptr) {
|
||||
this->queue_enable_sw_->publish_state(false);
|
||||
}
|
||||
this->prep_full_cycle_();
|
||||
this->repeat_count_ = 0;
|
||||
// if there is no active valve already, start the first valve in the cycle
|
||||
if (!this->active_req_.has_request()) {
|
||||
this->fsm_kick_();
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::start_single_valve(const optional<size_t> valve_number) {
|
||||
if (!valve_number.has_value() || (valve_number == this->active_valve())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->auto_adv_sw_ != nullptr) {
|
||||
this->auto_adv_sw_->publish_state(false);
|
||||
}
|
||||
if (this->queue_enable_sw_ != nullptr) {
|
||||
this->queue_enable_sw_->publish_state(false);
|
||||
}
|
||||
this->reset_cycle_states_(); // just in case auto-advance is switched on later
|
||||
this->repeat_count_ = 0;
|
||||
this->fsm_request_(valve_number.value());
|
||||
}
|
||||
|
||||
void Sprinkler::queue_valve(optional<size_t> valve_number, optional<uint32_t> run_duration) {
|
||||
if (valve_number.has_value()) {
|
||||
if (this->is_a_valid_valve(valve_number.value()) && (this->queued_valves_.size() < this->max_queue_size_)) {
|
||||
SprinklerQueueItem item{valve_number.value(), run_duration.value()};
|
||||
this->queued_valves_.insert(this->queued_valves_.begin(), item);
|
||||
ESP_LOGD(TAG, "Valve %u placed into queue with run duration of %u seconds", valve_number.value_or(0),
|
||||
run_duration.value_or(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::clear_queued_valves() {
|
||||
this->queued_valves_.clear();
|
||||
ESP_LOGD(TAG, "Queue cleared");
|
||||
}
|
||||
|
||||
void Sprinkler::next_valve() {
|
||||
if (this->state_ == IDLE) {
|
||||
this->reset_cycle_states_(); // just in case auto-advance is switched on later
|
||||
}
|
||||
this->manual_valve_ = this->next_valve_number_(
|
||||
this->manual_valve_.value_or(this->active_req_.valve_as_opt().value_or(this->number_of_valves() - 1)));
|
||||
if (this->manual_selection_delay_.has_value()) {
|
||||
this->set_timer_duration_(sprinkler::TIMER_VALVE_SELECTION, this->manual_selection_delay_.value());
|
||||
this->start_timer_(sprinkler::TIMER_VALVE_SELECTION);
|
||||
} else {
|
||||
this->fsm_request_(this->manual_valve_.value());
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::previous_valve() {
|
||||
if (this->state_ == IDLE) {
|
||||
this->reset_cycle_states_(); // just in case auto-advance is switched on later
|
||||
}
|
||||
this->manual_valve_ =
|
||||
this->previous_valve_number_(this->manual_valve_.value_or(this->active_req_.valve_as_opt().value_or(0)));
|
||||
if (this->manual_selection_delay_.has_value()) {
|
||||
this->set_timer_duration_(sprinkler::TIMER_VALVE_SELECTION, this->manual_selection_delay_.value());
|
||||
this->start_timer_(sprinkler::TIMER_VALVE_SELECTION);
|
||||
} else {
|
||||
this->fsm_request_(this->manual_valve_.value());
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::shutdown(bool clear_queue) {
|
||||
this->cancel_timer_(sprinkler::TIMER_VALVE_SELECTION);
|
||||
this->active_req_.reset();
|
||||
this->manual_valve_.reset();
|
||||
this->next_req_.reset();
|
||||
for (auto &vo : this->valve_op_) {
|
||||
vo.stop();
|
||||
}
|
||||
this->fsm_transition_to_shutdown_();
|
||||
if (clear_queue) {
|
||||
this->clear_queued_valves();
|
||||
this->repeat_count_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::pause() {
|
||||
if (this->paused_valve_.has_value() || !this->active_req_.has_request()) {
|
||||
return; // we can't pause if we're already paused or if there is no active valve
|
||||
}
|
||||
this->paused_valve_ = this->active_valve();
|
||||
this->resume_duration_ = this->time_remaining();
|
||||
this->shutdown(false);
|
||||
ESP_LOGD(TAG, "Paused valve %u with %u seconds remaining", this->paused_valve_.value_or(0),
|
||||
this->resume_duration_.value_or(0));
|
||||
}
|
||||
|
||||
void Sprinkler::resume() {
|
||||
if (this->paused_valve_.has_value() && (this->resume_duration_.has_value())) {
|
||||
ESP_LOGD(TAG, "Resuming valve %u with %u seconds remaining", this->paused_valve_.value_or(0),
|
||||
this->resume_duration_.value_or(0));
|
||||
this->fsm_request_(this->paused_valve_.value(), this->resume_duration_.value());
|
||||
this->reset_resume_();
|
||||
} else {
|
||||
ESP_LOGD(TAG, "No valve to resume!");
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::resume_or_start_full_cycle() {
|
||||
if (this->paused_valve_.has_value() && (this->resume_duration_.has_value())) {
|
||||
this->resume();
|
||||
} else {
|
||||
this->start_full_cycle();
|
||||
}
|
||||
}
|
||||
|
||||
const char *Sprinkler::valve_name(const size_t valve_number) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
return this->valve_[valve_number].controller_switch->get_name().c_str();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
optional<size_t> Sprinkler::active_valve() { return this->active_req_.valve_as_opt(); }
|
||||
optional<size_t> Sprinkler::paused_valve() { return this->paused_valve_; }
|
||||
|
||||
optional<size_t> Sprinkler::queued_valve() {
|
||||
if (!this->queued_valves_.empty()) {
|
||||
return this->queued_valves_.back().valve_number;
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<size_t> Sprinkler::manual_valve() { return this->manual_valve_; }
|
||||
|
||||
size_t Sprinkler::number_of_valves() { return this->valve_.size(); }
|
||||
|
||||
bool Sprinkler::is_a_valid_valve(const size_t valve_number) {
|
||||
return ((valve_number >= 0) && (valve_number < this->number_of_valves()));
|
||||
}
|
||||
|
||||
bool Sprinkler::pump_in_use(SprinklerSwitch *pump_switch) {
|
||||
if (pump_switch == nullptr) {
|
||||
return false; // we can't do anything if there's nothing to check
|
||||
}
|
||||
// a pump must be considered "in use" if a (distribution) valve it supplies is active. this means:
|
||||
// - at least one SprinklerValveOperator:
|
||||
// - has a valve loaded that depends on this pump
|
||||
// - is in a state that depends on the pump: (ACTIVE and _possibly_ STARTING/STOPPING)
|
||||
// - if NO SprinklerValveOperator is active but there is a run request pending (active_req_.has_request()) and the
|
||||
// controller state is STARTING, valve open delay is configured but NOT pump_switch_off_during_valve_open_delay_
|
||||
for (auto &vo : this->valve_op_) { // first, check if any SprinklerValveOperator has a valve dependent on this pump
|
||||
if ((vo.state() != BYPASS) && (vo.pump_switch() != nullptr)) {
|
||||
// the SprinklerValveOperator is configured with a pump; now check if it is the pump of interest
|
||||
if ((vo.pump_switch()->off_switch() == pump_switch->off_switch()) &&
|
||||
(vo.pump_switch()->on_switch() == pump_switch->on_switch())) {
|
||||
// now if the SprinklerValveOperator has a pump and it is either ACTIVE, is STARTING with a valve delay or
|
||||
// is
|
||||
// STOPPING with a valve delay, its pump can be considered "in use", so just return indicating this now
|
||||
if ((vo.state() == ACTIVE) ||
|
||||
((vo.state() == STARTING) && this->start_delay_ && this->start_delay_is_valve_delay_) ||
|
||||
((vo.state() == STOPPING) && this->stop_delay_ && this->stop_delay_is_valve_delay_)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // if we end up here, no SprinklerValveOperator was in a "give-away" state indicating that the pump is in use...
|
||||
if (!this->valve_overlap_ && !this->pump_switch_off_during_valve_open_delay_ && this->switching_delay_.has_value() &&
|
||||
this->active_req_.has_request() && (this->state_ != STOPPING)) {
|
||||
// ...the controller is configured to keep the pump on during a valve open delay, so just return
|
||||
// whether or not the next valve shares the same pump
|
||||
return (pump_switch->off_switch() == this->valve_pump_switch(this->active_req_.valve())->off_switch()) &&
|
||||
(pump_switch->on_switch() == this->valve_pump_switch(this->active_req_.valve())->on_switch());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Sprinkler::set_pump_state(SprinklerSwitch *pump_switch, bool state) {
|
||||
if (pump_switch == nullptr) {
|
||||
return; // we can't do anything if there's nothing to check
|
||||
}
|
||||
|
||||
bool hold_pump_on = false;
|
||||
|
||||
for (auto &controller : this->other_controllers_) { // check if the pump is in use by another controller
|
||||
if (controller != this) { // dummy check
|
||||
if (controller->pump_in_use(pump_switch)) {
|
||||
hold_pump_on = true; // if another controller says it's using this pump, keep it on
|
||||
// at this point we know if there exists another SprinklerSwitch that is "on" with its
|
||||
// off_switch_ and on_switch_ pointers pointing to the same pair of switch objects
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hold_pump_on) {
|
||||
// at this point we know if there exists another SprinklerSwitch that is "on" with its
|
||||
// off_switch_ and on_switch_ pointers pointing to the same pair of switch objects...
|
||||
pump_switch->sync_valve_state(true); // ...so ensure our state is consistent
|
||||
ESP_LOGD(TAG, "Leaving pump on because another controller instance is using it");
|
||||
}
|
||||
|
||||
if (state) { // ...and now we can set the new state of the switch
|
||||
pump_switch->turn_on();
|
||||
} else if (!hold_pump_on && !this->pump_in_use(pump_switch)) {
|
||||
pump_switch->turn_off();
|
||||
} else if (hold_pump_on) { // we must assume the other controller will switch off the pump when done...
|
||||
pump_switch->sync_valve_state(false); // ...this only impacts latching valves
|
||||
}
|
||||
}
|
||||
|
||||
optional<uint32_t> Sprinkler::time_remaining() {
|
||||
if (this->active_req_.has_request()) { // first try to return the value based on active_req_...
|
||||
if (this->active_req_.valve_operator() != nullptr) {
|
||||
return this->active_req_.valve_operator()->time_remaining();
|
||||
}
|
||||
}
|
||||
for (auto &vo : this->valve_op_) { // ...else return the value from the first non-IDLE SprinklerValveOperator
|
||||
if (vo.state() != IDLE) {
|
||||
return vo.time_remaining();
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
SprinklerControllerSwitch *Sprinkler::control_switch(size_t valve_number) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
return this->valve_[valve_number].controller_switch;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SprinklerControllerSwitch *Sprinkler::enable_switch(size_t valve_number) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
return this->valve_[valve_number].enable_switch;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SprinklerSwitch *Sprinkler::valve_switch(const size_t valve_number) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
return &this->valve_[valve_number].valve_switch;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SprinklerSwitch *Sprinkler::valve_pump_switch(const size_t valve_number) {
|
||||
if (this->is_a_valid_valve(valve_number) && this->valve_[valve_number].pump_switch_index.has_value()) {
|
||||
return &this->pump_[this->valve_[valve_number].pump_switch_index.value()];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SprinklerSwitch *Sprinkler::valve_pump_switch_by_pump_index(size_t pump_index) {
|
||||
if (pump_index < this->pump_.size()) {
|
||||
return &this->pump_[pump_index];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t Sprinkler::hash_base() { return 3129891955UL; }
|
||||
|
||||
bool Sprinkler::valve_is_enabled_(const size_t valve_number) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
if (this->valve_[valve_number].enable_switch != nullptr) {
|
||||
return this->valve_[valve_number].enable_switch->state;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Sprinkler::mark_valve_cycle_complete_(const size_t valve_number) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
ESP_LOGD(TAG, "Marking valve %u complete", valve_number);
|
||||
this->valve_[valve_number].valve_cycle_complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Sprinkler::valve_cycle_complete_(const size_t valve_number) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
return this->valve_[valve_number].valve_cycle_complete;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t Sprinkler::next_valve_number_(const size_t first_valve) {
|
||||
if (this->is_a_valid_valve(first_valve) && (first_valve + 1 < this->number_of_valves()))
|
||||
return first_valve + 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t Sprinkler::previous_valve_number_(const size_t first_valve) {
|
||||
if (this->is_a_valid_valve(first_valve) && (first_valve - 1 >= 0))
|
||||
return first_valve - 1;
|
||||
|
||||
return this->number_of_valves() - 1;
|
||||
}
|
||||
|
||||
optional<size_t> Sprinkler::next_valve_number_in_cycle_(const optional<size_t> first_valve) {
|
||||
if (this->reverse_sw_ != nullptr) {
|
||||
if (this->reverse_sw_->state) {
|
||||
return this->previous_enabled_incomplete_valve_number_(first_valve);
|
||||
}
|
||||
}
|
||||
return this->next_enabled_incomplete_valve_number_(first_valve);
|
||||
}
|
||||
|
||||
void Sprinkler::load_next_valve_run_request_(optional<size_t> first_valve) {
|
||||
if (this->next_req_.has_request()) {
|
||||
if (!this->next_req_.run_duration()) { // ensure the run duration is set correctly for consumption later on
|
||||
this->next_req_.set_run_duration(this->valve_run_duration_adjusted(this->next_req_.valve()));
|
||||
}
|
||||
return; // there is already a request pending
|
||||
} else if (this->queue_enabled() && !this->queued_valves_.empty()) {
|
||||
this->next_req_.set_valve(this->queued_valves_.back().valve_number);
|
||||
if (this->queued_valves_.back().run_duration) {
|
||||
this->next_req_.set_run_duration(this->queued_valves_.back().run_duration);
|
||||
} else {
|
||||
this->next_req_.set_run_duration(this->valve_run_duration_adjusted(this->queued_valves_.back().valve_number));
|
||||
}
|
||||
this->queued_valves_.pop_back();
|
||||
} else if (this->auto_adv_sw_ != nullptr) {
|
||||
if (this->auto_adv_sw_->state) {
|
||||
if (this->next_valve_number_in_cycle_(first_valve).has_value()) {
|
||||
// if there is another valve to run as a part of a cycle, load that
|
||||
this->next_req_.set_valve(this->next_valve_number_in_cycle_(first_valve).value_or(0));
|
||||
this->next_req_.set_run_duration(
|
||||
this->valve_run_duration_adjusted(this->next_valve_number_in_cycle_(first_valve).value_or(0)));
|
||||
} else if ((this->repeat_count_++ < this->target_repeats_.value_or(0))) {
|
||||
ESP_LOGD(TAG, "Repeating - starting cycle %u of %u", this->repeat_count_ + 1,
|
||||
this->target_repeats_.value_or(0) + 1);
|
||||
// if there are repeats remaining and no more valves were left in the cycle, start a new cycle
|
||||
this->prep_full_cycle_();
|
||||
this->next_req_.set_valve(this->next_valve_number_in_cycle_(first_valve).value_or(0));
|
||||
this->next_req_.set_run_duration(
|
||||
this->valve_run_duration_adjusted(this->next_valve_number_in_cycle_(first_valve).value_or(0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
optional<size_t> Sprinkler::next_enabled_incomplete_valve_number_(const optional<size_t> first_valve) {
|
||||
auto new_valve_number = this->next_valve_number_(first_valve.value_or(this->number_of_valves() - 1));
|
||||
|
||||
while (new_valve_number != first_valve.value_or(this->number_of_valves() - 1)) {
|
||||
if (this->valve_is_enabled_(new_valve_number) && (!this->valve_cycle_complete_(new_valve_number))) {
|
||||
return new_valve_number;
|
||||
} else {
|
||||
new_valve_number = this->next_valve_number_(new_valve_number);
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<size_t> Sprinkler::previous_enabled_incomplete_valve_number_(const optional<size_t> first_valve) {
|
||||
auto new_valve_number = this->previous_valve_number_(first_valve.value_or(0));
|
||||
|
||||
while (new_valve_number != first_valve.value_or(0)) {
|
||||
if (this->valve_is_enabled_(new_valve_number) && (!this->valve_cycle_complete_(new_valve_number))) {
|
||||
return new_valve_number;
|
||||
} else {
|
||||
new_valve_number = this->previous_valve_number_(new_valve_number);
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
bool Sprinkler::any_valve_is_enabled_() {
|
||||
for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
|
||||
if (this->valve_is_enabled_(valve_number))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Sprinkler::start_valve_(SprinklerValveRunRequest *req) {
|
||||
if (!req->has_request()) {
|
||||
return; // we can't do anything if the request contains nothing
|
||||
}
|
||||
if (!this->is_a_valid_valve(req->valve())) {
|
||||
return; // we can't do anything if the valve number isn't valid
|
||||
}
|
||||
for (auto &vo : this->valve_op_) { // find the first available SprinklerValveOperator, load it and start it up
|
||||
if (vo.state() == IDLE) {
|
||||
auto run_duration = req->run_duration() ? req->run_duration() : this->valve_run_duration_adjusted(req->valve());
|
||||
ESP_LOGD(TAG, "Starting valve %u for %u seconds, cycle %u of %u", req->valve(), run_duration,
|
||||
this->repeat_count_ + 1, this->target_repeats_.value_or(0) + 1);
|
||||
req->set_valve_operator(&vo);
|
||||
vo.set_controller(this);
|
||||
vo.set_valve(&this->valve_[req->valve()]);
|
||||
vo.set_run_duration(run_duration);
|
||||
vo.set_start_delay(this->start_delay_, this->start_delay_is_valve_delay_);
|
||||
vo.set_stop_delay(this->stop_delay_, this->stop_delay_is_valve_delay_);
|
||||
vo.start();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::all_valves_off_(const bool include_pump) {
|
||||
for (size_t valve_index = 0; valve_index < this->number_of_valves(); valve_index++) {
|
||||
if (this->valve_[valve_index].valve_switch.state()) {
|
||||
this->valve_[valve_index].valve_switch.turn_off();
|
||||
}
|
||||
if (include_pump) {
|
||||
this->set_pump_state(this->valve_pump_switch(valve_index), false);
|
||||
}
|
||||
}
|
||||
ESP_LOGD(TAG, "All valves stopped%s", include_pump ? ", including pumps" : "");
|
||||
}
|
||||
|
||||
void Sprinkler::prep_full_cycle_() {
|
||||
if (this->auto_adv_sw_ != nullptr) {
|
||||
if (!this->auto_adv_sw_->state) {
|
||||
this->auto_adv_sw_->publish_state(true);
|
||||
}
|
||||
}
|
||||
if (!this->any_valve_is_enabled_()) {
|
||||
for (auto &valve : this->valve_) {
|
||||
if (valve.enable_switch != nullptr) {
|
||||
valve.enable_switch->publish_state(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
this->reset_cycle_states_();
|
||||
}
|
||||
|
||||
void Sprinkler::reset_cycle_states_() {
|
||||
for (auto &valve : this->valve_) {
|
||||
valve.valve_cycle_complete = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::reset_resume_() {
|
||||
this->paused_valve_.reset();
|
||||
this->resume_duration_.reset();
|
||||
}
|
||||
|
||||
void Sprinkler::fsm_request_(size_t requested_valve, uint32_t requested_run_duration) {
|
||||
this->next_req_.set_valve(requested_valve);
|
||||
this->next_req_.set_run_duration(requested_run_duration);
|
||||
// if state is IDLE or ACTIVE, call fsm_transition_() to start it immediately;
|
||||
// otherwise, fsm_transition() will pick up next_req_ at the next appropriate transition
|
||||
this->fsm_kick_();
|
||||
}
|
||||
|
||||
void Sprinkler::fsm_kick_() {
|
||||
if ((this->state_ == IDLE) || (this->state_ == ACTIVE)) {
|
||||
this->fsm_transition_();
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::fsm_transition_() {
|
||||
ESP_LOGVV(TAG, "fsm_transition_ called; state is %s", this->state_as_str_(this->state_).c_str());
|
||||
switch (this->state_) {
|
||||
case IDLE: // the system was off -> start it up
|
||||
// advances to ACTIVE
|
||||
this->fsm_transition_from_shutdown_();
|
||||
break;
|
||||
|
||||
case ACTIVE:
|
||||
// advances to STOPPING or ACTIVE (again)
|
||||
this->fsm_transition_from_valve_run_();
|
||||
break;
|
||||
|
||||
case STARTING: {
|
||||
// follows valve open delay interval
|
||||
this->set_timer_duration_(sprinkler::TIMER_SM,
|
||||
this->active_req_.run_duration() - this->switching_delay_.value_or(0));
|
||||
this->start_timer_(sprinkler::TIMER_SM);
|
||||
this->start_valve_(&this->active_req_);
|
||||
this->state_ = ACTIVE;
|
||||
if (this->next_req_.has_request()) {
|
||||
// another valve has been requested, so restart the timer so we pick it up quickly
|
||||
this->set_timer_duration_(sprinkler::TIMER_SM, this->manual_selection_delay_.value_or(1));
|
||||
this->start_timer_(sprinkler::TIMER_SM);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case STOPPING:
|
||||
// stop_delay_ has elapsed so just shut everything off
|
||||
this->active_req_.reset();
|
||||
this->manual_valve_.reset();
|
||||
this->all_valves_off_(true);
|
||||
this->state_ = IDLE;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (this->next_req_.has_request() && (this->state_ == IDLE)) {
|
||||
// another valve has been requested, so restart the timer so we pick it up quickly
|
||||
this->set_timer_duration_(sprinkler::TIMER_SM, this->manual_selection_delay_.value_or(1));
|
||||
this->start_timer_(sprinkler::TIMER_SM);
|
||||
}
|
||||
ESP_LOGVV(TAG, "fsm_transition_ complete; new state is %s", this->state_as_str_(this->state_).c_str());
|
||||
}
|
||||
|
||||
void Sprinkler::fsm_transition_from_shutdown_() {
|
||||
this->load_next_valve_run_request_();
|
||||
this->active_req_.set_valve(this->next_req_.valve());
|
||||
this->active_req_.set_run_duration(this->next_req_.run_duration());
|
||||
this->next_req_.reset();
|
||||
|
||||
this->set_timer_duration_(sprinkler::TIMER_SM, this->active_req_.run_duration() - this->switching_delay_.value_or(0));
|
||||
this->start_timer_(sprinkler::TIMER_SM);
|
||||
this->start_valve_(&this->active_req_);
|
||||
this->state_ = ACTIVE;
|
||||
}
|
||||
|
||||
void Sprinkler::fsm_transition_from_valve_run_() {
|
||||
if (!this->active_req_.has_request()) { // dummy check...
|
||||
this->fsm_transition_to_shutdown_();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->timer_active_(sprinkler::TIMER_SM)) { // only flag the valve as "complete" if the timer finished
|
||||
this->mark_valve_cycle_complete_(this->active_req_.valve());
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Valve cycle interrupted - NOT flagging valve as complete and stopping current valve");
|
||||
for (auto &vo : this->valve_op_) {
|
||||
vo.stop();
|
||||
}
|
||||
}
|
||||
|
||||
this->load_next_valve_run_request_(this->active_req_.valve());
|
||||
|
||||
if (this->next_req_.has_request()) { // there is another valve to run...
|
||||
bool same_pump =
|
||||
this->valve_pump_switch(this->active_req_.valve()) == this->valve_pump_switch(this->next_req_.valve());
|
||||
|
||||
this->active_req_.set_valve(this->next_req_.valve());
|
||||
this->active_req_.set_run_duration(this->next_req_.run_duration());
|
||||
this->next_req_.reset();
|
||||
|
||||
// this->state_ = ACTIVE; // state isn't changing
|
||||
if (this->valve_overlap_ || !this->switching_delay_.has_value()) {
|
||||
this->set_timer_duration_(sprinkler::TIMER_SM,
|
||||
this->active_req_.run_duration() - this->switching_delay_.value_or(0));
|
||||
this->start_timer_(sprinkler::TIMER_SM);
|
||||
this->start_valve_(&this->active_req_);
|
||||
} else {
|
||||
this->set_timer_duration_(
|
||||
sprinkler::TIMER_SM,
|
||||
this->switching_delay_.value() * 2 +
|
||||
(this->pump_switch_off_during_valve_open_delay_ && same_pump ? this->stop_delay_ : 0));
|
||||
this->start_timer_(sprinkler::TIMER_SM);
|
||||
this->state_ = STARTING;
|
||||
}
|
||||
} else { // there is NOT another valve to run...
|
||||
this->fsm_transition_to_shutdown_();
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::fsm_transition_to_shutdown_() {
|
||||
this->state_ = STOPPING;
|
||||
this->set_timer_duration_(sprinkler::TIMER_SM,
|
||||
this->start_delay_ + this->stop_delay_ + this->switching_delay_.value_or(0) + 1);
|
||||
this->start_timer_(sprinkler::TIMER_SM);
|
||||
}
|
||||
|
||||
std::string Sprinkler::state_as_str_(SprinklerState state) {
|
||||
switch (state) {
|
||||
case IDLE:
|
||||
return "IDLE";
|
||||
|
||||
case STARTING:
|
||||
return "STARTING";
|
||||
|
||||
case ACTIVE:
|
||||
return "ACTIVE";
|
||||
|
||||
case STOPPING:
|
||||
return "STOPPING";
|
||||
|
||||
case BYPASS:
|
||||
return "BYPASS";
|
||||
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) {
|
||||
if (this->timer_duration_(timer_index) > 0) {
|
||||
this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index),
|
||||
this->timer_cbf_(timer_index));
|
||||
this->timer_[timer_index].start_time = millis();
|
||||
this->timer_[timer_index].active = true;
|
||||
}
|
||||
ESP_LOGVV(TAG, "Timer %u started for %u sec", static_cast<size_t>(timer_index),
|
||||
this->timer_duration_(timer_index) / 1000);
|
||||
}
|
||||
|
||||
bool Sprinkler::cancel_timer_(const SprinklerTimerIndex timer_index) {
|
||||
this->timer_[timer_index].active = false;
|
||||
return this->cancel_timeout(this->timer_[timer_index].name);
|
||||
}
|
||||
|
||||
bool Sprinkler::timer_active_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].active; }
|
||||
|
||||
void Sprinkler::set_timer_duration_(const SprinklerTimerIndex timer_index, const uint32_t time) {
|
||||
this->timer_[timer_index].time = 1000 * time;
|
||||
}
|
||||
|
||||
uint32_t Sprinkler::timer_duration_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].time; }
|
||||
|
||||
std::function<void()> Sprinkler::timer_cbf_(const SprinklerTimerIndex timer_index) {
|
||||
return this->timer_[timer_index].func;
|
||||
}
|
||||
|
||||
void Sprinkler::valve_selection_callback_() {
|
||||
this->timer_[sprinkler::TIMER_VALVE_SELECTION].active = false;
|
||||
ESP_LOGVV(TAG, "Valve selection timer expired");
|
||||
if (this->manual_valve_.has_value()) {
|
||||
this->fsm_request_(this->manual_valve_.value());
|
||||
this->manual_valve_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::sm_timer_callback_() {
|
||||
this->timer_[sprinkler::TIMER_SM].active = false;
|
||||
ESP_LOGVV(TAG, "State machine timer expired");
|
||||
this->fsm_transition_();
|
||||
}
|
||||
|
||||
void Sprinkler::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Sprinkler Controller -- %s", this->name_.c_str());
|
||||
if (this->manual_selection_delay_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Manual Selection Delay: %u seconds", this->manual_selection_delay_.value_or(0));
|
||||
}
|
||||
if (this->target_repeats_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Repeat Cycles: %u times", this->target_repeats_.value_or(0));
|
||||
}
|
||||
if (this->start_delay_) {
|
||||
if (this->start_delay_is_valve_delay_) {
|
||||
ESP_LOGCONFIG(TAG, " Pump Start Valve Delay: %u seconds", this->start_delay_);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Pump Start Pump Delay: %u seconds", this->start_delay_);
|
||||
}
|
||||
}
|
||||
if (this->stop_delay_) {
|
||||
if (this->stop_delay_is_valve_delay_) {
|
||||
ESP_LOGCONFIG(TAG, " Pump Stop Valve Delay: %u seconds", this->stop_delay_);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Pump Stop Pump Delay: %u seconds", this->stop_delay_);
|
||||
}
|
||||
}
|
||||
if (this->switching_delay_.has_value()) {
|
||||
if (this->valve_overlap_) {
|
||||
ESP_LOGCONFIG(TAG, " Valve Overlap: %u seconds", this->switching_delay_.value_or(0));
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Valve Open Delay: %u seconds", this->switching_delay_.value_or(0));
|
||||
ESP_LOGCONFIG(TAG, " Pump Switch Off During Valve Open Delay: %s",
|
||||
YESNO(this->pump_switch_off_during_valve_open_delay_));
|
||||
}
|
||||
}
|
||||
for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
|
||||
ESP_LOGCONFIG(TAG, " Valve %u:", valve_number);
|
||||
ESP_LOGCONFIG(TAG, " Name: %s", this->valve_name(valve_number));
|
||||
ESP_LOGCONFIG(TAG, " Run Duration: %u seconds", this->valve_[valve_number].run_duration);
|
||||
if (this->valve_[valve_number].valve_switch.pulse_duration()) {
|
||||
ESP_LOGCONFIG(TAG, " Pulse Duration: %u milliseconds",
|
||||
this->valve_[valve_number].valve_switch.pulse_duration());
|
||||
}
|
||||
}
|
||||
if (!this->pump_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Total number of pumps: %u", this->pump_.size());
|
||||
}
|
||||
if (!this->valve_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Total number of valves: %u", this->valve_.size());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sprinkler
|
||||
} // namespace esphome
|
||||
528
esphome/components/sprinkler/sprinkler.h
Normal file
528
esphome/components/sprinkler/sprinkler.h
Normal file
@ -0,0 +1,528 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/switch/switch.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sprinkler {
|
||||
|
||||
enum SprinklerState : uint8_t {
|
||||
// NOTE: these states are used by both SprinklerValveOperator and Sprinkler (the controller)!
|
||||
IDLE, // system/valve is off
|
||||
STARTING, // system/valve is starting/"half open" -- either pump or valve is on, but the remaining pump/valve is not
|
||||
ACTIVE, // system/valve is running its cycle
|
||||
STOPPING, // system/valve is stopping/"half open" -- either pump or valve is on, but the remaining pump/valve is not
|
||||
BYPASS // used by SprinklerValveOperator to ignore the instance checking pump status
|
||||
};
|
||||
|
||||
enum SprinklerTimerIndex : uint8_t {
|
||||
TIMER_SM = 0,
|
||||
TIMER_VALVE_SELECTION = 1,
|
||||
};
|
||||
|
||||
class Sprinkler; // this component
|
||||
class SprinklerControllerSwitch; // switches that appear in the front end; based on switch core
|
||||
class SprinklerSwitch; // switches representing any valve or pump; provides abstraction for latching valves
|
||||
class SprinklerValveOperator; // manages all switching on/off of valves and associated pumps
|
||||
class SprinklerValveRunRequest; // tells the sprinkler controller what valve to run and for how long as well as what
|
||||
// SprinklerValveOperator is handling it
|
||||
template<typename... Ts> class StartSingleValveAction;
|
||||
template<typename... Ts> class ShutdownAction;
|
||||
template<typename... Ts> class ResumeOrStartAction;
|
||||
|
||||
class SprinklerSwitch {
|
||||
public:
|
||||
SprinklerSwitch();
|
||||
SprinklerSwitch(switch_::Switch *sprinkler_switch);
|
||||
SprinklerSwitch(switch_::Switch *off_switch, switch_::Switch *on_switch, uint32_t pulse_duration);
|
||||
|
||||
bool is_latching_valve(); // returns true if configured as a latching valve
|
||||
void loop(); // called as a part of loop(), used for latching valve pulses
|
||||
uint32_t pulse_duration() { return this->pulse_duration_; }
|
||||
bool state(); // returns the switch's current state
|
||||
void set_off_switch(switch_::Switch *off_switch) { this->off_switch_ = off_switch; }
|
||||
void set_on_switch(switch_::Switch *on_switch) { this->on_switch_ = on_switch; }
|
||||
void set_pulse_duration(uint32_t pulse_duration) { this->pulse_duration_ = pulse_duration; }
|
||||
void sync_valve_state(
|
||||
bool latch_state); // syncs internal state to switch; if latching valve, sets state to latch_state
|
||||
void turn_off(); // sets internal flag and actuates the switch
|
||||
void turn_on(); // sets internal flag and actuates the switch
|
||||
switch_::Switch *off_switch() { return this->off_switch_; }
|
||||
switch_::Switch *on_switch() { return this->on_switch_; }
|
||||
|
||||
protected:
|
||||
bool state_{false};
|
||||
uint32_t pulse_duration_{0};
|
||||
uint64_t pinned_millis_{0};
|
||||
switch_::Switch *off_switch_{nullptr}; // only used for latching valves
|
||||
switch_::Switch *on_switch_{nullptr}; // used for both latching and non-latching valves
|
||||
};
|
||||
|
||||
struct SprinklerQueueItem {
|
||||
size_t valve_number;
|
||||
uint32_t run_duration;
|
||||
};
|
||||
|
||||
struct SprinklerTimer {
|
||||
const std::string name;
|
||||
bool active;
|
||||
uint32_t time;
|
||||
uint32_t start_time;
|
||||
std::function<void()> func;
|
||||
};
|
||||
|
||||
struct SprinklerValve {
|
||||
SprinklerControllerSwitch *controller_switch;
|
||||
SprinklerControllerSwitch *enable_switch;
|
||||
SprinklerSwitch valve_switch;
|
||||
uint32_t run_duration;
|
||||
optional<size_t> pump_switch_index;
|
||||
bool valve_cycle_complete;
|
||||
std::unique_ptr<ShutdownAction<>> valve_shutdown_action;
|
||||
std::unique_ptr<StartSingleValveAction<>> valve_resumeorstart_action;
|
||||
std::unique_ptr<Automation<>> valve_turn_off_automation;
|
||||
std::unique_ptr<Automation<>> valve_turn_on_automation;
|
||||
};
|
||||
|
||||
class SprinklerControllerSwitch : public switch_::Switch, public Component {
|
||||
public:
|
||||
SprinklerControllerSwitch();
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_state_lambda(std::function<optional<bool>()> &&f);
|
||||
void set_restore_state(bool restore_state);
|
||||
Trigger<> *get_turn_on_trigger() const;
|
||||
Trigger<> *get_turn_off_trigger() const;
|
||||
void set_optimistic(bool optimistic);
|
||||
void set_assumed_state(bool assumed_state);
|
||||
void loop() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
bool assumed_state() override;
|
||||
|
||||
void write_state(bool state) override;
|
||||
|
||||
optional<std::function<optional<bool>()>> f_;
|
||||
bool optimistic_{false};
|
||||
bool assumed_state_{false};
|
||||
Trigger<> *turn_on_trigger_;
|
||||
Trigger<> *turn_off_trigger_;
|
||||
Trigger<> *prev_trigger_{nullptr};
|
||||
bool restore_state_{false};
|
||||
};
|
||||
|
||||
class SprinklerValveOperator {
|
||||
public:
|
||||
SprinklerValveOperator();
|
||||
SprinklerValveOperator(SprinklerValve *valve, Sprinkler *controller);
|
||||
void loop();
|
||||
void set_controller(Sprinkler *controller);
|
||||
void set_valve(SprinklerValve *valve);
|
||||
void set_run_duration(uint32_t run_duration); // set the desired run duration in seconds
|
||||
void set_start_delay(uint32_t start_delay, bool start_delay_is_valve_delay);
|
||||
void set_stop_delay(uint32_t stop_delay, bool stop_delay_is_valve_delay);
|
||||
void start();
|
||||
void stop();
|
||||
uint32_t run_duration(); // returns the desired run duration in seconds
|
||||
uint32_t time_remaining(); // returns seconds remaining (does not include stop_delay_)
|
||||
SprinklerState state(); // returns the valve's state/status
|
||||
SprinklerSwitch *pump_switch(); // returns this SprinklerValveOperator's pump's SprinklerSwitch
|
||||
|
||||
protected:
|
||||
void pump_off_();
|
||||
void pump_on_();
|
||||
void valve_off_();
|
||||
void valve_on_();
|
||||
void kill_();
|
||||
void run_();
|
||||
bool start_delay_is_valve_delay_{false};
|
||||
bool stop_delay_is_valve_delay_{false};
|
||||
uint32_t start_delay_{0};
|
||||
uint32_t stop_delay_{0};
|
||||
uint32_t run_duration_{0};
|
||||
uint64_t pinned_millis_{0};
|
||||
Sprinkler *controller_{nullptr};
|
||||
SprinklerValve *valve_{nullptr};
|
||||
SprinklerState state_{IDLE};
|
||||
};
|
||||
|
||||
class SprinklerValveRunRequest {
|
||||
public:
|
||||
SprinklerValveRunRequest();
|
||||
SprinklerValveRunRequest(size_t valve_number, uint32_t run_duration, SprinklerValveOperator *valve_op);
|
||||
bool has_request();
|
||||
bool has_valve_operator();
|
||||
void set_run_duration(uint32_t run_duration);
|
||||
void set_valve(size_t valve_number);
|
||||
void set_valve_operator(SprinklerValveOperator *valve_op);
|
||||
void reset();
|
||||
uint32_t run_duration();
|
||||
size_t valve();
|
||||
optional<size_t> valve_as_opt();
|
||||
SprinklerValveOperator *valve_operator();
|
||||
|
||||
protected:
|
||||
bool has_valve_{false};
|
||||
size_t valve_number_{0};
|
||||
uint32_t run_duration_{0};
|
||||
SprinklerValveOperator *valve_op_{nullptr};
|
||||
};
|
||||
|
||||
class Sprinkler : public Component, public EntityBase {
|
||||
public:
|
||||
Sprinkler();
|
||||
Sprinkler(const std::string &name);
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
/// add a valve to the controller
|
||||
void add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControllerSwitch *enable_sw = nullptr);
|
||||
|
||||
/// add another controller to the controller so it can check if pumps/main valves are in use
|
||||
void add_controller(Sprinkler *other_controller);
|
||||
|
||||
/// configure important controller switches
|
||||
void set_controller_main_switch(SprinklerControllerSwitch *controller_switch);
|
||||
void set_controller_auto_adv_switch(SprinklerControllerSwitch *auto_adv_switch);
|
||||
void set_controller_queue_enable_switch(SprinklerControllerSwitch *queue_enable_switch);
|
||||
void set_controller_reverse_switch(SprinklerControllerSwitch *reverse_switch);
|
||||
|
||||
/// configure a valve's switch object and run duration. run_duration is time in seconds.
|
||||
void configure_valve_switch(size_t valve_number, switch_::Switch *valve_switch, uint32_t run_duration);
|
||||
void configure_valve_switch_pulsed(size_t valve_number, switch_::Switch *valve_switch_off,
|
||||
switch_::Switch *valve_switch_on, uint32_t pulse_duration, uint32_t run_duration);
|
||||
|
||||
/// configure a valve's associated pump switch object
|
||||
void configure_valve_pump_switch(size_t valve_number, switch_::Switch *pump_switch);
|
||||
void configure_valve_pump_switch_pulsed(size_t valve_number, switch_::Switch *pump_switch_off,
|
||||
switch_::Switch *pump_switch_on, uint32_t pulse_duration);
|
||||
|
||||
/// value multiplied by configured run times -- used to extend or shorten the cycle
|
||||
void set_multiplier(optional<float> multiplier);
|
||||
|
||||
/// set how long the pump should start after the valve (when the pump is starting)
|
||||
void set_pump_start_delay(uint32_t start_delay);
|
||||
|
||||
/// set how long the pump should stop after the valve (when the pump is starting)
|
||||
void set_pump_stop_delay(uint32_t stop_delay);
|
||||
|
||||
/// set how long the valve should start after the pump (when the pump is stopping)
|
||||
void set_valve_start_delay(uint32_t start_delay);
|
||||
|
||||
/// set how long the valve should stop after the pump (when the pump is stopping)
|
||||
void set_valve_stop_delay(uint32_t stop_delay);
|
||||
|
||||
/// if pump_switch_off_during_valve_open_delay is true, the controller will switch off the pump during the
|
||||
/// valve_open_delay interval
|
||||
void set_pump_switch_off_during_valve_open_delay(bool pump_switch_off_during_valve_open_delay);
|
||||
|
||||
/// set how long the controller should wait to open/switch on the valve after it becomes active
|
||||
void set_valve_open_delay(uint32_t valve_open_delay);
|
||||
|
||||
/// set how long the controller should wait after opening a valve before closing the previous valve
|
||||
void set_valve_overlap(uint32_t valve_overlap);
|
||||
|
||||
/// set how long the controller should wait to activate a valve after next_valve() or previous_valve() is called
|
||||
void set_manual_selection_delay(uint32_t manual_selection_delay);
|
||||
|
||||
/// set how long the valve should remain on/open. run_duration is time in seconds
|
||||
void set_valve_run_duration(optional<size_t> valve_number, optional<uint32_t> run_duration);
|
||||
|
||||
/// if auto_advance is true, controller will iterate through all enabled valves
|
||||
void set_auto_advance(bool auto_advance);
|
||||
|
||||
/// set the number of times to repeat a full cycle
|
||||
void set_repeat(optional<uint32_t> repeat);
|
||||
|
||||
/// if queue_enable is true, controller will iterate through valves in the queue
|
||||
void set_queue_enable(bool queue_enable);
|
||||
|
||||
/// if reverse is true, controller will iterate through all enabled valves in reverse (descending) order
|
||||
void set_reverse(bool reverse);
|
||||
|
||||
/// returns valve_number's run duration in seconds
|
||||
uint32_t valve_run_duration(size_t valve_number);
|
||||
|
||||
/// returns valve_number's run duration (in seconds) adjusted by multiplier_
|
||||
uint32_t valve_run_duration_adjusted(size_t valve_number);
|
||||
|
||||
/// returns true if auto_advance is enabled
|
||||
bool auto_advance();
|
||||
|
||||
/// returns the current value of the multiplier
|
||||
float multiplier();
|
||||
|
||||
/// returns the number of times the controller is set to repeat cycles, if at all. check with 'has_value()'
|
||||
optional<uint32_t> repeat();
|
||||
|
||||
/// if a cycle is active, returns the number of times the controller has repeated the cycle. check with 'has_value()'
|
||||
optional<uint32_t> repeat_count();
|
||||
|
||||
/// returns true if the queue is enabled to run
|
||||
bool queue_enabled();
|
||||
|
||||
/// returns true if reverse is enabled
|
||||
bool reverse();
|
||||
|
||||
/// starts the controller from the first valve in the queue and disables auto_advance.
|
||||
/// if the queue is empty, does nothing.
|
||||
void start_from_queue();
|
||||
|
||||
/// starts a full cycle of all enabled valves and enables auto_advance.
|
||||
/// if no valves are enabled, all valves will be enabled.
|
||||
void start_full_cycle();
|
||||
|
||||
/// activates a single valve and disables auto_advance.
|
||||
void start_single_valve(optional<size_t> valve_number);
|
||||
|
||||
/// adds a valve into the queue. queued valves have priority over valves to be run as a part of a full cycle.
|
||||
/// NOTE: queued valves will always run, regardless of auto-advance and/or valve enable switches.
|
||||
void queue_valve(optional<size_t> valve_number, optional<uint32_t> run_duration);
|
||||
|
||||
/// clears/removes all valves from the queue
|
||||
void clear_queued_valves();
|
||||
|
||||
/// advances to the next valve (numerically)
|
||||
void next_valve();
|
||||
|
||||
/// advances to the previous valve (numerically)
|
||||
void previous_valve();
|
||||
|
||||
/// turns off all valves, effectively shutting down the system.
|
||||
void shutdown(bool clear_queue = false);
|
||||
|
||||
/// same as shutdown(), but also stores active_valve() and time_remaining() allowing resume() to continue the cycle
|
||||
void pause();
|
||||
|
||||
/// resumes a cycle that was suspended using pause()
|
||||
void resume();
|
||||
|
||||
/// if a cycle was suspended using pause(), resumes it. otherwise calls start_full_cycle()
|
||||
void resume_or_start_full_cycle();
|
||||
|
||||
/// returns a pointer to a valve's name string object; returns nullptr if valve_number is invalid
|
||||
const char *valve_name(size_t valve_number);
|
||||
|
||||
/// returns the number of the valve that is currently active, if any. check with 'has_value()'
|
||||
optional<size_t> active_valve();
|
||||
|
||||
/// returns the number of the valve that is paused, if any. check with 'has_value()'
|
||||
optional<size_t> paused_valve();
|
||||
|
||||
/// returns the number of the next valve in the queue, if any. check with 'has_value()'
|
||||
optional<size_t> queued_valve();
|
||||
|
||||
/// returns the number of the valve that is manually selected, if any. check with 'has_value()'
|
||||
/// this is set by next_valve() and previous_valve() when manual_selection_delay_ > 0
|
||||
optional<size_t> manual_valve();
|
||||
|
||||
/// returns the number of valves the controller is configured with
|
||||
size_t number_of_valves();
|
||||
|
||||
/// returns true if valve number is valid
|
||||
bool is_a_valid_valve(size_t valve_number);
|
||||
|
||||
/// returns true if the pump the pointer points to is in use
|
||||
bool pump_in_use(SprinklerSwitch *pump_switch);
|
||||
|
||||
/// switches on/off a pump "safely" by checking that the new state will not conflict with another controller
|
||||
void set_pump_state(SprinklerSwitch *pump_switch, bool state);
|
||||
|
||||
/// returns the amount of time remaining in seconds for the active valve, if any. check with 'has_value()'
|
||||
optional<uint32_t> time_remaining();
|
||||
|
||||
/// returns a pointer to a valve's control switch object
|
||||
SprinklerControllerSwitch *control_switch(size_t valve_number);
|
||||
|
||||
/// returns a pointer to a valve's enable switch object
|
||||
SprinklerControllerSwitch *enable_switch(size_t valve_number);
|
||||
|
||||
/// returns a pointer to a valve's switch object
|
||||
SprinklerSwitch *valve_switch(size_t valve_number);
|
||||
|
||||
/// returns a pointer to a valve's pump switch object
|
||||
SprinklerSwitch *valve_pump_switch(size_t valve_number);
|
||||
|
||||
/// returns a pointer to a valve's pump switch object
|
||||
SprinklerSwitch *valve_pump_switch_by_pump_index(size_t pump_index);
|
||||
|
||||
protected:
|
||||
uint32_t hash_base() override;
|
||||
|
||||
/// returns true if valve number is enabled
|
||||
bool valve_is_enabled_(size_t valve_number);
|
||||
|
||||
/// marks a valve's cycle as complete
|
||||
void mark_valve_cycle_complete_(size_t valve_number);
|
||||
|
||||
/// returns true if valve's cycle is flagged as complete
|
||||
bool valve_cycle_complete_(size_t valve_number);
|
||||
|
||||
/// returns the number of the next/previous valve in the vector
|
||||
size_t next_valve_number_(size_t first_valve);
|
||||
size_t previous_valve_number_(size_t first_valve);
|
||||
|
||||
/// returns the number of the next valve that should be activated in a full cycle.
|
||||
/// if no valve is next (cycle is complete), returns no value (check with 'has_value()')
|
||||
optional<size_t> next_valve_number_in_cycle_(optional<size_t> first_valve = nullopt);
|
||||
|
||||
/// loads next_req_ with the next valve that should be activated, including its run duration.
|
||||
/// if next_req_ already contains a request, nothing is done. after next_req_,
|
||||
/// queued valves have priority, followed by enabled valves if auto-advance is enabled.
|
||||
/// if no valve is next (for example, a full cycle is complete), next_req_ is reset via reset().
|
||||
void load_next_valve_run_request_(optional<size_t> first_valve = nullopt);
|
||||
|
||||
/// returns the number of the next/previous valve that should be activated.
|
||||
/// if no valve is next (cycle is complete), returns no value (check with 'has_value()')
|
||||
optional<size_t> next_enabled_incomplete_valve_number_(optional<size_t> first_valve);
|
||||
optional<size_t> previous_enabled_incomplete_valve_number_(optional<size_t> first_valve);
|
||||
|
||||
/// returns true if any valve is enabled
|
||||
bool any_valve_is_enabled_();
|
||||
|
||||
/// loads an available SprinklerValveOperator (valve_op_) based on req and starts it (switches it on).
|
||||
/// NOTE: if run_duration is zero, the valve's run_duration will be set based on the valve's configuration.
|
||||
void start_valve_(SprinklerValveRunRequest *req);
|
||||
|
||||
/// turns off/closes all valves, including pump if include_pump is true
|
||||
void all_valves_off_(bool include_pump = false);
|
||||
|
||||
/// prepares for a full cycle by verifying auto-advance is on as well as one or more valve enable switches.
|
||||
void prep_full_cycle_();
|
||||
|
||||
/// resets the cycle state for all valves
|
||||
void reset_cycle_states_();
|
||||
|
||||
/// resets resume state
|
||||
void reset_resume_();
|
||||
|
||||
/// make a request of the state machine
|
||||
void fsm_request_(size_t requested_valve, uint32_t requested_run_duration = 0);
|
||||
|
||||
/// kicks the state machine to advance, starting it if it is not already active
|
||||
void fsm_kick_();
|
||||
|
||||
/// advance controller state, advancing to target_valve if provided
|
||||
void fsm_transition_();
|
||||
|
||||
/// starts up the system from IDLE state
|
||||
void fsm_transition_from_shutdown_();
|
||||
|
||||
/// transitions from ACTIVE state to ACTIVE (as in, next valve) or to a SHUTDOWN or IDLE state
|
||||
void fsm_transition_from_valve_run_();
|
||||
|
||||
/// starts up the system from IDLE state
|
||||
void fsm_transition_to_shutdown_();
|
||||
|
||||
/// return the current FSM state as a string
|
||||
std::string state_as_str_(SprinklerState state);
|
||||
|
||||
/// Start/cancel/get status of valve timers
|
||||
void start_timer_(SprinklerTimerIndex timer_index);
|
||||
bool cancel_timer_(SprinklerTimerIndex timer_index);
|
||||
/// returns true if the specified timer is active/running
|
||||
bool timer_active_(SprinklerTimerIndex timer_index);
|
||||
/// time is converted to milliseconds (ms) for set_timeout()
|
||||
void set_timer_duration_(SprinklerTimerIndex timer_index, uint32_t time);
|
||||
/// returns time in milliseconds (ms)
|
||||
uint32_t timer_duration_(SprinklerTimerIndex timer_index);
|
||||
std::function<void()> timer_cbf_(SprinklerTimerIndex timer_index);
|
||||
|
||||
/// callback functions for timers
|
||||
void valve_selection_callback_();
|
||||
void sm_timer_callback_();
|
||||
void pump_stop_delay_callback_();
|
||||
|
||||
/// Maximum allowed queue size
|
||||
const uint8_t max_queue_size_{100};
|
||||
|
||||
/// Pump should be off during valve_open_delay interval
|
||||
bool pump_switch_off_during_valve_open_delay_{false};
|
||||
|
||||
/// Sprinkler valve cycle should overlap
|
||||
bool valve_overlap_{false};
|
||||
|
||||
/// Pump start/stop delay interval types
|
||||
bool start_delay_is_valve_delay_{false};
|
||||
bool stop_delay_is_valve_delay_{false};
|
||||
|
||||
/// Pump start/stop delay intervals
|
||||
uint32_t start_delay_{0};
|
||||
uint32_t stop_delay_{0};
|
||||
|
||||
/// Sprinkler controller state
|
||||
SprinklerState state_{IDLE};
|
||||
|
||||
/// The valve run request that is currently active
|
||||
SprinklerValveRunRequest active_req_;
|
||||
|
||||
/// The number of the manually selected valve currently selected
|
||||
optional<size_t> manual_valve_;
|
||||
|
||||
/// The number of the valve to resume from (if paused)
|
||||
optional<size_t> paused_valve_;
|
||||
|
||||
/// The next run request for the controller to consume after active_req_ is complete
|
||||
SprinklerValveRunRequest next_req_;
|
||||
|
||||
/// Set the number of times to repeat a full cycle
|
||||
optional<uint32_t> target_repeats_;
|
||||
|
||||
/// Set from time_remaining() when paused
|
||||
optional<uint32_t> resume_duration_;
|
||||
|
||||
/// Manual switching delay
|
||||
optional<uint32_t> manual_selection_delay_;
|
||||
|
||||
/// Valve switching delay
|
||||
optional<uint32_t> switching_delay_;
|
||||
|
||||
/// Number of times the full cycle has been repeated
|
||||
uint32_t repeat_count_{0};
|
||||
|
||||
/// Sprinkler valve run time multiplier value
|
||||
float multiplier_{1.0};
|
||||
|
||||
/// Queue of valves to activate next, regardless of auto-advance
|
||||
std::vector<SprinklerQueueItem> queued_valves_;
|
||||
|
||||
/// Sprinkler valve pump objects
|
||||
std::vector<SprinklerSwitch> pump_;
|
||||
|
||||
/// Sprinkler valve objects
|
||||
std::vector<SprinklerValve> valve_;
|
||||
|
||||
/// Sprinkler valve operator objects
|
||||
std::vector<SprinklerValveOperator> valve_op_{2};
|
||||
|
||||
/// Valve control timers
|
||||
std::vector<SprinklerTimer> timer_{
|
||||
{this->name_ + "sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)},
|
||||
{this->name_ + "vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)}};
|
||||
|
||||
/// Other Sprinkler instances we should be aware of (used to check if pumps are in use)
|
||||
std::vector<Sprinkler *> other_controllers_;
|
||||
|
||||
/// Switches we'll present to the front end
|
||||
SprinklerControllerSwitch *auto_adv_sw_{nullptr};
|
||||
SprinklerControllerSwitch *controller_sw_{nullptr};
|
||||
SprinklerControllerSwitch *queue_enable_sw_{nullptr};
|
||||
SprinklerControllerSwitch *reverse_sw_{nullptr};
|
||||
|
||||
std::unique_ptr<ShutdownAction<>> sprinkler_shutdown_action_;
|
||||
std::unique_ptr<ResumeOrStartAction<>> sprinkler_resumeorstart_action_;
|
||||
|
||||
std::unique_ptr<Automation<>> sprinkler_turn_off_automation_;
|
||||
std::unique_ptr<Automation<>> sprinkler_turn_on_automation_;
|
||||
};
|
||||
|
||||
} // namespace sprinkler
|
||||
} // namespace esphome
|
||||
@ -1128,6 +1128,49 @@ climate:
|
||||
ki: 0.0
|
||||
kd: 0.0
|
||||
|
||||
sprinkler:
|
||||
- id: yard_sprinkler_ctrlr
|
||||
main_switch: "Yard Sprinklers"
|
||||
auto_advance_switch: "Yard Sprinklers Auto Advance"
|
||||
reverse_switch: "Yard Sprinklers Reverse"
|
||||
pump_start_pump_delay: 2s
|
||||
pump_stop_valve_delay: 4s
|
||||
pump_switch_off_during_valve_open_delay: true
|
||||
valve_open_delay: 5s
|
||||
valves:
|
||||
- valve_switch: "Yard Valve 0"
|
||||
enable_switch: "Enable Yard Valve 0"
|
||||
pump_switch_id: gpio_switch1
|
||||
run_duration: 10s
|
||||
valve_switch_id: gpio_switch2
|
||||
- valve_switch: "Yard Valve 1"
|
||||
enable_switch: "Enable Yard Valve 1"
|
||||
pump_switch_id: gpio_switch1
|
||||
run_duration: 10s
|
||||
valve_switch_id: gpio_switch2
|
||||
- valve_switch: "Yard Valve 2"
|
||||
enable_switch: "Enable Yard Valve 2"
|
||||
pump_switch_id: gpio_switch1
|
||||
run_duration: 10s
|
||||
valve_switch_id: gpio_switch2
|
||||
- id: garden_sprinkler_ctrlr
|
||||
main_switch: "Garden Sprinklers"
|
||||
auto_advance_switch: "Garden Sprinklers Auto Advance"
|
||||
reverse_switch: "Garden Sprinklers Reverse"
|
||||
valve_overlap: 5s
|
||||
valves:
|
||||
- valve_switch: "Garden Valve 0"
|
||||
enable_switch: "Enable Garden Valve 0"
|
||||
pump_switch_id: gpio_switch1
|
||||
run_duration: 10s
|
||||
valve_switch_id: gpio_switch2
|
||||
- valve_switch: "Garden Valve 1"
|
||||
enable_switch: "Enable Garden Valve 1"
|
||||
pump_switch_id: gpio_switch1
|
||||
run_duration: 10s
|
||||
valve_switch_id: gpio_switch2
|
||||
|
||||
|
||||
cover:
|
||||
- platform: endstop
|
||||
name: Endstop Cover
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user