Casa Inteligente: Crie Cartas de Água e Eletricidade no Assistente Doméstico


Toda vez que recebo uma fatura de eletricidade e água, imagino - minha família está realmente consumindo tanto? Bem, sim, o banheiro tem piso aquecido e caldeira, mas eles não se excitam constantemente. Também parecemos economizar água (embora também gostemos de espirrar no banheiro). Alguns anos atrás, eu já conectei medidores de água e eletricidade a uma casa inteligente, mas isso estava preso a isso. As mãos chegaram à análise do consumo apenas agora, sobre as quais, de fato, trata-se deste artigo.

Recentemente, mudei para o Home Assistant como um sistema doméstico inteligente. Um dos motivos foi apenas a oportunidade de organizar a coleta de uma grande quantidade de dados com a possibilidade de construir convenientemente vários tipos de gráficos.

As informações descritas neste artigo não são novas, todas essas coisas com molhos diferentes já foram descritas na Internet. Mas cada artigo, como regra, descreve apenas uma abordagem ou aspecto. Eu tive que comparar todas essas abordagens e escolher a mais adequada. O artigo ainda não fornece informações abrangentes sobre a coleta de dados, mas é um tipo de sinopse de como eu fiz. Portanto, críticas construtivas e sugestões de melhoria são bem-vindas.

Formulação do problema


Portanto, o objetivo do exercício de hoje é obter belos gráficos do consumo de água e eletricidade:

  • De hora em hora por 2 dias
  • Diariamente por 2 semanas
  • (opcional) semanal e mensalmente

É aqui que algumas dificuldades nos esperam:

  • , , . .

    , , . 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


Primeiro, vamos ver o que o assistente de casa é fornecido imediatamente. Medir o consumo durante um período é uma funcionalidade altamente exigida. Obviamente, isso foi realizado há muito tempo na forma de um componente especializado - utility_meter.

A essência do componente é que dentro dele inicia a variável current_accumulated_value e a redefine após um período especificado (hora / semana / mês). O componente em si monitora a variável de entrada (o valor de algum tipo de sensor), assina as alterações no próprio valor - você apenas obtém o resultado final. Essa coisa é descrita em apenas algumas linhas no arquivo de configuração.

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

Aqui sensor.water_meter_cold é o valor atual do contador em litros, que eu recebo diretamente do pedaço de ferro por mqtt. O design cria 2 novos sensores water_cold_hour_um e water_cold_day_um, que acumulam leituras horárias e diárias, restaurando-as após um período. Aqui está um gráfico de bateria de meia hora.

imagem

O código do gráfico horário e diário da interface do usuário lovelace é semelhante a este:

      - 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

Na verdade, o problema dessa abordagem está nesse algoritmo. Como mencionei, para cada valor de entrada (a leitura atual do medidor para cada litro seguinte), 1kb de registro é gerado no banco de dados. Cada medidor de utilidade também gera um novo valor, que também é adicionado ao banco de dados. Se eu quiser coletar leituras horárias / diárias / semanais / mensais, e para vários bebedouros, e até adicionar um monte de medidores elétricos - serão muitos dados. Bem, mais precisamente, não há muitos dados, mas como o assistente de casa grava no banco de dados várias informações desnecessárias, o tamanho do banco de dados aumenta aos trancos e barrancos. Receio até estimar o tamanho da base para gráficos semanais e mensais.

Além disso, o medidor de utilidade sozinho não resolve a tarefa. O gráfico de valores que o medidor de utilidade fornece é uma função monotonicamente crescente que redefine para 0 a cada hora. Precisamos de um gráfico de consumo fácil de usar, quantos litros foram consumidos durante o período. O componente padrão do gráfico histórico não sabe como fazer isso, mas o componente externo da placa gráfica mini pode nos ajudar.

Este é o código do cartão para a interface do usuário lovelace:

      - 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'

Além das configurações padrão, como o nome do sensor, tipo de gráfico, cor (não gostei da laranja padrão), é importante observar três configurações:

  • group_by: hour - o gráfico será gerado com as colunas alinhadas no início da hora
  • points_per_hour: 1 - uma barra a cada hora
  • , aggregate_func: max — .



Não preste atenção em várias colunas à esquerda - este é o comportamento padrão de um componente se não houver dados. E não havia dados - apenas ativei a coleta de dados do medidor de utilidade apenas algumas horas atrás, apenas para fins deste artigo (em breve, mostrarei minha abordagem atual).

Nesta imagem, eu queria mostrar que, às vezes, a exibição de dados funciona e as barras realmente refletem os valores corretos. Mas isso não é tudo. A coluna selecionada para o intervalo das 11 às 12 da manhã, por algum motivo, exibe 19 litros, embora na tabela de dentes um pouco mais alta para o mesmo período vejamos o consumo de 62 litros do mesmo sensor. Um inseto ou as mãos estão tortas. Mas não entendo por que os dados à direita foram interrompidos - o consumo era normal, o que também é visível no cronograma detalhado.

Em geral, não consegui alcançar a credibilidade dessa abordagem - o gráfico quase sempre mostra algum tipo de heresia.

O mesmo código para o sensor diurno.

      - 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'

Observe que o parâmetro group_by está definido como intervalo e o parâmetro points_per_hour rege tudo. E esse é outro problema desse componente - points_per_hour funciona bem em gráficos em uma hora ou menos, mas nojento a grandes intervalos. Portanto, para obter uma coluna em um dia, tive que inserir o valor 1/24 = 0,04166666. Não estou falando de gráficos semanais e mensais.

Abordagem 2


Apenas descobrindo o assistente de casa, me deparei com este vídeo aqui:


Um amigo coleta dados de consumo de vários tipos de tomadas Xiaomi. Sua tarefa é um pouco mais fácil - basta exibir o valor do consumo para hoje, ontem e para o mês. Nenhuma programação é necessária.

Vamos deixar de lado a discussão sobre a integração manual de valores instantâneos de potência - eu já escrevi sobre a "precisão" dessa abordagem. Não está claro por que ele não usou os valores de consumo acumulado, que já são coletados na mesma saída. Na minha opinião, a integração dentro do pedaço de ferro funcionará melhor.

A partir do vídeo, tomamos a ideia de calcular manualmente o consumo para o período. O camponês conta apenas os valores para hoje e ontem, mas iremos além e tentaremos desenhar um gráfico. A essência do método proposto no meu caso é a seguinte.

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

Tudo isso pode ser feito por meio do próprio assistente de casa.

Você precisará escrever um pouco mais de código do que na abordagem anterior. Para começar, vamos obter essas "variáveis". Pronto, não temos a entidade "variável", mas você pode usar os serviços do broker mqtt. Enviaremos valores com o sinalizador reter = true lá - isso salvará o valor dentro do broker e você poderá retirá-lo a qualquer momento, mesmo quando o assistente de casa for reiniciado. Criei contadores de horas e dias imediatamente.

- 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 mágica acontece na automação, que é executada a cada hora e a cada noite, 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

A automação realiza duas ações:

  • O valor do intervalo é calculado como a diferença entre o valor inicial e o final
  • Atualizar valor base para o próximo intervalo

A representação gráfica neste caso é resolvida pelo gráfico histórico usual:

      - 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

É assim:



em princípio, isso já é o que você precisa. A vantagem desse método é que os dados são gerados uma vez por intervalo. Essa. apenas 24 entradas por dia para o gráfico horário.

Infelizmente, isso ainda não resolve o problema geral de uma base crescente. Se eu quiser uma programação de consumo mensal, terei que armazenar dados por pelo menos um ano. E como o assistente doméstico fornece apenas uma configuração para a duração do armazenamento de todo o banco de dados, isso significa que TODOS os dados no sistema terão que ser armazenados por um ano inteiro. Por exemplo, durante um ano eu consumo 200 metros cúbicos de água, o que significa que são 200.000 registros no banco de dados. E se você levar em conta outros sensores, o número se tornará indecente.

Abordagem 3


Felizmente, pessoas inteligentes já resolveram esse problema escrevendo o banco de dados do InfluxDB. Esse banco de dados é especialmente otimizado para armazenar dados com base no tempo e é ideal para armazenar valores de diferentes sensores. O sistema também fornece uma linguagem de consulta semelhante ao SQL que permite selecionar valores do banco de dados e depois agregá-los de várias maneiras. Finalmente, dados diferentes podem ser armazenados em momentos diferentes. Por exemplo, leituras que mudam frequentemente, como temperatura ou umidade, podem ser armazenadas por apenas algumas semanas, enquanto as leituras diárias do consumo de água podem ser armazenadas por um ano inteiro.

Além do InfluxDB, pessoas inteligentes também inventaram o Grafana, um sistema de gráficos baseado em dados do InfluxDB. O Grafana pode desenhar diferentes tipos de gráficos, personalizá-los em detalhes e, o mais importante, esses gráficos podem ser "presos" no assistente doméstico da interface do usuário da lovelace-UI.

Inspire-se aqui e aqui . Os artigos descrevem em detalhes o processo de instalação e conexão do InfluxDB e Grafana no assistente de casa. Vou me concentrar em resolver meu problema específico.

Então, primeiro de tudo, vamos começar a somar o valor do contador no influxDB. Uma parte da configuração do assistente doméstico (neste exemplo, vou me divertir não apenas com água fria, mas também com água quente):

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

Desative o salvamento dos mesmos dados no banco de dados interno do assistente doméstico, para não inflá-lo novamente:

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

Agora vamos ao console do InfluxDB e configuramos nosso banco de dados. Em particular, você precisa configurar por quanto tempo esses dados serão armazenados. Isso é regulado pelo chamado política de retenção - é semelhante aos bancos de dados dentro do banco de dados principal, com cada banco de dados interno tendo suas próprias configurações. Por padrão, todos os dados são armazenados em uma política de retenção chamada autogen, esses dados serão armazenados por uma semana. Desejo que os dados horários sejam armazenados por um mês, a semanal por um ano e o mensal nunca deve ser excluído. Crie uma política de retenção apropriada

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

Agora, de fato, o principal truque é a agregação de dados usando consulta contínua. Este é um mecanismo que inicia automaticamente uma consulta em intervalos especificados, agrega dados para essa consulta e adiciona o resultado a um novo valor. Vejamos um exemplo (escrevo em uma coluna para facilitar a leitura, mas, na verdade, tive que inserir esse comando em uma linha)

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:

  • Cria uma consulta contínua denominada cq_water_cold_hourly no banco de dados do homeassistant
  • A solicitação será executada a cada hora (hora (1h))
  • A solicitação recuperará todos os dados da medição - um homeassistant.autogen.l (litros), incluindo as leituras de água fria e quente
  • Os dados agregados serão agrupados por entity_id, que criará valores separados para água fria e quente.
  • , max(value)
  • homeassistant.month.water_meter_hour, month retention policy . entity_id value

À noite ou quando ninguém está em casa, não há consumo de água e, portanto, também não há novas entradas no homeassistant.autogen.l. Para evitar valores ausentes em consultas regulares, você pode usar o preenchimento (anterior). Isso forçará o InfluxDB a usar o valor da última hora.

Infelizmente, a consulta contínua tem uma peculiaridade: o truque de preenchimento (anterior) não funciona e os registros simplesmente não são criados. Além disso, esse é um problema intransponível que tem sido discutido há mais de um ano . Lidamos com esse problema mais tarde, e vamos preencher (anterior) na consulta contínua - isso não interfere.

Vamos verificar o que aconteceu (é claro, você precisa esperar algumas 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

Observe que os valores no banco de dados são armazenados no UTC; portanto, nesta lista, eles diferem em 3 horas - os valores de 7 horas na saída do InfluxDB correspondem aos valores de 10 horas nos gráficos acima. Observe também que entre 2 e 5 da manhã simplesmente não há registros - esse é o mesmo recurso da consulta contínua.

Como você pode ver, o valor agregado também é uma sequência monotonamente crescente, apenas os registros são menos frequentes - uma vez por hora. Mas isso não é um problema - podemos escrever outra consulta que produzirá os dados corretos para o 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)

Vou descriptografar:

  • No banco de dados homeassistant.month.water_meter_hour, extraímos os dados para entity_id = 'water_meter_cold' do último dia (hora> = now () -24h).
  • Como mencionei na sequência homeassistant.month.water_meter_hour, algumas entradas podem estar ausentes. Regeneraremos esses dados executando uma consulta com o tempo GROUP BY (1h). Esse preenchimento de tempo (anterior) funcionará conforme necessário, gerando os dados ausentes (a função assumirá o valor anterior)
  • O mais importante nesta solicitação é a função de diferença, que calculará a diferença entre as marcas horárias. Por si só, ele não funciona e requer uma função agregada. Que seja o max () usado antes.

O resultado da execução é semelhante a este

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

Das 2 às 5 da manhã (UTC) não houve consumo. No entanto, a consulta retornará o mesmo valor de consumo devido ao preenchimento (anterior), e a função de diferença subtrairá esse valor de si e obteremos 0 na saída, o que é realmente necessário.

A única coisa que resta é criar um cronograma. Para isso, abra o Grafana, abra um painel existente (ou crie um novo), crie um novo painel. As configurações do gráfico serão assim.



Vou exibir dados sobre água fria e quente em um gráfico. A solicitação é exatamente a mesma que eu descrevi acima.

Os parâmetros de exibição são definidos da seguinte maneira. Vou ter um gráfico com linhas, que passa por escadas. Vou explicar o parâmetro Stack logo abaixo. Existem mais algumas opções de exibição abaixo, mas elas não são tão interessantes.



Para adicionar a programação recebida ao assistente de casa, você precisa:

  • . -
  • , 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"

Observe que o período (últimos 2 dias) está definido aqui, e não nas configurações do painel.

O gráfico fica assim. Como não usei água quente nos últimos 2 dias, apenas um gráfico de água fria é desenhado.



Não decidi por mim mesma qual programação eu mais gosto, uma linha de etapa ou barras reais. Portanto, simplesmente darei um exemplo de um gráfico de consumo diário, somente desta vez em colunas. As solicitações são criadas da mesma maneira descrita acima. Os parâmetros de exibição são os seguintes:



Este gráfico se parece com o seguinte:



Então, sobre o parâmetro Stack. Neste gráfico, uma coluna de água fria é desenhada no topo de uma coluna de água quente. A altura total corresponde ao consumo total de água fria e quente do período.

Todos os gráficos mostrados são dinâmicos. Você pode passar o mouse sobre um ponto de interesse e ver os detalhes e o valor em um ponto específico.

Infelizmente, algumas colheres de alcatrão não conseguiram. No gráfico de barras (em contraste com o gráfico com linhas de etapa), o meio da barra não está no meio do dia, mas às 00:00. Essa. a metade esquerda da coluna é desenhada no lugar do dia anterior. Portanto, os gráficos para sábado e domingo são desenhados um pouco para a esquerda do que a zona azulada. Até eu descobrir como vencê-lo.

Outro problema é a incapacidade de funcionar corretamente em intervalos mensais. O fato é que a duração da hora / dia / semana é fixa, mas a duração do mês é diferente a cada vez. O InfluxDB pode funcionar apenas em intervalos regulares. Até agora, meu cérebro era suficiente para definir um intervalo fixo de 30 dias. Sim, o gráfico flutuará um pouco durante o ano e as colunas não corresponderão exatamente aos meses. Mas como isso é interessante para mim simplesmente como um dispositivo de exibição, então eu estou bem com isso.

Vejo pelo menos duas soluções:

  • Pontuação nos horários mensais e limite para semanal. 52 bares semanais para o ano parecem muito bons
  • №2, . . — .



Não sei por que, mas estou trabalhando em esse tipo de gráfico. Eles mostram que a vida está em pleno andamento e tudo está mudando. Ontem foi muito, hoje não é suficiente, amanhã será outra coisa. Resta trabalhar com as famílias sobre o tema do consumo. Mas mesmo com o apetite atual, apenas um número grande e incompreensível no pagamento já está se transformando em uma imagem bastante clara do consumo.

Apesar dos quase 20 anos de carreira como programador, eu praticamente não interceptei os bancos de dados. Portanto, a instalação de um banco de dados externo parecia algo tão obscuro e incompreensível. Tudo foi alterado pelo artigo mencionado acima - resultou que o erro de uma ferramenta adequada é feito em alguns cliques e, com uma ferramenta especializada, a tarefa de criar gráficos torna-se um pouco mais fácil.

Na manchete, mencionei o consumo de eletricidade. Infelizmente, no momento não posso dar um único gráfico. Um contador SDM120 está morto e o outro com erros ao acessar via Modbus. No entanto, isso não afeta o tópico deste artigo - os gráficos serão construídos da mesma maneira que para a água.

Neste artigo, citei as abordagens que eu mesmo tentei. Certamente, existem outras maneiras de organizar a coleta e a visualização de dados que eu não conheço. Conte-me sobre isso nos comentários, será muito interessante para mim. Ficarei feliz em receber críticas construtivas e novas idéias. Espero que o material apresentado também ajude alguém.

All Articles