Hogar inteligente: construya gráficos de agua y electricidad en Home Assistant


Cada vez que recibo una factura por electricidad y agua, me pregunto: ¿mi familia realmente consume tanto? Bueno, sí, el baño tiene suelo radiante y una caldera, pero no se alimentan constantemente. También parece que ahorramos agua (aunque también nos encantan las salpicaduras en el baño). Hace unos años, ya conecté medidores de agua y electricidad a una casa inteligente, pero esto se atascó en esto. Hands llegó al análisis del consumo solo ahora, sobre el que, de hecho, se trata este artículo.

Recientemente cambié a Home Assistant como un sistema de casa inteligente. Una de las razones era solo la oportunidad de organizar la recopilación de una gran cantidad de datos con la posibilidad de construir convenientemente varios tipos de gráficos.

La información descrita en este artículo no es nueva, todas estas cosas con diferentes salsas ya se han descrito en Internet. Pero cada artículo, como regla, describe solo un enfoque o aspecto. Tuve que comparar todos estos enfoques y elegir el más adecuado yo mismo. El artículo aún no proporciona información completa sobre la recopilación de datos, pero es una especie de sinopsis de cómo lo hice. De modo que las críticas constructivas y las sugerencias de mejora son bienvenidas.

Formulación del problema


Por lo tanto, el objetivo del ejercicio de hoy es obtener hermosos gráficos del consumo de agua y electricidad:

  • Cada hora durante 2 días.
  • Diariamente por 2 semanas
  • (opcional) semanal y mensual

Aquí es donde nos esperan algunas dificultades:

  • , , . .

    , , . home assistant, , mini-graph-card, :

    • ( , )
    • ( , )
  • , home assistant SQLite ( , , MySQL Postgres), . , , json

    {"entity_id": "sensor.water_cold_hourly", "old_state": {"entity_id": "sensor.water_cold_hourly", "state": "3", "attributes": {"source": "sensor.water_meter_cold", "status": "collecting", "last_period": "29", "last_reset": "2020-02-23T21:00:00.022246+02:00", "meter_period": "hourly", "unit_of_measurement": "l", "friendly_name": "water_cold_hourly", "icon": "mdi:counter"}, "last_changed": "2020-02-23T19:05:06.897604+00:00", "last_updated": "2020-02-23T19:05:06.897604+00:00", "context": {"id": "aafc8ca305ba4e49ad4c97f0eddd8893", "parent_id": null, "user_id": null}}, "new_state": {"entity_id": "sensor.water_cold_hourly", "state": "4", "attributes": {"source": "sensor.water_meter_cold", "status": "collecting", "last_period": "29", "last_reset": "2020-02-23T21:00:00.022246+02:00", "meter_period": "hourly", "unit_of_measurement": "l", "friendly_name": "water_cold_hourly", "icon": "mdi:counter"}, "last_changed": "2020-02-23T19:11:11.251545+00:00", "last_updated": "2020-02-23T19:11:11.251545+00:00", "context": {"id": "0de64b8af6f14bb9a419dcf3b200ef56", "parent_id": null, "user_id": null}}}

    ( , ), . SDM220 10-15 , 8. , . .. 100-200 . , ( home assistant raspberry PI), .
  • , . . (RS232/RS485/Modbus/Zigbee) .

    ( ), X - . . , . , , home assistant, , ( home assistant).

1


Primero, veamos qué se proporciona al asistente de casa de inmediato. Medir el consumo durante un período es una funcionalidad muy demandada. Por supuesto, se realizó hace mucho tiempo en forma de un componente especializado: utility_meter.

La esencia del componente es que en su interior inicia la variable current_accumulated_value y la restablece después de un período específico (hora / semana / mes). El componente mismo monitorea la variable entrante (el valor de algún tipo de sensor), se suscribe a los cambios en el valor en sí mismo: solo obtiene el resultado final. Esto se describe en unas pocas líneas en el archivo de configuración.

utility_meter:
  water_cold_hour_um:
    source: sensor.water_meter_cold
    cycle: hourly
  water_cold_day_um:
    source: sensor.water_meter_cold
    cycle: daily

Aquí sensor.water_meter_cold es el valor actual del contador en litros, que obtengo directamente de la pieza de hierro por mqtt. El diseño crea 2 sensores nuevos water_cold_hour_um y water_cold_day_um, que acumulan lecturas por hora y por día, restableciéndolos después de un período. Aquí hay un gráfico de batería de media hora.

imagen

El código de gráfico horario y diario para la interfaz de usuario de lovelace se ve así:

      - type: history-graph
        title: 'Hourly water consumption using vars'
        hours_to_show: 48
        entities:
          - sensor.water_hour

      - type: history-graph
        title: 'Daily water consumption using vars'
        hours_to_show: 360
        entities:
          - sensor.water_day

En realidad, el problema de este enfoque radica en este algoritmo. Como mencioné, para cada valor de entrada (la lectura actual del medidor para cada litro siguiente), se genera 1kb de registro en la base de datos. Cada medidor de servicios también genera un nuevo valor, que también se suma a la base de datos. Si quiero recopilar lecturas por hora / día / semana / mes, y para varios elevadores de agua, e incluso agregar un montón de medidores eléctricos, esta será una gran cantidad de datos. Bueno, más precisamente, no hay muchos datos, pero dado que el asistente doméstico escribe en la base de datos un montón de información adicional, el tamaño de la base de datos crecerá a pasos agigantados. Me temo incluso estimar el tamaño de la base para gráficos semanales y mensuales.

Además, el medidor de utilidad por sí solo no resuelve la tarea. El gráfico de valores que da el medidor de utilidad es una función monotónicamente creciente que se restablece a 0 cada hora. Necesitamos una tabla de consumo fácil de usar, cuántos litros se consumieron durante el período. El componente estándar de historial gráfico no sabe cómo hacer esto, pero el componente externo de minitarjeta gráfica puede ayudarnos.

Este es el código de tarjeta para lovelace-UI:

      - aggregate_func: max
        entities:
          - color: var(--primary-color)
            entity: sensor.water_cold_hour_um
        group_by: hour
        hours_to_show: 48
        name: "Hourly water consumption aggregated by utility meter"
        points_per_hour: 1
        show:
          graph: bar
        type: 'custom:mini-graph-card'

Además de las configuraciones estándar como el nombre del sensor, el tipo de gráfico, el color (no me gustó la naranja estándar), es importante tener en cuenta 3 configuraciones:

  • group_by: hour: el gráfico se generará con las columnas alineadas al comienzo de la hora
  • points_per_hour: 1 - una barra por cada hora
  • , aggregate_func: max — .



No preste atención a una serie de columnas a la izquierda; este es el comportamiento estándar de un componente si no hay datos. Y no había datos: solo activé la recopilación de datos del medidor de utilidad hace solo un par de horas solo por el bien de este artículo (hablaré sobre mi enfoque actual un poco más adelante).

En esta imagen, quería mostrar que a veces la visualización de datos incluso funciona, y las barras realmente reflejan los valores correctos. Pero eso no es todo. La columna seleccionada para el intervalo de 11 a 12 de la mañana, por alguna razón, muestra 19 litros, aunque en el gráfico con dientes un poco más alto para el mismo período vemos un consumo de 62 litros del mismo sensor. O un insecto o las manos están torcidas. Pero no entiendo por qué se rompieron los datos de la derecha: el consumo allí era normal, lo que también es visible en el horario de los dientes.

En general, no pude lograr la credibilidad de este enfoque: el gráfico casi siempre muestra algún tipo de herejía.

El mismo código para el sensor de día.

      - aggregate_func: max
        entities:
          - color: var(--primary-color)
            entity: sensor.water_cold_day_um
        group_by: interval
        hours_to_show: 360
        name: "Daily water consumption aggregated by utility meter"
        points_per_hour: 0.0416666666
        show:
          graph: bar
        type: 'custom:mini-graph-card'

Tenga en cuenta que el parámetro group_by está establecido en intervalo, y el parámetro points_per_hour rige todo. Y este es otro problema de este componente: points_per_hour funciona bien en gráficos en una hora o menos, pero asqueroso a grandes intervalos. Entonces, para obtener una columna en un día, tuve que ingresar el valor 1/24 = 0.04166666. No estoy hablando de gráficos semanales y mensuales.

Enfoque 2


Solo descubriendo al asistente de casa, me encontré con este video aquí:


Un amigo recopila datos de consumo de varios tipos de puntos de venta de Xiaomi. Su tarea es un poco más fácil: solo muestra el valor del consumo para hoy, ayer y para el mes. No se requieren horarios.

Dejemos de lado la discusión sobre la integración manual de los valores de potencia instantáneos: ya escribí sobre la "precisión" de este enfoque. No está claro por qué no usó los valores de consumo acumulados, que ya se recogen en el mismo punto de venta. En mi opinión, la integración dentro de la pieza de hierro funcionará mejor.

Del video tomamos la idea de calcular manualmente el consumo para el período. El campesino solo cuenta los valores para hoy y ayer, pero iremos más allá e intentaremos dibujar un gráfico. La esencia del método propuesto en mi caso es la siguiente.

  • ___,
  • ( ) . — , .
  • “” ___ .

Todo esto se puede hacer a través de w ... a través del asistente del hogar.

Tendrá que escribir más código que en el enfoque anterior. Para comenzar, obtengamos estas mismas "variables". Fuera de la caja, no tenemos la entidad "variable", pero puede usar los servicios del agente mqtt. Enviaremos valores con el indicador retener = verdadero allí; esto guardará el valor dentro del corredor, y puede extraerlo en cualquier momento, incluso cuando el asistente de inicio se reinicie. Hice contadores de horas y días de inmediato.

- platform: mqtt
  state_topic: "test/water/hour"
  name: water_hour
  unit_of_measurement: l

- platform: mqtt
  state_topic: "test/water/hour_begin"
  name: water_hour_begin
  unit_of_measurement: l

- platform: mqtt
  state_topic: "test/water/day"
  name: water_day
  unit_of_measurement: l

- platform: mqtt
  state_topic: "test/water/day_begin"
  name: water_day_begin
  unit_of_measurement: l

Toda la magia ocurre en la automatización, que se ejecuta cada hora y cada noche, respectivamente.

- id: water_new_hour
  alias: water_new_hour
  initial_state: true
  trigger:
    - platform: time_pattern
      minutes: 0
  action:
    - service: mqtt.publish
      data:
        topic: "test/water/hour"
        payload_template: >
          {{ (states.sensor.water_meter_cold.state|int) - (states.sensor.water_hour_begin.state|int) }}
        retain: true
    - service: mqtt.publish
      data:
        topic: "test/water/hour_begin"
        payload_template: >
          {{ states.sensor.water_meter_cold.state }}
        retain: true

- id: water_new_day
  alias: water_new_day
  initial_state: true
  trigger:
    - platform: time
      at: "00:00:00"
  action:
    - service: mqtt.publish
      data:
        topic: "test/water/day"
        payload_template: >
          {{ (states.sensor.water_meter_cold.state|int) - (states.sensor.water_day_begin.state|int) }}
        retain: true
    - service: mqtt.publish
      data:
        topic: "test/water/day_begin"
        payload_template: >
          {{ states.sensor.water_meter_cold.state }}
        retain: true

Ambas automatizaciones realizan 2 acciones:

  • El valor para el intervalo se calcula como la diferencia entre el valor inicial y el final
  • Actualizar el valor base para el próximo intervalo

La gráfica en este caso se resuelve mediante el gráfico de historia habitual:

      - type: history-graph
        title: 'Hourly water consumption using vars'
        hours_to_show: 48
        entities:
          - sensor.water_hour

      - type: history-graph
        title: 'Daily water consumption using vars'
        hours_to_show: 360
        entities:
          - sensor.water_day

Se ve así:



en principio, esto ya es lo que necesita. La ventaja de este método es que los datos se generan una vez por intervalo. Aquellos. solo 24 entradas por día para el gráfico horario.

Desafortunadamente, esto todavía no resuelve el problema general de una base en crecimiento. Si quiero un horario de consumo mensual, tendré que almacenar datos durante al menos un año. Y dado que el asistente doméstico proporciona solo una configuración para la duración del almacenamiento de toda la base de datos, esto significa que TODOS los datos del sistema deberán almacenarse durante todo un año. Por ejemplo, durante un año consumo 200 metros cúbicos de agua, lo que significa que hay 200,000 registros en la base de datos. Y si tiene en cuenta otros sensores, la figura se vuelve indecente en absoluto.

Enfoque 3


Afortunadamente, las personas inteligentes ya han resuelto este problema escribiendo la base de datos InfluxDB. Esta base de datos está especialmente optimizada para almacenar datos basados ​​en el tiempo y es ideal para almacenar valores de diferentes sensores. El sistema también proporciona un lenguaje de consulta similar a SQL que le permite seleccionar valores de la base de datos y luego agregarlos de varias maneras. Finalmente, se pueden almacenar diferentes datos en diferentes momentos. Por ejemplo, las lecturas que cambian con frecuencia, como la temperatura o la humedad, se pueden almacenar durante solo un par de semanas, mientras que las lecturas diarias del consumo de agua se pueden almacenar durante todo un año.

Además de InfluxDB, las personas inteligentes también inventaron Grafana, un sistema de gráficos basado en datos de InfluxDB. Grafana puede dibujar diferentes tipos de gráficos, personalizarlos en detalle y, lo que es más importante, estos gráficos se pueden "pegar" en el asistente de inicio de Lovelace-UI.

Inspírate aquí y aquí . Los artículos describen en detalle el proceso de instalación y conexión de InfluxDB y Grafana al asistente doméstico. Me enfocaré en resolver mi problema específico.

Entonces, antes que nada, comencemos a sumar el valor del contador en influxDB. Una parte de la configuración del asistente para el hogar (en este ejemplo, me divertiré no solo con agua fría, sino también con agua caliente):

influxdb:
  host: localhost
  max_retries: 3
  default_measurement: state
  database: homeassistant
  include:
    entities:
      - sensor.water_meter_hot
      - sensor.water_meter_cold

Desactive el almacenamiento de los mismos datos en la base de datos interna del asistente doméstico para no volver a inflarlos:

recorder:
  purge_keep_days: 10
  purge_interval: 1
  exclude:
    entities:
      - sensor.water_meter_hot
      - sensor.water_meter_cold

Ahora vamos a la consola InfluxDB y configuramos nuestra base de datos. En particular, debe configurar cuánto tiempo se almacenarán estos o esos datos. Esto está regulado por el llamado política de retención: es similar a las bases de datos dentro de la base de datos principal, y cada base de datos interna tiene su propia configuración. Por defecto, todos los datos se almacenan en una política de retención llamada autogen, estos datos se almacenarán durante una semana. Me gustaría que los datos por hora se almacenen durante un mes, los semanales durante un año y los mensuales nunca deberían eliminarse en absoluto. Crear una política de retención adecuada

CREATE RETENTION POLICY "month" ON "homeassistant" DURATION 30d REPLICATION 1
CREATE RETENTION POLICY "year" ON "homeassistant" DURATION 52w REPLICATION 1
CREATE RETENTION POLICY "infinite" ON "homeassistant" DURATION INF REPLICATION 1

Ahora, de hecho, el truco principal es la agregación de datos mediante consultas continuas. Este es un mecanismo que inicia automáticamente una consulta a intervalos específicos, agrega datos para esta consulta y agrega el resultado a un nuevo valor. Veamos un ejemplo (escribo en una columna para facilitar la lectura, pero de hecho tuve que ingresar este comando en una línea)

CREATE CONTINUOUS QUERY cq_water_hourly ON homeassistant 
BEGIN 
  SELECT max(value) AS value 
  INTO homeassistant.month.water_meter_hour 
  FROM homeassistant.autogen.l 
  GROUP BY time(1h), entity_id fill(previous) 
END

Este comando:

  • Crea una consulta continua llamada cq_water_cold_hourly en la base de datos de homeassistant
  • La solicitud se ejecutará cada hora (tiempo (1h))
  • La solicitud recuperará todos los datos de la medición 'a homeassistant.autogen.l (litros), incluidas las lecturas de agua fría y caliente
  • Los datos agregados se agruparán por entity_id, que creará valores separados para agua fría y caliente.
  • , max(value)
  • homeassistant.month.water_meter_hour, month retention policy . entity_id value

Por la noche o cuando no hay nadie en casa, no hay consumo de agua y, en consecuencia, tampoco hay nuevas entradas en homeassistant.autogen.l. Para evitar valores perdidos en consultas regulares, puede usar el relleno (anterior). Esto obligará a InfluxDB a usar el valor de la última hora.

Desafortunadamente, la consulta continua tiene una peculiaridad: el truco de relleno (anterior) no funciona y los registros simplemente no se crean. Además, este es un problema insuperable que se ha discutido durante más de un año . Trataremos este problema más tarde y dejaremos que el relleno (anterior) en consulta continua sea: no interfiere.

Veamos qué sucedió (por supuesto, debe esperar un par de horas):

> select * from homeassistant.month.water_meter_hour group by entity_id
...
name: water_meter_hour
tags: entity_id=water_meter_cold
time                 value
----                 -----
...
2020-03-08T01:00:00Z 370511
2020-03-08T02:00:00Z 370513
2020-03-08T05:00:00Z 370527
2020-03-08T06:00:00Z 370605
2020-03-08T07:00:00Z 370635
2020-03-08T08:00:00Z 370699
2020-03-08T09:00:00Z 370761
2020-03-08T10:00:00Z 370767
2020-03-08T11:00:00Z 370810
2020-03-08T12:00:00Z 370818
2020-03-08T13:00:00Z 370827
2020-03-08T14:00:00Z 370849
2020-03-08T15:00:00Z 370921

Tenga en cuenta que los valores en la base de datos se almacenan en UTC, por lo tanto, en esta lista difieren en 3 horas: los valores para las 7 a.m. en la salida InfluxDB corresponden a los valores para las 10 a.m. en los gráficos anteriores. También tenga en cuenta que entre las 2 y las 5 de la mañana simplemente no hay registros: esta es la misma característica de la consulta continua.

Como puede ver, el valor agregado también es una secuencia monótonamente creciente, solo los registros son menos frecuentes, una vez por hora. Pero esto no es un problema: podemos escribir otra consulta que produzca los datos correctos para el gráfico.

SELECT difference(max(value)) 
FROM homeassistant.month.water_meter_hour 
WHERE entity_id='water_meter_cold' and time >= now() -24h 
GROUP BY time(1h), entity_id 
fill(previous)

Descifraré:

  • De la base de datos homeassistant.month.water_meter_hour, extraemos los datos de entity_id = 'water_meter_cold' para el último día (hora> = ahora () -24h).
  • Como mencioné en la secuencia homeassistant.month.water_meter_hour, pueden faltar algunas entradas. Regeneraremos estos datos ejecutando una consulta con el tiempo GROUP BY (1h). Este tiempo de relleno (anterior) funcionará según sea necesario, generando los datos faltantes (la función tomará el valor anterior)
  • Lo más importante en esta solicitud es la función de diferencia, que calculará la diferencia entre las marcas por hora. Por sí solo, no funciona y requiere una función agregada. Deje que sea el max () utilizado antes.

El resultado de la ejecución se ve así

name: water_meter_hour
tags: entity_id=water_meter_cold
time                 difference
----                 ----------
...
2020-03-08T02:00:00Z 2
2020-03-08T03:00:00Z 0
2020-03-08T04:00:00Z 0
2020-03-08T05:00:00Z 14
2020-03-08T06:00:00Z 78
2020-03-08T07:00:00Z 30
2020-03-08T08:00:00Z 64
2020-03-08T09:00:00Z 62
2020-03-08T10:00:00Z 6
2020-03-08T11:00:00Z 43
2020-03-08T12:00:00Z 8
2020-03-08T13:00:00Z 9
2020-03-08T14:00:00Z 22
2020-03-08T15:00:00Z 72

De 2 a 5 de la mañana (UTC) no hubo consumo. Sin embargo, la consulta devolverá el mismo valor de consumo debido al relleno (anterior), y la función de diferencia restará este valor de sí misma y obtendrá 0 en la salida, que en realidad es necesaria.

Lo único que queda es construir un horario. Para hacer esto, abra Grafana, abra algunos paneles existentes (o cree un nuevo), cree un nuevo panel. La configuración del gráfico será así.



Mostraré datos sobre agua fría y caliente en un gráfico. La solicitud es exactamente la misma que describí anteriormente.

Los parámetros de visualización se configuran de la siguiente manera. Tendré un gráfico con líneas, que va por las escaleras. Explicaré el parámetro Stack justo debajo. Hay un par de opciones de visualización más abajo, pero no son tan interesantes.



Para agregar el horario recibido al asistente de hogar que necesita:

  • . -
  • , share
  • embed
  • current time range — URL
  • . light
  • URL lovelace-UI

      - type: iframe
        id: graf_water_hourly
        url: "http://192.168.10.200:3000/d-solo/rZARemQWk/water?orgId=1&panelId=2&from=now-2d&to=now&theme=light"

Tenga en cuenta que el intervalo de tiempo (últimos 2 días) se establece aquí, y no en la configuración del tablero.

El gráfico se ve así. No he usado agua caliente en los últimos 2 días, por lo que solo se dibuja un gráfico de agua fría.



No decidí por mí mismo qué horario me gusta más, una línea de paso o barras reales. Por lo tanto, simplemente daré un ejemplo de un gráfico de consumo diario, solo que esta vez en columnas. Las solicitudes se crean de la misma manera que se describe anteriormente. Los parámetros de visualización son los siguientes:



Este gráfico se ve así:



Entonces, sobre el parámetro Stack. En este gráfico, se dibuja una columna de agua fría encima de una columna de agua caliente. La altura total corresponde al consumo total de agua fría y caliente para el período.

Todos los gráficos mostrados son dinámicos. Puede pasar el mouse sobre un punto de interés y ver los detalles y el valor en un punto específico.

Desafortunadamente, un par de cucharas de alquitrán no pudieron. En el gráfico de barras (en contraste con el gráfico con líneas de paso), la mitad de la barra no está en el medio del día, sino a las 00:00. Aquellos. la mitad izquierda de la columna se dibuja en lugar del día anterior. Entonces, los gráficos para el sábado y el domingo se dibujan un poco a la izquierda que la zona azulada. Hasta que descubrí cómo ganarlo.

Otro problema es la incapacidad de trabajar correctamente a intervalos mensuales. El hecho es que la duración de la hora / día / semana es fija, pero la duración del mes es diferente cada vez. InfluxDB solo puede funcionar a intervalos regulares. Hasta ahora, mi cerebro era suficiente para establecer un intervalo fijo de 30 días. Sí, el gráfico flotará un poco durante el año y las columnas no se corresponderán exactamente con los meses. Pero dado que esto es interesante para mí simplemente como un dispositivo de visualización, entonces estoy de acuerdo con eso.

Veo al menos dos soluciones:

  • Puntuación en horarios mensuales y límite semanal. 52 bares semanales para el año se ven bastante bien
  • №2, . . — .



No sé por qué, pero estoy trabajando en ese tipo de gráficos. Muestran que la vida está en pleno apogeo y que todo está cambiando. Ayer fue mucho, hoy no es suficiente, mañana será otra cosa. Queda por trabajar con los hogares en el tema del consumo. Pero incluso con los apetitos actuales, solo una cifra grande e incomprensible en el pago ya se está convirtiendo en una imagen bastante clara del consumo.

A pesar de los casi 20 años de carrera como programador, prácticamente no me crucé con las bases de datos. Por lo tanto, la instalación de una base de datos externa parecía algo tan abstruso e incomprensible. Todo cambió con el artículo mencionado anteriormente : resultó que arruinar una herramienta adecuada se realiza en un par de clics, y con una herramienta especializada la tarea de graficar se vuelve un poco más fácil.

En el titular, mencioné el consumo de electricidad. Lamentablemente, por el momento no puedo dar un solo gráfico. Un contador SDM120 está muerto y el otro tiene errores al acceder a través de Modbus. Sin embargo, esto no afecta el tema de este artículo: los gráficos se construirán de la misma manera que para el agua.

En este artículo, cité los enfoques que probé yo mismo. Seguramente hay algunas otras formas de organizar la recopilación y visualización de datos que no conozco. Cuéntamelo en los comentarios, será muy interesante para mí. Estaré encantado de críticas constructivas y nuevas ideas. Espero que el material presentado también ayude a alguien.

All Articles