Litter Robot cat toilet monitoring with DJT11LM Aqara vibration sensor

My two Canadian Sphynx cat use the Litter Robot toilet. It is one of the most useful things a cat owner can have as it saves me loads of time cleaning. Most of the time it functions perfectly, but on couple of occasions it got stuck half way through the cleaning cycle, leaving my cats worried - “WTF has happened to the toilet?” One can buy a Wifi module and connect the Litter Robot to the app and be able to get notifications, but it costs 100 USD while pretty much the same functionality can be achieved using the vibration sensor and some rule logic in OH.

The idea is that the sensor mounted on top of the container would detect motion once the cleaning cycle start and will begin sending the XYZ coordinates of its location, so we can identify the position of the container and detect the current cycle ( cleaning, filling and ready cycle)

mqtt-vibro.things

    Thing topic sens_vibr02 "Vibration02" @ "LitterRobot" {
  Channels:
    Type number : batt              "Battery"       [ stateTopic="zigbee2mqtt/sens_vibr02", transformationPattern="JSONPATH:$.battery" ]
    Type number : volt              "Voltage"       [ stateTopic="zigbee2mqtt/sens_vibr02", transformationPattern="JSONPATH:$.voltage" ]
    Type number : link              "Link"          [ stateTopic="zigbee2mqtt/sens_vibr02", transformationPattern="JSONPATH:$.linkquality" ]
    Type number : angle             "Angle"         [ stateTopic="zigbee2mqtt/sens_vibr02", transformationPattern="JSONPATH:$.angle" ]
    Type number : angle_x           "Angle_x"       [ stateTopic="zigbee2mqtt/sens_vibr02", transformationPattern="JSONPATH:$.angle_x" ]
    Type number : angle_y           "Angle_y"       [ stateTopic="zigbee2mqtt/sens_vibr02", transformationPattern="JSONPATH:$.angle_y" ]
    Type number : angle_z           "Angle_z"       [ stateTopic="zigbee2mqtt/sens_vibr02", transformationPattern="JSONPATH:$.angle_z" ]
    Type number : angle_x_absolute  "Angle_x_abs"   [ stateTopic="zigbee2mqtt/sens_vibr02", transformationPattern="JSONPATH:$.angle_x_absolute" ]
    Type number : angle_y_absolute  "Angle_y_abs"   [ stateTopic="zigbee2mqtt/sens_vibr02", transformationPattern="JSONPATH:$.angle_y_absolute" ]
    Type string : sensitivity       "Sensitivity"   [ stateTopic="zigbee2mqtt/sens_vibr02", transformationPattern="JSONPATH:$.sensitivity" ]
    Type number : strength          "Strength"      [ stateTopic="zigbee2mqtt/sens_vibr02", transformationPattern="JSONPATH:$.strength" ]
    Type string : action            "Action"        [ stateTopic="zigbee2mqtt/sens_vibr02", transformationPattern="REGEX:(.*action.*)∩JSONPATH:$.action" ]
    Type number : lastseen_epoch    "LastSeen"      [ stateTopic="zigbee2mqtt/sens_vibr02", transformationPattern="JSONPATH:$.last_seen" ]
}

mqtt-vibro.items

Number          sens_vibr02_batt                        "Vibr02 Battery [%.1f %%]"                              <battery>               (gPers_Change_Day, gZB_bat)     {channel="mqtt:topic:mymosquitto:sens_vibr02:batt"}
Number          sens_vibr02_volt                        "Vibr02 Volt [%d mV]"                                   <energy>                                                {channel="mqtt:topic:mymosquitto:sens_vibr02:volt"}
Number          sens_vibr02_link                        "Vibr02 Link [%s]"                                      <qualityofservice>      (gPers_Change_Day)              {channel="mqtt:topic:mymosquitto:sens_vibr02:link"}
Number          sens_vibr02_angle                       "Vibr02 Angle [%d]"                                     <incline>                                               {channel="mqtt:topic:mymosquitto:sens_vibr02:angle"}
Number          sens_vibr02_angle_x                     "Vibr02 Angle X [%d]"                                   <incline>                                               {channel="mqtt:topic:mymosquitto:sens_vibr02:angle_x"}
Number          sens_vibr02_angle_y                     "Vibr02 Angle Y [%d]"                                   <incline>                                               {channel="mqtt:topic:mymosquitto:sens_vibr02:angle_y"}
Number          sens_vibr02_angle_z                     "Vibr02 Angle Z [%d]"                                   <incline>                                               {channel="mqtt:topic:mymosquitto:sens_vibr02:angle_z"}
Number          sens_vibr02_angle_x_abs                 "Vibr02 Absolute X [%d]"                                <incline>                                               {channel="mqtt:topic:mymosquitto:sens_vibr02:angle_x_absolute"}
Number          sens_vibr02_angle_y_abs                 "Vibr02 Absolute Y [%d]"                                <incline>                                               {channel="mqtt:topic:mymosquitto:sens_vibr02:angle_y_absolute"}
String          sens_vibr02_sensitivity                 "Vibr02 Sensitivity [%s]"                                                                                       {channel="mqtt:topic:mymosquitto:sens_vibr02:sensitivity"}
Number          sens_vibr02_strength                    "Vibr02 Strength [%d]"                                  <heating>               (gPers_Change_Day)              {channel="mqtt:topic:mymosquitto:sens_vibr02:strength"}
String          sens_vibr02_action                      "Vibr02 Action [%s]"                                    <movecontrol>           (gPers_Change_Day)              {channel="mqtt:topic:mymosquitto:sens_vibr02:action"}
Number          sens_vibr02_lastseen_epoch              "Vibr02 [%s]"                                                                   (gZB_lastseen)                  {channel="mqtt:topic:mymosquitto:sens_vibr02:lastseen_epoch"}
DateTime        sens_vibr02_lastseen_datetime           "Last Seen: [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]"

Some specific items holding informatiob on the current status, time of last update, number of clean cycles the toilet has made and .how many times a day it got stuck.
litterrobot.items

String  litterrobot_status              "LitterRobot status [%s]"                                               <toilet>        (gPers_Change_Day)
String  litterrobot_lastupdate          "LitterRobot last update  [%1$ta, %1$ty-%1$tm-%1$td %1$tH:%1$tM:%1$tS]"
Switch  litterrobot_timer                                                                                                                               {expire="120s, command=OFF"} // Timer
Number  litterrobot_day_cycles          "Daily cycles [%d]"                                                     <pressure>      (gPers_Change_Day)
Number  litterrobot_day_stuck           "Daily stuck [%d]"                                                      <error>         (gPers_Change_Day)

First rule determines the current cleaning phase, but first you have to record the measurement of the sensor at three points, when the toilet is empty, when the toilet is filled and when the toilet is ready.
I use the angle_x, angle_y, angle_z for this. Also it makes sense not to glue the sensor to the toilet but use velcro instead, so you can remove the sensor when you need to clean the container. I would expect the you would need to “recalibrate” the angle_XYZ after you reattach the sensor.

The second rule waits 2 minutes after the completion of the last phase and checks the location. This extra delay is needed as cats can sometime stop the cleaning cycle by applying pressure on the toilet during rotation.

The third rule checks number of cycles the toiltet had run for and resets the daily counter.

zb_vibro-litterrobot.rules

rule "LitterRobot status"
  
when
    Item sens_vibr02_angle_x received update or
    Item sens_vibr02_angle_y received update or 
    Item sens_vibr02_angle_z received update

then
      //Ph1 - Empty
      var ph1_angle_x_target = 38
      var ph1_angle_x_upper  = ph1_angle_x_target + 5
      var ph1_angle_x_lower  = ph1_angle_x_target - 5

      var ph1_angle_y_target = 50
      var ph1_angle_y_upper  = ph1_angle_y_target + 5
      var ph1_angle_y_lower  = ph1_angle_y_target - 5

      var ph1_angle_z_target = -11
      var ph1_angle_z_upper  = ph1_angle_z_target + 5
      var ph1_angle_z_lower  = ph1_angle_z_target - 5
      //Ph2 - Fill
      var ph2_angle_x_target = 27
      var ph2_angle_x_upper  = ph2_angle_x_target + 5
      var ph2_angle_x_lower  = ph2_angle_x_target - 5

      var ph2_angle_y_target = -7
      var ph2_angle_y_upper  = ph2_angle_y_target + 5
      var ph2_angle_y_lower  = ph2_angle_y_target - 5

      var ph2_angle_z_target = 62
      var ph2_angle_z_upper  = ph2_angle_z_target + 5
      var ph2_angle_z_lower  = ph2_angle_z_target - 5
      //Ph3 - Ready
      var ph3_angle_x_target = 2
      var ph3_angle_x_upper  = ph3_angle_x_target + 5
      var ph3_angle_x_lower  = ph3_angle_x_target - 5

      var ph3_angle_y_target = -10
      var ph3_angle_y_upper  = ph3_angle_y_target + 5
      var ph3_angle_y_lower  = ph3_angle_y_target - 5

      var ph3_angle_z_target = 80
      var ph3_angle_z_upper  = ph3_angle_z_target + 5
      var ph3_angle_z_lower  = ph3_angle_z_target - 5

//    logInfo("LitterRobot DEBUG", "X: " + sens_vibr02_angle_x.state + " Y: " + sens_vibr02_angle_y.state + " Z: " + sens_vibr02_angle_z.state + " TILT: " + sens_vibr02_action.state)

    if ( sens_vibr02_angle_x.state >= ph1_angle_x_lower && sens_vibr02_angle_x.state <= ph1_angle_x_upper &&
         sens_vibr02_angle_y.state >= ph1_angle_y_lower && sens_vibr02_angle_y.state <= ph1_angle_y_upper &&
         sens_vibr02_angle_z.state >= ph1_angle_z_lower && sens_vibr02_angle_z.state <= ph1_angle_z_upper &&
         sens_vibr02_action.state == "tilt") {
//       logInfo("LitterRobot Ph1 ", "X: " + sens_vibr02_angle_x.state + " Y: " + sens_vibr02_angle_y.state + " Z: " + sens_vibr02_angle_z.state + " TILT: " + sens_vibr02_action.state)
       postUpdate(litterrobot_status, "Ph1 - Empty")
       postUpdate(litterrobot_lastupdate,new DateTimeType())
       postUpdate(litterrobot_timer,  "ON")
    }

    if ( sens_vibr02_angle_x.state >= ph2_angle_x_lower && sens_vibr02_angle_x.state <= ph2_angle_x_upper &&
         sens_vibr02_angle_y.state >= ph2_angle_y_lower && sens_vibr02_angle_y.state <= ph2_angle_y_upper &&
         sens_vibr02_angle_z.state >= ph2_angle_z_lower && sens_vibr02_angle_z.state <= ph2_angle_z_upper &&
         sens_vibr02_action.state == "tilt") {
//       logInfo("LitterRobot Ph2 ", "X: " + sens_vibr02_angle_x.state + " Y: " + sens_vibr02_angle_y.state + " Z: " + sens_vibr02_angle_z.state + " TILT: " + sens_vibr02_action.state)
       postUpdate(litterrobot_status, "Ph2 - Fill")
       postUpdate(litterrobot_lastupdate,new DateTimeType())
       postUpdate(litterrobot_timer,  "ON")
    }

    if ( sens_vibr02_angle_x.state >= ph3_angle_x_lower && sens_vibr02_angle_x.state <= ph3_angle_x_upper &&
         sens_vibr02_angle_y.state >= ph3_angle_y_lower && sens_vibr02_angle_y.state <= ph3_angle_y_upper &&
         sens_vibr02_angle_z.state >= ph3_angle_z_lower && sens_vibr02_angle_z.state <= ph3_angle_z_upper &&
         sens_vibr02_action.state == "tilt") {
//       logInfo("LitterRobot Ph3 ", "X: " + sens_vibr02_angle_x.state + " Y: " + sens_vibr02_angle_y.state + " Z: " + sens_vibr02_angle_z.state + " TILT: " + sens_vibr02_action.state)
       postUpdate(litterrobot_status, "Ph3 - Ready")
       postUpdate(litterrobot_lastupdate,new DateTimeType())
       postUpdate(litterrobot_timer,  "ON")
       postUpdate(sens_vibr02_action,  "")      //Need to erase action, so that hourly updates do not trigger the Phase3 while toilet is stationary
    }
end



rule "LitterRobot timer OFF"
  when
    Item litterrobot_timer received update "OFF"
  then
   // Initialise variables if NULL
   if ( litterrobot_day_cycles.state == NULL ) {
      logInfo("VARIABLE: ", "uninitialised " + litterrobot_day_cycles)
      postUpdate(litterrobot_day_cycles,0)
      Thread::sleep(10)
   }

   if ( litterrobot_day_stuck.state == NULL ) {
      logInfo("VARIABLE: ", "uninitialised " + litterrobot_day_stuck)
      postUpdate(litterrobot_day_stuck,0)
      Thread::sleep(10)
   }

   // update counters and send notifications
   if ( litterrobot_status.state != "Ph3 - Ready" ) {
      logInfo("LitterRobot ", "Stuck after " + litterrobot_status.state )
      sendPushoverMessage(pushoverBuilder("LitterRobot: STUCK after " + litterrobot_status.state).withEmergencyPriority())
      postUpdate(litterrobot_status, "STUCK")
      postUpdate(litterrobot_day_stuck,litterrobot_day_stuck.state as DecimalType + 1)
      Thread::sleep(10)
   }
   else {
      logInfo("LitterRobot ", "Cycled and ready")
      sendPushoverMessage(pushoverBuilder("LitterRobot cycled and ready"))
      postUpdate(litterrobot_day_cycles,litterrobot_day_cycles.state as DecimalType + 1)
      Thread::sleep(10)
   }
end


rule "Check usage and reset counter"
   when
      Time cron "0 0 0 * * ?"
   then
      if ( litterrobot_day_cycles.state == 0 ) {
         logInfo("LitterRobot ", "Didn't cycle in the last 24hr. Please investigate.")
      }
      postUpdate(litterrobot_day_cycles,0)
      postUpdate(litterrobot_day_stuck,0)
end

Finally the .sitemap portion

        Frame label="Litter Robot" {
                Text item=litterrobot_status
                Text item=litterrobot_lastupdate
                Text item=litterrobot_day_cycles
                Text item=litterrobot_day_stuck
        }
5 Likes

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.