Como saber se a maquina da Roupa terminou de lavar

Desde que vivo sozinho que tenho um problema com as lavagens de roupa, acontece-me muitas vezes colocar a roupa a lavar e esquecer-me dela no final, ficando dentro da maquina, quase sempre isso dá direito a ter de repetir a lavagem.

Assim sendo o meu objetivo era acabar com este desperdício de recursos, então neste pequeno tutorial vou explicar como fiz e o que fiz para obter isso.

1 - Eu já tinha instalado no meu quadro elétrico um Shelly EM a medir o consumo total de energia, então no meu caso aproveitei o segundo canal e coloquei uma pinça a medir apenas o circuito da maquina roupa (aqui em casa é separado), no entanto podem optar por outras soluções, por ex uma plug wifi ou zigbee desde que tenha monitorização de consumo elétrico, pois isso é a base desta solução. Em relação ao shelly em vamos usar a entidade de potencia e de energia, pois mais a frente vão perceber que aproveitei a energia para tornar a coisa mais bonita.

2 - Criar uma entidade input text para a maquina da roupa, eu faço isso usando a opção Helpers nas configurações do HA.

image

3 - Automação com o NodeRed

[{"id":"82c4ed04.94f0e","type":"tab","label":"Maquina Roupa","disabled":false,"info":""},{"id":"6455ddb.0d82124","type":"comment","z":"82c4ed04.94f0e","name":"Automação para definir estado da Maquina Roupa","info":"","x":210,"y":40,"wires":[]},{"id":"178b2fe0.b4219","type":"api-call-service","z":"82c4ed04.94f0e","name":"Set Parada - Off","server":"48c6a047.be8eb","version":1,"debugenabled":false,"service_domain":"input_text","service":"set_value","entityId":"input_text.maquina_roupa","data":"{\t  \"value\": \"off\"\t}","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":460,"y":100,"wires":[[]]},{"id":"68a336fe.856108","type":"api-call-service","z":"82c4ed04.94f0e","name":"Set A lavar - On","server":"48c6a047.be8eb","version":1,"debugenabled":false,"service_domain":"input_text","service":"set_value","entityId":"input_text.maquina_roupa","data":"{\t  \"value\": \"on\"\t}","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":460,"y":160,"wires":[[]]},{"id":"7ab0c377.ac137c","type":"api-current-state","z":"82c4ed04.94f0e","name":"off","server":"48c6a047.be8eb","version":1,"outputs":2,"halt_if":"off","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"input_text.maquina_roupa","state_type":"str","state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","blockInputOverrides":false,"x":270,"y":160,"wires":[["68a336fe.856108"],[]]},{"id":"283b1e4c.2778c2","type":"api-current-state","z":"82c4ed04.94f0e","name":"on","server":"48c6a047.be8eb","version":1,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"input_text.maquina_roupa","state_type":"str","state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","blockInputOverrides":false,"x":270,"y":100,"wires":[["178b2fe0.b4219"],[]]},{"id":"aa87fdc2.edbe7","type":"function","z":"82c4ed04.94f0e","name":"","func":"const haStates = global.get('homeassistant').homeAssistant.states;\nconst entity = haStates['sensor.energy_maquina_roupa'];\n\nif(msg.payload) {\n    const data = {\n        state: entity.state,\n        startTime: Date.now(),\n    };\n    context.set('savedState', data);\n} else {\n    const data = context.get('savedState');\n\n    if (data === undefined) {\n        return;\n    }\n\n    const energy = parseFloat(Math.abs(entity.state - data.state)).toFixed(2);\n\n    const duration = millisToMinutesAndSeconds(Date.now() - data.startTime);\n\n    const payload = {\n        energy: energy,\n        duration: duration,\n    }\n\n    msg.payload = payload;\n\n    return msg;\n}\n\nfunction millisToMinutesAndSeconds(millis) {\n  let minutes = Math.floor(millis / 60000);\n  let seconds = ((millis % 60000) / 1000).toFixed(0);\n  return minutes + \":\" + (seconds < 10 ? '0' : '') + seconds;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":280,"y":220,"wires":[["5441bfa3.16bfd"]]},{"id":"5441bfa3.16bfd","type":"link out","z":"82c4ed04.94f0e","name":"Not Maq Roupa OFF","links":["1d117b66.6e9cb5"],"x":395,"y":220,"wires":[]},{"id":"5df4b74f.953e88","type":"server-state-changed","z":"82c4ed04.94f0e","name":"Maquina Roupa","server":"48c6a047.be8eb","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_text.maquina_roupa","entityidfiltertype":"exact","outputinitially":false,"state_type":"habool","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":100,"y":220,"wires":[["aa87fdc2.edbe7"]]},{"id":"bec9f91b.672ae8","type":"server-state-changed","z":"82c4ed04.94f0e","name":"Power Maquina","server":"48c6a047.be8eb","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.power_maquina_lavar_roupa","entityidfiltertype":"exact","outputinitially":false,"state_type":"num","haltifstate":"10","halt_if_type":"num","halt_if_compare":"lt","outputs":2,"output_only_on_state_change":true,"for":"120","forType":"num","forUnits":"seconds","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":100,"y":100,"wires":[["283b1e4c.2778c2"],[]]},{"id":"e12dff48.96ee7","type":"server-state-changed","z":"82c4ed04.94f0e","name":"Power Maquina","server":"48c6a047.be8eb","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.power_maquina_lavar_roupa","entityidfiltertype":"exact","outputinitially":false,"state_type":"num","haltifstate":"5","halt_if_type":"num","halt_if_compare":"gt","outputs":2,"output_only_on_state_change":true,"for":"10","forType":"num","forUnits":"seconds","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":100,"y":160,"wires":[["7ab0c377.ac137c"],[]]},{"id":"48c6a047.be8eb","type":"server","name":"Home Assistant","addon":true}]

Esta automação faz o seguinte, assim que a maquina consome mais de 10W durante 5s seguidos e a maquina da roupa esta OFF, o nodered escreve ON na entidade input text criada para a maquina da roupa, e sempre que a potencia da maquina é inferior a 5W durante 120s e a maquina está ON, o nodered escreve na entidade da maquina OFF. Esses 120s existem para evitar falsos OFF durante o ciclo, isto porque por vezes a maquina esta parada, com consumo residual.

Além disto com o NodeRed uso um outro flow que me permite calcular o consumo de energia kw/h durante o ciclo de lavagem e o tempo total de lavagem da maquina. e esse flow cria uma notificação que me é enviada para o telegram

[{"id":"aa87fdc2.edbe7","type":"function","z":"82c4ed04.94f0e","name":"","func":"const haStates = global.get('homeassistant').homeAssistant.states;\nconst entity = haStates['sensor.energy_maquina_roupa'];\n\nif(msg.payload) {\n    const data = {\n        state: entity.state,\n        startTime: Date.now(),\n    };\n    context.set('savedState', data);\n} else {\n    const data = context.get('savedState');\n\n    if (data === undefined) {\n        return;\n    }\n\n    const energy = parseFloat(Math.abs(entity.state - data.state)).toFixed(2);\n\n    const duration = millisToMinutesAndSeconds(Date.now() - data.startTime);\n\n    const payload = {\n        energy: energy,\n        duration: duration,\n    }\n\n    msg.payload = payload;\n\n    return msg;\n}\n\nfunction millisToMinutesAndSeconds(millis) {\n  let minutes = Math.floor(millis / 60000);\n  let seconds = ((millis % 60000) / 1000).toFixed(0);\n  return minutes + \":\" + (seconds < 10 ? '0' : '') + seconds;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":280,"y":220,"wires":[["5441bfa3.16bfd"]]},{"id":"5441bfa3.16bfd","type":"link out","z":"82c4ed04.94f0e","name":"Not Maq Roupa OFF","links":["1d117b66.6e9cb5"],"x":395,"y":220,"wires":[]},{"id":"5df4b74f.953e88","type":"server-state-changed","z":"82c4ed04.94f0e","name":"Maquina Roupa","server":"48c6a047.be8eb","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_text.maquina_roupa","entityidfiltertype":"exact","outputinitially":false,"state_type":"habool","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":100,"y":220,"wires":[["aa87fdc2.edbe7"]]},{"id":"48c6a047.be8eb","type":"server","name":"Home Assistant","addon":true}]

image

[{"id":"4ccf887.a246978","type":"template","z":"a2477c22.c6cb1","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"A lavagem da maquina da roupa terminou,\na energia consumida durante o ciclo foi de {{payload.energy}}kWh,\ne o tempo total foi de {{payload.duration}}.","output":"str","x":220,"y":240,"wires":[["188e4a34.825236"]]},{"id":"188e4a34.825236","type":"telegrambot-notify","z":"a2477c22.c6cb1","name":"Máquina Roupa","bot":"75de7d79.3586a4","chatId":"","message":"","parseMode":"","x":400,"y":240,"wires":[]},{"id":"75de7d79.3586a4","type":"telegrambot-config","botname":"Hassio","usernames":"JMTHassio_BoT","chatIds":"942164466","pollInterval":"300"}]

image

Vão acabar com uma mensagem no telegram desde genero.
image

INFO Importante - tudo isto foi retirado das mentes brilhantes do utilizadores deste forum, do discord e da internet em geral.

Qualquer duvida ou sugestão, é só colocar aí…

6 Curtiram

No meu caso eu coloquei um sensor de luz no LED power da máquina, funcionou muito bem.
Também coloquei um switch na tampa para continuar avisando até que a tampa se abra.

2 Curtiram

Obrigado pelo Tutorial.

Como alterar o node “function” de forma a que em vez de minutos e segundos, retorne horas, minutos e segundos?

As minhas máquinas demoram sempre mais de uma hora, e a notificação fica sempre com demasiados minutos!!

boas… usa este update do node function. com este update já tens isso como queres.

[{"id":"e732616e.5efd1","type":"function","z":"8c2bdcbd.fb616","name":"calcular kwh e tempo da lavagem","func":"const haStates = global.get('homeassistant').homeAssistant.states;\nconst entity = haStates['sensor.vanmoof_energia_2'];\n\nif(msg.payload) {\n    const data = {\n        state: entity.state,\n        startTime: Date.now(),\n    };\n    context.set('savedState', data);\n} else {\n    const data = context.get('savedState');\n    \n    if (data === undefined) {\n        return;\n    }\n    \n    const energy = parseFloat(Math.abs(entity.state - data.state)).toFixed(2);\n    \n    \n    const duration = millisToMinutesAndSeconds(Date.now() - data.startTime);\n    \n    const payload = {\n        energy: energy,\n        duration: duration,\n    }\n\n    msg.payload = payload;\n    \n    return msg;\n}\nfunction millisToMinutesAndSeconds(millis) {\n  let hours = Math.floor(millis / 3600000);\n  let minutes = Math.floor(millis / 60000)%60;\n  let seconds = ((millis % 60000) / 1000).toFixed(0);\n  return hours + \":\" + (minutes < 10 ? '0' : '') + minutes + \":\" + (seconds < 10 ? '0' : '') + seconds;\n}\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":400,"y":220,"wires":[["7ba40a83.f94784","dc3450b9.fee67","f93d2e67.738fb"]]}]

Fiz a importação do flow, alterei as entidades, mas no nod function tenho o erro

Error: Invalid property expression: unexpected ' ' at position 4

A posição 4 dentro do nod só tem isto

if(msg.payload) {

O que poderá estar a originar este erro?

Não faço ideia, aqui funciona bem…

Boas pessoal,

Utilizei aqui os scripts do nodered para implementar o mesmo mas com a torradeira :slight_smile: e a notificação vai para a TV.

Pelo meu debug não está a conseguir ler/escrever no input text que criei no HA.
Será que conseguem dar uma ajuda?

Só consigo ter output na deteção do power da tomada.

Mostra me o call service que tens a frente, esse é o que escreve no input text.

Mas do On que verifica o input não sai nada para o debug.

Até pus o debug SetOFF a mostrar a msg completa.

[{"id":"6455ddb.0d82124","type":"comment","z":"82c4ed04.94f0e","name":"Automação para definir estado da Maquina Roupa","info":"","x":230,"y":60,"wires":[]},{"id":"178b2fe0.b4219","type":"api-call-service","z":"82c4ed04.94f0e","name":"Set Parada - Off","server":"48c6a047.be8eb","version":3,"debugenabled":false,"service_domain":"input_text","service":"set_value","entityId":"input_text.maquina_roupa","data":"{\t  \"value\": \"off\"\t}","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":480,"y":120,"wires":[[]]},{"id":"68a336fe.856108","type":"api-call-service","z":"82c4ed04.94f0e","name":"Set A lavar - On","server":"48c6a047.be8eb","version":3,"debugenabled":false,"service_domain":"input_text","service":"set_value","entityId":"input_text.maquina_roupa","data":"{\t  \"value\": \"on\"\t}","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":480,"y":180,"wires":[[]]},{"id":"7ab0c377.ac137c","type":"api-current-state","z":"82c4ed04.94f0e","name":"off","server":"48c6a047.be8eb","version":2,"outputs":2,"halt_if":"off","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_text.maquina_roupa","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"x":290,"y":180,"wires":[["68a336fe.856108"],[]]},{"id":"283b1e4c.2778c2","type":"api-current-state","z":"82c4ed04.94f0e","name":"on","server":"48c6a047.be8eb","version":2,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_text.maquina_roupa","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"x":290,"y":120,"wires":[["178b2fe0.b4219"],[]]},{"id":"bec9f91b.672ae8","type":"server-state-changed","z":"82c4ed04.94f0e","name":"Power Maquina","server":"48c6a047.be8eb","version":3,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.maquina_roupa_power","entityidfiltertype":"exact","outputinitially":false,"state_type":"num","haltifstate":"10","halt_if_type":"num","halt_if_compare":"lt","outputs":2,"output_only_on_state_change":true,"for":"90","forType":"num","forUnits":"seconds","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":120,"y":120,"wires":[["283b1e4c.2778c2"],[]]},{"id":"e12dff48.96ee7","type":"server-state-changed","z":"82c4ed04.94f0e","name":"Power Maquina","server":"48c6a047.be8eb","version":3,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.maquina_roupa_power","entityidfiltertype":"exact","outputinitially":false,"state_type":"num","haltifstate":"10","halt_if_type":"num","halt_if_compare":"gt","outputs":2,"output_only_on_state_change":true,"for":"10","forType":"num","forUnits":"seconds","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":120,"y":180,"wires":[["7ab0c377.ac137c"],[]]},{"id":"48c6a047.be8eb","type":"server","name":"Home Assistant","version":1,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]

O flow que estou a usar aqui, e nunca me falhou…

TUDO A FUNCIONAR - Info para alguém que venha a precisar

Ao fim de 1001 debugs, verifiquei que algo se passava com o helper input_text que tinha criado no HA para o efeito. Estava tudo OK, mas o node-red não lia (nem escrevia) no input_text do HA (pensava Eu).
Depois fui ao HA Developer Tools e inicializei o state do input_text por exemplo para “off”. Assim já consegue fazer as verificações e por sua vez a escrita e funciona tudo impecável.

Realmente, pensando agora nisso, ao verificar se está “on” ou se está “off” e o state está null, nunca vai escrever “off” ou “on” a primeira vez porque a condição é sempre false. Talvez uma solução sem precisar de inicializar o input_text seja trocar as condições e verificar ao contrário porque assim inclui a verificação do null inicial:

is: "on" -> escreve "off"
ficará
is not "off" -> escreve "off"

e

is: "off" -> escreve "on"
ficará
is not "on" -> escreve "on"

De resto só não percebi onde verifica o tempo da condição < 5W durante 120s…

Desde já obrigado pelas respostas.

Pois faz sentido… Não tinha estado definido então não fazia nada. Não me recordo se me aconteceu isso…


Copyright © 2017-2021. Todos os direitos reservados
CPHA.pt - info@cpha.pt


FAQ | Termos de Serviço/Regras | Política de Privacidade