Australian Users: Amber electric

I spent a couple of hours yesterday on this and have got a price being calculated, but it doesnโ€™t seem to match the app at this point. Iโ€™ve found some other code so Iโ€™ll compare with that and try to understand the pricing model.
Would you just want the current import and export prices for the current 30 minute period? Or maybe prices for the next 6 periods or something?
If the latter, and you want to be able to read those from Rule Machine, I think the attributes would have to be something like:
importprice0
importprice30
importprice60
importprice90

etc.

2 Likes

Yeah good point. You want to be able to know what current price is and what next price will be over say next four periods ..so you can prep your air con to cool like crazy and then switch off before it kicks in

The main delayed use case would be knowing well in advanced that it was spiking soon and you can prepare the cooling.

Glad to hear you're looking at it

If you get it going I'll happily buy you lunch or a six pack!

For those that have access to this data and are amber electric customers who also have a smart thermostat, there are some really cool things you could do with rule machine in terms of perfect timing for when to turn the air conditioning on if it's going to be hot in the next 6 hours for example.

I'm thinking about using the Google chrome integration to announce if it sees a spike in electricity usage (I have efergy energy monitoring hooked up ).It can warn and say whatever you just turned on please turn it off to save money as it's quite expensive now.

I am with amber and really like them

Yeah I've been tracking the costs since signing up (I'm not with then yet in cooling off period)

And noticing a difference between forecast and actual. Especially last night in melbourne where I am around 6pm cause of the hot weather. Forecast was low by 12c. But my friends who use it say it's usually more accurate when it gets closer to the time.

Here's a simple device driver that gets the current import and export prices as c/kWh.

The price doesn't quite match what the app and updated website shows. I've only been a customer for less that a week but I get the impression that the website and the app also had a slight discrepancy. I think the old website was using this API - now it seems to use a graphql API query.
Once we work out how future pricing should be represented it can be extended, most of the code is already there.

/**
 *	Amber Electric Price Watch
 *  Author: Paul Donovan (paul@donovansbrain.co.uk)
 *
 *	Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *	in compliance with the License. You may obtain a copy of the License at:
 *
 *			http://www.apache.org/licenses/LICENSE-2.0
 *
 *	Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *	on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *	for the specific language governing permissions and limitations under the License.
 *
 */

static String version() {
	return "0.1"
}

static String pricesApi() {
    return "https://api.amberelectric.com.au/prices/listprices"
}

preferences {
    input("postcode", "string", title:"Postcode",
		required: true)
    input ("pollingInterval", "enum", title: "Polling Interval (minutes)", 
           options: ["5", "10", "15", "30", "60", "180"], 
           defaultValue: "15", required: true)
	input("debugEnable", "bool", title: "Enable debug logging?", defaultValue: true)
}

metadata {
	definition (name: "Amber Electric Price Watch", namespace: "donovansbrain", author: "pjam73") {

		capability "Refresh"
		capability "Polling"

		attribute "importPrice", "number"
        attribute "exportPrice", "number"
        
        command "refresh"
	}
}

void poll() {
	pullData()
}

void refresh() {
	pullData()
}

void updated() {
	if (!state.updatedLastRanAt || now() >= state.updatedLastRanAt + 2000) {
		state.updatedLastRanAt = now()
		trace "updated() called with settings: ${settings.inspect()}".toString()
        
		pullData()
		startPoll()
		if(debugEnable) runIn(1800,logsOff)
	} else {
		trace "updated() ran within the last 2 seconds - skipping"
	}
}

void logsOff() {
	debug "debug logging disabled..."
	device.updateSetting("debugEnable",[value:"false",type:"bool"])
}

void startPoll() {
	unschedule()
	// Schedule polling based on preference setting
	def sec = Math.round(Math.floor(Math.random() * 60))
	def min = Math.round(Math.floor(Math.random() * settings.pollingInterval.toInteger()))
	String cron = "${sec} ${min}/${settings.pollingInterval.toInteger()} * * * ?" // every N min
	trace "startPoll: schedule('$cron', pullData)".toString()
	schedule(cron, pullData)
}



void pullData() {

	log.info "Requesting data from Amber API"

    def postParams = [
        uri: pricesApi(),
        contentType: "application/json",
        requestContentType: "application/json",
        body : ["postcode": settings.postcode.toString()]
	]
    
    try {
        httpPost(postParams) {resp ->
            if (resp.status == 200) {
                def amberData = resp.data.data
                
                def staticImportPrices = amberData.staticPrices.E1
                def staticExportPrices = amberData.staticPrices.B1
                def variablePrices = amberData.variablePricesAndRenewables
                
                def recent30minPeriod = variablePrices.findAll { it.periodSource == "30MIN" && it.periodType == "ACTUAL" }[-1]
                def fiveMinPeriods = variablePrices.findAll { it.periodSource == "5MIN" }
                def recent5minPeriod = fiveMinPeriods? fiveMinPeriods.first() : null
                
                def impp = calculateImportPrice(staticImportPrices, recent30minPeriod)
                def expp = calculateExportPrice(staticExportPrices, recent30minPeriod)
                
                log.info "import price: ${impp}, export price: ${expp}"
                
                sendEvent(name: "importPrice", value: impp, unit: "c/kWh")
                sendEvent(name: "exportPrice", value: expp, unit: "c/kWh")
      
                
            } else {
                debug "error: HTTP status" : resp.status.toString()
            }
            
        }
    } 
	catch(Exception e) {
		log.warn "error occured: ${e}"
	}

}

def calculateImportPrice(staticImport, variablePeriod) {
    def importPrice = staticImport.totalfixedKWHPrice.toBigDecimal() + staticImport.lossFactor.toBigDecimal() * variablePeriod.wholesaleKWHPrice.toBigDecimal()
    importPrice = Math.round(importPrice)
    return importPrice
}

def calculateExportPrice(staticExport, variablePeriod) {
    def exportPrice = staticExport.totalfixedKWHPrice.toBigDecimal() - staticExport.lossFactor.toBigDecimal() * variablePeriod.wholesaleKWHPrice.toBigDecimal()
    exportPrice = Math.round(exportPrice)
    return exportPrice
}

void debug(String msg) {
	if(debugEnable) log.debug device.displayName+' - '+msg
}

void trace(String msg) {
	if(debugEnable) log.trace device.displayName+' - '+msg
}
2 Likes

fantastic work @pjam73 thank you so much.

I'll contact amber and tell them the api doesn't match the app/website and see what they say. Also will ask about future pricing.

for some reason I have two community accounts . I am @JoelD

The API I'm using is fetching the future prices. I'm just not storing it in any attributes yet because I/we need to think about how we'd like to use it. Do we just want a rolling next30, next60 next90 etc. prices, do we want a highExpenseTime set to e.g. '2020-11-05 19:00' if the price at 7pm > 100c ? etc.

hmm. Fair enough - Most people would use this with rule machine so it comes down to what is easier to use there I guess?

My use case would be as follows:
IF thermostat is HOT AND next30 Price = < X THEN
Turn on AirCon

But also I'd see a use case where if you see a price spike coming up and it's going to be hot soon for example you'd want to pre cool the house prior.
SO:
IF weather forecast is HOT and next60/90 Price = > X THEN
Turn on AirCon
If next30 price => Y
TURN off Air Con

Both use cases would use the next30/60/90 data quite well.

I think if users wanted to track highExepenseTime = date/time and that is configurable it would help but I see this been managed in RM.

what do you think?

here's the response on why it's not accurate and more details on their api:

We don't yet have a fully public API that we guarantee backwards compatibility for. We're planning to launch a fully supported public API in the next 12 months, but until then this API will give you indicative prices.
Note that you will need to switch to the new API when it is released.

Here are the full details for accessing our current API:
1. GUI (ie Postman)
Ensure you enter the method as POST and the postcode payload in the Body , not Params .
โ€‹โ€‹

โ€‹โ€‹

2. Windows based curl

curl -v -i -L https://api.amberelectric.com.au/prices/listprices -H "Content-Type: application/json" --data "{\"postcode\":\"3000\"}"

3. Unix based curl

curl -X POST \\<https://api.amberelectric.com.au/prices/listprices> \\-H 'Content-Type: application/json' \\-d '{ "postcode": "3000" }'

From the data there, the formula for usage prices is:

data.staticPrices.E1.totalfixedKWHPrice + data.staticPrices.E1.lossFactor * data.variablePricesAndRenewables.[period].wholesaleKWHPrice

And the formula for Export to Grid prices is:

data.staticPrices.B1.totalfixedKWHPrice - data.staticPrices.B1.lossFactor * data.variablePricesAndRenewables.[period].wholesaleKWHPrice

See the bottom of this email for a full example.

All timestamps in the data are market time (ie Brisbane time) and at period end , not start (so if it's 9:15am, the current period is the 9:30am one). These prices are GST inclusive, so divide by 1.1 if you need them exclusive.

Let me know if you have further questions - hopefully the example below will make things clear!

Example: Get the current general price for postcode 3000 @ 18:15 on 13/10/2020 in Melbourne
( full results in attached file listprices-postcode_3000-time_2020-10-13T17_15_57.json )

From the formula, first find the static prices (prices that do not change every 30 minutes). Use data.staticPrices.E1 as this represents the static prices for general usage.

{
"serviceResponseType": 1,
"data": {
"currentNEMtime": "2020-10-13T17:15:57",
"postcode": "3000",
...
"staticPrices": {

"E1": {
"dataAvailable": true,
"networkDailyPrice": "28.63043692",
"basicMeterDailyPrice": "0",
"additionalSmartMeterDailyPrice": "22",
"amberDailyPrice": "32.87671233",
"totalDailyPrice": "83.50714925",
"networkKWHPrice": "7.766",
"marketKWHPrice": "1.718",
"greenKWHPrice": "4.2471",
"carbonNeutralKWHPrice": "0.11",
"lossFactor": "1.04063232",
"offsetKWHPrice": "0.11",
"totalfixedKWHPrice": "9.59400",
...
},
"E2": {
...
},

"B1": {
...
}
},
"variablePricesAndRenewables": [ ... ]
},
"message": ""
}

Note: data.staticPrices.E2 is for controlled load usage and data.staticPricesB1 is for solar exports.

Store the two static price fields:

  • totalfixedKWHPrice = 9.59400
  • lossFactor = 1.04063232

Now find the variable price (price that updates every 5 minutes and is averaged over a 30 minute block). Variable prices are stored with 30 minute timestamps in market time (Brisbane time).

13/10/2020 is daylight savings time in Melbourne, so for 18:15 in Melbourne , find the timestamp for 17:15 in market time (since market time ignores daylight savings). 30 minute timestamps are stored by the 30 minute period end , so the timestamp in market time is 2020-10-13T17:30:00.

{
"serviceResponseType": 1,
"data": {
...
"staticPrices": { ... },
"variablePricesAndRenewables": [
{
"periodType": "ACTUAL",
"semiScheduledGeneration": "1140.9",
"operationalDemand": "5017.25",
"rooftopSolar": "182.761",
"createdAt": "2020-10-13T17:15:41",
"wholesaleKWHPrice": "4.2856000000000005",
"region": "VIC1",
"period": "2020-10-12T17:30:00",
"renewablesPercentage": "0.25454965383727074",
"periodSource": "30MIN",
"percentileRank": "0.4915254237288136"
},
...
{
"periodType": "ACTUAL",
"operationalDemand": "30532.94000",
"rooftopSolar": "190.04",
"wholesaleKWHPrice": "5.39879",

"region": "VIC1",
"period": "2020-10-13T17:30:00",

"periodSource": "5MIN",
"latestPeriod": "2020-10-13T17:15:00",
"usage": "30532.94000",
"renewablesPercentage": "0.17017",
"percentileRank": "0.8050847457627118"
},
{
"periodType": "FORECAST",
"semiScheduledGeneration": "645.927",
"operationalDemand": "5140.97",
"rooftopSolar": "56.497",
"forecastedAt": "2020-10-13T17:00:00",
"forecastedAt+period": "2020-10-13T17:00:00+2020-10-13T18:00:00",
"createdAt": "2020-10-13T17:15:41",
"wholesaleKWHPrice": "5.2541863",
"region": "VIC1",
"period": "2020-10-13T18:00:00", "renewablesPercentage": "0.13514737082505765",
"periodSource": "30MIN",
"percentileRank": "0.7796610169491526"
},
...
]
},
"message": ""
}

Store the variable price field:

  • wholesaleKWHPrice = 5.39879

A few things to note here:

  • For the current live price, find the latest period with type ACTUAL โ€‹
    • If periodSource is 30MIN the price has been finalised

    • If periodSource is 5MIN the price is still fluctuating every 5 minutes

  • Periods with type FORECAST are prices in the future

Now calculate the current price:

currentWholesalePrice = data.staticPrices.E1.totalfixedKWHPrice + data.staticPrices.E1.lossFactor * data.variablePricesAndRenewables.[period].wholesaleKWHPrice 
currentWholesalePrice = 9.59400 + (1.04063232 * 5.39879)
currentWholesalePrice = 15.21 cents

As indicated by periodSource and latestPeriod fields, this price updates every 5 minutes. If you want to match the live price in the Amber app, you should create an average of these 5 minute live prices over each 30 minute block.

1 Like

Not sure if this shows why it's not accurate or shows any other features I didn't post originally . Only explanation is that it's not backwards compatible fully.

Very cheap pricing Right now !! Time to use washing machine!

If only I had an electric car!

I think what would be really useful for this driver would be for it to show the cheapest price over the next 24 hours. So people could put this on a dashboard somewhere so they could glance at it and then set the dishwasher to be delayed by x hours or the washing machine or dryer rather than having to look it up in the app as well.

or even show where the cheapest price in the next 24 hours is in an hour format?

I am now on amber - going well and so far much cheaper (19c prior 15c now average)

I have updated all my automations to not run or turn off if the price spikes above 40c (i just set a virtual switch called Price Spike) - (which is about 30 minutes a day some days not often) need more time to see how it works.

Here's what i'm doing:
Lighting automation - do not turn off but allow turn off only if price spiking switch on
Cooling/heating - does not auto turn on if price spike on
fans/other automations - disable if price spike

What i am thinking of adding:
If amber alert me of an upcming spike like $3.00 or more or even $17

  • Put house into away mode essentially.
  • How I have it now - users can override but if price is that high i'd like to auto shut off anything that is turned on i.e tv/cooling/lights - pretty easy but wife may not like it
  • if we get forecast pricing - this will only be really valuable when the api is more reliable i fear... but it would be good to still start cooling the house earlier if a price of 40 cents of so is expectedin next 90 mins.

Issues:
the amber api is delayed by 20-30 mins sometimes. Last night the price went to 43 c per hour and the api was showing 20 for a good 20 minutes before it caught up. (I have asked amber if they are improving current api at all or do we need to wait for full public... guessing the answer is the latter

Do you guy's have Solar Systems? What are the prices like in the evening peak period when your Solar fades out, it's hot and your AC is running?

i don't - but you can make good money if the export price is high and you don't use your solar power (including aircon/power) go out to the local mall :slight_smile:

in evening last night it was 40 c for about 1 hour around 6/7pm

1 Like

Even on a perfect day my system isn't making much power after 6pm (~1.5kW) and if it's hot I'm importing power as my AC cools down upstairs.

https://pvoutput.org/intraday.jsp?id=4988&sid=27734&dt=20201111&gs=0&m=0

it can happen during the day but i have not seen that data yet

I'm definitely going to have a look - currently I have a pretty good deal:

29c p/kWh Peak
14c p/kWh Off-peak (inc weekends)
10.2c p/kWh Solar FiT
Daily Charge $1.08

1 Like

Apparently there is a 5min price and 30 min price. Hence the delay. Nice.

Going to try this out here's the detail from amber:

During a 30 minute period, the price is updated every 5 minutes in the energy market. These are returned as 5MIN prices in the API. During a 30 minute period, the live price shown in the app is an average of these 5MIN periods (we collect each of the 5MIN prices and average them). At 25 minutes into a 30 minute period , the price is finalised and is returned as a 30MIN price in the API.

So at 12:20, the latest 30MIN price will give you the price at 12:00 (you should actually be using the 5MIN price). At 12:25, the latest 30MIN price will give you the 12:30 price (you should be using the 30MIN price).

In the code you shared, you're only using the 30MIN price, never the 5MIN price. This explains why your data is always at least 25 minutes out of date. :+1:โ€‹

If you update your code to use recent5minPeriod when available, you should get a result closer to the app. It won't be perfect as this won't perform the average of all 5MIN prices like the app does.