Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Tibber] Added "today" API channel #14236

Merged
merged 3 commits into from
Jan 22, 2023
Merged

[Tibber] Added "today" API channel #14236

merged 3 commits into from
Jan 22, 2023

Conversation

Markinus
Copy link
Contributor

This commit exends the supported Tibber API with channel "today". This channel is very similar to "tomorrow" and gets the hourly prices of the current day as JSON string.

@Markinus Markinus requested a review from kjoglum as a code owner January 16, 2023 22:13
@jlaur
Copy link
Contributor

jlaur commented Jan 16, 2023

Thanks for the pull request!

See also #13416 (comment). It's worth considering now if historical prices are relevant - what are the use-cases?

  • Today's prices are partly historical (i.e. some of them might be irrelevant).
  • Tomorrow's prices doesn't represent all future prices (there's a gap from now and until midnight).

If the historical prices for today are of interest, why not yesterday's prices also?

If the use-cases are mostly for calculating costs in the future in order to schedule lowest price for running dishwasher etc., if might be worth providing just one channel containing current hour + all available hours into the future (i.e. until midnight tomorrow when available).

I'm not saying anything is wrong with this PR, just wanted to brainstorm a little. I'm also interested in the use-cases. See also openhab/openhab-core#3478 and #14222.

@Markinus
Copy link
Contributor Author

Hi,

I can understand your comment but I already think we should separate this PR and your concern. There are two issues that we need to clarify.

First to the "normal" operators and use cases.

  • Normally an operation takes more than an hour. That means that if I want to turn something on then I don't need the cheapest starting point but the cheapest period. This can start the day before.

  • Examples for smaller devices are the washing machine, dryer or dishwasher where this deviation may not be so serious.

  • With larger devices, however, it looks different. These include, for example:

a. An EV or PHEV. Here, a process sometimes takes hours and that with very high power. The right time slot is very crucial. In addition, it must often be the most favorable slot at night, which may well start on today's day and continue into the next day.

b. A PV storage. In winter, the PV storage is mostly unused. During this time it can be charged with cheap electricity at night and the house can consume it from the storage during the day. Also here it can be that this slot also starts on today's day and goes over into the next day.

c. If the storage is too small and not enough for the whole day. Then I also need the cheapest slot on the day to then switch off the storage and draw electricity from the grid. This can still be today depending on how much additional PV comes over the day.

  • So the user needs time slots. I think that would be something that could really help.

a. the cheapest 2/3/4/5 hour time slot, total, day and night.

b. the average price for the respective period

c. a switch that switches it so that the user can use it automatically.

  • The data are therefore not only historical. I am only interested in the future data of the current day but these are very relevant in many cases.

  • This would all be very useful but also associated with some effort.

Now to the "professional" users.

  • Currently the binding has no possibility to access today's future data. This is really missing.

  • This "today" interface is exactly like the "tomorrow" so that the same tools can be used.

  • Someone who can script can solve all the upper use cases

  • The solution was very simple and fast

Suggestion:

Let's take a two-step approach. First integrate a solution with this PR at all. Secondly, we can discuss if such time slots as described above would not be something useful.

Greetings

@jlaur
Copy link
Contributor

jlaur commented Jan 17, 2023

@Markinus - thanks for describing the use-cases, this is valuable input. I do think it confirms that the simplest solution (for the user) would be one channel containing both today and tomorrow. This way you don't have to look up the data in two different channels when performing calculations across midnight.

IMHO it shouldn't require much additional work to combine the two payloads, if you agree this would simplify matters for the users. The problem with the two-step approach (which is actually a three-step approach because first step was taken in #13416) is backwards compatibility concerns. It's easier to create new channels that it is to remove existing ones, since this is a breaking change.

WDYT?

One additional clarification:

Normally an operation takes more than an hour. That means that if I want to turn something on then I don't need the cheapest starting point but the cheapest period. This can start the day before.

I'm not sure I fully understand this. Unless the device is already running and you want to calculate the total price until it completes, prices before current hour shouldn't be needed/relevant? If already running since yesterday and you want to calculate the total cost, you already lack data from yesterday. So having historic prices today seems to only cover a corner-case?

@Markinus
Copy link
Contributor Author

In my opinion, backwards compatibility is not really a problem. Since the channel for "tomorrow" already exists, it can't really be removed. It is then no problem if the one for "today" would also be added. Both are the same and can stay in the binding for professional users.

For normal users, in my opinion, even an array with data from now until tomorrow night is not really easier. The size of the array changes every hour and you have to search for the time again and again. With "today" or "tomorrow" you know exactly when these areas start.

It helps a user much more if he would get switches for the most favorable time ranges at night and during the day. Then he can choose the right one and switch his devices directly with it (for 2,3,4,5 hours time ranges).

To your question an example: When I come home in the evening and plug in the PV then the charging process should not start directly. The software knows that I leave in the morning and the car needs for example:: 40 kWh. The software then searches from just now and at night the cheapest four hours where the charging can take place. This can be e.g.: from today 23h to tomorrow at 3 o'clock. So I need the data from today and tomorrow. The data from now to the past are irrelevant but they do not hurt.

If the user had direct switch items for the time ranges then he could, depending on how long the time is for the device, directly use them.

But as I said before, this PR does not hurt at all.

@jlaur jlaur added the enhancement An enhancement or new feature for an existing add-on label Jan 17, 2023
Signed-off-by: Markus Eckhardt <[email protected]>
@Markinus
Copy link
Contributor Author

Markinus commented Jan 17, 2023

@jlaur an another idea. We could replace "tomorrow_prices" with "priceInfo" and send the whole "PriceInfo" JSONObject. So every one should have what he need. Maybe we should extend the prices with all informations too. So there wouldn't be a new channel but the people would have to change their code.

PriceInfo

Field Argument Type Description
current Price The energy price right now
today [Price]! The hourly prices of the current day
tomorrow [Price]! The hourly prices of the upcoming day
PriceInfo Field Argument Type Description current [Price](https://developer.tibber.com/docs/reference#price)

The energy price right now
today [Price]!

The hourly prices of the current day
tomorrow [Price]!

The hourly prices of the upcoming day

Price

Field Argument Type Description
total Float The total price (energy + taxes)
energy Float Nord Pool spot price
tax Float The tax part of the price (guarantee of origin certificate, energy tax (Sweden only) and VAT)
startsAt String The start time of the price
currency String! The price currency
level PriceLevel The price level compared to recent price values
Price Field Argument Type Description total [Float](https://developer.tibber.com/docs/reference#float)

The total price (energy + taxes)
energy Float

Nord Pool spot price
tax Float

The tax part of the price (guarantee of origin certificate, energy tax (Sweden only) and VAT)
startsAt String

The start time of the price
currency String!

The price currency
level PriceLevel

The price level compared to recent price values

@jlaur
Copy link
Contributor

jlaur commented Jan 18, 2023

another idea. We could replace "tomorrow_prices" with "priceInfo" and send the whole "PriceInfo" JSONObject.

In that case I would rather go with the new channel you have implemented in this PR. The problem I see here is that no abstraction is provided for the user, so they will still need to merge two different sources to get relevant future prices.

Just to be very verbose:

tomorrow_prices (example reduced):

[
  {
    "startsAt": "2023-01-19T00:00:00.000+02:00",
    "total": 3.8472
  },
  {
    "startsAt": "2023-01-19T01:00:00.000+02:00",
    "total": 3.0748
  }
]

today_prices

[
  {
    "startsAt": "2023-01-18T22:00:00.000+02:00",
    "total": 4.1014
  },
  {
    "startsAt": "2023-01-18T23:00:00.000+02:00",
    "total": 4.0265
  }
]

My proposal:

future_prices (when 'now' is 1:30 before midnight, thus everything from tomorrow_prices, but filtered prices from before start of current hour from today_prices)

[
  {
    "startsAt": "2023-01-18T22:00:00.000+02:00",
    "total": 4.1014
  },
  {
    "startsAt": "2023-01-18T23:00:00.000+02:00",
    "total": 4.0265
  },
  {
    "startsAt": "2023-01-19T00:00:00.000+02:00",
    "total": 3.8472
  },
  {
    "startsAt": "2023-01-19T01:00:00.000+02:00",
    "total": 3.0748
  }
]

This would be simpler to use in rules as IMHO this separation of today and tomorrow is artificial IMHO and just reflects how Tibber for some reason has decided to expose the data. Other services behave differently, see for example https://api.energidataservice.dk/dataset/Elspotprices?start=utcnow&filter={%22PriceArea%22:%22DK1%22}&columns=HourUTC,SpotPriceDKK

"Simpler" because having only one data source. So the JSON could be converted into a Map as lookup table, see for example https://community.openhab.org/t/dishwasher-price-calculation-automation/139207

@Markinus
Copy link
Contributor Author

Markinus commented Jan 21, 2023

I understand your point of view, but there is another way to look at it. If you have two separate arrays/maps/lists for both days instead of one for everything, then access is much easier. If you want to know the price for 5 o'clock then you access directly the index 5 of the arrays. If you want to know the price between 4-6 o'clock then you just take a sublist(4-6). For the night you can easily build a sublist from both, directly over the desired times.
If everything is in one then you always have to search because the length of the arrays/maps/lists is different for each hour (because the old hours of the day are omitted). It is of course possible to deal with this but is it really easier?

@jlaur
Copy link
Contributor

jlaur commented Jan 21, 2023

I understand your point of view, but there is another way to look at it. If you have two separate arrays/maps/lists for both days instead of one for everything, then access is much easier. If you want to know the price for 5 o'clock then you access directly the index 5 of the arrays. If you want to know the price between 4-6 o'clock then you just take a sublist(4-6). For the night you can easily build a sublist from both, directly over the desired times. If everything is in one then you always have to search because the length of the arrays/maps/lists is different for each hour (because the old hours of the day are omitted). It is of course possible to deal with this but is it really easier?

Thanks for adding this perspective, I didn't think of the use-case being able to directly reference by index, so probably this is useful in combination with a transformation service?

@kjoglum, @seime - everything okay with you?

Comment on lines +190 to +193
JsonArray today = rootJsonObject.getAsJsonObject("data").getAsJsonObject("viewer")
.getAsJsonObject("home").getAsJsonObject("currentSubscription").getAsJsonObject("priceInfo")
.getAsJsonArray("today");
updateState(TODAY_PRICES, new StringType(today.toString()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider checking if channel is linked to avoid building this array otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a copy like the other channels are updated. I think it doesn't make sense to add this check for this channel only. This kind of optimisation should be done in the feature for all channels in a new PR.

@kjoglum
Copy link
Contributor

kjoglum commented Jan 21, 2023

@kjoglum, @seime - everything okay with you?

Nothing to add from my side, looks ok.

@Markinus
Copy link
Contributor Author

@jlaur Yes, tranformation service. I have a JS Script and it's used for both today and tomorrow.

function(file, id, name) {
	if ( file == null )
        return 0;
	var jsonObj = JSON.parse(file);
	var value;
	if(name == "startsAt") {
		value = jsonObj[parseInt(id)].startsAt;
	} else if (name == "total") {
		value = jsonObj[parseInt(id)].total;
	} else if (name == "number") {
		value = jsonObj.length;
	} else {
		value = "not supportet in JS"
	}

	return value;

})(input, hourId, valueName)

In Rule:

...
var List<ZonedDateTime> tomorrowTimeStamps = new ArrayList()
var List<Float> tomorrowPrices = new ArrayList()
var List<ZonedDateTime> todayTimeStamps = new ArrayList()
var List<Float> todayPrices = new ArrayList()
...
var Integer nbrData = Integer.parseInt(transform("JS", "TibberParser.js?hourId=9999&valueName=number", tomorrowPricesData))
for(var Integer i=0; i<nbrData; i++) {
	var String time = transform("JS", "TibberParser.js?hourId="+i.toString()+"&valueName=startsAt", tomorrowPricesData)
	var timeZDT = ZonedDateTime.parse(time)
	tomorrowTimeStamps.add(timeZDT.withZoneSameInstant(ZoneId.systemDefault()))
	tomorrowPrices.add(Float.parseFloat(transform("JS", "TibberParser.js?hourId="+i.toString()+"&valueName=total", tomorrowPricesData)))
	time = transform("JS", "TibberParser.js?hourId="+i.toString()+"&valueName=startsAt", todayPricesData)
	timeZDT = ZonedDateTime.parse(time)
	todayTimeStamps.add(timeZDT.withZoneSameInstant(ZoneId.systemDefault()))
	todayPrices.add(Float.parseFloat(transform("JS", "TibberParser.js?hourId="+i.toString()+"&valueName=total", todayPricesData)))
}
...

<item-type>String</item-type>
<label>Prices for today as a JSON array</label>
<description>JSON array of tuples startsAt,total, e.g. {["startsAt": "2022-09-10T00:00:00+02:00", "total": 5.332},
{"startsAt": ...}]. See binding documantation for full example.</description>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{"startsAt": ...}]. See binding documantation for full example.</description>
{"startsAt": ...}]. See binding documentation for full example.</description>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's back. I see you fixed it also for tomorrow in i18n properties - please also fix it accordingly in this file.

Signed-off-by: Markus Eckhardt <[email protected]>
Copy link
Contributor

@jlaur jlaur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jlaur jlaur merged commit 8ee8a1e into openhab:main Jan 22, 2023
@jlaur jlaur added this to the 4.0 milestone Jan 22, 2023
nemerdaud pushed a commit to nemerdaud/openhab-addons that referenced this pull request Feb 28, 2023
* Added "today" API channel

Signed-off-by: Markus Eckhardt <[email protected]>
renescherer pushed a commit to renescherer/openhab-addons that referenced this pull request Mar 23, 2023
* Added "today" API channel

Signed-off-by: Markus Eckhardt <[email protected]>
leifbladt pushed a commit to leifbladt/openhab-addons that referenced this pull request Apr 14, 2023
* Added "today" API channel

Signed-off-by: Markus Eckhardt <[email protected]>
FordPrfkt pushed a commit to FordPrfkt/openhab-addons that referenced this pull request Apr 20, 2023
* Added "today" API channel

Signed-off-by: Markus Eckhardt <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement An enhancement or new feature for an existing add-on
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants