[TUTO] - Design - Zendure & Bubble-Card

Hi,

Vous avez des panneaux photovoltaïques et des batteries et vous voulez un design un peu plus plaisant que ce qui existe de base sur HA ?

Je vous présente une petite adaptation de la fabuleuse Bubble-Card (merci @Clooos) dans un système de production/stockage d’énergie; Ici avec le système Zendure à adapter à votre système et à vos sensors…

Cettet adaptation de Bubble-card a été reprise ici et par @Julien_Galliot qui a bien aimé ce format semble-t-il :wink: et qui présente très bien l’intégration de Zendure dans HA via ioBroker ou via l’add-on développé par @FireSon que je teste également.

Pour ceux qui sont intéressés, vous devriez pouvoir facilement reproduire ce genre de dashboard avec la Bubble-Card, Card-mod et les quelques ajustements ci-dessous.
Pour les différentes possibilités de la Bubble Card, voir ici : [ CARTE ] Bubble Card - Des pop-up et une collection de cartes minimalistes

Le code est sûrement perfectible, n’hésitez pas :wink:


:battery: Zendure Flow — flèche rouge/verte dynamique selon le sens de la batterie

Pretez attention au clip-path qui dessine la flèche selon le flux de la batterie — vous pouvez créer votre propre flèche ou autre forme, voir ici

🧩 Template sensor flow (calcul du flux)
- sensor:
    - name: "Zen Flow batt (W)"
      unique_id: "zen_flow"
      unit_of_measurement: "W"
      state_class: measurement
      state: >
        {% set input = states('sensor.io_zen_recoit_w') | float(0) %}
        {% set output = states('sensor.io_zen_envoie_w') | float(0) %}
        {{ input - output }}
      availability: >
        {{ states('sensor.io_zen_recoit_w') not in ['unavailable', 'unknown'] and
           states('sensor.io_zen_envoie_w') not in ['unavailable', 'unknown'] }}
      device_class: power
🎨 Bubble Card Flow Batterie (trick css)
type: custom:bubble-card
card_type: button
button_type: state
entity: sensor.zen_flow
styles: |-
  :host {
    --color-negative: #ad2b2b;
    --color-positive: #5F730B;
    --color-neutral: #4D4D4D;
    --color-zero-line: #FFFFFF;
  }
  .bubble-button-card-container {
    position: relative;
    border-radius: 8px;
    height: 20px;
    overflow: hidden;
  }
  .bubble-button-card-container::before {
    content: '';
    position: absolute;
    top: 0;
    height: 100%;
    width: 2px;
    left: 50%;
    background: var(--color-zero-line);
    z-index: 2;
    transform: translateX(-50%);
  }
  .bubble-button-card-container::after {
    content: '';
    position: absolute;
    top: 0;
    height: 100%;
    display: ${hass.states['sensor.zen_flow'].state == -10 ? 'none' : 'block'};
    width: max(calc(${Math.abs(hass.states['sensor.zen_flow'].state) / 1500 * 50}%),
               ${(""+Math.round(hass.states['sensor.zen_flow'].state)).length * 10 + 20}px);
    left: ${hass.states['sensor.zen_flow'].state < 0 
      ? 'calc(50% - max(calc(' + (Math.abs(hass.states['sensor.zen_flow'].state) / 1500 * 50) + '%), ' + ((""+Math.round(hass.states['sensor.zen_flow'].state)).length * 10 + 20) + 'px))'
      : '50%'};
    background: ${hass.states['sensor.zen_flow'].state < 0 ? 'var(--color-negative)' : 'var(--color-positive)'};
    z-index: 1;
    clip-path: ${hass.states['sensor.zen_flow'].state < 0
      ? 'polygon(40% 0%, 40% 20%, 100% 20%, 100% 80%, 40% 80%, 40% 100%, 0% 50%)'
      : 'polygon(40% 0%, 40% 20%, 0 20%, 0 80%, 40% 80%, 40% 100%, 100% 50%)'};
  }
  ha-card::after {
    position: absolute;
    top: 50%;
    left: ${hass.states['sensor.zen_flow'].state < 0
      ? 'calc(50% - max(calc(' + (Math.abs(hass.states['sensor.zen_flow'].state) / 1500 * 50) + '%), ' + ((""+Math.round(hass.states['sensor.zen_flow'].state)).length * 10 + 20) + 'px) / 2)'
      : 'calc(50% + max(calc(' + (Math.abs(hass.states['sensor.zen_flow'].state) / 1500 * 50) + '%), ' + ((""+Math.round(hass.states['sensor.zen_flow'].state)).length * 10 + 20) + 'px) / 2)'};
    transform: translate(-50%, -50%);
    padding: 2px 10px;
    border-radius: 25px;
    border: 0;
    color: #fff;
    content: "${Math.round(state)} W";
    background: none;
    text-align: center;
    display: block;
    z-index: 3;
  }
card_mod:
  style: |
    ha-icon {
      color: {% if states('sensor.zen_flow') | float(0) < 0 %} var(--color-negative) 
             {% else %} var(--color-positive) {% endif %} !important;
    }
icon: ""
name: Flux-batterie
sub_button: []
card_layout: large
slider_live_update: true
show_state: false
show_attribute: false
show_last_changed: false
force_icon: false
grid_options:
  columns: 12
  rows: 1
scrolling_effect: false
show_icon: true
show_name: false
double_tap_action:
  action: more-info
hold_action:
  action: more-info
tap_action:
  action: navigate
  navigation_path: "#shelly"

:sunny: Production PV (avec météo animée via les icônes de lovelace-meteofrance-weather-card)

🌤️ Bubble Card PV avec icône météo animée
type: custom:bubble-card
card_type: button
button_type: state
animated_icons: true
entity: sensor.zendure_pv_total
styles: |-
  :host {
      --color-ok: #bfac1a;
      --color-background: var(--bubble-button-main-background-color, var(--bubble-main-background-color, var(--background-color-2, var(--secondary-background-color))));
      --color-notification-bubble: #bfac1a;
      --color-bar: var(--color-ok);
      --progress: ${Math.round((Number(state) < 2200 ? Number(state) : 2200) / 2200 * 100)}%;
  }
  ha-icon {
    background-image: url("/local/community/lovelace-meteofrance-weather-card/icons/${
      hass.states['sensor.meteo_ponderee'].state === 'clear-day' ? 'day.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'clear-night' ? 'night.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'partlycloudy' ? 'cloudy-day-3.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'partly-cloudy-day' ? 'cloudy-day-3.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'partly-cloudy-night' ? 'cloudy-night-3.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'cloudy' ? 'cloudy.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'fog' ? 'fog.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'windy' ? 'windy.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'windy-variant' ? 'windy-night.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'hail' ? 'snowy-rainy.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'lightning' ? 'thunder.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'lightning-rainy' ? 'lightning-rainy.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'rainy' ? 'rainy-5.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'rainy-5' ? 'rainy-5.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'rainy-6' ? 'rainy-6.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'rainy-7' ? 'rainy-7.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'snowy' ? 'snowy-6.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'snowy-rainy' ? 'snowy-rainy.svg' :
      hass.states['sensor.meteo_ponderee'].state === 'exceptional' ? 'lightning-rainy.svg' : 
      'day.svg'
    }");
    background-size: calc(130%);
    background-repeat: no-repeat;
    background-position: center;
    mask-image: none;
    -webkit-mask-image: none;
    width: 40px;
    height: 40px;
    opacity: 0.6!important;
  }
  .bubble-button-card-container {
    background: var(--color-background) !important;
    position: relative;
  }
  .bubble-button-card-container::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    width: var(--progress) !important;
    background: var(--color-bar) !important;
    z-index: 0;
  }
@keyframes rotatePulse {
    0% { transform: rotate(0deg) scale(1); }
    50% { transform: rotate(180deg) scale(1.1); }
    100% { transform: rotate(360deg) scale(1); }
}
name: Prod PV
icon: url("/local/community/lovelace-meteofrance-weather-card/icons/day.svg")
sub_button: []
layout_options:
  grid_columns: 2
  grid_rows: 1
card_layout: large

:date: Affichage des jours Tempo RTE + prix EDF

📘 Stack Bubble RTE (bleu/blanc/rouge + prix)
type: horizontal-stack
cards:
  - type: custom:bubble-card
    show_state: false
    show_attribute: false
    show_last_changed: false
    force_icon: false
    sub_button: []
    show_icon: false
    scrolling_effect: false
    card_layout: large
    icon: mdi:calendar-today
    name: Aujourd'hui
    button_type: state
    styles: |-
      .bubble-button-card-container {
        #background: ${state === 'rouge' ? 'red' : ''} 'red' !important;
        #background-color: rgb(71,19,37);
        background: ${hass.states['sensor.rte_tempo_couleur_actuelle'].state === 'Rouge' 
          ? 'rgb(142, 35, 35)' 
          : hass.states['sensor.rte_tempo_couleur_actuelle'].state === 'Blanc' 
          ? 'rgb(201, 191, 191)' 
          : hass.states['sensor.rte_tempo_couleur_actuelle'].state === 'Bleu' 
          ? 'rgb(15, 117, 137)' 
          : ''} !important;
        border-radius: 32px 0 0 32px !important;
      }
    attribute: friendly_name
    show_name: true
    card_type: button
    entity: sensor.rte_tempo_couleur_actuelle_visuel
  - type: custom:bubble-card
    show_state: false
    show_attribute: false
    show_last_changed: false
    force_icon: false
    sub_button:
      - entity: sensor.rte_tempo_prochaine_couleur_format
        show_icon: false
        show_state: true
        state_background: false
        show_background: false
      - entity: sensor.edf_tempo_prix_actuel
        show_icon: false
        state_background: false
        show_background: false
        show_state: true
    show_icon: false
    scrolling_effect: false
    card_layout: large-2-rows
    icon: mdi:calendar-today
    name: " "
    button_type: state
    styles: |-
      .bubble-button-card-container {
          border-radius: 0 0 0 0 !important;
      }
      .name-without-icon {
      margin-left: 1px !important;
      }
    attribute: friendly_name
    show_name: false
    card_type: button
    entity: sensor.rte_tempo_prochaine_couleur_format
  - type: custom:bubble-card
    show_state: true
    show_attribute: false
    show_last_changed: false
    force_icon: false
    sub_button: []
    show_icon: false
    scrolling_effect: false
    card_layout: large
    icon: mdi:calendar-today
    name: " Restants"
    button_type: state
    styles: |-
      .bubble-button-card-container {
          border-radius: 0 0 0 0 !important;
      }
    attribute: friendly_name
    show_name: true
    columns: 2
    rows: 2
    card_type: button
    entity: sensor.rte_tempo_cycle_jours_restants_rouge
  - type: custom:bubble-card
    show_state: false
    show_attribute: false
    show_last_changed: false
    force_icon: false
    sub_button: []
    show_icon: false
    scrolling_effect: false
    card_layout: large
    icon: mdi:calendar-today
    name: demain
    button_type: state
    styles: |-
      .bubble-button-card-container {
          #background: rgb(206, 30, 30) !important;
          background: ${hass.states['sensor.rte_tempo_prochaine_couleur'].state === 'Rouge' 
          ? 'rgb(142, 35, 35)' 
          : hass.states['sensor.rte_tempo_prochaine_couleur'].state === 'Blanc' 
          ? 'rgb(201, 191, 191)' 
          : hass.states['sensor.rte_tempo_prochaine_couleur'].state === 'Bleu' 
          ? 'rgb(15, 117, 137)' 
          : ''} !important;
          border-radius: 0 32px 32px 0 !important;
      }
    attribute: friendly_name
    show_name: true
    columns: 2
    rows: 2
    card_type: button
    entity: sensor.rte_tempo_prochaine_couleur_visuel

:house: Affichage en dégradé (gradient) pour pièces ou zones

🏡 Carte à fond rayé pour zones personnalisées (petit trick css)
type: horizontal-stack
cards:
  - type: custom:bubble-card
    entity: sensor.shelly_3em_c_power
    card_mod:
      style: >
        .bubble-button-background {
          background: linear-gradient(45deg, #72500b 1%, #fff 1%, #fff 49%,
                                      #72500b 49%, #72500b 51%, #fff 51%, #fff 99%, #72500b 99%);
          background-size: 10px 10px;
          background-position: 0 0;
        }
  - type: custom:bubble-card
    entity: sensor.conso_maison_instant_w
    card_mod:
      style: >
        .bubble-button-background {
          background: linear-gradient(45deg, #72500b 1%, #fff 1%, #fff 49%,
                                      #72500b 49%, #72500b 51%, #fff 51%, #fff 99%, #72500b 99%);
          background-size: 10px 10px;
          background-position: 0 0;
        }

2 « J'aime »

Salut @yannweb
Superbes tes cards !

Je n’ai pas de production solaire, ni de VE, mais certaines cartes m’intéresse ^^
Ce sont celles des énergies ^^
Pourrais-tu mettre le code ce quelques unes d’entres elles ?

Et enfin, au sujet des cartes hachurées, je n’ai rien qui s’affiche en collant ton code et en mettant des entités d’énergie…
C’est normal ?

1 « J'aime »

C’est déjà installé :sweat_smile:

1 « J'aime »

Bonjour tu aurais le template pour :
sensor.rte_tempo_prochaine_couleur_format
Stp
Merci d’avance

@yannweb
Tu voudrais bien partager, s’il te plait, le code de quelques-unes de tes cartes de puissances ? :innocent:

Voici, il formate par ex ceci 2025-04-04T04:00:00+00:00 en 04 à 6h (Jour + heure).

    - name: "RTE Tempo Prochaine Couleur Format"
      state: >
        {% set timestamp = as_timestamp(states('sensor.rte_tempo_prochaine_couleur_changement')) %}
        {% if timestamp %}
          {{ timestamp | timestamp_custom('%d à %Hh', true) }}
        {% else %}
          "Indisponible"
        {% endif %}
      availability: "{{ states('sensor.rte_tempo_prochaine_couleur_changement') not in ['unknown', 'unavailable'] }}"
1 « J'aime »

C’est du Bubble Card standard :wink:

type: horizontal-stack
cards:
  - type: custom:bubble-card
    card_type: button
    button_type: slider
    entity: number.io_zen_discharge_limit
    name: Limite-décharge
    show_state: true
    tap_action:
      action: more-info
    double_tap_action:
      action: toggle
    hold_action:
      action: toggle
  - type: custom:bubble-card
    card_type: button
    button_type: slider
    entity: number.io_zen_charge_limit
    name: Limite-charge
    show_state: true
    tap_action:
      action: more-info
    double_tap_action:
      action: toggle
    hold_action:
      action: toggle
1 « J'aime »

Salut :wink:

J’ai oublié de mentionner qu’il fallait installer card-mod (en plus de bubble-card)
Je corrige le sujet…

Merci :wink:
Cependant je voulais plutôt parler ce celles-ci :


:sweat_smile:

type: horizontal-stack
cards:
  - type: custom:bubble-card
    card_type: button
    show_state: false
    show_attribute: false
    show_last_changed: false
    force_icon: true
    sub_button:
      - entity: sensor.wall_plug_1_power
        show_name: false
        show_icon: false
        state_background: false
        show_background: true
        show_state: true
    show_icon: true
    scrolling_effect: true
    card_layout: large
    icon: mdi:lightbulb-group
    name: Guirlande
    button_type: switch
    entity: switch.wall_plug_1
    button_action:
      tap_action:
        action: none
      double_tap_action:
        action: more-info
      hold_action:
        action: toggle
  - type: custom:bubble-card
    card_type: button
    show_state: false
    show_attribute: false
    show_last_changed: false
    force_icon: true
    sub_button:
      - entity: sensor.jardin_nous_az1_power
        show_name: false
        show_icon: false
        state_background: false
        show_background: true
        show_state: true
        tap_action:
          action: toggle
        double_tap_action:
          action: more-info
    show_icon: true
    scrolling_effect: false
    card_layout: large
    icon: mdi:lightbulb-group
    name: Jardin
    button_type: switch
    entity: switch.jardin_nous_az1
    tap_action:
      action: toggle
    hold_action:
      action: more-info
    button_action:
      tap_action:
        action: none
      double_tap_action:
        action: more-info
      hold_action:
        action: toggle

1 « J'aime »

Merci beaucoup super boulot

1 « J'aime »

Bonjour,

@yannweb , pourrais-tu expliquer la partie suivante ?

    card_mod:
      style: >
        .bubble-button-background {
          background: linear-gradient(45deg, #72500b 1%, #fff 1%, #fff 49%,
                                      #72500b 49%, #72500b 51%, #fff 51%, #fff 99%, #72500b 99%);
          background-size: 10px 10px;
          background-position: 0 0;
        }

J’aimerais bien changer la couleur car chez moi c’est illisible en tant que tel XD

En te remerciant par avance :wink:

Votre couleur de police est plus claire que celle de mon theme, c’est effectivement illisible.
Peut-être qu’en rajoutant sous:
background-size: 10px 10px;
background-position: 0 0;
un petit :
color: #333; (ou autre)

Ou en changeant/modifiant le thème.

Sympa la carte avec la météo… mais pas moyen de mettre en place :sweat_smile: