Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 88 additions & 8 deletions custom_components/wattplan/flows/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
vol,
)

CONF_ACCEPT_MANUAL_SCHEDULING = "accept_manual_scheduling"


class WattPlanConfigFlow(_SharedSourceFlow, ConfigFlow, domain=DOMAIN):
"""Handle a config flow for WattPlan."""

Expand Down Expand Up @@ -472,6 +475,7 @@ class WattPlanOptionsFlow(_SharedSourceFlow, OptionsFlowWithReload):

_data: dict[str, Any]
_options: dict[str, Any]
_pending_timer_options: dict[str, Any] | None
_selected_subentry_id: str | None
_source_state: SourceFlowState

Expand All @@ -484,6 +488,7 @@ def __init__(self, config_entry: ConfigEntry) -> None:
self._options.setdefault(
CONF_OPTIMIZER_PROFILE, OPTIMIZER_PROFILE_BALANCED
)
self._pending_timer_options = None
self._selected_subentry_id = None
self._source_state = SourceFlowState()

Expand Down Expand Up @@ -545,23 +550,30 @@ async def async_step_planner_timers(
) -> ConfigFlowResult:
"""Configure timer behavior flags."""
if user_input is not None:
self._options.update(user_input)
self.hass.config_entries.async_update_entry(
self.config_entry, options=self._options
)
return await self.async_step_init()

self._pending_timer_options = dict(user_input)
if (
user_input[CONF_PLANNING_ENABLED]
and user_input[CONF_ACTION_EMISSION_ENABLED]
):
return await self._async_commit_planner_timers()
return await self._async_show_planner_timers_warning()

return self._async_show_planner_timers_form()

def _async_show_planner_timers_form(self) -> ConfigFlowResult:
"""Show the timer settings form."""
defaults = self._pending_timer_options or self._options
return self.async_show_form(
step_id="planner_timers",
data_schema=vol.Schema(
{
vol.Required(
CONF_PLANNING_ENABLED,
default=self._options[CONF_PLANNING_ENABLED],
default=defaults[CONF_PLANNING_ENABLED],
): selector.BooleanSelector(),
vol.Required(
CONF_ACTION_EMISSION_ENABLED,
default=self._options[CONF_ACTION_EMISSION_ENABLED],
default=defaults[CONF_ACTION_EMISSION_ENABLED],
): selector.BooleanSelector(),
}
),
Expand All @@ -570,6 +582,74 @@ async def async_step_planner_timers(
},
)

async def _async_commit_planner_timers(self) -> ConfigFlowResult:
"""Persist staged timer options and return to the options menu."""
if self._pending_timer_options is not None:
self._options.update(self._pending_timer_options)
self._pending_timer_options = None
self.hass.config_entries.async_update_entry(
self.config_entry, options=self._options
)
return await self.async_step_init()

async def _async_show_planner_timers_warning(self) -> ConfigFlowResult:
"""Show the warning step matching the staged timer settings."""
if self._pending_timer_options is None:
return await self.async_step_init()

return self.async_show_form(
step_id=self._planner_timers_warning_step_id(),
data_schema=vol.Schema(
{
vol.Required(
CONF_ACCEPT_MANUAL_SCHEDULING,
default=False,
): selector.BooleanSelector()
}
),
)

def _planner_timers_warning_step_id(self) -> str:
"""Return the warning step id for staged timer settings."""
assert self._pending_timer_options is not None
planning_enabled = self._pending_timer_options[CONF_PLANNING_ENABLED]
action_enabled = self._pending_timer_options[CONF_ACTION_EMISSION_ENABLED]
if not planning_enabled and not action_enabled:
return "planner_timers_warning_both"
if not planning_enabled:
return "planner_timers_warning_planning"
return "planner_timers_warning_action_emission"

async def _async_handle_planner_timers_warning(
self, user_input: dict[str, Any] | None
) -> ConfigFlowResult:
"""Handle manual scheduler acknowledgement."""
if self._pending_timer_options is None:
return await self.async_step_init()
if user_input is not None:
if user_input[CONF_ACCEPT_MANUAL_SCHEDULING]:
return await self._async_commit_planner_timers()
return self._async_show_planner_timers_form()
return await self._async_show_planner_timers_warning()

async def async_step_planner_timers_warning_planning(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Warn when scheduled planning is disabled."""
return await self._async_handle_planner_timers_warning(user_input)

async def async_step_planner_timers_warning_action_emission(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Warn when scheduled action emission is disabled."""
return await self._async_handle_planner_timers_warning(user_input)

async def async_step_planner_timers_warning_both(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Warn when all automatic scheduler behavior is disabled."""
return await self._async_handle_planner_timers_warning(user_input)

async def async_step_battery_entities(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
Expand Down
30 changes: 30 additions & 0 deletions custom_components/wattplan/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,36 @@
"action_emission_enabled": "When enabled, battery policy states like preserve/self_consume/grid_charge are published every {slot_minutes} minutes."
}
},
"planner_timers_warning_planning": {
"title": "Manual planning acknowledgement",
"description": "Scheduled planning is disabled.\n\nWattPlan will not create fresh plans automatically. Fresh plan generation is now user-managed, and you must call `wattplan.run_optimize_now` from your own automation or schedule when you want WattPlan to read sources and calculate a new plan.",
"data": {
"accept_manual_scheduling": "I understand that I must schedule `wattplan.run_optimize_now` myself"
},
"data_description": {
"accept_manual_scheduling": "Leave this unchecked to return to Scheduler settings and adjust these options."
}
},
"planner_timers_warning_action_emission": {
"title": "Manual action publishing acknowledgement",
"description": "Scheduled action emission is disabled.\n\nWattPlan will not publish the current slot's battery and load actions automatically. Current-slot action publishing is now user-managed, and you must call `wattplan.refresh_sensors` from your own automation or schedule when you want action and status entities refreshed from the current plan.",
"data": {
"accept_manual_scheduling": "I understand that I must schedule `wattplan.refresh_sensors` myself"
},
"data_description": {
"accept_manual_scheduling": "Leave this unchecked to return to Scheduler settings and adjust these options."
}
},
"planner_timers_warning_both": {
"title": "Manual cadence acknowledgement",
"description": "Scheduled planning and scheduled action emission are disabled.\n\nWattPlan has no automatic cadence. You must drive both `wattplan.run_optimize_now` and `wattplan.refresh_sensors` from your own automations or schedules if you want fresh plans and timely entity and status updates.",
"data": {
"accept_manual_scheduling": "I understand that I must schedule both WattPlan services myself"
},
"data_description": {
"accept_manual_scheduling": "Leave this unchecked to return to Scheduler settings and adjust these options."
}
},
"battery_entities": {
"title": "Battery entities",
"description": "Edit or remove configured batteries.",
Expand Down
30 changes: 30 additions & 0 deletions custom_components/wattplan/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,36 @@
"action_emission_enabled": "When enabled, battery policy states like preserve/self_consume/grid_charge are published every {slot_minutes} minutes."
}
},
"planner_timers_warning_planning": {
"title": "Manual planning acknowledgement",
"description": "Scheduled planning is disabled.\n\nWattPlan will not create fresh plans automatically. Fresh plan generation is now user-managed, and you must call `wattplan.run_optimize_now` from your own automation or schedule when you want WattPlan to read sources and calculate a new plan.",
"data": {
"accept_manual_scheduling": "I understand that I must schedule `wattplan.run_optimize_now` myself"
},
"data_description": {
"accept_manual_scheduling": "Leave this unchecked to return to Scheduler settings and adjust these options."
}
},
"planner_timers_warning_action_emission": {
"title": "Manual action publishing acknowledgement",
"description": "Scheduled action emission is disabled.\n\nWattPlan will not publish the current slot's battery and load actions automatically. Current-slot action publishing is now user-managed, and you must call `wattplan.refresh_sensors` from your own automation or schedule when you want action and status entities refreshed from the current plan.",
"data": {
"accept_manual_scheduling": "I understand that I must schedule `wattplan.refresh_sensors` myself"
},
"data_description": {
"accept_manual_scheduling": "Leave this unchecked to return to Scheduler settings and adjust these options."
}
},
"planner_timers_warning_both": {
"title": "Manual cadence acknowledgement",
"description": "Scheduled planning and scheduled action emission are disabled.\n\nWattPlan has no automatic cadence. You must drive both `wattplan.run_optimize_now` and `wattplan.refresh_sensors` from your own automations or schedules if you want fresh plans and timely entity and status updates.",
"data": {
"accept_manual_scheduling": "I understand that I must schedule both WattPlan services myself"
},
"data_description": {
"accept_manual_scheduling": "Leave this unchecked to return to Scheduler settings and adjust these options."
}
},
"battery_entities": {
"title": "Battery entities",
"description": "Edit or remove configured batteries.",
Expand Down
4 changes: 4 additions & 0 deletions docs/entities-and-services.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ WattPlan operates in two distinct steps:
1. **Optimize** (`run_optimize_now`) — runs the planner and calculates a new plan. This is the slow step that reads all energy sources and solves the optimization. Run it as often as you want fresh plans, but it can be infrequent if the optimizer is slow.
2. **Refresh** (`refresh_sensors`) — reads the already-calculated plan and pushes the current slot's actions to HA sensor entities. This is fast and can be called frequently to keep action sensors up to date without re-running the optimizer.

By default, WattPlan schedules both steps on the configured planner interval. If you disable scheduled planning in Scheduler settings, fresh plan generation becomes your responsibility and you should call `wattplan.run_optimize_now` from your own automation or schedule. If you disable scheduled action emission, current-slot action publishing becomes your responsibility and you should call `wattplan.refresh_sensors` from your own automation or schedule. If you disable both, WattPlan has no automatic cadence.

The current plan's `expires_at` remains the authoritative expiry timestamp. When automatic stages are disabled, WattPlan still uses that timestamp to decide whether a plan is fresh when it runs, but you own the trigger cadence for plan refreshes and entity/status updates.

WattPlan exposes the following services:

| Service | Purpose |
Expand Down
Loading
Loading