Skip to content

Commit

Permalink
Add ability to query the Agile Outgoing Tariff to find highest export…
Browse files Browse the repository at this point in the history
… price

`import` and `export` should be set to True or False as required,
`import = True` and `export = False` for the Agile Octopus tariff
and `import = False` and `export = True` for the Agile Outgoing Octopus tariff.

The defaults if these options are not found in the apps.yaml file
are: `import = True` and `export = False`
  • Loading branch information
badguy99 committed Mar 29, 2020
1 parent c1791c6 commit 45696c8
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 30 deletions.
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
# octoblock
Octoblock is an app which works under [AppDaemon](https://www.home-assistant.io/docs/ecosystem/appdaemon/) within [Home Assistant](https://www.home-assistant.io/) which finds the cheapest “n” hour block and works out the price of that block, for the Octopus Energy, Agile Octopus tariff. It creates and sets sensors for the cost and start time, for example, using the apps.yaml file below, the following entities are created and then updated:
Octoblock is an app which works under [AppDaemon](https://www.home-assistant.io/docs/ecosystem/appdaemon/) within [Home Assistant](https://www.home-assistant.io/) which finds the cheapest “n” hour block for import or the most expensive “n” hour block for export, and works out the price of that block, for the Octopus Energy, Agile Octopus / Agile Outgoing Octopus tariffs. It creates and sets sensors for the cost and start time, for example, using the apps.yaml file below, the following entities are created and then updated:
```
sensor.octopus_1hour_time
sensor.octopus_1hour_cost
sensor.octopus_1hour_price
sensor.octopus_1_5hour_time
sensor.octopus_1_5hour_cost
sensor.octopus_1_5hour_price
```

Sensors for export will be created with naming such as:
```
sensor.octopus_export_1hour_time
sensor.octopus_export_1hour_price
```

[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs)
Expand Down Expand Up @@ -41,6 +47,8 @@ The module and class sections need to remain as above, other sections should be
| hour | Yes | 1 |
| start_period | Yes | today |
| use_timezone | Yes | True |
| import | Yes | True |
| export | Yes | False |
You can have multiple blocks with different time periods (`hour` setting) or starting points (`start_period` setting) as needed. It will work with whole hour or half hour blocks in the `hour` setting.

Expand All @@ -64,6 +72,8 @@ Using `today` start_period this has only turned on once during the day

`use_timezone` can be set to True or False, and defaults to False, it allows you to specify if the date/time should be displayed in UTC (False), or using Europe/London (True) as the timezone. For example, `2020-03-29T02:00:00Z` or `2020-03-29T03:00:00 BST` respectively.

`import` and `export` should be set to True or False as required, `import = True` and `export = False` for the Agile Octopus tariff and `import = False` and `export = True` for the Agile Outgoing Octopus tariff.

### Home Assistant Automation

The created start time sensors can then be used to trigger automations within Home Assistant.
Expand Down
94 changes: 67 additions & 27 deletions apps/octoblock/octoblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,25 @@ class OctoBlock(hass.Hass):
def initialize(self):
time = datetime.datetime.now()
time = time + datetime.timedelta(seconds=5)
self.run_every(self.get_best_period_and_cost, time, 30 * 60)
self.run_every(self.period_and_cost_callback, time, 30 * 60)

def get_best_period_and_cost(self, kwargs):
hours = self.args.get('hour', 1)
def period_and_cost_callback(self, kwargs):
self.hours = self.args.get('hour', 1)
region = self.args.get('region', 'H')
start_period = self.args.get('start_period', 'now')
start_period = str(start_period).lower()
use_timezone = self.args.get('use_timezone', False)
self.use_timezone = self.args.get('use_timezone', False)
self.incoming = self.args.get('import', True)
self.outgoing = self.args.get('export', False)

if self.outgoing:
# incoming defaults to True in case it hasnt been added to
# apps.yaml as it wasnt an option when originally released
# However if export is True, import must be False
self.incoming = False
if self.args.get('import') and self.args.get('export'):
self.log('import and export should not both be True in the ' +
'same configuration block', level='ERROR')
if start_period == 'today':
d = datetime.date.today().isoformat() + 'T00:00:00'
elif start_period == 'now':
Expand All @@ -29,52 +39,82 @@ def get_best_period_and_cost(self, kwargs):
' defaulting to "now"')
d = datetime.datetime.now().isoformat()

r = requests.get(
'https://api.octopus.energy/v1/products/AGILE-18-02-21/' +
'electricity-tariffs/E-1R-AGILE-18-02-21-' +
str(region).upper() + '/standard-unit-rates/?period_from=' + d)
self.get_period_and_cost(region, d)

hours = str(self.hours).replace(".", "_")
if self.incoming:
self.set_state('sensor.octopus_' + hours + 'hour_time',
state=self.time,
attributes={'icon': 'mdi:clock-outline'})
self.set_state('sensor.octopus_' + hours + 'hour_price',
state=round(self.price, 4),
attributes={'unit_of_measurement': 'p/kWh',
'icon': 'mdi:flash'})
elif self.outgoing:
self.set_state('sensor.octopus_export_' + hours + 'hour_time',
state=self.time,
attributes={'icon': 'mdi:clock-outline'})
self.set_state('sensor.octopus_export_' + hours + 'hour_price',
state=round(self.price, 4),
attributes={'unit_of_measurement': 'p/kWh',
'icon': 'mdi:flash-outline'})

def get_period_and_cost(self, region, timeperiod):
baseurl = 'https://api.octopus.energy/v1/products/'
if self.incoming:
r = requests.get(
baseurl + 'AGILE-18-02-21/electricity-tariffs/' +
'E-1R-AGILE-18-02-21-' + str(region).upper() +
'/standard-unit-rates/?period_from=' + timeperiod)
elif self.outgoing:
r = requests.get(
baseurl + 'AGILE-OUTGOING-19-05-13/electricity-tariffs/' +
'E-1R-AGILE-OUTGOING-19-05-13-' + str(region).upper() +
'/standard-unit-rates/?period_from=' + timeperiod)

tariff = json.loads(r.text)
tariffresults = tariff[u'results']
tariffresults.reverse()

blocks = float(hours) * 2
blocks = float(self.hours) * 2
blocks = int(blocks)

for period in tariffresults:
curridx = tariffresults.index(period)
tr_len = len(tariffresults)
if curridx > tr_len-blocks:
period[str(hours) + '_hour_average'] = 99
if self.incoming:
period[str(self.hours) + '_hour_average'] = 99
elif self.outgoing:
period[str(self.hours) + '_hour_average'] = 0
continue
cost = 0
for block in range(blocks):
cost = cost + (tariffresults[curridx+block][u'value_inc_vat'])
cost = cost / blocks
period[str(hours) + '_hour_average'] = cost
period[str(self.hours) + '_hour_average'] = cost

self.minprice = min(
period[str(hours) + '_hour_average'] for period in tariffresults)
self.log('Lowest average price for {} hour block'.format(str(hours)) +
' is: {} p/kWh'.format(self.minprice))
if self.incoming:
self.price = min(
period[str(self.hours) + '_hour_average']
for period in tariffresults)
self.log('Lowest average price for {}'.format(str(self.hours)) +
' hour block is: {} p/kWh'.format(self.price))
elif self.outgoing:
self.price = max(
period[str(self.hours) + '_hour_average']
for period in tariffresults)
self.log('Highest average price for {}'.format(str(self.hours)) +
' hour block is: {} p/kWh'.format(self.price))

for period in tariffresults:
if period[str(hours) + '_hour_average'] == self.minprice:
if period[str(self.hours) + '_hour_average'] == self.price:
self.time = period[u'valid_from']
if use_timezone:
if self.use_timezone:
fmt = '%Y-%m-%dT%H:%M:%S %Z'
greenwich = pytz.timezone('Europe/London')
date_time = dateutil.parser.parse(self.time)
local_datetime = date_time.astimezone(greenwich)
self.time = local_datetime.strftime(fmt)
self.log('Lowest priced {} hour period'.format(str(hours)) +
self.log('Best priced {} hour period'.format(str(self.hours)) +
' starts at: {}'.format(self.time))

hours = str(hours).replace(".", "_")
self.set_state('sensor.octopus_' + hours + 'hour_time',
state=self.time,
attributes={'icon': 'mdi:clock-outline'})
self.set_state('sensor.octopus_' + hours + 'hour_price',
state=round(self.minprice, 4),
attributes={'unit_of_measurement': 'p/kWh',
'icon': 'mdi:flash'})

0 comments on commit 45696c8

Please sign in to comment.