Skip to content
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Target timeframes was a feature that has been extracted out of the [Octopus Energy integration](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy). The idea is you can configure binary sensors that will find and turn on during the most optimal time periods based on external data sources, targeting either the lowest or highest values. What these values represent can be anything. In the original integration, the values represented cost of energy, and so the cheapest periods were discovered. But it could represent other things like

* Temperature to turn on sprinklers during the hottest times of the day
* Energy prices to turn on devices when cost is the cheapest
* Carbon emissions to turn on devices when renewables on the grid are at their highest
* Solar generation to turn on devices when the most energy is being generated.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,6 @@ blueprint:
- sensor
integration: target_timeframes
multiple: false
octopus_energy_previous_day_rates:
name: Previous day rates
description: The previous day rates event sensor supplied by Octopus Energy. More information can be found at https://bottlecapdave.github.io/HomeAssistant-OctopusEnergy/entities/electricity/#previous-day-rates.
selector:
entity:
filter:
- domain:
- event
integration: octopus_energy
multiple: false
octopus_energy_current_day_rates:
name: Current day rates
description: The current day rates event sensor supplied by Octopus Energy. More information can be found at https://bottlecapdave.github.io/HomeAssistant-OctopusEnergy/entities/electricity/#current-day-rates.
Expand Down Expand Up @@ -61,6 +51,13 @@ blueprint:
selector:
number:
mode: box
octopus_energy_weighting:
name: Octopus Energy rate weighting
description: The weighting to apply to the Octopus Energy rates when calculating the final value for the period.
default: 0.7
selector:
number:
mode: box
carbon_intensity_current_day_rates:
name: Current day rates
description: The current day rates event sensor supplied by Carbon Intensity. More information can be found at https://bottlecapdave.github.io/HomeAssistant-CarbonIntensity/entities/#current-day-rates.
Expand All @@ -81,20 +78,26 @@ blueprint:
- event
integration: carbon_intensity
multiple: false
carbon_intensity_weighting:
name: Carbon Intensity forecast weighting
description: The weighting to apply to the Carbon Intensity forecast when calculating the final value for the period.
default: 0.3
selector:
number:
mode: box
variables:
target_timeframe_data_source_sensor: !input target_timeframe_data_source_sensor
octopus_energy_previous_day_rates: !input octopus_energy_previous_day_rates
octopus_energy_current_day_rates: !input octopus_energy_current_day_rates
octopus_energy_next_day_rates: !input octopus_energy_next_day_rates
octopus_energy_free_electricity: !input octopus_energy_free_electricity
octopus_energy_free_electricity_weighting: !input octopus_energy_free_electricity_weighting
carbon_intensity_current_day_rates: !input carbon_intensity_current_day_rates
carbon_intensity_next_day_rates: !input carbon_intensity_next_day_rates
octopus_energy_weighting: !input octopus_energy_weighting
carbon_intensity_weighting: !input carbon_intensity_weighting
mode: queued
max: 4
triggers:
- platform: state
entity_id: !input octopus_energy_previous_day_rates
- platform: state
entity_id: !input octopus_energy_current_day_rates
- platform: state
Expand All @@ -110,9 +113,6 @@ action:
- action: target_timeframes.update_target_timeframe_data_source
data: >
{%- set all_oe_rates = [] -%}
{%- if state_attr(octopus_energy_previous_day_rates, 'rates') != None -%}
{%- set all_oe_rates = all_oe_rates + state_attr(octopus_energy_previous_day_rates, 'rates') -%}
{%- endif -%}
{%- if state_attr(octopus_energy_current_day_rates, 'rates') != None -%}
{%- set all_oe_rates = all_oe_rates + state_attr(octopus_energy_current_day_rates, 'rates') -%}
{%- endif -%}
Expand All @@ -132,12 +132,19 @@ action:
{%- set all_ci_rates = all_ci_rates + state_attr(carbon_intensity_next_day_rates, 'rates') -%}
{%- endif -%}

{%- set min_rate = all_oe_rates | map(attribute='value_inc_vat') | min -%}
{%- set max_rate = all_oe_rates | map(attribute='value_inc_vat') | max -%}
{%- set min_carbon = all_ci_rates | map(attribute='intensity_forecast') | min -%}
{%- set max_carbon = all_ci_rates | map(attribute='intensity_forecast') | max -%}
{%- set rate_diff = max_rate - min_rate if max_rate - min_rate != 0 else 1 %}
{%- set carbon_diff = max_carbon - min_carbon if max_carbon - min_carbon != 0 else 1 %}

{%- set data = namespace(new_rates=[]) -%}
{%- for rate in all_oe_rates -%}
{%- set start = rate["start"] | as_timestamp | timestamp_utc -%}
{%- set end = rate["end"] | as_timestamp | timestamp_utc -%}
{%- set value = rate["value_inc_vat"] | float -%}
{%- set value = (((rate["value_inc_vat"] | float - min_rate) / rate_diff) * octopus_energy_weighting) -%}

{%- set free_namespace = namespace(is_free=False) -%}
{%- for free_session in free_electricity_rates -%}
{%- set free_start = free_session["start"] | as_timestamp | timestamp_utc -%}
Expand All @@ -158,7 +165,7 @@ action:

{%- set metadata = { "rate": rate["value_inc_vat"], "is_capped": rate["is_capped"] } -%}
{%- if carbon_intensity_namespace.rate -%}
{%- set value = value * (carbon_intensity_namespace.rate["intensity_forecast"] | float) -%}
{%- set value = value + (((carbon_intensity_namespace.rate["intensity_forecast"] | float - min_carbon) / carbon_diff) * carbon_intensity_weighting) -%}
{%- set metadata = dict(metadata.items(), carbon_intensity=carbon_intensity_namespace.rate["intensity_forecast"] | float) -%}
{%- endif -%}

Expand Down
2 changes: 1 addition & 1 deletion _docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Target timeframes was a feature that has been extracted out of the [Octopus Energy integration](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy). The idea is you can configure binary sensors that will find and turn on during the most optimal time periods based on external data sources, targeting either the lowest or highest values. What these values represent can be anything. In the original integration, the values represented cost of energy, and so the cheapest periods were discovered. But it could represent other things like

* Temperature to turn on sprinklers during the hottest times of the day
* Energy prices to turn on devices when cost is the cheapest
* Carbon emissions to turn on devices when renewables on the grid are at their highest
* Solar generation to turn on devices when the most energy is being generated.

Expand Down
8 changes: 8 additions & 0 deletions _docs/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ The following services are available if you have set up at least one [target tim

For updating a given [target timeframe's](./setup/target_timeframe.md) config. This allows you to change target timeframes sensors dynamically based on other outside criteria (e.g. you need to adjust the target hours to top up home batteries).

!!! warning

This will cause the sensor to re-evaluate the target times, which may result in different times being picked.

| Attribute | Optional | Description |
| ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `target.entity_id` | `no` | The name of the target sensor whose configuration is to be updated. |
Expand Down Expand Up @@ -126,6 +130,10 @@ The following services are available if you have set up at least one [rolling ta

For updating a given [rolling target timeframe's](./setup/rolling_target_timeframe.md) config. This allows you to change rolling target timeframes sensors dynamically based on other outside criteria (e.g. you need to adjust the target hours to top up home batteries).

!!! warning

This will cause the sensor to re-evaluate the target times, which may result in different times being picked.

| Attribute | Optional | Description |
| ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `target.entity_id` | `no` | The name of the target sensor whose configuration is to be updated. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def __init__(self, hass: HomeAssistant, data_source_id: str, config_entry, confi
self._attributes = self._config.copy()
self._last_evaluated = None
self._data_source_id = data_source_id
self._attributes["data_source_id"] = self._data_source_id

is_rolling_target = True
if CONFIG_TARGET_ROLLING_TARGET in self._config:
Expand Down Expand Up @@ -205,6 +206,7 @@ async def async_update(self):
self._attributes["next_average_value"] = active_result["next_average_value"]
self._attributes["next_min_value"] = active_result["next_min_value"]
self._attributes["next_max_value"] = active_result["next_max_value"]
self._attributes["data_source_id"] = self._data_source_id

self._state = active_result["is_active"]

Expand Down Expand Up @@ -295,6 +297,7 @@ async def async_update_rolling_target_timeframe_config(self, target_hours=None,
self._config = config
self._attributes = self._config.copy()
self._target_timeframes = []
await self.async_update()
self.async_write_ha_state()

if persist_changes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def __init__(self, hass: HomeAssistant, data_source_id: str, config_entry, confi
self._attributes = self._config.copy()
self._last_evaluated = None
self._data_source_id = data_source_id
self._attributes["data_source_id"] = self._data_source_id

is_rolling_target = True
if CONFIG_TARGET_ROLLING_TARGET in self._config:
Expand All @@ -86,6 +87,7 @@ def __init__(self, hass: HomeAssistant, data_source_id: str, config_entry, confi

self._data_source_data = initial_data if initial_data is not None else []
self._target_timeframes = []


self._hass = hass
self.entity_id = generate_entity_id("binary_sensor.{}", self.unique_id, hass=hass)
Expand Down Expand Up @@ -230,6 +232,7 @@ async def async_update(self):
self._attributes["next_average_value"] = active_result["next_average_value"]
self._attributes["next_min_value"] = active_result["next_min_value"]
self._attributes["next_max_value"] = active_result["next_max_value"]
self._attributes["data_source_id"] = self._data_source_id

self._state = active_result["is_active"]

Expand Down Expand Up @@ -326,6 +329,7 @@ async def async_update_target_timeframe_config(self, target_start_time=None, tar
self._config = config
self._attributes = self._config.copy()
self._target_timeframes = []
await self.async_update()
self.async_write_ha_state()

if persist_changes:
Expand Down