Octopus Agile pricing binding

Hi all,

Just been searching to see whether there is already a binding to get the current and future prices of electricity on the Octopus Agile tariff in the UK. The price of power is different every half hour (sometimes you even get paid to use electricity). I’d love a binding that grabbed the data as it would make it possible to write rules to run appliances when the price is lowest or pause appliances when it goes over a certain threshold.

At this stage, I’m wondering if anyone else has this tariff and whether others would be interested in such a binding?

I imagine that there are/will be many electricity suppliers that offer this so perhaps it exists and if it was created should be generic so other providers can be used.


1 Like

I’ve just swapped to Octopus on the recommendations of a friend, with the view to considering their Agile tariff.

I’d be interested in something that showed the rate.

I don’t have this tariff yet as I’m tied to my current supplier until July next year but I do like the look of it.

Looking ahead, if I do go for it, I would like to see a binding that does current rate and the future rates for the next 24hrs.

It should be possible to do something with the HTTP1 binding but a dedicated binding could be better, including channels like “cheapest half hour”, “cheapest hour” or “cheapest 3 hours” returned as DateTime to allow starting appliances.

URL for the rates is here.

You can constrain the data coming back using the from and to times as per this example.

You’ll need to use the appropriate region code (appears immediately before /standard…):
A – Eastern England
B – East Midlands
C – London
D – Merseyside and Northern Wales
E – West Midlands
F – North Eastern England
G – North Western England
H – Southern England
J – South Eastern England
K – Southern Wales
L – South Western England
M – Yorkshire
N – Southern Scotland
P – Northern Scotland

Install the binding http1 either in your addons.cfg file or using PaperUI
Configure an http cache in your http.cfg file:


You can do this without a cache but it will make multiple calls on the API; this method calls it once every four hours. You can’t specify a time to make the http call, only a refresh interval but this will call it at least once between 1600 and 0000 (the time between when it should be updated and when the current day’s data runs out.

Here’s a clunky way of populating a set of items by creating an item for each half hourly price:

Number AgilePrice2230 "Agile Price at 2230" { http="<[Agile:3600000:JSONPATH($.results[0].value_inc_vat)]" }
Number AgilePrice2200 "Agile Price at 2200" { http="<[Agile:3600000:JSONPATH($.results[1].value_inc_vat)]" }
Number AgilePrice2130 "Agile Price at 2130" { http="<[Agile:3600000:JSONPATH($.results[2].value_inc_vat)]" }
Number AgilePrice2100 "Agile Price at 2100" { http="<[Agile:3600000:JSONPATH($.results[3].value_inc_vat)]" }
Number AgilePrice2030 "Agile Price at 2030" { http="<[Agile:3600000:JSONPATH($.results[4].value_inc_vat)]" }
Number AgilePrice2000 "Agile Price at 2000" { http="<[Agile:3600000:JSONPATH($.results[5].value_inc_vat)]" }
Number AgilePrice1930 "Agile Price at 1930" { http="<[Agile:3600000:JSONPATH($.results[6].value_inc_vat)]" }
Number AgilePrice1900 "Agile Price at 1900" { http="<[Agile:3600000:JSONPATH($.results[7].value_inc_vat)]" }
Number AgilePrice1830 "Agile Price at 1830" { http="<[Agile:3600000:JSONPATH($.results[8].value_inc_vat)]" }
Number AgilePrice1800 "Agile Price at 1800" { http="<[Agile:3600000:JSONPATH($.results[9].value_inc_vat)]" }
Number AgilePrice1730 "Agile Price at 1730" { http="<[Agile:3600000:JSONPATH($.results[10].value_inc_vat)]" }
Number AgilePrice1700 "Agile Price at 1700" { http="<[Agile:3600000:JSONPATH($.results[11].value_inc_vat)]" }
Number AgilePrice1630 "Agile Price at 1630" { http="<[Agile:3600000:JSONPATH($.results[12].value_inc_vat)]" }
Number AgilePrice1600 "Agile Price at 1600" { http="<[Agile:3600000:JSONPATH($.results[13].value_inc_vat)]" }
Number AgilePrice1530 "Agile Price at 1530" { http="<[Agile:3600000:JSONPATH($.results[14].value_inc_vat)]" }
Number AgilePrice1500 "Agile Price at 1500" { http="<[Agile:3600000:JSONPATH($.results[15].value_inc_vat)]" }
Number AgilePrice1430 "Agile Price at 1430" { http="<[Agile:3600000:JSONPATH($.results[16].value_inc_vat)]" }
Number AgilePrice1400 "Agile Price at 1400" { http="<[Agile:3600000:JSONPATH($.results[17].value_inc_vat)]" }
Number AgilePrice1330 "Agile Price at 1330" { http="<[Agile:3600000:JSONPATH($.results[18].value_inc_vat)]" }
Number AgilePrice1300 "Agile Price at 1300" { http="<[Agile:3600000:JSONPATH($.results[19].value_inc_vat)]" }
Number AgilePrice1230 "Agile Price at 1230" { http="<[Agile:3600000:JSONPATH($.results[20].value_inc_vat)]" }
Number AgilePrice1200 "Agile Price at 1200" { http="<[Agile:3600000:JSONPATH($.results[21].value_inc_vat)]" }
Number AgilePrice1130 "Agile Price at 1130" { http="<[Agile:3600000:JSONPATH($.results[22].value_inc_vat)]" }
Number AgilePrice1100 "Agile Price at 1100" { http="<[Agile:3600000:JSONPATH($.results[23].value_inc_vat)]" }
Number AgilePrice1030 "Agile Price at 1030" { http="<[Agile:3600000:JSONPATH($.results[24].value_inc_vat)]" }
Number AgilePrice1000 "Agile Price at 1000" { http="<[Agile:3600000:JSONPATH($.results[25].value_inc_vat)]" }
Number AgilePrice0930 "Agile Price at 0930" { http="<[Agile:3600000:JSONPATH($.results[26].value_inc_vat)]" }
Number AgilePrice0900 "Agile Price at 0900" { http="<[Agile:3600000:JSONPATH($.results[27].value_inc_vat)]" }
Number AgilePrice0830 "Agile Price at 0830" { http="<[Agile:3600000:JSONPATH($.results[28].value_inc_vat)]" }
Number AgilePrice0800 "Agile Price at 0800" { http="<[Agile:3600000:JSONPATH($.results[29].value_inc_vat)]" }
Number AgilePrice0730 "Agile Price at 0730" { http="<[Agile:3600000:JSONPATH($.results[30].value_inc_vat)]" }
Number AgilePrice0700 "Agile Price at 0700" { http="<[Agile:3600000:JSONPATH($.results[31].value_inc_vat)]" }
Number AgilePrice0630 "Agile Price at 0630" { http="<[Agile:3600000:JSONPATH($.results[32].value_inc_vat)]" }
Number AgilePrice0600 "Agile Price at 0600" { http="<[Agile:3600000:JSONPATH($.results[33].value_inc_vat)]" }
Number AgilePrice0530 "Agile Price at 0530" { http="<[Agile:3600000:JSONPATH($.results[34].value_inc_vat)]" }
Number AgilePrice0500 "Agile Price at 0500" { http="<[Agile:3600000:JSONPATH($.results[35].value_inc_vat)]" }
Number AgilePrice0430 "Agile Price at 0430" { http="<[Agile:3600000:JSONPATH($.results[36].value_inc_vat)]" }
Number AgilePrice0400 "Agile Price at 0400" { http="<[Agile:3600000:JSONPATH($.results[37].value_inc_vat)]" }
Number AgilePrice0330 "Agile Price at 0330" { http="<[Agile:3600000:JSONPATH($.results[38].value_inc_vat)]" }
Number AgilePrice0300 "Agile Price at 0300" { http="<[Agile:3600000:JSONPATH($.results[39].value_inc_vat)]" }
Number AgilePrice0230 "Agile Price at 0230" { http="<[Agile:3600000:JSONPATH($.results[40].value_inc_vat)]" }
Number AgilePrice0200 "Agile Price at 0200" { http="<[Agile:3600000:JSONPATH($.results[41].value_inc_vat)]" }
Number AgilePrice0130 "Agile Price at 0130" { http="<[Agile:3600000:JSONPATH($.results[42].value_inc_vat)]" }
Number AgilePrice0100 "Agile Price at 0100" { http="<[Agile:3600000:JSONPATH($.results[43].value_inc_vat)]" }
Number AgilePrice0030 "Agile Price at 0030" { http="<[Agile:3600000:JSONPATH($.results[44].value_inc_vat)]" }
Number AgilePrice0000 "Agile Price at 0000" { http="<[Agile:3600000:JSONPATH($.results[45].value_inc_vat)]" }
Number AgilePrice2330 "Agile Price at 2330" { http="<[Agile:3600000:JSONPATH($.results[46].value_inc_vat)]" }
Number AgilePrice2300 "Agile Price at 2300" { http="<[Agile:3600000:JSONPATH($.results[47].value_inc_vat)]" }

The items are refreshed every hour so the maximum interval between the cached API return being updated and the item being updated is one hour. Once the API is updated with tomorrow’s values, this method will overwrite any remaining values for today with corresponding values for tomorrow.

There is no checking in this so it is dependent on the API always feeding the results in reverse order from the end of the day at 2300Z (=0000 CET, the time the electricity market day ends). If you specify a From and To time, you could potentially control the indexing (or create a situation where it is never right!)

You could then manipulate or read these items with rules or you could use a Group Function to get a minimum or average.

This method is way too clunky and error prone. I’m sure there’s a better way of doing this using rules on the whole JSON string returned from the API, including checking the “valid_from” date and time. The initial problem with that is that as from OpenHab 2.3.0, there’s a change to the JSONPATH transformation so you can’t grab multiple instances of “results” using JSONPATH ($.results) and extract individual results using indexing but you can grab the whole string using:

String AgilePrice "Agile Price JSON string" { http="<[Agile:14400000:NOTRANSFORMATION()]" }

It throws an error because NOTRANSFORMATION doesn’t exist but it then updates the item with the whole, untransformed string, which is what we want. You’ll get the error each time the item refreshes so every 4 hours in this example.

A bit more research required on the possibilities but I need to think about the use cases first to work out what to extract and how.

OK, so I have now tidied this up with some rules.

.items file

// Agile Tariff Items
Number agileRateNow "Current price of electricity [%.3fp]"
Contact agilePlunge "Contact showing if current rate is plunge"

Number agile3Price "Price of electricity in cheapest 3 hours [%.3fp]"
Number agile3Index "Index number of the start of the cheapest 3 hour period [%d]"
Contact agile3Lowest "Contact showing if current time is in cheapest 3 hours"

Number agile6Price "Price of electricity in cheapest 6 hours [%.3fp]"
Number agile6Index "Index number of the start of the cheapest 6 hour period [%d]"
Contact agile6Lowest "Contact showing if current time is in cheapest 6 hours"

.rules file

// Rules to process Octopus Agile Time of Use rates
import java.util.List

// Rules use an array of values 0 - 96 to represent the 96 half hour periods from midnight today to 2359 tomorrow

// initialise array with length 96 and setting 100 to represent a null rate (as rates can be positive, 0 or negative)
var List<Number> rates = newArrayList(100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100)

rule AgileMidnight
	Time cron "0 0 0 1/1 * ? *"	// at midnight to move the rates to the right day
	for (var i=0 ; i < 48 ; i++) rates.set(i,rates.get(i+48))	//move rates from tomorrow to today
	for (var i=48 ; i < 96 ; i++) rates.set(i,100)			//reset tomorrows rates
	if (agile3Index.state as Number >=48) agile3Index.sendCommand(agile3Index.state as Number - 48) // move the cheapest period index to today if it was tomorrow
	if (agile6Index.state as Number >=48) agile3Index.sendCommand(agile6Index.state as Number - 48) // move the cheapest period index to today if it was tomorrow

rule AgileCheckForNewRates
        System started or               //run the rule at startup
        Time cron "0 15 16-20/1 1/1 * ? *"      //and hourly from 4:15pm to 8:15pm when the rates for the next day should be available
	if (rates.get(48) == 100) {	//only call the api if there are no rates for tomorrow
		// reset all rates ready for refresh from api
		for (var i=0 ; i < 96 ; i++) rates.set(i,100)		//reset the arrary ready for new data

		val String dateString = now.toString("yyyy-MM-dd")	//get today's date as a string to insert into the url
		val Number today$ = dateString.substring(8,10)
		val String agileJSON = sendHttpGetRequest("https://api.octopus.energy/v1/products/AGILE-18-02-21/electricity-tariffs/E-1R-AGILE-18-02-21-B/standard-unit-rates/?period_from="+dateString+"T00:00Z")
		val Number rateCount = (Integer::parseInt(transform("JSONPATH", "$.count", agileJSON)))	//extract the number of rates and convert to an integer
		logWarn ("rules.AgileCheckForNewRates" , "Read "+rateCount.toString+" rates from Octopus API")

		for (var i = 0 ; i < (rateCount) ; i++) {	//for each rate
			var Number index = 0
			var Number rate = (Double::parseDouble(transform("JSONPATH", "$.results["+i+"].value_inc_vat", agileJSON)))	//extract the rate and convert to a number
			var String dtfrom = transform("JSONPATH", "$.results["+i+"].valid_from", agileJSON)	//extract the start date/time
			var Number hour = (Integer::parseInt(dtfrom.substring(11,13))) //extract the hour and convert to a number
			index = (hour * 2)
			if (dtfrom.substring(14,15) == "3") {
				index += 1	//add one for the rates starting at the half hour
			if (dtfrom.substring(8,10) != today$) {
				index += 48	//tomorrow's rates are indexed after today's
//			logWarn ("rules.AgileMidnight" , "Index: "+index.toString+" rate: "+rate.toString)
			var index1 = index.intValue
		// Check for cheapest 3 and 6 hour rates
                var Number rate3 = 1000 // initialise cheapest 3 hour rate
       	        var Number index3 = 99  // initialise index of start time of cheapest 3 hours
               	var Number rate6 = 2000 // initialise cheapest 6 hour rate
                var Number index6 = 99  // initialise index of start time of cheapest 6 hours

		// Calculate the index starting now
		val String timeString = now.toString("HH:mm")      //get current hour as a string
		val Number hour = (Integer::parseInt(timeString.substring(0,2))) //extract the hour and convert to a number
		var Number index = (hour * 2)
		if (timeString.substring(3,4) == "3" || timeString.substring(3,4) == "4" || timeString.substring(3,4) == "5") {
 		index = (index + 1)     //add one for the rates starting at the half hour
 		val index1 = index.intValue
                for (var i = index1 ; i < (rateCount-5) ; i++) {       //for each rate starting now
			var Number rate3sum = 0	//Set rates back to zero each time
			var Number rate6sum = 0
			for (var offset = 0 ; offset < 12 ; offset++) { // Look at next 12 rates

				if (offset < 6) { // Only add next 3 rates if  we have't already added 3 hours worth
					rate3sum = rate3sum+rates.get(i+offset)

				if (i <= (rateCount-12)) {	// Only read rates if all 12 are available
					rate6sum = rate6sum+rates.get(i+offset)

			if (rate3sum < rate3) {	//If this is currently the cheapest rate, record the index
				index3 = i
				rate3 = rate3sum

                        if (i <= (rateCount-12) && rate6sum < rate6) { //If we are still collecting 6 hour rates and this is currently the cheapest rate, record the index
                                index6 = i
                                rate6 = rate6sum
		rate3 = (rate3 / 6)	//Calculate the average rate over the cheapest 3 hours (6 half hours)
		logWarn ("rules.AgileCheckForNewRates" , "Cheapest 3 hours: "+rate3.toString+" at index: "+index3.toString)

		rate6 = rate6 / 12	//Calculate the average rate over the cheapest 6 hours (12 half hours)
		logWarn ("rules.AgileCheckForNewRates" , "Cheapest 6 hours: "+rate6.toString+" at index: "+index6.toString)


rule AgileRateChange
        System started or               //run the rule at startup Note there is a race condition at system startup that means that this rule may run before the array is populated
 	Time cron "0 0/30 * 1/1 * ? *" //Run rule at each new hour/half hour when rate changes
        val String dateString = now.toString("HH:mm")      //get current hour as a string
        val Number hour = (Integer::parseInt(dateString.substring(0,2))) //extract the hour and convert to a number
	var Number index = (hour * 2)

        if (dateString.substring(3,4) == "3" || dateString.substring(3,4) == "4" || dateString.substring(3,4) == "5") {
		index = (index + 1)     //add one for the rates starting at the half hour

	val index1 = index.intValue
	val rate = rates.get(index1)

	if (index >= agile3Index.state as Number && index < (agile3Index.state as Number +6)) agile3Lowest.sendCommand(CLOSED) else agile3Lowest.sendCommand(OPEN) // Close the contact if during the cheapest 3 hours
	if (index >= agile6Index.state as Number && index < (agile6Index.state as Number +12)) agile6Lowest.sendCommand(CLOSED) else agile6Lowest.sendCommand(OPEN) // Close the contact if during the cheapest 6 hours
	if (rate <= 0) agilePlunge.sendCommand(CLOSED) else agilePlunge.sendCommand(OPEN)

The principle of operation is to create an array of prices from midnight today until 2300 tomorrow, indexed from 0 to 95. On the first run, the array is blank so it will retrieve what it can from the API and populate the array. If it is before tomorrow’s rates are available, midnight tomorrow (array entry 48) is left at 100.

Starting at 1615, when the new rates should be available, it rule checks to see if it has tomorrows rates. If not it will fill the array from the API. If tomorrows rates are available, tomorrow midnight rate will be populated, if not, it will still be at 100 and the next time the rule is run it will check the API again.

After filling the array, the rule will identify the index and rate for the cheapest 3 hours (eg for washing machine, dishwasher or tumble dryer) and 6 hours (e.g. charging EV or heating hot water). The rule starts from now because there is no point in identifying cheapest periods in the past.

At midnight, the rule moves all of the values that were for tomorrow and moves them into today and initialises tomorrows rates. It also brings any cheapest period index values to today.

Every half hour, the rule updates the current rate and closes a contact if the current period is within the cheapest 3 or 6 hours. There is also a contact for plunge rates so you can switch on power consuming items whenever rates are free or paying back.

To use the contact, you can create a rule triggered on:

when Item agile3Lowest changed from OPEN to CLOSED
1 Like

Thanks for the script, just got it working with OH3 with the changes to the date code below:

val String dateString = String::format( "%1$tY-%1$tm-%1$td", new java.util.Date ) //get today's date as a string to insert into the url

val String timeString = String::format( "%1$tH:%1$tM", new java.util.Date ) //get current hour as a string

I have a slight issue though (might no be an issue, not sure), the API call is only returning 94 rows, not 96 as I expect for the rates array. It seems the very last few hours (23:00 and 23:30) is missing. Is anyone else having this issue? should I be concerned?

Api call is https://api.octopus.energy/v1/products/AGILE-18-02-21/electricity-tariffs/E-1R-AGILE-18-02-21-J/standard-unit-rates/?period_from=2021-02-02T00:00Z

Thanks for posting the OH3 compatible code. I made the same correction but the forum doesn’t allow me to reply to my own post too many times so I couldn’t share it.

You are right, the number of half hour rates returned by the API isn’t an issue. Pricing for energy works on a European market and hence uses European days that finish one hour ahead compared to UK time. The times in the API are UK times so the last hour of the UK day is the first hour of the next day in Europe, for which no price is available.

Although the rules are working fine for me, I’m looking at rewriting the approach to use the persistence service rather than the array method. You can post records to Influxdb with timestamps so I’m thinking of reading the Json response from the API and storing the rates in the database with the appropriate start times.

Thanks for the information, it’s working great. With regards to persistence, I have started to move away from the array and have 94 number items (!!!), my code is currently a bit of a mess, but it allows me to display all the rates in the sitemap. The 30 minute task also sets past rates to ‘100’ and the sitemap hides the items that are set to ‘100’. So it only shows current rates. If it’s of interest i’ll share the code once all the bugs are ironed out.

I started out with individual items but I felt it was a bit clunky and wasn’t easy for things like “cheapest 3 hours” rules. Also the way I did it was vulnerable to changes in the way the API data is presented.

If you persist the rates with the right time stamps, you could show them as a chart in the new OH3 UI (or in HABPanel in OH2). You might also be able to retrieve them all as a table in a sitemap using a URL pointed at the persistence service. But maybe I’ve just spend too much time trying to write code that squeezes the max out of low resource systems because your method works perfectly well.

I’m probably going to go for the Go Faster tariff rather than Agile but I’ll still try to develop the Agile stuff a bit further even if just to see if I want to switch at some point in the future.

Using the persist rates with timestamps and charts sounds like a much more elegant solution compared to my massive loops through 91 number items.

However, for me, this works and I can see all the rates. Resources are not an issue as I’m running on intel atom pc running at about 2% cpu usage. So, it’s all working well for me with your array code (thanks!) and my additional code for the number items, although if you do update your code with the persistence service, I would be really interested in switching :slight_smile:

The go-faster rate looks interesting!

I’m glad it’s working for you. Updating the code is a rainy day project as I’m not on this tariff but if I do get around to it, I will post it back here.

1 Like

Not fully tested yet, but I changed @barneyd’s code to put the rates into the index as per local time rather than the published Europe time. the requested UTC times (see posts below). It’s probably best to just remove the ‘Z’ from the end of the API call and ignore all this.

		for (var i = 0 ; i < (rateCount) ; i++) {	//for each rate
			var Number index = 0
			var Number rate = (Double::parseDouble(transform("JSONPATH", "$.results["+i+"].value_inc_vat", agileJSON)))	//extract the rate and convert to a number
			var String dtfrom = transform("JSONPATH", "$.results["+i+"].valid_from", agileJSON)	//extract the start date/time
			var String dtfromLoc = ZonedDateTime.parse(dtfrom).withZoneSameInstant(ZoneId.systemDefault()).toString() // Convert the time to local time
			var Number hour = (Integer::parseInt(dtfromLoc.substring(11,13))) //extract the hour and convert to a number

			index = (hour * 2)
			if (dtfromLoc.substring(14,15) == "3") {
				index += 1	//add one for the rates starting at the half hour
			if (dtfromLoc.substring(8,10) != today$) {
				index += 48	//tomorrow's rates are indexed after today's
//			logWarn ("rules.AgileMidnight" , "Index: "+index.toString+" rate: "+rate.toString)
			var index1 = index.intValue

Oh yes, you also need to change the json line to:

		val String yesterdayDateString = String::format( "%1$tY-%1$tm-%1$td", ZonedDateTime.now.toLocalDateTime.minusDays(1) )
		val String agileJSON = sendHttpGetRequest("https://api.octopus.energy/v1/products/AGILE-18-02-21/electricity-tariffs/E-1R-AGILE-18-02-21-G/standard-unit-rates/?period_from="+yesterdayDateString+"T23:00Z")

I thought the times in the API are UK times, which is why there is never 2300-2330 and 2330-0000 slots for the day ahead.

Hmm. Admittedly I hadn’t properly read the API documentation. My aim was to match the rule results with what I see on my Octopus dashboard, and the code as it was gave me a 1 hour offset.

The last part of this statement might be relevant

We strongly recommend that timezone information is included on all datetime parameters. If no timezone information is included, the “Europe/London” timezone will be assumed and results may vary between GMT and British Summer Time.

Indeed. Putting that Z at the end of the API request puts the result in UTC, so the rule was correct for half the year!

Interesting, I wrote this during GMT and haven’t looked at the raw response from the API since the start of BST. I wrote it when I was considering Agile but I’ve settled on Go Faster after observing the Agile Rates.

I’ve just checked this out. The Z in the URL refers to the period_from query parameter. It doesn’t change the timing of the results. All taking the Z off does is to additionally include results for 2300-2330 and 2330-0000 the previous day.

The indexing of the array was based on the latest reading (the first one in the JSON response) being 2230-2300. During BST, that reading is actually shown as 2130Z-2200Z, although that should translate back to 2230-2300 BST with the way the indexing is used in other rules. I did say in one of my earlier posts that the rule is vulnerable to changes in the presentation of the data in the API response. I started working on a better solution that uses the valid_from field in the response to timestamp an entry in a persistence database, which is a much more robust way of storing the data than an indexed array. That’s on hold as I’m not using this tariff.

All of the results are shown as zulu so actually, it seems that it’s the results that are right for half the year. I guess that’s why the peak period currently runs from 1500Z to 1800Z.

I need to put a wet towel over my head and work out what this means for the timings.

I had thought it was just me with the issue. Anyway, I have a rather sloppy bit of code that does this:

if (true) { //TODO: code to find out if we are in british summer time
index = index + 2

I had tried to work out if BST, but could not manage it with the new java time in OH3, and ended up hard coding it with a reminder when the clocks change to set to FALSE in the code! Awful I know :rofl:

I still haven’t thought it through enough to work out what is going on with the indexing. My assumption when I first wrote it was that the first slot in the Json reponse was the latest slot. At the time, it was 2230 - 2300 GMT (which also happens to the 2230 - 2300 Z). When I look at the response now, the latest slot is 2130 - 2200 Z, which is 2230 - 2300 BST. In other words, the first slot in the response is always 2230 - 2300 “human” time. Looked at that way, the indexing in the array should be the same all year round.