[OH4] Octopus Energy (UK) - real-time electricity consumption data with Home Mini

Overview

If you are a customer of Octopus Energy in the UK, and you have a Home Mini installed and working, then you are uploading your consumption data to Octopus every 10 seconds.

You are able to access this data via their GraphQL API at a refresh rate of roughly 30 seconds, providing near real-time electricity consumption data. Whilst the obvious down-side is that you are relying on a non-local third-party service to provide data, the obvious up-side is that this data is the exact data that is used to generate your bills.

Prerequisites

  • Octopus Home Mini
  • Octopus API Key

Method

1 Find meter GUID

Copied directly from Victoria Scales’s blog, first obtain a short-lived access token, replacing <API_KEY> with your API key:

curl -v \
    https://api.octopus.energy/v1/graphql/ \
    --data '{ "query": "mutation {obtainKrakenToken(input: {APIKey:\"<API_KEY>\"}) {token}}"}' \
    -H "Content-Type: application/json"

With this access token, now find the meter GUID, replacing the two <…> placeholders:

curl -v \
    https://api.octopus.energy/v1/graphql/ \
    --data '{ "query": "query MyQuery {account(accountNumber: \"<YOUR_ACCOUNT_NUMBER>\") {electricityAgreements(active: true){meterPoint {meters(includeInactive: false) {smartDevices {deviceId}}}}}}"}' \
    -H "Content-Type: application/json" \
    -H "Authorization: JWT <TOKEN_FROM_OBTAIN_TOKEN_RESPONSE>"

Extract the meter GUID value from the deviceID key. It should be 16 characters in pairs of two, each pair separated by hyphens (eg. AA-BB-CC-DD-EE-FF-GG-HH).

2 Create items

We will be importing two bits of data, for which we will need an Item each:

  1. Live demand (instant power draw, in Watts)
  2. Cumulative consumption (effectively your meter reading, in kWh)

I have created my items using Items files, which includes an Equipment item WebService so I can easily find this data in the semantic model:

//EQUIPMENT
Group gElectricityLive "Electricity consumption (Octopus)" (gElectricitySupply) ["WebService"]

//LIVE CONSUMPTION DATA
Number nElectricityDemand "Live electricity demand" (gElectricityLive) ["Measurement", "Power"]
Number nElectricityMeterReading "Live electricity meter reading" (gElectricityLive) ["Measurement", "Energy"]

3 Setup rule

We will import this data into the two Items using Javascript in a rule. (I had originally wanted to do all this via the HTTP binding, but couldn’t find a clean way of doing so.)

I have setup the rule in two steps:

  1. Create a script
  2. Create a rule which calls the script every 30 seconds.

You may wish to combine the two steps into a single rule.

Script: live_octopus_energy_data

A JavaScript script:

var url = "https://api.octopus.energy/v1/graphql/"
var contentType = "application/json"

//Get token first
var content1 = '{ "query": "mutation {obtainKrakenToken(input: {APIKey:\\"<YOUR_API_KEY>\\"}) {token refreshToken refreshExpiresIn}}"}'
var json_data1 = actions.HTTP.sendHttpPostRequest(url, contentType, content1)
var token = JSON.parse(json_data1).data.obtainKrakenToken.token

//Generate header
var headers = []
headers["Authorization"] = "JWT " + token 
//Generate HTTP post content
var content2 = '{ "query": "{smartMeterTelemetry(deviceId: \\"<YOUR_METER_GUID>\\") {readAt demand consumption}}"}'

//Grab data
var json_data2 = actions.HTTP.sendHttpPostRequest(url, contentType, content2, headers, 10000)
//Convert data to JSON object
var json_obj = JSON.parse(json_data2)

if (json_obj.data.smartMeterTelemetry != null) {
  items.getItem('nElectricityDemand').postUpdate(json_obj.data.smartMeterTelemetry[0].demand)
  items.getItem('nElectricityMeterReading').postUpdate(json_obj.data.smartMeterTelemetry[0].consumption/1000)
}

Note that I divide the consumption value by 1000 to generate kWh from the returned Wh.

Rule:

configuration: {}
triggers:
  - id: "1"
    configuration:
      cronExpression: 0/30 * * * * ? *
    type: timer.GenericCronTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      considerConditions: true
      ruleUIDs:
        - live_octopus_energy_data
    type: core.RunRuleAction

I tried refresh rates faster than 30 seconds, but found I was hitting rate limits. It seems like the Octopus API is happy receiving data requests every 30 seconds.

2 Likes

This is amazing thank you. I have been trying to do this since I got my Home Mini! I can now finally get rid of my old CurrentCost energy monitor!

I did make some changes to the rule so that it works on OH3. I really should upgrade, something for the new year.

rule AgileRealTime
when
	Time cron "0/30 * * * * ? *"  //every 30 secs
then
    logWarn ("rules.AgileRealTime" , "Attempting to get new realtime octopus smart meter data")

    var url = "https://api.octopus.energy/v1/graphql/"
    var contentType = "application/json"

    // Get token first
    var content1 = '{ "query": "mutation {obtainKrakenToken(input: {APIKey:\\"<api key>\\"}) {token refreshToken refreshExpiresIn}}"}'
    var json_data1 = sendHttpPostRequest(url, contentType, content1)
    var token = transform("JSONPATH", "$.data.obtainKrakenToken.token", json_data1)	

    // Generate header
    val headers = newHashMap("Authorization" -> "JWT " + token)
    
    // Generate HTTP post content
    var content2 = '{ "query": "{smartMeterTelemetry(deviceId: \\"<device id>\\") {readAt demand consumption}}"}'

    // Grab data
    var json_data2 = sendHttpPostRequest(url, contentType, content2, headers, 10000)

    // Update items
    nElectricityDemand.postUpdate(transform("JSONPATH", "$.data.smartMeterTelemetry[0].demand", json_data2) )
    nElectricityMeterReading.postUpdate(Integer::parseInt(transform("JSONPATH", "$.data.smartMeterTelemetry[0].consumption", json_data2))/1000)
    
end
1 Like

Looks great! I assume this is RulesDSL? If so, that still works fine in OH4!

Yes. That’s good to know it will working in OH4 (and my other rules). It’ll definitely make things easier for me when I finally upgrade!

Just got it working for gas too. You’ll need to get your gas meter guid the same as the electric just changing electricityAgreements to gasAgreements.

octopus.items

Number GasMeterDemand "Gas usage (Home Mini) [%.2f kWh]" 
Number GasMeterReading "Live gas meter reading [%.2f]"  

octopus.rules

rule OctopusGasMeterUpdate
when
	Time cron "0 0/30 * * * ? *"  // run evry half hour
then
    logWarn ("rules.OctopusMeterTelemetryGas" , "Get realtime octopus smart meter gas data")
    var url = "https://api.octopus.energy/v1/graphql/"
    var contentType = "application/json"

    // Get token first
    var content1 = '{ "query": "mutation {obtainKrakenToken(input: {APIKey:\\"<api key>\\"}) {token refreshToken refreshExpiresIn}}"}'
    var json_data1 = sendHttpPostRequest(url, contentType, content1)
    var token = transform("JSONPATH", "$.data.obtainKrakenToken.token", json_data1)	
    
    // Generate header
    val headers = newHashMap("Authorization" -> "JWT " + token)
    
    // Generate HTTP post content
    var content2 = '{ "query": "{smartMeterTelemetry(deviceId: \\"<device id>\\") {readAt demand consumption}}"}'

    // Grab data
    var json_data2 = sendHttpPostRequest(url, contentType, content2, headers, 10000)    
    logWarn ("rules.OctopusMeterTelemetryGas" , "Response realtime octopus smart meter gas data : " + json_data2)

    // Set default 0 values if not set to stop parse errors
    if (GasMeterReading.state === NULL){ GasMeterReading.postUpdate(0) }

    // Update items
    var consumption = (Double::parseDouble(transform("JSONPATH", "$.data.smartMeterTelemetry[0].consumption", json_data2))) / 1000;
    var demand = consumption - (GasMeterReading.state as DecimalType).doubleValue;  // demand is null from kraken so calculating from consumption - last value
    GasMeterDemand.postUpdate(demand)
    GasMeterReading.postUpdate(consumption)
end

1 Like

Looks great!

You probably already know this, but just in case: bear in mind that in the UK, most (all?) gas smart meters are battery powered, and only send a reading via your mains-powered electricity meter every 30 minutes. This is true even with the Octopus Mini: the Mini itself will upload gas data every 10 seconds or so, but it’ll be the same meter reading for 30 minutes. Thus ditto for the data downloaded from the Kraken API - it only has a resolution of 30 minutes (for gas), as evidenced in the graph!

1 Like

Hi

See below, as I had this working and was meaning to post before, thought I would add it here.
The Octopus Kraken Token has a 1 hour life time so no need to get it every 30 seconds. I use the http binding with the following file configuration to get a token only when needed.

Just substitute in the below example with the API Key found via your Octopus online account under personal details

Thing http:url:auth "Octopus Electricity Kraken token" [
    baseURL ="https://api.octopus.energy/v1/graphql/",
    stateMethod = "POST",
    commandMethod ="POST",
    headers = "content-type = application/json",
    refresh = 3570]{ //3570 59 mins. Token lasts 1 hour
        Type string: token "Octopus Token" [stateTransformation="JSONPATH:$.data.obtainKrakenToken.token",stateContent="{\"query\":\"mutation obtainKrakenToken($input:ObtainJSONWebTokenInput!){obtainKrakenToken(input:$input){refreshToken refreshExpiresIn payload token}}\",\"variables\":{\"input\":{\"APIKey\":\"<TOKEN>\"}},\"operationName\":\"obtainKrakenToken\"}"]
    }

you will also need an item like:

String    octopusToken    "Octopus_Token [%s] "   {channel="http:url:auth:token"}

and my rule uses:

var String token = octopusToken.state.toString

This is excellent. What would also be great is to be able to calculate daily & monthly usage costs based on this data. I use Intelligent Octopus Go, so have cheaper rates between 11.30pm and 5.30am. Would it be possible to include usage costs in this as well?

Hi Lee

There is a wealth of data available, have a browse through the Kraken API
Kraken Mutations
You can test these out and get your particular case working using the GraphQL interface.
GraphQL.
There is also a more basic Octopus REST API that might be an easier start if you want.
Octopus Rest API

If you get something working remember to share with the community :grinning_face: