Shelly Gen2 et scripts dont antenne bluetooth pour Nut et modules "beacon compatible"

Hello Jeandhom,

Le plugin est déjà super complex, je ne pense pas que traiter des payloads BT soit son but.

Pour moi ce serait plus le travail d’un docker, qui ferait tourner theengs/decoder afin de prendre les payloads BT en MQTT et les renvoyer en MQTT, pour être ensuite intégrés dans des eq/cmd avec jMQTT.

Ca pourrait aussi être un plugin à part s’appuyant sur MQTT Manager et créant directement les équipements dans Jeedom.

Désolé,
Bad

2 « J'aime »

Salut Bad,
Merci pour ton expertise.

Je n’ai qu’un seul shelly PLUS en service, il est au RDC.
Malgré qu’il capte bien le Nut situé au sous-sol, je projette de remplacer le shelly 1 de la porte de garage situé aussi au sous-sol par un shelly PLUS 1.
Afin de savoir qui a remonté l’information affichée par le widget, j’ai modifié le script.

let origine = JSON.stringify(Shelly.getDeviceInfo().name);
function scanCB(ev, res) {
  if (ev === BLE.Scanner.SCAN_RESULT) {
    if (res.local_name === 'nut') {
      let topic = 'shellies/script/bluetooth/nut/' + res.addr + '/';
      let unixtime = JSON.stringify(Shelly.getComponentStatus("sys").unixtime);
      if(MQTT.isConnected()) {
        MQTT.publish(topic + 'rssi', JSON.stringify(res.rssi), 0, false);
        MQTT.publish(topic + 'unixtime', unixtime, 0, false);
        MQTT.publish(topic + 'presence', unixtime, 0, false);
        MQTT.publish(topic + 'origine', origine, 0, false);
      }
    }
  }
}

BLE.Scanner.Start({ duration_ms: -1}, scanCB);

Capture d’écran du 2023-03-03 21-56-46

Après de l’autoformation en JS et puis plus particulièrement en mJS, je vous propose une amélioration de ce script.
L’ancien script a besoin de Jeedom via un scénario pour détecter l’absence d’un Nut.
Maintenant, c’est le script, lui même, qui signale directement au broker l’absence d’un Nut.

Pour ce faire, j’ai ajouté deux modules.

  • le premier sert à connaître, via le broker, le dernier timestamp de présence de chaque Nut.
  • le deuxième est un timer qui surveille, toutes les dix secondes, si chaque Nut n’a pas signalé sa présence depuis les quatre-vingt-dix dernières secondes, puis le publie sur le broker.

EDIT - La fonction scanCB semble, rarement, renvoyer une adresse MAC non valide. J’ajoute au script une vérification de cette adresse.

let origine = JSON.stringify(Shelly.getDeviceInfo().name);
let genericTopic = "shellies/script/bluetooth/nut/";
let nuts = [];
let callback = [genericTopic, nuts];

function isMacValid(mac) {
  if (mac.length !== 17) {
    return false;
  }
  for (let i=0; i<17; i++) {
    if (i%3 === 2) {
      if (mac.at(i) !== 0x3a) { //0x3a = :
        return false;
      }
    } else {
      //0x30 = 0, 0x39 = 9, 0x61 = a et 0x66 = f
      if (mac.at(i) < 0x30 || (mac.at(i) > 0x39 && mac.at(i) < 0x61) || mac.at(i) > 0x66) {
        return false;
      }
    }
  }
  return true;
}

MQTT.subscribe(
  genericTopic + "#",
  function (topic, message, callback) {
    let endTopic = topic.slice(callback[0].length + 18);
    if (endTopic === "unixtime") {
      let mac = topic.slice(callback[0].length, callback[0].length + 17);
      let existePas = 1;
      for (let i=0; i<callback[1].length; i++) {
        if (callback[1][i].mac === mac) {
          callback[1][i].timePublish = JSON.parse(message);
          existePas = 0;
        }
      }
      if (existePas === 1 && isMacValid(mac)) {
        callback[1].push({mac: mac, timePublish: JSON.parse(message)});
      }
    }
  },
  callback
);

Timer.set(
  10000,
  true,
  function (callback) {
    for (let i=0; i<callback[1].length; i++) {
      if (callback[1][i].timePublish + 90 <= Shelly.getComponentStatus("sys").unixtime) {
        if(MQTT.isConnected()) {
          MQTT.publish(callback[0] + callback[1][i].mac + '/presence', "0", 0, false);
        }
      }
    }
  },
  callback
);

function scanCB(ev, res, genericTopic) {
  if (ev === BLE.Scanner.SCAN_RESULT) {
   if (res.local_name === 'nut' && isMacValid(res.addr)) {
      let topic = genericTopic + res.addr + '/';
      let unixtime = JSON.stringify(Shelly.getComponentStatus("sys").unixtime);
      if(MQTT.isConnected()) {
        MQTT.publish(topic + 'rssi', JSON.stringify(res.rssi), 0, false);
        MQTT.publish(topic + 'unixtime', unixtime, 0, false);
        MQTT.publish(topic + 'presence', "1", 0, false);
        MQTT.publish(topic + 'origine', origine, 0, false);
      }
    }
  }
}

BLE.Scanner.Start({ duration_ms: -1}, scanCB, genericTopic);
2 « J'aime »

Ajout d’une commande info par Shelly permettant de connaître le nombre de Nuts présent dans sa zone de réception.

Capture d’écran du 2023-05-22 19-13-28

let origine = Shelly.getDeviceInfo().name;
let genericTopic = "shellies/script/scanNut/";
let nutsShelly = []; //nuts vus par ce Shelly
let nutsBroker = []; //nuts vus par le broker

if(MQTT.isConnected()) {
  MQTT.publish(genericTopic + origine + '/nombreNut', "0", 0, false);
}

function isMacValid(mac) {
  if (mac.length !== 17) {
    return false;
  }
  for (let i=0; i<17; i++) {
    if (i%3 === 2) {
      if (mac.at(i) !== 0x3a) { //0x3a = :
        return false;
      }
    } else {
      //0x30 = 0, 0x39 = 9, 0x61 = a et 0x66 = f
      if (mac.at(i) < 0x30 || (mac.at(i) > 0x39 && mac.at(i) < 0x61) || mac.at(i) > 0x66) {
        return false;
      }
    }
  }
  return true;
}

function nutAjout (mac, unixtime, nuts) {
  let existePas = 1;
  for (let i=0; i<nuts.length; i++) {
    if (nuts[i].mac === mac) {
      nuts[i].timePublish = unixtime;
      existePas = 0;
    }
  }
  if (existePas === 1 && isMacValid(mac)) {
    nuts.push({mac: mac, timePublish: unixtime});
  }
}

MQTT.subscribe(
  genericTopic + "#",
  function (topic, message, callback) {
    let endTopic = topic.slice(callback[0].length + 18);
    if (endTopic === "unixtime") {
      let mac = topic.slice(callback[0].length, callback[0].length + 17);
      nutAjout (mac, JSON.parse(message), callback[1]);
    }
  },
  [genericTopic, nutsBroker]
);

Timer.set(
  10000,
  true,
  function (callback) {
    for (let i=0; i<callback[1].length; i++) {
      if (callback[1][i].timePublish + 110 <= Shelly.getComponentStatus("sys").unixtime) {
        if(MQTT.isConnected()) {
          MQTT.publish(callback[0] + callback[1][i].mac + '/presence', "0", 0, false);
        }
      }
    }
    for (let i=0; i<callback[3].length; i++) {
      if (callback[3][i].timePublish + 220 <= Shelly.getComponentStatus("sys").unixtime) {
        callback[3].splice(i, 1);
        if(MQTT.isConnected()) {
          MQTT.publish(callback[0] + callback[2] + '/nombreNut', JSON.stringify(callback[3].length), 0, false);
        }
      }
    }
  },
  [genericTopic, nutsBroker, origine, nutsShelly]
);

function scanCB(ev, res, callback) {
  if (ev === BLE.Scanner.SCAN_RESULT) {
    if (res.local_name === 'nut' && isMacValid(res.addr)) {
      let mac = res.addr;
      let topic = callback[0] + mac + '/';
      let unixtime = Shelly.getComponentStatus("sys").unixtime;
      nutAjout (mac, unixtime, callback[1]);
      if(MQTT.isConnected()) {
        MQTT.publish(topic + 'rssi', JSON.stringify(res.rssi), 0, false);
        MQTT.publish(topic + 'unixtime', JSON.stringify(unixtime), 0, false);
        MQTT.publish(topic + 'presence', "1", 0, false);
        MQTT.publish(topic + 'origine', JSON.stringify(callback[2]), 0, false);
        MQTT.publish(callback[0] + callback[2] + '/nombreNut', JSON.stringify(callback[1].length), 0, false);
      }
    }
  }
}

BLE.Scanner.Start({ duration_ms: -1}, scanCB, [genericTopic, nutsShelly, origine]);
1 « J'aime »

Ajout de la liste des Nuts pour Chaque Shelly.

let origine = Shelly.getDeviceInfo().name;
let genericTopic = "shellies/script/scanNut/";
let nutsShelly = []; //nuts vus par ce Shelly
let nutsBroker = []; //nuts vus par le broker
let nutsName = {
                 "aa:aa:aa:aa:aa:aa": "Nut01",
                 "bb:bb:bb:bb:bb:bb": "Nut02",
                 "cc:cc:cc:cc:cc:cc": "Nut03",
                 "dd:dd:dd:dd:dd:dd": "Nut04"
               };

if(MQTT.isConnected()) {
  MQTT.publish(genericTopic + origine + '/nombreNut', "0", 0, false);
  MQTT.publish(genericTopic + origine + '/nameNut', "", 0, false);
}

function isMacValid(mac) {
  if (mac.length !== 17) {
    return false;
  }
  for (let i=0; i<17; i++) {
    if (i%3 === 2) {
      if (mac.at(i) !== 0x3a) { //0x3a = :
        return false;
      }
    } else {
      //0x30 = 0, 0x39 = 9, 0x61 = a et 0x66 = f
      if (mac.at(i) < 0x30 || (mac.at(i) > 0x39 && mac.at(i) < 0x61) || mac.at(i) > 0x66) {
        return false;
      }
    }
  }
  return true;
}

function nutList (nuts, nutsName) {
  let nutsList = "";
  for (let i=0; i<nuts.length; i++) {
    nutsList += nutsName[nuts[i].mac] + " ";
  }
  let long = nutsList.length - 1;
  nutsList = nutsList.slice(0, long);
  return nutsList;
}

function nutAjout (mac, unixtime, nuts) {
  let existePas = 1;
  for (let i=0; i<nuts.length; i++) {
    if (nuts[i].mac === mac) {
      nuts[i].timePublish = unixtime;
      existePas = 0;
    }
  }
  if (existePas === 1 && isMacValid(mac)) {
    nuts.push({mac: mac, timePublish: unixtime});
  }
}

MQTT.subscribe(
  genericTopic + "#",
  function (topic, message, callback) {
    let endTopic = topic.slice(callback[0].length + 18);
    if (endTopic === "unixtime") {
      let mac = topic.slice(callback[0].length, callback[0].length + 17);
      nutAjout (mac, JSON.parse(message), callback[1]);
    }
  },
  [genericTopic, nutsBroker]
);

Timer.set(
  10000,
  true,
  function (callback) {
    for (let i=0; i<callback[1].length; i++) {
      if (callback[1][i].timePublish + 110 <= Shelly.getComponentStatus("sys").unixtime) {
        if(MQTT.isConnected()) {
          MQTT.publish(callback[0] + callback[1][i].mac + '/presence', "0", 0, false);
        }
      }
    }
    for (let i=0; i<callback[3].length; i++) {
      if (callback[3][i].timePublish + 110 <= Shelly.getComponentStatus("sys").unixtime) {
        callback[3].splice(i, 1);
        let list = nutList (callback[3], callback[4]);
        if(MQTT.isConnected()) {
          MQTT.publish(callback[0] + callback[2] + '/nombreNut', JSON.stringify(callback[3].length), 0, false);
          MQTT.publish(callback[0] + callback[2] + '/nameNut', list, 0, false);
        }
      }
    }
  },
  [genericTopic, nutsBroker, origine, nutsShelly, nutsName]
);

function scanCB(ev, res, callback) {
  if (ev === BLE.Scanner.SCAN_RESULT) {
    if (res.local_name === 'nut' && isMacValid(res.addr)) {
      let mac = res.addr;
      let topic = callback[0] + mac + '/';
      let unixtime = Shelly.getComponentStatus("sys").unixtime;
      nutAjout (mac, unixtime, callback[1]);
      let list = nutList (callback[1], callback[3]);
      if(MQTT.isConnected()) {
        MQTT.publish(topic + 'rssi', JSON.stringify(res.rssi), 0, false);
        MQTT.publish(topic + 'unixtime', JSON.stringify(unixtime), 0, false);
        MQTT.publish(topic + 'presence', "1", 0, false);
        MQTT.publish(topic + 'origine', JSON.stringify(callback[2]), 0, false);
        MQTT.publish(callback[0] + callback[2] + '/nombreNut', JSON.stringify(callback[1].length), 0, false);
        MQTT.publish(callback[0] + callback[2] + '/nameNut', list, 0, false);
      }  
    }
  }
}

BLE.Scanner.Start({ duration_ms: -1}, scanCB, [genericTopic, nutsShelly, origine, nutsName]);

Ajout du monitoring de l’état (stop/start) du script.

Topic : topicDuShelly/status/script:1 avec [running].

Depuis la version 1.0.3 du firmware des Shelly Gen2, le script ne fonctionne plus car la fonction MQTT.subscribe() ne supporte plus les topics finissant par # (c’est gênant!).
Les développeurs d’Allterco Robotics sont informés du bug et ils sont dessus.

Salut @Jeandhom ,

Tout d’abord merci pour ce bout de script qui collait à la perfection à mon besoin ! :pray:

J’ai essayé de passer mon Shelly I4+ en version stable 1.0.7 hier. Manifestement cela n’a pas résolu le problème de calcul de la présence. A confirmer.

Cela me renvoi toujours les données principales donc je calcul la présence dans Jeedom, moindre mal. Je garde aussi sous le coude la possibilité de remonter la présence d’à peu près n’importe quel équipement bluetooth à proximité. C’est top !

Encore merci à toi pour ce partage !! :grin:

1 « J'aime »

Le support Shelly m’a confirmé que ce soucis n’était pas réglé avec le firmware 1.0.7.

EDIT: ni avec le firmware 1.0.8.

à suivre donc !
superbe idée en tous cas, merci.

1 « J'aime »

Ça semble bouger chez Shelly.

Capture d’écran du 2023-11-14 18-26-29

J’aime beaucoup ton idée d’utiliser les shelly comme des antennes de présence :wink: j’ai aucun Bluetooth car je fais ma presence avec Homebridge (évidemment ;-)) mais je salue l’effort ! En lisant la doc Shelly des scripts j’ai justement vu cette partie Bluetooth et je pensais justement à la présence, j’ai cherché et tu l’avais fait :slight_smile: gg :slight_smile:

1 « J'aime »

Le firmware 1.1.0 vient de sortir, il permet de retrouver le fonctionnement normal du script.

1 « J'aime »

Oui j’ai vu ça ! C’est top !
Fini la bidouille côté Jeedom pour remonter la présence, c’est parfait !
Merci encore pour ce script, il ferme mon portail sans problèmes tous les jours ! :wink:

1 « J'aime »

Bonjour,

Merci pour le script , est ce qu’il est possible de jouer sur la fréquence des envois MQTT par le shelly ?

Je ne sais pas, il faudrait fouiller dans la documentation pour voir si il y a quelque chose.

Super ce sujet! Ça m’intéresse beaucoup! Je vais voir aussi pour faire remonter les infos de cateurs style Windows/door sur le fil mqtt ; merci @Jeandhom !

1 « J'aime »

Merci pour le retour, j’ai bien cherché mais rien trouvé qui fonctionnait.

Autre question, tu as une astuce pour convertir dans jeedom les unixtime en datetime comme sur le widget?

Capture d’écran du 2024-02-11 11-14-36

date('d/m/Y H:i:s', #value#)

A partir de la version 4.4.