diff --git a/.gitignore b/.gitignore index d488d9e..d144764 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules/ **/__pycache__/** +run_tests.sh +run_unit_tests.sh diff --git a/README.md b/README.md index 190ac8d..f62fa08 100644 --- a/README.md +++ b/README.md @@ -9,18 +9,9 @@ Custom component to use the data from [carbonintensity.org.uk](https://carbonint - [HACS](#hacs) - [Manual](#manual) - [How to setup](#how-to-setup) - - [Your Settings](#your-settings) - [Target Rates](#target-rates) - - [From and To times](#from-and-to-times) - - [Offset](#offset) - - [Rolling Target](#rolling-target) - - [Examples](#examples) - - [Continuous](#continuous) - - [Intermittent](#intermittent) - - [Services](#services) - - [Service carbon\_intensity.update\_target\_config](#service-carbon_intensityupdate_target_config) - [FAQ](#faq) - - [I'm having issues with the integration](#im-having-issues-with-the-integration) + - [Sponsorship](#sponsorship) ## How to install @@ -36,211 +27,20 @@ To install, place the contents of `custom_components` into the ` Please note this is temporary and will not persist between restarts. - -| Attribute | Optional | Description | -| ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------| -| `target.entity_id` | `no` | The name of the target sensor whose configuration is to be updated | -| `data.hours` | `yes` | The optional number of hours the rate sensor should come on during a 24 hour period. Must be divisible by 0.5. | -| `data.start_time` | `yes` | The optional time the evaluation period should start. Must be in the format of `HH:MM`. | -| `data.end_time` | `yes` | The optional time the evaluation period should end. Must be in the format of `HH:MM`. | -| `data.offset` | `yes` | The optional offset to apply to the target rate when it starts. Must be in the format `(+/-)HH:MM:SS` | - -This can be used via automations in the following way. Assuming we have the following inputs. - -```yaml -input_number: - carbon_intensity_hours: - name: Carbon Intensity Target Hours - min: 0 - max: 24 - -input_text: - # From/to would ideally use input_datetime, but we need the time in a different format - carbon_intensity_from: - name: Carbon Intensity Target From - initial: "00:00" - carbon_intensity_to: - name: Carbon Intensity Target To - initial: "00:00" - carbon_intensity_offset: - name: Carbon Intensity Target Offset - initial: "-00:00:00" -``` - -Then an automation might look like the following - -```yaml -automations: - - alias: Update target rate config - trigger: - - platform: state - entity_id: - - input_number.carbon_intensity_hours - - input_text.carbon_intensity_from - - input_text.carbon_intensity_to - - input_text.carbon_intensity_offset - condition: [] - action: - - service: carbon_intensity.update_target_config - data: - hours: > - "{{ states('input_number.carbon_intensity_hours') | string }}" - start_time: > - {{ states('input_text.carbon_intensity_from') }} - end_time: > - {{ states('input_text.carbon_intensity_to') }} - offset: > - {{ states('input_text.carbon_intensity_offset') }} - target: - entity_id: binary_sensor.carbon_intensity_target_example -``` +Please follow the [setup guide](./_docs/setup_target_rate.md) to setup. ## FAQ -### I'm having issues with the integration - -The first thing to do is increase the log levels for the component. This can be done by setting the following values in your `configuration.yaml` file. - -```yaml -logger: - logs: - custom_components.carbon_intensity: info -``` +Before raising anything, please read through the [faq](./_docs/faq.md). If you have questions, then you can raise a [discussion](https://github.com/BottlecapDave/HomeAssistant-CarbonIntensity/discussions). If you have found a bug or have a feature request please [raise it](https://github.com/BottlecapDave/HomeAssistant-CarbonIntensity/issues) using the appropriate report template. -If you don't have access to this file, then you should be able to set the log levels using the [available services](https://www.home-assistant.io/integrations/logger/). +## Sponsorship -Once done, you'll need to reload the integration and then check the "Full Home Assistant Log" from the `logs page`. You should then see entries associated with this component. +If you are enjoying the integration, if possible why not make a one off or monthly [GitHub sponsorship](https://github.com/sponsors/bottlecapdave). diff --git a/_docs/entities.md b/_docs/entities.md new file mode 100644 index 0000000..7856f69 --- /dev/null +++ b/_docs/entities.md @@ -0,0 +1,14 @@ +# Entities + +- [Entities](#entities) + - [Current Rating](#current-rating) + +## Current Rating + +`sensor.carbon_intensity_{{REGION}}_current_rating` + +The forecasted intensity rating of the current 30 minute period. + +| Attribute | Type | Description | +|-----------|------|-------------| +| `rate` | `string` | The rate for the current 30 minute period | \ No newline at end of file diff --git a/_docs/events.md b/_docs/events.md new file mode 100644 index 0000000..8d569f0 --- /dev/null +++ b/_docs/events.md @@ -0,0 +1,46 @@ +# Events + +- [Events](#events) + - [Current Day Rates](#current-day-rates) + - [Next Day Rates](#next-day-rates) + +The following events are raised by the integration. These events power various entities mentioned above. They can also be used to trigger automations. An example automation might look like the following + +```yaml +- alias: CI rates change + trigger: + - platform: event + event_type: carbon_intensity_next_day_rates + condition: [] + action: + - event: notify_channels + event_data_template: + mode: message + title: OE price changes + message: > + New rates available for {{ trigger.event.data.region }}. Starting value is {{ trigger.event.data.rates[0]["intensity_forecast"] }} + target: <@ULU7111GU> + length_hint: 00:00:04 +``` + +## Current Day Rates + +`carbon_intensity_current_day_rates` + +This is fired when the current day rates are updated. + +| Attribute | Type | Description | +|-----------|------|-------------| +| `rates` | `list` | The list of rates applicable for the current day | +| `region` | `string` | The region the rates are for | + +## Next Day Rates + +`carbon_intensity_next_day_rates` + +This is fired when the next day rates are updated. + +| Attribute | Type | Description | +|-----------|------|-------------| +| `rates` | `list` | The list of rates applicable for the next day | +| `region` | `string` | The region the rates are for | \ No newline at end of file diff --git a/_docs/faq.md b/_docs/faq.md new file mode 100644 index 0000000..c9cb8e1 --- /dev/null +++ b/_docs/faq.md @@ -0,0 +1,10 @@ +# FAQ + +- [FAQ](#faq) + - [How do I increase the logs for the integration?](#how-do-i-increase-the-logs-for-the-integration) + +## How do I increase the logs for the integration? + +If you are having issues, it would be helpful to include Home Assistant logs as part of any raised issue. This can be done by following the [instructions](https://www.home-assistant.io/docs/configuration/troubleshooting/#enabling-debug-logging) outlined by Home Assistant. + +You should run these logs for about a day and then include the contents in the issue. Please be sure to remove any personal identifiable information from the logs before including them. \ No newline at end of file diff --git a/_docs/readme.md b/_docs/readme.md new file mode 100644 index 0000000..e6f9ee0 --- /dev/null +++ b/_docs/readme.md @@ -0,0 +1,48 @@ +# Home Assistant Carbon Intensity Docs + +- [Home Assistant Carbon Intensity Docs](#home-assistant-carbon-intensity-docs) + - [How to setup](#how-to-setup) + - [Entities](#entities) + - [Target Rate Sensors](#target-rate-sensors) + - [Events](#events) + - [Services](#services) + - [Energy Dashboard](#energy-dashboard) + - [Community Contributions](#community-contributions) + - [FAQ](#faq) + + +## How to setup + +Please follow the [setup guide](./setup_account.md) to setup your initial account. This guide details the configuration, along with the entities that will be available to you. + +## Entities + +A full list of default entities can be found [here](./entities.md) + +## Target Rate Sensors + +These sensors calculate the lowest continuous or intermittent rates **within a 24 hour period** and turn on when these periods are active. + +These sensors can then be used in automations to turn on/off devices that save you (and the planet) energy and money. You can go through this flow as many times as you need target rate sensors. + +Please follow the [setup guide](./setup_target_rate.md) to setup. + +## Events + +This integration raises several events, which can be used for various tasks like automations. For more information, please see the [events docs](./events.md). + +## Services + +This integration includes several services. Please review them in the [services doc](./services.md). + +## Energy Dashboard + +The core sensors have been designed to work with the energy dashboard. Please see the [guide](./energy_dashboard.md) for instructions on how to set this up. + +## Community Contributions + +A collection of community contributions can be found [here](./community.md). + +## FAQ + +Before raising anything, please read through the [faq](./faq.md). If you have questions, then you can raise a [discussion](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/discussions). If you have found a bug or have a feature request please [raise it](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/issues) using the appropriate report template. \ No newline at end of file diff --git a/_docs/services.md b/_docs/services.md new file mode 100644 index 0000000..69091af --- /dev/null +++ b/_docs/services.md @@ -0,0 +1,70 @@ +# Services + +- [Services](#services) + - [Service carbon\_intensity.update\_target\_config](#service-carbon_intensityupdate_target_config) + +There are a few services available within this integration, which are detailed here. + +## Service carbon_intensity.update_target_config + +Service for updating a given target rate's config. This allows you to change target rates sensors dynamically based on other outside criteria (e.g. you need to adjust the target hours to top up home batteries). + +> Please note this is temporary and will not persist between restarts. + +| Attribute | Optional | Description | +| ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------| +| `target.entity_id` | `no` | The name of the target sensor whose configuration is to be updated | +| `data.hours` | `yes` | The optional number of hours the rate sensor should come on during a 24 hour period. Must be divisible by 0.5. | +| `data.start_time` | `yes` | The optional time the evaluation period should start. Must be in the format of `HH:MM`. | +| `data.end_time` | `yes` | The optional time the evaluation period should end. Must be in the format of `HH:MM`. | +| `data.offset` | `yes` | The optional offset to apply to the target rate when it starts. Must be in the format `(+/-)HH:MM:SS` | + +This can be used via automations in the following way. Assuming we have the following inputs. + +```yaml +input_number: + carbon_intensity_hours: + name: Carbon Intensity Target Hours + min: 0 + max: 24 + +input_text: + # From/to would ideally use input_datetime, but we need the time in a different format + carbon_intensity_from: + name: Carbon Intensity Target From + initial: "00:00" + carbon_intensity_to: + name: Carbon Intensity Target To + initial: "00:00" + carbon_intensity_offset: + name: Carbon Intensity Target Offset + initial: "-00:00:00" +``` + +Then an automation might look like the following + +```yaml +automations: + - alias: Update target rate config + trigger: + - platform: state + entity_id: + - input_number.carbon_intensity_hours + - input_text.carbon_intensity_from + - input_text.carbon_intensity_to + - input_text.carbon_intensity_offset + condition: [] + action: + - service: carbon_intensity.update_target_config + data: + hours: > + "{{ states('input_number.carbon_intensity_hours') | string }}" + start_time: > + {{ states('input_text.carbon_intensity_from') }} + end_time: > + {{ states('input_text.carbon_intensity_to') }} + offset: > + {{ states('input_text.carbon_intensity_offset') }} + target: + entity_id: binary_sensor.carbon_intensity_target_example +``` diff --git a/_docs/setup_account.md b/_docs/setup_account.md new file mode 100644 index 0000000..3cc8ece --- /dev/null +++ b/_docs/setup_account.md @@ -0,0 +1,10 @@ +# Setup Account + +- [Setup Account](#setup-account) + - [Region](#region) + +Setup is done entirely via the [integration UI](https://my.home-assistant.io/redirect/config_flow_start/?domain=carbon_intensity). + +## Region + +In order to provide accurate readings, you'll need to set the region you're tracking. This will be used to work out the carbon intensity in your area. \ No newline at end of file diff --git a/_docs/setup_target_rate.md b/_docs/setup_target_rate.md new file mode 100644 index 0000000..e0f5236 --- /dev/null +++ b/_docs/setup_target_rate.md @@ -0,0 +1,168 @@ +# Setup Target Rate Sensor(s) + +- [Setup Target Rate Sensor(s)](#setup-target-rate-sensors) + - [Setup](#setup) + - [Target Timeframe](#target-timeframe) + - [Agile Users](#agile-users) + - [Hours](#hours) + - [Offset](#offset) + - [Rolling Target](#rolling-target) + - [Attributes](#attributes) + - [Services](#services) + - [Examples](#examples) + - [Examples](#examples-1) + - [Continuous](#continuous) + - [Intermittent](#intermittent) + +After you've configured your [account](./setup_account.md), you'll be able to configure target rate sensors. These are configured by adding subsequent instances of the integration going through the [normal flow](https://my.home-assistant.io/redirect/config_flow_start/?domain=octopus_energy). + +These sensors calculate the lowest continuous or intermittent rates **within a 24 hour period** and turn on when these periods are active. If you are targeting an export meter, then the sensors will calculate the highest continuous or intermittent rates **within a 24 hour period** and turn on when these periods are active. + +These sensors can then be used in automations to turn on/off devices that save you (and the planet) energy and money. You can go through this flow as many times as you need target rate sensors. + +Each sensor will be in the form `binary_sensor.octopus_energy_target_{{TARGET_RATE_NAME}}`. + +## Setup + +### Target Timeframe + +If you're wanting your devices to come on during a certain timeframe, for example while you're at work, you can set the minimum and/or maximum times for your target rate sensor. These are specified in 24 hour clock format and will attempt to find the optimum discovered period during these times. + +The `from/start` time can be set in the field `The minimum time to start the device` and the `to/end` time can be set in the field `The maximum time to stop the device`. + +If not specified, these default from `00:00:00` to `00:00:00` the following day. + +If for example you want to look at prices overnight you could set the minimum time to something like `20:00` and your maximum time to something like `05:00`. If the minimum time is "before" the maximum time, then it will treat the maximum time as the time for the following day. + +> Please note: The target rate will not be evaluated until **all data** is available for the specified timeframe. Therefore if we're looking between `00:00` and `00:00`, full rate information must exist between this time. Whereas if times are between `10:00` and `16:00`, then rate information is only needed between these times before it can be calculated. + +#### Agile Users + +If you are an agile user, then agile prices are available from [11pm to 11pm UK time](https://developer.octopus.energy/docs/api/#agile-octopus) and published at `16:00` UK time. Therefore, you cannot specify a timeframe that starts before `16:00` and ends after `23:00` because the target rate(s) will not be able to be calculated until part way through the specified timeframe as this is when the full set will become available. We recommend you set your timeframes to `16:00`/`16:00` or `23:00`/`23:00` if you're wanting to target a full 24 hours, but other valid times might include + + +| from/start | to/end | Notes | +|-|-|-| +| `10:00` | `23:00` | our start time is before 4pm, but our end time is not after 11pm | +| `16:30` | `23:30` | our start time is after 4pm, so our end time can be after 11pm | +| `17:00` | `14:00` | our start time is after 4pm and our end time is before our start time so therefore for the next day. Doing this might delay when the target rate sensor is calculated depending on when the rates are made available for the next day (e.g. if they're late for publishing). | + +This is not automatically done by the integration as I didn't want to cause confusion for users when they didn't set anything nor did I want behaviour to implicitly change when users switch tariffs. + +See the examples below for how this can be used and how rates will be selected. + +### Hours + +The hours that you require for the sensor to find. This should be in decimal format and represent 30 minute increments. For example 30 minutes would be `0.5`, 1 hour would be `1` or `1.0`, 1 hour and 30 minutes would be `1.5`, etc. + +### Offset + +You may want your target rate sensors to turn on a period of time before the optimum discovered period. For example, you may be turning on a robot vacuum cleaner for a 30 minute clean and want it to charge during the optimum period. For this, you'd use the `offset` field and set it to `-00:30:00`, which can be both positive and negative and go up to a maximum of 24 hours. This will shift when the sensor turns on relative to the optimum period. For example, if the optimum period is between `2023-01-18T10:00` and `2023-01-18T11:00` with an offset of `-00:30:00`, the sensor will turn on between `2023-01-18T09:30` and `2023-01-18T10:30`. + +### Rolling Target + +Depending on how you're going to use the sensor, you might want the best period to be found throughout the day so it's always available. For example, you might be using the sensor to turn on a washing machine which you might want to come on at the best time regardless of when you use the washing machine. This can result in the sensor coming on more than the target hours, and therefore should be used in conjuction with other sensors. You can activate this behaviour by setting the `Re-evaluate multiple times a day` checkbox. + +However, you might also only want the target time to occur once a day so once the best time for that day has passed it won't turn on again. For example, you might be using the sensor to turn on something that isn't time critical and could wait till the next day like a charger. This is the default behaviour and is done by not setting the `Re-evaluate multiple times a day` checkbox. + +## Attributes + +The following attributes are available on each sensor + +| Attribute | Type | Description | +|-----------|------|-------------| + +## Services + +There are services available associated with target rate sensors. Please review them in the [services doc](./services.md). + +## Examples + +#### Examples + +Lets look at a few examples. Lets say we have the the following (unrealistic) set of intensity values. + +| start | end | value | +| ----- | --- | ----- | +| `2023-01-01T00:00` | `2023-01-01T00:30` | 6 | +| `2023-01-01T00:30` | `2023-01-01T05:00` | 12 | +| `2023-01-01T05:00` | `2023-01-01T05:30` | 7 | +| `2023-01-01T05:30` | `2023-01-01T18:00` | 20 | +| `2023-01-01T18:00` | `2023-01-01T23:30` | 34 | +| `2023-01-01T23:30` | `2023-01-02T00:30` | 5 | +| `2023-01-02T00:30` | `2023-01-02T05:00` | 12 | +| `2023-01-02T05:00` | `2023-01-02T05:30` | 7 | +| `2023-01-02T05:30` | `2023-01-02T18:00` | 20 | +| `2023-01-02T18:00` | `2023-01-02T23:00` | 34 | +| `2023-01-02T23:30` | `2023-01-03T00:00` | 6 | + +##### Continuous + +If we look at a continuous sensor that we want on for 1 hour. + +If we set no from/to times, then our 24 hour period being looked at ranges from `00:00:00` to `23:59:59`. + +The following table shows what this would be like. + +| current date/time | period | `Re-evaluate multiple times a day` | reasoning | +| ------------------ | ------------------------------------- | ---------------------------------- | --------- | +| `2023-01-01T00:00` | `2023-01-01T00:00` - `2023-01-01T01:00` | `false` | while 5 is our lowest rate within the current 24 hour period, it doesn't cover our whole 1 hour and is next to a high 34 rate. A rate of 6 is the next available rate with a low following rate. | +| `2023-01-01T01:00` | `2023-01-02T00:00` - `2023-01-02T01:00` | `false` | Our lowest period is in the past, so we have to wait until our target period has passed to look at the next evaluation period. | +| `2023-01-01T01:00` | `2023-01-01T04:30` - `2023-01-01T05:30` | `true` | The rate of 6 is in the past, so 7 is our next lowest rate. 12 is smaller rate than 20 so we start in the rate period before to fill our desired hour. | +| `2023-01-01T23:30` | None | `true` | There is no longer enough time available in the current 24 hour period, so we have to wait until our target period has passed to look at the next evaluation period. | + +If we set our from/to times for `05:00` to `19:00`, we then limit the period that we look at. The following table shows what this would be like. + +| current date/time | period | `Re-evaluate multiple times a day` | reasoning | +| ------------------ | ------------------------------------- | ---------------------------------- | --------- | +| `2023-01-01T00:00` | `2023-01-01T05:00` - `2023-01-01T06:00` | `false` | The rate of 12 is no longer available as it's outside of our `from` time. | +| `2023-01-01T06:30` | `2023-01-02T05:00` - `2023-01-02T06:00` | `false` | Our lowest period is in the past, so we have to wait until our target period has passed to look at the next evaluation period. | +| `2023-01-01T06:30` | `2023-01-01T06:30` - `2023-01-01T07:30` | `true` | The rate of 7 is in the past, so we must look for the next lowest combined rate | +| `2023-01-01T18:00` | `2023-01-01T18:00` - `2023-01-01T19:00` | `true` | The rate of 20 is in the past, so we must look for the next lowest combined rate which is 34 | +| `2023-01-01T18:30` | None | `true` | There is no longer enough time available within our restricted time, so we have to wait until our target period has passed to look at the next evaluation period. | + +If we set our from/to times to look over two days, from `20:00` to `06:00`, we then limit the period that we look at to overnight. The following table shows what this would be like. + +| current date/time | period | `Re-evaluate multiple times a day` | reasoning | +| ------------------ | ------------------------------------- | ---------------------------------- | --------- | +| `2023-01-01T20:00` | `2023-01-01T23:30` - `2023-01-02T01:30` | `false` | Our lowest rate of 5 now falls between our overnight time period so is available | +| `2023-01-02T02:00` | `2023-01-01T23:30` - `2023-01-02T01:30` | `false` | Our lowest period is in the past, so we have to wait until our target period has passed to look at the next evaluation period. | +| `2023-01-02T02:00` | `2023-01-02T04:30` - `2023-01-02T05:30` | `true` | The rate of 5 is in the past, so we must look for the next lowest combined rate, which includes our half hour rate at 7 | +| `2023-01-02T05:30` | None | `true` | There is no longer enough time available within our restricted time, so we have to wait until our target period has passed to look at the next evaluation period. | + +If we set an offset of `-00:30:00`, then while the times might be the same, the target rate sensor will turn on 30 minutes before the select rate period starts. Any set time restrictions **will not** include the offset. + +##### Intermittent + +If we look at an intermittent sensor that we want on for 1 hour total (but not necessarily together). + +If we set no from/to times, then our 24 hour period being looked at ranges from `00:00:00` to `23:59:59`. + +The following table shows what this would be like. + +| current date/time | period | `Re-evaluate multiple times a day` | reasoning | +| ------------------ | ------------------------------------- | ---------------------------------- | --------- | +| `2023-01-01T00:00` | `2023-01-01T00:00` - `2023-01-01T00:30`, `2023-01-01T23:30` - `2023-01-02T00:00` | `false` | Our sensor will go on for 30 minutes at the lowest intensity, then 30 minutes at the next lowest intensity. | +| `2023-01-01T01:00` | `2023-01-01T00:00` - `2023-01-01T00:30`, `2023-01-01T23:30` - `2023-01-02T00:00` | `false` | Our sensor will go on for 30 minutes at the lowest intensity, which will be in the past, then 30 minutes at the next lowest intensity. | +| `2023-01-01T01:00` | `2023-01-01T05:00` - `2023-01-01T05:30`, `2023-01-01T23:30` - `2023-01-02T00:00` | `true` | Our sensor will go on for 30 minutes at the second lowest intensity, then 30 minutes at the third lowest intensity. | +| `2023-01-01T23:30` | None | `true` | There is no longer enough time available in the current 24 hour period, so we have to wait until our target period has passed to look at the next evaluation period. | + +If we set our from/to times for `05:00` to `19:00`, we then limit the period that we look at. The following table shows what this would be like. + +| current date/time | period | `Re-evaluate multiple times a day` | reasoning | +| ------------------ | ------------------------------------- | ---------------------------------- | --------- | +| `2023-01-01T00:00` | `2023-01-01T05:00` - `2023-01-01T05:30`, `2023-01-01T05:30` - `2023-01-01T06:00` | `false` | Our lowest intensities are outside our target range, so we need to look at the next cheapest. Luckily on our scenario the two lowest intensities are next to each other. | +| `2023-01-01T06:30` | `2023-01-01T05:00` - `2023-01-01T05:30`, `2023-01-01T05:30` - `2023-01-01T06:00` | `false` | Both of our lowest intensities in the target range are in the past. | +| `2023-01-01T06:30` | `2023-01-01T06:30` - `2023-01-01T07:00`, `2023-01-01T07:00` - `2023-01-01T07:30` | `true` | Both of our lowest intensities in the target range are in the past, so we must look for the next lowest combined rate | +| `2023-01-01T18:30` | None | `true` | There is no longer enough time available within our restricted time, so we have to wait until our target period has passed to look at the next evaluation period. | + +If we set our from/to times to look over two days, from `20:00` to `06:00`, we then limit the period that we look at to overnight. The following table shows what this would be like. + +| current date/time | period | `Re-evaluate multiple times a day` | reasoning | +| ------------------ | ------------------------------------- | ---------------------------------- | --------- | +| `2023-01-01T20:00` | `2023-01-01T23:30` - `2023-01-02T00:30`, `2023-01-02T05:00` - `2023-01-02T05:30` | `false` | Our lowest rate of 5 now falls between our overnight time period so is available | +| `2023-01-02T02:00` | `2023-01-01T23:30` - `2023-01-02T00:30`, `2023-01-02T05:00` - `2023-01-02T05:30` | `false` | Our lowest period is in the past, but we still have a rate in the future so our sensor will only come on once. | +| `2023-01-02T02:00` | `2023-01-02T02:00` - `2023-01-02T02:30`, `2023-01-02T05:00` - `2023-01-02T05:30` | `true` | The rate of 5 is in the past, so we must look for the next lowest combined rate, which includes our half hour rate at 7 | +| `2023-01-02T05:30` | None | `true` | There is no longer enough time available within our restricted time, so we have to wait until our target period has passed to look at the next evaluation period. | + +If we set an offset of `-00:30:00`, then while the times might be the same, the target rate sensor will turn on 30 minutes before the select rate period starts. Any set time restrictions **will not** include the offset. \ No newline at end of file diff --git a/custom_components/carbon_intensity/entities/__init__.py b/custom_components/carbon_intensity/entities/__init__.py new file mode 100644 index 0000000..acaa36a --- /dev/null +++ b/custom_components/carbon_intensity/entities/__init__.py @@ -0,0 +1,19 @@ +from datetime import datetime + +def get_current_rate(current: datetime, rates: list): + current_rate = None + if rates is not None: + for period in rates: + if current >= period["from"] and current <= period["to"]: + current_rate = period + break + + return current_rate + + if current_rate != None: + self._attributes = { + "rate": current_rate, + } + + if current_rate != None: + self._state = current_rate["intensity_forecast"] \ No newline at end of file diff --git a/custom_components/carbon_intensity/sensors/current_rating.py b/custom_components/carbon_intensity/entities/current_rating.py similarity index 65% rename from custom_components/carbon_intensity/sensors/current_rating.py rename to custom_components/carbon_intensity/entities/current_rating.py index 8029eb1..5d39d93 100644 --- a/custom_components/carbon_intensity/sensors/current_rating.py +++ b/custom_components/carbon_intensity/entities/current_rating.py @@ -5,32 +5,34 @@ CoordinatorEntity ) from homeassistant.components.sensor import ( - SensorEntity, + RestoreSensor, ) -from homeassistant.helpers.restore_state import RestoreEntity +from ..entities import get_current_rate +from ..utils import get_region_for_unique_id_from_id, get_region_from_id _LOGGER = logging.getLogger(__name__) -class CarbonIntensityCurrentRating(CoordinatorEntity, SensorEntity, RestoreEntity): +class CarbonIntensityCurrentRating(CoordinatorEntity, RestoreSensor): """Sensor for displaying the current rate.""" - def __init__(self, coordinator): + def __init__(self, coordinator, region: str): """Init sensor.""" # Pass coordinator to base class super().__init__(coordinator) self._state = None + self._region = region @property def unique_id(self): """The id of the sensor.""" - return f"carbon_intensity_current_rating" + return f"carbon_intensity_{get_region_for_unique_id_from_id(self._region)}_current_rating" @property def name(self): """Name of the sensor.""" - return f"Carbon Intensity Current Rating" + return f"Carbon Intensity {get_region_from_id(self._region)} Current Rating" @property def icon(self): @@ -57,21 +59,15 @@ def state(self): _LOGGER.info(f"Updating CarbonIntensityCurrentRating") - current_rate = None - if self.coordinator.data != None: - for period in self.coordinator.data: - if now >= period["from"] and now <= period["to"]: - current_rate = period - break - - if current_rate != None: - self._attributes = { - "rate": current_rate, - "all_rates": self.coordinator.data - } - - if current_rate != None: - self._state = current_rate["intensity_forecast"] + rates = self.coordinator.data.rates if self.coordinator is not None and self.coordinator.data is not None else None + + current_rate = get_current_rate(now, rates) + self._attributes = { + "rate": current_rate + } + + if current_rate is not None: + self._state = current_rate["intensity_forecast"] return self._state @@ -85,6 +81,8 @@ async def async_added_to_hass(self): self._state = state.state self._attributes = {} for x in state.attributes.keys(): - self._attributes[x] = state.attributes[x] + + if x != "all_rates": + self._attributes[x] = state.attributes[x] _LOGGER.debug(f'Restored CarbonIntensityCurrentRating state: {self._state}') \ No newline at end of file diff --git a/custom_components/carbon_intensity/sensors/__init__.py b/custom_components/carbon_intensity/sensors/__init__.py deleted file mode 100644 index e69de29..0000000