Sort/compare multiple number items (Gas/Petrol Prices)

I got the local gas prices (in Germany) from an API called tankerkoenig.
The description is on another smarthome forum:

Is it possible to compare the gas prices within OpenHAB?
I want to get the lowest price from all stations (sometimes two or more stations are the cheapest)

Is there some algorithm like bubblesort?

If i want to do it manually (comparing 11 stations 11 times) it leads to a few error messages

snippet of my rule:

if((Tankstelle7_E5.state as DecimalType<=Tankstelle1_E5.state as DecimalType) && (Tankstelle7_E5.state as DecimalType<=Tankstelle2_E5.state as DecimalType){do sth}

2016-03-27 21:09:27.836 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'E5 Prices Changed': Cannot cast org.openhab.core.types.UnDefType to org.openhab.core.library.types.DecimalType

1 Like

Hi there,

i am working on the same issue. In my Opinion the task consitsts of two areas. First area ist to display the values in a sorted manner and the second is to sort the items/values.

Idea to display items: Create a number of proxy items. One for the station name and one for the value. based on this we should be able to assign the sorted value to the proxy items

Idea to sort values: Create a kind of arraylistand sort it using standrd sort algos from java.

Open topic: which is the right collection to store name and value and then sort by value.

Thomas

You can put the Items into an ArrayList and call the sort method.

For example:

val ArrayList<DecimalType> list = new ArrayList<DecimalType>
list.add(Tankstelle1_E5.state as DecimalType)
...
val sorted = ArrayList::sort(list)

Thanks for the example, but for Fuel prices we should sort as well the stantion name oder a unique identifier, because it is not enough to know that the is a really cheap station.

Thomas

The question was just asking for a sort function which I demonstrated how to use. Using the function in this context is an exercise left for the student. :slight_smile:

You can create hashmaps and use the value as a key and the name as the value, sort the keys and get the name from the lowest.

val HashMap<DecimalTyple, String> list = newHashmap

if(list.get(Tankstelle1_E5.state as DecimalType) != null)  list.put(Taskstelle1_E5.state, list.get(Taskstelle1_E5.state)+", "+stationname)
else list.put(Taskstelle1_E5.state, name)
...

val sorted = ArrayList(Collections::sort(list.keys))
val name = list.get(sorted.get(0))
2 Likes

Yes, You are right, your first answer was correct.

Thanks a lot for the solution. I will try that.

Thomas

I will try your approach you described in the German “KNX User Forum”

My solution works, but it only shows me the highest and lowest price

I hope it is okay to mention your post from another site here: Link

@Dibbler42 Will your script handle closed stations?

Thanks :slight_smile:

Feel free it is for the community and if someone finds a better solution …

Thomas

1 Like

A no. This is only an orientation for me and in general stations are not closed during regualr office hours so i dont care. But it shoud be possible to impement that by introducing some more variables.

Thomas

This is my quick approach, that can handle closed stations:
But: The information provider will not support the prices via the detail.php anymore

Maybe the items can be transferred, in order to use the script.
(API-Keys can be banned, if they’re using the old interface which leads to much more traffic)

The new Inferface looks like this (all stations (up to 10) in one request)
https://creativecommons.tankerkoenig.de/json/prices.php + the desired stations + APIKEY
{"ok":true,"license":"CC BY 4.0 - http:\/\/creativecommons.tankerkoenig.de","data":"MTS-K","prices":{"00000000-0000-0000-0000-000000000001":{"status":"open","e5":1.299,"e10":false,"diesel":1.059},"00000000-0000-0000-0000-000000000002":{"status":"open","e5":1.349,"e10":1.329,"diesel":1.119}}}

rule:

rule "E5 Update"
when
    Item Gas_Prices_E5 received update or
    Item Gas_Prices_E5 changed
then
        var Lock lockE5 = new ReentrantLock()
        var Map <String, Double> E5PricesMap = new HashMap <String, Double>()    // Stationsnamen und Preise für den Kraftstoff
        var ArrayList <StringItem> E5sortItems =  new ArrayList <StringItem>()
        var i = 0
        lockE5.lock()        
        E5sortItems.add(0, Tankstelle1_E5_TXT)
        E5sortItems.add(1, Tankstelle2_E5_TXT)
        E5sortItems.add(2, Tankstelle3_E5_TXT)

        if(Tankstelle1Closed.state==OFF){E5PricesMap.put("Shell",		(Tankstelle1_E5.state as DecimalType).doubleValue())}
		if(Tankstelle2Closed.state==OFF){E5PricesMap.put("Esso",		(Tankstelle2_E5.state as DecimalType).doubleValue())}
		if(Tankstelle3Closed.state==OFF){E5PricesMap.put("Star",		(Tankstelle3_E5.state as DecimalType).doubleValue())}

		if(Tankstelle1Closed.state==ON)	{E5PricesMap.put("geschlossen1",	(0).doubleValue())}
        if(Tankstelle2Closed.state==ON)	{E5PricesMap.put("geschlossen2",	(0).doubleValue())}
        if(Tankstelle3Closed.state==ON)	{E5PricesMap.put("geschlossen3",	(0).doubleValue())}
		
        // Map nach Preisen sortieren und die Werte dann dem Anzeigearray zuweisen
        for (PriceEntry : E5PricesMap.entrySet.sortBy[value]) {
            if(PriceEntry.getValue()!=0){E5sortItems.get(i).postUpdate(String::format("%s - %.3f €", PriceEntry.getKey(), PriceEntry.getValue()))}
            if(PriceEntry.getValue()==0){E5sortItems.get(i).postUpdate("geschlossen")}
			i = i+1
        }
        lockE5.unlock()
		postUpdate(E5_LastUpdate, new DateTimeType())
end

sitemap:

Text label="Spritpreise" icon="gas_station_64_2" {
			Group item=Gas_Prices_E5 {
			Text item=E5_LastUpdate 		valuecolor=[E5_LastUpdate>7200="red", E5_LastUpdate>3600="orange"]			label="Letze Änderung 		[%1$tA, %1$tR]"
			Text item=Price_E5_LOW
			Text item=Price_E5_HIGH
			Switch item=Gas_Prices_Period label="" mappings=[1="Tag", 2="Woche"]
			Chart  item=Gas_Prices_E5 period=D refresh=3600 visibility=[Gas_Prices_Period==1, Gas_Prices_Period=="Uninitialized"]
			Chart  item=Gas_Prices_E5 period=W refresh=3600 visibility=[Gas_Prices_Period==2]
			Frame label="Geöffnete Tankstellen" {
			Text item=Tankstelle1_E5_TXT label="[%s]" visibility=[Tankstelle1_E5_TXT!="geschlossen"]
			Text item=Tankstelle2_E5_TXT label="[%s]" visibility=[Tankstelle2_E5_TXT!="geschlossen"]
			Text item=Tankstelle3_E5_TXT label="[%s]" visibility=[Tankstelle3_E5_TXT!="geschlossen"]}}}

Martin from Tankerkönig informed my yesterday that we should use the prices.php because it is more efficient than the detail script. I found two main changes:

1.) In the Item definition the ID should be changed to IDS in the URL. The rest is ok
2.) In the JSON script detail should be replaced by prices

Currentlich i do not use the option to get mor than one station in a request, because i do not know how to handle that correctly. And unfortunately my openhab2 does not send any https requests at the moment.

Thomas

1 Like

Somehow this isn’t working for me. Any ideas?

val ArrayList<Integer> activeValvesUnOrdered = new ArrayList<Integer>
    gIrrigationTime.members.filter[t|t.state > 0].forEach[i|
        activeValvesUnOrdered.add(Integer::parseInt(i.name.toString.split("_").get(1)))
    ]
    val ArrayList<Integer> activeValvesOn = new ArrayList<Integer>
    activeValvesOn = ArrayList::sort(activeValvesUnOrdered)
2017-04-18 22:00:52.725 [ERROR] [.script.engine.ScriptExecutionThread] - Rule 'Irrigation switch cascade': An error occured during the script execution: The name '<XFeatureCallImplCustom>::sort(<XFeatureCallImplCustom>)' cannot be resolved to an item or type.

Edit: This works:

Collections.sort(activeValvesUnOrdered)

i get an error, why? I only want receive the diesel price from this station.

2018-01-17 22:06:35.985 [ERROR] [org.openhab.io.net.http.HttpUtil ] - Fatal transport error: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
2018-01-17 22:06:35.990 [ERROR] [ab.binding.http.internal.HttpBinding] - No response received from ‘https://creativecommons.tankerkoenig.de/json/detail.php?id=51d4b546-a095-1aa0-e100-80009459e03a&apikey=xxxxx

Item:
Number JET “JET Station [Diesel: %.4f €]” {
http="<[https://creativecommons.tankerkoenig.de/json/detail.php?id=51d4b546-a095-1aa0-e100-80009459e03a&apikey=xxx:60000:REGEX(.?diesel.?:.(.?),.)]"
}

René

I’m not certain but I would guess that the website is using an SSL certificate signed by a Certificate Authority that your Java does not trust. Or perhaps the certification is malformed. If that is the case there is likely not much you can do about it if the site doesn’t have an http:// address you can use.

The call in Explorer is error-free, also with POSTMAN. If I start the query with http, it automatically switches to https.

Whether it works or not in Explorer or POSTMAN is irrelevant if the certificate is not signed by a CA in Java’s list of trusted CAs.

okay, I have asked the provider and wait for the answer

Anyone with a working solution ?
Thanks in advance.

Because “Working with Groups” from @rlkoshak is one of my favorite approaches I just use:
(Where G_Fuel is: Group:Number:MIN G_Fuel "Sprit-Preise")

rule "lowest fuel price check"
when
    Member of G_Fuel changed
then
	var itemLabel = triggeringItem.label.toString
	var itemState = triggeringItem.state as Number
	TK_Chpst_TS.postUpdate(itemLabel)
	TK_Chpst_Dsl.postUpdate(itemState)
	logInfo("weather.rules", "*** Member of G_Fuel changed. ItemLabel: " + itemLabel + " Wert: " + itemState + " G_Fuel: " + G_Fuel.state.toString)
end