HomeKit y ioBroker Hagamos amigos en casa


Sin lugar a dudas, Apple iOS sigue siendo uno de los sistemas operativos móviles más populares, lo que significa que los sistemas de automatización modernos deben poder integrarse en este ecosistema y brindar la oportunidad de interacción. Esto es exactamente para lo que está diseñado el marco Homekit, que le permite trabajar con dispositivos inteligentes desde la pantalla del iPhone / iPad / iWatch, y más recientemente, Mac (macOS Mojave).


La mayoría de los sistemas de automatización (no me gusta el nombre comercial "casa inteligente") han incluido módulos para integrarse con Homekit, pero incluso un usuario capacitado no siempre puede descubrir cómo hacer que su dispositivo esté disponible en la aplicación Home (o Eve).


Hoy les diré cómo hacer estas manipulaciones en el sistema ioBroker (este es un sistema de automatización abierto y gratuito). Pero para no dar estúpidamente todos los ejemplos de dispositivos, quiero explicar algunos principios y mostrar enfoques, sabiendo cuáles, usted puede implementar fácilmente otros ejemplos.


"Conocer algunos principios compensa fácilmente la ignorancia de algunos hechos".
Claude Adrian Helvetius


ioBroker. Controladores, dispositivos y estados


En primer lugar, quiero explicar qué es un dispositivo en el sistema ioBroker y cómo se presenta.


Permítame recordarle que el sistema ioBroker es modular y que los módulos de extensión se denominan controladores (o adaptadores). Un controlador es un módulo de integración con algún dispositivo o grupo de dispositivos, unidos por una funcionalidad, protocolo o fabricante común, y por lo tanto puede "arrastrar" de uno a varios dispositivos al sistema ioBroker. Otra característica es la capacidad de crear múltiples instancias del mismo controlador, que difieren en cualquier configuración.


Pero cada dispositivo es único e inimitable, tiene diferentes características y capacidades. En base a esto, ioBroker se enfoca principalmente no en el dispositivo en sí, sino en sus características, que están representadas por estados. Un estado es un objeto interno ioBroker que acepta y almacena un valor. Se pueden considerar los sinónimos del estado: signos, atributos, características, propiedades, eventos. Ejemplos de condiciones: "temperatura", "nivel de brillo", "nivel de batería", "indicador de encendido", "indicador de error", "indicador de presión", "indicador de doble presión", etc. Por lo tanto, cada dispositivo está representado por muchos estados diferentes.


Estructura del objeto


Los estados se pueden dividir en informativos (muestran información del dispositivo y mutables), el usuario o el script pueden cambiarlos y enviar estos cambios al dispositivo. En consecuencia, cuando algo cambia en el dispositivo (estos datos se muestran en los estados y cuando el estado cambia de ioBroker (por el usuario o por el script)), el dispositivo recibe una señal sobre el cambio y debe responder en consecuencia (depende del dispositivo en sí y del controlador que lo acompaña) obras).


Todos los estados del dispositivo se combinan en un solo árbol (registro) de estados. Se agrupan primero por dispositivo (en algunos casos, la canalización todavía se usa) y luego por instancias de controladores.


El concepto de temas de protocolo MQTT se adapta fácilmente a dicho árbol de estado. De esta manera, puede conectar equipos adicionales o sistemas de terceros que admitan el protocolo MQTT. Es suficiente instalar el controlador MQTT: la rama correspondiente aparecerá en el árbol de estado.


Y hay todo tipo de servicios en línea que pueden proporcionar información útil y / o hacer posible controlar otros equipos (alarmas de automóviles, por ejemplo). El resultado de la interacción con estos servicios también se representa como un conjunto de estados.


Árbol de estado


En total, un dispositivo en ioBroker está representado por un conjunto de estados que caracterizan el dispositivo y permiten interactuar con él.


Homekit Accesorios, servicios y especificaciones.


Ahora recurra a Homekit. Aquí se aplica la clasificación de dispositivos, su funcionalidad y características.


Categorías de dispositivos de Homekit


Los accesorios son el equivalente de un dispositivo físico. El accesorio tiene una categoría para asignarlo a un grupo específico.


Los servicios son el equivalente de la funcionalidad que tiene un accesorio. Un accesorio puede tener varios servicios.


Los servicios indican las capacidades del dispositivo: lámpara, batería, botón, sensor de calidad del aire, puerta, filtro de aire, cámara ...


Es el servicio el que determina la pantalla, el comportamiento del dispositivo y el conjunto de características.


Característica es el equivalente de los atributos / propiedades que caracterizan un servicio. Son las características las que determinan si el dispositivo está encendido, el nivel de brillo de la lámpara o cuántas veces se presiona el botón. Un solo servicio puede tener muchas características.


Estructura del objeto


Las aplicaciones que funcionan con Homekit leen los servicios y las características de los accesorios, y luego muestran y le permiten cambiar los valores en las características a través de la interfaz de usuario. Los valores modificados se envían a los dispositivos Homekit para aplicarlos, y desde los dispositivos Homekit, respectivamente, también se envían los valores de las características con algunos cambios desde el lado del dispositivo.


En total, el dispositivo en HomeKit parece ser un accesorio con un conjunto de servicios y características.


Yahka Nos unimos al concepto


Para trabajar con Homekit, ioBroker utiliza el controlador Yahka (se deben instalar módulos adicionales antes de la instalación), un complemento de una biblioteca conocida https://github.com/KhaosT/HAP-NodeJS , que también construye el popular proyecto HomeBridge. Esta biblioteca está diseñada para crear una puerta de enlace / puente virtual que proporciona un conjunto de dispositivos virtuales en HomeKit. Configurando los dispositivos y servicios virtuales en consecuencia, estableciendo los valores de las características, obtenemos el dispositivo terminado en Homekit y la aplicación Home, y también podemos pedirle a Siri que lo administre.


El controlador Yahka está diseñado para configurar accesorios, agregarles servicios e indicar la correspondencia de características (HomeKit) y estados (ioBroker).


Pero primero, después de la instalación, debe configurar la puerta de enlace y acceder a la aplicación de inicio. Después de la configuración, todos los dispositivos agregados a la puerta de enlace se agregarán automáticamente al Inicio. Para hacer esto, especifique "Nombre del dispositivo" (es conveniente especificar solo letras latinas) y recuerde el código PIN (o establezca el suyo).


Configuración de puerta de enlace


Vamos a la aplicación Inicio y agregamos un nuevo accesorio.



Ahora vamos a los dispositivos. Todo estaría bien si el conjunto de estados para el dispositivo en ioBroker correspondiera claramente al conjunto de servicios y características en HomeKit. Y sería aún mejor si los valores en los estados fueran adecuados para los valores de las características. Pero a menudo esto no es así, y tienes que encontrar formas inusuales de atracar. Hablaré sobre algunos de ellos a continuación, y tendrá que implementar todas las demás opciones usted mismo "a imagen y semejanza".


Por conveniencia, creé un documento con la traducción de servicios y tipos, así como los posibles valores de las características. Todos los tipos y servicios utilizados corresponden a la biblioteca HAP-NodeJS .


Sensor de temperatura


Este es el ejemplo más simple: todo lo que tiene que hacer es tener un estado que contenga el valor numérico de la temperatura. Se puede obtener desde cualquier lugar: desde sensores o desde servicios de Internet (clima).

Debe agregar un dispositivo de la categoría Sensor, agregar el servicio Sensor de temperatura al dispositivo y asignarle un nombre a este servicio. Hay 5 características en este servicio, de las cuales la más importante para nosotros es CurrentTemperature.


Termómetro accesorio


Servicio de sensor de temperatura


Es suficiente para indicar el nombre del estado correspondiente a la temperatura en la característica CurrentTemperature.


Agregue aquí también el servicio de humedad del sensor de humedad, y se creará un icono de accesorio separado en Homekit.


Servicio de sensor de humedad


Ahorre, y ya está. Ahora puedes recurrir a Siri y preguntarle sobre la temperatura y la humedad.



Conversación con siri



Batería


Otro servicio simple. Su truco es que se puede agregar a casi cualquier accesorio. Agregue el servicio BatteryService e indique en la característica BatteryLevel un estado que contenga el porcentaje de carga de la batería. Después de eso, los datos de carga aparecerán en los datos adicionales sobre el dispositivo.


Servicio de batería


Puede configurar inmediatamente el signo de "carga baja" (característica StatusLowBattery), si el valor del estado especificado es igual a 1, el icono correspondiente se mostrará en la imagen del dispositivo.


Pero, ¿qué pasa si no tiene ese estado, pero quiere ver el icono de batería baja? Debe crear este estado manualmente o con un script e indicar el estado creado en las características.


Ahora solo queda establecer correctamente el valor verdadero en este estado. Para hacer esto, utilizaremos el script: se establecerá cuando la batería alcance el 30 por ciento.


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

Después de la primera ejecución, el script creará un estado y se puede seleccionar en las características.



Este letrero se mostrará en imágenes de accesorios.



y detalles del dispositivo


Lámparas


Las bombillas son diferentes: brillantes, cálidas, rojas. Hay 4 casos:


  • Simple: controlado por encendido y apagado
  • Regulable: también controlado por nivel de brillo
  • Con temperatura: es posible controlar la temperatura del brillo
  • Color: puede controlar el color del brillo

Para cada uno de estos casos, hay una característica correspondiente en el servicio de Bombilla:


  • Encendido - encendido / apagado
  • Brillo - nivel de brillo
  • Hue - sombra
  • Saturación - Saturación
  • Temperatura de color - temperatura de color

En el caso simple, en la característica "Encendido", indicamos el estado responsable de encender y apagar.



Si la lámpara es regulable, también indicamos el estado con el nivel de brillo.



¡Además de asignar estados correctos, es importante observar el intervalo de valores aceptables!


Ejemplo: en algunos casos, el estado responsable del brillo de la lámpara puede tomar valores de 0 a 255, pero en Homekit estos valores están limitados a un intervalo de 0 a 100. Para este caso, puede usar las funciones de conversión del controlador Yahka. La función "nivel255" simplemente convierte el intervalo de valores 0..255 en el intervalo 0..100 (y viceversa).


Las siguientes dificultades pueden surgir si su lámpara es de color, pero el color utilizado es RGB. Puede ser tres estados diferentes o un número (o cadena). En este caso, deberá convertir de un espacio de color RGB a otro espacio XYB (HomeKit usa este espacio), o al plano XY.


Para hacer esto, necesita hacer 2 nuevos estados (Hue y Saturación), en los que convertiremos los valores del estado RGB y viceversa.


La secuencia de comandos resultante para el color es
 //       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 temperatura de color se puede hacer más fácilmente: si se conoce el rango de valores disponibles para su lámpara, se puede convertir al intervalo disponible para HomeKit (a través de la función scaleInt ).


Servicio de bombilla



Más profundo en la lámpara



Termostato


Termostato: un dispositivo para mantener la temperatura establecida (servicio del termostato). En consecuencia, la característica principal del termostato es la temperatura deseada (temperatura objetivo). Además de la temperatura establecida, se puede indicar la temperatura actual (CurrentTemperature), que es de naturaleza informativa (ya que el dispositivo solo la lee desde los sensores).


Desde la aplicación Inicio, la temperatura objetivo se establece en el termostato y se rastrea la temperatura actual. En mi termostato (Zont) solo había estos dos estados: estaban disponibles a través de la API de servicio en la nube.


Por la belleza de mostrar el dispositivo en HomeKit, tuve que agregar un par de constantes: el estado actual de calentamiento está activo (1), el estado objetivo de calentamiento es automático (3).


Servicio de termostato



Selección de temperatura


Puertas


Con una puerta de garaje (servicio GarageDoorOpener), todo es más complicado que con un termostato.


De las características disponibles, la puerta tiene un estado objetivo (TargetDoorState), que indica nuestro deseo de que la puerta esté "abierta" o "cerrada". Pero también debe mostrar correctamente el estado actual de la puerta (CurrentDoorState): ¿están abiertos o cerrados, o tal vez se abren o cierran?


En mi caso, las puertas se abrieron a través de mqtt en ioBroker con varios estados de información:


  • signo de apertura de puerta (OB)
  • señal de movimiento de la puerta (LW)

Estados de control de puertas de garaje


Gracias a estos estados, puede calcular el estado actual de la puerta:


  • si no hay OB ni DV, entonces las puertas están cerradas
  • si no hay OB y ​​hay un DV, entonces las puertas se abren
  • si hay un OB y ​​no DV, entonces las puertas están abiertas
  • si hay un OB y ​​hay un DV, entonces la puerta se cierra

Para enviar una señal para abrir y cerrar la puerta, tengo dos estados separados (sería posible administrar con un estado, pero tengo dos), que envían un mensaje a través de mqtt al controlador de control de la puerta:


  • señal de apertura
  • señal de cierre

Para enviar una señal, debe simular un botón de "clic": establezca el valor en verdadero y, después de un tiempo, reinícielo en falso. En este sentido, para integrarse con HomeKit, era necesario crear otro estado: el "estado objetivo de la puerta", cuando se cambia, se enviará la señal correspondiente.


El signo de apertura de la puerta se puede reemplazar por el estado objetivo (es decir, para qué apuntará el objetivo)


  • si la CA está "cerrada" y no hay DV, entonces la puerta está cerrada
  • si la CA está "cerrada" y hay un DV, entonces las puertas se abren
  • si la CA está "abierta" y no hay DV, entonces la puerta está abierta
  • si la CA está "abierta" y hay un DV, entonces la puerta se cierra

También crearemos un estado separado "estado de puerta actual", y lo rellenaremos en el script dependiendo del valor de los signos y del estado de destino.


Script de cambio de estado para puertas de garaje
 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);      }  } }); 

Después de ejecutar el script, puede configurar las características del servicio de puerta de garaje:


Servicio GarageDoorOpener



Cámara


Para agregar una cámara a HomeKit, se utilizará el método "clásico". Se organiza la transmisión de la imagen desde la cámara a través del módulo ffmpeg. A través de él, la secuencia de entrada se codificará, cifrará y se entregará a Homekit.


En primer lugar, debe instalar ffmpeg en el servidor donde se encuentra ioBroker.


Para cada plataforma, se instala de diferentes maneras, puede ensamblarlo desde la fuente o buscar un ensamblaje listo, por ejemplo, aquí: https://www.johnvansickle.com/ffmpeg/ Debe tener un codificador libx264. Puede verificar el codificador después de instalar ffmpeg con el comando:


 ffmpeg -codecs | grep 264 

Los resultados deben contener una línea del formulario:


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

Para Raspberry Pi 3, puede usar el ensamblaje listo para usar, que tiene un códec compatible con la codificación de hardware de GPU (h264_omx, consume menos recursos). Ponlo así:


 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 

Ambos códecs están presentes en este ensamblado: libx264 y h264_omx


A continuación, debe obtener la dirección de la transmisión de la cámara que debe transmitirse (este paso está más allá del alcance de este artículo). Por ejemplo, puede tomar una transmisión pública preparada .


Ahora agregue la cámara a Yahka, indique la dirección de la transmisión y, si es necesario, cambie los parámetros del códec, el tamaño de la imagen y la velocidad de fotogramas.


Importante: las combinaciones de parámetros son muy importantes para la visualización correcta de la cámara en Homekit y dependen de la cámara y la transmisión. También afecta el rendimiento del sistema, ya que El proceso de ejecución de ffmpeg consume muchos recursos.


Agregar una cámara


Configuración de transmisión


Las cámaras se agregan como dispositivos separados fuera de la puerta de enlace, y deben agregarse de la misma manera que la puerta de enlace


Miniatura de la cámara


Transmitido desde la cámara


Bono


Como beneficio adicional, hablaré sobre el uso inusual de la transmisión de la cámara.


Usando el mismo ffmpeg, en lugar de la cámara, puede intentar transmitir la imagen, cualquier imagen. Estas imágenes también se pueden combinar con la transmisión de video. Puede mostrar texto, gráficos y otra información en la imagen.


Texto superpuesto


Como resultado, puede obtener un tablero interesante. Y si actualiza la imagen periódicamente, obtiene datos dinámicos.


Como ejemplo, saqué un gráfico de cambios en algunos indicadores en forma de una imagen (archivo en el disco). Este gráfico se actualiza una vez por minuto y sobrescribe la imagen en el archivo.


(las funciones createImage1, createImage2, la formación de un gráfico y la imposición de texto en una imagen están fuera del alcance de este artículo, pero daré una pista).

Te diré cómo puedes obtener un gráfico en forma de imagen.


IoBroker tiene una forma estándar de crear gráficos: el controlador Flot. Este controlador está emparejado con un controlador web y muestra el resultado en un navegador. Pero para obtener el gráfico creado en el servidor (en el script) como una imagen, se necesita un controlador PhantomJS adicional, que toma una "captura de pantalla" de la página (en la que dibujaremos un gráfico Flot).


Pero hablaré sobre una forma alternativa de construir gráficos en el servidor en un script.


Existe una biblioteca de Chart.js http://www.chartjs.org/ que le permite dibujar gráficos atractivos en el navegador (ejemplos http://www.chartjs.org/samples/latest/ ).


Para dibujar, utiliza el "lienzo" (lienzo, lienzo) del navegador. Por lo tanto, para dibujar con esta biblioteca en el servidor, debe usar la versión "servidor" de los objetos "lienzo" y DOM. Esto es lo que hace el paquete chartjs-node ( https://github.com/vmpowerio/chartjs-node ).


La dependencia principal para este paquete es el paquete de lienzo ( https://github.com/Automattic/node-canvas ), que debe instalarse globalmente (o en la carpeta iobroker). Es importante instalar todas las dependencias para la plataforma donde coloque https://github.com/Automattic/node-canvas#compiling .


Después de eso, puede agregar chart.js, módulos chartjs-node en la configuración del controlador de JavaScript. Deben instalarse correctamente, sin errores. De lo contrario, lidie con los errores y resuélvalos.


Y luego, puedes escribir un guión.


A continuación se muestra un script para un ejemplo, como incluye el uso del controlador Historial y usa nombres de estado específicos.


Atencion El guión tiene construcciones complicadas para principiantes: Promise. Esta es una forma conveniente de no escribir funciones con devolución de llamada, sino de hacer cadenas de pasos. Entonces, por ejemplo, es conveniente hacer esto para obtener datos de la historia de los estados.


 '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); }); } 

Transmitir imagen en lugar de transmitir


La imagen en miniatura se actualiza aproximadamente una vez por minuto, por lo que configuraremos la imagen para que se actualice cada 10 segundos:


 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 peculiaridad es que en el proceso de transmisión de la imagen, es necesario reemplazar la imagen lo suficientemente rápido para que ffmpeg no se bloquee :) Por lo tanto, la imagen se forma primero en un archivo, y luego el archivo se renombra al que se utilizó para la traducción.


Ahora, en la configuración de la cámara, especifique el nombre del archivo generado en lugar de la dirección de transmisión y agregue la configuración de que la imagen está "actualizada" (parámetro "-loop 1"). Esto se configura en las propiedades avanzadas de la cámara. Estas propiedades no son más que opciones de línea de comandos para ejecutar ffmpeg. Por lo tanto, se deben encontrar combinaciones de parámetros en la documentación y ejemplos de ffmpeg.


Las propiedades se dividen en 2 tipos: para recibir una "vista previa" (una pequeña imagen de la cámara) y para transmitir. Por lo tanto, puede especificar diferentes archivos de origen de imagen, por ejemplo con diferentes detalles.


Opciones de inicio de ffmpeg


Imagen de transmisión en vivo


Conclusión


ioBroker . , . , , .


, Yahka , Material. , HomeKit.


Yahka, HomeKit — Ham, HomeBridge . .

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


All Articles