Bonjour,
Bon, alors après ~1 mois et demi de HA, mon dashboard commence a prendre forme.
Au quotidien, nous interagissons avec la domotique soit avec la google home, soit via notre téléphone (quasi pas depuis la tablette, et peu depuis l’ordinateur, sauf moi pour paramétrer), je me suis donc penché sur mes dashboards smartphone.
Après divers essais, et tests d’utilisation, je suis arrivé à la conclusion que l’utilisation de tuiles n’était pas souvent efficace ergonomiquement parlant pour nous, même si cela reste plus joli visuellement, la solution se tourne sur un visuel des plans de la maison avec des infos de base d’un coup d’oeil, puis en un clic on arrive sur plus de détail (c’est peut être aussi que j’ai « trop » d’appareils a piloter … ) . Cela permet une navigation plus intuitive et rapide (et importante pour le WAF ).
Mieux que des mots, la démo! heuu, non, des images en fait :).
Donc en gros, j’utilise 3 pages, correspondant aux 3 niveaux de la maison:
Puis en fonction des boutons, j’affiche des infos de base (assez limitées pour que ce soit lisible et clair sur smartphone), et en double cliquant (parfois 1 seul clic), un détail qui diffère d’un bouton à l’autre.
Boutons (global):
Pour mes boutons, j’utilise majoritairement l’excellente carte « custom:button-card », dont il existe nombreux tutos. un des éléments importants, c’est sa capacité à gérer des « templates » (via « button_card_templates »), j’ai donc regroupé pour la plupart de mes boutons, les paramètres communs. Pour rappel, un template peux lui même utiliser d’autres templates.
J’utilise donc un template « base » qui est commun a tous mes boutons (et qui en gros correspond à un bouton de version dashboard), puis en suite j’ai des déclinaisons, notamment pour la vue de mes plans.
Templates globaux
################################################################
################################################################
# Templates for cards
################################################################
################################################################
##### Base for all the cards (normal by default) ###########
# Ratio, generic colours, name, format, font
base:
show_state: false
show_icon: false
aspect_ratio: 1/1
state:
## Specific style when state is "On"
- value: 'on'
styles:
card:
- background-color: 'rgba(255, 255, 255, 0.8)'
- border: 1px rgba(80, 80, 80) solid
name:
- color: 'rgba(0, 0, 0, 0.6)'
custom_fields:
temperature:
- color: "#8d8e90"
double_tap_action:
action: more-info
styles:
name:
- top: 74%
- left: 11%
- position: absolute
card:
- font-family: Passion One
- letter-spacing: 0px
- color: 'rgba(255, 255, 255, 0.3)'
- font-size: 100%
- background-color: 'rgba(80, 80, 80, 0.7)'
- border-radius: 15%
- box-shadow: none
- transition: none
- border: 1px rgba(255, 255, 255) solid
############### Specific for map tiles ################
base_map:
template: base
show_name: false
styles:
custom_fields:
temperature:
- top: 73%
- width: 100%
- justify-self: center
- align-self: center
- font-size: >
[[[
var window_width = window.screen.width;
if (window_width > 600) {return '17px';}
else {return '13px';}
]]]
- color: white
- font-weight: black
- position: absolute
Volets:
De base, le bouton affiche l’état de position du volet par l’image (arrondi a 25%):
Templates des volets
################################################################
# Covers
################################################################
################## Global Cover ################
cover_circle:
custom_fields:
circle_cover: >
[[[
const position = Math.round(entity.attributes.current_position);
const radius = 20.5;
const circumference = radius * 2 * Math.PI;
return `<svg viewBox="0 0 50 50"><circle cx="25" cy="25" r="${radius}" stroke="#b2b2b2" stroke-width="1.5" fill="none" style=" transform:rotate(-90deg); transform-origin: 50% 50%; stroke-dasharray: ${circumference};
stroke-dashoffset: ${circumference - position / 100 * circumference};" /> <text x="50%" y="54%" fill="#8d8e90" font-size="14" text-anchor="middle" alignment-baseline="middle">${position}<tspan font-size="10">%</tspan></text></svg>`;
]]]
cover_icon:
styles:
custom_fields:
icon_cover:
[top: 20%, left: 10%, width: 50px, position: absolute]
custom_fields:
icon_cover: >
[[[
const position = Math.round(entity.attributes.current_position);
if (position < 5) {return `<svg viewBox="0 0 50 50"><path d="M3 4H21V8H19V20H17V8H7V20H5V8H3V4M8 9H16V11H8V9M8 12H16V14H8V12M8 15H16V17H8V15M8 18H16V20H8V18Z" fill="#8d8e90"/></svg>`;}
else if ((position > 5) && (position < 35)) {return `<svg viewBox="0 0 50 50"><path d="M 3 4 L 21 4 L 21 8 L 19 8 L 19 20 L 17 20 L 17 8 L 7 8 L 7 20 L 5 20 L 5 8 L 3 8 L 3 4 M 8 9 L 16 9 L 16 11 L 8 11 L 8 9 M 8 12 L 16 12 L 16 14 L 8 14 L 8 12 M 8 15 L 16 15 L 16 17 L 8 17 L 8 15" fill="#8d8e90"/></svg>`;}
else if ((position >= 35) && (position < 65)) {return `<svg viewBox="0 0 50 50"><path d="M 3 4 L 21 4 L 21 8 L 19 8 L 19 20 L 17 20 L 17 8 L 7 8 L 7 20 L 5 20 L 5 8 L 3 8 L 3 4 M 8 9 L 16 9 L 16 11 L 8 11 L 8 9 M 8 12 L 16 12 L 16 14 L 8 14 L 8 12" fill="#8d8e90"/></svg>`;}
else if ((position >= 65) && (position < 95)) {return `<svg viewBox="0 0 50 50"><path d="M 3 4 L 21 4 L 21 8 L 19 8 L 19 20 L 17 20 L 17 8 L 7 8 L 7 20 L 5 20 L 5 8 L 3 8 L 3 4 M 8 9 L 16 9 L 16 11 L 8 11 L 8 9" fill="#8d8e90"/></svg>`;}
else {return `<svg viewBox="0 0 50 50"><path d="M 3 4 L 21 4 L 21 8 L 19 8 L 19 20 L 17 20 L 17 8 L 7 8 L 7 20 L 5 20 L 5 8 L 3 8 L 3 4" fill="#8d8e90"/></svg>`;}
]]]
cover:
template:
- base
- cover_circle
- cover_icon
styles:
custom_fields:
cover_icon:
[top: 18%, left: 6%, width: 40%, position: absolute]
circle_cover:
[top: 10%, left: 50%, width: 45%, position: absolute, letter-spacing: 0px]
cover_map:
template:
- base_map
- base_forced_on
- cover_icon
show_name: false
styles:
custom_fields:
icon_cover:
[top: 20%, left: 20%, width: 120%, position: absolute]
Exemple de déclaration de volet
- type: 'custom:button-card'
entity: cover.salon
template: cover_map
style:
top: 18%
left: 95%
width: 13%
puis au clic j’accède aux actions, et au positionnement exacte.
Pour ce faire, j’ai donc défini la popup card correspondante:
Popup
cover.salon:
title: Salon
card:
type: 'custom:shutter-card'
entities:
- cover.salon
style: |
.sc-shutter-position, .sc-shutter-label {
display: none !important;
}
Lumières:
De base, le bouton affiche l’icone de l’ampoule, avec une couleur différente en fonction de si l’ampoule est allumée ou pas, et la couleur le cas échéant.
Exemple de déclaration d'une lumière
- type: 'custom:button-card'
entity: light.salon
template:
- light_map
style:
top: 7%
left: 95%
width: 13%
Templates utilisés
light_map:
template:
- base_map
- light_icon
styles:
custom_fields:
icon_hue:
[top: 20%, left: 27%, width: 46%, position: absolute]
light_icon:
custom_fields:
icon_hue: >
[[[
const state = entity.state === 'on' ? 'animate' : null;
return `
<svg viewBox="0 0 50 50">
<style>@keyframes animate{0%{transform: scale(0.85);}20%{transform: scale(1.1);}40%{transform: scale(0.95);}60%{transform: scale(1.03);}80%{transform: scale(0.97);}100%{transform: scale(1);}}.animate{animation: animate 0.8s; transform-origin: center;}</style>
<path fill="rgba(180, 180, 180)" d="M 29.147 48.105 L 21.281 48.105 C 21.281 48.105 20.157 48.173 20.157 48.629 C 20.157 49.087 20.801 49.218 21.281 49.218 L 29.147 49.218 C 29.629 49.218 30.271 49.152 30.271 48.629 C 30.271 48.105 29.147 48.105 29.147 48.105 Z M 34.446 46.209 L 16.144 46.209 C 16.144 46.209 14.857 46.209 14.857 46.733 C 14.857 47.256 15.822 47.319 16.144 47.319 L 34.606 47.319 C 34.927 47.319 35.89 47.256 35.89 46.733 C 35.729 46.209 34.446 46.209 34.446 46.209 Z M 34.446 44.248 L 16.144 44.248 C 16.144 44.248 14.857 44.248 14.857 44.771 C 14.857 45.295 15.822 45.359 16.144 45.359 L 34.606 45.359 C 34.927 45.359 35.89 45.295 35.89 44.771 C 35.729 44.248 34.446 44.248 34.446 44.248 Z M 34.446 42.351 L 25.295 42.351 L 16.144 42.351 C 16.144 42.351 14.857 42.351 14.857 42.874 C 14.857 43.397 15.822 43.463 16.144 43.463 L 34.606 43.463 C 34.927 43.463 35.89 43.397 35.89 42.874 C 35.89 42.351 34.446 42.351 34.446 42.351 Z M 42.794 27.181 C 37.494 23.714 31.556 23.519 25.294 23.519 C 19.194 23.519 11.809 23.781 7.796 27.181 C 7.635 27.246 7.635 27.379 7.956 27.639 C 8.597 28.161 13.252 32.346 13.094 39.932 C 13.094 40.651 12.932 40.978 13.094 41.044 C 13.094 41.109 13.094 41.5 14.857 41.5 L 35.729 41.5 C 37.334 41.5 37.334 41.174 37.494 41.044 L 37.494 39.932 C 37.334 32.346 41.99 28.161 42.633 27.639 C 42.794 27.379 42.794 27.31 42.794 27.181" style=""/>
<path fill="rgba(150, 150, 150)" d="M 6.828 35.24 C 14.261 41.681 38.81 39.96 43.204 35.24 C 43.872 34.362 44.375 33.484 44.707 32.825 C 48.372 20.309 48.215 15.922 48.215 14.824 C 47.874 5.16 34.192 3.846 28.021 3.846 L 22.347 3.846 C 16.172 3.846 2.321 4.943 2.152 14.824 C 2.152 15.922 1.983 20.309 5.66 32.825 C 5.66 33.484 6.158 34.582 6.828 35.24 Z" style=""/>
<path class="${state}" fill="var(--button-card-light-color-no-temperature)" d="M 7.783 34.174 C 14.751 40.14 37.764 38.546 41.882 34.174 C 42.509 33.361 42.981 32.547 43.292 31.937 C 46.727 20.344 46.58 16.28 46.58 15.265 C 46.26 6.312 33.433 5.093 27.65 5.093 L 22.332 5.093 C 16.543 5.093 3.559 6.11 3.4 15.265 C 3.4 16.28 3.241 20.344 6.688 31.937 C 6.688 32.547 7.156 33.564 7.783 34.174 Z" style=""/>
</svg>
`; ]]]
light_circle:
custom_fields:
circle_light: >
[[[ if (entity.state === 'on' && entity.attributes.brightness) { const
brightness = Math.round(entity.attributes.brightness / 2.54); const
radius = 20.5; const circumference = radius * 2 * Math.PI; return `<svg
viewBox="0 0 50 50"><circle cx="25" cy="25" r="${radius}"
stroke="#b2b2b2" stroke-width="1.5" fill="none" style=" transform:
rotate(-90deg); transform-origin: 50% 50%; stroke-dasharray:
${circumference}; stroke-dashoffset: ${circumference - brightness / 100
* circumference};" /> <text x="50%" y="54%" fill="#8d8e90"
font-size="14" text-anchor="middle"
alignment-baseline="middle">${brightness}<tspan
font-size="10">%</tspan></text></svg>`; } ]]]
Un simple clique fait un « toggle », puis un double clic permet d’ajuster couleur et intensité si l’ampoule le permet. J’utilise pour cela la carte light-entity-card.
Exemple de déclaration de popup
light.salon:
title: Salon
card:
type: custom:light-entity-card
entity: light.salon
full_width_sliders: true
smooth_color_wheel: true
effects_list: false
Température:
Plutot que de démultiplier les boutons, les thermomètre étant normalement dans des pièces avec une lumière (eh ouai! ), j’ai juste ajouté l’info sur le bouton des lumières des pièces concernées.
Exemple de déclaration
- type: 'custom:button-card'
entity: light.wc
template:
- light_map
style:
top: 67%
left: 81%
width: 13%
custom_fields:
temperature: >
[[[ return states['sensor.temp_wc'].state + '°' ]]]
Nb: Je n’utilise pas de template particulier pour cette info car je l’ai déjà géré dans mon template de base.
Puis au double clic, en plus d’avoir les commandes « supplémentaires » des mes lumières, j’ajoute le graphe de la température concernée. Pour le graphe, j’utilise la carte mini-graph-card.
Exemple de déclaration de la popup
light.wc:
title: WC
card:
type: vertical-stack
cards:
- type: custom:light-entity-card
entity: light.wc
full_width_sliders: true
smooth_color_wheel: true
effects_list: false
- type: 'custom:mini-graph-card'
entities:
- entity: sensor.temp_wc
hours_to_show: 30
points_per_hour: 1
hour24: true
show:
name: false
icon: false
Aspirateur:
De base, le bouton affiche l’icone du robot aspirateur (qui est animé si jamais l’aspirateur est en route), puis au clic alors j’accède aux commandes de l’aspirateur
Déclaration du bouton
- type: 'custom:button-card'
entity: vacuum.roborock
show_name: false
template:
- base_map_forced_on
style:
top: 56%
left: 95%
width: 13%
styles:
card:
- background-color: 'rgba(255, 255, 255, 0.8)'
custom_fields:
my_icon:
[top: 20%, left: 20%, width: 60%, position: absolute]
custom_fields:
my_icon: >
[[[
const state = entity.state === 'cleaning' ? 'vacuum_animated' : '';
return `<svg viewBox="0 0 24 24">
<style>.vacuum_animated {animation: vacuum_animated 5s infinite; transform-origin: center;} @keyframes vacuum_animated {10% {transform: scale(1.2);} 20% {transform: scale(1);} 50% {transform: rotate(180deg);} 100% {transform:rotate(360deg);}}</style>
<path class="${state}" fill="#9da0a2" d="M12,2C14.65,2 17.19,3.06 19.07,4.93L17.65,6.35C16.15,4.85 14.12,4 12,4C9.88,4 7.84,4.84 6.35,6.35L4.93,4.93C6.81,3.06 9.35,2 12,2M3.66,6.5L5.11,7.94C4.39,9.17 4,10.57 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12C20,10.57 19.61,9.17 18.88,7.94L20.34,6.5C21.42,8.12 22,10.04 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12C2,10.04 2.58,8.12 3.66,6.5M12,6A6,6 0 0,1 18,12C18,13.59 17.37,15.12 16.24,16.24L14.83,14.83C14.08,15.58 13.06,16 12,16C10.94,16 9.92,15.58 9.17,14.83L7.76,16.24C6.63,15.12 6,13.59 6,12A6,6 0 0,1 12,6M12,8A1,1 0 0,0 11,9A1,1 0 0,0 12,10A1,1 0 0,0 13,9A1,1 0 0,0 12,8Z" />
</svg>`;
]]]
Déclaration de la popup
vacuum.roborock:
title: Aspirateur
card:
type: 'custom:xiaomi-vacuum-card'
entity: vacuum.roborock
vendor: xiaomi
image: /local/community/lovelace-xiaomi-vacuum-card/vacuum.png
name: Aspirateur
attributes:
main_brush:
label: 'Brosse Principale: '
side_brush:
label: 'Brosse Latérale: '
filter:
label: 'Filtre: '
sensor:
label: 'Capteurs: '
Version 2: ajout d’une visu en temps réel de la map de chez moi, avec possibilité de déplacer l’aspirateur à un endroit précis, et/ou de nettoyer une zone particulière:
Pour ce faire, j’ai installé les 2 cartes suivantes:
GitHub - PiotrMachowski/Home-Assistant-custom-components-Xiaomi-Cloud-Map-Extractor: This custom integration provides a way to present a live view of a map for Xiaomi (Roborock/Viomi/Roidmi/Dreame) vacuums without a need for rooting. pour créer une caméra virtuelle qui renvoie en live le plan de la maison tel que vu dans l’application Xiaomi.
GitHub - PiotrMachowski/lovelace-xiaomi-vacuum-map-card: This card provides a user-friendly way to fully control Xiaomi, Valetudo, Neato and Roomba (+ possibly other) vacuums in Home Assistant. pour afficher cette caméra, et m’en servir de base pour les actions de positionnement ou de nettoyage de zone.
VMC Double flux:
De base le bouton affiche la vitesse de ventilation, puis au clic, le détail de ventilation / commandes.
Musique (basique):
J’ai plusieurs points d’écoute de musique (paramétrées en squeezebox), de base le bouton affiche l’icone de musique, avec une petite animation si il y a une lecture en cours, puis en cliquant j’accède a la lecture en cours, la bibliothèque…
avec notamment la possibilité de synchro des enceintes
Médias du salon (et la ça se complique un peu):
En fait j’ai une télécommande harmony, avec plusieurs activités (comme TV, SmartTV, musique ou encore Cinéma avec le projecteur). De base, le bouton affiche une icone en fonction de l’activité on cours, puis une animation si il y a une lecture. Puis lors d’un clic, le contenu dépend de l’activité.
Si je suis en mode musique, alors j’affiche les mêmes infos que pour les enceintes connectées, avec en plus le volume de l’ampli.
En mode TV, j’affiche le player de la TV
En mode SmartTV, j’affiche le player de la TV, mais aussi celui de ma NvidiaShield, et celui de l’ampli
En mode Ciné, j’affiche le player de ma Shield, celui de l’ampli, les commandes du vidéoprojecteur, et de l’écran du vidéoprojecteur.
Puis en cas de besoin, si je clique sur l’icone de ma télécommande, j’accède aux commandes principales/importantes des différents appareils média (notamment en cas de dysfonctionnement de quelque chose)
Bandeau global par étage:
Pour les lumières et volets, c’est plutot simple, j’ai des commandes groupées pour l’ensemble d’un niveau (j’ai d’ailleurs fait de même pour certaines « grandes » pièces, exemple pour fermer tous les volets de la pièce de vie.
Pour le chauffage, j’ai un plancher chauffant hydrolique, avec 3 zones distinctes (chaque zone pilotée par une vanne mélangeuse que je peux ouvrir/fermer a un % particulier).
Les infos/commandes sont donc dispos après un clic sur le bouton de température du niveau complet.Plus de détails sur la gestion du thermostat et de la V3V: Suggestions pour piloter un plancher chauffant hydrolique (via PAC) - #6 par lilian76
Monitoring:
Version 1
Dans ma page d’accueil, j’ai une tuile qui affiche l’ensemble des appareils que je souhaite monitorer.
Un premier bouton me donne une visu sur la dispo / indispo des appareils monitorés, puis pour les appareils de type « système » (VM, Nuc, Nas…), j’ai un bouton dédié donant des infos complémentaires (CPU / RAM/ HDD).
Si vous souhaitez plus de détails, vous pouvez consulter: Avis / Retour d’expériences pour une vue de monitoring / santé
Version 2
Mon monitoring version desktop me permet une vision globale de l’ensemble du système:
Toutes les catégories dans « devices » déroulent le détail par appareil afin d’identifier les éventuels problèmes (par exemple appareil plus accessible, ou faible niveau de batterie):
En complément, chaque « tuile » (ce sont des button cards), que ce soit une machine, une VM ou encore n’importe quel appareil permettent de naviguer vers l’url la plus adaptées (DSM, interface proxmox, page web des shelly ou encore des esp…), ce qui me permet de m’en servir un peu comme page générale d’accès.
Petite astuce:
J’ai pas mal galéré a trouver une solution pour mettre des « button card » sous les « fold-entity-row » car a la base c’est surtout pour des entity cards. J’ai bien tenté « collapsable-cards », mais le résultat était approximatif, surtout graphiquement (beaucoup de bidouillages à faire sur les style pour avoir un rendu propre), et au final la solution est bien de garder des fold-entity-row, mais qui utilise pour son contenu (en head, et en entities), des « decluttering-cards ».
Un bout de code vaut mieux qu’un long discours :):
Exemple de ligne de device
# ------- Bluetooth
- type: custom:fold-entity-row
style: |
#items {
padding-left: 0px !important;
margin-top: 6px !important;
}
#head {
height: 29px;
font-size: 12px;
}
head:
type: custom:decluttering-card
template: title_tile
variables:
- entity: binary_sensor.devices_bluetooth
- ok: sensor.devices_bluetooth_on
- ko: sensor.devices_bluetooth_off
- low_battery: sensor.devices_bluetooth_low_battery
- name: Bluetooth
- ok_title: 'Ok'
- ko_title: 'Ko'
- battery_title: 'Low Battery'
entities:
- type: custom:decluttering-card
template: bluetooth_grid
Puis les déclarations decluttering associées
decluttering_templates:
#------ title_tile
title_tile:
default:
- ok_title: ' '
- ko_title: ' '
- battery_title: ' '
card:
type: custom:multiple-entity-row
entity: '[[entity]]'
show_state: false
name: '[[name]]'
entities:
- entity: '[[low_battery]]'
name: '[[battery_title]]'
styles:
width: 55px
text-align: center
- entity: '[[ok]]'
name: '[[ok_title]]'
styles:
width: 50px
text-align: center
- entity: '[[ko]]'
name: '[[ko_title]]'
styles:
width: 50px
text-align: center
#------ bluetooth_grid
bluetooth_grid:
card:
type: grid
square: false
columns: 4
cards:
- type: custom:decluttering-card
template: device_tile
variables:
- entity: binary_sensor.bt_thermo_sdb2
- type: custom:decluttering-card
template: device_tile
variables:
- entity: binary_sensor.bt_thermo_cuisine
- type: custom:decluttering-card
template: device_tile
variables:
- entity: binary_sensor.bt_thermo_bureau
- type: custom:decluttering-card
template: device_tile
variables:
- entity: binary_sensor.bt_thermo_lywsd03_1
- type: custom:decluttering-card
template: device_tile
variables:
- entity: binary_sensor.bt_thermo_lywsd03_2
#------ device_tile
device_tile:
default:
- entity: ''
card:
type: custom:button-card
entity: '[[entity]]'
aspect_ratio: 3/2
name: |
[[[
return states['[[entity]]'].attributes.friendly_name
]]]
tap_action:
action: url
url_path: |
[[[ return states['[[entity]]'].attributes.url ]]]
styles:
card:
- border-radius: 10px
- padding: 10%
- font-size: 10px
- text-shadow: 0px 0px 5px black
grid:
- grid-template-areas: '"i info " "n n" "ip ip"'
- grid-template-columns: 60% 40%
- grid-template-rows: 70% 25% 25%
name:
- font-weight: bold
- font-size: 12px
- color: white
- align-self: middle
- padding-bottom: 4px
img_cell:
- justify-content: middle
- align-items: start
- margin: none
icon:
- color: |
[[[
if (states['[[entity]]'].state == 'on')
{return 'deepskyblue';}
else
{return 'red';}
]]]
- margin-top: '-10%'
custom_fields:
ip:
- text-align: middle
- font-size: 12px
info:
- self-align: start
- padding-bottom: 20px
- font-size: 12px
custom_fields:
ip: |
[[[
var ip = states['[[entity]]'].attributes.ip;
if (typeof ip === 'string') {
return `<p>
<span style="color: var(--text-color-sensor);">${states['[[entity]]'].attributes.ip}</span>
</p>`;}
]]]
info: |
[[[
var battery = states['[[entity]]'].attributes.battery;
var rssi = states['[[entity]]'].attributes.rssi;
if (typeof battery === 'string') {
var battery_lvl = states[battery].state;
if (battery_lvl < 15) {var bat_color = 'red';} else {var bat_color = 'deepskyblue';}
var result =
`<p>
<ha-icon icon="mdi:battery" style="width:16px; color:${bat_color};"></ha-icon>
</br><span style="color: var(--text-color-sensor);">${states[battery].state}%</span></span></br>
</p>`;
} else if (typeof rssi === "string") {
var result =
`<p>
<ha-icon icon="mdi:wifi" style="width:16px; color: grey;"></ha-icon>
</br><span style="color: var(--text-color-sensor);">${states[rssi].state}dBm</span></span></br>
</p>`;}
return result;
]]]
N’hésitez pas si vous avez des questions, ou même des suggestions d’amélioration!