Cat Litter Box Tracker

This project senses when the cat has used the litter box. If detected, it turns the light in the bathroom where the litter box is kept to red. It also sends me a push notification if I am at home and not asleep. Otherwise, it sends the notification when I arrive home or when I get up in the morning.

When I clean the litter box, the light turns green briefly, so I know the sensor has been reset. Then, the light either turns back off if the light switch is off, or to a normal white color if the light switch is on.

In addition, if anyone needs to use this restroom before I get to clean the litter box, the light switch still functions normally, and will always turn on to a normal white color when the switch is used, and then back to red when the switch is turned off if the litter box still needs to be cleaned.

Light Turns Red When Litter Box Used

Light Briefly Turns Green, Then Back to Off/Normal When Litter Box Cleaned

Hardware

I had some extra Zigbee sensors from other projects laying around, so I incorporated them into this project. Any motion sensor and contact sensor would have worked. I had to buy a new bulb and a Shelly 1 to put behind the switch.

Aqara Zigbee Motion Sensor

I stuck this Aqara Zigbee motion sensor under the sink, high enough that it should be blocked from catching any motion not associated with the litter box. There is also a curtain under the front of the sink that stays closed. The sensors have been up and running for about two weeks now, and I haven’t had any false detections.

Sonoff Zigbee Contact Sensor

Next, I stuck this Sonoff Zigbee Contact Sensor I got from AliExpress on the Litter Genie so I could detect when it was opened.

Feit Electric Bulb (using Tuya Local)

The bathroom where we keep the litter box has a light fixture with an exposed bulb, and I wanted to be able to continue using an Edison bulb. The only smart Edison bulb I could find that was capable of changing colors was this wifi one from Feit Electric. It’s not super bright, but it is good enough for this application.

I set the bulb up in Home Assistant using the Local Tuya integration from HACS. I had to do a little bit of trial and error to find the values I wanted to use for the white and green light settings.

Shelly 1

I used a Shelly 1 behind the light switch so I could replicate normal functionality of the switch. Basically, I have it set so the relay is separated from the switch. That means I can tell the power to just always stay on, and then send the status of the physical switch to Home Assistant. I have set all my Shelly devices up to communicate with Home Assistant locally through MQTT.

Home Assistant Helper

I used HA’s helpers to set up an input_boolean entity. I created this in the UI by going to Configuration/Automations & Scenes/Helpers and clicking the “Add Helper” button, then “Toggle.” I then added a name, in my case “Kitty Litter Box”.

Automations:

I created five automations in the UI for this project, and I’ll do my best to explain them and the logic behind them. I don’t use Node Red, but all of this should be achievable in both platforms. All of my automations utilize the “choose” function:

First I want the motion sensor to turn on the helper

I added the helper so I could have it play middle man for notifications and provide a persistent status. This way, I can delay notifications when they wouldn’t be helpful, like when I’m not at home or I’m asleep in bed. I made all of these in the UI, but the yaml is below. This automation also turns the helper off when the litter genie’s lid is opened.

# automations.yaml

- alias: Kitty Litter Box Tracker - Turn On/Off Boolean
  description: ''
  trigger:
  - platform: state
    entity_id: binary_sensor.kitty_litter_motion
    from: 'off'
    to: 'on'
    id: litteron
  - platform: state
    entity_id: binary_sensor.litter_genie_zigbee
    id: litteroff
    from: 'off'
    to: 'on'
  condition: []
  action:
  - choose:
    - conditions:
      - condition: trigger
        id: litteron
      - condition: template
        value_template: '{{ ( as_timestamp(now()) - as_timestamp(states.binary_sensor.litter_genie_zigbee.last_changed)
          |int(0) ) > 90 }}'
      sequence:
      - service: input_boolean.turn_on
        target:
          entity_id: input_boolean.kitty_litter_box
    - conditions:
      - condition: trigger
        id: litteroff
      sequence:
      - service: input_boolean.turn_off
        target:
          entity_id: input_boolean.kitty_litter_box
    default: []
  mode: single

Then, I want the lights to be set according to the helper’s status

I want the light switch to function normally so I don’t run afoul of my spouse and guests, since this is also the guest bathroom. I am using a Shelly 1 wired behind the switch, set to “detached mode,” so the bulb is always powered, and Home Assistant is turning the bulb on and off when it detects a switch state change. The shelly switch is “binary_sensor.half_bath_shelly_1_input_0” and the light bulb is “light.half_bath_light”

Kitty Litter Box Sensor

First, an automation to control what happens to the lights when the helper’s status changes:

- alias: Kitty Litter Box Tracker - Lights
  description: ''
  trigger:
  - platform: state
    entity_id: input_boolean.kitty_litter_box
    from: 'off'
    to: 'on'
    id: litterlighton
  - platform: state
    entity_id: input_boolean.kitty_litter_box
    from: 'on'
    to: 'off'
    id: litterlightoff
  condition: []
  action:
  - choose:
    - conditions:
      - condition: trigger
        id: litterlighton
      - condition: state
        entity_id: binary_sensor.half_bath_shelly_1_input_0
        state: 'off'
      sequence:
      - service: light.turn_on
        data:
          rgb_color:
          - 255
          - 0
          - 0
          brightness_pct: 50
        target:
          entity_id:
          - light.half_bath_light
    - conditions:
      - condition: trigger
        id: litterlightoff
      sequence:
      - service: light.turn_on
        data:
          rgb_color:
          - 36
          - 255
          - 37
        target:
          entity_id:
          - light.half_bath_light
      - delay:
          hours: 0
          minutes: 0
          seconds: 5
          milliseconds: 0
      - choose:
        - conditions:
          - condition: state
            entity_id: binary_sensor.half_bath_shelly_1_input_0
            state: 'off'
          sequence:
          - service: light.turn_off
            target:
              entity_id: light.half_bath_light
        - conditions:
          - condition: state
            entity_id: binary_sensor.half_bath_shelly_1_input_0
            state: 'on'
          sequence:
          - service: light.turn_on
            data:
              rgb_color:
              - 255
              - 255
              - 75
            target:
              entity_id:
              - light.half_bath_light
        default: []
    default: []
  mode: single

Then, an automation to control what happens to the lights when the light switch is used:

- alias: Lights - Half Bath - Switch Automations
  description: ''
  trigger:
  - platform: state
    entity_id: binary_sensor.half_bath_shelly_1_input_0
    from: 'off'
    to: 'on'
    id: switchon
  - platform: state
    entity_id: binary_sensor.half_bath_shelly_1_input_0
    from: 'on'
    to: 'off'
    id: switchoff
  condition: []
  action:
  - choose:
    - conditions:
      - condition: trigger
        id: switchon
      sequence:
      - service: light.turn_on
        data:
          rgb_color:
          - 255
          - 255
          - 75
          brightness_pct: 100
        target:
          entity_id:
          - light.half_bath_light
    - conditions:
      - condition: trigger
        id: switchoff
      - condition: state
        entity_id: input_boolean.kitty_litter_box
        state: 'on'
      sequence:
      - service: light.turn_on
        data:
          rgb_color:
          - 255
          - 0
          - 0
          brightness_pct: 50
        target:
          entity_id:
          - light.half_bath_light
    - conditions:
      - condition: trigger
        id: switchoff
      - condition: state
        entity_id: input_boolean.kitty_litter_box
        state: 'off'
      sequence:
      - service: light.turn_off
        target:
          entity_id: light.half_bath_light
    default: []
  mode: single

Last, I want to get an actionable notification, depending on where I am and what my status is:

As I mentioned before, I added the helper so I could have it play middle man for notifications and persistent status. This way, I can delay notifications when they wouldn’t be helpful, like when I’m not at home or I’m asleep in bed. Here are the automations for sending me a notification. I have a “bedtime” script that turns out all the lights in my house and makes sure the doors are locked and the alarm is on. The notifications are conditioned to only send if I’m home and the bedtime script has not been run.

First, the automation to send the notification:

- alias: Kitty Litter Box Tracker - Notify that litter box needs to be cleaned
  description: ''
  trigger:
  - platform: state
    id: littermotion
    from: 'off'
    to: 'on'
    for:
      hours: 0
      minutes: 0
      seconds: 30
      milliseconds: 0
    entity_id: input_boolean.kitty_litter_box
  - platform: zone
    entity_id: device_tracker.my_iphone
    zone: zone.home
    event: enter
    id: arrivinghomelitterneedstobeemptied
  - at: 08:30:00
    platform: time
    id: morning
  condition:
  - condition: state
    entity_id: input_boolean.kitty_litter_box
    state: 'on'
  action:
  - choose:
    - conditions:
      - condition: trigger
        id: littermotion
      - condition: template
        value_template: '{{ ( as_timestamp(now()) - as_timestamp(state_attr(''script.bedtime'',
          ''last_triggered'')) |int(0) ) > 28800 }}'
      - condition: zone
        entity_id: device_tracker.my_iphone
        zone: zone.home
      sequence:
      - service: notify.mobile_app_my_iphone
        data:
          title: Litter Box Alert
          message: The litter box needs to be emptied.
          data:
            actions:
            - action: LITTER_CLEAN
              title: I cleaned the litter box.
            - action: REMIND_CLEAN
              title: Remind me in an hour.
    - conditions:
      - condition: trigger
        id: arrivinghomelitterneedstobeemptied
      sequence:
      - delay:
          hours: 0
          minutes: 2
          seconds: 0
          milliseconds: 0
      - service: notify.mobile_app_my_iphone
        data:
          title: Litter Box Alert
          message: The litter box needs to be emptied.
          data:
            actions:
            - action: LITTER_CLEAN
              title: I cleaned the litter box.
            - action: REMIND_CLEAN
              title: Remind me in an hour.
    - conditions:
      - condition: trigger
        id: morning
      - condition: zone
        entity_id: device_tracker.my_iphone
        zone: zone.home
      sequence:
      - service: notify.mobile_app_my_iphone
        data:
          title: Litter Box Alert
          message: The litter box needs to be emptied.
          data:
            actions:
            - action: LITTER_CLEAN
              title: I cleaned the litter box.
            - action: REMIND_CLEAN
              title: Remind me in an hour.
    default: []
  mode: single

Then, a second automation here for processing the actionable response:

- alias: Kitty Litter Box Tracker - iOS Response Processing
  description: ''
  trigger:
  - event_data:
      actionName: LITTER_CLEAN
    event_type: ios.notification_action_fired
    platform: event
    id: litterclean
  - event_data:
      actionName: REMIND_CLEAN
    event_type: ios.notification_action_fired
    platform: event
    id: remindclean
  condition: []
  action:
  - choose:
    - conditions:
      - condition: trigger
        id: litterclean
      sequence:
      - service: input_boolean.turn_off
        target:
          entity_id: input_boolean.kitty_litter_box
    - conditions:
      - condition: trigger
        id: remindclean
      sequence:
      sequence:
        - delay:
            hours: 1
            minutes: 0
            seconds: 0
            milliseconds: 0
        - condition: state
          entity_id: input_boolean.kitty_litter_box
          state: 'on'
        - service: notify.mobile_app_my_iphone
          data:
            title: Litter Box Alert
            message: 'Reminder: The litter box needs to be emptied.'
            data:
              actions:
                - action: LITTER_CLEAN
                  title: I cleaned the litter box.
                - action: REMIND_CLEAN
                  title: Remind me in an hour.
    default: []
  mode: single

Conditional Cards for Lovelace UI

I already have a cat tab on my Lovelace dashboard because we track the cat’s insulin shots - so I just added a couple of conditional cards that only show if the litter box needs to be cleaned. Basically, the idea is that I never use these because the process is automated. However, it is sometimes nice to just have a record of if it’s currently dirty, and if so, since when?

Kitty Shot Sensor

type: vertical-stack
cards:
  - type: conditional
    conditions:
      - entity: input_boolean.kitty_litter_box
        state: 'on'
    card:
      type: markdown
      content: >-
        ## <center>Litter box needs to be cleaned. (Since  {{
        relative_time(states.input_boolean.kitty_litter_box.last_changed) }}
        ago).
  - type: conditional
    conditions:
      - entity: input_boolean.kitty_litter_box
        state: 'on'
    card:
      type: button
      tap_action:
        action: call-service
        service: input_boolean.turn_off
        service_data: {}
        target:
          entity_id: input_boolean.kitty_litter_box
      name: Mark as Cleaned
      icon: mdi:shovel
      icon_height: 50px

CAT TAX


(#theonewhopoops)

5 Likes

Could you maybe tell me a little more about your setup (or potential setup)? I have the motion detector mounted so that it only detects motion over the litterbox. The bathroom can be used as normal without triggering any litterbox tracking automations.

As described in the original post, I have the light switch on the wall set up so that it functions normally - even when the red light is on. Essentially, the red indicator light only shows if the light switch is in the off position. If you turn the light switch on, the normal white light comes on and you can use the bathroom as usual. The red light comes back when you turn the light switch off unless you have also cleaned the litterbox while the switch was on. (Green only shows for a few seconds when the litter genie lid is opened to confirm that the counter/tracker has been reset without having to check the phone).

However…

Since I set all of this up, my wife and I had a baby - which has really cut down on my free time to clean the litterbox. It turned out the light was basically red all of the time which began to defeat the purpose. So, I’ve reworked it a little.

First, I’ve added a counter helper to give the cat three trips to the litterbox before I’m summoned by the red light (though you could pick any number).

Now, when motion is detected, the counter is incremented by 1, with some conditions built in to avoid multiple triggers from the same trip. This is the automation I use to do that:

alias: Kitty Litter Box Tracker - Set Counter
description: ""
trigger:
  - platform: state
    entity_id:
      - binary_sensor.kitty_litter_motion
    from: "off"
    to: "on"
    for:
      hours: 0
      minutes: 0
      seconds: 30
condition:
  - condition: template
    value_template: >-
      {{ ( as_timestamp(now()) -
      as_timestamp(states.binary_sensor.litter_genie_zigbee.last_changed)
      |int(0) ) > 90 }}
action:
  - service: counter.increment
    data: {}
    target:
      entity_id: counter.kitty_litter_counter
mode: single

And here is the more complicated automation for marking the litterbox “dirty” (like in the original post) so that the light automations are once again in play. (Light turns red if wall switch is off and litterbox has been used 3+ times since last cleaned):

alias: Kitty Litter Box Tracker - Turn On/Off Boolean
description: ""
trigger:
  - platform: numeric_state
    entity_id: counter.kitty_litter_counter
    id: litteron
    above: "2"
  - platform: state
    entity_id: binary_sensor.litter_genie_zigbee
    id: litteroff
    from: "off"
    to: "on"
  - platform: state
    entity_id:
      - input_boolean.kitty_litter_box
    id: litteroff2
    from: "on"
    to: "off"
  - platform: state
    entity_id:
      - input_button.litter_cleaned
    id: litteroff3
condition: []
action:
  - choose:
      - conditions:
          - condition: trigger
            id: litteron
          - condition: template
            value_template: >-
              {{ ( as_timestamp(now()) -
              as_timestamp(states.binary_sensor.litter_genie_zigbee.last_changed)
              |int(0) ) > 90 }}
        sequence:
          - service: input_boolean.turn_on
            target:
              entity_id: input_boolean.kitty_litter_box
            data: {}
      - conditions:
          - condition: or
            conditions:
              - condition: trigger
                id: litteroff
              - condition: trigger
                id: litteroff2
              - condition: trigger
                id: litteroff3
        sequence:
          - service: input_boolean.turn_off
            target:
              entity_id: input_boolean.kitty_litter_box
            data: {}
          - service: counter.reset
            data: {}
            target:
              entity_id: counter.kitty_litter_counter
          - data_template:
              datetime: >-
                {{
                as_timestamp(states.sensor.date_time_iso.state)|timestamp_custom("%Y-%m-%d
                %H:%M:%S") }}
            entity_id: input_datetime.kitty_litter_last_cleaned
            service: input_datetime.set_datetime
    default:
      - service: script.dummy
        data: {}
mode: single

Finally, I also added some template sensors in config to update the display a little:

- sensor:
#~~~~~~~~~~~~~~~~~~~~~~~~~~#
#~~ Kitty Litter Tracker ~~#
#~~~~~~~~~~~~~~~~~~~~~~~~~~#
      - name: Kitty Litter Box Dirty Since
        state: >-
            {% set t = (now() - as_timestamp(states('input_datetime.kitty_litter_dirty_since')) | as_datetime).total_seconds() %}
              {{ t | timestamp_custom('%-H hr %-M min' if t > 3600 else '%-M min', false) }}

      - name: Kitty Litter Last Cleaned
        state: >-
            {{ as_timestamp(states('input_datetime.kitty_litter_last_cleaned'))|timestamp_custom("%A at %-I:%M %p") }}

Card looks like this (I can share this code too if interested):

image