Il y a un mois, je terminais le billet sur le Shelly transformé en passerelle BLE par une idée : avec plusieurs passerelles qui voient le même Tile, on doit pouvoir savoir où il se trouve. Je l'ai fait. Pas encore la pièce exacte, mais déjà la question qui compte le plus dans une maison : qui est là, et qui est parti ?
Deux changements par rapport au Shelly. Les passerelles sont maintenant trois ESP32 sous OpenMQTTGateway ↗, une à l'entrée, une à l'étage, une dans la salle à manger. Et c'est Home Assistant qui reçoit le tout. Il tourne depuis quelques semaines à côté de Jeedom, pour voir, et la présence m'a semblé un bon premier vrai sujet pour le mettre à l'épreuve.
Le principe
Un Tile émet en permanence une petite annonce Bluetooth, avec son adresse MAC. Chaque ESP32 capte ces annonces et publie en MQTT ce qu'il a entendu, avec la puissance du signal (le RSSI) :
bt/OMG_ESP32_BLE_ENTREE/BTtoMQTT/D01122334455
{"id":"D0:11:22:33:44:55","rssi":-82,"brand":"Tile","model":"Smart Tracker"}
(Les adresses de ce billet sont bidon, pour des raisons qui deviendront claires à la fin.)
À partir de là, la chaîne dans Home Assistant :
Tile ─BLE─▶ 3 × ESP32 ─MQTT─▶ 3 capteurs RSSI
│
▼
binary_sensor « Présence »
│ automatisation
▼
MQTT home / not_home (retain)
│
▼
device_tracker ─▶ person
Ça fait beaucoup d'étages pour une information binaire. J'y reviens.
Les trois capteurs
Un capteur par ESP32 et par Tile, créé par MQTT Discovery : je publie un message de configuration sur un topic homeassistant/... et l'entité apparaît toute seule. Pour l'entrée :
{
"name": "RSSI Jérôme Entrée",
"state_topic": "bt/OMG_ESP32_BLE_ENTREE/BTtoMQTT/D01122334455",
"value_template": "{{ value_json.rssi }}",
"unit_of_measurement": "dBm",
"device_class": "signal_strength",
"expire_after": 180,
"unique_id": "rssi_jerome_entree",
"device": { "identifiers": ["tile_jerome"], "name": "Tile Jérôme" }
}
Même chose pour l'étage et la salle à manger, seul le nom de la passerelle change dans le topic. Retenez la ligne expire_after, c'est le premier piège.
Combiner : au moins une antenne me voit
Un capteur template fait la synthèse. Un capteur sans valeur vaut -200, et il suffit qu'une seule des trois antennes me capte au-dessus de -100 dBm :
template:
- binary_sensor:
- name: "Présence Jérôme"
device_class: presence
state: >
{% set r1 = states('sensor.rssi_jerome_entree') | float(-200) %}
{% set r2 = states('sensor.rssi_jerome_etage') | float(-200) %}
{% set r3 = states('sensor.rssi_jerome_sam') | float(-200) %}
{{ [r1, r2, r3] | max > -100 }}
Pour l'instant je ne me sers que du maximum. La comparaison entre les trois, pour deviner la pièce, viendra plus tard. Chaque chose en son temps.
Pourquoi ce détour par MQTT
C'est ici que le schéma paraît absurde. J'ai un binary_sensor qui dit on ou off : pourquoi ne pas l'attacher directement à ma personne dans Home Assistant ?
Parce que l'entité person n'accepte que des device_tracker. Pas de capteur binaire, pas de template. Il faut donc fabriquer un tracker. Le plus simple que j'ai trouvé : une automatisation qui republie l'état en MQTT, et un tracker MQTT qui l'écoute.
- alias: "Sync présence Jérôme vers MQTT"
mode: restart
trigger:
- platform: state
entity_id: binary_sensor.presence_jerome
action:
- service: mqtt.publish
data:
topic: "home/persons/jerome"
payload_template: >
{{ 'home' if is_state('binary_sensor.presence_jerome', 'on') else 'not_home' }}
qos: 1
retain: true
{
"name": "Présence Jérôme (MQTT)",
"state_topic": "home/persons/jerome",
"payload_home": "home",
"payload_not_home": "not_home",
"source_type": "bluetooth",
"unique_id": "jerome_tracker"
}
Le retain: true a un bon effet de bord : au redémarrage de Home Assistant, le broker lui rend le dernier état connu, et personne ne repasse en « inconnu ».
Il reste à lier ce tracker à la personne, dans Paramètres → Personnes. Ajouter les autres membres de la maison revient ensuite à recopier la recette avec une autre adresse MAC. Je me suis écrit une procédure pas à pas pour ça, parce que dans six mois je n'aurai plus rien retenu.
Les trois pièges
Rien de tout ça n'est compliqué. Mais j'ai perdu plus de temps sur ces trois points que sur tout le reste.
Je sors, et la maison me croit toujours là. Quand un Tile disparaît, les ESP32 ne publient pas « il n'est plus là » : ils ne publient plus rien. Et un capteur MQTT sans nouvelle garde sa dernière valeur, indéfiniment. -78 dBm, figé, pendant que je suis au travail. C'est le rôle d'expire_after : sans message pendant 180 secondes, le capteur passe en « indisponible », le template le lit comme -200, et tout redescend en cascade. L'erreur est bête, et c'est justement pour ça qu'elle passe : tant qu'on teste à la maison, tout fonctionne.
Une personne bloquée en « inconnu ». Tout était configuré, le capteur binaire était correct, et la personne restait à unknown. Le tracker n'avait simplement jamais reçu de message, puisque l'automatisation ne se déclenche que sur un changement d'état. Une publication manuelle de home sur le topic, depuis MQTT Explorer, a suffi à l'amorcer. Autre variante du même problème : le capteur à on, la personne à not_home. Même diagnostic, l'automatisation n'avait pas tourné, un automation.trigger a remis tout le monde d'accord.
Un Tile localisé « par GPS ». Sans source_type dans le message de découverte, Home Assistant considère que le tracker est un GPS. Ça ne casse rien de visible, mais c'est faux, et ça change la façon dont il traite les zones. Une ligne à ajouter, "source_type": "bluetooth".
Au passage, un capteur combiné « quelqu'un est à la maison » ne coûte plus rien une fois les personnes en place : un or entre leurs états. C'est lui qui sert réellement dans les automatisations.
Ce que mes antennes voient d'autre
C'est la partie qui m'a le plus occupé, alors qu'elle n'était pas prévue.
Tout ce montage fonctionne pour une raison simple : l'adresse de mon Tile ne change pas. C'est elle que je filtre dans chaque topic, et elle est la même aujourd'hui qu'il y a un mois. Or un ESP32 en écoute passive n'a besoin de rien pour l'entendre : ni appairage, ni mot de passe, ni application. Il capte ce qui passe, et en ouvrant MQTT Explorer on s'en rend compte vite : il n'y a pas que nos Tile dans la liste. Des montres, des écouteurs, des téléphones, des appareils qui ne sont pas à nous et qui passent dans la rue.
Retourné, le raisonnement est moins amusant. Si trois ESP32 à quelques euros savent à quelle heure je quitte la maison, n'importe qui en posant un près de ma porte peut le savoir aussi, et tenir le relevé pendant des semaines. Il n'a même pas besoin de savoir que c'est moi : une adresse qui apparaît tous les matins à 7h40 et revient vers 18h raconte déjà une vie. Il suffit de la croiser avec une seule autre information pour mettre un nom dessus.
Le sens inverse existe aussi, et il est pire : un tracker glissé dans le sac de quelqu'un, précisément pour le suivre. Ce n'est pas théorique, et c'est pour ça que les fabricants ont fini par ajouter de quoi détecter un tracker inconnu qui voyage avec soi — Tile a sa fonction « Scan and Secure » dans l'application.
Je n'en tire pas de conclusion dramatique, j'ai gardé mes Tile. Mais on ne regarde plus pareil un objet qui annonce son identifiant en permanence, quand on vient de construire soi-même le système qui l'écoute. C'est le genre de chose que je préfère avoir comprise en la montant qu'en la lisant.
Ce que je retiens
- La brique
personn'accepte que desdevice_tracker: prévoir le détour dès le départ plutôt que de le découvrir. - En MQTT, l'absence de message n'est pas un message. Sans
expire_after, un capteur ne sait pas mourir. retain: truesur les états qui doivent survivre à un redémarrage.- Une automatisation qui ne réagit qu'aux changements a besoin d'un premier état pour démarrer.
- Un identifiant Bluetooth stable, c'est pratique pour moi et pour n'importe qui d'autre. Les deux vont ensemble.
La suite, c'est la pièce : comparer les trois RSSI pour savoir si je suis en bas ou à l'étage. Là, les choses se compliquent, parce qu'un corps humain entre l'antenne et le Tile suffit à faire perdre plusieurs décibels.