mirror of
https://github.com/esphome/esphome.git
synced 2024-12-25 23:14:54 +01:00
Sprinkler "v2" updates (#4159)
* Add standby switch * Add support for arbitrary run duration in start_single_valve action * Add divider feature * Allow zero multiplier * Fixes for #3740, misc. cleanup and polishing * Integrate number components for multiplier, repeat and run duration * Add various methods to get time remaining * Add next_prev_ignore_disabled flag * Optimize next/previous valve selection methods * Add numbers_use_minutes flag * Initialize switch states as they are set up * Ensure SprinklerControllerSwitch has state if it's not restored * Add repeat validation * Misc. clean-up and tweaking * Fix bugprone-integer-division * More clean-up * Set entity_category for standby_switch * Set default entity_category for numbers * More housekeeping * Add run request tracking * Fix time remaining calculation * Use native unit_of_measurement for run duration numbers * Unstack some ifs
This commit is contained in:
parent
38a01988a5
commit
98b3d294aa
4 changed files with 854 additions and 176 deletions
|
@ -2,23 +2,36 @@ import esphome.codegen as cg
|
|||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.components import number
|
||||
from esphome.components import switch
|
||||
from esphome.const import (
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_ID,
|
||||
CONF_INITIAL_VALUE,
|
||||
CONF_MAX_VALUE,
|
||||
CONF_MIN_VALUE,
|
||||
CONF_NAME,
|
||||
CONF_REPEAT,
|
||||
CONF_RESTORE_VALUE,
|
||||
CONF_RUN_DURATION,
|
||||
CONF_STEP,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
UNIT_MINUTE,
|
||||
UNIT_SECOND,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["switch"]
|
||||
AUTO_LOAD = ["number", "switch"]
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
|
||||
CONF_AUTO_ADVANCE_SWITCH = "auto_advance_switch"
|
||||
CONF_DIVIDER = "divider"
|
||||
CONF_ENABLE_SWITCH = "enable_switch"
|
||||
CONF_MAIN_SWITCH = "main_switch"
|
||||
CONF_MANUAL_SELECTION_DELAY = "manual_selection_delay"
|
||||
CONF_MULTIPLIER = "multiplier"
|
||||
CONF_MULTIPLIER_NUMBER = "multiplier_number"
|
||||
CONF_NEXT_PREV_IGNORE_DISABLED = "next_prev_ignore_disabled"
|
||||
CONF_PUMP_OFF_SWITCH_ID = "pump_off_switch_id"
|
||||
CONF_PUMP_ON_SWITCH_ID = "pump_on_switch_id"
|
||||
CONF_PUMP_PULSE_DURATION = "pump_pulse_duration"
|
||||
|
@ -30,7 +43,11 @@ CONF_PUMP_SWITCH = "pump_switch"
|
|||
CONF_PUMP_SWITCH_ID = "pump_switch_id"
|
||||
CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY = "pump_switch_off_during_valve_open_delay"
|
||||
CONF_QUEUE_ENABLE_SWITCH = "queue_enable_switch"
|
||||
CONF_REPEAT_NUMBER = "repeat_number"
|
||||
CONF_REVERSE_SWITCH = "reverse_switch"
|
||||
CONF_RUN_DURATION_NUMBER = "run_duration_number"
|
||||
CONF_SET_ACTION = "set_action"
|
||||
CONF_STANDBY_SWITCH = "standby_switch"
|
||||
CONF_VALVE_NUMBER = "valve_number"
|
||||
CONF_VALVE_OPEN_DELAY = "valve_open_delay"
|
||||
CONF_VALVE_OVERLAP = "valve_overlap"
|
||||
|
@ -43,10 +60,14 @@ CONF_VALVES = "valves"
|
|||
|
||||
sprinkler_ns = cg.esphome_ns.namespace("sprinkler")
|
||||
Sprinkler = sprinkler_ns.class_("Sprinkler", cg.Component)
|
||||
SprinklerControllerNumber = sprinkler_ns.class_(
|
||||
"SprinklerControllerNumber", number.Number, cg.Component
|
||||
)
|
||||
SprinklerControllerSwitch = sprinkler_ns.class_(
|
||||
"SprinklerControllerSwitch", switch.Switch, cg.Component
|
||||
)
|
||||
|
||||
SetDividerAction = sprinkler_ns.class_("SetDividerAction", automation.Action)
|
||||
SetMultiplierAction = sprinkler_ns.class_("SetMultiplierAction", automation.Action)
|
||||
QueueValveAction = sprinkler_ns.class_("QueueValveAction", automation.Action)
|
||||
ClearQueuedValvesAction = sprinkler_ns.class_(
|
||||
|
@ -67,6 +88,19 @@ ResumeAction = sprinkler_ns.class_("ResumeAction", automation.Action)
|
|||
ResumeOrStartAction = sprinkler_ns.class_("ResumeOrStartAction", automation.Action)
|
||||
|
||||
|
||||
def validate_min_max(config):
|
||||
if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]:
|
||||
raise cv.Invalid(f"{CONF_MAX_VALUE} must be greater than {CONF_MIN_VALUE}")
|
||||
|
||||
if (config[CONF_INITIAL_VALUE] > config[CONF_MAX_VALUE]) or (
|
||||
config[CONF_INITIAL_VALUE] < config[CONF_MIN_VALUE]
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_INITIAL_VALUE} must be a value between {CONF_MAX_VALUE} and {CONF_MIN_VALUE}"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def validate_sprinkler(config):
|
||||
for sprinkler_controller_index, sprinkler_controller in enumerate(config):
|
||||
if len(sprinkler_controller[CONF_VALVES]) <= 1:
|
||||
|
@ -104,9 +138,18 @@ def validate_sprinkler(config):
|
|||
f"{CONF_VALVE_OPEN_DELAY} must be defined when {CONF_PUMP_SWITCH_OFF_DURING_VALVE_OPEN_DELAY} is enabled"
|
||||
)
|
||||
|
||||
if (
|
||||
CONF_REPEAT in sprinkler_controller
|
||||
and CONF_REPEAT_NUMBER in sprinkler_controller
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"Do not specify {CONF_REPEAT} when using {CONF_REPEAT_NUMBER}; use number component's {CONF_INITIAL_VALUE} instead"
|
||||
)
|
||||
|
||||
for valve in sprinkler_controller[CONF_VALVES]:
|
||||
if (
|
||||
CONF_VALVE_OVERLAP in sprinkler_controller
|
||||
and CONF_RUN_DURATION in valve
|
||||
and valve[CONF_RUN_DURATION] <= sprinkler_controller[CONF_VALVE_OVERLAP]
|
||||
):
|
||||
raise cv.Invalid(
|
||||
|
@ -114,6 +157,7 @@ def validate_sprinkler(config):
|
|||
)
|
||||
if (
|
||||
CONF_VALVE_OPEN_DELAY in sprinkler_controller
|
||||
and CONF_RUN_DURATION in valve
|
||||
and valve[CONF_RUN_DURATION]
|
||||
<= sprinkler_controller[CONF_VALVE_OPEN_DELAY]
|
||||
):
|
||||
|
@ -170,6 +214,14 @@ def validate_sprinkler(config):
|
|||
raise cv.Invalid(
|
||||
f"Either {CONF_VALVE_SWITCH_ID} or {CONF_VALVE_OFF_SWITCH_ID} and {CONF_VALVE_ON_SWITCH_ID} must be specified in valve configuration"
|
||||
)
|
||||
if CONF_RUN_DURATION not in valve and CONF_RUN_DURATION_NUMBER not in valve:
|
||||
raise cv.Invalid(
|
||||
f"Either {CONF_RUN_DURATION} or {CONF_RUN_DURATION_NUMBER} must be specified for each valve"
|
||||
)
|
||||
if CONF_RUN_DURATION in valve and CONF_RUN_DURATION_NUMBER in valve:
|
||||
raise cv.Invalid(
|
||||
f"Do not specify {CONF_RUN_DURATION} when using {CONF_RUN_DURATION_NUMBER}; use number component's {CONF_INITIAL_VALUE} instead"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
|
@ -190,11 +242,20 @@ SPRINKLER_ACTION_REPEAT_SCHEMA = cv.maybe_simple_value(
|
|||
SPRINKLER_ACTION_SINGLE_VALVE_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(Sprinkler),
|
||||
cv.Optional(CONF_RUN_DURATION): cv.templatable(cv.positive_time_period_seconds),
|
||||
cv.Required(CONF_VALVE_NUMBER): cv.templatable(cv.positive_int),
|
||||
},
|
||||
key=CONF_VALVE_NUMBER,
|
||||
)
|
||||
|
||||
SPRINKLER_ACTION_SET_DIVIDER_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(Sprinkler),
|
||||
cv.Required(CONF_DIVIDER): cv.templatable(cv.positive_int),
|
||||
},
|
||||
key=CONF_DIVIDER,
|
||||
)
|
||||
|
||||
SPRINKLER_ACTION_SET_MULTIPLIER_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(Sprinkler),
|
||||
|
@ -232,7 +293,30 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema(
|
|||
cv.Optional(CONF_PUMP_OFF_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
cv.Optional(CONF_PUMP_ON_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
cv.Optional(CONF_PUMP_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
cv.Required(CONF_RUN_DURATION): cv.positive_time_period_seconds,
|
||||
cv.Optional(CONF_RUN_DURATION): cv.positive_time_period_seconds,
|
||||
cv.Optional(CONF_RUN_DURATION_NUMBER): cv.maybe_simple_value(
|
||||
number.NUMBER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SprinklerControllerNumber),
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG
|
||||
): cv.entity_category,
|
||||
cv.Optional(CONF_INITIAL_VALUE, default=900): cv.positive_int,
|
||||
cv.Optional(CONF_MAX_VALUE, default=86400): cv.positive_int,
|
||||
cv.Optional(CONF_MIN_VALUE, default=1): cv.positive_int,
|
||||
cv.Optional(CONF_RESTORE_VALUE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_STEP, default=1): cv.positive_int,
|
||||
cv.Optional(CONF_SET_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_UNIT_OF_MEASUREMENT, default=UNIT_SECOND
|
||||
): cv.one_of(UNIT_MINUTE, UNIT_SECOND, lower="True"),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate_min_max,
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Required(CONF_VALVE_SWITCH): cv.maybe_simple_value(
|
||||
switch.switch_schema(SprinklerControllerSwitch),
|
||||
key=CONF_NAME,
|
||||
|
@ -268,8 +352,55 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema(
|
|||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_STANDBY_SWITCH): cv.maybe_simple_value(
|
||||
switch.switch_schema(
|
||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_NEXT_PREV_IGNORE_DISABLED, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MANUAL_SELECTION_DELAY): cv.positive_time_period_seconds,
|
||||
cv.Optional(CONF_MULTIPLIER_NUMBER): cv.maybe_simple_value(
|
||||
number.NUMBER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SprinklerControllerNumber),
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG
|
||||
): cv.entity_category,
|
||||
cv.Optional(CONF_INITIAL_VALUE, default=1): cv.positive_float,
|
||||
cv.Optional(CONF_MAX_VALUE, default=10): cv.positive_float,
|
||||
cv.Optional(CONF_MIN_VALUE, default=0): cv.positive_float,
|
||||
cv.Optional(CONF_RESTORE_VALUE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_STEP, default=0.1): cv.positive_float,
|
||||
cv.Optional(CONF_SET_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate_min_max,
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_REPEAT): cv.positive_int,
|
||||
cv.Optional(CONF_REPEAT_NUMBER): cv.maybe_simple_value(
|
||||
number.NUMBER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SprinklerControllerNumber),
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG
|
||||
): cv.entity_category,
|
||||
cv.Optional(CONF_INITIAL_VALUE, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_MAX_VALUE, default=10): cv.positive_int,
|
||||
cv.Optional(CONF_MIN_VALUE, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_RESTORE_VALUE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_STEP, default=1): cv.positive_int,
|
||||
cv.Optional(CONF_SET_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate_min_max,
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_PUMP_PULSE_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_VALVE_PULSE_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Exclusive(
|
||||
|
@ -301,6 +432,19 @@ CONFIG_SCHEMA = cv.All(
|
|||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"sprinkler.set_divider",
|
||||
SetDividerAction,
|
||||
SPRINKLER_ACTION_SET_DIVIDER_SCHEMA,
|
||||
)
|
||||
async def sprinkler_set_divider_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_DIVIDER], args, cg.float_)
|
||||
cg.add(var.set_divider(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"sprinkler.set_multiplier",
|
||||
SetMultiplierAction,
|
||||
|
@ -385,6 +529,9 @@ async def sprinkler_start_single_valve_to_code(config, action_id, template_arg,
|
|||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_VALVE_NUMBER], args, cg.uint8)
|
||||
cg.add(var.set_valve_to_start(template_))
|
||||
if CONF_RUN_DURATION in config:
|
||||
template_ = await cg.templatable(config[CONF_RUN_DURATION], args, cg.uint32)
|
||||
cg.add(var.set_valve_run_duration(template_))
|
||||
return var
|
||||
|
||||
|
||||
|
@ -455,6 +602,79 @@ async def to_code(config):
|
|||
)
|
||||
cg.add(var.set_controller_reverse_switch(sw_rev_var))
|
||||
|
||||
if CONF_STANDBY_SWITCH in sprinkler_controller:
|
||||
sw_stb_var = await switch.new_switch(
|
||||
sprinkler_controller[CONF_STANDBY_SWITCH]
|
||||
)
|
||||
await cg.register_component(
|
||||
sw_stb_var, sprinkler_controller[CONF_STANDBY_SWITCH]
|
||||
)
|
||||
cg.add(var.set_controller_standby_switch(sw_stb_var))
|
||||
|
||||
if CONF_MULTIPLIER_NUMBER in sprinkler_controller:
|
||||
num_mult_var = await number.new_number(
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER],
|
||||
min_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][
|
||||
CONF_MIN_VALUE
|
||||
],
|
||||
max_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][
|
||||
CONF_MAX_VALUE
|
||||
],
|
||||
step=sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_STEP],
|
||||
)
|
||||
await cg.register_component(
|
||||
num_mult_var, sprinkler_controller[CONF_MULTIPLIER_NUMBER]
|
||||
)
|
||||
cg.add(
|
||||
num_mult_var.set_initial_value(
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_INITIAL_VALUE]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
num_mult_var.set_restore_value(
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_RESTORE_VALUE]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_SET_ACTION in sprinkler_controller[CONF_MULTIPLIER_NUMBER]:
|
||||
await automation.build_automation(
|
||||
num_mult_var.get_set_trigger(),
|
||||
[(float, "x")],
|
||||
sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_SET_ACTION],
|
||||
)
|
||||
|
||||
cg.add(var.set_controller_multiplier_number(num_mult_var))
|
||||
|
||||
if CONF_REPEAT_NUMBER in sprinkler_controller:
|
||||
num_repeat_var = await number.new_number(
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER],
|
||||
min_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MIN_VALUE],
|
||||
max_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MAX_VALUE],
|
||||
step=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_STEP],
|
||||
)
|
||||
await cg.register_component(
|
||||
num_repeat_var, sprinkler_controller[CONF_REPEAT_NUMBER]
|
||||
)
|
||||
cg.add(
|
||||
num_repeat_var.set_initial_value(
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER][CONF_INITIAL_VALUE]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
num_repeat_var.set_restore_value(
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER][CONF_RESTORE_VALUE]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_SET_ACTION in sprinkler_controller[CONF_REPEAT_NUMBER]:
|
||||
await automation.build_automation(
|
||||
num_repeat_var.get_set_trigger(),
|
||||
[(float, "x")],
|
||||
sprinkler_controller[CONF_REPEAT_NUMBER][CONF_SET_ACTION],
|
||||
)
|
||||
|
||||
cg.add(var.set_controller_repeat_number(num_repeat_var))
|
||||
|
||||
for valve in sprinkler_controller[CONF_VALVES]:
|
||||
sw_valve_var = await switch.new_switch(valve[CONF_VALVE_SWITCH])
|
||||
await cg.register_component(sw_valve_var, valve[CONF_VALVE_SWITCH])
|
||||
|
@ -470,6 +690,12 @@ async def to_code(config):
|
|||
else:
|
||||
cg.add(var.add_valve(sw_valve_var))
|
||||
|
||||
cg.add(
|
||||
var.set_next_prev_ignore_disabled_valves(
|
||||
sprinkler_controller[CONF_NEXT_PREV_IGNORE_DISABLED]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_MANUAL_SELECTION_DELAY in sprinkler_controller:
|
||||
cg.add(
|
||||
var.set_manual_selection_delay(
|
||||
|
@ -524,6 +750,11 @@ async def to_code(config):
|
|||
for sprinkler_controller in config:
|
||||
var = await cg.get_variable(sprinkler_controller[CONF_ID])
|
||||
for valve_index, valve in enumerate(sprinkler_controller[CONF_VALVES]):
|
||||
if CONF_RUN_DURATION not in valve:
|
||||
valve[CONF_RUN_DURATION] = valve[CONF_RUN_DURATION_NUMBER][
|
||||
CONF_INITIAL_VALUE
|
||||
]
|
||||
|
||||
if CONF_VALVE_SWITCH_ID in valve:
|
||||
valve_switch = await cg.get_variable(valve[CONF_VALVE_SWITCH_ID])
|
||||
cg.add(
|
||||
|
@ -561,6 +792,35 @@ async def to_code(config):
|
|||
)
|
||||
)
|
||||
|
||||
if CONF_RUN_DURATION_NUMBER in valve:
|
||||
num_rd_var = await number.new_number(
|
||||
valve[CONF_RUN_DURATION_NUMBER],
|
||||
min_value=valve[CONF_RUN_DURATION_NUMBER][CONF_MIN_VALUE],
|
||||
max_value=valve[CONF_RUN_DURATION_NUMBER][CONF_MAX_VALUE],
|
||||
step=valve[CONF_RUN_DURATION_NUMBER][CONF_STEP],
|
||||
)
|
||||
await cg.register_component(num_rd_var, valve[CONF_RUN_DURATION_NUMBER])
|
||||
|
||||
cg.add(
|
||||
num_rd_var.set_initial_value(
|
||||
valve[CONF_RUN_DURATION_NUMBER][CONF_INITIAL_VALUE]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
num_rd_var.set_restore_value(
|
||||
valve[CONF_RUN_DURATION_NUMBER][CONF_RESTORE_VALUE]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_SET_ACTION in valve[CONF_RUN_DURATION_NUMBER]:
|
||||
await automation.build_automation(
|
||||
num_rd_var.get_set_trigger(),
|
||||
[(float, "x")],
|
||||
valve[CONF_RUN_DURATION_NUMBER][CONF_SET_ACTION],
|
||||
)
|
||||
|
||||
cg.add(var.configure_valve_run_duration_number(valve_index, num_rd_var))
|
||||
|
||||
for sprinkler_controller in config:
|
||||
var = await cg.get_variable(sprinkler_controller[CONF_ID])
|
||||
for controller_to_add in config:
|
||||
|
|
|
@ -7,6 +7,18 @@
|
|||
namespace esphome {
|
||||
namespace sprinkler {
|
||||
|
||||
template<typename... Ts> class SetDividerAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetDividerAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
TEMPLATABLE_VALUE(uint32_t, divider)
|
||||
|
||||
void play(Ts... x) override { this->sprinkler_->set_divider(this->divider_.optional_value(x...)); }
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetMultiplierAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetMultiplierAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
@ -98,8 +110,12 @@ template<typename... Ts> class StartSingleValveAction : public Action<Ts...> {
|
|||
explicit StartSingleValveAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||
|
||||
TEMPLATABLE_VALUE(size_t, valve_to_start)
|
||||
TEMPLATABLE_VALUE(uint32_t, valve_run_duration)
|
||||
|
||||
void play(Ts... x) override { this->sprinkler_->start_single_valve(this->valve_to_start_.optional_value(x...)); }
|
||||
void play(Ts... x) override {
|
||||
this->sprinkler_->start_single_valve(this->valve_to_start_.optional_value(x...),
|
||||
this->valve_run_duration_.optional_value(x...));
|
||||
}
|
||||
|
||||
protected:
|
||||
Sprinkler *sprinkler_;
|
||||
|
|
|
@ -75,6 +75,34 @@ void SprinklerSwitch::sync_valve_state(bool latch_state) {
|
|||
}
|
||||
}
|
||||
|
||||
void SprinklerControllerNumber::setup() {
|
||||
float value;
|
||||
if (!this->restore_value_) {
|
||||
value = this->initial_value_;
|
||||
} else {
|
||||
this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
|
||||
if (!this->pref_.load(&value)) {
|
||||
if (!std::isnan(this->initial_value_)) {
|
||||
value = this->initial_value_;
|
||||
} else {
|
||||
value = this->traits.get_min_value();
|
||||
}
|
||||
}
|
||||
}
|
||||
this->publish_state(value);
|
||||
}
|
||||
|
||||
void SprinklerControllerNumber::control(float value) {
|
||||
this->set_trigger_->trigger(value);
|
||||
|
||||
this->publish_state(value);
|
||||
|
||||
if (this->restore_value_)
|
||||
this->pref_.save(&value);
|
||||
}
|
||||
|
||||
void SprinklerControllerNumber::dump_config() { LOG_NUMBER("", "Sprinkler Controller Number", this); }
|
||||
|
||||
SprinklerControllerSwitch::SprinklerControllerSwitch()
|
||||
: turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
|
||||
|
||||
|
@ -101,12 +129,9 @@ void SprinklerControllerSwitch::write_state(bool state) {
|
|||
this->turn_off_trigger_->trigger();
|
||||
}
|
||||
|
||||
if (this->optimistic_)
|
||||
this->publish_state(state);
|
||||
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; }
|
||||
|
||||
|
@ -114,30 +139,16 @@ Trigger<> *SprinklerControllerSwitch::get_turn_on_trigger() const { return this-
|
|||
Trigger<> *SprinklerControllerSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; }
|
||||
|
||||
void SprinklerControllerSwitch::setup() {
|
||||
if (!this->restore_state_)
|
||||
return;
|
||||
this->state = this->get_initial_state_with_restore_mode().value_or(false);
|
||||
|
||||
auto restored = this->get_initial_state();
|
||||
if (!restored.has_value())
|
||||
return;
|
||||
|
||||
ESP_LOGD(TAG, " Restored state %s", ONOFF(*restored));
|
||||
if (*restored) {
|
||||
if (this->state) {
|
||||
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; }
|
||||
void SprinklerControllerSwitch::dump_config() { LOG_SWITCH("", "Sprinkler Switch", this); }
|
||||
|
||||
SprinklerValveOperator::SprinklerValveOperator() {}
|
||||
SprinklerValveOperator::SprinklerValveOperator(SprinklerValve *valve, Sprinkler *controller)
|
||||
|
@ -328,6 +339,8 @@ SprinklerValveRunRequest::SprinklerValveRunRequest(size_t valve_number, uint32_t
|
|||
bool SprinklerValveRunRequest::has_request() { return this->has_valve_; }
|
||||
bool SprinklerValveRunRequest::has_valve_operator() { return !(this->valve_op_ == nullptr); }
|
||||
|
||||
void SprinklerValveRunRequest::set_request_from(SprinklerValveRunRequestOrigin origin) { this->origin_ = origin; }
|
||||
|
||||
void SprinklerValveRunRequest::set_run_duration(uint32_t run_duration) { this->run_duration_ = run_duration; }
|
||||
|
||||
void SprinklerValveRunRequest::set_valve(size_t valve_number) {
|
||||
|
@ -345,6 +358,7 @@ void SprinklerValveRunRequest::set_valve_operator(SprinklerValveOperator *valve_
|
|||
|
||||
void SprinklerValveRunRequest::reset() {
|
||||
this->has_valve_ = false;
|
||||
this->origin_ = USER;
|
||||
this->run_duration_ = 0;
|
||||
this->valve_op_ = nullptr;
|
||||
}
|
||||
|
@ -362,6 +376,8 @@ optional<size_t> SprinklerValveRunRequest::valve_as_opt() {
|
|||
|
||||
SprinklerValveOperator *SprinklerValveRunRequest::valve_operator() { return this->valve_op_; }
|
||||
|
||||
SprinklerValveRunRequestOrigin SprinklerValveRunRequest::request_is_from() { return this->origin_; }
|
||||
|
||||
Sprinkler::Sprinkler() {}
|
||||
Sprinkler::Sprinkler(const std::string &name) : EntityBase(name) {}
|
||||
|
||||
|
@ -404,8 +420,6 @@ void Sprinkler::add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControll
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -433,26 +447,37 @@ void Sprinkler::set_controller_main_switch(SprinklerControllerSwitch *controller
|
|||
|
||||
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::set_controller_standby_switch(SprinklerControllerSwitch *standby_switch) {
|
||||
this->standby_sw_ = standby_switch;
|
||||
|
||||
this->sprinkler_standby_turn_on_automation_ = make_unique<Automation<>>(standby_switch->get_turn_on_trigger());
|
||||
this->sprinkler_standby_shutdown_action_ = make_unique<sprinkler::ShutdownAction<>>(this);
|
||||
this->sprinkler_standby_turn_on_automation_->add_actions({sprinkler_standby_shutdown_action_.get()});
|
||||
}
|
||||
|
||||
void Sprinkler::set_controller_multiplier_number(SprinklerControllerNumber *multiplier_number) {
|
||||
this->multiplier_number_ = multiplier_number;
|
||||
}
|
||||
|
||||
void Sprinkler::set_controller_repeat_number(SprinklerControllerNumber *repeat_number) {
|
||||
this->repeat_number_ = repeat_number;
|
||||
}
|
||||
|
||||
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;
|
||||
valve_switch->turn_off();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -464,6 +489,8 @@ void Sprinkler::configure_valve_switch_pulsed(size_t valve_number, switch_::Swit
|
|||
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;
|
||||
valve_switch_off->turn_off();
|
||||
valve_switch_on->turn_off();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -478,6 +505,7 @@ void Sprinkler::configure_valve_pump_switch(size_t valve_number, switch_::Switch
|
|||
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
|
||||
pump_switch->turn_off();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -496,15 +524,49 @@ void Sprinkler::configure_valve_pump_switch_pulsed(size_t valve_number, switch_:
|
|||
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
|
||||
pump_switch_off->turn_off();
|
||||
pump_switch_on->turn_off();
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::configure_valve_run_duration_number(size_t valve_number,
|
||||
SprinklerControllerNumber *run_duration_number) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
this->valve_[valve_number].run_duration_number = run_duration_number;
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::set_divider(optional<uint32_t> divider) {
|
||||
if (!divider.has_value()) {
|
||||
return;
|
||||
}
|
||||
if (divider.value() > 0) {
|
||||
this->set_multiplier(1.0 / divider.value());
|
||||
this->set_repeat(divider.value() - 1);
|
||||
} else if (divider.value() == 0) {
|
||||
this->set_multiplier(1.0);
|
||||
this->set_repeat(0);
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::set_multiplier(const optional<float> multiplier) {
|
||||
if (multiplier.has_value()) {
|
||||
if (multiplier.value() > 0) {
|
||||
this->multiplier_ = multiplier.value();
|
||||
}
|
||||
if ((!multiplier.has_value()) || (multiplier.value() < 0)) {
|
||||
return;
|
||||
}
|
||||
this->multiplier_ = multiplier.value();
|
||||
if (this->multiplier_number_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (this->multiplier_number_->state == multiplier.value()) {
|
||||
return;
|
||||
}
|
||||
auto call = this->multiplier_number_->make_call();
|
||||
call.set_value(multiplier.value());
|
||||
call.perform();
|
||||
}
|
||||
|
||||
void Sprinkler::set_next_prev_ignore_disabled_valves(bool ignore_disabled) {
|
||||
this->next_prev_ignore_disabled_ = ignore_disabled;
|
||||
}
|
||||
|
||||
void Sprinkler::set_pump_start_delay(uint32_t start_delay) {
|
||||
|
@ -559,47 +621,118 @@ void Sprinkler::set_manual_selection_delay(uint32_t manual_selection_delay) {
|
|||
}
|
||||
|
||||
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();
|
||||
}
|
||||
if (!valve_number.has_value() || !run_duration.has_value()) {
|
||||
return;
|
||||
}
|
||||
if (!this->is_a_valid_valve(valve_number.value())) {
|
||||
return;
|
||||
}
|
||||
this->valve_[valve_number.value()].run_duration = run_duration.value();
|
||||
if (this->valve_[valve_number.value()].run_duration_number == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (this->valve_[valve_number.value()].run_duration_number->state == run_duration.value()) {
|
||||
return;
|
||||
}
|
||||
auto call = this->valve_[valve_number.value()].run_duration_number->make_call();
|
||||
if (this->valve_[valve_number.value()].run_duration_number->traits.get_unit_of_measurement() == min_str) {
|
||||
call.set_value(run_duration.value() / 60.0);
|
||||
} else {
|
||||
call.set_value(run_duration.value());
|
||||
}
|
||||
call.perform();
|
||||
}
|
||||
|
||||
void Sprinkler::set_auto_advance(const bool auto_advance) {
|
||||
if (this->auto_adv_sw_ != nullptr) {
|
||||
this->auto_adv_sw_->publish_state(auto_advance);
|
||||
if (this->auto_adv_sw_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (this->auto_adv_sw_->state == auto_advance) {
|
||||
return;
|
||||
}
|
||||
if (auto_advance) {
|
||||
this->auto_adv_sw_->turn_on();
|
||||
} else {
|
||||
this->auto_adv_sw_->turn_off();
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::set_repeat(optional<uint32_t> repeat) { this->target_repeats_ = repeat; }
|
||||
void Sprinkler::set_repeat(optional<uint32_t> repeat) {
|
||||
this->target_repeats_ = repeat;
|
||||
if (this->repeat_number_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (this->repeat_number_->state == repeat.value()) {
|
||||
return;
|
||||
}
|
||||
auto call = this->repeat_number_->make_call();
|
||||
call.set_value(repeat.value_or(0));
|
||||
call.perform();
|
||||
}
|
||||
|
||||
void Sprinkler::set_queue_enable(bool queue_enable) {
|
||||
if (this->queue_enable_sw_ != nullptr) {
|
||||
this->queue_enable_sw_->publish_state(queue_enable);
|
||||
if (this->queue_enable_sw_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (this->queue_enable_sw_->state == queue_enable) {
|
||||
return;
|
||||
}
|
||||
if (queue_enable) {
|
||||
this->queue_enable_sw_->turn_on();
|
||||
} else {
|
||||
this->queue_enable_sw_->turn_off();
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::set_reverse(const bool reverse) {
|
||||
if (this->reverse_sw_ != nullptr) {
|
||||
this->reverse_sw_->publish_state(reverse);
|
||||
if (this->reverse_sw_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (this->reverse_sw_->state == reverse) {
|
||||
return;
|
||||
}
|
||||
if (reverse) {
|
||||
this->reverse_sw_->turn_on();
|
||||
} else {
|
||||
this->reverse_sw_->turn_off();
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::set_standby(const bool standby) {
|
||||
if (this->standby_sw_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (this->standby_sw_->state == standby) {
|
||||
return;
|
||||
}
|
||||
if (standby) {
|
||||
this->standby_sw_->turn_on();
|
||||
} else {
|
||||
this->standby_sw_->turn_off();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
if (!this->is_a_valid_valve(valve_number)) {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
if (this->valve_[valve_number].run_duration_number != nullptr) {
|
||||
if (this->valve_[valve_number].run_duration_number->traits.get_unit_of_measurement() == min_str) {
|
||||
return static_cast<uint32_t>(roundf(this->valve_[valve_number].run_duration_number->state * 60));
|
||||
} else {
|
||||
return static_cast<uint32_t>(roundf(this->valve_[valve_number].run_duration_number->state));
|
||||
}
|
||||
}
|
||||
return this->valve_[valve_number].run_duration;
|
||||
}
|
||||
|
||||
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 = this->valve_run_duration(valve_number);
|
||||
}
|
||||
run_duration = static_cast<uint32_t>(roundf(run_duration * this->multiplier_));
|
||||
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)) {
|
||||
|
@ -615,16 +748,24 @@ bool Sprinkler::auto_advance() {
|
|||
return false;
|
||||
}
|
||||
|
||||
float Sprinkler::multiplier() { return this->multiplier_; }
|
||||
float Sprinkler::multiplier() {
|
||||
if (this->multiplier_number_ != nullptr) {
|
||||
return this->multiplier_number_->state;
|
||||
}
|
||||
return this->multiplier_;
|
||||
}
|
||||
|
||||
optional<uint32_t> Sprinkler::repeat() { return this->target_repeats_; }
|
||||
optional<uint32_t> Sprinkler::repeat() {
|
||||
if (this->repeat_number_ != nullptr) {
|
||||
return static_cast<uint32_t>(roundf(this->repeat_number_->state));
|
||||
}
|
||||
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_;
|
||||
}
|
||||
if (this->active_req_.has_request() && this->auto_advance()) {
|
||||
return this->repeat_count_;
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
@ -643,7 +784,22 @@ bool Sprinkler::reverse() {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Sprinkler::standby() {
|
||||
if (this->standby_sw_ != nullptr) {
|
||||
return this->standby_sw_->state;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Sprinkler::start_from_queue() {
|
||||
if (this->standby()) {
|
||||
ESP_LOGD(TAG, "start_from_queue called but standby is enabled; no action taken");
|
||||
return;
|
||||
}
|
||||
if (this->multiplier() == 0) {
|
||||
ESP_LOGD(TAG, "start_from_queue called but multiplier is set to zero; no action taken");
|
||||
return;
|
||||
}
|
||||
if (this->queued_valves_.empty()) {
|
||||
return; // if there is nothing in the queue, don't do anything
|
||||
}
|
||||
|
@ -651,25 +807,29 @@ void Sprinkler::start_from_queue() {
|
|||
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->set_auto_advance(false);
|
||||
this->set_queue_enable(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->standby()) {
|
||||
ESP_LOGD(TAG, "start_full_cycle called but standby is enabled; no action taken");
|
||||
return;
|
||||
}
|
||||
if (this->multiplier() == 0) {
|
||||
ESP_LOGD(TAG, "start_full_cycle called but multiplier is set to zero; no action taken");
|
||||
return;
|
||||
}
|
||||
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->set_queue_enable(false);
|
||||
|
||||
this->prep_full_cycle_();
|
||||
this->repeat_count_ = 0;
|
||||
// if there is no active valve already, start the first valve in the cycle
|
||||
|
@ -678,20 +838,25 @@ void Sprinkler::start_full_cycle() {
|
|||
}
|
||||
}
|
||||
|
||||
void Sprinkler::start_single_valve(const optional<size_t> valve_number) {
|
||||
void Sprinkler::start_single_valve(const optional<size_t> valve_number, optional<uint32_t> run_duration) {
|
||||
if (this->standby()) {
|
||||
ESP_LOGD(TAG, "start_single_valve called but standby is enabled; no action taken");
|
||||
return;
|
||||
}
|
||||
if (this->multiplier() == 0) {
|
||||
ESP_LOGD(TAG, "start_single_valve called but multiplier is set to zero; no action taken");
|
||||
return;
|
||||
}
|
||||
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->set_auto_advance(false);
|
||||
this->set_queue_enable(false);
|
||||
|
||||
this->reset_cycle_states_(); // just in case auto-advance is switched on later
|
||||
this->repeat_count_ = 0;
|
||||
this->fsm_request_(valve_number.value());
|
||||
this->fsm_request_(valve_number.value(), run_duration.value_or(0));
|
||||
}
|
||||
|
||||
void Sprinkler::queue_valve(optional<size_t> valve_number, optional<uint32_t> run_duration) {
|
||||
|
@ -714,8 +879,17 @@ 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)));
|
||||
this->manual_valve_.value_or(this->active_req_.valve_as_opt().value_or(this->number_of_valves() - 1)),
|
||||
!this->next_prev_ignore_disabled_, true);
|
||||
|
||||
if (!this->manual_valve_.has_value()) {
|
||||
ESP_LOGD(TAG, "next_valve was called but no valve could be started; perhaps next_prev_ignore_disabled allows only "
|
||||
"enabled valves and no valves are enabled?");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -728,8 +902,17 @@ 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)));
|
||||
this->previous_valve_number_(this->manual_valve_.value_or(this->active_req_.valve_as_opt().value_or(0)),
|
||||
!this->next_prev_ignore_disabled_, true);
|
||||
|
||||
if (!this->manual_valve_.has_value()) {
|
||||
ESP_LOGD(TAG, "previous_valve was called but no valve could be started; perhaps next_prev_ignore_disabled allows "
|
||||
"only enabled valves and no valves are enabled?");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -758,7 +941,7 @@ void Sprinkler::pause() {
|
|||
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->resume_duration_ = this->time_remaining_active_valve();
|
||||
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));
|
||||
|
@ -795,6 +978,13 @@ const char *Sprinkler::valve_name(const size_t valve_number) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
optional<SprinklerValveRunRequestOrigin> Sprinkler::active_valve_request_is_from() {
|
||||
if (this->active_req_.has_request()) {
|
||||
return this->active_req_.request_is_from();
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<size_t> Sprinkler::active_valve() { return this->active_req_.valve_as_opt(); }
|
||||
optional<size_t> Sprinkler::paused_valve() { return this->paused_valve_; }
|
||||
|
||||
|
@ -829,8 +1019,7 @@ bool Sprinkler::pump_in_use(SprinklerSwitch *pump_switch) {
|
|||
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
|
||||
// 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_)) {
|
||||
|
@ -881,7 +1070,93 @@ void Sprinkler::set_pump_state(SprinklerSwitch *pump_switch, bool state) {
|
|||
}
|
||||
}
|
||||
|
||||
optional<uint32_t> Sprinkler::time_remaining() {
|
||||
uint32_t Sprinkler::total_cycle_time_all_valves() {
|
||||
uint32_t total_time_remaining = 0;
|
||||
|
||||
for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
|
||||
total_time_remaining += this->valve_run_duration_adjusted(valve);
|
||||
}
|
||||
|
||||
if (this->valve_overlap_) {
|
||||
total_time_remaining -= this->switching_delay_.value_or(0) * (this->number_of_valves() - 1);
|
||||
} else {
|
||||
total_time_remaining += this->switching_delay_.value_or(0) * (this->number_of_valves() - 1);
|
||||
}
|
||||
|
||||
return total_time_remaining;
|
||||
}
|
||||
|
||||
uint32_t Sprinkler::total_cycle_time_enabled_valves() {
|
||||
uint32_t total_time_remaining = 0;
|
||||
uint32_t valve_count = 0;
|
||||
|
||||
for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
|
||||
if (this->valve_is_enabled_(valve)) {
|
||||
total_time_remaining += this->valve_run_duration_adjusted(valve);
|
||||
valve_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (valve_count) {
|
||||
if (this->valve_overlap_) {
|
||||
total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
|
||||
} else {
|
||||
total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return total_time_remaining;
|
||||
}
|
||||
|
||||
uint32_t Sprinkler::total_cycle_time_enabled_incomplete_valves() {
|
||||
uint32_t total_time_remaining = 0;
|
||||
uint32_t valve_count = 0;
|
||||
|
||||
for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
|
||||
if (this->valve_is_enabled_(valve) && !this->valve_cycle_complete_(valve)) {
|
||||
if (!this->active_valve().has_value() || (valve != this->active_valve().value())) {
|
||||
total_time_remaining += this->valve_run_duration_adjusted(valve);
|
||||
valve_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (valve_count) {
|
||||
if (this->valve_overlap_) {
|
||||
total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
|
||||
} else {
|
||||
total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return total_time_remaining;
|
||||
}
|
||||
|
||||
uint32_t Sprinkler::total_queue_time() {
|
||||
uint32_t total_time_remaining = 0;
|
||||
uint32_t valve_count = 0;
|
||||
|
||||
for (auto &valve : this->queued_valves_) {
|
||||
if (valve.run_duration) {
|
||||
total_time_remaining += valve.run_duration;
|
||||
} else {
|
||||
total_time_remaining += this->valve_run_duration_adjusted(valve.valve_number);
|
||||
}
|
||||
valve_count++;
|
||||
}
|
||||
|
||||
if (valve_count) {
|
||||
if (this->valve_overlap_) {
|
||||
total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
|
||||
} else {
|
||||
total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return total_time_remaining;
|
||||
}
|
||||
|
||||
optional<uint32_t> Sprinkler::time_remaining_active_valve() {
|
||||
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();
|
||||
|
@ -895,6 +1170,25 @@ optional<uint32_t> Sprinkler::time_remaining() {
|
|||
return nullopt;
|
||||
}
|
||||
|
||||
optional<uint32_t> Sprinkler::time_remaining_current_operation() {
|
||||
auto total_time_remaining = this->time_remaining_active_valve();
|
||||
|
||||
if (total_time_remaining.has_value()) {
|
||||
if (this->auto_advance()) {
|
||||
total_time_remaining = total_time_remaining.value() + this->total_cycle_time_enabled_incomplete_valves();
|
||||
total_time_remaining =
|
||||
total_time_remaining.value() +
|
||||
(this->total_cycle_time_enabled_valves() * (this->repeat().value_or(0) - this->repeat_count().value_or(0)));
|
||||
}
|
||||
|
||||
if (this->queue_enabled()) {
|
||||
total_time_remaining = total_time_remaining.value() + this->total_queue_time();
|
||||
}
|
||||
return total_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;
|
||||
|
@ -957,30 +1251,60 @@ bool Sprinkler::valve_cycle_complete_(const size_t valve_number) {
|
|||
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;
|
||||
optional<size_t> Sprinkler::next_valve_number_(const optional<size_t> first_valve, const bool include_disabled,
|
||||
const bool include_complete) {
|
||||
auto valve = first_valve.value_or(0);
|
||||
size_t start = first_valve.has_value() ? 1 : 0;
|
||||
|
||||
return 0;
|
||||
if (!this->is_a_valid_valve(valve)) {
|
||||
valve = 0;
|
||||
}
|
||||
|
||||
for (size_t offset = start; offset < this->number_of_valves(); offset++) {
|
||||
auto valve_of_interest = valve + offset;
|
||||
if (!this->is_a_valid_valve(valve_of_interest)) {
|
||||
valve_of_interest -= this->number_of_valves();
|
||||
}
|
||||
|
||||
if ((this->valve_is_enabled_(valve_of_interest) || include_disabled) &&
|
||||
(!this->valve_cycle_complete_(valve_of_interest) || include_complete)) {
|
||||
return valve_of_interest;
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
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;
|
||||
optional<size_t> Sprinkler::previous_valve_number_(const optional<size_t> first_valve, const bool include_disabled,
|
||||
const bool include_complete) {
|
||||
auto valve = first_valve.value_or(this->number_of_valves() - 1);
|
||||
size_t start = first_valve.has_value() ? 1 : 0;
|
||||
|
||||
return this->number_of_valves() - 1;
|
||||
if (!this->is_a_valid_valve(valve)) {
|
||||
valve = this->number_of_valves() - 1;
|
||||
}
|
||||
|
||||
for (size_t offset = start; offset < this->number_of_valves(); offset++) {
|
||||
auto valve_of_interest = valve - offset;
|
||||
if (!this->is_a_valid_valve(valve_of_interest)) {
|
||||
valve_of_interest += this->number_of_valves();
|
||||
}
|
||||
|
||||
if ((this->valve_is_enabled_(valve_of_interest) || include_disabled) &&
|
||||
(!this->valve_cycle_complete_(valve_of_interest) || include_complete)) {
|
||||
return valve_of_interest;
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
if (this->reverse()) {
|
||||
return this->previous_valve_number_(first_valve, false, false);
|
||||
}
|
||||
return this->next_enabled_incomplete_valve_number_(first_valve);
|
||||
return this->next_valve_number_(first_valve, false, false);
|
||||
}
|
||||
|
||||
void Sprinkler::load_next_valve_run_request_(optional<size_t> first_valve) {
|
||||
void Sprinkler::load_next_valve_run_request_(const 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()));
|
||||
|
@ -988,58 +1312,37 @@ void Sprinkler::load_next_valve_run_request_(optional<size_t> first_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);
|
||||
this->next_req_.set_request_from(QUEUE);
|
||||
if (this->queued_valves_.back().run_duration) {
|
||||
this->next_req_.set_run_duration(this->queued_valves_.back().run_duration);
|
||||
} else {
|
||||
this->queued_valves_.pop_back();
|
||||
} else if (this->multiplier()) {
|
||||
this->next_req_.set_run_duration(this->valve_run_duration_adjusted(this->queued_valves_.back().valve_number));
|
||||
this->queued_valves_.pop_back();
|
||||
} else {
|
||||
this->next_req_.reset();
|
||||
}
|
||||
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));
|
||||
} else if (this->auto_advance() && this->multiplier()) {
|
||||
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_request_from(CYCLE);
|
||||
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->repeat().value_or(0))) {
|
||||
ESP_LOGD(TAG, "Repeating - starting cycle %u of %u", this->repeat_count_ + 1, this->repeat().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_();
|
||||
if (this->next_valve_number_in_cycle_().has_value()) { // this should always succeed here, but just in case...
|
||||
this->next_req_.set_valve(this->next_valve_number_in_cycle_().value_or(0));
|
||||
this->next_req_.set_request_from(CYCLE);
|
||||
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)));
|
||||
this->valve_run_duration_adjusted(this->next_valve_number_in_cycle_().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))
|
||||
|
@ -1058,8 +1361,9 @@ void Sprinkler::start_valve_(SprinklerValveRunRequest *req) {
|
|||
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);
|
||||
ESP_LOGD(TAG, "%s is starting valve %u for %u seconds, cycle %u of %u",
|
||||
this->req_as_str_(req->request_is_from()).c_str(), req->valve(), run_duration, this->repeat_count_ + 1,
|
||||
this->repeat().value_or(0) + 1);
|
||||
req->set_valve_operator(&vo);
|
||||
vo.set_controller(this);
|
||||
vo.set_valve(&this->valve_[req->valve()]);
|
||||
|
@ -1085,15 +1389,14 @@ void Sprinkler::all_valves_off_(const bool include_pump) {
|
|||
}
|
||||
|
||||
void Sprinkler::prep_full_cycle_() {
|
||||
if (this->auto_adv_sw_ != nullptr) {
|
||||
if (!this->auto_adv_sw_->state) {
|
||||
this->auto_adv_sw_->publish_state(true);
|
||||
}
|
||||
}
|
||||
this->set_auto_advance(true);
|
||||
|
||||
if (!this->any_valve_is_enabled_()) {
|
||||
for (auto &valve : this->valve_) {
|
||||
if (valve.enable_switch != nullptr) {
|
||||
valve.enable_switch->publish_state(true);
|
||||
if (!valve.enable_switch->state) {
|
||||
valve.enable_switch->turn_on();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1169,14 +1472,19 @@ void Sprinkler::fsm_transition_() {
|
|||
|
||||
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;
|
||||
if (this->next_req_.has_request()) { // there is a valve to run...
|
||||
this->active_req_.set_valve(this->next_req_.valve());
|
||||
this->active_req_.set_request_from(this->next_req_.request_is_from());
|
||||
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_() {
|
||||
|
@ -1186,7 +1494,9 @@ void Sprinkler::fsm_transition_from_valve_run_() {
|
|||
}
|
||||
|
||||
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());
|
||||
if ((this->active_req_.request_is_from() == CYCLE) || (this->active_req_.request_is_from() == USER)) {
|
||||
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_) {
|
||||
|
@ -1201,6 +1511,7 @@ void Sprinkler::fsm_transition_from_valve_run_() {
|
|||
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_request_from(this->next_req_.request_is_from());
|
||||
this->active_req_.set_run_duration(this->next_req_.run_duration());
|
||||
this->next_req_.reset();
|
||||
|
||||
|
@ -1230,6 +1541,22 @@ void Sprinkler::fsm_transition_to_shutdown_() {
|
|||
this->start_timer_(sprinkler::TIMER_SM);
|
||||
}
|
||||
|
||||
std::string Sprinkler::req_as_str_(SprinklerValveRunRequestOrigin origin) {
|
||||
switch (origin) {
|
||||
case USER:
|
||||
return "USER";
|
||||
|
||||
case CYCLE:
|
||||
return "CYCLE";
|
||||
|
||||
case QUEUE:
|
||||
return "QUEUE";
|
||||
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
std::string Sprinkler::state_as_str_(SprinklerState state) {
|
||||
switch (state) {
|
||||
case IDLE:
|
||||
|
@ -1300,8 +1627,8 @@ void Sprinkler::dump_config() {
|
|||
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->repeat().has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Repeat Cycles: %u times", this->repeat().value_or(0));
|
||||
}
|
||||
if (this->start_delay_) {
|
||||
if (this->start_delay_is_valve_delay_) {
|
||||
|
@ -1329,7 +1656,7 @@ void Sprinkler::dump_config() {
|
|||
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);
|
||||
ESP_LOGCONFIG(TAG, " Run Duration: %u seconds", this->valve_run_duration(valve_number));
|
||||
if (this->valve_[valve_number].valve_switch.pulse_duration()) {
|
||||
ESP_LOGCONFIG(TAG, " Pulse Duration: %u milliseconds",
|
||||
this->valve_[valve_number].valve_switch.pulse_duration());
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "esphome/components/switch/switch.h"
|
||||
|
||||
#include <vector>
|
||||
|
@ -10,6 +11,8 @@
|
|||
namespace esphome {
|
||||
namespace sprinkler {
|
||||
|
||||
const std::string min_str = "min";
|
||||
|
||||
enum SprinklerState : uint8_t {
|
||||
// NOTE: these states are used by both SprinklerValveOperator and Sprinkler (the controller)!
|
||||
IDLE, // system/valve is off
|
||||
|
@ -24,7 +27,14 @@ enum SprinklerTimerIndex : uint8_t {
|
|||
TIMER_VALVE_SELECTION = 1,
|
||||
};
|
||||
|
||||
enum SprinklerValveRunRequestOrigin : uint8_t {
|
||||
USER,
|
||||
CYCLE,
|
||||
QUEUE,
|
||||
};
|
||||
|
||||
class Sprinkler; // this component
|
||||
class SprinklerControllerNumber; // number components that appear in the front end; based on number core
|
||||
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
|
||||
|
@ -76,6 +86,7 @@ struct SprinklerTimer {
|
|||
};
|
||||
|
||||
struct SprinklerValve {
|
||||
SprinklerControllerNumber *run_duration_number;
|
||||
SprinklerControllerSwitch *controller_switch;
|
||||
SprinklerControllerSwitch *enable_switch;
|
||||
SprinklerSwitch valve_switch;
|
||||
|
@ -88,6 +99,25 @@ struct SprinklerValve {
|
|||
std::unique_ptr<Automation<>> valve_turn_on_automation;
|
||||
};
|
||||
|
||||
class SprinklerControllerNumber : public number::Number, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
Trigger<float> *get_set_trigger() const { return set_trigger_; }
|
||||
void set_initial_value(float initial_value) { initial_value_ = initial_value; }
|
||||
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
|
||||
|
||||
protected:
|
||||
void control(float value) override;
|
||||
float initial_value_{NAN};
|
||||
bool restore_value_{true};
|
||||
Trigger<float> *set_trigger_ = new Trigger<float>();
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
class SprinklerControllerSwitch : public switch_::Switch, public Component {
|
||||
public:
|
||||
SprinklerControllerSwitch();
|
||||
|
@ -96,27 +126,19 @@ class SprinklerControllerSwitch : public switch_::Switch, public Component {
|
|||
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 {
|
||||
|
@ -160,6 +182,7 @@ class SprinklerValveRunRequest {
|
|||
SprinklerValveRunRequest(size_t valve_number, uint32_t run_duration, SprinklerValveOperator *valve_op);
|
||||
bool has_request();
|
||||
bool has_valve_operator();
|
||||
void set_request_from(SprinklerValveRunRequestOrigin origin);
|
||||
void set_run_duration(uint32_t run_duration);
|
||||
void set_valve(size_t valve_number);
|
||||
void set_valve_operator(SprinklerValveOperator *valve_op);
|
||||
|
@ -168,12 +191,14 @@ class SprinklerValveRunRequest {
|
|||
size_t valve();
|
||||
optional<size_t> valve_as_opt();
|
||||
SprinklerValveOperator *valve_operator();
|
||||
SprinklerValveRunRequestOrigin request_is_from();
|
||||
|
||||
protected:
|
||||
bool has_valve_{false};
|
||||
size_t valve_number_{0};
|
||||
uint32_t run_duration_{0};
|
||||
SprinklerValveOperator *valve_op_{nullptr};
|
||||
SprinklerValveRunRequestOrigin origin_{USER};
|
||||
};
|
||||
|
||||
class Sprinkler : public Component, public EntityBase {
|
||||
|
@ -196,6 +221,11 @@ class Sprinkler : public Component, public EntityBase {
|
|||
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);
|
||||
void set_controller_standby_switch(SprinklerControllerSwitch *standby_switch);
|
||||
|
||||
/// configure important controller number components
|
||||
void set_controller_multiplier_number(SprinklerControllerNumber *multiplier_number);
|
||||
void set_controller_repeat_number(SprinklerControllerNumber *repeat_number);
|
||||
|
||||
/// 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);
|
||||
|
@ -207,9 +237,18 @@ class Sprinkler : public Component, public EntityBase {
|
|||
void configure_valve_pump_switch_pulsed(size_t valve_number, switch_::Switch *pump_switch_off,
|
||||
switch_::Switch *pump_switch_on, uint32_t pulse_duration);
|
||||
|
||||
/// configure a valve's run duration number component
|
||||
void configure_valve_run_duration_number(size_t valve_number, SprinklerControllerNumber *run_duration_number);
|
||||
|
||||
/// sets the multiplier value to '1 / divider' and sets repeat value to divider
|
||||
void set_divider(optional<uint32_t> divider);
|
||||
|
||||
/// value multiplied by configured run times -- used to extend or shorten the cycle
|
||||
void set_multiplier(optional<float> multiplier);
|
||||
|
||||
/// enable/disable skipping of disabled valves by the next and previous actions
|
||||
void set_next_prev_ignore_disabled_valves(bool ignore_disabled);
|
||||
|
||||
/// set how long the pump should start after the valve (when the pump is starting)
|
||||
void set_pump_start_delay(uint32_t start_delay);
|
||||
|
||||
|
@ -250,6 +289,9 @@ class Sprinkler : public Component, public EntityBase {
|
|||
/// if reverse is true, controller will iterate through all enabled valves in reverse (descending) order
|
||||
void set_reverse(bool reverse);
|
||||
|
||||
/// if standby is true, controller will refuse to activate any valves
|
||||
void set_standby(bool standby);
|
||||
|
||||
/// returns valve_number's run duration in seconds
|
||||
uint32_t valve_run_duration(size_t valve_number);
|
||||
|
||||
|
@ -274,6 +316,9 @@ class Sprinkler : public Component, public EntityBase {
|
|||
/// returns true if reverse is enabled
|
||||
bool reverse();
|
||||
|
||||
/// returns true if standby is enabled
|
||||
bool standby();
|
||||
|
||||
/// 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();
|
||||
|
@ -283,7 +328,7 @@ class Sprinkler : public Component, public EntityBase {
|
|||
void start_full_cycle();
|
||||
|
||||
/// activates a single valve and disables auto_advance.
|
||||
void start_single_valve(optional<size_t> valve_number);
|
||||
void start_single_valve(optional<size_t> valve_number, optional<uint32_t> run_duration = nullopt);
|
||||
|
||||
/// 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.
|
||||
|
@ -316,6 +361,9 @@ class Sprinkler : public Component, public EntityBase {
|
|||
/// 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 what invoked the valve that is currently active, if any. check with 'has_value()'
|
||||
optional<SprinklerValveRunRequestOrigin> active_valve_request_is_from();
|
||||
|
||||
/// returns the number of the valve that is currently active, if any. check with 'has_value()'
|
||||
optional<size_t> active_valve();
|
||||
|
||||
|
@ -341,8 +389,23 @@ class Sprinkler : public Component, public EntityBase {
|
|||
/// 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 the amount of time in seconds required for all valves
|
||||
uint32_t total_cycle_time_all_valves();
|
||||
|
||||
/// returns the amount of time in seconds required for all enabled valves
|
||||
uint32_t total_cycle_time_enabled_valves();
|
||||
|
||||
/// returns the amount of time in seconds required for all enabled & incomplete valves, not including the active valve
|
||||
uint32_t total_cycle_time_enabled_incomplete_valves();
|
||||
|
||||
/// returns the amount of time in seconds required for all valves in the queue
|
||||
uint32_t total_queue_time();
|
||||
|
||||
/// returns the amount of time remaining in seconds for the active valve, if any
|
||||
optional<uint32_t> time_remaining_active_valve();
|
||||
|
||||
/// returns the amount of time remaining in seconds for all valves remaining, including the active valve, if any
|
||||
optional<uint32_t> time_remaining_current_operation();
|
||||
|
||||
/// returns a pointer to a valve's control switch object
|
||||
SprinklerControllerSwitch *control_switch(size_t valve_number);
|
||||
|
@ -371,9 +434,13 @@ class Sprinkler : public Component, public EntityBase {
|
|||
/// 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 in the vector or nullopt if no valves match criteria
|
||||
optional<size_t> next_valve_number_(optional<size_t> first_valve = nullopt, bool include_disabled = true,
|
||||
bool include_complete = true);
|
||||
|
||||
/// returns the number of the previous valve in the vector or nullopt if no valves match criteria
|
||||
optional<size_t> previous_valve_number_(optional<size_t> first_valve = nullopt, bool include_disabled = true,
|
||||
bool include_complete = true);
|
||||
|
||||
/// 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()')
|
||||
|
@ -385,11 +452,6 @@ class Sprinkler : public Component, public EntityBase {
|
|||
/// 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_();
|
||||
|
||||
|
@ -424,7 +486,10 @@ class Sprinkler : public Component, public EntityBase {
|
|||
/// starts up the system from IDLE state
|
||||
void fsm_transition_to_shutdown_();
|
||||
|
||||
/// return the current FSM state as a string
|
||||
/// return the specified SprinklerValveRunRequestOrigin as a string
|
||||
std::string req_as_str_(SprinklerValveRunRequestOrigin origin);
|
||||
|
||||
/// return the specified SprinklerState state as a string
|
||||
std::string state_as_str_(SprinklerState state);
|
||||
|
||||
/// Start/cancel/get status of valve timers
|
||||
|
@ -446,6 +511,9 @@ class Sprinkler : public Component, public EntityBase {
|
|||
/// Maximum allowed queue size
|
||||
const uint8_t max_queue_size_{100};
|
||||
|
||||
/// When set to true, the next and previous actions will skip disabled valves
|
||||
bool next_prev_ignore_disabled_{false};
|
||||
|
||||
/// Pump should be off during valve_open_delay interval
|
||||
bool pump_switch_off_during_valve_open_delay_{false};
|
||||
|
||||
|
@ -518,12 +586,19 @@ class Sprinkler : public Component, public EntityBase {
|
|||
SprinklerControllerSwitch *controller_sw_{nullptr};
|
||||
SprinklerControllerSwitch *queue_enable_sw_{nullptr};
|
||||
SprinklerControllerSwitch *reverse_sw_{nullptr};
|
||||
SprinklerControllerSwitch *standby_sw_{nullptr};
|
||||
|
||||
/// Number components we'll present to the front end
|
||||
SprinklerControllerNumber *multiplier_number_{nullptr};
|
||||
SprinklerControllerNumber *repeat_number_{nullptr};
|
||||
|
||||
std::unique_ptr<ShutdownAction<>> sprinkler_shutdown_action_;
|
||||
std::unique_ptr<ShutdownAction<>> sprinkler_standby_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_;
|
||||
std::unique_ptr<Automation<>> sprinkler_standby_turn_on_automation_;
|
||||
};
|
||||
|
||||
} // namespace sprinkler
|
||||
|
|
Loading…
Reference in a new issue