If you are interested in gas prices for certain gas stations, you need to sign up for a (free) API key, and figure out the IDs of the gas stations. For that go to this website, Position the blue marker on the location you are interested in, and then click on all the gas stations you want to include. Finally click on Tankstellen übernehmen - this will open a JSON with the data, from there extract the id (and possibly the other data as well).
Binding
As usual I’m deploying everything using Ansible, which makes my life here so much easier. Let’s start by installing the Binding:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- name:Get list of available and installed extensionsuri:url:"http://{{ ansible_host }}:8080/rest/extensions"register:oh2_extensionschanged_when:false- name:Install extensionsuri:url:"http://{{ ansible_host }}:8080/rest/extensions/{{ item }}/install"method:POSTwhen:"not (oh2_extensions.json|byattr('id', item))[0].installed"with_items:- binding-tankerkoenigregister:oh2_install_extensions
This can of course be also done by using the Paper UI, but this way I can reinstall the openHAB system any time I want.
Things
Now that I have a list of IDs, I put them into a list in Ansible, and use a loop to create the tankerkoenig.things file:
The Binding provides one global Item, the holiday channel. That is a Switch which indicates if today is a holiday. For every gas station in the list, there are four different channels: e10, e5, diesel and station_open. The .items file needs a couple new channels, and I’m rolling this out using an Ansible Template:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Group GasStationPrices_E10
Group GasStationPrices_E5
Group GasStationPrices_Diesel
Group GasStation_Open
Switch Station_Holidays "Today is holiday: [%s]"{ channel="tankerkoenig:webservice:Tankpreise:holiday"}{%for tk in tankerkoenig %}// Station: {{ tk[1] }}
Number TK_{{ tk[0]}}_E10 "E10 [%.3f €]"(GasStationPrices_E10){ channel="tankerkoenig:station:Tankpreise:{{ tk[0] }}:e10"}
Number TK_{{ tk[0]}}_E5 "E5 [%.3f €]"(GasStationPrices_E5){ channel="tankerkoenig:station:Tankpreise:{{ tk[0] }}:e5"}
Number TK_{{ tk[0]}}_Diesel "Diesel [%.3f €]"(GasStationPrices_Diesel){ channel="tankerkoenig:station:Tankpreise:{{ tk[0] }}:diesel"}
Contact TK_{{ tk[0]}}_Open "Station is [%s]"(GasStation_Open){ channel="tankerkoenig:station:Tankpreise:{{ tk[0] }}:station_open"}{% endfor %}
Using a template makes it easy to make any modifications. The resulting /etc/openhab2/items/tankerkoenig.items file:
Group GasStationPrices_E10
Group GasStationPrices_E5
Group GasStationPrices_Diesel
Group GasStation_Open
Switch Station_Holidays "Today is holiday: [%s]"{ channel="tankerkoenig:webservice:Tankpreise:holiday"}// Station: Aral 1
Number TK_Aral1_E10 "E10 [%.3f €]"(GasStationPrices_E10){ channel="tankerkoenig:station:Tankpreise:Aral1:e10"}
Number TK_Aral1_E5 "E5 [%.3f €]"(GasStationPrices_E5){ channel="tankerkoenig:station:Tankpreise:Aral1:e5"}
Number TK_Aral1_Diesel "Diesel [%.3f €]"(GasStationPrices_Diesel){ channel="tankerkoenig:station:Tankpreise:Aral1:diesel"}
Contact TK_Aral1_Open "Station is [%s]"(GasStation_Open){ channel="tankerkoenig:station:Tankpreise:Aral1:station_open"}// Station: Shell 1
Number TK_Shell1_E10 "E10 [%.3f €]"(GasStationPrices_E10){ channel="tankerkoenig:station:Tankpreise:Shell1:e10"}
Number TK_Shell1_E5 "E5 [%.3f €]"(GasStationPrices_E5){ channel="tankerkoenig:station:Tankpreise:Shell1:e5"}
Number TK_Shell1_Diesel "Diesel [%.3f €]"(GasStationPrices_Diesel){ channel="tankerkoenig:station:Tankpreise:Shell1:diesel"}
Contact TK_Shell1_Open "Station is [%s]"(GasStation_Open){ channel="tankerkoenig:station:Tankpreise:Shell1:station_open"}// Station: Esso 1
Number TK_Esso1_E10 "E10 [%.3f €]"(GasStationPrices_E10){ channel="tankerkoenig:station:Tankpreise:Esso1:e10"}
Number TK_Esso1_E5 "E5 [%.3f €]"(GasStationPrices_E5){ channel="tankerkoenig:station:Tankpreise:Esso1:e5"}
Number TK_Esso1_Diesel "Diesel [%.3f €]"(GasStationPrices_Diesel){ channel="tankerkoenig:station:Tankpreise:Esso1:diesel"}
Contact TK_Esso1_Open "Station is [%s]"(GasStation_Open){ channel="tankerkoenig:station:Tankpreise:Esso1:station_open"}
After a couple minutes, the Binding will report ONLINE, and start fetching data.
Telegram
All the data is near worthless when I can’t query it when needed. We already have a Telegram Bot sitting in a channel which reports all kind of events. That bot can also answer queries. On top of that I want the prices sorted, lowest first. That requires a little bit of Java in the .rules file:
importjava.util.HashMap
rule "Telegram Bot receive tanken"
when
Item telegramlastMessageDate received update
then
// no option selected, send back the available options
if(telegramLastMessage.state.toString.toLowerCase=="/tanken"){
val telegramAction = getActions("telegram","telegram:telegramBot:HA_Bot")
telegramAction.sendTelegram(Long::parseLong(telegramLastMessageChatId.state.toString),"Options:\n/tanken e10\n/tanken diesel\n/tanken e5")// select and sort Diesel prices, then return the list
}elseif(telegramLastMessage.state.toString.toLowerCase=="/tanken diesel"){
var reply ="Dieselpreise:"
val HashMap<String, DecimalType> DieselMap = newHashMap
{%for tk in tankerkoenig %}if(TK_{{ tk[0]}}_Diesel.state!= NULL && TK_{{ tk[0]}}_Diesel.state!= UNDEF && TK_{{ tk[0]}}_Open.state== OPEN){
DieselMap.put("{{ tk[1] }}",(TK_{{ tk[0]}}_Diesel.state as DecimalType))}{% endfor %}for(Price : DieselMap.entrySet.sortBy[value]){
var String key = Price.getKey().toString
var String value = Price.getValue().toString
reply = reply + String::format("\n%s: `%s`€", key, value)}
logInfo("Dieselpreise", reply)
val telegramAction = getActions("telegram","telegram:telegramBot:HA_Bot")
telegramAction.sendTelegram(Long::parseLong(telegramLastMessageChatId.state.toString), reply)// select and sort E10 prices, then return the list
}elseif(telegramLastMessage.state.toString.toLowerCase=="/tanken e10"){
var reply ="Preise E10:"
val HashMap<String, DecimalType> E10Map = newHashMap
{%for tk in tankerkoenig %}if(TK_{{ tk[0]}}_E10.state!= NULL && TK_{{ tk[0]}}_E10.state!= UNDEF && TK_{{ tk[0]}}_Open.state== OPEN){
logInfo("Tankerkoenig E10 Debug","{}: {}","{{ tk[1] }}", TK_{{ tk[0]}}_E10.state.toString)
E10Map.put("{{ tk[1] }}",(TK_{{ tk[0]}}_E10.state as DecimalType))}{% endfor %}for(Price : E10Map.entrySet.sortBy[value]){
var String key = Price.getKey().toString
var String value = Price.getValue().toString
reply = reply + String::format("\n%s: `%s`€", key, value)}
logInfo("Preise E10", reply)
val telegramAction = getActions("telegram","telegram:telegramBot:HA_Bot")
telegramAction.sendTelegram(Long::parseLong(telegramLastMessageChatId.state.toString), reply)// select and sort E5 prices, then return the list
}elseif(telegramLastMessage.state.toString.toLowerCase=="/tanken e5"){
var reply ="Preise E5:"
val HashMap<String, DecimalType> E5Map = newHashMap
{%for tk in tankerkoenig %}if(TK_{{ tk[0]}}_E5.state!= NULL && TK_{{ tk[0]}}_E5.state!= UNDEF && TK_{{ tk[0]}}_Open.state== OPEN){
E5Map.put("{{ tk[1] }}",(TK_{{ tk[0]}}_E5.state as DecimalType))}{% endfor %}for(Price : E5Map.entrySet.sortBy[value]){
var String key = Price.getKey().toString
var String value = Price.getValue().toString
reply = reply + String::format("\n%s: `%s`€", key, value)}
logInfo("Preise E5", reply)
val telegramAction = getActions("telegram","telegram:telegramBot:HA_Bot")
telegramAction.sendTelegram(Long::parseLong(telegramLastMessageChatId.state.toString), reply)// currently unknown option, send back an error
}elseif(telegramLastMessage.state.toString.length()> 6 && telegramLastMessage.state.toString.toLowerCase.substring(0, 7)=="/tanken"){
val telegramAction = getActions("telegram","telegram:telegramBot:HA_Bot")
telegramAction.sendTelegram(Long::parseLong(telegramLastMessageChatId.state.toString),"Unknown option!")}
end
Let’s go over the details:
1
importjava.util.HashMap
I’m using a hash map in the Rule, need to include the Java library for HashMap.
1
2
3
4
rule "Telegram Bot receive tanken"
when
Item telegramlastMessageDate received update
then
I’m using the last message date for the trigger, because a user can send the same message multiple times, and that will not trigger an update for the message content.
1
2
3
4
// no option selected, send back the available options
if(telegramLastMessage.state.toString.toLowerCase=="/tanken"){
val telegramAction = getActions("telegram","telegram:telegramBot:HA_Bot")
telegramAction.sendTelegram(Long::parseLong(telegramLastMessageChatId.state.toString),"Options:\n/tanken e10\n/tanken diesel\n/tanken e5")
If just the /tanken command is sent, send back a list of available gas options.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// select and sort Diesel prices, then return the list
}elseif(telegramLastMessage.state.toString.toLowerCase=="/tanken diesel"){
var reply ="Dieselpreise:"
val HashMap<String, DecimalType> DieselMap = newHashMap
{%for tk in tankerkoenig %}if(TK_{{ tk[0]}}_Diesel.state!= NULL && TK_{{ tk[0]}}_Diesel.state!= UNDEF && TK_{{ tk[0]}}_Open.state== OPEN){
DieselMap.put("{{ tk[1] }}",(TK_{{ tk[0]}}_Diesel.state as DecimalType))}{% endfor %}for(Price : DieselMap.entrySet.sortBy[value]){
var String key = Price.getKey().toString
var String value = Price.getValue().toString
reply = reply + String::format("\n%s: `%s`€", key, value)}
logInfo("Dieselpreise", reply)
val telegramAction = getActions("telegram","telegram:telegramBot:HA_Bot")
telegramAction.sendTelegram(Long::parseLong(telegramLastMessageChatId.state.toString), reply)
This piece of code is the workhorse for the Diesel prices (and the E10 and E5 code are similar). It’s a mix of Ansible templating for the gas stations and Java code here. I think one can extract the data from the GasStationPrices_Diesel and GasStation_Open group directly in Java, without creating the if statements in Ansible in a loop, but my Java is not good enough for this.
First I’m creating a new empty HashMap. This hash map is populated with data from all currently open gas stations which report a price for the selected fuel. The internal station name is used as key, the fuel price as value. Doesn’t work the other way around: a hash map can only hold one value for a key, and multiple gas stations can have the same price. Closed gas stations are ignored. Finally the reply is populated by going over the by-value sorted list, and adding all station names and prices. The resulting string is send back as reply using the Telegram Bot.
The final version in /etc/openhab2/rules/tankerkoenig.rules: