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
6 changes: 6 additions & 0 deletions _docs/blueprints.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,9 @@ This blueprint will provide the data source for Octopus Energy rates as provided
!!! warning

This automation will run when any of the underlying entities update. This make take a while initially. If you want the data available immediately, then you'll need to run the automation manually.

### Weather

[Install blueprint](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fbottlecapdave.github.io%2FHomeAssistant-TargetTimeframes%2Fblueprints%2Ftarget_timeframes_weather.yaml) | [Source](./blueprints/target_timeframes_weather.yaml)

This blueprint will provide the data source based on the forecast of the provide weather sensor and will use the specified `Forecast attribute` as the value for the calculation of the target timeframes. The forecast will be refreshed every 30 minutes.
78 changes: 78 additions & 0 deletions _docs/blueprints/target_timeframes_weather.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
blueprint:
name: Target Timeframes - Weather source
description: Configures a target timeframe data source from Weather
domain: automation
author: BottlecapDave
input:
target_timeframe_data_source_sensor:
name: Target timeframe data source sensor
description: The data source sensor which represents the data source to update
selector:
entity:
filter:
- domain:
- sensor
integration: target_timeframes
multiple: false
weather_sensor:
name: Weather sensor
description: The weather sensor to get the forecast for.
selector:
entity:
filter:
- domain:
- weather
multiple: false
forecast_attribute:
name: Forecast attribute
description: Type in the name of the desired forecast attribute to use as the value (e.g. "precipitation", "uv_index" or "temperature"). See the metadata of the result for possible options
default: "condition"
selector:
text:
variables:
target_timeframe_data_source_sensor: !input target_timeframe_data_source_sensor
weather_sensor: !input weather_sensor
forecast_attribute: !input forecast_attribute
millisecond_jitter: >
{{ range(1, 1000) | random }}
mode: queued
max: 4
triggers:
- trigger: time_pattern
minutes: '/30'
condition: []
action:
# Add a bit of jitter so the API isn't hit at once
- delay:
milliseconds: >
{{ millisecond_jitter }}
- action: weather.get_forecasts
target:
entity_id: !input weather_sensor
data:
type: hourly
response_variable: weather_forecast
- variables:
data_source_data: >
{%- set forecast_items = weather_forecast[weather_sensor]["forecast"] -%}
{%- set data = namespace(items=[]) -%}
{%- for forecast in forecast_items -%}
{%- set start = forecast["datetime"] | as_timestamp | timestamp_utc -%}
{%- set end = ((start | as_datetime) + timedelta(minutes=30)) | as_timestamp | timestamp_utc -%}
{%- set value = forecast[forecast_attribute] | float -%}
{%- set new_item = [{ 'start': start , 'end': end, 'value': value, 'metadata': forecast }] -%}

{%- set data.items = data.items + new_item -%}

{%- set start = end -%}
{%- set end = ((start | as_datetime) + timedelta(minutes=30)) | as_timestamp | timestamp_utc -%}
{%- set new_item = [{ 'start': start , 'end': end, 'value': value, 'metadata': forecast }] -%}

{%- set data.items = data.items + new_item -%}
{%- endfor -%}
{{ data.items }}
- action: target_timeframes.update_target_timeframe_data_source
data: >
{{ { 'data': data_source_data } }}
target:
entity_id: !input target_timeframe_data_source_sensor
22 changes: 20 additions & 2 deletions _docs/setup/target_timeframe.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,27 @@ This will only evaluate target times if no target times have been calculated or

For example, lets say we have a continuous target which looks between `00:00` and `08:00` has existing target times from `2023-01-02T01:00` to `2023-01-02T02:00`.

* If the current time is `2023-01-02T00:59`, then the target times will be re-evaluated and might change if the target period (i.e. `2023-01-02T00:30` to `2023-01-02T08:30`) has better values than the existing target times (e.g. the external weightings have changed).
* If the current time is `2023-01-02T00:59`, then the target times will be re-evaluated and might change if the target period (i.e. `2023-01-02T00:30` to `2023-01-02T08:00`) has better values than the existing target times (e.g. the external weightings have changed).
* If the current time is `2023-01-02T01:00`, the the target times will not be re-evaluated because we've entered our current target times, even if the evaluation period has cheaper times.
* If the current time is `2023-01-02T02:01`, the the target times will be re-evaluated because our existing target times are in the past and will find the best times in the new rolling target period (i.e. `2023-01-02T02:00` to `2023-01-02T10:00`).
* If the current time is `2023-01-02T02:01`, the the target times will be re-evaluated because our existing target times are in the past and will find the best times in the new target period (i.e. `2023-01-02T02:00` to `2023-01-02T08:00`).

#### Always

This will always evaluate the best target times for the target period, even if the sensor is in the middle of an existing target time period.

For example, lets say we have a continuous target which looks between `00:00` and `08:00` and has existing target times from `2023-01-02T01:00` to `2023-01-02T02:00`.

* If the current time is `2023-01-02T00:59`, then the target times will be re-evaluated and might change if the new target period (i.e. `2023-01-02T00:30` to `2023-01-02T08:30`) has better times than the existing target times.
* If the current time is `2023-01-02T01:31`, then the target times will be re-evaluated and might change if the new target period (i.e. `2023-01-02T01:30` to `2023-01-02T08:30`) has better times than the existing target times.
* If the current time is `2023-01-02T02:01`, the the target times will be re-evaluated because our existing target times are in the past and will find the best times in the new target period (i.e. `2023-01-02T02:00` to `2023-01-02T08:00`).

!!! note

This is only supported when [Re-evaluate within time frame](#re-evaluate-within-time-frame) is enabled, otherwise it will behave the same as the other options.

!!! warning

This setting means that you could end up with the sensor not turning on for the fully requested hours as the target times might be moved ahead half way through the picked times. It also could mean that the sensor doesn't come on at all during the requested look ahead hours (e.g. 8) because the lowest period kept moving back.

### Offset

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
CONFIG_TARGET_MINIMUM_REQUIRED_MINUTES_IN_SLOT,
CONFIG_TARGET_NAME,
CONFIG_TARGET_OFFSET,
CONFIG_TARGET_ROLLING_TARGET,
CONFIG_TARGET_START_TIME,
CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE,
CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE_ALWAYS,
CONFIG_TARGET_TYPE,
CONFIG_TARGET_TYPE_CONTINUOUS,
CONFIG_TARGET_WEIGHTING,
Expand Down Expand Up @@ -186,4 +189,8 @@ def validate_target_timeframe_config(data):
if minimum_required_minutes_in_slot is not None and (minimum_required_minutes_in_slot < 1 or minimum_required_minutes_in_slot > 30):
errors[CONFIG_TARGET_DANGEROUS_SETTINGS] = "invalid_minimum_required_minutes_in_slot"

if (data[CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE] == CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE_ALWAYS and
(CONFIG_TARGET_ROLLING_TARGET not in data or data[CONFIG_TARGET_ROLLING_TARGET] == False)):
errors[CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE] = "always_evaluation_not_supported"

return errors
1 change: 1 addition & 0 deletions custom_components/target_timeframes/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
options=[
selector.SelectOptionDict(value=CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE_ALL_IN_PAST, label="All existing target rates are in the past"),
selector.SelectOptionDict(value=CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE_ALL_IN_FUTURE_OR_PAST, label="Existing target rates haven't started or finished"),
selector.SelectOptionDict(value=CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE_ALWAYS, label="Always"),
],
mode=selector.SelectSelectorMode.DROPDOWN,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ async def async_update(self):
is_target_timeframe_complete = is_rolling_target == False and is_target_timeframe_complete_in_period(
current_local_date,
applicable_target_start,
applicable_target_end, self._target_timeframes,
applicable_target_end,
self._target_timeframes,
self._config[CONFIG_TARGET_NAME]
)

Expand Down
3 changes: 2 additions & 1 deletion custom_components/target_timeframes/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@
"minimum_or_maximum_value_not_specified": "Either minimum and/or maximum value must be specified for minimum hours mode",
"minimum_value_not_less_than_maximum_value": "Minimum value must be less or equal to the maximum value if both are specified",
"invalid_integer": "Value must be a number with no decimal places",
"invalid_minimum_required_minutes_in_slot": "Value must be between 1 and 30"
"invalid_minimum_required_minutes_in_slot": "Value must be between 1 and 30",
"always_evaluation_not_supported": "Always evaluation mode is only supported when \"Re-evaluate multiple times a day\" is enabled"
}
},
"rolling_target_time_period": {
Expand Down
Loading