Faire des jauges de ce type

un changement merci mais il y a encore autre chose à priori

il y a une erreur dans la position du curseur dans le dernier paragraphe, remplace le par ça :

    cursor:
      - position: absolute
      - top: "[[[ return 'calc(55% - 12.5px)'; ]]]"
      - left: |
          [[[ 
            const value = Number(entity.state);
            const min = -25;
            const max = 60;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const t = clamp(value, min, max);
            const barWidth = window.innerWidth <= 600 ? 220 : 250;
            const halfCursor = 8;
            const barRightPercent = window.innerWidth <= 600 ? 4 : 7;
            const percent = ((t - min) / (max - min)) * 100;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(100% - ${barRightPercent}% - ${barWidth}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]

ton curseur sera a la bonne position, a toi de gérer la couleur, car c’est pas top top, ni même le frais a 0° :), je pense qu’il fait froid a 0° :slight_smile:

1 « J'aime »

Et à 60° C je change de planète…

3 « J'aime »

Oui il faut revoir les commentaires, je voulais déjà régler la position etc. Et après je vais utiliser un number test pour adapter les zones couleurs etc

Merci en effet on est déjà mieux :

image

type: custom:button-card
entity: sensor.netatmo_jardin_temperature
show_state: false
show_icon: false
show_name: false
tap_action: none
double_tap_action: none
hold_action: none
custom_fields:
  icon:
    card:
      type: custom:button-card
      icon: mdi:thermometer
      styles:
        card:
          - aspect-ratio: 1/1
          - width: "[[[ return window.innerWidth <= 600 ? '20px' : '25px' ]]]"
          - border: none
          - border-radius: 50%
          - background: none
        icon:
          - width: "[[[ return window.innerWidth <= 600 ? '18px' : '20px' ]]]"
          - color: white
  icon_border:
    card:
      type: custom:button-card
      styles:
        card:
          - aspect-ratio: 1/1
          - width: "[[[ return window.innerWidth <= 600 ? '23px' : '25px' ]]]"
          - border: 1px solid white
          - border-radius: 50%
          - background: none
  name:
    card:
      type: custom:button-card
      name: Netatmo Jardin Température
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: "[[[ return window.innerWidth <= 600 ? '0.9rem' : '1.0rem' ]]]"
          - align-self: start
          - justify-self: start
          - color: white
          - font-weight: 500
  bar:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - height: 6px
          - width: |
              [[[ return window.innerWidth <= 600 ? "220px" : "250px"; ]]]
          - border-radius: 999px
          - border: 0
          - padding: 0
          - background: |-
              linear-gradient(to right, rgba(0,0,255,1.0) 0%,
              rgba(0,165,255,1.0) 40%, rgba(0,255,0,1.0) 65%,
              rgba(255,255,0,1.0) 75%, rgba(255,0,0,1.0) 100%)
  cursor:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - width: 12px
          - height: 25px
          - border-radius: 999px
          - border: 4px solid rgba(42,45,54,1.0)
          - background-color: |
              [[[ 
                const minTemp = -20;
                const maxTemp = 60;
                const temp = Number(entity.state) || minTemp;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [0, 0, 255, 1.0] },
                  { p: 40,c: [0, 165, 255, 1.0] },
                  { p: 65,  c: [0, 255, 0, 1.0] },
                  { p: 75,c: [255, 255, 0, 1.0] },
                  { p: 100, c: [255, 0, 0, 1.0] }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1], b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
  value:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: true
      state_display: |
        [[[ 
          const v = Number(entity.state); 
          return isNaN(v) ? '—' : v.toFixed(1); 
        ]]]
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        state:
          - color: white
          - font-size: "[[[ return window.innerWidth <= 600 ? '1.6rem' : '1.8rem' ]]]"
          - font-weight: 700
  unit:
    card:
      type: custom:button-card
      name: °C
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: "[[[ return window.innerWidth <= 600 ? '1.1rem' : '1.3rem' ]]]"
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 800
  min_val:
    card:
      type: custom:button-card
      name: "-20°C"
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: "[[[ return window.innerWidth <= 600 ? '0.8rem' : '1.0rem' ]]]"
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  max_val:
    card:
      type: custom:button-card
      name: 60°C
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: "[[[ return window.innerWidth <= 600 ? '0.8rem' : '1.0rem' ]]]"
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  comment:
    card:
      type: custom:button-card
      show_name: true
      show_icon: false
      show_state: false
      name: |
        [[[
          const temp = Number(entity.state);
          if (temp < -5) return "Froid extrême";
          if (temp >= -5 && temp < 0) return "Très froid";
          if (temp >= 0 && temp < 10) return "Froid";
          if (temp >= 10 && temp < 15) return "Frais";
          if (temp >= 15 && temp < 25) return "Confortable";
          if (temp >= 25 && temp < 30) return "Chaud";
          if (temp >= 30 && temp < 40) return "Très chaud";
          if (temp >= 40) return "Chaleur extrême";
        ]]]
      styles:
        card:
          - height: 25px
          - width: auto
          - border-radius: 999px
          - border: none
          - padding: 0px 8px 0px 8px
          - background: white
          - background-color: |
              [[[ 
                const minTemp = -20;
                const maxTemp = 60;
                const temp = Number(entity.state);
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = isNaN(temp) ? 0 : clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [0, 0, 255, 1.0] },
                  { p: 40,c: [0, 165, 255, 1.0] },
                  { p: 65,  c: [0, 255, 0, 1.0] },
                  { p: 75,c: [255, 255, 0, 1.0] },
                  { p: 100, c: [255, 0, 0, 1.0] }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1], b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},0.5)`;
              ]]]
        name:
          - font-size: "[[[ return window.innerWidth <= 600 ? '0.8rem' : '0.9rem' ]]]"
          - font-weight: 600
          - line-height: |
              [[[ 
                return window.innerWidth <= 600 ? "0.9" : "normal";
              ]]]
          - color: |
              [[[ 
                const minTemp = -20;
                const maxTemp = 60;
                const temp = Number(entity.state);
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = isNaN(temp) ? 0 : clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [0, 0, 255, 1.0] },
                  { p: 40,c: [0, 165, 255, 1.0] },
                  { p: 65,  c: [0, 255, 0, 1.0] },
                  { p: 75,c: [255, 255, 0, 1.0] },
                  { p: 100, c: [255, 0, 0, 1.0] }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1], b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
styles:
  card:
    - background-color: rgba(42,45,54,1.0)
    - aspect-ratio: 7/1
    - cursor: default
  custom_fields:
    icon:
      - position: absolute
      - top: 55%
      - left: 4%
      - transform: translate(-50%, -50%)
    icon_border:
      - position: absolute
      - top: "[[[ return window.innerWidth <= 600 ? '56%' : '55%' ]]]"
      - left: 4%
      - transform: translate(-50%, -50%)
    name:
      - position: absolute
      - top: "[[[ return window.innerWidth <= 600 ? '1%' : '3%' ]]]"
      - left: 8%
    value:
      - position: absolute
      - top: 0%
      - right: 6.5%
      - margin-top: "-0.5%"
    unit:
      - position: absolute
      - right: 2%
      - top: 1%
      - margin-left: 1%
    min_val:
      - position: absolute
      - bottom: 2%
      - left: "[[[ return window.innerWidth <= 600 ? '34%' : '38%' ]]]"
    max_val:
      - position: absolute
      - bottom: 2%
      - right: "[[[ return window.innerWidth <= 600 ? '2%' : '4%' ]]]"
    comment:
      - position: absolute
      - top: 55%
      - left: |
          [[[ 
            const iconLeftPercent = 1;
            const iconDiameterPx = window.innerWidth <= 600 ? 20 : 25;
            const barWidthPx = window.innerWidth <= 600 ? 220 : 250;
            const barRightPercent = window.innerWidth <= 600 ? 4 : 7;
            const iconRightExpr = `calc(${iconLeftPercent}% + ${iconDiameterPx}px)`;
            const barLeftExpr = `calc(100% - ${barRightPercent}% - ${barWidthPx}px)`;
            return `calc( ( ${iconRightExpr} + ${barLeftExpr} ) / 2 )`;
          ]]]
      - transform: translate(-50%, -50%)
    bar:
      - position: absolute
      - right: "[[[ return window.innerWidth <= 600 ? '4%' : '7%' ]]]"
      - top: 55%
      - transform: translateY(-50%)
    cursor:
      - position: absolute
      - top: "[[[ return 'calc(55% - 12.5px)'; ]]]"
      - left: |
          [[[ 
            const value = Number(entity.state);
            const min = -25;
            const max = 60;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const t = clamp(value, min, max);
            const barWidth = window.innerWidth <= 600 ? 220 : 250;
            const halfCursor = 8;
            const barRightPercent = window.innerWidth <= 600 ? 4 : 7;
            const percent = ((t - min) / (max - min)) * 100;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(100% - ${barRightPercent}% - ${barWidth}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]

Je me demandais s’il était possible de l’adapter au capteur de pression aussi et mettre à différents endroits de la barre une icône pluie, soleil etc… Comme les baromètres :thinking: il faut que je regarde ça et réussir sur mes températures à avoir le min et le max

Bonjour à toutes et à tous,

Ma très modeste contribution, à partir de ce que M. Button Card nous a gentiment fait dans le post en lien de ma réponse.
J’ai rajouté un graphique, sur 7 jours.

A noter aussi que j’ai retiré les évolutions de couleur sur le texte dans le commentaire en dessous. Car je trouvais que ça manquait souvent de contraste, j’ai donc mis en blanc.

Voici le résultat:

Et le code associé :

type: custom:button-card
entity: sensor.lywsd02_tempc
show_state: false
show_icon: false
show_name: false
tap_action: none
double_tap_action: none
hold_action: none
custom_fields:
  icon_and_name:
    card:
      type: horizontal-stack
      cards:
        - type: custom:button-card
          icon: mdi:thermometer
          styles:
            card:
              - aspect-ratio: 1/1
              - width: 40px
              - padding: 0
              - border: 4px solid rgba(167,176,205,1.0)
              - border-radius: 50%
              - background: none
              - margin-left: 10px
              - margin-top: 5px
            icon:
              - width: 90%
              - color: white
        - type: custom:button-card
          name: Température Salon
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-top: 5px
            name:
              - font-size: 1.2rem
              - justify-self: start
              - text-align: left
              - color: white
              - font-weight: 500
              - padding-top: "[[[ return window.innerWidth <= 600 ? '2%' : '6%' ]]]"
  dividing_line:
    card:
      type: custom:button-card
      styles:
        card:
          - width: 600px
          - height: 1px
          - padding: 0
          - border: 1px solid rgba(54,56,68,1.0)
          - border-radius: 0
          - background: none
          - overflow: hidden
  mini_graph:
    card:
      type: custom:mini-graph-card
      entities:
        - entity: sensor.lywsd02_tempc
          name: Salon
          show_fill: false
        - entity: sun.sun
          color: gray
          name: Sun
          show_line: false
          show_points: false
          show_legend: false
          y_axis: secondary
      show:
        name: false
        icon: false
        state: false
        points: false
      state_map:
        - value: above_horizon
          label: Day
        - value: below_horizon
          label: Night
      hours_to_show: 168
      points_per_hour: 0.5
      color_thresholds:
        - value: 14
          color: "#0000FF"
        - value: 16
          color: "#00A5FF"
        - value: 22
          color: "#00FF00"
        - value: 24
          color: "#FFFF00"
        - value: 26
          color: "#FFA500"
        - value: 30
          color: "#FF0000"
      card_mod:
        style: |
          ha-card {
            --ha-card-background:
          }
  value_and_unit:
    card:
      type: horizontal-stack
      cards:
        - type: custom:button-card
          show_name: false
          show_icon: false
          show_state: true
          state_display: |
            [[[
              return parseFloat(entity.state).toFixed(1);
            ]]]
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-right: "-10%"
            state:
              - color: white
              - font-size: 1.4rem
              - font-weight: 800
        - type: custom:button-card
          name: °C
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-left: "-10%"
            name:
              - font-size: 1.0rem
              - align-self: start
              - justify-self: start
              - color: rgba(167,176,205,1.0)
              - font-weight: 800
              - padding-top: 0%
  bar:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - height: 12px
          - width: "[[[ return window.innerWidth <= 600 ? '300px' : '360px' ]]]"
          - border-radius: 999px
          - border: 0
          - padding: 0px
          - background: >-
              linear-gradient(to right, rgba(0,0,255,1.0) 0%,
              rgba(0,165,255,1.0) 25%, rgba(0,255,0,1.0) 50%,
              rgba(255,255,0,1.0) 62.5%, rgba(255,165,0,1.0) 75%,
              rgba(255,0,0,1.0) 100%)
  cursor:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - width: 16px
          - height: 32px
          - border-radius: 999px
          - border: 4px solid rgba(93,83,103,1.0)
          - background-color: |
              [[[
                const minTemp = 14; // MIN CHANGÉ
                const maxTemp = 30; // MAX CHANGÉ
                const temp = Number(entity.state) || minTemp;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100); 
                const stops = [
                  { p: 0,   c: [0, 0, 255]    },
                  { p: 25,  c: [0, 165, 255]  },
                  { p: 50,  c: [0, 255, 0]    },
                  { p: 62.5,  c: [255, 255, 0]  },
                  { p: 75,  c: [255, 165, 0]  },
                  { p: 100, c: [255, 0, 0]    }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
  min_val:
    card:
      type: custom:button-card
      name: 14°C
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  max_val:
    card:
      type: custom:button-card
      name: 30°C
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  comment:
    card:
      type: custom:button-card
      show_name: true
      show_icon: false
      show_state: false
      name: |
        [[[
          const temp = Number(entity.state) || 0;
          if (temp < 18) { // < 18°C
            return "Trop froid";
          }
          if (temp >= 18 && temp < 20) {
            return "Frais mais acceptable";
          }
          if (temp >= 20 && temp < 22) {
            return "Confort idéal";
          }
          if (temp >= 22 && temp < 24) {
            return "Légèrement chaud";
          }
          if (temp >= 24) {
            return "Trop chaud";
          }
          return "Indisponible";
        ]]]
      styles:
        card:
          - width: auto
          - height: auto
          - border-radius: 999px
          - border: 0
          - padding: 5px 10px 5px 10px
          - background: white
          - background-color: |
              [[[
                const minTemp = 14; 
                const maxTemp = 30;
                const temp = Number(entity.state) || minTemp;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100); 
                const stops = [
                  { p: 0,   c: [0, 0, 255]    }, // 14°C
                  { p: 25,  c: [0, 165, 255]  }, // 18°C
                  { p: 50,  c: [0, 255, 0]    }, // 22°C
                  { p: 62.5,  c: [255, 255, 0]  }, // 24°C
                  { p: 75,  c: [255, 165, 0]  }, // 26°C
                  { p: 100, c: [255, 0, 0]    }  // 30°C
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},0.5)`;
              ]]]
        name:
          - font-size: 0.8rem
          - font-weight: 600
          - color: rgba(255,255,255,1.0)
  min_temp_week:
    card:
      type: custom:button-card
      icon: mdi:arrow-collapse-down
      show_name: false
      show_state: true
      layout: icon_state
      state_display: |
        [[[
          return parseFloat(states['sensor.temperature_min_7j'].state).toFixed(1) + "°";
        ]]]
      styles:
        card:
          - width: 80px
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        state:
          - font-size: 1.2rem
          - align-self: end
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 600
        icon:
          - width: 20px
          - color: rgba(167,176,205,1.0)
  max_temp_week:
    card:
      type: custom:button-card
      icon: mdi:arrow-collapse-up
      show_name: false
      show_state: true
      layout: state_icon
      state_display: |
        [[[
          return parseFloat(states['sensor.temperature_max_7j'].state).toFixed(1) + "°";
        ]]]
      styles:
        card:
          - width: 80px
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        state:
          - font-size: 1.2rem
          - align-self: end
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 600
          - padding-right: 100px
        icon:
          - width: 20px
          - left: 12px
          - color: rgba(167,176,205,1.0)
styles:
  card:
    - background-color: rgba(93,83,103,1.0)
    - aspect-ratio: "[[[ return window.innerWidth <= 600 ? '1.4/1' : '1.7/1' ]]]"
    - cursor: default
  custom_fields:
    icon_and_name:
      - position: absolute
      - top: 2%
      - left: 1%
    dividing_line:
      - position: absolute
      - top: 21%
      - left: 0%
    mini_graph:
      - position: absolute
      - top: 25%
      - left: 50%
      - transform: translate(-50%, 0)
      - z-index: 2
      - width: 90%
    value_and_unit:
      - position: absolute
      - top: 62%
      - left: |
          [[[
            const value = Number(entity.state) || 0;
            const min = 14;
            const max = 30;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = 360;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
      - transform: translateX(-50%)
    bar:
      - position: absolute
      - left: 50%
      - top: 78%
      - transform: translate(-50%, -50%)
    cursor:
      - position: absolute
      - top: "[[[ return 'calc(78% - 16px)'; ]]]"
      - left: |
          [[[
            const value = Number(entity.state) || 0;
            const min = 14;
            const max = 30;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = 360;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
    min_val:
      - position: absolute
      - top: 84%
      - left: "[[[ return window.innerWidth <= 600 ? '8%' : '12%' ]]]"
    max_val:
      - position: absolute
      - top: 84%
      - right: "[[[ return window.innerWidth <= 600 ? '7%' : '10%' ]]]"
    comment:
      - position: absolute
      - left: 50%
      - top: 92%
      - transform: translate(-50%, -50%)
    min_temp_week:
      - position: absolute
      - top: 65%
      - left: 5%
    max_temp_week:
      - position: absolute
      - top: 65%
      - right: 2%

2 « J'aime »

Ahhhhh, un nouvel adepte :rofl: Veux-tu être le petit gourou de cette nouvelle secte ?

Un nouvel adepte, je veux bien.
Par contre, tu es notre gourou à nous tous. Nous n’arrivons pas à ta cheville ô maitre !

2 « J'aime »

Juste le petit gourou (il faudra que tu changes d’avatar : petit gourou, pas petit hibou), je me garde le rôle de grand gourou (gris… pas roux). Dès que j’aurai finis la carte « heures creuses » universelle, je me pencherais à nouveau sur cette série de carte pour la rendre universelle elle aussi.

1 « J'aime »

Salut, @btncrd,

J’ai zappé une formation.
Ou trouve tu t’es logo m3of ?
C’est une intégration maison ?
Ton code pour l’indice Qualité de l’air c’est les même que Pollen ?

D’avance merci.

A installer via HACS ou en manuel :

1 « J'aime »

Bonjour @romain4559

C’est issu d’une intégration non « maison » : GitHub - beecho01/material-symbols: Material Symbols for Home Assistant is a collection of 15,613 Google Material Symbols for use within Home Assistant. It uses the icon-set produced and maintained by iconify.

Lol, grillé par @xTG

2 « J'aime »

Bonsoir,

si cela peut intéresser quelqu’un je me suis fait un streamline_templates qui reprend pas mal de choses de ce sujet.
Il prend en entrée soit un capteur d’humidité soit un capteur de température en se basant sur le device_class.

Le rendu graphique :

Je n’ai pas de capteur d’humidité extérieure, et je n’ai pas encore créé mes capteurs de température min/max sur la journée, d’où le mix entre les deux pour la capture. :laughing:

Pas de couleurs pour le mini-graph car il ne semble pas possible d’injecter un threshold variable (ou alors des trucs semi-dynamique à base de !secrets mais je ne trouvais pas cela joli…).
Donc je ne pouvais pas différentier une courbe de température d’une courbe d’humidité.

Le template utilise l’entité sun.sun, à noter aussi que j’ai inversé le jour et la nuit par rapport à ce que Tank proposait.
Je trouve plus logique de voir la nuit à « zéro » plutôt que le jour.

Code des cartes :

type: custom:streamline-card
template: jauge
variables:
  entity: sensor.temperature_exterieure
  name: Température extérieure
  entity_variable_min: sensor.temperature_min_du_jour
  entity_variable_max: sensor.temperature_max_du_jour
type: custom:streamline-card
template: jauge
variables:
  entity: sensor.climacontrol_living_room_sensor_humidity
  name: Humidité intérieure
  entity_variable_min: null
  entity_variable_max: null

Le template :

streamline_templates:
  jauge:
    card:
      type: custom:button-card
      entity: '[[entity]]'
      show_state: false
      show_icon: false
      show_name: false
      tap_action: none
      double_tap_action: none
      hold_action: none
      custom_fields:
        icon_and_name:
          card:
            type: horizontal-stack
            cards:
              - type: custom:button-card
                icon: |
                  [[[
                    if( entity.attributes.device_class == "temperature" )
                      return "mdi:thermometer";
                    else if( entity.attributes.device_class == "humidity" )
                      return "mdi:water"
                    else
                      return "mdi:alert-circle";
                  ]]]
                styles:
                  card:
                    - aspect-ratio: 1/1
                    - width: 40px
                    - padding: 0
                    - border: 4px solid rgba(167,176,205,1.0)
                    - border-radius: 50%
                    - background: none
                  icon:
                    - width: 90%
                    - color: white
              - type: custom:button-card
                name: |
                  [[[
                    return "[[name]]";
                  ]]]
                styles:
                  card:
                    - width: auto
                    - padding: 0
                    - border: none
                    - border-radius: 0
                    - background: none
                  name:
                    - font-size: 1.4rem
                    - justify-self: start
                    - text-align: left
                    - color: white
                    - font-weight: 500
                    - padding-top: '[[[ return window.innerWidth <= 600 ? ''2%'' : ''6%'' ]]]'
        dividing_line:
          card:
            type: custom:button-card
            styles:
              card:
                - width: 600px
                - height: 1px
                - padding: 0
                - border: 1px solid rgba(54,56,68,1.0)
                - border-radius: 0
                - background: none
                - overflow: hidden
        mini_graph:
          card:
            type: custom:mini-graph-card
            entities:
              - entity: '[[entity]]'
                show_fill: false
                color: darkgrey
              - entity: sun.sun
                color: grey
                show_line: false
                show_points: false
                show_legend: false
                y_axis: secondary
            show:
              name: false
              icon: false
              state: false
              points: false
              labels: false
              labels_secondary: false
            state_map:
              - value: below_horizon
                label: Night
              - value: above_horizon
                label: Day
            hours_to_show: 168
            points_per_hour: 0.5
            card_mod:
              style: |
                ha-card {
                  --ha-card-background:
                }
        value_and_unit:
          card:
            type: horizontal-stack
            cards:
              - type: custom:button-card
                show_name: false
                show_icon: false
                show_state: true
                state_display: |
                  [[[
                    const status = entity.state;
                    return parseFloat(status).toFixed(1);
                  ]]]
                styles:
                  card:
                    - width: auto
                    - padding: 0
                    - border: none
                    - border-radius: 0
                    - background: none
                    - margin-right: '-10%'
                  state:
                    - color: white
                    - font-size: 1.8rem
                    - font-weight: 800
              - type: custom:button-card
                name: |
                  [[[
                    if( entity.attributes.device_class == "temperature" )
                      return "°C";
                    else if( entity.attributes.device_class == "humidity" )
                      return '%';
                    else
                      return '';
                  ]]]
                styles:
                  card:
                    - width: auto
                    - padding: 0
                    - border: none
                    - border-radius: 0
                    - background: none
                    - margin-left: '-10%'
                  name:
                    - font-size: 1.2rem
                    - align-self: start
                    - justify-self: start
                    - color: rgba(167,176,205,1.0)
                    - font-weight: 800
                    - padding-top: 40%
        bar:
          card:
            type: custom:button-card
            show_name: false
            show_icon: false
            show_state: false
            styles:
              card:
                - height: 12px
                - width: '[[[ return window.innerWidth <= 600 ? ''300px'' : ''360px'' ]]]'
                - border-radius: 999px
                - border: 0
                - padding: 0px
                - background: |
                    [[[
                      if( entity.attributes.device_class == "temperature" )
                        return 'linear-gradient(to right,rgba(0,0,255,1.0) 0%,rgba(0,165,255,1.0) 25%,rgba(0,255,0,1.0) 50%,rgba(255,255,0,1.0) 75%,rgba(255,0,0,1.0) 100%)';
                      else if( entity.attributes.device_class == "humidity" )
                        return 'linear-gradient(to right, rgba(255,0,0,1.0) 0%,rgba(255,165,0,1.0) 10%, rgba(255,255,0,1.0) 30%,rgba(0,255,0,1.0) 50%, rgba(255,255,0,1.0) 70%,rgba(255,165,0,1.0) 90%, rgba(255,0,0,1.0) 100%)';
                      else
                        return 'none';
                    ]]]
        cursor:
          card:
            type: custom:button-card
            show_name: false
            show_icon: false
            show_state: false
            styles:
              card:
                - width: 16px
                - height: 32px
                - border-radius: 999px
                - border: 4px solid rgba(42,45,54,1.0)
                - background-color: |
                    [[[
                      var stops = [];
                      var minVal = 0;
                      var maxVal = 1;
                      if( entity.attributes.device_class == "temperature" )
                      {
                        minVal = -15;
                        maxVal = 45;
                        stops = [
                          { p: 0,   c: [0, 0, 255]    },
                          { p: 25,  c: [0, 165, 255]  },
                          { p: 50,  c: [0, 255, 0]    },
                          { p: 75,  c: [255, 255, 0]  },
                          { p: 100, c: [255, 0, 0]    }
                        ];
                      }
                      else if( entity.attributes.device_class == "humidity" )
                      {
                        minVal = 0;
                        maxVal = 100;
                        stops = [
                          { p: 0,   c: [255, 0,   0]  },
                          { p: 30,  c: [255, 165, 0]  },
                          { p: 40,  c: [255, 255, 0]  },
                          { p: 50,  c: [0,   255, 0]  },
                          { p: 60,  c: [255, 255, 0]  },
                          { p: 70,  c: [255, 165, 0]  },
                          { p: 100, c: [255, 0,   0]  }
                        ];
                      }
                      else
                      {
                        stops = [
                          { p: 0,   c: [255, 0,   0]  },
                          { p: 100, c: [255, 0,   0]  }
                        ];
                      }
                      const s = entity.state;
                      const temp = Number(s);
                      const value = isNaN(temp) ? minVal : temp;
                      const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                      const percent = clamp(((value - minVal) / (maxVal - minVal)) * 100, 0, 100);
                      let i = 1;
                      while (i < stops.length && percent > stops[i].p) i++;
                      const a = stops[i - 1];
                      const b = stops[i];
                      const t = (percent - a.p) / (b.p - a.p);
                      const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                      const r = lerp(a.c[0], b.c[0], t);
                      const g = lerp(a.c[1], b.c[1], t);
                      const bcol = lerp(a.c[2], b.c[2], t);
                      return `rgba(${r},${g},${bcol},1.0)`;
                    ]]]
        min_val:
          card:
            type: custom:button-card
            name: |
              [[[
                if( entity.attributes.device_class == "temperature" )
                  return "-15°C";
                else if( entity.attributes.device_class == "humidity" )
                  return "0%";
                else
                  return '';
              ]]]
            styles:
              card:
                - width: auto
                - padding: 0
                - border: none
                - border-radius: 0
                - background: none
              name:
                - font-size: 1.0rem
                - align-self: start
                - justify-self: start
                - color: rgba(167,176,205,1.0)
                - font-weight: 400
        max_val:
          card:
            type: custom:button-card
            name: |
              [[[
                if( entity.attributes.device_class == "temperature" )
                  return "45°C";
                else if( entity.attributes.device_class == "humidity" )
                  return "100%";
                else
                  return '';
              ]]]
            styles:
              card:
                - width: auto
                - padding: 0
                - border: none
                - border-radius: 0
                - background: none
              name:
                - font-size: 1.0rem
                - align-self: start
                - justify-self: start
                - color: rgba(167,176,205,1.0)
                - font-weight: 400
        comment:
          card:
            type: custom:button-card
            show_name: true
            show_icon: false
            show_state: false
            name: |
              [[[
                const s = entity.state;
                const value = Number(s);
                if( entity.attributes.device_class == "temperature" )
                {
                  if (value < -5) return "Froid extrême";
                  if (value >= -5 && value < 0) return "Très froid";
                  if (value >= 0 && value < 10) return "Frais";
                  if (value >= 10 && value < 20) return "Confortable";
                  if (value >= 20 && value < 30) return "Chaud";
                  if (value >= 30 && value < 40) return "Très chaud";
                  if (value >= 40) return "Chaleur extrême";
                }
                else if( entity.attributes.device_class == "humidity" )
                {
                  if (value < 30) return "Trop sec";
                  if (value >= 30 && value < 40) return "Sec mais acceptable";
                  if (value >= 40 && value < 60) return "Zone idéale de confort";
                  if (value >= 60 && value < 70) return "Humide mais tolérable";
                  if (value >= 70) return "Trop humide";
                }
                else
                  return '';
              ]]]
            styles:
              card:
                - width: auto
                - height: auto
                - border-radius: 999px
                - border: 0
                - padding: 5px 10px 5px 10px
                - background-color: |
                    [[[
                      var stops = [];
                      var minVal = 0;
                      var maxVal = 1;
                      if( entity.attributes.device_class == "temperature" )
                      {
                        minVal = -15;
                        maxVal = 45;
                        stops = [
                          { p: 0,   c: [0, 0, 255]    },
                          { p: 25,  c: [0, 165, 255]  },
                          { p: 50,  c: [0, 255, 0]    },
                          { p: 75,  c: [255, 255, 0]  },
                          { p: 100, c: [255, 0, 0]    }
                        ];
                      }
                      else if( entity.attributes.device_class == "humidity" )
                      {
                        minVal = 0;
                        maxVal = 100;
                        stops = [
                          { p: 0,   c: [255, 0,   0]  },
                          { p: 30,  c: [255, 165, 0]  },
                          { p: 40,  c: [255, 255, 0]  },
                          { p: 50,  c: [0,   255, 0]  },
                          { p: 60,  c: [255, 255, 0]  },
                          { p: 70,  c: [255, 165, 0]  },
                          { p: 100, c: [255, 0,   0]  }
                        ];
                      }
                      else
                      {
                        stops = [
                          { p: 0,   c: [255, 0,   0]  },
                          { p: 100, c: [255, 0,   0]  }
                        ];
                      }
                      const s = entity.state;
                      const temp = Number(s);
                      const value = isNaN(temp) ? minVal : temp;
                      const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                      const percent = clamp(((value - minVal) / (maxVal - minVal)) * 100, 0, 100); 
                      let i = 1;
                      while (i < stops.length && percent > stops[i].p) i++;
                      const a = stops[i - 1];
                      const b = stops[i];
                      const t = (percent - a.p) / (b.p - a.p);
                      const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                      const r = lerp(a.c[0], b.c[0], t);
                      const g = lerp(a.c[1], b.c[1], t);
                      const bcol = lerp(a.c[2], b.c[2], t);
                      return `rgba(${r},${g},${bcol},0.5)`;
                    ]]]
              name:
                - font-size: 1.0rem
                - font-weight: 600
                - color: |
                    [[[
                      var stops = [];
                      var minVal = 0;
                      var maxVal = 1;
                      if( entity.attributes.device_class == "temperature" )
                      {
                        minVal = -15;
                        maxVal = 45;
                        stops = [
                          { p: 0,   c: [0, 0, 255]    },
                          { p: 25,  c: [0, 165, 255]  },
                          { p: 50,  c: [0, 255, 0]    },
                          { p: 75,  c: [255, 255, 0]  },
                          { p: 100, c: [255, 0, 0]    }
                        ];
                      }
                      else if( entity.attributes.device_class == "humidity" )
                      {
                        minVal = 0;
                        maxVal = 100;
                        stops = [
                          { p: 0,   c: [255, 0,   0]  },
                          { p: 30,  c: [255, 165, 0]  },
                          { p: 40,  c: [255, 255, 0]  },
                          { p: 50,  c: [0,   255, 0]  },
                          { p: 60,  c: [255, 255, 0]  },
                          { p: 70,  c: [255, 165, 0]  },
                          { p: 100, c: [255, 0,   0]  }
                        ];
                      }
                      else
                      {
                        stops = [
                          { p: 0,   c: [255, 0,   0]  },
                          { p: 100, c: [255, 0,   0]  }
                        ];
                      }
                      const s = entity.state;
                      const temp = Number(s);
                      const value = isNaN(temp) ? minVal : temp;
                      const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                      const percent = clamp(((value - minVal) / (maxVal - minVal)) * 100, 0, 100);
                      let i = 1;
                      while (i < stops.length && percent > stops[i].p) i++;
                      const a = stops[i - 1];
                      const b = stops[i];
                      const t = (percent - a.p) / (b.p - a.p);
                      const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                      const r = lerp(a.c[0], b.c[0], t);
                      const g = lerp(a.c[1], b.c[1], t);
                      const bcol = lerp(a.c[2], b.c[2], t);
                      return `rgba(${r},${g},${bcol},1.0)`;
                    ]]]
        min_hist_value:
          card:
            type: custom:button-card
            icon: |
              [[[
                const s = states['[[entity_variable_min]]'];
                if( s === undefined )
                  return '';
                return 'mdi:arrow-collapse-down';
              ]]]
            show_name: false
            show_state: true
            layout: icon_state
            state_display: |
              [[[
                const s = states['[[entity_variable_min]]'];
                if( s === undefined )
                  return '';
                if( entity.attributes.device_class == "temperature" )
                  return parseFloat(s.state).toFixed(1) + "°C";
                else if( entity.attributes.device_class == "humidity" )
                  return parseFloat(s.state).toFixed(1) + "%";
                else
                  return parseFloat(s.state).toFixed(1);
              ]]]
            styles:
              card:
                - width: 80px
                - padding: 0
                - border: none
                - border-radius: 0
                - background: none
              state:
                - font-size: 1.2rem
                - align-self: end
                - justify-self: start
                - color: rgba(167,176,205,1.0)
                - font-weight: 600
              icon:
                - width: 20px
                - color: rgba(167,176,205,1.0)
        max_hist_value:
          card:
            type: custom:button-card
            icon: |
              [[[
                const s = states['[[entity_variable_max]]'];
                if( s === undefined )
                  return '';
                return 'mdi:arrow-collapse-up';
              ]]]
            show_name: false
            show_state: true
            layout: state_icon
            state_display: |
              [[[
                const s = states['[[entity_variable_max]]'];
                if( s === undefined )
                  return '';
                if( entity.attributes.device_class == "temperature" )
                  return parseFloat(s.state).toFixed(1) + "°C";
                else if( entity.attributes.device_class == "humidity" )
                  return parseFloat(s.state).toFixed(1) + "%";
                else
                  return parseFloat(s.state).toFixed(1);
              ]]]
            styles:
              card:
                - width: 80px
                - padding: 0
                - border: none
                - border-radius: 0
                - background: none
              state:
                - font-size: 1.2rem
                - align-self: end
                - justify-self: start
                - color: rgba(167,176,205,1.0)
                - font-weight: 600
                - padding-right: 130px
              icon:
                - width: 20px
                - color: rgba(167,176,205,1.0)
      styles:
        card:
          - background-color: rgba(42,45,54,1.0)
          - margin-bottom: 5px
          - aspect-ratio: '[[[ return window.innerWidth <= 600 ? ''1.6/1'' : ''1.9/1'' ]]]'
          - cursor: default
        custom_fields:
          icon_and_name:
            - position: absolute
            - top: 2%
            - left: 1%
          dividing_line:
            - position: absolute
            - top: 20%
            - left: 0%
          mini_graph:
            - position: absolute
            - top: 23%
            - left: 50%
            - transform: translate(-50%, 0)
            - z-index: 2
            - width: 90%
          value_and_unit:
            - position: absolute
            - top: 65%
            - left: |
                [[[
                  var min = 0
                  var max = 1
                  if( entity.attributes.device_class == "temperature" )
                  {
                    min = -15
                    max = 45
                  }
                  else if( entity.attributes.device_class == "humidity" )
                  {
                    min = 0
                    max = 100
                  }
                  const s = entity.state;
                  const value = isNaN(Number(s)) ? min : Number(s);
                  const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                  const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
                  const barWidth = (window.innerWidth <= 600) ? 300 : 360;
                  const halfCursor = 8;
                  const offsetPx = (barWidth * percent) / 100;
                  return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
                ]]]
            - transform: translateX(-50%)
          bar:
            - position: absolute
            - left: 50%
            - top: 82%
            - transform: translate(-50%, -50%)
          cursor:
            - position: absolute
            - top: '[[[ return ''calc(82% - 16px)'' ]]]'
            - left: |
                [[[
                  var min = 0
                  var max = 1
                  if( entity.attributes.device_class == "temperature" )
                  {
                    min = -15
                    max = 45
                  }
                  else if( entity.attributes.device_class == "humidity" )
                  {
                    min = 0
                    max = 100
                  }
                  const s = entity.state;
                  const value = isNaN(Number(s)) ? min : Number(s);
                  const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                  const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
                  const barWidth = (window.innerWidth <= 600) ? 300 : 360;
                  const halfCursor = 8;
                  const offsetPx = (barWidth * percent) / 100;
                  return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
                ]]]
          min_val:
            - position: absolute
            - bottom: 8%
            - left: '[[[ return window.innerWidth <= 600 ? ''8%'' : ''12%'' ]]]'
          max_val:
            - position: absolute
            - bottom: 8%
            - right: '[[[ return window.innerWidth <= 600 ? ''7%'' : ''10%'' ]]]'
          comment:
            - position: absolute
            - left: 50%
            - bottom: '-3%'
            - transform: translate(-50%, -50%)
          min_hist_value:
            - position: absolute
            - top: 67%
            - left: 5%
          max_hist_value:
            - position: absolute
            - top: 67%
            - right: 2%
3 « J'aime »

Merci @xTG !
Adoptée ^^ en conservant la partie color threshold en ce qui me concerne

1 « J'aime »

Whooooo, je vais finir par demander des royalties :rofl:

3 « J'aime »