Looks good. How are you pulling the grid intensity and from where? I’ve got a REST sensor setup but I’m not overly happy with it.
I really wish there was a better gauge in HA or Node-RED.
Looks good. How are you pulling the grid intensity and from where? I’ve got a REST sensor setup but I’m not overly happy with it.
I really wish there was a better gauge in HA or Node-RED.
Here’s my sensor. Documentation, including region IDs (pasted below), is here.
- platform: rest
resource: https://api.carbonintensity.org.uk/regional/regionid/8
name: "West Midlands carbon intensity"
unit_of_measurement: 'g/kWh'
value_template: '{{ value_json.data.0.data.0.intensity.forecast }}'
scan_interval: 600
1 North Scotland
2 South Scotland
3 North West England
4 North East England
5 South Yorkshire
6 North Wales, Merseyside and Cheshire
7 South Wales
8 West Midlands
9 East Midlands
10 East England
11 South West England
12 South England
13 London
14 South East England
15 England
16 Scotland
17 Wales
Thanks - almost exactly the same.
Have you found anywhere that tells you the boundaries for their very low / low / medium
etc values?
I pull off the intensity as the state and the forecast as an attribute.
- platform: rest
name: Carbon Intensity
resource: https://api.carbonintensity.org.uk/regional/regionid/2
value_template: '{{ value_json.data[0].data[0].intensity.index }}'
json_attributes_path: "$.data[0].data[0].intensity"
json_attributes:
- "forecast"
I haven’t, no. I’d imagine this is deliberate on their part to allow them to pull the category thresholds about as more renewables come online and relative performance changes.
For example, the average for 2012 was over 500g/kWh, and this was down to under 200g/kWh in 2019. Progress on the UK grid has been phenomenal and generally under-reported.
I’ve added this in now. Setting hours
to 0
with start_period
set to now
in apps.yaml will return the current import or export price. And set sensors sensor.octopus_current_price
or sensor.octopus_export_current_price
as appropriate.
Here’s my script for working out the cheapest n-block cost, which I call octoplan.py
. Seems to do broadly the same as @badguy’s octoblock
but without needing appdaemon
. It’s a bit less “integrated” as a result, of course: it’s read with a command_line
sensor to give the cheapest times display as per my screenshot above; and also queried with a bash script (run via a shell_command
automation) that writes the immersion start time to a text file that HA reads with another command_line
sensor. Clunky, but I understand what it’s doing.
The from
and to
arguments need to be in recognizable time formats, and it’ll probably fail in all sorts of horrible ways if you feed it invalid arguments.
My most recent addition is the latest
flag, which I use for the immersion schedule. If there are two or more periods with exactly the same cost in the time span being evaluated, the default is to return the earliest. If you specify --latest
, it’ll instead return the latest block which makes more sense for an overnight immersion run.
The first line refers to octopus_lib
. This is Octopus’s David Winterbottom’s client.py
from here which I have downloaded and renamed octopus_lib.py
to give me half a chance of remembering what it is in three years’ time. You will need to ensure you have installed the Python requests
library as well as the imports below (dateutil
is installed as py-dateutil
).
The final section of the script generates an output list of time/cost dictionaries of cheapest times averaged over 30m in output[0]
to 4h in output[7]
. The final line prints out the JSON for 30m, 1h, 2h and 4h only, but you can select others if needed.
If you use it and it breaks, you get to keep all the pieces. Please be considerate in querying Octopus’s API: I run this half-hourly via an automation trigger (see earlier posts).
from octopus_lib import APIClient
import argparse
import datetime
import json
from dateutil import parser
from dateutil.tz import tzlocal
APIKEY = 'YOUR_API_KEY'
REGIONCODE = 'E' # replace with your region code
argparser = argparse.ArgumentParser(description='Finds cheapest Agile chunks')
argparser.add_argument('-f',
'--from',
help='Start time for search',
required=False)
argparser.add_argument('-t',
'--to',
help='End time for search',
required=False)
argparser.add_argument('-l',
'--latest',
help='Find latest rather than earliest occurrence',
action='store_true',
required=False)
args = vars(argparser.parse_args())
from_ts = None
to_ts = None
if args['from'] is not None:
from_ts = parser.parse(args['from']).astimezone(tzlocal())
if args['to'] is not None:
to_ts = parser.parse(args['to']).astimezone(tzlocal())
latest = args['latest']
# pull in the rates
octo = APIClient(APIKEY)
now = datetime.datetime.now()
rates = octo.agile_tariff_unit_rates(REGIONCODE, period_from=now)
# API returns rates as latest-first, fix that...
rates['results'].reverse()
# Extract costs and times into lists
costs = []
times = []
for result in rates['results']:
period_str = result['valid_from']
time_ts = parser.parse(timestr=period_str).astimezone(tzlocal())
if from_ts is not None and time_ts < from_ts:
# start specified and not yet reached: continue searching
continue
if to_ts is not None and time_ts >= to_ts:
# end specified and reached: break out
break
times.append(time_ts)
costs.append(result['value_inc_vat'])
# Build a matrix of half-hour periods (vertical) and 1-8 averaging periods
# (horizontal) (so second column is hour average costs)
cost_matrix = []
for x in range(len(costs)):
cost_row = [costs[x]]
for y in range(1, 8):
if len(costs) - x < y + 1:
# table ends before averaging period
cost_row.append(None)
else:
cost_total = 0
for z in range(y + 1):
cost_total = cost_total + costs[x + z]
cost_row.append(round(cost_total / (z + 1), 2))
cost_matrix.append(cost_row)
output = []
for x in range(8):
# Build column, find minimum
cost_col = []
for y in range(len(costs)):
if cost_matrix[y][x] is not None:
cost_col.append(cost_matrix[y][x])
if latest:
mindex = len(cost_col) - 1 - cost_col[::-1].index(min(cost_col))
else:
mindex = cost_col.index(min(cost_col))
output.append({"time": "%s" % times[mindex].isoformat(),
"cost": "%.2f" % cost_col[mindex]})
# output a list for cheapest 30m, 1h, 2h and 4h
print(json.dumps([output[0], output[1], output[3], output[7]]))
Just added this in but get the following error:
2020-04-13 10:04:44.829543 WARNING octo_block_now: Traceback (most recent call last):
File "/usr/lib/python3.8/site-packages/appdaemon/threading.py", line 766, in worker
funcref(self.AD.sched.sanitize_timer_kwargs(app, args["kwargs"]))
File "/config/appdaemon/apps/octoblock/octoblock.py", line 42, in period_and_cost_callback
elif start_period == 'now':
File "/config/appdaemon/apps/octoblock/octoblock.py", line 94, in get_period_and_cost
ZeroDivisionError: division by zero
@james_hiscott I’m hoping that is just a transient issue due to the way the apps restart and threading in appdaemon, as there is no way the new code should be able to get there if hours = 0.
yup, restart of appdaemon fixed it. Should of tired that first.
PS. that 1.7 update is a very nice idea. Thanks for all the hard work on this, it is the backbone of lots of my automatons now
Been reading this topic with interest because I’m thinking about getting an EV and I’m already with octopus (albeit a normal plan), I was wondering if there’s anyway to dynamically get the best n hours?
I’m thinking about the following potential scenario. I know my ev needs so much power to fill which I dynamically work out means today I need 3 hours charge, then I could query your component to find the cheapest 3 hour charge today. But tomorrow it could be 2 hours etc. Possible?
With the node I wrote, yes you can specify the window size. Just always request a number of windows, they are returned as an array.
However, one of the gotchas currently is that the assumption is the cheapest window will be overnight. Recently (in the UK) that has not been the case.
I really need to add some additional logic so you can specify the window within which you want the cheapest period window, so between 8pm and 8am when the car is home for instance. Currently with the cheapest in the afternoon, the car would not charge overnight!
That’s what my script a couple of posts up does. You can supply the from and to times (within “now” to the end of the available data), and it returns a list of 1–8 block (30m to 4h) cheapest periods within those times, assuming equal power consumption across the window. In reality, an EV will slow its charge down as it nears 100%.
Hi all - relative new comer to HA - I’ve been gradually been migrating my domoticz setup and now i’m looking to add Octopus usage data to my new dashboard.
I’m currently pursuing the Octocost option but getting stuck right at the end.
Over the last few days I’ve installed the SSH module, HACS, APPDaemon and installed the Octocost app through HACS and configured my Octopus API settings in Apps.yaml all without issue. But for some reason, I’m not seeing any of the sensor entities show up.
There’s nothing obvious in the log.
Until now, all my integrations have shown up automatically, and that’s what I was expecting having added my API settings and restarted the server, but nothing - no Octopus entities in config/entities at all.
The last step in the Octocost readme shows the attached - as a newcomer I thought this was just a markdown way of illustrating what lovelace UI should be rendering automatically but am I supposed to do something manually? I tried it in configuration.yaml but from the error messages - that is obviously not the correct place for it.
Many thanks
Check the AppDaemon logs.
Hey @baz123 - thanks hadn’t thought to look into AppDaemon stuff at all
Logs themselves are empty but the ‘state’ column for the Octocost app shows initialize_error
Can you restart AppDaemon, then refresh the AppDaemon logs and see if they have anything useful in. Also could you post your apps.yaml configuration for Octocost, with sensitive information (MPAN, Serial number, API auth key) redacted
As @badguy says, if you restart AppDaemon and refresh the logs, you will probably see an error message (if there is one). Need to keep refreshing - doesn’t auto refresh.
getting somewhere now
Logs show this
2020-04-24 10:44:09.362206 WARNING octocost: ------------------------------------------------------------
2020-04-24 10:44:09.360828 WARNING octocost: Traceback (most recent call last):
File "/usr/lib/python3.8/site-packages/appdaemon/app_management.py", line 145, in initialize_app
await utils.run_in_executor(self, init)
File "/usr/lib/python3.8/site-packages/appdaemon/utils.py", line 276, in run_in_executor
response = future.result()
File "/usr/lib/python3.8/concurrent/futures/thread.py", line 57, in run
result = self.fn(*self.args, **self.kwargs)
File "/config/appdaemon/apps/octocost/octocost.py", line 25, in initialize
self.run_every(self.cost_and_usage_callback, time, 120 * 60)
File "/usr/lib/python3.8/site-packages/appdaemon/utils.py", line 191, in inner_sync_wrapper
f = run_coroutine_threadsafe(self, coro(self, *args, **kwargs))
File "/usr/lib/python3.8/site-packages/appdaemon/utils.py", line 285, in run_coroutine_threadsafe
result = future.result(self.AD.internal_function_timeout)
File "/usr/lib/python3.8/concurrent/futures/_base.py", line 439, in result
return self.__get_result()
File "/usr/lib/python3.8/concurrent/futures/_base.py", line 388, in __get_result
raise self._exception
File "/usr/lib/python3.8/site-packages/appdaemon/adapi.py", line 2476, in run_every
raise ValueError("start cannot be in the past")
ValueError: start cannot be in the past
2020-04-24 10:44:09.353797 WARNING octocost: ------------------------------------------------------------
2020-04-24 10:44:09.352734 WARNING octocost: Unexpected error running initialize() for octocost
2020-04-24 10:44:09.351262 WARNING octocost: ------------------------------------------------------------
and the apps.yaml
octocost:
module: octocost
class: OctoCost
region: J
mpan: <my mpan is here>
serial: <my serial is here>
auth: <my octo Api key is here>
startdate: 2020-05-05
Tried the start date in january and in may in case the error was about that.