Skip to content

Commit 1c5bdd4

Browse files
Merge pull request #20 from BottlecapDave/develop
Next release
2 parents 6a3b1ef + 6627bb8 commit 1c5bdd4

File tree

15 files changed

+422
-13
lines changed

15 files changed

+422
-13
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ To install, place the contents of `custom_components` into the `<config director
2929

3030
## How to setup
3131

32-
You will initially need to setup one or more [data sources](https://bottlecapdave.github.io/HomeAssistant-TargetTimeframes/setup/data_source). You'll then need to setup one or more [target timeframe](https://bottlecapdave.github.io/HomeAssistant-TargetTimeframes/setup/target_timeframe) or [rolling target timeframe](https://bottlecapdave.github.io/HomeAssistant-TargetTimeframes/setup/rolling_target_timeframe) sensors.
32+
It is recommended to consult the [getting started](https://bottlecapdave.github.io/HomeAssistant-TargetTimeframes/setup/getting_started) guide.
3333

3434
## Events
3535

42.8 KB
Loading
45.5 KB
Loading

_docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ To install, place the contents of `custom_components` into the `<config director
2929

3030
## How to setup
3131

32-
You will initially need to setup one or more [data sources](./setup/data_source.md). You'll then need to setup one or more [target timeframe](./setup/target_timeframe.md) or [rolling target timeframe](./setup/rolling_target_timeframe.md) sensors.
32+
It is recommended to consult the [getting started](./setup/getting_started.md) guide.
3333

3434
## Events
3535

_docs/services.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ There are a few services available within this integration, which are detailed h
66

77
### target_timeframes.update_target_timeframe_data_source
88

9-
Updates the source data for a given targetframe period. This will replace any existing data for the source.
9+
Updates the source data for a given target frame. This will update any existing data for the source, but keep any existing data that hasn't been provided. It will also remove any data that is older than 1 day previous to today. For example, if today is 2025-05-17, then all data before 2025-05-16 will be removed.
1010

1111
There are a collection of [blueprints](./blueprints.md#data-sources) available for loading popular data sources.
1212

1313
| Attribute | Optional | Description |
1414
| ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------- |
1515
| `target.entity_id` | `no` | The name of the [data source last updated](./setup/data_source.md#data-source-last-updated) sensor whose underlying data is to be updated. |
16+
| `data.replace_all_existing_data` | `yes` | Determines if the provided data should replace all existing data. If not provided or false, then new data will be added to existing data and existing data will be replaced where start/end times match. |
1617
| `data.data` | `no` | The collection of data to update the data source with. This will override any previous data. For the target rate sensors to work properly, you should target having data for the whole of yesterday, today and tomorrow (e.g. if today is 2025-01-04, you should aim to have data from 2025-01-03T00:00:00 to 2025-01-06T00:00:00).
1718

1819
The structure of the data should match the following

_docs/setup/getting_started.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Getting started
2+
3+
## Installing the integration
4+
5+
The first thing you need to do is install the new integration. Full instructions can be found on the [home page](../index.md#how-to-install).
6+
7+
## Setting up data sources
8+
9+
Once installed, you'll need to setup a data source that represents data coming from this integration. This can be done by [following the link](https://my.home-assistant.io/redirect/config_flow_start/?domain=target_timeframes) or searching for the integration in your integrations view. You'll need a data source for each unique set of data that your target timeframes will be based upon. This could be something like your electricity rates from an integration like [Octopus Energy](https://bottlecapdave.github.io/HomeAssistant-OctopusEnergy/) or carbon intensity from an integration like [Carbon Intensity](https://bottlecapdave.github.io/HomeAssistant-CarbonIntensity/).
10+
11+
Full details of everything that can be configured can be found in the [Data Source](./data_source.md). An example for Octopus Energy, the name would be something like `Octopus Energy Import` with a source id of `octopus_energy_import`, but you can pick whatever you want as long as it's unique within the integration.
12+
13+
![Data Source window](../assets/target_timeframes_data_source.png)
14+
15+
## Configuring data source data
16+
17+
Once you've configured a data source, you'll need to get data assigned to the data source so any associated target timeframes can be based upon that data. The data is added via an available [service](../services.md#target_timeframesupdate_target_timeframe_data_source). To make things easier, there are also a [range of blueprints](../blueprints.md#data-sources) you can install which add data from external integrations into a Target Timeframe given data source.
18+
19+
## Setting up Target Timeframe sensors
20+
21+
Now we have our data source representing our data and data syncing from an external source into Target Timeframes, it's time to now add a target timeframe sensor. Each type of sensor are added as a sub entry to our data source. You can see below what the menu looks like.
22+
23+
![Integration sub menu](../assets/target_timeframes_sub_menu.png)
24+
25+
For more detail on what each of these sensors provide, you should consult the detailed page on [target timeframe](./target_timeframe.md) and [rolling target timeframe](./rolling_target_timeframe.md).

custom_components/target_timeframes/entities/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ def apply_offset(date_time: datetime, offset: str, inverse = False):
3333

3434
return date_time + timedelta(hours=hours, minutes=minutes, seconds=seconds)
3535

36+
def is_target_timeframe_complete_in_period(current_date: datetime, applicable_time_periods: list | None, target_timeframes: list | None):
37+
if applicable_time_periods is None or target_timeframes is None or len(applicable_time_periods) < 1 or len(target_timeframes) < 1:
38+
return False
39+
40+
return (
41+
applicable_time_periods[0]["start"] <= target_timeframes[0]["start"] and
42+
applicable_time_periods[-1]["end"] >= target_timeframes[-1]["end"] and
43+
target_timeframes[-1]["end"] <= current_date
44+
)
45+
3646
def get_fixed_applicable_time_periods(current_date: datetime, target_start_time: str, target_end_time: str, time_period_values: list, is_rolling_target = True):
3747
if (target_start_time is not None):
3848
target_start = parse_datetime(current_date.strftime(f"%Y-%m-%dT{target_start_time}:00%z"))

custom_components/target_timeframes/entities/data_source.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from homeassistant.core import HomeAssistant, callback
44
from homeassistant.exceptions import ServiceValidationError
5-
from homeassistant.util.dt import (utcnow)
5+
from homeassistant.util.dt import (utcnow, now)
66

77
from homeassistant.const import (
88
STATE_UNAVAILABLE,
@@ -19,7 +19,7 @@
1919
from ..utils.attributes import dict_to_typed_dict
2020
from ..const import DOMAIN, EVENT_DATA_SOURCE
2121
from ..storage.data_source_data import async_save_cached_data_source_data
22-
from ..utils.data_source_data import validate_data_source_data
22+
from ..utils.data_source_data import DataSourceItem, merge_data_source_data, validate_data_source_data
2323

2424
_LOGGER = logging.getLogger(__name__)
2525

@@ -82,7 +82,7 @@ async def async_added_to_hass(self):
8282
_LOGGER.debug(f'Restored state: {self._state}')
8383

8484
@callback
85-
async def async_update_target_timeframe_data_source(self, data):
85+
async def async_update_target_timeframe_data_source(self, data, replace_all_existing_data = False):
8686
"""Update target timeframe data source"""
8787
result = validate_data_source_data(data, self._source_id)
8888
if result.success == False:
@@ -94,9 +94,21 @@ async def async_update_target_timeframe_data_source(self, data):
9494
},
9595
)
9696

97-
await async_save_cached_data_source_data(self._hass, self._source_id, result.data)
97+
data_source_data = (
98+
result.data
99+
if replace_all_existing_data
100+
else merge_data_source_data(
101+
now(),
102+
result.data,
103+
list(map(lambda x: DataSourceItem.parse_obj(x), self._attributes["data"]))
104+
if "data" in self._attributes
105+
else None
106+
)
107+
)
108+
109+
await async_save_cached_data_source_data(self._hass, self._source_id, data_source_data)
98110

99-
data_dict = list(map(lambda x: x.dict(), result.data))
111+
data_dict = list(map(lambda x: x.dict(), data_source_data))
100112
self._attributes["data"] = data_dict
101113
self._state = utcnow()
102114
self.async_write_ha_state()

custom_components/target_timeframes/entities/target_timeframe.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
extract_config,
5050
get_fixed_applicable_time_periods,
5151
get_target_time_period_info,
52+
is_target_timeframe_complete_in_period,
5253
should_evaluate_target_timeframes
5354
)
5455

@@ -128,7 +129,8 @@ async def async_update(self):
128129
# Find the current rate. Rates change a maximum of once every 30 minutes.
129130
current_date = utcnow()
130131

131-
should_evaluate = should_evaluate_target_timeframes(current_date, self._target_timeframes, self._config[CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE] if CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE in self._config else CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE_ALL_IN_PAST)
132+
evaluation_mode = self._config[CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE] if CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE in self._config else CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE_ALL_IN_PAST
133+
should_evaluate = should_evaluate_target_timeframes(current_date, self._target_timeframes, evaluation_mode)
132134
if should_evaluate:
133135
_LOGGER.debug(f'{len(self._data_source_data) if self._data_source_data is not None else None} time periods found')
134136

@@ -172,7 +174,9 @@ async def async_update(self):
172174
is_rolling_target
173175
)
174176

175-
if applicable_time_periods is not None:
177+
is_target_timeframe_complete = is_rolling_target == False and is_target_timeframe_complete_in_period(current_local_date, applicable_time_periods, self._target_timeframes)
178+
179+
if applicable_time_periods is not None and is_target_timeframe_complete == False:
176180
number_of_slots = math.ceil(target_hours * 2)
177181
weighting = create_weighting(self._config[CONFIG_TARGET_WEIGHTING] if CONFIG_TARGET_WEIGHTING in self._config else None, number_of_slots)
178182

custom_components/target_timeframes/sensor.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ async def async_setup_entry(hass, entry, async_add_entities):
3030
}
3131
],
3232
),
33+
vol.Optional("replace_all_existing_data"): bool,
3334
},
3435
extra=vol.ALLOW_EXTRA,
3536
),

0 commit comments

Comments
 (0)