Monitoring the Cat Litter Box in Home-Assistant

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:

  1. Know then the cat was using the litter box
  2. Know when it was in need of cleaning
  3. 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.

Equipment: Aqara motion and Aqara Door and Window Zigbee sensors mouned inside the Peewee EcoDome XL litter box.

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

EntityStatesDescription
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:

Home-Assistant Dashboard. The litter box clean and ready to use
Home-Assistant Dashboard. The litter box is full and need emptying
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.

Share