Home Assistant Dog Potty Training Timer/Logger

1) Purpose

The fastest possible housebreaking (especially in condos/high-rises) by making sure you always know exactly when your dog is due to go out. This focuses strictly on potty timing drivers: Eat/Drink and Out/Pee/Poop. These actions set two different timers, send push notifications, and record everything to a history log.


Yes my dog’s name is Chicken Nugget. Ignore the furbo switch – that isn’t in the below. That was a hard kill to the camera when I’m home.

2) What it Does

This project adds a compact “Potty Tracker” widget and backend logic to Home Assistant. By tapping a button when an event occurs, you update a “Due Time,” and Home Assistant alerts you via mobile notifications when that time expires. It’s more of a human automator than involving smart devices. No doubt this could be adapted for many other things beyond dog training.

Key Features:

  • One-tap buttons: OUT / PEE / POOP / EAT.
  • Accident Modifier (BAD): A toggle to tag the next entry as an indoor accident for pattern tracking.
  • Live Countdown: A “Due in / Overdue” markdown display.
  • Dual Timers: Configurable intervals for standard outings vs. immediate post-meal needs.
  • Smart Sleep Windows: Notifications only auto-enable if the next timer lands between 06:00 and 22:00.
  • Anti-Spam Alerts: Overdue notifications fire in specific windows (Immediate, 10m late, 30m late).

NOTE: I have a “BEDTIME” script that turns off the notifcation switch when I go to bed.

3) Background and Training Details

Potty training in a condo involves a built-in delay (hallways, elevators, and distance to grass). Consistency is the only way to succeed. This tracker makes the “when do we go again?” decision data-driven so you can stay ahead of the dog’s biological clock. The old “rub their nose in it” method has shown to achieve slower results with other negative consequences.

The Training Strategy:

  • The Reward Window: Dogs associate action with reward in a tiny window (1–3 seconds). We use high-value treats (like dehydrated smelt) immediately upon outdoor success.
  • The Signal: Pair this with a predictable “ask” (like a bell by the door). Our dog is a 1-year-old rescue stray; literature suggests that for rescues, it can take 2 to 6 months of total consistency before they are fully reliable.
  • Pattern Recognition: By tagging indoor accidents with the “Accident” modifier, you can see if your intervals are too long or if a specific meal time is causing issues.

4) The Code

Dependencies

  • HACS: custom:button-card
  • Home Assistant Mobile App: For push notifications.
  • Logbook: (Core integration).

A. Helpers (configuration.yaml)

Add these to your YAML or create them via the Settings > Helpers UI.

input_boolean:
  dog_bad_modifier:
    name: "Accident Modifier"
    icon: mdi:alert-decagram
  dog_notifications_enabled:
    name: "Potty Notifications"
    icon: mdi:bell-ring
  dog_show_log:
    name: "Show Potty History"
    icon: mdi:history

input_datetime:
  dog_due_time:
    name: "Next Potty Break Due"
    has_date: true
    has_time: true
    icon: mdi:clock-alert

input_number:
  dog_interval_out:
    name: "Timer: Out/Pee/Poop"
    min: 0.5
    max: 8
    step: 0.5
    mode: box
    unit_of_measurement: "h"
    initial: 3.0
    icon: mdi:door-open

  dog_interval_eat:
    name: "Timer: After Eating"
    min: 0.5
    max: 8
    step: 0.5
    mode: box
    unit_of_measurement: "h"
    initial: 1.0
    icon: mdi:food-drumstick

input_text:
  dog_activity_log_store:
    name: "Potty History Store"
    icon: mdi:history

B. Script (scripts.yaml)

This script calculates the timers and handles the “Smart Notification Window.” The first script calculates the timer and handles re-enabling notifications. The second is an example of the “Bed Button” logic to silence alerts at night.

dog_log_activity:
  alias: Log Potty Activity
  icon: mdi:dog
  mode: single
  fields:
    activity_type:
      description: "Activity Type"
  sequence:
    # --- SMART NOTIFICATION LOGIC ---
    # Re-enable alerts ONLY if the next alert falls between 06:00 and 22:00.
    - choose:
        - conditions:
            - condition: template
              value_template: >
                {% set interval = states('input_number.dog_interval_eat')|float
                   if activity_type == 'Eat'
                   else states('input_number.dog_interval_out')|float %}
                {% set next_alert = (now() + timedelta(hours=interval)).strftime('%H:%M') %}
                {{ '06:00' <= next_alert <= '22:00' }}
          sequence:
            - service: input_boolean.turn_on
              target:
                entity_id: input_boolean.dog_notifications_enabled

    # Log to History (Check for accident modifier)
    - choose:
        - conditions:
            - condition: state
              entity_id: input_boolean.dog_bad_modifier
              state: "on"
          sequence:
            - service: logbook.log
              data:
                name: "Dog Tracker"
                message: "⚠️ ACCIDENT: {{ activity_type }}"
                entity_id: input_text.dog_activity_log_store
            - service: input_boolean.turn_off
              target:
                entity_id: input_boolean.dog_bad_modifier
      default:
        - service: logbook.log
          data:
            name: "Dog Tracker"
            message: "{{ activity_type }}"
            entity_id: input_text.dog_activity_log_store

    # Set the Countdown
    - service: input_datetime.set_datetime
      target:
        entity_id: input_datetime.dog_due_time
      data:
        timestamp: >
          {% if activity_type == 'Eat' %}
            {{ (now().timestamp() + (states('input_number.dog_interval_eat')|float * 3600)) }}
          {% else %}
            {{ (now().timestamp() + (states('input_number.dog_interval_out')|float * 3600)) }}
          {% endif %}

# Bed Button / Go to Sleep Script
go_bed_actions:
  alias: Bed Button
  sequence:
    - service: input_boolean.turn_off
      target:
        entity_id: input_boolean.dog_notifications_enabled
    # [Add other bedtime actions here like turning off lights]

C. Automation (automations.yaml)

Handles overdue alerts. Replace mobile_app_your_phone with your actual notify service.

- id: 'dog_potty_countdown_alert'
  alias: Dog Potty Overdue Alert
  trigger:
    - platform: time_pattern
      minutes: "/5"
  condition:
    - condition: state
      entity_id: input_boolean.dog_notifications_enabled
      state: 'on'
    - condition: template
      value_template: >
        {% set due = states('input_datetime.dog_due_time') %}
        {% if due not in ['unknown', 'unavailable'] %}
          {% set due_dt = due | as_datetime | as_local %}
          {% set overdue_min = ((now() - due_dt).total_seconds() / 60) | int %}
          {{ overdue_min >= 0 and (
             overdue_min < 5 or
             (overdue_min >= 10 and overdue_min < 15) or
             (overdue_min >= 30 and overdue_min < 35)
          ) }}
        {% else %}
          false
        {% endif %}
  action:
    - service: notify.mobile_app_your_phone
      data:
        message: "Potty break needed! Timer expired."
        title: "Dog Alert 🐶"
        data:
          ttl: 0
          priority: high

D. Dashboard Card (Lovelace YAML)

type: vertical-stack
title: Dog Potty Tracker 🐶
cards:
  - type: grid
    columns: 6
    cards:
      - type: custom:button-card
        icon: mdi:door-open
        name: OUT
        tap_action:
          action: call-service
          service: script.dog_log_activity
          service_data: {activity_type: Out}
        show_name: true
      - type: custom:button-card
        icon: mdi:emoticon-poop
        name: POOP
        tap_action:
          action: call-service
          service: script.dog_log_activity
          service_data: {activity_type: Poop}
        show_name: true
      - type: custom:button-card
        icon: mdi:water
        name: PEE
        tap_action:
          action: call-service
          service: script.dog_log_activity
          service_data: {activity_type: Pee}
        show_name: true
      - type: custom:button-card
        icon: mdi:food-drumstick
        name: EAT
        tap_action:
          action: call-service
          service: script.dog_log_activity
          service_data: {activity_type: Eat}
        show_name: true
      - type: custom:button-card
        entity: input_boolean.dog_bad_modifier
        name: ACCIDENT
        icon: mdi:alert-decagram
        show_name: true
        tap_action: {action: toggle}
        styles:
          card:
            - background-color: "[[[ return states['input_boolean.dog_bad_modifier'].state === 'on' ? 'red' : 'initial'; ]]]"
      - type: custom:button-card
        entity: input_boolean.dog_show_log
        name: LOGS
        icon: mdi:clipboard-list
        show_name: true
        tap_action: {action: toggle}
  - type: markdown
    content: >
      <center><h2>
      {% set due_state = states('input_datetime.dog_due_time') %}
      {% if due_state not in ['unknown', 'unavailable'] %}
        {% set due = due_state | as_datetime | as_local %}
        {% set remaining = (due - now()).total_seconds() %}
        {% if remaining > 0 %}
          ✅ Due in: {{ (remaining / 3600) | int }}:{{ '%02d' % ((remaining % 3600) / 60) | int }}
        {% else %}
          🚨 OVERDUE: {{ (remaining | abs / 3600) | int }}:{{ '%02d' % ((remaining | abs % 3600) / 60) | int }}
        {% endif %}
      {% else %} Ready... {% endif %}
      </h2></center>
  - type: entities
    entities:
      - entity: input_boolean.dog_notifications_enabled
      - entity: input_number.dog_interval_out
      - entity: input_number.dog_interval_eat
  - type: conditional
    conditions:
      - entity: input_boolean.dog_show_log
        state: "on"
    card:
      type: logbook
      entities: [input_text.dog_activity_log_store]
      hours_to_show: 24
1 Like