sMug@replicatorbe
~/Publications/Domotique/transformer-un-shelly-en-passerelle-ble-pour-mes-tile

Transformer un Shelly en passerelle BLE pour mes Tile

Domotique / /6 min de lecture/mis à jour le 19 septembre 2026

Un Shelly 1 Mini Gen3 déjà au mur, des trackers Tile qui parlent Bluetooth, un plugin Jeedom qui attend du MQTT : trois briques qui marchent très bien chacune de leur côté et refusent de se parler. Plutôt que de modifier Jeedom, j'ai appris au Shelly à parler le dialecte qu'il connaissait déjà.

J'aime bien les problèmes où plusieurs technologies fonctionnent parfaitement chacune de leur côté, mais refusent de se parler entre elles. C'est exactement ce qui m'est arrivé avec mes trackers Tile, un Shelly 1 Mini Gen3 et mon installation Jeedom.

L'objectif de départ tenait en une phrase : utiliser un petit Shelly déjà présent dans la maison pour scanner les périphériques Bluetooth Low Energy et faire remonter les Tile dans Jeedom via MQTT.

Sur le papier, rien de très compliqué. En pratique, il fallait comprendre ce que le scanner BLE du Shelly remontait réellement, identifier le protocole des Tile, puis faire en sorte que mon plugin MQTT reconnaisse le Shelly comme une véritable passerelle BLE.

Émuler plutôt que modifier

J'utilise déjà dans Jeedom mon propre plugin de détection MQTT, basé sur la logique d'OpenMQTTGateway. L'idée était donc de ne pas le réinventer pour l'occasion.

Note ajoutée en 2026 : ce plugin est depuis publié, jeedom-plugin-mqttbe ↗. Le lien n'existait pas au moment où j'ai écrit ce billet, je l'ajoute pour que la référence soit vérifiable.

Au lieu de modifier le plugin pour accepter un nouveau type de passerelle, j'ai regardé ce qu'il attendait comme données et j'ai fait parler le Shelly dans le même langage.

C'est une approche que j'aime bien : quand deux systèmes ne sont pas compatibles, avant de modifier les deux, regarder si l'un peut simplement émuler suffisamment le comportement de l'autre.

Faire parler le Shelly en BLE

Le Shelly 1 Mini Gen3 dispose d'un scanner BLE accessible depuis son environnement de scripting. La première difficulté a été la version du firmware : la documentation et les exemples trouvés en ligne ne correspondent pas toujours à ce qui est réellement disponible sur la version installée. Sur le mien, l'appel fonctionnel est :

BLE.Scanner.Start(...)

et non l'une des autres formes que l'on croise dans les exemples en ligne. Même chose côté langage : le moteur de script est un Espruino modifié, et plusieurs fonctions JavaScript classiques n'y sont tout simplement pas. Ça paraît anecdotique, mais c'est le genre de détail qui transforme un script de dix lignes en une petite séance de debugging.

Identifier les Tile

Les Tile annoncent le service Bluetooth FEED. Le scanner du Shelly permet donc de filtrer directement dessus :

let tileData = result.service_data["feed"];

De là je récupère l'adresse MAC, le RSSI et les données du service, que je convertis en hexadécimal. Cette conversion mérite un mot : je ne pouvais pas simplement faire un JSON.stringify() de tout ce qui arrive, parce que ces données sont binaires et que leur traitement direct provoque des problèmes d'encodage. D'où une conversion volontairement bête — un octet, deux caractères :

function toHex(data) {
    if (data === undefined || data === null) {
        return "";
    }

    let result = "";

    for (let i = 0; i < data.length; i++) {
        let h = data.charCodeAt(i).toString(16);

        if (h.length === 1) {
            h = "0" + h;
        }

        result = result + h;
    }

    return result.toUpperCase();
}

Ce qu'attend Jeedom

Restait le plus intéressant : faire croire à mon plugin que le Shelly était une passerelle compatible. Comme il s'inspire d'OpenMQTTGateway, il attend deux topics :

Mini1G3Cuisine/SYStoMQTT
Mini1G3Cuisine/BTtoMQTT/<MAC>

Le premier annonce la passerelle, le second transporte les périphériques BLE détectés. Le chemin complet tient en trois cases :

   Tile  ──BLE──▶  Shelly 1 Mini Gen3  ──MQTT──▶  Jeedom

Pour une Tile, je construis un JSON calqué sur ce que le plugin sait lire :

{
  "id": "d3:46:4c:e6:95:4b",
  "rssi": -81,
  "servicedatauuid": "0xfeed",
  "servicedata": "02006A6CC331FE86EFCC",
  "brand": "Tile",
  "model": "Smart Tracker",
  "model_id": "TILE",
  "type": "TRACK",
  "device": "Tile Tracker"
}

Et pour la passerelle elle-même, un SYStoMQTT publié en retain avec l'identité du Shelly : MAC, version de firmware, uptime lu dans Shelly.getComponentStatus("sys").

C'est là que le test devient satisfaisant. Après publication de ce message, le Shelly est apparu tout seul dans Jeedom comme passerelle. Aucune modification côté plugin, aucun traitement manuel : le Shelly parle simplement un format déjà connu.

Le script complet

let MQTT_BASE = "Mini1G3Cuisine";

let DEVICE = Shelly.getDeviceInfo();

function toHex(data) {
    if (data === undefined || data === null) {
        return "";
    }

    let result = "";

    for (let i = 0; i < data.length; i++) {
        let h = data.charCodeAt(i).toString(16);

        if (h.length === 1) {
            h = "0" + h;
        }

        result = result + h;
    }

    return result.toUpperCase();
}

function cleanMac(mac) {
    let result = "";

    for (let i = 0; i < mac.length; i++) {
        if (mac.charAt(i) !== ":") {
            result = result + mac.charAt(i);
        }
    }

    return result.toUpperCase();
}

function publishGateway() {

    if (!MQTT.isConnected()) {
        print("MQTT not connected");
        return;
    }

    let msg = {
        mac: DEVICE.mac,
        ip: "",
        version: DEVICE.ver,
        env: "Shelly",
        uptime: 0
    };

    let sysStatus = Shelly.getComponentStatus("sys");

    if (sysStatus !== null) {
        if (sysStatus.uptime !== undefined) {
            msg.uptime = sysStatus.uptime;
        }
    }

    let json = JSON.stringify(msg);

    print("GATEWAY:");
    print(json);

    MQTT.publish(
        MQTT_BASE + "/SYStoMQTT",
        json,
        0,
        true
    );
}

function publishTile(result, tileData) {

    let mac = result.addr;

    let msg = {
        id: mac,
        rssi: result.rssi,
        servicedatauuid: "0xfeed",
        servicedata: toHex(tileData),
        brand: "Tile",
        model: "Smart Tracker",
        model_id: "TILE",
        type: "TRACK",
        device: "Tile Tracker"
    };

    let json = JSON.stringify(msg);

    print("TILE:");
    print(json);

    if (MQTT.isConnected()) {
        MQTT.publish(
            MQTT_BASE + "/BTtoMQTT/" + cleanMac(mac),
            json
        );
    }
}

function onScan(event, result) {

    if (event !== BLE.Scanner.SCAN_RESULT) {
        return;
    }

    if (result.service_data === undefined ||
        result.service_data === null) {
        return;
    }

    let tileData = result.service_data["feed"];

    if (tileData === undefined) {
        return;
    }

    publishTile(result, tileData);
}

print("================================");
print("Shelly OpenMQTTGateway emulator");
print("Name : " + DEVICE.name);
print("MAC  : " + DEVICE.mac);
print("Model: " + DEVICE.model);
print("FW   : " + DEVICE.ver);
print("Base : " + MQTT_BASE);
print("================================");

// Découverte de la passerelle
publishGateway();

// Scan BLE
BLE.Scanner.Start(
    {
        duration_ms: BLE.Scanner.INFINITE_SCAN,
        active: false,
        interval_ms: 240,
        window_ms: 80
    },
    onScan
);

print("Tile scanner started");

Et après ?

Le plus intéressant n'est pas de complexifier le script : il fonctionne. Le Shelly scanne les Tile, les informations remontent en MQTT, Jeedom reconnaît la passerelle.

La suite logique, c'est le RSSI. Il donne déjà une indication de proximité entre le tracker et la passerelle ; avec plusieurs Shelly qui voient la même Tile, comparer les niveaux permet de deviner dans quelle zone elle se trouve.

                 Tile
                  │
        ┌─────────┼─────────┐
        ▼         ▼         ▼
     Shelly 1  Shelly 2  Shelly 3
        └─────────┼─────────┘
                MQTT
                  │
                  ▼
               Jeedom  →  présence / zone

On passerait alors d'un simple BLE → MQTT à une petite localisation distribuée. C'est aussi ce que j'aime dans ce genre de projet : au départ je voulais juste récupérer mes Tile dans Jeedom, et en regardant un peu plus loin on se retrouve avec du BLE, du MQTT, de l'embarqué, de l'analyse de trames et de l'automatisation.

Le tout sur un petit Shelly qui était déjà installé dans la maison.