HomeKit et ioBroker Faisons des amis à la maison


Sans aucun doute, Apple iOS reste l'un des systèmes d'exploitation mobiles les plus populaires, ce qui signifie que les systèmes d'automatisation modernes devraient pouvoir s'intégrer dans cet écosystème et assurer l'interopérabilité. C'est exactement pour cela que le framework Homekit est conçu, ce qui vous permet de travailler avec des appareils intelligents à partir de l'écran iPhone / iPad / iWatch, et plus récemment, le Mac (macOS Mojave).


La plupart des systèmes d'automatisation (je n'aime pas le nom marketing "maison intelligente") comportent depuis longtemps des modules pour s'intégrer à Homekit, mais même un utilisateur formé ne peut pas toujours simplement trouver comment rendre son appareil disponible dans l'application Home (ou Eve).


Aujourd'hui, je vais vous dire comment faire ces manipulations dans le système ioBroker (c'est un système d'automatisation ouvert et gratuit). Mais afin de ne pas donner bêtement tous les nombreux exemples d'appareils, je veux expliquer certains principes et montrer les approches, sachant lesquels, vous pouvez facilement mettre en œuvre d'autres exemples.


"La connaissance de certains principes compense facilement l'ignorance de certains faits."
Claude Adrian Helvetius


ioBroker. Pilotes, périphériques et états


Tout d'abord, je veux expliquer ce qu'est un appareil dans le système ioBroker et comment il est présenté.


Permettez-moi de vous rappeler que le système ioBroker est modulaire et que les modules d'extension sont appelés pilotes (ou adaptateurs). Un pilote est un module d'intégration avec un périphérique ou un groupe de périphériques, uni par une fonctionnalité, un protocole ou un fabricant commun, et peut donc "faire glisser" un ou plusieurs périphériques dans le système ioBroker. Une autre caractéristique est la possibilité de créer plusieurs instances du même pilote, différents dans tous les paramètres.


Mais chaque appareil est unique et inimitable, possède des caractéristiques et des capacités différentes. Sur cette base, ioBroker se concentre principalement non pas sur l'appareil lui-même, mais sur ses caractéristiques, qui sont représentées par des états. Un état est un objet ioBroker interne qui accepte et stocke une valeur. Des synonymes de l'état peuvent être envisagés: signes, attributs, caractéristiques, propriétés, événements. Exemples de conditions: "température", "niveau de luminosité", "niveau de batterie", "indicateur de mise sous tension", "indicateur d'erreur", "indicateur de pression", "indicateur de double pression", etc. Ainsi, chaque appareil est représenté par de nombreux états différents.


Structure d'objet


Les états peuvent être divisés en états informatifs - ils affichent les informations de l'appareil et les états mutables - ils peuvent être modifiés par l'utilisateur ou le script et envoyer ces modifications à l'appareil. En conséquence, lorsque quelque chose change sur l'appareil - ces données sont affichées dans les états, et lorsque l'état change de ioBroker (par l'utilisateur ou par le script) - l'appareil reçoit un signal sur le changement et doit répondre en conséquence (cela dépend de l'appareil lui-même et du pilote qui l'accompagne) travaux).


Tous les états de périphérique sont combinés dans une seule arborescence (registre) d'états. Ils sont d'abord regroupés par périphérique (dans certains cas, la canalisation est toujours utilisée), puis par instances de pilote.


Le concept de sujets de protocole MQTT s'intègre facilement dans un tel arbre d'état. De cette façon, vous pouvez connecter des équipements supplémentaires ou des systèmes tiers prenant en charge le protocole MQTT. Il suffit d'installer le pilote MQTT - la branche correspondante apparaîtra dans l'arborescence d'état.


Et il existe toutes sortes de services en ligne qui peuvent fournir des informations utiles et / ou permettre de contrôler d'autres équipements (alarmes de voiture par exemple). Le résultat de l'interaction avec ces services est également représenté comme un ensemble d'états.


Arbre d'état


Au total, un appareil dans ioBroker est représenté par un ensemble d'états caractérisant l'appareil et permettant d'interagir avec lui.


Homekit Accessoires, services et spécifications


Maintenant, tournez-vous vers Homekit. Ici, la classification des appareils, leur fonctionnalité et leurs caractéristiques est appliquée.


Catégories d'appareils Homekit


Les accessoires sont l'équivalent d'un appareil physique. L'accessoire a une catégorie pour l'attribuer à un groupe spécifique.


Les services sont l'équivalent des fonctionnalités d'un accessoire. Un accessoire peut avoir plusieurs services.


Les services indiquent les capacités de l'appareil: lampe, batterie, bouton, capteur de qualité de l'air, porte, filtre à air, appareil photo ..


C'est le service qui détermine l'affichage, le comportement de l'appareil et l'ensemble des caractéristiques.


La caractéristique est l'équivalent des attributs / propriétés qui caractérisent un service. Ce sont les caractéristiques qui déterminent si l'appareil est allumé, le niveau de luminosité de la lampe ou le nombre de pressions sur le bouton. Un service unique peut avoir de nombreuses caractéristiques.


Structure d'objet


Les applications qui fonctionnent avec Homekit lisent les services et les caractéristiques des accessoires, puis affichent et vous permettent de modifier les valeurs des caractéristiques via l'interface utilisateur. Les valeurs modifiées sont envoyées aux appareils Homekit pour les appliquer, et à partir des appareils Homekit, respectivement, les valeurs des caractéristiques sont également envoyées avec quelques modifications du côté de l'appareil.


Au total, l'appareil dans HomeKit semble être un accessoire avec un ensemble de services et de fonctionnalités.


Yahka. Nous rejoignons le concept


Pour travailler avec Homekit, ioBroker utilise le pilote Yahka ( des modules supplémentaires doivent être installés avant l'installation) - un module complémentaire à une bibliothèque bien connue https://github.com/KhaosT/HAP-NodeJS , qui construit également le populaire projet HomeBridge. Cette bibliothèque est conçue pour créer une passerelle / passerelle virtuelle qui fournit un ensemble de périphériques virtuels dans HomeKit. En configurant les appareils virtuels et les services en conséquence, en définissant les valeurs des caractéristiques, nous obtenons l'appareil fini dans Homekit et l'application Home, et nous pouvons également demander à Siri de le gérer.


Le pilote Yahka est juste conçu pour configurer les accessoires, leur ajouter des services et indiquer la correspondance des caractéristiques (HomeKit) et des états (ioBroker).


Mais d'abord, après l'installation, vous devez configurer la passerelle et l'intégrer dans l'application Home. Après la configuration, tous les appareils ajoutés à la passerelle seront automatiquement ajoutés au Home. Pour ce faire, spécifiez "Nom du périphérique" (il est souhaitable de ne spécifier que des lettres latines) et n'oubliez pas le code PIN (ou définissez le vôtre).


Configuration de la passerelle


Nous allons à l'application Home et ajoutons un nouvel accessoire.



Passons maintenant aux appareils. Tout irait bien si l'ensemble des états de l'appareil dans ioBroker correspondait clairement à l'ensemble des services et fonctionnalités de HomeKit. Et ce serait encore mieux si les valeurs dans les états étaient adaptées aux valeurs des caractéristiques. Mais souvent, ce n'est pas le cas, et vous devez trouver des moyens inhabituels d'accoster. J'en parlerai ci-dessous, et vous devrez implémenter vous-même toutes les autres options, "à l'image et à la ressemblance".


Pour plus de commodité, j'ai créé un document avec la traduction des services et des types, ainsi que les valeurs possibles des caractéristiques. Tous les types et services utilisés correspondent à la bibliothèque HAP-NodeJS .


Capteur de température


Ceci est l'exemple le plus simple - tout ce que vous avez à faire est d'avoir un état contenant la valeur numérique de la température. Il peut être obtenu de n'importe où: à partir de capteurs ou de services Internet (météo).

Vous devez ajouter un appareil de la catégorie Capteur, ajouter le service TemperatureSensor à l'appareil et donner un nom à ce service. Il existe 5 caractéristiques dans ce service, dont la plus importante pour nous est la température actuelle.


Thermomètre accessoire


Service de capteur de température


Il suffit d'indiquer le nom de l'état correspondant à la température dans la caractéristique CurrentTemperature.


Ajoutez également le service d'humidité HumiditySensor, et une icône d'accessoire séparée sera créée dans Homekit.


Service de capteur d'humidité


Enregistrez et vous avez terminé. Vous pouvez maintenant vous tourner vers Siri et lui poser des questions sur la température et l'humidité.



Conversation avec Siri



La batterie


Un autre service simple. Son astuce est qu'il peut être ajouté à presque tous les accessoires. Ajoutez le service BatteryService et indiquez dans la caractéristique BatteryLevel un état contenant le pourcentage de charge de la batterie. Après cela, les données de charge apparaîtront dans les données supplémentaires sur l'appareil.


BatteryService


Vous pouvez immédiatement définir le signe de «faible charge» (caractéristique StatusLowBattery), si la valeur de l'état spécifié est égale à 1, l'icône correspondante sera affichée sur l'image de l'appareil.


Mais que se passe-t-il si vous ne disposez pas d'un tel état, mais que vous souhaitez voir l'icône de batterie faible? Vous devez créer cet état manuellement ou à l'aide d'un script et indiquer l'état créé dans les caractéristiques.


Maintenant, il ne reste plus qu'à définir correctement la valeur true dans cet état. Pour ce faire, nous utiliserons le script - il deviendra vrai lorsque la batterie atteindra 30%.


createState(""); on({id: "zigbee.0.00158d0001f41725.battery", change: "ne"}, function (obj) {   var value = obj.state.val; setState("javascript.0.", (value <= 30)); }); 

Après la première exécution, le script crée un état et peut être sélectionné dans les caractéristiques.



Ce signe sera affiché sur les images d'accessoires



et les détails de l'appareil


Lampes


Les ampoules sont différentes - lumineuses, chaudes, rouges. Il y a 4 cas:


  • Simple - contrôlé par marche et arrêt
  • Dimmable - également contrôlé par le niveau de luminosité
  • Avec la température - il est possible de contrôler la température de la lueur
  • Couleur - vous pouvez contrôler la couleur de la lueur

Pour chacun de ces cas, il existe une caractéristique correspondante dans le service Ampoule:


  • On - on / off
  • Luminosité - niveau de luminosité
  • Teinte - ombre
  • Saturation - Saturation
  • ColorTemperature - température de couleur

Dans le cas simple, dans la caractéristique "On", nous indiquons l'état responsable de l'allumage et de l'extinction.



Si la lampe est dimmable, nous indiquons en outre l'état avec le niveau de luminosité.



En plus d'attribuer des états corrects, il est important de respecter l'intervalle des valeurs acceptables!


Exemple: dans certains cas, l'état responsable de la luminosité de la lampe peut prendre des valeurs de 0 à 255, mais dans Homekit ces valeurs sont limitées à un intervalle de 0 à 100. Pour ce cas, vous pouvez utiliser les fonctions de conversion du pilote Yahka. La fonction "level255" convertit simplement l'intervalle de valeurs 0..255 en intervalle 0..100 (et vice versa).


Les difficultés suivantes peuvent survenir si votre lampe est colorée, mais la couleur utilisée est RVB. Il peut s'agir de trois états différents ou d'un nombre (ou chaîne). Dans ce cas, vous devrez convertir d'un espace colorimétrique RVB en un autre espace XYB (cet espace est utilisé par HomeKit) ou en plan XY.


Pour ce faire, vous devez créer 2 nouveaux états (Teinte et Saturation), dans lesquels nous convertirons les valeurs de l'état RVB et vice versa.


Le script résultant pour la couleur est
 //       createState("Hue"); createState("Sat"); //      RGB- on({id: "Hue", ack: false, change: 'any'}, function (obj) {  var hue = parseInt(obj.state.val);  var sat = parseInt(getState('Sat').val);  var res = hsvToRgb(hue, sat, 100);  setRGB(parseInt(res[0]), parseInt(res[1]), parseInt(res[2])); }); //    RGB- function setRGB(r, g, b){  var val = ('00'+r.toString(16)).slice(-2)+('00'+g.toString(16)).slice(-2)+('00'+b.toString(16)).slice(-2);  // RGB-   setState('zigbee.0.00124b0014d016ab.color', val, false); } //   HSV   RGB function hsvToRgb(h, s, v) {  var r, g, b;  var i;  var f, p, q, t;   h = Math.max(0, Math.min(360, h));  s = Math.max(0, Math.min(100, s));  v = Math.max(0, Math.min(100, v));  s /= 100;  v /= 100;   if(s == 0) {      r = g = b = v;      return [          Math.round(r * 255),          Math.round(g * 255),          Math.round(b * 255)      ];  }   h /= 60;  i = Math.floor(h);  f = h - i;  p = v * (1 - s);  q = v * (1 - s * f);  t = v * (1 - s * (1 - f));   switch(i) {      case 0:          r = v;          g = t;          b = p;          break;       case 1:          r = q;          g = v;          b = p;          break;       case 2:          r = p;          g = v;          b = t;          break;       case 3:          r = p;          g = q;          b = v;          break;       case 4:          r = t;          g = p;          b = v;          break;       default: // case 5:          r = v;          g = p;          b = q;  }   return [      Math.round(r * 255),      Math.round(g * 255),      Math.round(b * 255)  ]; } 

La température de couleur peut être plus facile - si la plage de valeurs disponibles pour votre lampe est connue, elle peut être convertie en l'intervalle disponible pour HomeKit (via la fonction scaleInt ).


Service d'ampoules



Plus profondément dans la lampe



Thermostat


Thermostat - un appareil pour maintenir la température réglée (service de thermostat). En conséquence, la principale caractéristique du thermostat est la température souhaitée (TargetTemperature). En plus de la température réglée, la température actuelle (CurrentTemperature) peut être indiquée, qui est de nature informative (puisque l'appareil ne la lit que depuis les capteurs).


Depuis l'application Home, la température cible est réglée dans le thermostat et la température actuelle est suivie. Dans mon thermostat (Zont), il n'y avait que ces deux états - ils étaient disponibles via l'api du cloud de service.


Pour la beauté de l'affichage de l'appareil dans HomeKit, j'ai dû ajouter quelques constantes: l'état actuel du chauffage est actif (1), l'état cible du chauffage est automatique (3).


Service de thermostat



Sélection de la température


Portes


Avec une porte de garage (service GarageDoorOpener), tout est plus délicat qu'avec un thermostat.


Parmi les caractéristiques disponibles, la porte a un état cible (TargetDoorState), ce qui indique notre désir que la porte soit «ouverte» ou «fermée». Mais vous devez également afficher correctement l'état actuel de la porte (CurrentDoorState): sont-ils ouverts ou fermés, ou peuvent-ils s'ouvrir ou se fermer?


Dans mon cas, les portes ont été ouvertes via mqtt dans ioBroker avec plusieurs états d'information:


  • signe d'ouverture de porte (OB)
  • signe de mouvement du portail (LW)

États de commande de porte de garage


Grâce à ces états, vous pouvez calculer l'état actuel de la porte:


  • s'il n'y a pas d'OB ni de DV, alors les portes sont fermées
  • s'il n'y a pas d'OB et qu'il y a un DV, alors les portes s'ouvrent
  • s'il y a un OB et pas de DV, alors les portes sont ouvertes
  • s'il y a un OB et qu'il y a un DV, alors la porte se ferme

Pour envoyer un signal d'ouverture et de fermeture du portail, j'ai deux états distincts (il serait possible de gérer avec un seul état, mais j'en ai deux), qui envoient un message via mqtt au contrôleur de contrôle du portail:


  • signal d'ouverture
  • signal de fermeture

Pour envoyer un signal, vous devez simuler un bouton "clic": définissez la valeur sur true, et après un certain temps, réinitialisez-la sur false. À cet égard, pour s'intégrer à HomeKit, il était nécessaire de créer un autre état - «l'état cible de la porte», une fois modifié, le signal correspondant sera envoyé.


Le signe d'ouverture de la porte peut être remplacé par l'état cible (c'est-à-dire ce que l'objectif visera):


  • si l'AC est «fermée» et qu'il n'y a pas de DV, alors la porte est fermée
  • si l'AC est «fermée» et qu'il y a un DV, alors les portes s'ouvrent
  • si l'AC est «ouverte» et qu'il n'y a pas de DV, alors la porte est ouverte
  • si le CA est «ouvert» et qu'il y a un DV, alors la porte se ferme

Nous allons également créer un état séparé "état actuel de la porte", et nous le remplirons dans le script en fonction de la valeur des signes et de l'état cible.


Script de changement d'état pour les portes de garage
 createState("gate_0.current"); //   createState("gate_0.target"); //   //    0,   300 on({id: "mqtt.0.gate.gpio.13", ack: false, val: 1}, function (obj) {  setStateDelayed("mqtt.0.gate.gpio.13", 0,  300); }); on({id: "mqtt.0.gate.gpio.12", ack: false, val: 1}, function (obj) {  setStateDelayed("mqtt.0.gate.gpio.12", 0,  300); }); //     on({id: "mqtt.0.gate.is_open", ack: false, val: 1}, function (obj) {  // ""  setState("javascript.0.gate_0.current", 0, true); }); on({id: "mqtt.0.gate.is_open", ack: false, val: 0}, function (obj) {  // ""  setState("javascript.0.gate_0.current", 1, true); }); //    - ,      on({id: "javascript.0.gate_0.target", ack: false, val: 0}, function (obj) {  setState("mqtt.0.gate.gpio.12", 1); }); //    - ,      on({id: "javascript.0.gate_0.target", ack: false, val: 1}, function (obj) {  setState("mqtt.0.gate.gpio.13", 1); }); on({id: "mqtt.0.gate.in_progress", ack: true, change: 'any'}, function (obj) {  //    " ",      if (obj.state.val === 1) {      //    "",         const target = getState("javascript.0.gate_0.target");      if (target.val === 0) {          // ""          setState("javascript.0.gate_0.current", 2, true);      } else {          // ""          setState("javascript.0.gate_0.current", 3, true);      }  }  //    " ",      if (obj.state.val === 0) {      //    "",         const target = getState("javascript.0.gate_0.target");      if (target.val === 0) {          // ""          setState("javascript.0.gate_0.current", 0, true);      } else {          // ""          setState("javascript.0.gate_0.current", 1, true);      }  } }); 

Après avoir exécuté le script, vous pouvez configurer les caractéristiques du service de porte de garage:


Service GarageDoorOpener



Appareil photo


Pour ajouter une caméra à HomeKit, la méthode "classique" sera utilisée. La diffusion de l'image depuis la caméra via le module ffmpeg est organisée. Grâce à lui, le flux d'entrée sera encodé, crypté et donné à Homekit.


Tout d'abord, vous devez installer ffmpeg sur le serveur où se trouve ioBroker.


Pour chaque plateforme, elle est installée de différentes manières, vous pouvez l'assembler à partir de la source, ou rechercher un assemblage fini, par exemple ici: https://www.johnvansickle.com/ffmpeg/ Doit avoir un encodeur libx264. Vous pouvez vérifier l'encodeur après avoir installé ffmpeg avec la commande:


 ffmpeg -codecs | grep 264 

Les résultats doivent contenir une ligne du formulaire


 DEV.LS h264                 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (decoders: h264 h264_v4l2m2m h264_vdpau ) (encoders: libx264 libx264rgb h264_v4l2m2m ) 

Pour Raspberry Pi 3, vous pouvez utiliser l' assemblage prêt à l' emploi , qui dispose d'un codec avec prise en charge du codage matériel GPU (h264_omx, consomme moins de ressources). Mettez-le comme ceci:


 wget https://github.com/legotheboss/YouTube-files/raw/master/ffmpeg_3.1.4-1_armhf.deb sudo dpkg -i ffmpeg_3.1.4-1_armhf.deb 

Les deux codecs sont présents dans cet assemblage: libx264 et h264_omx


Ensuite, vous devez obtenir l'adresse du flux de caméra qui doit être diffusé (cette étape dépasse le cadre de cet article). Par exemple, vous pouvez prendre un flux public prêt à l'emploi .


Ajoutez maintenant la caméra à Yahka, indiquez l'adresse du flux et si nécessaire modifiez les paramètres du codec, la taille de l'image, la fréquence d'images.


Important: les combinaisons de paramètres sont très importantes pour l'affichage correct de la caméra dans Homekit et dépendent de la caméra et du flux. Il affecte également les performances du système, comme Le processus en cours d'exécution de ffmpeg consomme beaucoup de ressources.


Ajout d'une caméra


Configuration du flux


Les caméras sont ajoutées en tant que périphériques distincts en dehors de la passerelle, et elles doivent être ajoutées de la même manière que la passerelle


Vignette de la caméra


Diffusion depuis la caméra


Bonus


En prime, je vais parler de l'utilisation inhabituelle de la diffusion par caméra.


En utilisant le même ffmpeg, au lieu de l'appareil photo, vous pouvez essayer de diffuser l'image, n'importe quelle image. Ces images peuvent également être combinées avec le flux vidéo. Vous pouvez afficher du texte, des graphiques et d'autres informations sur l'image.


Superposition de texte Cast


En conséquence, vous pouvez obtenir un tableau de bord intéressant. Et si vous mettez à jour l'image périodiquement, vous obtenez des données dynamiques.


À titre d'exemple, j'ai sorti un graphique des changements de certains indicateurs sous la forme d'une image (fichier sur disque). Ce graphique est mis à jour une fois par minute et écrase l'image dans le fichier.


(Les fonctions createImage1, createImage2, la formation d'un graphique et l'imposition de texte sur une image dépassent le cadre de cet article, mais je vais vous donner un indice).

Je vais vous dire comment obtenir un graphique sous forme d'image.


IoBroker a une façon standard de construire des graphiques - le pilote Flot. Ce pilote est associé à un pilote Web et affiche le résultat dans un navigateur. Mais pour obtenir le graphique créé sur le serveur (dans le script) en tant qu'image, un pilote PhantomJS supplémentaire est nécessaire, qui prend une «capture d'écran» de la page (sur laquelle nous allons dessiner un graphique Flot).


Mais je vais parler d'une autre façon de construire des graphiques sur le serveur dans un script.


Il existe une telle bibliothèque Chart.js http://www.chartjs.org/ qui vous permet de dessiner de jolis graphiques dans le navigateur (exemples http://www.chartjs.org/samples/latest/ ).


Pour le dessin, il utilise le «canvas» (canvas, canvas) du navigateur. Par conséquent, pour dessiner en utilisant cette bibliothèque sur le serveur, vous devez utiliser la version «serveur» des objets «canvas» et DOM. C'est ce que fait le package chartjs-node ( https://github.com/vmpowerio/chartjs-node ).


La principale dépendance de ce package est le package canvas ( https://github.com/Automattic/node-canvas ), qui doit être installé globalement (ou dans le dossier iobroker). Il est important d'installer toutes les dépendances de la plateforme sur laquelle vous mettez https://github.com/Automattic/node-canvas#compiling .


Après cela, vous pouvez ajouter chart.js, modules chartjs-node dans les paramètres du pilote javascript. Ils doivent s'installer correctement, sans erreurs. Sinon, traitez les erreurs et résolvez-les.


Et puis, vous pouvez écrire un script.


Voici un script pour un exemple, comme il inclut l'utilisation du pilote d'historique et utilise des noms d'état spécifiques.


Attention! Le script a des constructions compliquées pour les débutants - Promise. C'est un moyen pratique de ne pas écrire de fonctions avec rappel, mais de faire des chaînes d'étapes. Ainsi, par exemple, il est commode de le faire pour obtenir des données de l'historique des états.


 'use strict'; const ChartjsNode = require('chartjs-node'); /** *  sendTo  Promise,      */ function sendToPromise(adapter, cmd, params) { return new Promise((resolve, reject) => { sendTo(adapter, cmd, params, (result) => { resolve(result); }); }); } //    const chartColors = { black: 'rgb(0, 0, 0)', red: 'rgb(255, 99, 132)', orange: 'rgb(255, 159, 64)', yellow: 'rgb(255, 205, 86)', green: 'rgb(75, 192, 192)', blue: 'rgb(54, 162, 235)', purple: 'rgb(153, 102, 255)', grey: 'rgb(201, 203, 207)' }; /** *        * : * @param config -     * @param filename -     * : * @param Promise -    */ function doDraw(config, filename) { //     640x480  var chartNode = new ChartjsNode(640, 480); return chartNode.drawChart(config) .then(() => { //     return chartNode.writeImageToFile('image/png', filename); }); } /** *     ChartJS. * : * @param Promise -    */ function prepareDraw0(){ // ,    var ; //  Promise     return new Promise((resolve, reject)=>{resolve()}) //       ,      .then(()=>{ //  ,   ,      = [ {"val":3,"ack":1,"ts":1539063874301}, {"val":5,"ack":1,"ts":1539063884299}, {"val":5.3,"ack":1,"ts":1539063894299}, {"val":3.39,"ack":1,"ts":1539063904301}, {"val":5.6,"ack":1,"ts":1539063914300}, {"val":-1.3,"ack":1,"ts":1539063924300}, {"val":-6.3,"ack":1,"ts":1539063934302}, {"val":1.23,"ack":1,"ts":1539063944301}, ]; }) //   -    .then(()=>{ const chartJsOptions = { //   -  type: 'line', data: { //    datasets: [ { //   label: '', //  backgroundColor: chartColors.black, borderColor: chartColors.black, //   pointRadius: 3, //    borderWidth: 3, //     ''        data: .map((item) => { return {y: item.val, t: new Date(item.ts)} }), //   -  fill: false, } ] }, options: { //   legend: { labels: { //   fontSize: 20, }, }, //   scales: { //  X xAxes: [{ //  -   type: 'time', display: true, //   scaleLabel: { display: true, labelString: '' }, }], //  Y yAxes: [{ //  -  type: 'linear', display: true, //   scaleLabel: { display: true, labelString: '' }, }] } } }; return chartJsOptions; }); } /** *     ChartJS. *         , *     . * * : * @param hours -  ,     * : * @param Promise -    */ function prepareDraw1(hours){ //   ,      const end = new Date().getTime(), start = end - 3600000*(hours || 1); // 1 =   //  ,       //   var , 2, 1, 2, 2; //  Promise     return new Promise((resolve, reject)=>{resolve()}) //       'mqtt.0.ESP_Easy..Temperature' .then(() => { return sendToPromise('history.0', 'getHistory', { id: 'mqtt.0.ESP_Easy..Temperature', options: { start: start, end: end, aggregate: 'onchange' } } ).then((result) => { //     ''  = result.result; }); }) //       'sonoff.0.chicken2.DS18B20_Temperature' .then(() => { return sendToPromise('history.0', 'getHistory', { id: 'sonoff.0.chicken2.DS18B20_Temperature', options: { start: start, end: end, aggregate: 'onchange' } }).then((result)=>{ //     '2' 2 = result.result; }); }) .then(() => { return sendToPromise('history.0', 'getHistory', { id: 'sonoff.0.sonoff_chicken_vent.DS18B20_Temperature', options: { start: start, end: end, aggregate: 'onchange' } }).then((result)=>{ 1 = result.result; }); }) .then(() => { return sendToPromise('history.0', 'getHistory', { id: 'sonoff.0.chicken2.POWER1', options: { start: start, end: end, aggregate: 'onchange' } }).then((result)=>{ 2 = result.result; }); }) .then(() => { return sendToPromise('history.0', 'getHistory', { id: 'sonoff.0.chicken2.POWER2', options: { start: start, end: end, aggregate: 'onchange' } }).then((result)=>{ 2 = result.result; }); }) //   -    .then(()=>{ const chartJsOptions = { //   -  type: 'line', data: { //    datasets: [ { //           label: ' ('+[.length - 1].val+')', //  backgroundColor: chartColors.blue, borderColor: chartColors.blue, //  . 0 -   pointRadius: 0, //    borderWidth: 3, //     ''        data: .map((item) => { return {y: item.val, t: new Date(item.ts)} }), //   -  fill: false, //   Y yAxisID: 'y-axis-1', },{ label: ' 1 ('+1[1.length - 1].val+')', backgroundColor: chartColors.green, borderColor: chartColors.green, pointRadius: 0, borderWidth: 3, data: 1.map((item) => { return {y: item.val, t: new Date(item.ts)} }), fill: false, yAxisID: 'y-axis-1', },{ label: ' 2 ('+2[2.length - 1].val+')', backgroundColor: chartColors.red, borderColor: chartColors.red, pointRadius: 0, borderWidth: 3, data: 2.map((item) => { return {y: item.val, t: new Date(item.ts)} }), fill: false, yAxisID: 'y-axis-1', },{ label: ' 2  ('+2[2.length - 1].val+')', backgroundColor: chartColors.yellow, borderColor: chartColors.yellow, pointRadius: 0, borderWidth: 1, data: 2.map((item) => { return {y: (item.val) ? 1 : 0, t: new Date(item.ts)} }), fill: true, lineTension: 0, steppedLine: true, yAxisID: 'y-axis-2', },{ label: ' 2  ('+2[2.length - 1].val+')', backgroundColor: chartColors.grey, borderColor: chartColors.grey, pointRadius: 0, borderWidth: 1, data: 2.map((item) => { return {y: (item.val) ? -1 : 0, t: new Date(item.ts)} }), fill: true, lineTension: 0, steppedLine: true, yAxisID: 'y-axis-2', } ] }, options: { //   legend: { labels: { //   fontSize: 20, }, }, //   scales: { //  X xAxes: [{ //  -   type: 'time', display: true, //   scaleLabel: { display: true, labelString: '' }, //    () time: { unit: 'minute', displayFormats: { minute: 'HH:mm' } }, }], //  Y yAxes: [{ //  -  type: 'linear', display: true, //   scaleLabel: { display: true, labelString: '' }, //   -  position: 'left', //   id: 'y-axis-1', },{ type: 'linear', display: true, scaleLabel: { display: true, labelString: '  ' }, ticks: { min: -4, max: 2 }, //   -  position: 'right', id: 'y-axis-2', }] } } }; return chartJsOptions; }); } function createImage(filename, callback){ // filename -  ,       //    prepareDraw1(2) //     .then((result) => { //        return doDraw(result, filename); }) .then(()=>{ if (callback) callback(); }) .catch((err)=>{ console.error(err); }); } 

Diffuser l'image au lieu du flux


L'image miniature est mise à jour environ une fois par minute, nous allons donc configurer l'image pour qu'elle soit mise à jour toutes les 10 secondes:


 var fs = require('fs'); //  10    schedule("*/10 * * * * *", () => {  createImage1('/tmp/1_new.jpg', ()=> {      fs.renameSync('/tmp/1_new.jpg', '/tmp/1.jpg');  });  createImage2('/tmp/2_new.jpg', ()=> {      fs.renameSync('/tmp/2_new.jpg', '/tmp/2.jpg');  }); }); 

La particularité est que dans le processus de diffusion de l'image, il est nécessaire de remplacer l'image assez rapidement pour que ffmpeg ne se bloque pas :) Par conséquent, l'image est d'abord formée dans un fichier, puis le fichier est renommé en celui utilisé pour la traduction.


Maintenant, dans les paramètres de la caméra, spécifiez le nom du fichier généré au lieu de l'adresse du flux et ajoutez les paramètres selon lesquels l'image est "mise à jour" (paramètre "-loop 1"). Ceci est configuré dans les propriétés avancées de la caméra. Ces propriétés ne sont rien de plus que des options de ligne de commande pour exécuter ffmpeg. Par conséquent, des combinaisons de paramètres doivent être trouvées dans la documentation et les exemples de ffmpeg.


Les propriétés sont divisées en 2 types: pour recevoir un «aperçu» (une petite image de la caméra) et pour la diffusion. Par conséquent, vous pouvez spécifier différents fichiers source d'image, par exemple avec des détails différents.


Options de démarrage de Ffmpeg


Image de diffusion en direct


Conclusion


ioBroker . , . , , .


, Yahka , Material. , HomeKit.


Yahka, HomeKit — Ham, HomeBridge . .

Source: https://habr.com/ru/post/fr433798/


All Articles