I do a “Integration” of Andorra Fuel/Gas prices with command line. If it’s helpful.
Looks like that might use some Ajax calls so could potentially add as a source
If only I could. Perhaps I should lobby my MP to make it mandatory that all fuel retailers upload their data!
Good to see that Costco is now included though so thanks for that!
Ask your MP to get the CMA to hurry up and make a decision about what they want to do lol, the public need good quality fuel price data in an accessible way, which will drive competition in the market. You are more likely to go to that Shell garage at 136.9 than the other Shell garage a mile away costing 154.9.
Plus for towns etc it could boost local economy… all a win win, but I guess the usual gov red tape gets in the way. Anyway too much politics now for my liking haha.
Hi all.
Got this up and running for my home location. Works great!
Working on adding a similar list but using my phone location for lat/long.
The plan would be to load up the dashboard and call the script from a refresh button or maybe update it periodically.
I’ve got the script working and returning the correct information, but I can’t figure out how to get the script response into an entity that I can use on the dashboard.
I’m guessing I should set the value of templated device in the script, but can’t wrap my head around it!
Script below:
find_fuel:
sequence:
- data:
type: E10
location:
latitude: |
{{ state_attr('device_tracker.my_phone', 'latitude') | float }}
longitude: |
{{ state_attr('device_tracker.my_phone', 'longitude') | float }}
radius: |
{{ 10000 }}
response_variable: data
action: fuel_prices.find_fuels
- if:
- condition: template
value_template: "{{ (data.fuels | count) > 0 }}"
then:
- variables:
response: |
{{ { 'value': data.fuels } }}
- stop: ''
response_variable: response
This returns the following if I call the script from developer tools (truncated for ease!)
value:
- id: asda_gcjt9rht7c4s
name: Asda CF40 2JQ
address: Colliers Way,, Tonypandy
postal_code: CF40 2JQ
latitude: 51.62609
longitude: -3.45367
brand: Asda
available_fuels:
E10: 1.317
B7: 1.4069999999999998
fuel_details:
E10: {}
B7: {}
currency: GBP
last_updated: "2025-01-12T16:13:16.383004"
next_update: "2025-01-13T16:13:16.335234"
props:
source: asda
source_id: gcjt9rht7c4s
prevent_cache_cleanup: true
distance: 4.263773178123694
cost: 1.317
- id: motorfuelgroup_gcjtbdjwg42k
name: Texaco CF41 7PN
address: MFG Ton Pentre, Ystrad Road, Pentre
postal_code: CF41 7PN
latitude: 51.6479
longitude: -3.48597
brand: Texaco
available_fuels:
E10: 1.339
E5: 1.5590000000000002
B7: 1.429
SDV: 1.599
fuel_details:
E10: {}
E5: {}
B7: {}
SDV: {}
currency: GBP
last_updated: "2025-01-12T16:13:16.466836"
next_update: "2025-01-13T16:13:16.432061"
props:
source: motorfuelgroup
source_id: gcjtbdjwg42k
prevent_cache_cleanup: true
distance: 3.342210286266886
cost: 1.339
Appreciate any help you can offer.
Hi,
Trigger templates are what you are after. There is lots of info and examples on the forms. But as a starting point:
I will post an example of what I use for tracking fuel prices around my car (Traccar).
Thanks @11harveyj.
I thought it would be something like this, but I still can’t wrap my head around how it’s supposed to work!
Any examples much appreciated!
Here you go, basically this updates whenever sensor.entity_with_location
moves or when home assistant starts, or when I change a input boolean to use cheapest fuels (if off it will use nearest) or finally whenever a button is pressed. This is for your configuration.yaml file under the template
key.
The service call expects the location in meters (because thats how the selector works in the UI), so I’ve done 5 * 1609.34
for 5 miles.
- trigger:
- platform: state
entity_id:
- sensor.entity_with_location
attribute: longitude
- platform: state
entity_id:
- sensor.entity_with_location
attribute: latitude
- platform: homeassistant
event: start
- platform: state
entity_id:
- input_button.car_update_nearest_fuels
- input_boolean.car_use_cheapest_station
action:
- service: fuel_prices.find_fuel_station
data:
location:
latitude: |
{{ state_attr('sensor.entity_with_location', 'latitude') | float }}
longitude: |
{{ state_attr('sensor.entity_with_location', 'longitude') | float }}
radius: |
{{ 5 * 1609.34 }}
response_variable: data
- variables:
data: >
{% if states('input_boolean.car_use_cheapest_station') == 'on' %}
{{ data['items'] | sort(attribute='available_fuels.B7') }}
{% else %}
{{ data['items'] | sort(attribute='distance') }}
{% endif %}
sensor:
- name: Car Nearest Fuel Station
unique_id: Car Nearest Fuel Station
state: |
{{ data[0].available_fuels.B7 | float }}
availability: |
{{ data | count > 0 }}
state_class: total
device_class: monetary
unit_of_measurement: GBP
attributes:
other_stations: |
{{ data }}
latitude: |
{{ data[0].latitude }}
longitude: |
{{ data[0].longitude }}
name: |
{{ data[0].name }}
station: |
{{ data[0] }}
If all goes well you should get an entity like this:
I’m only interested in diesel so have selected B7 as the state, but you can change that to whatever fuel type you like (E10, E5 etc.)
If you want multiple sensors, just add more to the bottom under the sensor key. All of the data from the service call will still be available under the data
variable.
Looks great!
Won’t quite work for me unfortunately. Having a list of prices and postcodes is a necessity!
I’ve managed to get it working, but can’t get it styled nicely.
Managed to get it working and looking nice too (both with a little help from ChatGPT!!)
This can be updated by clicking/tapping the header, and returns a list of petrol stations within a 10 mile radius of whatever GPS device you’re using (my phone in this case)
Clicking/tapping on the station opens a link in Google Maps.
If anyone’s interested in how I did it, let me know!
I’ve got a docs site, I can publish your solution (crediting you of course) onto there if you like?
I’ll put it all together later. There are quite a few requirements to getting it to work!
There’s probably a more elegant solution too…
Do you mind sharing your code for this card?
Nearby Stations
Some prerequisites:
- You will need the python scripts integration running.
Add the following to your configuration.yaml if it’s not already there.
python_script:
- For the Lovelace card, you will need the Lovelace HTML Jinja2 Template card.
Download here: https://github.com/PiotrMachowski/Home-Assistant-Lovelace-HTML-Jinja2-Template-card
Or install via HACS.
Once that’s done, we need to create two input_text entities to handle the fuel station info. I split the post/zip code into a separate entity to increase the number of results before hitting the 255 character limit.
Add the following to configuration.yaml
input_text: #no need to duplicate this if it already exists!
fuel_prices_data:
name: Fuel Prices Data
initial: Reloaded
max: 255
fuel_postcodes:
name: Fuel Postcodes
initial: Reloaded
max: 255
We also need to add a template sensor to the template section of configuration.yaml.
template: #no need to duplicate this if it already exists!
- sensor:
- name: "Fuel Price List"
state: >
{% set fuel_list = states('input_text.fuel_prices_data') or 'No fuel data available' %}
{{ fuel_list }}
attributes:
formatted_list: >
{% set fuels = states('input_text.fuel_prices_data') %}
{% if fuels %}
{% if fuels != 'reloading'%}
{% set postcodes = states('input_text.fuel_postcodes') %}
{% set fuels = fuels.split(', ') %}
{% set postcodes = postcodes.split(',') %}
{% for fuel in fuels %}
{% set index = loop.index0 %}
{% set parts = fuel.split(': ') %}
{% set parts2 = parts[0].split(' - ') %}
{% set fuel_name = parts2[1].replace(' ', '+') %}
{% set postcode = postcodes[index].replace(' ', '+') %}
{% set query = fuel_name ~ '+' ~ postcode %}
<div class="fuelrow" style="width: 100%; display: flex; align-items: center; padding: 0px; margin-bottom: 8px;">
<span style="color: rgb(68, 115, 158); height: 40px; width: 40px; line-height: 40px; text-align: center;"><ha-icon icon="mdi:gas-station"></ha-icon></span>
<span style="margin-left: 16px;"><a href="https://www.google.com/maps/search/?api=1&query={{ query }} petrol" target="_blank" style="text-decoration: none; color: inherit;">{{ parts[0] }}</a></span><span style='margin-left: auto;'>£{{ parts[1] }}</span>
</div>
{% endfor %}
{% else %}
<div>Searching...</div>
{% endif %}
{% else %}
<div>No fuel data available.</div>
{% endif %}
Then add the following to scripts.yaml, set the fuel type and radius, and also change the device tracker to whatever device you are using.
find_fuel:
sequence:
- action: input_text.set_value
target:
entity_id: input_text.fuel_prices_data
data:
value: reloading
- data:
type: E10 #change to whatever fuel type you need
location:
latitude: |
{{ state_attr('device_tracker.your_device', 'latitude') | float }}
longitude: |
{{ state_attr('device_tracker.your_device', 'longitude') | float }}
radius: |
{{ 10 * 1609.34 }}
#change 10 to whatever radius you want in miles.
response_variable: data
action: fuel_prices.find_fuels
- if:
- condition: template
value_template: "{{ (data.fuels | count) > 0 }}"
then:
- variables:
response: |
{{ { 'value': data.fuels } }}
- service: python_script.set_fuel_prices
data:
fuels: "{{ data.fuels }}"
- stop: ''
response_variable: response
Last step is to save the following as set_fuel_prices.py in the python_scripts folder in your config folder.
This iterates through the results from fuel_prices.find_fuels and sends the results to the text_inputs we set up earlier.
fuels = data.get('fuels', [])
fuel_strings = [f"{fuel['distance']:.1f} mi - {fuel['brand']}: {fuel['cost']:.2f}" for fuel in fuels]
fuel_postcodes = [f"{fuel['postal_code']}" for fuel in fuels]
max_length = 255
restricted_fuel_strings = []
final_fuel_postcodes = []
length_so_far = 0
i = 0
for fuel_string in fuel_strings:
additional_length = len(fuel_string) + (2 if restricted_fuel_strings else 0)
if length_so_far + additional_length <= max_length:
restricted_fuel_strings.append(fuel_string)
final_fuel_postcodes.append(fuel_postcodes[i])
length_so_far += additional_length
i += 1
else:
break
fuel_data_str = ", ".join(restricted_fuel_strings)
fuel_postcode_str = ",".join(final_fuel_postcodes)
hass.services.call('input_text', 'set_value', {
'entity_id': 'input_text.fuel_prices_data',
'value': fuel_data_str
})
hass.services.call('input_text', 'set_value', {
'entity_id': 'input_text.fuel_postcodes',
'value': fuel_postcode_str
})
Restart everything and head over to your dashboard.
Add the following card:
type: grid
cards:
- type: heading
icon: mdi:gas-station
heading: Nearby
heading_style: title
tap_action:
action: perform-action
perform_action: script.find_fuel
target: {}
- type: custom:html-template-card
content: |
{{ state_attr('sensor.fuel_price_list', 'formatted_list') }}
And hopefully, that should do it.
Press the “Nearby” header to reload the list. Tap a result to open directions in Google Maps.
By default, results are ordered by price. I’ve not managed to figure out how to sort by distance yet, but I’m sure it’s pretty straight-forward if you know how!
I’m also sure there is room for improvement here.
We could add options to the frontend to choose fuel type and to order by distance, price, etc. That should all be pretty easy.
After copy/pasting the above I get the error:
Failed to perform the action script/find_fuel. Template output exceeded maximum size of 262144 characters
Any ideas on what the problem is? Thanks.
It’s probably the Fuel Price List template sensor going over the maximum size. Not something I’d encountered, or even knew existed to be honest!
Let’s try taking out the Google Maps link, see if that works.
Replace the template sensor in configuration with the following:
template:
- sensor:
- name: "Fuel Price List"
state: >
{% set fuel_list = states('input_text.fuel_prices_data') or 'No fuel data available' %}
{{ fuel_list }}
attributes:
formatted_list: >
{% set fuels = states('input_text.fuel_prices_data') %}
{% if fuels %}
{% if fuels != 'reloading'%}
{% set postcodes = states('input_text.fuel_postcodes') %}
{% set fuels = fuels.split(', ') %}
{% set postcodes = postcodes.split(',') %}
{% for fuel in fuels %}
{% set index = loop.index0 %}
{% set parts = fuel.split(': ') %}
{% set parts2 = parts[0].split(' - ') %}
{% set fuel_name = parts2[1].replace(' ', '+') %}
{% set postcode = postcodes[index].replace(' ', '+') %}
{% set query = fuel_name ~ '+' ~ postcode %}
<div class="fuelrow" style="width: 100%; display: flex; align-items: center; padding: 0px; margin-bottom: 8px;">
<span style="color: rgb(68, 115, 158); height: 40px; width: 40px; line-height: 40px; text-align: center;"><ha-icon icon="mdi:gas-station"></ha-icon></span>
<span style="margin-left: 16px;">{{ parts[0] }}</span><span style='margin-left: auto;'>£{{ parts[1] }}</span>
</div>
{% endfor %}
{% else %}
<div>Searching...</div>
{% endif %}
{% else %}
<div>No fuel data available.</div>
{% endif %}
If that works, but you want the map link in there, we’ll figure something out.
Replaced the syntax but same error result. Thanks.
Hmmmm…
Can you run the script from Developer Tools and paste the output here?
I have run ‘Scripts.find_fuel’ and get the same error message
Failed to perform the action script/find_fuel. Template output exceeded maximum size of 262144 characters
I would say the template engine is internally rendering the template into text before it’s being sent to the python script.
You might want to try lowering your radius, I know there are some providers who give us loads of attributes, and other providers who give us hardly any.
I think the contents of the HA script could probably done inside the python script though to eliminate that step entirely.