We have quite recently acquired a cat, and because of that we have also acquired a litter box.
The box is dumb, just expensive plastic and no electronics. So I decided to make it smart and Integrate it in Home-Assistant.
I had three goals i wanted to accomplis with this project:
- Know then the cat was using the litter box
- Know when it was in need of cleaning
- Know when it was clean and ready to use
The Cat
First I want to introduce the cat, his name is Kabe and he is a 2,5 year old Maine Coon.
The project
There is a few way to detect if the cat toilet is in use, but I think a motion sensor might just be the best option and that is what I am using. This has one big problem, when I clean out the feces it will detect me and registrer the toilet as “in use”. It will also not know when it is cleaned out.
So I found a solution that has been proved to work very well for me, and that is a combination of a motion sensor and a contact/magnet sensor.
The motion sensor is attached to the inside of lid facing inwards, the magnet sensor is attached on the same lid with the magnet part on the box itself, so it will registrer contact when the lid is closed.
I have made a flow in Node-Red that is using these two sensors to conclude if the toilet is eater In use, in Maintenance mode, or if it Need emptying.
The logic in short:
- When the contact sensor is open, a Maintenance entity will turn on
- When the contact sensor closes it will wait two minutes and if the motion sensor is off, then it turn off Maintenance entity. If there is still motion it will check for motion every 2 minutes until it’s off and then turn off the Maintenance entity)
- When the Maintenance entity is set off the Need Emptying entity is set to off.
- When the Maintenance entity is on the motion sensor will not change the state of the litter box
- If the motion sensor is detecting motion, and the Maintenance entity is off, a In use sensor is set to on.
- The In use sensor will stay on until motion is not detected for 1 minute, and it will then set In use to off and set Need Emptying to on.
Entities
Entity | States | Description |
binary_sensor.zigbee_47_contact | “on”, “off” | Aqara Door and Window sensor (from zigbee2mqtt) |
binary_sensor.zigbee_37_occupancy | “on”, “off” | Aqara motion sensor (from zigbee2mqtt) |
input_boolean.cat_toilet_in_use | “on”, “off” | input_boolean helper entity (Home-Assistant) |
input_boolean.cat_toilet_maintenance | “on”, “off” | input_boolean helper entity (Home-Assistant) |
input_boolean.cat_toilet_need_emptying | “on”, “off” | input_boolean helper entity (Home-Assistant) |
input_text.cat_toilet_status | “Clear”, “Maintenance”, “Need emptying” | input_text helper entity (Home-Assistant) |
Node-Red
In Node-Red his is how it is set up.
Exported flow ready for import:
[{"id":"b49b7b1585817963","type":"server-state-changed","z":"cbdda1d3e0543731","name":"Contact Sensor","server":"ce440416.77f1e8","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"binary_sensor.zigbee_47_contact","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is_not","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":true,"ignorePrevStateUnknown":true,"ignorePrevStateUnavailable":true,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":200,"y":4300,"wires":[["a2b905bbcb7e376b"]]},{"id":"a2b905bbcb7e376b","type":"switch","z":"cbdda1d3e0543731","name":"Switch Actions","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"on","vt":"str"},{"t":"eq","v":"off","vt":"str"}],"checkall":"false","repair":false,"outputs":2,"x":380,"y":4300,"wires":[["ddfab27e4c6a66de","e64cf209f6c7b869"],["fb28c35b1974e38a","bcddb99ee0a45bac","6ac78e39c0337a7b"]]},{"id":"d3a77e6608b47524","type":"api-call-service","z":"cbdda1d3e0543731","name":"Turn off \"Maintenance\" boolan","server":"ce440416.77f1e8","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_off","areaId":[],"deviceId":[],"entityId":["input_boolean.cat_toilet_maintenance"],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":930,"y":4260,"wires":[[]]},{"id":"ddfab27e4c6a66de","type":"api-call-service","z":"cbdda1d3e0543731","name":"Turn on \"Maintenance\" boolan","server":"ce440416.77f1e8","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_on","areaId":[],"deviceId":[],"entityId":["input_boolean.cat_toilet_maintenance"],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":710,"y":4180,"wires":[[]]},{"id":"ae54390d488167aa","type":"server-state-changed","z":"cbdda1d3e0543731","name":"Motion Sensor","server":"ce440416.77f1e8","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"binary_sensor.zigbee_37_occupancy","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is_not","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":true,"ignorePrevStateUnknown":true,"ignorePrevStateUnavailable":true,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":190,"y":4500,"wires":[["6596e6db2371126b"]]},{"id":"6596e6db2371126b","type":"switch","z":"cbdda1d3e0543731","name":"Switch Actions","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"on","vt":"str"},{"t":"eq","v":"off","vt":"str"}],"checkall":"false","repair":false,"outputs":2,"x":360,"y":4500,"wires":[["b01ef8d7cb6332ec"],["e8e393d09def3ee2","dba52d812826bbb9"]]},{"id":"b01ef8d7cb6332ec","type":"delay","z":"cbdda1d3e0543731","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":540,"y":4480,"wires":[["69c8ebbd471c84c1"]]},{"id":"69c8ebbd471c84c1","type":"api-current-state","z":"cbdda1d3e0543731","name":"\"Maintenance\" boolan = on","server":"ce440416.77f1e8","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.cat_toilet_maintenance","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":0,"forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":740,"y":4480,"wires":[[],["824373f6bafebf50","7a0e2bd2f626fb5c"]]},{"id":"824373f6bafebf50","type":"api-call-service","z":"cbdda1d3e0543731","name":"","server":"ce440416.77f1e8","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_on","areaId":[],"deviceId":[],"entityId":["input_boolean.cat_toilet_in_use"],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1000,"y":4460,"wires":[[]]},{"id":"e8e393d09def3ee2","type":"api-current-state","z":"cbdda1d3e0543731","name":"\"Maintenance\" boolan = on","server":"ce440416.77f1e8","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"input_boolean.cat_toilet_maintenance","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":0,"forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":640,"y":4560,"wires":[[],["67c8f68b06a32708","0847cb08bec759de"]]},{"id":"67c8f68b06a32708","type":"api-call-service","z":"cbdda1d3e0543731","name":"Turn on \"Need emptying\" boolan","server":"ce440416.77f1e8","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_on","areaId":[],"deviceId":[],"entityId":["input_boolean.cat_toilet_need_emptying"],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":950,"y":4540,"wires":[[]]},{"id":"bcddb99ee0a45bac","type":"api-call-service","z":"cbdda1d3e0543731","name":"Turn off \"Need emptying\" boolan","server":"ce440416.77f1e8","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_off","areaId":[],"deviceId":[],"entityId":["input_boolean.cat_toilet_need_emptying"],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":710,"y":4360,"wires":[[]]},{"id":"dba52d812826bbb9","type":"api-call-service","z":"cbdda1d3e0543731","name":"Turn off \"In use\" boolan","server":"ce440416.77f1e8","version":5,"debugenabled":false,"domain":"input_boolean","service":"turn_off","areaId":[],"deviceId":[],"entityId":["input_boolean.cat_toilet_in_use"],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":630,"y":4520,"wires":[[]]},{"id":"fb28c35b1974e38a","type":"delay","z":"cbdda1d3e0543731","name":"","pauseType":"delay","timeout":"2","timeoutUnits":"minutes","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":640,"y":4320,"wires":[["b90f19272ee3c646"]]},{"id":"7a0e2bd2f626fb5c","type":"api-call-service","z":"cbdda1d3e0543731","name":"Set status \"In use\"","server":"ce440416.77f1e8","version":5,"debugenabled":false,"domain":"input_text","service":"set_value","areaId":[],"deviceId":[],"entityId":["input_text.cat_toilet_status"],"data":"{\"value\":\"In use\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":990,"y":4500,"wires":[[]]},{"id":"6ac78e39c0337a7b","type":"api-call-service","z":"cbdda1d3e0543731","name":"Set status \"Clear\"","server":"ce440416.77f1e8","version":5,"debugenabled":false,"domain":"input_text","service":"set_value","areaId":[],"deviceId":[],"entityId":["input_text.cat_toilet_status"],"data":"{\"value\":\"Clear\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":670,"y":4400,"wires":[[]]},{"id":"0847cb08bec759de","type":"api-call-service","z":"cbdda1d3e0543731","name":"Set status \"Need emptying\"","server":"ce440416.77f1e8","version":5,"debugenabled":false,"domain":"input_text","service":"set_value","areaId":[],"deviceId":[],"entityId":["input_text.cat_toilet_status"],"data":"{\"value\":\"Need emptying\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":940,"y":4580,"wires":[[]]},{"id":"e64cf209f6c7b869","type":"api-call-service","z":"cbdda1d3e0543731","name":"Set status \"Maintenance\"","server":"ce440416.77f1e8","version":5,"debugenabled":false,"domain":"input_text","service":"set_value","areaId":[],"deviceId":[],"entityId":["input_text.cat_toilet_status"],"data":"{\"value\":\"Maintenance\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":690,"y":4220,"wires":[[]]},{"id":"b90f19272ee3c646","type":"api-current-state","z":"cbdda1d3e0543731","name":"Motion Sensor","server":"ce440416.77f1e8","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"binary_sensor.zigbee_37_occupancy","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":0,"forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":660,"y":4260,"wires":[["fb28c35b1974e38a"],["d3a77e6608b47524"]]},{"id":"c6bbcf9dedf8f62c","type":"comment","z":"cbdda1d3e0543731","name":"Cat Litter Box","info":"","x":190,"y":4180,"wires":[]},{"id":"ce440416.77f1e8","type":"server","name":"Home Assistant","version":2,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30}]
Dashboard
The root card I am using is a vertical-stack populated with a horizontal-stack card and a history-graph card.
Inside the horizontal-stack I have a glance card showing if the litter box is in use and if the litter box is full. I also have a markdown card with jinja2 templates showing status and last time it was used/emptied.
The history-graph card last in the vertical-stack is showing the litter box’s status for the last 24h. And this is the result:
type: vertical-stack
title: Kabe's poop box
cards:
- type: horizontal-stack
cards:
- show_name: true
show_icon: true
show_state: false
type: glance
entities:
- entity: input_boolean.cat_toilet_in_use
name: In use
icon: mdi:cat
- entity: input_boolean.cat_toilet_need_emptying
name: Full
icon: mdi:emoticon-poop
state_color: true
columns: 2
- type: markdown
content: >-
{% if is_state("input_text.cat_toilet_status", "Clear") -%}
<font size="+1"> STATUS:<font color="green"> Clear</font></font>
Last used: {{
relative_time(states.input_boolean.cat_toilet_in_use.last_changed) }}
ago.
Last emptied: {{
relative_time(states.input_boolean.cat_toilet_maintenance
.last_changed) }} ago.
{%- endif %}
{% if is_state("input_text.cat_toilet_status", "Maintenance") -%}
<font size="+1"> STATUS:<font color="orange">
Maintenance</font></font>
Last used: {{
relative_time(states.input_boolean.cat_toilet_in_use.last_changed) }}
ago.
Last emptied: Now.
{%- endif %}
{% if is_state("input_text.cat_toilet_status", "Need emptying") -%}
<font size="+1"> STATUS:<font color="red"> Need
emptying</font></font>
Last used: {{
relative_time(states.input_boolean.cat_toilet_in_use.last_changed) }}
ago.
Last emptied: {{
relative_time(states.input_boolean.cat_toilet_maintenance
.last_changed) }} ago.
{%- endif %}
{% if is_state("input_text.cat_toilet_status", "In use") -%}
<font size="+1"> STATUS:<font color="magenta"> In use</font></font>
Last used: Now.
Last emptied: {{
relative_time(states.input_boolean.cat_toilet_maintenance
.last_changed) }} ago.
{%- endif %}
- type: history-graph
entities:
- entity: input_text.cat_toilet_status
hours_to_show: 24
Thats i, I aI am very happy with the result, and this is actually a very useful use of Home-Assistant.