Script surveillance des appareils à batterie

EDIT 20/07/2023: Le code à été mit à jour et devrait résoudre le problème de l’erreur “Cannot read properties of undefined (reading ‘lastUpdated’)”

Bonsoir,

Je partage un script permettant de surveiller facilement les appareils sur batteries. (Modifiable pour tout autre type d’appareil)

D’abord pourquoi j’ai fais ceci? J’ai un certain nombre d’appareil sur batterie (capteurs d’ouverture, sondes, détecteur de mouvement, …) et je me rend compte, souvent longtemps après, qu’un appareil n’a plus de batterie.

J’ai beau utiliser différents flow pour surveiller mes appareils, aucun n’est vraiment fiable.

Celui ci dessous par exemple ne trouve pas les capteurs qui n’ont qu’une alarme batterie (certain ne donne pas leur pourcentage de batterie). Ou que le niveau de batterie était supérieur au seuil de 5% avant de ne plus avoir de batterie (c’est beaucoup le cas sur les sondes Aqara ou le niveau de batterie est très mal relevé).

J’utilise celui ci également. Alors lui renvoie tout et n’importe quoi, je ne sais pas ce que prend en compte la carte “Un appareil n’a pas émis de signal pendant 1 jour” mais je reçois beaucoup de fausse alerte (des sondes de température par exemple, quand on sait que la température change quand même plusieurs fois dans une journée, des lumières dont c’est sur qu’elles ont été allumées dans la journée, des volets qui sont ouvert et fermé tous les jours…)
De plus il plafonne à 1 jour, donc c’est super court, et on ne peut rien filtrer. Et on reçois 1 notif à chaque fois! Donc c’est certainement vrai pour quelques appareils mais ils sont perdu dans une foule de fausses infos.

Mon idée était donc de faire un script pour récupérer une liste d’appareils avec certains critères:

  • filtrer uniquement les appareils sur batteries
  • rechercher en premier si l’appareil n’a pas de soucis de communication
  • récupérer toutes les dates qui sont disponibles dans leur paramètres et n’en sortir que la plus récente
  • pouvoir personnaliser le temps entre maintenant et le dernier signal de l’appareil (si on veut une alerte à 1h ou 15jours sans signal, c’est possible!)
  • pouvoir ajouter des appareils à une liste blanche pour qu’il ne soit pas pris en compte
  • et surtout, tout récupérer sous une forme de liste à peu près lisible (et pas 1 notif par appareil)

Le code est donc le suivant:


let myArgs = JSON.parse(args[0]);
const hoursSinceLastUpdate = myArgs.hoursSinceLastUpdate;
let whiteList;

if (myArgs.whiteList) {
  whiteList = (myArgs.whiteList.replace(/\s/g, '')); //Get the devices on white list and remove spaces in their name (get problem with some device who have space in name)
}
else whiteList = 'null';




const devices = await Homey.devices.getDevices();// Get all devices
let nextList = "";
let previousList = "";
let deviceName;
let idActuel = Object.values(await Homey.devices.getDevices())[0].id; //Récupère l'id de Homey pour avoir un id de base 



let actualDevice;
let actualDeviceString;
let actualDeviceCapabilitiesString;
let capabilityObjString;
let deviceNameString;
let dateNow;
let batteryDate;
let temperatureDate;
let pressureDate;
let humidityDate;
let luminanceDate;
let alarmBatteryDate;
let alarmContactDate;
let alarmTamperDate;
let alarmMotionDate;
let alarmHeatDate;
let alarmSmokeDate;
let alarmWaterDate;
let dateCompare;




for (const device of Object.values(devices)) {


  dateNow = new Date();
  idActuel = device.id;
  actualDevice = await Homey.devices.getDevice({ id: idActuel });
  actualDeviceString = JSON.stringify(actualDevice);
  actualDeviceCapabilitiesString = JSON.stringify(actualDevice.capabilities);
  capabilityObjString = JSON.stringify(actualDevice.capabilitiesObj);
  deviceNameString = JSON.stringify(actualDevice.name);

  if (!whiteList.toLowerCase().includes(((actualDevice.name).toLowerCase()).replace(/\s/g, ''))) { //Check if device is on white list

    if (!actualDeviceCapabilitiesString.includes("measure_battery")) {
      continue; //if device hasn't battery, ignore it
    }

    // Search if there an error message 
    if ((JSON.stringify(actualDevice.unavailableMessage)) !== "null") {
      previousList = nextList;
      nextList = previousList + actualDevice.name + ": " + actualDevice.unavailableMessage + " ⚠️" + "\n\n";
      continue;
    }
    //search if the device has a battery. Then get all last update of the capabilities (if exist)
    if (actualDeviceCapabilitiesString.includes("measure_battery")) {
      //log(actualDevice.name);
      batteryDate = new Date(actualDevice.capabilitiesObj.measure_battery.lastUpdated);
      dateCompare = batteryDate;
      temperatureDate = 0;
      pressureDate = 0;
      humidityDate = 0;
      luminanceDate = 0;
      alarmBatteryDate = 0;
      alarmContactDate = 0;
      alarmTamperDate = 0;
      alarmMotionDate = 0;
      alarmHeatDate = 0;
      alarmSmokeDate = 0;
      alarmWaterDate = 0;

      if (capabilityObjString.includes("measure_temperature")) {
        temperatureDate = new Date(actualDevice.capabilitiesObj.measure_temperature.lastUpdated);
      }
      if (capabilityObjString.includes("measure_pressure")) {
        pressureDate = new Date(actualDevice.capabilitiesObj.measure_pressure.lastUpdated);
      }
      if (capabilityObjString.includes("measure_humidity")) {
        humidityDate = new Date(actualDevice.capabilitiesObj.measure_humidity.lastUpdated);
      }
      if (capabilityObjString.includes("alarm_battery")) {
        alarmBatteryDate = new Date(actualDevice.capabilitiesObj.alarm_battery.lastUpdated);
      }
      if (capabilityObjString.includes("alarm_contact")) {
        alarmContactDate = new Date(actualDevice.capabilitiesObj.alarm_contact.lastUpdated);
      }
      if (capabilityObjString.includes("alarm_tamper")) {
        alarmTamperDate = new Date(actualDevice.capabilitiesObj.alarm_tamper.lastUpdated);
      }
      if (capabilityObjString.includes("alarm_motion")) {
        alarmMotionDate = new Date(actualDevice.capabilitiesObj.alarm_motion.lastUpdated);
      }
      if (capabilityObjString.includes("measure_luminance")) {
        luminanceDate = new Date(actualDevice.capabilitiesObj.measure_luminance.lastUpdated);
      }
      if (capabilityObjString.includes("alarm_heat")) {
        alarmHeatDate = new Date(actualDevice.capabilitiesObj.alarm_heat.lastUpdated);
      }
      if (capabilityObjString.includes("alarm_smoke")) {
        alarmSmokeDate = new Date(actualDevice.capabilitiesObj.alarm_smoke.lastUpdated);
      }
      if (capabilityObjString.includes("alarm_water")) {
        alarmWaterDate = new Date(actualDevice.capabilitiesObj.alarm_water.lastUpdated);
      }
    }
    //compare all dates and get the most recent
    if ((dateCompare < temperatureDate) && (temperatureDate !== 0)) {
      dateCompare = temperatureDate
    }
    if ((dateCompare < pressureDate) && (pressureDate !== 0)) {
      dateCompare = pressureDate;
    }
    if ((dateCompare < humidityDate) && (humidityDate !== 0)) {
      dateCompare = humidityDate;
    }
    if ((dateCompare < luminanceDate) && (luminanceDate !== 0)) {
      dateCompare = luminanceDate;
    }
    if ((dateCompare < alarmBatteryDate) && (alarmBatteryDate !== 0)) {
      dateCompare = alarmBatteryDate;
    }
    if ((dateCompare < alarmContactDate) && (alarmContactDate !== 0)) {
      dateCompare = alarmContactDate;
    }
    if ((dateCompare < alarmHeatDate) && (alarmHeatDate !== 0)) {
      dateCompare = alarmHeatDate;
    }
    if ((dateCompare < alarmMotionDate) && (alarmMotionDate !== 0)) {
      dateCompare = alarmMotionDate;
    }
    if ((dateCompare < alarmSmokeDate) && (alarmMotionDate !== 0)) {
      dateCompare = alarmSmokeDate;
    }
    if ((dateCompare < alarmTamperDate) && (alarmTamperDate !== 0)) {
      dateCompare = alarmTamperDate;
    }
    if ((dateCompare < alarmWaterDate) && (alarmWaterDate !== 0)) {
      dateCompare = alarmWaterDate;
    }
    //chech if last update is older than we want, if it is, log the device in the list
    let difference = dateNow - dateCompare;
    if (difference > (((hoursSinceLastUpdate * 60) * 60) * 1000)) { //convert millisecond in hour
      previousList = nextList;
      nextList = previousList + actualDevice.name + ": " + dateCompare.toLocaleString("fr-FR") + "\n\n";
    }
  }

}
//create a String tag from the list
if (nextList === "") { //tag cant be empty, so if no device returned by the script write it (or what you want)
  nextList = "No device was returned by the script";
  await tag('No Signal List ', nextList);
  return false;
}
else {
  await tag('No Signal List ', nextList);
  //log(nextList);
  return true;
}

Le flow que j’ai mis en place est celui ci:

La carte HomeyScript est une carte ET “Run script with argument”. Elle est à paramétrer avec le nom de votre script et l’argument est le suivant:

{"hoursSinceLastUpdate": 120} // Paramètre obligatoire, 120 (5jrs) correspond au nombre d'heure entre le dernier signal de l'appareil et maintenant

Si vous souhaitez ajouter des appareils à ignorer, ajoutez “whiteList”:

{"hoursSinceLastUpdate": 120, "whiteList": "Les noms de vos appareils séparés par une virgule cette phrase ne fait qu'un appareil, celle ci un autre, encore un..."}

La “whiteList” n’est pas sensible à la casse ni au espace. “Sonde Séjour” ou “sonDE SéJoUr” ou “sondeséjour” fonctionnera. En revanche n’oubliez pas les accents!

Cela ressort ainsi:

Je ne suis pas codeur, ni dans l’informatique, je suis menuisier donc un peu à l’opposé! Alors mon code n’est peut être pas très propre, donc n’hésitez pas à partager toute amélioration.
Si vous souhaitez y ajouter des choses ou fonctionnalités dites le moi je pourrai essayer.

Bonne soirée

1 Like

(Sorry about english, but i don’t speak French)

Just so you know, you can also use the Device Capabilities App for this.

It has an “Retrieve Insights from [x] minutes ago for [device]”.
You will also recieve an token with the “changed-since” in milliseconds.
Set it to 0 minutes, and you can check that token/tag to see if it is more then 24 * 60 * 60 * 1000.

Salut,

Avec cette application, il faut créer 1 flow par appareil et il ne vérifie qu’une capacité.

J’obtiens le 19 juillet 2022 avec ce flow, le script me donne le 11 novembre 2022, car il vérifie toutes les dates possibles pour renvoyer la plus récente.

===============================================

Hi,

With this app you need to do 1 flow per device, and you only check 1 capability.

Get July 19 2022 with this flow, the script give November 11 2022, because it check all dates as possible to get and, return the newest.

Well, i need one flowcard per device, to check for instance, the last-changed-datetime for the temperature.

But it was not ment as replacement for your awesome script, only to inform you that you can also check the last-changed-datetime from within flows, instead of HomeyScript.

Well, if you check the last-changed for the battery.
But the idea was to check the last-changed for (for example) temperature. If that has not been set for 24 hours, the battery is dead or at least there is a communication fault.

I saw your script and (after translating it to Dutch) it seemed to be a solution/script for something that i also created an in-flow-solution for, so i wanted to share that with you :wink:

2 Likes

I tried to use your script, but it doesn’t work for me. Maybe you know what I do wrong?
i copied the script above and paste it in homey script. when I do a test is shows the notification “error” (see picture)

the seconds question : how do you get the logic cart? when I take the “run with argument” cart it doesn’t show anything for a logic? so how can I send an tekst to my phone?

hope you can help me

thank you


Hello,

If you want to test it in HomeyScript, replace the line

let myArgs = JSON.parse(args[0]); // This line get the argument sended by the flow

to:

let myArgs = {"hoursSinceLastUpdate": 120};

And to see list in log, you need to add log line before the "return true;" like this:

  log (nextList);
  return true;

The tag is create with the line:

await tag('No Signal List ', nextList); /*'No Signal List' is the name of the tag.
You can change it as you want. There are 2 lines like this*/

You will see it after the first time you run this code.

Check too if the line below matches to your date format:

nextList = previousList + actualDevice.name + ": " + dateCompare.toLocaleString("fr-FR") + "\n";

Thank you very much, it works!

I saw that you said that you’re not a developer? im not but I want it to learn this to, where did you learn to make this script?

I learn with the web!
I’ve been using Arduino for many years, so even though the languages are differents, the logic is relatively the same. Look how are made the other script to learn how they work, try and try yours again.
With patience and a lot of reading on the web, you can do whatever you want. :wink:

aah haha thank you, yes I try to always learn from other scripts. the site you send can help me to. I need to read more on the web I think :rofl:

Salut sebyldino !

Merci pour ton code ! il me fait gagner du temps car j’etais en recherche d’un similaire.

Par contre je n’arrive pas trouver l’origine de mon souci, et serais preneur de ton coup d’oeil !

les dates ne correspondent pas :wink:

du coup, est-ce que ton script check les communications ou alors la date de non evenement comme celui entouré en rouge sur mon image

merci !!

Apologies for speaking English in French thread :wink:

Would you have some tip, what is wrong with this error ?

obrazek

Le script check n’importe quelle date émise par l’appareil et renvoie la plus récente.
Dans ton cas, par exemple, tu peux très bien avoir eu la dernière alarm_contact il y a 13 jours mais le capteur à envoyé son niveau de batterie le 7/03/2023 à 22h23

Donc au moins jusqu’à cette date, ton capteur communiquait avec Homey.

1 Like

Hello,

What do you want to do? Because i dont understand what you did. :sweat_smile:

I just copy/pasted your script with argument (RUN Homescript with arg) and it finished with error…never provided any output. :slight_smile:

When tested directly as Homeyscript :

:x: Script Error

:warning: TypeError: Cannot read properties of undefined (reading ‘lastUpdated’)
at testdevice.js:56:75
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async HomeyScriptApp.runScript (/app.js:495:22)
at async Object.runScript (/api.js:30:22)

…isn’t it maybe because of devices with capabilities that don’t have a lastUpdated?
Robert modified his script here - A script to check sensor last update - #9 by robertklep, do you think you can improve your script maybe ? :wink:

You need to create a new script in homeyscript, past the code and save it.

Then create a flow like this:

image

The argument is {"hoursSinceLastUpdate": 120} (120 is number of hours since last signal you want to check)

Thank you but that’s exactly what I did… it’s a pity it doesn’t work for me, when it reach sensor with missing capability… anyway, thank you

Your problem is caused by this line:

    batteryDate = new Date(actualDevice.capabilitiesObj.measure_battery.lastUpdated);

So you have a device with battery but without “lastUpdated” capabilities.

You can ignore it with the whiteList:

{"hoursSinceLastUpdate": 120, "whiteList": "the names of the devices you want to ignore separate by a comma this sentence do one device, this another device, and another, ..."}

So if the problem is with the device “tvRemote” you can ignore it like this:

{"hoursSinceLastUpdate": 120, "whiteList": "tvRemote"}
1 Like

Will try, thank you!

Bonjour,
Je me permets de réouvrir le sujet car je suis dans la même problématique que Sharkys.
Lorsque je lance le script j’ai le message d’erreur suivant :
“Cannot read properties of undefined (reading ‘lastUpdated’)”

De plus, je n’arrive pas à trouver le tag “No Signal List”. J’ai donc créé une variable mais je crois que ce n’est pas correct.

Merci d’avance pour votre aide