Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature implementation example: Monitor food consumption #41

Open
magicmega opened this issue Jul 13, 2023 · 1 comment
Open

Feature implementation example: Monitor food consumption #41

magicmega opened this issue Jul 13, 2023 · 1 comment

Comments

@magicmega
Copy link

magicmega commented Jul 13, 2023

Summary

This code takes the detailed attributes from the SureHA bowl entities to track daily consumption for the cat and also set up some notifications.

Integrations

Sure Petcare has the following entities exposed. I don't think I use any of these in the code below, but they are in the lovelace frontend code below so including them for completion

Entities exposed; (not yaml)

binary_sensor.nik
sensor.niks_bowl_battery_level
binary_sensor.niks_bowl_connectivity_2
binary_sensor.surepet_hub 

SureHA integration (installed through HACS)

SureHA entities

Entities exposed: (not yaml)

Feeder Nik’s bowl
sensor.feeder_niks_bowl

Feeder Nik’s bowl Battery Level
sensor.feeder_niks_bowl_battery_level

Hub Surepet Hub
binary_sensor.hub_surepet_hub

Nik - Dry food remaining
sensor.feeder_niks_bowl_bowl_0

Nik - Wet food remaining
sensor.feeder_niks_bowl_bowl_1

Nik’s Bowl Connectivity
binary_sensor.niks_bowl_connectivity

Pet Nik
binary_sensor.pet_nik

Config entry:

sensors: !include sensors.yaml
var: !include variables.yaml

Sensors & vars

Set up the following sensors

sensors.yaml

  - platform: template
    sensors:
      nik_daily_dry:
        friendly_name: "Nik daily dry food (replicate var))"
        value_template: "{{ states('var.nik_dry_since_2am') | float}}"
        unit_of_measurement: "g"
        
      nik_daily_wet:
        friendly_name: "Nik daily wet food (replicate var)"
        value_template: "{{ states('var.nik_wet_since_2am') | float}}"
        unit_of_measurement: "g"
    
      nik_daily_wet_and_dry:
        friendly_name: "Nik daily total food (replicate var)"
        value_template: "{{ states('var.nik_wet_since_2am') | float + states('var.nik_dry_since_2am') | float}}"
        unit_of_measurement: "g"    
    
      nik_daily_dry_calories:  #optional
        friendly_name: "Nik daily dry food calorie consumption"
        value_template: "{{ states('input_number.nik_dry_food_calories_per_kg') | float /1000 * (states('sensor.nik_daily_dry') | float) }}"
        unit_of_measurement: "kCal"
        
      nik_daily_wet_calories:  #optional
        friendly_name: "Nik daily wet food calorie consumption"
        value_template: "{{ states('input_number.nik_wet_food_calories_per_kg') | float /1000 * (states('sensor.nik_daily_wet') | float) }}"
        unit_of_measurement: "kCal"

      nik_daily_total_calories:
        friendly_name: "Nik daily total food calorie consumption"
        value_template: "{{ states('sensor.nik_daily_dry_calories') | float + states('sensor.nik_daily_wet_calories') | float }}"
        unit_of_measurement: "kCal"        

      nik_feeding_change_wet: 
        friendly_name: "Nik Feeding Change - Wet"
        unit_of_measurement: "g"
        unique_id: nik_feeding_change_wet
        value_template: >-
         {% set feeder_status_attr = state_attr('binary_sensor.pet_nik', 'status') %}
         {{ feeder_status_attr.feeding.change.1 | float }}

      nik_feeding_change_dry: 
        friendly_name: "Nik Feeding Change - Dry"
        unit_of_measurement: "g"
        unique_id: nik_feeding_change_dry
        value_template: >-
         {% set feeder_status_attr = state_attr('binary_sensor.pet_nik', 'status') %}
         {{ feeder_status_attr.feeding.change.0 | float }}         
         
      nik_feeding_time:
        friendly_name: "Nik last ate at"
        unique_id: nik_feeding_time
        value_template: >-
         {% set feeder_status_attr = state_attr('binary_sensor.pet_nik', 'status') %}
         {{ as_timestamp(feeder_status_attr.feeding.at) | timestamp_custom('%a, %I:%M %p') }}

Set up the following 'var'. I have variables+history added in HACS - i think this is using this.

var.yaml

nik_dry_since_2am:
  friendly_name: 'Nik dry food consumption since 2am'
  initial_value: 0
  unique_id: 'nikdryfoodconsumption'
  icon: mdi:cat
  unit_of_measurement: g

nik_wet_since_2am:
  friendly_name: 'Nik wet food consumption since 2am'
  initial_value: 0
  unique_id: 'nikwetfoodconsumption'
  icon: mdi:cat
  unit_of_measurement: g

Optional - use utility meter to view consumption over time:

utility_meter:
  nik_daily_dry_food_consumption:
    name: 'Nik daily dry food meter'
    source: sensor.nik_daily_dry
    cycle: daily
    offset:
      hours: 2
    net_consumption: false
    delta_values: false
    periodically_resetting: true
  nik_daily_wet_food_consumption:
    name: 'Nik daily wet food meter'
    source: sensor.nik_daily_wet
    cycle: daily
    offset:
      hours: 2
    net_consumption: false
    delta_values: false
    periodically_resetting: true

Also make sure you're including the above sensor and var entities in your recorder.

Some input helpers

Sorry not sure how to show these in Yaml as i set them up in the UI


(Input Numbers)

set these up with step = 1
Nik dry food Calories per kg
input_number.nik_dry_food_calories_per_kg

Nik Dry Food Last Weight
input_number.nik_dry_food_last_weight

Nik wet food Calories per kg
input_number.nik_wet_food_calories_per_kg

Nik Wet Food Last Weight
input_number.nik_wet_food_last_weight

(Input DateTime)

Nik last ate
input_datetime.nik_last_ate

Nik last fed
input_datetime.nik_last_fed

(Input boolean -toggle)

Nik_fed_breakfast
input_boolean.nik_fed_breakfast

nik_fed_dinner
input_boolean.nik_fed_dinner

Automations and scripts

This is triggered when the weight of the bowls change. It works out if it is added to (fed) or subtracted from (ate). I've included this as one automation as previously I had them separately and both were changing similar variables so ended up both running.

When the cat eats, it adds the recent feed to the daily consumption variables and sets the time of the feed
When the cat is fed, it sets the time that the cat was last fed (i.e. the bowls were filled by a human), updates the variables measuring how much is left in each bowl, and sets the booleans for 'fed dinner' or 'fed breakfast' based on the time

alias: Nik last fed or ate
description: Nik's bowl changes weight - action 'fed' or 'ate'
trigger:
  - platform: template
    value_template: >
      {{(((states("sensor.feeder_niks_bowl_bowl_0") | float) !=
      (states("input_number.nik_dry_food_last_weight")) | float)) or
      ((states("sensor.feeder_niks_bowl_bowl_1") | float) !=
      (states("input_number.nik_wet_food_last_weight") | float))}}
    for:
      hours: 0
      minutes: 0
      seconds: 10
    variables:
      dry_from: >-
        {{states('input_number.nik_dry_food_last_weight') |
        float('unavailable')}}
      dry_to: "{{states('sensor.feeder_niks_bowl_bowl_0') | float('unavailable')}}"
      wet_from: >-
        {{states('input_number.nik_wet_food_last_weight') |
        float('unavailable')}}
      wet_to: "{{states('sensor.feeder_niks_bowl_bowl_1') | float('unavailable')}}"
condition:
  - condition: and
    conditions:
      - condition: template
        value_template: "{{ dry_from not in ['unknown', 'unavailable'] }}"
      - condition: template
        value_template: "{{ dry_to not in ['unknown', 'unavailable'] }}"
      - condition: template
        value_template: "{{ wet_from not in ['unknown', 'unavailable'] }}"
      - condition: template
        value_template: "{{ wet_to not in ['unknown', 'unavailable'] }}"
      - condition: template
        value_template: "{{ not(wet_from == wet_to and dry_from == dry_to) }}"
action:
  - variables:
      dry_from: "{{states('input_number.nik_dry_food_last_weight') | float(0.0)}}"
      dry_to: "{{states('sensor.feeder_niks_bowl_bowl_0') | float(0.0)}}"
      wet_from: "{{states('input_number.nik_wet_food_last_weight') | float(0.0)}}"
      wet_to: "{{states('sensor.feeder_niks_bowl_bowl_1') | float(0.0)}}"
      what_happened: >
        {% if (dry_to > dry_from ) or (wet_to > wet_from) %} added_food {% elif
        (dry_to < dry_from ) or (wet_to < wet_from) %} ate_food {% else %} error
        {% endif %}
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ what_happened == 'added_food' }}"
        sequence:
          - service: input_datetime.set_datetime
            data:
              datetime: "{{now()}}"
            target:
              entity_id: input_datetime.nik_last_fed
          - service: input_number.set_value
            data:
              value: "{{states('sensor.feeder_niks_bowl_bowl_0')}}"
            target:
              entity_id: input_number.nik_dry_food_last_weight
          - service: input_number.set_value
            data:
              value: "{{states('sensor.feeder_niks_bowl_bowl_1')}}"
            target:
              entity_id: input_number.nik_wet_food_last_weight
          - if:
              - condition: time
                after: "04:30:00"
                before: "15:00:00"
                weekday:
                  - sun
                  - mon
                  - tue
                  - wed
                  - thu
                  - fri
                  - sat
            then:
              - service: input_boolean.turn_on
                data: {}
                target:
                  entity_id: input_boolean.nik_fed_breakfast
            else:
              - if:
                  - condition: time
                    after: "14:59:59"
                    before: "01:00:00"
                    weekday:
                      - sun
                      - mon
                      - tue
                      - wed
                      - thu
                      - fri
                      - sat
                then:
                  - service: input_boolean.turn_on
                    data: {}
                    target:
                      entity_id: input_boolean.nik_fed_dinner
      - conditions:
          - condition: template
            value_template: "{{ what_happened == 'ate_food' }}"
        sequence:
          - service: input_datetime.set_datetime
            data:
              datetime: "{{now()}}"
            target:
              entity_id: input_datetime.nik_last_ate
          - service: input_number.set_value
            data:
              value: "{{states('sensor.feeder_niks_bowl_bowl_0')}}"
            target:
              entity_id: input_number.nik_dry_food_last_weight
          - service: input_number.set_value
            data:
              value: "{{states('sensor.feeder_niks_bowl_bowl_1')}}"
            target:
              entity_id: input_number.nik_wet_food_last_weight
          - service: var.set
            data:
              entity_id: var.nik_wet_since_2am
              value: >-
                {{states("var.nik_wet_since_2am") | float -
                states("sensor.nik_feeding_change_wet") | float}}
          - service: var.set
            data:
              entity_id: var.nik_dry_since_2am
              value: >-
                {{states("var.nik_dry_since_2am") | float -
                states("sensor.nik_feeding_change_dry") | float}}
    default:
      - stop: No trigger id
trace:
  stored_traces: 10
mode: single


This next one resets the dinner and breakfast flags at 2am.


- id: '[yours here 3]'
  alias: Reset Nik dinner breakfast flags
  description: ''
  trigger:
  - platform: time
    at: 02:00:00
  condition: []
  action:
  - service: input_boolean.turn_off
    data: {}
    target:
      entity_id:
      - input_boolean.nik_fed_dinner
      - input_boolean.nik_fed_breakfast
  mode: single

This next one notifies me if Nik hasn't been fed breakfast by 8.30am, and again at 10am and 11am

- id: '[yours here 4]'
  alias: Nik hungry (breakfast)
  description: ''
  trigger:
  - platform: time
    at: 08:30:00
  - platform: time
    at: '10:00:00'
  - platform: time
    at: '11:00:00'
  condition:
  - condition: state
    entity_id: input_boolean.nik_fed_breakfast
    state: 'off'
  action:
  - service: notify.[your-mobile-app-address-here-without-brackets]
    data:
      message: Cat is hungry :(
      title: Nik hasn't been fed breakfast yet!!
  mode: single


This one does the same but for dinner - 7.30pm etc.

- id: '[yours here 5]'
  alias: Nik hungry (dinner)
  description: ''
  trigger:
  - platform: time
    at: '19:30:00'
  - platform: time
    at: '22:00:00'
  - platform: time
    at: '21:00:00'
  condition:
  - condition: state
    entity_id: input_boolean.nik_fed_dinner
    state: 'off'
  action:
  - service: notify.[your-mobile-address-here-without-brackets]
    data:
      message: Cat is hungry :(
      title: Nik hasn't been fed dinner yet!!
  mode: single

This one tells me if its after 6pm, nik hasn't been fed dinner, and he has come in and tried to eat (usually if i'm nearby i won't feed him until he comes in, to make it as fresh as possible

- id: '[yours here 6]'
  alias: Nik hungry (dinner and tried)
  description: ''
  trigger:
  - platform: state
    entity_id:
    - input_datetime.nik_last_ate
    for:
      hours: 0
      minutes: 0
      seconds: 10
  condition:
  - condition: state
    entity_id: input_boolean.nik_fed_dinner
    state: 'off'
  - condition: time
    after: '18:00:00'
    weekday:
    - sat
    - fri
    - thu
    - wed
    - tue
    - mon
    - sun
    before: '23:59:00'
  action:
  - service: notify.[your-mobile-address-here-without-brackets]
    data:
      message: Cat is hungry and just tried to eat :(
      title: Nik hasn't been fed dinner yet and he has just tried to eat something
        in his bowl!!
  mode: single

Same for breakfast

- id: '[yours here 7]'
  alias: Nik hungry (breakfast and tried)
  description: ''
  trigger:
  - platform: state
    entity_id:
    - input_datetime.nik_last_ate
    for:
      hours: 0
      minutes: 0
      seconds: 10
  condition:
  - condition: state
    entity_id: input_boolean.nik_fed_breakfast
    state: 'off'
    for:
      hours: 0
      minutes: 0
      seconds: 10
  - condition: time
    after: 07:00:00
    weekday:
    - sat
    - fri
    - thu
    - wed
    - tue
    - mon
    - sun
    before: '15:00:00'
  action:
  - service: notify.[your-mobile-address-here-without-brackets]
    data:
      message: Cat is hungry and just tried to eat :(
      title: Nik hasn't been fed breakfast yet and he has just tried to eat something
        in his bowl!!
  mode: single

This one lets me know if either the dry or wet bowl has dipped below 15g


- id: '[yours here 8]'
  alias: Food bowl low
  description: ''
  trigger:
  - platform: numeric_state
    entity_id: sensor.feeder_niks_bowl_bowl_0
    below: 15
    id: dry_low
  - platform: numeric_state
    entity_id: sensor.feeder_niks_bowl_bowl_1
    below: 15
    id: wet_low
  condition:
  - condition: time
    after: 07:00:00
    before: '21:00:00'
    weekday:
    - sun
    - mon
    - tue
    - wed
    - thu
    - fri
    - sat
  action:
  - service: notify.[person1]
    data:
      title: Cat hungry - low food
      message: "{% if states('sensor.feeder_niks_bowl_bowl_0') | int <= 15 -%} \n
        \ {% if states('sensor.feeder_niks_bowl_bowl_1') | int <= 15 -%}\n    Both
        bowls are low!  \n  {% else -%}\n    Dry bowl is low! \n  {%- endif -%}\n{%-
        else -%}\n  {% if states('sensor.feeder_niks_bowl_bowl_1') | int <= 15 -%}\n
        \   Wet bowl is low!  \n  {% else -%}\n    No bowl is low. \n  {%- endif -%}\n{%-
        endif -%}\n{{''}}\nDry: {{states('sensor.feeder_niks_bowl_bowl_0') | int }}g
        \ Wet: {{states('sensor.feeder_niks_bowl_bowl_1') | int }}g\n"
      data:
        push:
          sound:
            name: default
            volume: 1
        group: Cats
  - condition: state
    entity_id: person.[person2]
    state: home
  - service: notify.mobile_app_[person2]
    data:
      title: Cat hungry - low food
      message: "{% if states('sensor.feeder_niks_bowl_bowl_0') | int <= 15 -%} \n
        \ {% if states('sensor.feeder_niks_bowl_bowl_1') | int <= 15 -%}\n    Both
        bowls are low!  \n  {% else -%}\n    Dry bowl is low! \n  {%- endif -%}\n{%-
        else -%}\n  {% if states('sensor.feeder_niks_bowl_bowl_1') | int <= 15 -%}\n
        \   Wet bowl is low!  \n  {% else -%}\n    No bowl is low. \n  {%- endif -%}\n{%-
        endif -%}\n{{''}}\nDry: {{states('sensor.feeder_niks_bowl_bowl_0') | int }}g
        \ Wet: {{states('sensor.feeder_niks_bowl_bowl_1') | int }}g\n"
      data:
        push:
          sound:
            name: default
            volume: 1
        group: Cats
  mode: single

This one resets the Nik food counter at 2am

- id: '[yours here 9]'
  alias: Reset Nik food counter
  description: ''
  trigger:
  - platform: time
    at: 02:00:00
  condition: []
  action:
  - service: var.set
    data:
      entity_id: var.nik_dry_since_2am
      value: 0
  - service: var.set
    data:
      entity_id: var.nik_wet_since_2am
      value: 0
  mode: single

Lovelace

This not very tidy, and lots of extra stuff in here you don't need to show but I had there as i was debugging. And there's something funny going on in my apex-cards at the crossover from June to July - the flatline data there shows up properly on my mobile app but not on web desktop for some reason.

image

Note: I have a few custom lovelace frontends installed through HACs - e.g. apexcharts

views:
  - title: Home
    cards:
      - show_name: true
        show_icon: true
        show_state: true
        type: glance
        entities:
          - entity: input_boolean.nik_fed_breakfast
          - entity: input_boolean.nik_fed_dinner
        title: Nik
      - type: horizontal-stack
        cards:
          - type: gauge
            entity: sensor.feeder_niks_bowl_bowl_0
            name: Dry food in bowl
            unit: g
            max: 75
            min: 0
            needle: true
            severity:
              green: 30
              yellow: 15
              red: 0
          - type: gauge
            entity: sensor.feeder_niks_bowl_bowl_1
            name: Wet food in bowl
            unit: g
            max: 75
            min: 0
            needle: true
            severity:
              green: 30
              yellow: 15
              red: 0
      - type: custom:apexcharts-card
        header:
          show: true
          title: Nik daily food consumption
          show_states: true
          colorize_states: true
        graph_span: 30d
        span:
          end: day
          offset: +0d 2 hr
        update_interval: 24hr
        yaxis:
          - min: 0
        apex_config:
          chart:
            stacked: true
        all_series_config:
          extend_to: now
          float_precision: 0
        series:
          - entity: sensor.nik_daily_wet
            type: column
            name: Wet
            group_by:
              func: max
              duration: 24 hr
          - entity: sensor.nik_daily_dry
            type: column
            name: Dry
            group_by:
              func: max
              duration: 24 hr
          - entity: sensor.nik_daily_wet_and_dry
            type: column
            name: Total
            group_by:
              func: max
              duration: 24 hr
            show:
              in_chart: false
              in_header: true
      - type: entities
        entities:
          - entity: var.nik_dry_since_2am
          - entity: var.nik_wet_since_2am
        title: Nik food since 2am
      - type: entities
        entities:
          - entity: sensor.feeder_niks_bowl_bowl_0
          - entity: sensor.feeder_niks_bowl_bowl_1
          - entity: sensor.feeder_niks_bowl
          - entity: binary_sensor.nik
          - entity: binary_sensor.pet_nik
          - entity: sensor.niks_bowl_battery_level
          - entity: binary_sensor.niks_bowl_connectivity
          - entity: input_datetime.nik_last_ate
          - entity: input_datetime.nik_last_fed
          - entity: sensor.nik_feeding_change_dry
          - entity: sensor.nik_feeding_change_wet
        title: Nik
      - type: custom:apexcharts-card
        header:
          show: true
          title: Nik daily calorie consumption
          show_states: true
          colorize_states: true
        graph_span: 30d
        span:
          end: day
          offset: +0d 2hr
        update_interval: 24hr
        yaxis:
          - min: 0
        apex_config:
          chart:
            stacked: true
          annotations:
            yaxis:
              - 'y': 266
                borderColor: '#00E396'
                label:
                  borderColor: '#00E396'
                  style:
                    color: '#fff'
                    background: '#00E396'
                  text: Rec
        all_series_config:
          extend_to: now
          float_precision: 0
        series:
          - entity: sensor.nik_daily_wet_calories
            type: column
            name: Wet
            group_by:
              func: max
              duration: 24 hr
          - entity: sensor.nik_daily_dry_calories
            type: column
            name: Dry
            group_by:
              func: max
              duration: 24 hr
          - entity: sensor.nik_daily_total_calories
            type: column
            name: Total
            group_by:
              func: max
              duration: 24 hr
            show:
              in_chart: false
              in_header: true
      - type: history-graph
        entities:
          - entity: input_number.nik_wet_food_last_weight
          - entity: input_number.nik_dry_food_last_weight
        hours_to_show: 4
        title: Last weight
      - type: history-graph
        entities:
          - entity: sensor.feeder_niks_bowl_bowl_1
          - entity: sensor.feeder_niks_bowl_bowl_0
        title: Nik's food
        hours_to_show: 8
      - type: entities
        entities:
          - input_number.nik_dry_food_calories_per_kg
          - input_number.nik_wet_food_calories_per_kg
      - type: entities
        entities:
          - entity: var.nik_dry_since_2am
          - entity: sensor.nik_daily_dry
          - entity: input_number.nik_dry_food_last_weight
          - entity: var.nik_wet_since_2am
          - entity: sensor.nik_daily_wet
          - entity: input_number.nik_wet_food_last_weight
          - entity: sensor.nik_daily_wet_and_dry

@jafrouk
Copy link

jafrouk commented Jan 10, 2024

@magicmega on my sureha install I'm not getting the _bowl_0 and _bowl_1 entities you are showing above, I recently installed SureHA through HACS.

Also, I can confirm you do need the Variables+History integration, but I think you had typos in your suggested addition to confiiguration.yaml (sensors/var but should be senor/variable) - this worked for me, again on a fresh install of Variables_History:

sensor: !include sensors.yaml
variable: !include var.yaml

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants