Prix Carburant - quelle extension choisir?

J’ai fixé le json mais c’est bizarre car ça fonctionne chez moi aussi sans le fix.

hello, suites aux quelques issues et PR sur le sujet, j’ai fait une beta qui change le moyen d’avoir les infos des stations:
on prend la datasource mergée d’OSM puis on override par le fichier local qu’on avait déjà actuellement
si certains peuvent tester la dernière beta (dispo via HACS), cf la PR

Hello @Aohzan

Merci pour l'intégration, c'est elle qui fait tout le travail ingrat.

Pour l'affichage, j'ai écrit une carte Lovelace qui se branche directement dessus :
Prix Carburant Card — GitHub - Pulpyyyy/carte-burant: Carte Lovelace pour Home Assistant : tableau des prix des carburants par station et par carburant, à partir de l'intégration Prix Carburant. · GitHub

Prix Carburant Card

Prix Carburant Card

Une ligne par station, une colonne par carburant. Elle lit les entités de
l'intégration par leurs attributs (station_id + fuel_type) et non par
leur entity_id : renommer une entité ne casse rien, et il n'y a aucun sensor
template à écrire.

En vrac :

  • choix et ordre des stations et des colonnes, à la souris comme en YAML ;
  • tri configurable, et clic sur les en-têtes pour trier à la volée ;
  • prix le plus bas en vert, le plus haut en rouge, ex æquo compris ;
  • noms et villes surchargeables, logos par enseigne ;
  • éditeur graphique complet, en français et en anglais.

Installation : HACS → dépôt personnalisé → catégorie Lovelace. Sans aucune
configuration, elle trouve toutes les stations remontées par l'intégration,
triées par distance :

type: custom:prix-carburant-card

Je voulais juste faire un petit bouton pour rechercher les stations autour de moi quand je suis ailleurs qu'a la maison.
Ensuite il fallait que ca ne jure pas trop avec la carte de @Pulpy-Luke donc on copie le style au mieux.
Ensuite pourquoi limiter l'affichage à un seul carburant donc on lance plusieur recherche et on fusionne l'affichage.
Ensuite je ne suis pas tout seul a la maison donc le bouton doit prendre en compte quel utilisateur clicque dessus... bref ca termine en monstre :downcast_face_with_sweat:

Par defaut j'affiche la carte de @Pulpy-Luke

Quand un membre de la famille clique sur le bouton "Stations autour de moi", ca execute un script de recherche et j'affiche les stations-service trouvées. Les informations remontées étant beaucoup moins détaillées (cf doc de l'integration), c'est moins jolie mais ca reste très pratique. Les noms des stations-service sont cliquables et ouvrent directement Google Maps sur la localisation de la station (<- Peut etre une option à ajouter sur ta carte @Pulpy-Luke ?)

Pour en arriver la, il faut:

Un template sensor à ajouter dans le fichier templates.yaml (le monstre)

Il est gros mais il fait la fusion de toutes les recherches par stations-services.

- trigger:
    - platform: event
      event_type: nearby_gas_stations

  sensor:
    - name: Gas Stations Near Me
      unique_id: gas_stations_near_me
      state: "{{ now().isoformat() }}"
      attributes:
        stations: >
          {% set e10 = trigger.event.data.stations_e10 or [] %}
          {% set sp95 = trigger.event.data.stations_sp95 or [] %}
          {% set sp98 = trigger.event.data.stations_sp98 or [] %}
          {% set gazole = trigger.event.data.stations_gazole or [] %}
          {% set e85 = trigger.event.data.stations_e85 or [] %}
          {% set gpl = trigger.event.data.stations_gpl or [] %}
          {% set gas_stations = namespace(items=[]) %}

          {% for e in e10 %}
            {% set match_sp95 = sp95
              | selectattr('name', 'eq', e.name)
              | selectattr('address', 'eq', e.address)
              | list
            %}
            {% set match_sp98 = sp98
              | selectattr('name', 'eq', e.name)
              | selectattr('address', 'eq', e.address)
              | list
            %}
            {% set match_gazole = gazole
              | selectattr('name', 'eq', e.name)
              | selectattr('address', 'eq', e.address)
              | list
            %}
            {% set match_e85 = e85
              | selectattr('name', 'eq', e.name)
              | selectattr('address', 'eq', e.address)
              | list
            %}
            {% set match_gpl = gpl
              | selectattr('name', 'eq', e.name)
              | selectattr('address', 'eq', e.address)
              | list
            %}

            {% set gas_stations.items = gas_stations.items + [{
              'name': e.name,
              'address': e.address,
              'latitude': e.latitude,
              'longitude': e.longitude,
              'e10': e.price,
              'sp95': match_sp95[0].price if match_sp95 | count > 0 else none,
              'sp98': match_sp98[0].price if match_sp98 | count > 0 else none,
              'gazole': match_gazole[0].price if match_gazole | count > 0 else none,
              'e85': match_e85[0].price if match_e85 | count > 0 else none,
              'gpl': match_gpl[0].price if match_gpl | count > 0 else none
            }] %}
          {% endfor %}

          {% for s in sp95 %}
            {% set existing = gas_stations.items
              | selectattr('name', 'eq', s.name)
              | selectattr('address', 'eq', s.address)
              | list
            %}

            {% if existing | count == 0 %}
              {% set match_sp98 = sp98
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}
              {% set match_gazole = gazole
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}
              {% set match_e85 = e85
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}
              {% set match_gpl = gpl
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}

              {% set gas_stations.items = gas_stations.items + [{
                'name': s.name,
                'address': s.address,
                'latitude': s.latitude,
                'longitude': s.longitude,
                'e10': none,
                'sp95': s.price,
                'sp98': match_sp98[0].price if match_sp98 | count > 0 else none,
                'gazole': match_gazole[0].price if match_gazole | count > 0 else none,
                'e85': match_e85[0].price if match_e85 | count > 0 else none,
                'gpl': match_gpl[0].price if match_gpl | count > 0 else none
              }] %}
            {% endif %}
          {% endfor %}

          {% for s in sp98 %}
            {% set existing = gas_stations.items
              | selectattr('name', 'eq', s.name)
              | selectattr('address', 'eq', s.address)
              | list
            %}

            {% if existing | count == 0 %}
              {% set match_gazole = gazole
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}
              {% set match_e85 = e85
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}
              {% set match_gpl = gpl
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}

              {% set gas_stations.items = gas_stations.items + [{
                'name': s.name,
                'address': s.address,
                'latitude': s.latitude,
                'longitude': s.longitude,
                'e10': none,
                'sp95': none,
                'sp98': s.price,
                'gazole': match_gazole[0].price if match_gazole | count > 0 else none,
                'e85': match_e85[0].price if match_e85 | count > 0 else none,
                'gpl': match_gpl[0].price if match_gpl | count > 0 else none
              }] %}
            {% endif %}
          {% endfor %}

          {% for s in gazole %}
            {% set existing = gas_stations.items
              | selectattr('name', 'eq', s.name)
              | selectattr('address', 'eq', s.address)
              | list
            %}

            {% if existing | count == 0 %}
              {% set match_e85 = e85
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}
              {% set match_gpl = gpl
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}

              {% set gas_stations.items = gas_stations.items + [{
                'name': s.name,
                'address': s.address,
                'latitude': s.latitude,
                'longitude': s.longitude,
                'e10': none,
                'sp95': none,
                'sp98': none,
                'gazole': s.price,
                'e85': match_e85[0].price if match_e85 | count > 0 else none,
                'gpl': match_gpl[0].price if match_gpl | count > 0 else none
              }] %}
            {% endif %}
          {% endfor %}

          {% for s in e85 %}
            {% set existing = gas_stations.items
              | selectattr('name', 'eq', s.name)
              | selectattr('address', 'eq', s.address)
              | list
            %}

            {% if existing | count == 0 %}
              {% set match_gpl = gpl
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}

              {% set gas_stations.items = gas_stations.items + [{
                'name': s.name,
                'address': s.address,
                'latitude': s.latitude,
                'longitude': s.longitude,
                'e10': none,
                'sp95': none,
                'sp98': none,
                'gazole': none,
                'e85': s.price,
                'gpl': match_gpl[0].price if match_gpl | count > 0 else none
              }] %}
            {% endif %}
          {% endfor %}

          {% for s in gpl %}
            {% set existing = gas_stations.items
              | selectattr('name', 'eq', s.name)
              | selectattr('address', 'eq', s.address)
              | list
            %}

            {% if existing | count == 0 %}
              {% set gas_stations.items = gas_stations.items + [{
                'name': s.name,
                'address': s.address,
                'latitude': s.latitude,
                'longitude': s.longitude,
                'e10': none,
                'sp95': none,
                'sp98': none,
                'gazole': none,
                'e85': none,
                'gpl': s.price
              }] %}
            {% endif %}
          {% endfor %}

          {{ gas_stations.items }}

Un bouton pour lancer la recherche

Ici il faut remplacer les tracker par les vôtres "Pierre": "person.pierre"
A gauche, indiquez le nom exact de l'utilisateur Home Assistant.
A droite, indiquez l'entité person (ou le tracker) associée à cet utilisateur.
Carte utilisé: button-card

type: custom:button-card
show_icon: true
show_name: true
show_state: false
show_label: true
color_type: icon
layout: icon_name
name: Stations autour de moi
icon: mdi:target
tap_action:
  action: perform-action
  perform_action: script.find_gas_stations_near_me
  data:
    tracker: |
      [[[
        const map = {
          "Pierre": "person.pierre",
          "Paul": "person.paul",
          "Jack": "person.jack"
        };
        return map[hass.user.name];
      ]]]
styles:
  card:
    - border-radius: 18px
    - padding: 5px
  grid:
    - grid-template-areas: '"i n"'
    - grid-template-columns: 56px auto
    - grid-template-rows: auto auto
    - column-gap: 0px
    - row-gap: 0px
    - align-items: center
  img_cell:
    - width: 40px
    - height: 40px
    - border-radius: 50%
    - display: flex
    - align-items: center
    - justify-content: center
    - justify-self: start
    - align-self: center
  icon:
    - width: 24px
    - height: 24px
  name:
    - font-size: 14px
    - font-weight: 700
    - line-height: 1.2
    - justify-self: start
    - white-space: nowrap
    - overflow: hidden
    - text-overflow: ellipsis
    - margin-left: -5px

Le script éxécuté par le bouton

Ici, vous pouvez supprimer les carburants qui ne vous intéressent pas et ajuster le rayon de recherche (ici 5 km).

alias: Find Gas Stations Near Me
sequence:
  - action: prix_carburant.find_nearest_stations
    data:
      distance: 5
      entity_id: '{{ tracker }}'
      fuel: E10
    response_variable: stations_e10
  - action: prix_carburant.find_nearest_stations
    data:
      distance: 5
      entity_id: '{{ tracker }}'
      fuel: SP95
    response_variable: stations_sp95
  - action: prix_carburant.find_nearest_stations
    data:
      distance: 5
      entity_id: '{{ tracker }}'
      fuel: SP98
    response_variable: stations_sp98
  - action: prix_carburant.find_nearest_stations
    data:
      distance: 5
      entity_id: '{{ tracker }}'
      fuel: Gazole
    response_variable: stations_gazole
  #- action: prix_carburant.find_nearest_stations
  #  data:
  #    distance: 5
  #    entity_id: '{{ tracker }}'
  #    fuel: E85
  #  response_variable: stations_e85
  #- action: prix_carburant.find_nearest_stations
  #  data:
  #    distance: 5
  #    entity_id: '{{ tracker }}'
  #    fuel: GPLc
  #  response_variable: stations_gpl
  - event: nearby_gas_stations
    event_data:
      stations_e10: '{{ stations_e10.stations }}'
      stations_sp95: '{{ stations_sp95.stations }}'
      stations_sp98: '{{ stations_sp98.stations }}'
      stations_gazole: '{{ stations_gazole.stations }}'
      #stations_e85: '{{ stations_e85.stations }}'
      #stations_gpl: '{{ stations_gpl.stations }}'
fields:
  tracker:
    description: Person entity or device_tracker


La carte pour afficher les stations-service

Carte utilisé: HTML Jinja2 Template card

type: custom:html-template-card
ignore_line_breaks: true
entities:
  - sensor.gas_stations_near_me
content: >-
  {% set stations = state_attr('sensor.gas_stations_near_me', 'stations') or [] %}
  {% set e10_prices = stations | map(attribute='e10') | reject('none') | list %}
  {% set sp95_prices = stations | map(attribute='sp95') | reject('none') | list %}
  {% set sp98_prices = stations | map(attribute='sp98') | reject('none') | list %}
  {% set gazole_prices = stations | map(attribute='gazole') | reject('none') | list %}
  {% set best_e10 = e10_prices | min if e10_prices else none %}
  {% set worst_e10 = e10_prices | max if e10_prices else none %}
  {% set best_sp95 = sp95_prices | min if sp95_prices else none %}
  {% set worst_sp95 = sp95_prices | max if sp95_prices else none %}
  {% set best_sp98 = sp98_prices | min if sp98_prices else none %}
  {% set worst_sp98 = sp98_prices | max if sp98_prices else none %}
  {% set best_gazole = gazole_prices | min if gazole_prices else none %}
  {% set worst_gazole = gazole_prices | max if gazole_prices else none %}

  <style>
    .center { text-align: center; }

    .gas-wrapper {
      overflow-x: auto;
      max-width: 100%;
      border-radius: 12px;
    }

    .gas-table {
      min-width: 850px;
      width: 100%;
      border-collapse: collapse;
      font-size: 14px;
      color: #ffffff;
    }

    .gas-table th {
      background: #1d1d1d;
      color: #a3a3a3;
      padding: 4px 6px;
      text-align: left;
      white-space: nowrap;
      font-weight: 600;
      border-bottom: 1px solid #333333;
      margin-bottom: 4px;
    }

    .gas-table td {
      padding: 4px 6px;
      border-bottom: 1px solid #2a2a2a;
      white-space: nowrap;
      vertical-align: middle;
    }

    .gas-table tr:nth-child(even) {
      background: #282828;
    }

    .gas-table tr:nth-child(odd) {
      background: #1c1c1C;
    }

    .gas-table td:first-child {
      min-width: 170px;
      max-width: 170px;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    .gas-table th:first-child {
      background: #1d1d1d;
    }

    .gas-table tbody tr:hover {
      background: #323232;
    }
    
    .gas-table tbody tr:hover td {
      background: #323232;
    }
    
    .gas-table tbody tr:hover td:first-child {
      background: #323232;
    }

    .gas-title {
      margin: 0 0 10px 0;
      font-weight: 700;
      font-size: 14px;
      color: #ffffff;
    }

    .station-link,
    .station-link:visited {
      color: #ffffff;
      text-decoration: underline;
      text-decoration-style: dotted;
      text-underline-offset: 2px;
      font-size: 14px;
      display: inline-block;
      max-width: 150px;
      overflow: hidden;
      text-overflow: ellipsis;
      vertical-align: middle;
    }
    
    .station-link:hover {
      color: #369bd4;
      text-decoration: underline;
      text-decoration-style: dotted;
      text-underline-offset: 2px;
    }
    
    .station-link:active {
      color: #369bd4;
    }

    .price {
      text-align: right;
      font-size: 14px;
    }

    .best-price {
      color: #4caa40;
      font-size: 14px;
      font-weight: 700;
      text-align: center;
    }

    .middle-price {
      font-size: 14px;
      text-align: center;
    }

    .worst-price {
      color: #e05252;
      font-size: 14px;
      font-weight: 700;
      text-align: center;
    }

    .empty-price {
      color: #ffffff;
      text-align: center;
    }

    .address {
      font-size: 14px;
    }
  </style>

  <div class="gas-title">
    ⛽ {{ stations | count }} stations trouvées
  </div>

  <div class="gas-wrapper">
    <table class="gas-table">
      <thead>
        <tr>
          <th>Station</th>
          <th>E10 €/L</th>
          <th>SP95 €/L</th>
          <th>SP98 €/L</th>
          <th>Diesel €/L</th>
          <th>City</th>
          <th>Address</th>
        </tr>
      </thead>

      <tbody>
        {% for s in stations %}
          {% set address_parts = s.address.split(',') %}
          {% set street = address_parts[0].strip() %}
          {% set city_raw = address_parts[-1].strip() %}
          {% set city = city_raw | regex_replace('^[0-9]{5}\\s*', '') %}
          {% set maps_url = 'https://www.google.com/maps/search/?api=1&query=' ~ s.latitude ~ ',' ~ s.longitude %}

          <tr>
            <td title="{{ s.name }}">              
                <a class="station-link" target="_blank" href="{{ maps_url }}">{{ s.name }}</a>
              </a>
            </td>

            {% if s.e10 is not none %}
              {% if s.e10 == best_e10 %}
                <td class="best-price">{{ s.e10 }}</td>
              {% elif s.e10 == worst_e10 %}
                <td class="worst-price">{{ s.e10 }}</td>
              {% else %}
                <td class="middle-price">{{ s.e10 }}</td>
              {% endif %}
            {% else %}
              <td class="empty-price">-</td>
            {% endif %}

            {% if s.sp95 is not none %}
              {% if s.sp95 == best_sp95 %}
                <td class="best-price">{{ s.sp95 }}</td>
              {% elif s.sp95 == worst_sp95 %}
                <td class="worst-price">{{ s.sp95 }}</td>
              {% else %}
                <td class="middle-price">{{ s.sp95 }}</td>
              {% endif %}
            {% else %}
              <td class="empty-price">-</td>
            {% endif %}

            {% if s.sp98 is not none %}
              {% if s.sp98 == best_sp98 %}
                <td class="best-price">{{ s.sp98 }}</td>
              {% elif s.sp98 == worst_sp98 %}
                <td class="worst-price">{{ s.sp98 }}</td>
              {% else %}
                <td class="middle-price">{{ s.sp98 }}</td>
              {% endif %}
            {% else %}
              <td class="empty-price">-</td>
            {% endif %}

            {% if s.gazole is not none %}
              {% if s.gazole == best_gazole %}
                <td class="best-price">{{ s.gazole }}</td>
              {% elif s.gazole == worst_gazole %}
                <td class="worst-price">{{ s.gazole }}</td>
              {% else %}
                <td class="middle-price">{{ s.gazole }}</td>
              {% endif %}
            {% else %}
              <td class="empty-price">-</td>
            {% endif %}
          
            <td class="address">{{ city }}</td>

            <td class="address">{{ street }}</td>
          </tr>
        {% endfor %}
      </tbody>
    </table>
  </div>

Bonne idée @Kro, c'est en place ! :slightly_smiling_face:

La 1.0.5 de la carte vient de sortir (mise à jour via HACS) : le nom de la station devient un lien qui l'ouvre dans une app de cartes — et si la colonne nom n'est pas affichée, le lien se pose sur la ville, l'adresse ou l'enseigne.

Pour l'activer : éditeur graphique → section Affichage → « Ouvrir la station sur une carte », ou en YAML :

map_link: auto # ou google | apple | waze

  • google, apple (Plans) ou waze : le même service partout ;
  • auto : selon l'appareil — sélecteur d'applications sur Android (lien geo:, qui respecte l'app carto préférée, Waze comprise), Plans sur iPhone, Google Maps sur PC.

Petite déception au passage : il n'existe pas d'URL « magique » qui ouvre partout l'app choisie par l'utilisateur donc geo: n'existe que sur Android, et iOS n'ouvre que Plans ou le site du service visé. auto est la meilleure approximation possible.

La station s'ouvre pile sur sa position GPS et si l'intégration ne la fournit pas, la recherche se fait sur son adresse. Le reste ne change pas : un clic ailleurs sur la ligne ouvre toujours la fiche de l'entité.

Merci ! Ca marche bien chez moi. Google Map sur PC et Plan sur iPhone (en mode auto)

Toujours pour plus de cohérence, j'ai mis à jour le style de la carte afin que le nom de la station, lorsqu'il est cliquable, soit souligné en pointillés et devienne bleu au survol de la souris.

Petite amélioration visuelle, à la demande de la famille, pour pouvoir modifier dynamiquement le rayon de recherche des stations à proximité.

Recording-2026-08-19-193252

Prérequies: button-card et HTML Jinja2 Template card

Mise en place:

Un template sensor à ajouter dans le fichier templates.yaml
# Fusionne les résultats de plusieurs recherches par carburant afin d'obtenir
# une seule liste de stations avec tous les prix disponibles
- trigger:
    - platform: event
      event_type: nearby_gas_stations

  sensor:
    - name: Gas Stations Near Me
      unique_id: gas_stations_near_me
      state: "{{ now().isoformat() }}"
      attributes:
        stations: >
          {% set e10 = trigger.event.data.stations_e10 or [] %}
          {% set sp95 = trigger.event.data.stations_sp95 or [] %}
          {% set sp98 = trigger.event.data.stations_sp98 or [] %}
          {% set gazole = trigger.event.data.stations_gazole or [] %}
          {% set e85 = trigger.event.data.stations_e85 or [] %}
          {% set gpl = trigger.event.data.stations_gpl or [] %}
          {% set gas_stations = namespace(items=[]) %}

          {% for e in e10 %}
            {% set match_sp95 = sp95
              | selectattr('name', 'eq', e.name)
              | selectattr('address', 'eq', e.address)
              | list
            %}
            {% set match_sp98 = sp98
              | selectattr('name', 'eq', e.name)
              | selectattr('address', 'eq', e.address)
              | list
            %}
            {% set match_gazole = gazole
              | selectattr('name', 'eq', e.name)
              | selectattr('address', 'eq', e.address)
              | list
            %}
            {% set match_e85 = e85
              | selectattr('name', 'eq', e.name)
              | selectattr('address', 'eq', e.address)
              | list
            %}
            {% set match_gpl = gpl
              | selectattr('name', 'eq', e.name)
              | selectattr('address', 'eq', e.address)
              | list
            %}

            {% set gas_stations.items = gas_stations.items + [{
              'name': e.name,
              'address': e.address,
              'latitude': e.latitude,
              'longitude': e.longitude,
              'e10': e.price,
              'sp95': match_sp95[0].price if match_sp95 | count > 0 else none,
              'sp98': match_sp98[0].price if match_sp98 | count > 0 else none,
              'gazole': match_gazole[0].price if match_gazole | count > 0 else none,
              'e85': match_e85[0].price if match_e85 | count > 0 else none,
              'gpl': match_gpl[0].price if match_gpl | count > 0 else none
            }] %}
          {% endfor %}

          {% for s in sp95 %}
            {% set existing = gas_stations.items
              | selectattr('name', 'eq', s.name)
              | selectattr('address', 'eq', s.address)
              | list
            %}

            {% if existing | count == 0 %}
              {% set match_sp98 = sp98
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}
              {% set match_gazole = gazole
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}
              {% set match_e85 = e85
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}
              {% set match_gpl = gpl
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}

              {% set gas_stations.items = gas_stations.items + [{
                'name': s.name,
                'address': s.address,
                'latitude': s.latitude,
                'longitude': s.longitude,
                'e10': none,
                'sp95': s.price,
                'sp98': match_sp98[0].price if match_sp98 | count > 0 else none,
                'gazole': match_gazole[0].price if match_gazole | count > 0 else none,
                'e85': match_e85[0].price if match_e85 | count > 0 else none,
                'gpl': match_gpl[0].price if match_gpl | count > 0 else none
              }] %}
            {% endif %}
          {% endfor %}

          {% for s in sp98 %}
            {% set existing = gas_stations.items
              | selectattr('name', 'eq', s.name)
              | selectattr('address', 'eq', s.address)
              | list
            %}

            {% if existing | count == 0 %}
              {% set match_gazole = gazole
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}
              {% set match_e85 = e85
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}
              {% set match_gpl = gpl
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}

              {% set gas_stations.items = gas_stations.items + [{
                'name': s.name,
                'address': s.address,
                'latitude': s.latitude,
                'longitude': s.longitude,
                'e10': none,
                'sp95': none,
                'sp98': s.price,
                'gazole': match_gazole[0].price if match_gazole | count > 0 else none,
                'e85': match_e85[0].price if match_e85 | count > 0 else none,
                'gpl': match_gpl[0].price if match_gpl | count > 0 else none
              }] %}
            {% endif %}
          {% endfor %}

          {% for s in gazole %}
            {% set existing = gas_stations.items
              | selectattr('name', 'eq', s.name)
              | selectattr('address', 'eq', s.address)
              | list
            %}

            {% if existing | count == 0 %}
              {% set match_e85 = e85
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}
              {% set match_gpl = gpl
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}

              {% set gas_stations.items = gas_stations.items + [{
                'name': s.name,
                'address': s.address,
                'latitude': s.latitude,
                'longitude': s.longitude,
                'e10': none,
                'sp95': none,
                'sp98': none,
                'gazole': s.price,
                'e85': match_e85[0].price if match_e85 | count > 0 else none,
                'gpl': match_gpl[0].price if match_gpl | count > 0 else none
              }] %}
            {% endif %}
          {% endfor %}

          {% for s in e85 %}
            {% set existing = gas_stations.items
              | selectattr('name', 'eq', s.name)
              | selectattr('address', 'eq', s.address)
              | list
            %}

            {% if existing | count == 0 %}
              {% set match_gpl = gpl
                | selectattr('name', 'eq', s.name)
                | selectattr('address', 'eq', s.address)
                | list
              %}

              {% set gas_stations.items = gas_stations.items + [{
                'name': s.name,
                'address': s.address,
                'latitude': s.latitude,
                'longitude': s.longitude,
                'e10': none,
                'sp95': none,
                'sp98': none,
                'gazole': none,
                'e85': s.price,
                'gpl': match_gpl[0].price if match_gpl | count > 0 else none
              }] %}
            {% endif %}
          {% endfor %}

          {% for s in gpl %}
            {% set existing = gas_stations.items
              | selectattr('name', 'eq', s.name)
              | selectattr('address', 'eq', s.address)
              | list
            %}

            {% if existing | count == 0 %}
              {% set gas_stations.items = gas_stations.items + [{
                'name': s.name,
                'address': s.address,
                'latitude': s.latitude,
                'longitude': s.longitude,
                'e10': none,
                'sp95': none,
                'sp98': none,
                'gazole': none,
                'e85': none,
                'gpl': s.price
              }] %}
            {% endif %}
          {% endfor %}

          {{ gas_stations.items }}
Le script éxécuté par le bouton de recherche
alias: Find Gas Stations Near Me
sequence:
  - action: prix_carburant.find_nearest_stations
    data:
      distance: '{{ distance }}'
      entity_id: '{{ tracker }}'
      fuel: E10
    response_variable: stations_e10
  - action: prix_carburant.find_nearest_stations
    data:
      distance: '{{ distance }}'
      entity_id: '{{ tracker }}'
      fuel: SP95
    response_variable: stations_sp95
  - action: prix_carburant.find_nearest_stations
    data:
      distance: '{{ distance }}'
      entity_id: '{{ tracker }}'
      fuel: SP98
    response_variable: stations_sp98
  - action: prix_carburant.find_nearest_stations
    data:
      distance: '{{ distance }}'
      entity_id: '{{ tracker }}'
      fuel: Gazole
    response_variable: stations_gazole
  - event: nearby_gas_stations
    event_data:
      stations_e10: '{{ stations_e10.stations }}'
      stations_sp95: '{{ stations_sp95.stations }}'
      stations_sp98: '{{ stations_sp98.stations }}'
      stations_gazole: '{{ stations_gazole.stations }}'
fields:
  tracker:
    description: Person entity or device_tracker

Le script éxécuté par le bouton de reinitialisation
sequence:
  - event: nearby_gas_stations
    event_data:
      stations: []
alias: Reset Gas Stations Near Me
description: ''
Un helper Input Number: input_number.gas_station_radius

Le code de la carte principale

Ici il faut remplacer les tracker par les vôtres "Pierre": "person.pierre"
A gauche, indiquez le nom exact de l'utilisateur Home Assistant.
A droite, indiquez l'entité person (ou le tracker) associée à cet utilisateur.

type: vertical-stack
cards:
  - type: heading
    icon: mdi:gas-station
    heading: Prix des carburants
    heading_style: title
  - type: custom:button-card
    entity: input_number.gas_station_radius
    name: Stations à proximité
    icon: mdi:target
    show_icon: true
    show_name: true
    show_state: true
    show_label: false
    state_display: |
      [[[
        const radius = parseInt(states['input_number.gas_station_radius']?.state ?? 5);
        return `Rayon actuel : ${radius} km`;
      ]]]
    tap_action:
      action: perform-action
      perform_action: script.find_gas_stations_near_me
      data:
        tracker: |
          [[[
            const map = {
              "Pierre": "person.pierre",
              "Paul": "person.paul",
              "Jack": "person.jack"
            };
            return map[hass.user.name];
          ]]]
        distance: |
          [[[
            return parseInt(states['input_number.gas_station_radius'].state);
          ]]]
    custom_fields:
      controls:
        card:
          type: horizontal-stack
          cards:
            - type: custom:button-card
              icon: mdi:minus
              tap_action:
                action: perform-action
                perform_action: input_number.decrement
                target:
                  entity_id: input_number.gas_station_radius
              styles:
                card:
                  - width: 40px
                  - height: 40px
                  - border-radius: 50%
                  - background: '#FFFFFF29'
                  - box-shadow: none
                  - padding: 0
                icon:
                  - color: white
                  - width: 18px
            - type: custom:button-card
              icon: mdi:plus
              tap_action:
                action: perform-action
                perform_action: input_number.increment
                target:
                  entity_id: input_number.gas_station_radius
              styles:
                card:
                  - width: 40px
                  - height: 40px
                  - border-radius: 50%
                  - background: '#FFFFFF29'
                  - box-shadow: none
                  - padding: 0
                icon:
                  - color: white
                  - width: 18px
    styles:
      card:
        - border-radius: 18px
        - padding: 10px
      grid:
        - grid-template-areas: |
            "i n controls"
            "i s ."
        - grid-template-columns: 56px 1fr 100px
        - column-gap: 15px
        - align-items: center
      img_cell:
        - width: 44px
        - height: 44px
        - border-radius: 50%
        - background: '#FFFFFF29'
        - display: flex
        - align-items: center
        - justify-content: center
        - justify-self: start
      icon:
        - color: white
        - width: 25px
        - height: 25px
      name:
        - color: white
        - font-size: 15px
        - font-weight: 700
        - line-height: 1.2
        - white-space: nowrap
        - overflow: hidden
        - text-overflow: ellipsis
        - justify-self: start
        - margin-left: -18px
        - margin-top: -20px
      state:
        - color: '#FFFFFFC7'
        - font-size: 12px
        - justify-self: start
        - margin-left: -18px
        - margin-top: -18px
      custom_fields:
        controls:
          - grid-area: controls
          - display: flex
          - justify-content: flex-end
  - title: Fuel prices
    show_title: false
    stations: []
    columns:
      - logo
      - distance
      - E10
      - SP95
      - SP98
      - Gazole
      - city
      - address
      - updated
    decimals: 3
    highlight: true
    unit: €/L
    sort: E10
    sort_desc: false
    sortable: true
    more_info: true
    map_link: auto
    background: null
    color_min: '#4caa40'
    color_max: '#e05252'
    type: custom:prix-carburant-card
visibility:
  - condition: state
    attribute: stations
    entity: sensor.gas_stations_near_me
    state: ''
Le code de la carte du resultat de la recherche
type: vertical-stack
cards:
  - type: heading
    icon: mdi:gas-station
    heading: Prix des carburants
    heading_style: title
  - type: custom:button-card
    show_icon: true
    show_name: true
    show_state: false
    show_label: true
    color_type: icon
    layout: icon_name
    name: Réinitialiser
    icon: mdi:gas-station-off
    tap_action:
      action: perform-action
      perform_action: script.reset_gas_stations_near_me
    styles:
      card:
        - border-radius: 18px
        - padding: 5px
      grid:
        - grid-template-areas: '"i n"'
        - grid-template-columns: 56px auto
        - grid-template-rows: auto auto
        - column-gap: 0px
        - row-gap: 0px
        - align-items: center
      img_cell:
        - width: 40px
        - height: 40px
        - border-radius: 50%
        - display: flex
        - align-items: center
        - justify-content: center
        - justify-self: start
        - align-self: center
      icon:
        - width: 24px
        - height: 24px
      name:
        - font-size: 14px
        - font-weight: 700
        - line-height: 1.2
        - justify-self: start
        - white-space: nowrap
        - overflow: hidden
        - text-overflow: ellipsis
        - margin-left: -5px
      label:
        - font-size: 10px
        - opacity: 0.7
        - justify-self: start
        - align-self: start
        - white-space: nowrap
        - overflow: hidden
        - text-overflow: ellipsis
        - margin-left: -5px
  - type: custom:html-template-card
    ignore_line_breaks: true
    entities:
      - sensor.gas_stations_near_me
    content: >-
      {% set stations = state_attr('sensor.gas_stations_near_me', 'stations') or
      [] %}

      {% set e10_prices = stations | map(attribute='e10') | reject('none') |
      list %}

      {% set sp95_prices = stations | map(attribute='sp95') | reject('none') |
      list %}

      {% set sp98_prices = stations | map(attribute='sp98') | reject('none') |
      list %}

      {% set gazole_prices = stations | map(attribute='gazole') | reject('none')
      | list %}


      {% set best_e10 = e10_prices | min if e10_prices else none %}

      {% set worst_e10 = e10_prices | max if e10_prices else none %}

      {% set best_sp95 = sp95_prices | min if sp95_prices else none %}

      {% set worst_sp95 = sp95_prices | max if sp95_prices else none %}

      {% set best_sp98 = sp98_prices | min if sp98_prices else none %}

      {% set worst_sp98 = sp98_prices | max if sp98_prices else none %}

      {% set best_gazole = gazole_prices | min if gazole_prices else none %}

      {% set worst_gazole = gazole_prices | max if gazole_prices else none %}

      <style>
        .center {
          text-align: center;
        }

        .gas-wrapper {
          overflow-x: auto;
          max-width: 100%;
          border-radius: 12px;
        }

        .gas-table {
          min-width: 850px;
          width: 100%;
          border-collapse: collapse;
          font-size: 14px;
          color: var(--primary-text-color);
        }

        .gas-table th {
          background: var(--card-background-color);
          color: var(--secondary-text-color);
          padding: 4px 6px;
          text-align: left;
          white-space: nowrap;
          font-weight: 600;
          border-bottom: 1px solid var(--divider-color);
        }

        .gas-table td {
          padding: 4px 6px;
          border-bottom: 1px solid var(--divider-color);
          white-space: nowrap;
          vertical-align: middle;
          color: var(--primary-text-color);
        }

        .gas-table tr:nth-child(even) {
          background: color-mix(
            in srgb,
            var(--card-background-color) 92%,
            var(--primary-text-color) 8%
          );
        }

        .gas-table tr:nth-child(odd) {
          background: var(--card-background-color);
        }

        .gas-table td:first-child {
          min-width: 170px;
          max-width: 170px;
          overflow: hidden;
          text-overflow: ellipsis;
        }

        .gas-table th:first-child {
          background: var(--card-background-color);
        }

        .gas-table tbody tr:hover,
        .gas-table tbody tr:hover td,
        .gas-table tbody tr:hover td:first-child {
          background: var(--secondary-background-color);
        }

        .gas-title {
          margin: 0 0 10px 0;
          font-weight: 700;
          font-size: 14px;
          color: var(--primary-text-color);
        }

        .station-link,
        .station-link:visited {
          color: var(--primary-text-color);
          text-decoration: underline;
          text-decoration-style: dotted;
          text-underline-offset: 2px;
          font-size: 14px;
          display: inline-block;
          max-width: 150px;
          overflow: hidden;
          text-overflow: ellipsis;
          vertical-align: middle;
        }

        .station-link:hover,
        .station-link:active {
          color: var(--primary-color);
        }

        .price {
          text-align: right;
          font-size: 14px;
        }

        .best-price {
          color: var(--success-color, #4caf50) !important;
          font-size: 14px;
          font-weight: 700;
          text-align: center;
        }

        .middle-price {
          color: var(--primary-text-color);
          font-size: 14px;
          text-align: center;
        }

        .worst-price {
          color: var(--error-color, #f44336) !important;
          font-size: 14px;
          font-weight: 700;
          text-align: center;
        }

        .empty-price {
          color: var(--secondary-text-color);
          text-align: center;
        }

        .address {
          color: var(--primary-text-color);
          font-size: 14px;
        }
      </style>

      <div class="gas-title">
        ⛽ {{ stations | count }} stations trouvées dans un rayon de {{ states['input_number.gas_station_radius'].state | int }} km
      </div>

      <div class="gas-wrapper">
        <table class="gas-table">
          <thead>
            <tr>
              <th>Station</th>
              <th>E10 €/L</th>
              <th>SP95 €/L</th>
              <th>SP98 €/L</th>
              <th>Diesel €/L</th>
              <th>City</th>
              <th>Address</th>
            </tr>
          </thead>

          <tbody>
            {% for s in stations %}
              {% set address_parts = s.address.split(',') %}
              {% set street = address_parts[0].strip() %}
              {% set city_raw = address_parts[-1].strip() %}
              {% set city = city_raw | regex_replace('^[0-9]{5}\\s*', '') %}
              {% set maps_url = 'https://www.google.com/maps/search/?api=1&query=' ~ s.latitude ~ ',' ~ s.longitude %}

              <tr>
                <td title="{{ s.name }}">              
                    <a class="station-link" target="_blank" href="{{ maps_url }}">{{ s.name }}</a>
                  </a>
                </td>

                {% if s.e10 is not none %}
                  {% if s.e10 == best_e10 %}
                    <td class="best-price">{{ s.e10 }}</td>
                  {% elif s.e10 == worst_e10 %}
                    <td class="worst-price">{{ s.e10 }}</td>
                  {% else %}
                    <td class="middle-price">{{ s.e10 }}</td>
                  {% endif %}
                {% else %}
                  <td class="empty-price">-</td>
                {% endif %}

                {% if s.sp95 is not none %}
                  {% if s.sp95 == best_sp95 %}
                    <td class="best-price">{{ s.sp95 }}</td>
                  {% elif s.sp95 == worst_sp95 %}
                    <td class="worst-price">{{ s.sp95 }}</td>
                  {% else %}
                    <td class="middle-price">{{ s.sp95 }}</td>
                  {% endif %}
                {% else %}
                  <td class="empty-price">-</td>
                {% endif %}

                {% if s.sp98 is not none %}
                  {% if s.sp98 == best_sp98 %}
                    <td class="best-price">{{ s.sp98 }}</td>
                  {% elif s.sp98 == worst_sp98 %}
                    <td class="worst-price">{{ s.sp98 }}</td>
                  {% else %}
                    <td class="middle-price">{{ s.sp98 }}</td>
                  {% endif %}
                {% else %}
                  <td class="empty-price">-</td>
                {% endif %}

                {% if s.gazole is not none %}
                  {% if s.gazole == best_gazole %}
                    <td class="best-price">{{ s.gazole }}</td>
                  {% elif s.gazole == worst_gazole %}
                    <td class="worst-price">{{ s.gazole }}</td>
                  {% else %}
                    <td class="middle-price">{{ s.gazole }}</td>
                  {% endif %}
                {% else %}
                  <td class="empty-price">-</td>
                {% endif %}
              
                <td class="address">{{ city }}</td>

                <td class="address">{{ street }}</td>
              </tr>
            {% endfor %}
          </tbody>
        </table>
      </div>
visibility:
  - condition: state
    attribute: stations
    entity: sensor.gas_stations_near_me
    state_not: ''

EDIT: Information obselete, la fonciton de recherche est inclu depuis la version 1.0.6 de la carte carburant

Merci pour la carte, je gérer via un truc fait maison avec du custom etc...
Je viens de remplacer par ta carte c'est top !!!
Merci encore.

Bonjour !
Bravo à tous les 2 pour le boulot !

Je suis vraiment intéressé par ce qu'à fait Kro : "Station à proximité".
Est-ce prévu un article de A à Z pour obtenir le résultat de l'image ?

ça serait tellement bien et je pense que la communauté serait très intéressé surtout en ce moment ^^"

Bonjour @jojo007,

Effectivement, cela nécessite un peu de bricolage.

Pour resumer, il faut installer via HACS:

Ensuite, suis pas à pas la partie mise en place de mon post et tu auras exactement le même rendu.

EDIT: Information obselete, la fonciton de recherche est inclu depuis la version 1.0.6 de la carte carburant

OK merci beaucoup, je vais tester ça asap :wink:

Si tu peux patienter jusqu’à la semaine prochaine, je proposerai une PR à @Pulpy-Luke qui intégre la fonctionnalité de recherche. Plus aucune dependance à button-card et HTML-Jinja2 et plus rien a creer a la main.

Teaser:

Une petite semaine :wink:
Je regarde plus en détail (j'ai vu 2 ou 3 trucs) et je publie, merci

Nouvelle version de la carte 1.0.6 : elle peut maintenant chercher les stations les moins chères autour de toi, directement depuis le tableau de bord.

:pray: Un grand merci à @Kro ! Il avait monté ça de son côté (sensor template, script et carte maison, voir son message plus haut), puis a proposé de l'intégrer directement à la carte : c'est sa contribution qui est à l'origine de cette version. Et merci encore pour l'idée du lien vers Google Maps / Plans / Waze de la 1.0.5.

Ce que ça donne

  • une barre au-dessus du tableau : − / + pour le rayon (1 à 30 km), :mag: pour lancer la recherche ;
  • des pastilles pour choisir les carburants, parmi ceux que remonte l'intégration. Le choix est mémorisé par compte Home Assistant : le même sur le téléphone et sur l'ordi ;
  • la recherche part de ta position (la personne liée à ton compte), sinon du domicile : ça marche aussi sur une tablette murale ;
  • aucune requête sans clic sur :mag:, donc l'API des prix n'est pas sollicitée pour rien.

Pour l'activer

Dans l'éditeur, nouvelle section « Recherche à proximité », ou en YAML :

Configuration YAML
type: custom:prix-carburant-card
search: true

Elle est désactivée par défaut : rien ne change pour les cartes existantes.

Il faut une version de l'intégration Prix Carburant qui propose le service prix_carburant.find_nearest_stations.

Mise à jour via HACS ; le détail est dans les notes de version et le README :

Bonjour, je viens d'essayer et j'ai cette erreur :

type: custom:prix-carburant-card
search: true

La carte est en 1.06 et l'intégration prix carburant est la 3.26.4, cache vidé (ctrl + f5).

J'ai du rater quelque chose.

Salut,

Ton erreur est vraiment bizarre... j'imagine que ton ha est à jour, non ?
Ta carte est dans une pile horizontale/verticale ou une popup ? Si oui, tu peux essayer la mettre juste en direct dans un dashboard ?

Je viens d'installer prix carburant et la carte... c'est vraiment cool mais le plus pertinent en ce moment c'est savoir si la station essence est en rupture

J'ai l'impression que la valeur du prix passe en statut "inconnu" lorsque la station est en rupture, savez vous si c'est le cas ?

Inconnu c'est ce que traduit HA quand le capteur n'a pas de valeur. Et la rupture ne provoque sans doute pas ça, tu dois pouvoir continuer à voir le prix à dernière mise à jour.
A priori un carburant en rupture contient un attribut "shortage_since" avec une date si c'est le cas

ca serait bien que ca soit affiché sur la carte alors

Par contre le jour où on sera à 9.999 le litre ca marchera plus, y a quelques mois j'aurais dit aucune chance que ca arrive mais maintenant..