ProHoster > Blog > administração > Casa inteligente: construímos gráficos de consumo de água e eletricidade no Home Assistant
Casa inteligente: construímos gráficos de consumo de água e eletricidade no Home Assistant
Cada vez que recebo um pagamento de luz e água, fico surpreso - minha família realmente consome tanto? Bom, sim, o banheiro tem piso aquecido e caldeira, mas não acendem fogo o tempo todo. Também parecemos estar economizando água (embora também gostemos de brincar no banheiro). Há vários anos eu já medidores de água conectados и electricidade para uma casa inteligente, mas foi aí que as coisas emperraram. Só agora começamos a analisar o consumo, que é o tema deste artigo.
Recentemente mudei para o Home Assistant como meu sistema doméstico inteligente. Um dos motivos foi justamente a oportunidade de organizar a coleta de uma grande quantidade de dados com a possibilidade de construir de forma conveniente diversos tipos de gráficos.
As informações descritas neste artigo não são novas, todas essas coisas sob diferentes molhos já foram descritas na Internet. Mas cada artigo geralmente descreve apenas uma abordagem ou aspecto. Tive que comparar todas essas abordagens e escolher eu mesmo a mais adequada. O artigo ainda não traz informações completas sobre a coleta de dados, mas é uma espécie de resumo de como fiz isso. Portanto, críticas construtivas e sugestões de melhorias são bem-vindas.
Formulação do problema
Portanto, o objetivo do exercício de hoje é obter belos gráficos de consumo de água e energia elétrica:
De hora em hora durante 2 dias
Diariamente durante 2 semanas
(opcional) semanal e mensal
Existem algumas dificuldades com isso:
Os componentes gráficos padrão geralmente são muito ruins. Na melhor das hipóteses, você pode construir um gráfico de linhas ponto por ponto.
Se você procurar bem, poderá encontrar componentes de terceiros que expandem os recursos do gráfico padrão. Para uma auxiliar doméstica, em princípio, este é um componente bom e bonito cartão minigráfico, mas também é um tanto limitado:
É difícil definir os parâmetros de um gráfico de barras em intervalos grandes (a largura da barra é definida em frações de hora, o que significa que intervalos maiores que uma hora serão definidos em números fracionários)
Você não pode adicionar entidades diferentes a um gráfico (por exemplo, temperatura e umidade ou combinar um gráfico de barras com uma linha)
Por padrão, o assistente doméstico não apenas usa o banco de dados SQLite mais primitivo (e eu, um faz-tudo, não consegui instalar o MySQL ou Postgres), mas os dados não são armazenados da maneira ideal. Assim, por exemplo, toda vez que você altera até mesmo o menor parâmetro digital de um parâmetro, um json enorme com cerca de um quilobyte de tamanho é gravado no banco de dados
Tenho muitos sensores (sensores de temperatura em cada sala, medidores de água e eletricidade), e alguns também geram muitos dados. Por exemplo, o medidor de eletricidade SDM220 sozinho gera cerca de uma dúzia de valores a cada 10-15 segundos, e eu gostaria de instalar cerca de 8 desses medidores.Há também um monte de parâmetros que são calculados com base em outros sensores. Que. todos esses valores podem facilmente inflar o banco de dados em 100-200 MB diariamente. Em uma semana o sistema mal se moverá e em um mês o pen drive morrerá (no caso de uma instalação típica de um assistente doméstico em um Raspberry PI), e o armazenamento de dados por um ano inteiro está fora de questão.
Se você tiver sorte, seu medidor poderá contar o consumo sozinho. Você pode recorrer ao medidor a qualquer momento e perguntar a que horas está o valor do consumo acumulado. Via de regra, todos os medidores de energia elétrica que possuem interface digital (RS232/RS485/Modbus/Zigbee) oferecem esta oportunidade.
É pior se o dispositivo puder simplesmente medir algum parâmetro instantâneo (por exemplo, potência ou corrente instantânea) ou simplesmente gerar pulsos a cada X watts-hora ou litros. Depois é preciso pensar em como e com o que integrá-lo e onde acumular valor. Existe o risco de perder o próximo relatório por qualquer motivo, e a precisão do sistema como um todo levanta questões. Você pode, é claro, confiar tudo isso a um sistema doméstico inteligente como o assistente doméstico, mas ninguém cancelou a questão sobre o número de registros no banco de dados, e não será possível consultar os sensores mais de uma vez por segundo (um limitação da arquitetura do assistente doméstico).
Abordagem 1
Primeiro, vamos ver o que o assistente doméstico oferece pronto para uso. Medir o consumo durante um período é uma funcionalidade muito procurada. Claro, ele foi implementado há muito tempo na forma de um componente especializado - utility_meter.
A essência do componente é que ele cria internamente uma variável current_accumulated_value e a redefine após um período especificado (hora/semana/mês). O próprio componente monitora a variável de entrada (o valor de algum sensor), subscreve-se às alterações no valor - você apenas obtém o resultado final. Isso é descrito em apenas algumas linhas no arquivo de configuração
Aqui sensor.water_meter_cold é o valor atual do medidor em litros que recebo diretamente do pedaço de ferro por mqtt. O projeto cria 2 novos sensores water_cold_hour_um e water_cold_day_um, que acumulam leituras horárias e diárias, zerando-as após o término do período. Aqui está um gráfico da bateria horária durante meio dia.
O código para gráficos horários e diários para lovelace-UI é assim:
- 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 desta abordagem está neste algoritmo. Como já mencionei, para cada valor de entrada (leitura atual do medidor para cada litro seguinte) são gerados 1kb de registros no banco de dados. Cada medidor da concessionária também gera um novo valor, que também é adicionado à base. Se eu quiser coletar leituras horárias/diárias/semanais/mensais, e para vários risers de água, e adicionar um pacote de medidores elétricos, serão muitos dados. Bem, mais precisamente, não há muitos dados, mas como o assistente doméstico grava um monte de informações desnecessárias no banco de dados, o tamanho do banco de dados aumentará aos trancos e barrancos. Tenho medo até de estimar o tamanho da base dos gráficos semanais e mensais.
Além disso, o medidor de energia por si só não resolve o problema. O gráfico dos valores produzidos pelo medidor da concessionária é uma função monotonicamente crescente que volta a 0 a cada hora. Precisamos de um gráfico de consumo que seja compreensível para o usuário, mostrando quantos litros foram consumidos no período. O componente gráfico histórico padrão não pode fazer isso, mas o componente externo do minigráfico pode nos ajudar.
Este é o código do cartão 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'
Além das configurações padrão como nome do sensor, tipo de gráfico, cor (não gostei do padrão laranja), é importante observar 3 configurações:
group_by:hour — o gráfico será gerado com as barras alinhadas ao início da hora
points_per_hour: 1 - uma barra para cada hora
E o mais importante, agregado_func: max - obtém o valor máximo em cada hora. É este parâmetro que transforma o gráfico dente de serra em barras
Não preste atenção na linha de colunas à esquerda – esse é o comportamento padrão do componente se não houver dados. Mas não havia dados - eu só ativei a coleta de dados do medidor de serviços públicos há algumas horas apenas por causa deste artigo (descreverei minha abordagem atual abaixo).
Nesta imagem eu queria mostrar que às vezes a exibição dos dados funciona e as barras realmente refletem os valores corretos. Mas isso não é tudo. Por algum motivo, a coluna selecionada para o período das 11 às 12h exibe 19 litros, embora no gráfico dentado um pouco mais alto para o mesmo período do mesmo sensor vejamos um consumo de 62 litros. Ou há um bug ou as mãos estão tortas. Mas ainda não entendo por que os dados à direita foram quebrados - o consumo lá estava normal, o que também é visível no gráfico dentado.
Em geral, não consegui alcançar a plausibilidade dessa abordagem - o gráfico quase sempre mostra algum tipo de heresia.
Código semelhante 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 governa tudo. E aí reside outro problema com este componente - points_per_hour funciona bem em gráficos de uma hora ou menos, mas é péssimo em intervalos maiores. Portanto, para obter uma coluna em um dia, tive que inserir o valor 1/24=0.04166666. Não estou nem falando de gráficos semanais e mensais.
Abordagem 2
Enquanto ainda entendia o assistente doméstico, me deparei com este vídeo:
Um amigo recolhe dados de consumo de vários tipos de tomadas Xiaomi. A tarefa dele é um pouco mais simples – basta exibir o valor do consumo de hoje, de ontem e do mês. Não são necessários horários.
Deixemos de lado as discussões sobre a integração manual de valores de potência instantâneos - já escrevi acima sobre a “precisão” dessa abordagem. Não está claro por que ele não utilizou os valores de consumo acumulado, que já são cobrados pelo mesmo ponto de venda. Na minha opinião, a integração dentro do hardware funcionará melhor.
Do vídeo tiraremos a ideia de contar manualmente o consumo ao longo de um período. O cara só conta os valores de hoje e de ontem, mas vamos além e vamos tentar fazer um gráfico. A essência do método proposto no meu caso é a seguinte.
Vamos criar uma variável value_at_the_beginning_of_hour, na qual registraremos as leituras atuais do medidor
Utilizando o cronômetro, no final da hora (ou no início da próxima) calculamos a diferença entre a leitura atual e a armazenada no início da hora. Essa diferença será o consumo da hora atual - vamos salvar o valor no sensor, e futuramente construiremos um gráfico com base nesse valor.
Você também precisa “redefinir” a variável value_at_beginning_of_hour escrevendo o valor atual do contador lá.
Tudo isso pode ser feito através do próprio assistente doméstico.
Você terá que escrever um pouco mais de código do que na abordagem anterior. Primeiro, vamos criar essas mesmas “variáveis”. Fora da caixa não temos a entidade “variável”, mas podemos usar os serviços do corretor mqtt. Enviaremos valores para lá com o sinalizador reter=true - isso salvará o valor dentro do corretor, e ele poderá ser retirado de lá a qualquer momento, mesmo quando o assistente inicial for reinicializado. Fiz contadores horários e diários de uma só vez.
- 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 a magia acontece na automação, que funciona a cada hora e todas as noites, respectivamente.
Calcule o valor de um intervalo como a diferença entre os valores inicial e final
Atualize o valor base para o próximo intervalo
A construção de gráficos 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
Parece assim:
Em princípio, isso já é o que é necessário. A vantagem deste método é que os dados são gerados uma vez por intervalo. Aqueles. apenas 24 registros por dia para um gráfico horário.
Infelizmente, isto ainda não resolve o problema geral de uma base crescente. Se eu quiser um gráfico de consumo mensal, terei que armazenar dados por pelo menos um ano. E como o Home Assistant fornece apenas uma configuração de duração de armazenamento para todo o banco de dados, isso significa que TODOS os dados do sistema deverão ser armazenados por um ano inteiro. Por exemplo, num ano consumo 200 metros cúbicos de água, o que significa que isso significa 200000 mil entradas na base de dados. E se você levar em conta outros sensores, o número geralmente se torna indecente.
Abordagem 3
Felizmente, pessoas inteligentes já resolveram esse problema escrevendo o banco de dados InfluxDB. Este banco de dados é especialmente otimizado para armazenar dados baseados em tempo e é ideal para armazenar valores de diferentes sensores. O sistema também fornece uma linguagem de consulta semelhante a SQL que permite extrair valores do banco de dados e 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 de consumo de água podem ser armazenadas por um ano inteiro.
Além do InfluxDB, pessoas inteligentes também inventaram o Grafana, um sistema para desenhar gráficos baseado em dados do InfluxDB. Grafana pode desenhar diferentes tipos de gráficos, personalizá-los detalhadamente e, o mais importante, esses gráficos podem ser “conectados” ao assistente doméstico lovelace-UI.
Inspire-se aqui и aqui. Os artigos descrevem detalhadamente o processo de instalação e conexão do InfluxDB e Grafana ao assistente doméstico. Vou me concentrar em resolver meu problema específico.
Então, primeiro de tudo, vamos começar a adicionar o valor do contador no influxDB. Um pedaço da configuração do assistente doméstico (neste exemplo vou me divertir não só com água fria, mas também com água quente):
Vamos agora para o console do InfluxDB e configurar nosso banco de dados. Em particular, você precisa configurar por quanto tempo determinados dados serão armazenados. Isso é regulado pelo chamado. política de retenção - é semelhante aos bancos de dados dentro de um 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. Gostaria que os dados horários fossem mantidos por um mês, os dados semanais fossem mantidos por um ano e os dados mensais nunca fossem excluídos. Vamos criar a 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, na verdade, o truque principal é a agregação de dados usando consulta contínua. Este é um mecanismo que executa automaticamente uma consulta em intervalos especificados, agrega os dados para esta consulta e adiciona o resultado a um novo valor. Vejamos um exemplo (escrevo em uma coluna para facilitar a leitura, mas na realidade tive que inserir este 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 chamada cq_water_cold_hourly no banco de dados homeassistant
A solicitação será executada a cada hora (time(1h))
A solicitação irá extrair todos os dados da medição 'homeassistant.autogen.l (litros), incluindo leituras de água fria e quente
Os dados agregados serão agrupados por entidade_id, o que nos dará valores separados para água fria e quente
Como o contador de litros é uma sequência crescente monotonicamente dentro de cada hora, será necessário tomar o valor máximo, portanto a agregação será realizada pela função max(valor)
O novo valor será gravado em homeassistant.month.water_meter_hour, onde mês é o nome da política de retenção com período de retenção de um mês. Além disso, os dados sobre água fria e quente serão espalhados em registros separados com o id_da_entidade correspondente e o valor no campo de valor
À noite ou quando não há ninguém em casa, não há consumo de água e, portanto, não há novas entradas em homeassistant.autogen.l. Para evitar valores ausentes em consultas regulares, você pode usar fill(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, este é algum tipo de problema intransponível que vem sendo discutido há vários anos. Trataremos desse problema mais tarde, mas deixe fill(previous) ficar 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 em UTC, portanto, esta lista difere em 3 horas – os valores de 7h na saída do InfluxDB correspondem aos valores de 10h nos gráficos acima. Observe também que entre 2h e 5h 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 crescente monotonicamente, apenas as entradas ocorrem com menos frequência - uma vez por hora. Mas isso não é um problema – podemos escrever outra consulta que recuperará 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:
Do banco de dados homeassistant.month.water_meter_hour extrairemos os dados de entidade_id='water_meter_cold' do último dia (hora >= agora() -24h).
Como já mencionei, algumas entradas podem estar faltando na sequência homeassistant.month.water_meter_hour. Iremos regenerar esses dados executando uma consulta com GROUP BY time(1h). Este preenchimento de tempo (anterior) funcionará conforme o esperado, gerando os dados faltantes (a função assumirá o valor anterior)
O mais importante nesta solicitação é a função diferença, que calculará a diferença entre as marcas das horas. Não funciona sozinho e requer uma função de agregação. Seja este o max() usado antes.
Das 2h às 5h (UTC) não houve consumo. Porém, a consulta retornará o mesmo valor de consumo graças a fill(anterior), e a função diferença subtrairá esse valor de si mesma e a saída será 0, que é exatamente o que é necessário.
Resta apenas construir um gráfico. Para fazer isso, abra o Grafana, abra algum painel existente (ou crie um novo) e crie um novo painel. As configurações do gráfico serão assim.
Exibirei dados de água fria e quente no mesmo gráfico. A solicitação é exatamente a mesma que descrevi acima.
Os parâmetros de exibição são definidos da seguinte forma. Para mim será um gráfico com linhas, que vai em degraus (escadas). Explicarei o parâmetro Stack abaixo. Existem mais algumas opções de exibição abaixo, mas elas não são tão interessantes.
Para adicionar o gráfico resultante ao assistente doméstico, você precisa:
saia do modo de edição de gráfico. Por algum motivo, as configurações corretas de compartilhamento de gráficos são oferecidas apenas na página do painel
Clique no triângulo ao lado do nome do gráfico e selecione compartilhar no menu
Na janela que se abre, vá para a guia incorporar
Desmarque o intervalo de tempo atual - definiremos o intervalo de tempo via URL
Selecione o tópico desejado. No meu caso é leve
Copie o URL resultante para o cartão de configurações do lovelace-UI
Observe que o intervalo de tempo (últimos 2 dias) é definido aqui e não nas configurações do painel.
O gráfico fica assim. Não usei água quente nos últimos 2 dias, então apenas o gráfico da água fria é desenhado.
Ainda não decidi qual gráfico gosto mais, um passo de linha ou barras reais. Portanto, darei simplesmente um exemplo de gráfico de consumo diário, só que desta vez em barras. As consultas são construídas de forma semelhante às descritas acima. As opções de exibição são:
Este gráfico é assim:
Então, sobre o parâmetro Stack. Neste gráfico, uma coluna de água fria é desenhada em cima 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 o ponto de interesse e ver os detalhes e o valor de um ponto específico.
Infelizmente, havia algumas moscas na sopa. Em um gráfico de barras (ao contrário de um gráfico com linhas escalonadas), o meio da barra não está no meio do dia, mas às 00:00. Aqueles. a metade esquerda da coluna é desenhada no lugar do dia anterior. Assim, os gráficos de sábado e domingo são desenhados ligeiramente à esquerda da zona azulada. Até que descobri como derrotá-lo.
Outro problema é a incapacidade de trabalhar adequadamente 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 só pode funcionar em intervalos iguais. Até agora meu cérebro foi suficiente para definir um intervalo fixo de 30 dias. Sim, o gráfico irá flutuar um pouco ao longo do ano e as barras não corresponderão exatamente aos meses. Mas como estou interessado nisso simplesmente como um medidor de exibição, estou bem com isso.
Vejo pelo menos duas soluções:
Desista dos gráficos mensais e limite-se aos semanais. 52 barras semanais para o ano parecem muito boas
Considere o próprio consumo mensal como método nº 2 e use grafana apenas para gráficos bonitos. Será uma solução bastante precisa. Você pode até sobrepor gráficos do ano passado para comparação - a grafana também pode fazer isso.
Conclusão
Não sei por que, mas sou obcecado por esse tipo de gráfico. Eles mostram que a vida está a todo vapor e tudo está mudando. Ontem foi muito, hoje é pouco, amanhã será outra coisa. Resta trabalhar com os membros do agregado familiar sobre o tema do consumo. Mas mesmo com os apetites atuais, apenas um número grande e incompreensível no boleto já está se transformando em um quadro de consumo bastante compreensível.
Apesar da minha carreira de quase 20 anos como programador, praticamente não tive contato com bancos de dados. Portanto, instalar um banco de dados externo parecia algo tão obscuro e incompreensível. Mudou tudo artigo acima — descobriu-se que anexar uma ferramenta adequada é feito com apenas alguns cliques e, com uma ferramenta especializada, a tarefa de traçar gráficos fica um pouco mais fácil.
No título mencionei o consumo de eletricidade. Infelizmente, no momento não posso fornecer nenhum gráfico. Um medidor SDM120 morreu para mim e o outro apresenta problemas quando acessado via Modbus. No entanto, isso não afeta de forma alguma o tema deste artigo - os gráficos serão construídos da mesma forma que para a água.
Neste artigo apresentei as abordagens que experimentei. Certamente existem outras maneiras de organizar a coleta e visualização de dados que eu não conheço. Conte-me nos comentários, ficarei muito interessado. Terei prazer em receber críticas construtivas e novas ideias. Espero que o material apresentado também ajude alguém.