Food in the Freezer Tracker

This is going to be a long post. I am going to explain what started it, how it progressed, and what it finally evolved into.

We are empty nesters and find it tough to cook for two these days. Not sure how in the hell we did it before :slight_smile:

We recently purchased a vacuum sealer, started cooking ‘casserole’ (Lasagne, baked Mac and Cheese, etc) type dishes, cut them into eight portions, seal, and freeze. Then, each of us could select what we wanted each evening and pull it out in the morning. This was cool for both of us because we didn’t both need to eat the same thing every night.

I wanted to be able to track these foods so I knew when to make more and what to make.

So, here is what I was doing in HA:

  • Manually create an input_number for each item that allowed me to keep track of them.
  • Use custom:numberbox-card to display the amount available with a + and - icon to make changes to our inventory.
  • Manually add each item to a dashboard card so the inventory was available and could be adjusted as we ‘pulled’ items from the freezer.

As this evolved, we didn’t want to hit the basement each morning to pull from inventory and put in the fridge each morning for our dinners. So, we needed two inventories. Basement freezer and kitchen freezer (in the fridge). Once moved to the actual fridge for the evening, it should not be in any inventory count.

At this point, I manually created another input_number for the fridge. So, I had input_number.freezer_item1 and input_number.fridge_item1 for each item. I was using:

  - type: horizontal-stack
    title: Item 1
    cards:
      - type: custom:numberbox-card
        icon_plus: mdi:void
		picture: mdi:null
        entity: input_number.fridge_item1
        name: false
      - type: custom:numberbox-card
	    picture: mdi:null
        entity: input_number.freezer_item1
        name: false

This hides the Plus sign on the fridge card. Yes, it is still there and can be used if something does get out of sync.

I built two automations to trigger when either the fridge or freezer type of input_number was changed.
When the fridge type changed, I only cared if it was zero and went to -1. If so, put it back to zero and decrement the freezer type. (Straight from the freezer to the fridge for that night)
When the freezer type changed and went down from what it was, increment the fridge type. (From the freezer to the fridge freezer for future)

Eventually, we wanted a larger variety. We both searched the nets for other ‘casserole’ types of dishes to expand our selection.

Okay, how can I automate this so each item magically appears? :slight_smile:

So, I started Googling about HA and the different things I wanted to do.

  • Dynamically build input_numbers
  • Automatically build the numberbox-cards for those input_numbers
  • Automatically add the input_numbers to the automation triggers
  • Automatically build dashboard using the input_numbers

My original plan was to use areas for the input_numbers for the different types of frozen food we had (breakfast, dinner, frozen foods). But, you can only use areas from the UI. So, I used the naming convention to define the types.
For example:
input_number.fridge_breakfast_item1
input_number.freezer_breakfast_item1
input_number.fridge_dinner_item2
input_number.freezer_dinner_item2
input_number.fridge_frozen_item3
input_number.freezer_frozen_item3

Frozen is for store bought frozen foods (chopped onions, hash browns, fries, etc).

Custom items used:
decluttering_card-GitHub - custom-cards/decluttering-card: 🧹 Declutter your lovelace configuration with the help of this card
card-templater-GitHub - gadgetchnnel/lovelace-card-templater: Custom Lovelace card which allows Jinja2 templates to be applied to other cards
numberbox-card-GitHub - htmltiger/numberbox-card: Replace input_number sliders with plus and minus buttons
auto-entities-GitHub - thomasloven/lovelace-auto-entities: 🔹Automatically populate the entities-list of lovelace cards

What is needed:

  • Additions to configuration.yaml
  • An HA script to build the input_number text file (along with two other text files to paste into the two automations)
  • An HA automation for each input_number location (freezer and fridge)
  • Two shell scripts to deal with the three text files
  • Two views in a dashboard

configuration.yaml - Adds three notify.files, two shell_commands, and !include my inputnumber YAML file.

notify:
  - platform: file
    name: scriptfridge
    filename: scriptfridge.txt
    timestamp: false
  - platform: file
    name: scriptfreezer
    filename: scriptfreezer.txt
    timestamp: false
  - platform: file
    name: inputnumber_frozenfoods
    filename: inputnumber_frozenfoods.yaml
    timestamp: false

shell_command:
  clear_frozen: bash /config/clearfrozen.sh
  fix_frozen: bash /config/fixfrozen.sh

input_number: !include inputnumber_frozenfoods.yaml

clearfrozen.sh - empties the text files I build in my script below

cd "${0%/*}"
cp inputnumber_frozenfoods.yaml inputnumber_frozenfoods.old
rm scriptfridge.txt scriptfreezer.txt inputnumber_frozenfoods.yaml
touch scriptfridge.txt scriptfreezer.txt inputnumber_frozenfoods.yaml

Build frozen foods input numbers Script - Sets the items[] variable to the list of input_numbers I need to create, loop through that list to build the inputnumber_frozenfoods.yaml, scriptfridge.txt, and scriptfreezer.txt files.

alias: Build frozen foods input numbers
variables:
  items:
    - breakfast|Item 1
    - dinner|Item 2
    - dinner|Item 3
    - frozen|Item 4
    - frozen|Item 5
sequence:
  - service: shell_command.clear_frozen
    data: {}
  - service: notify.inputnumber_frozenfoods
    data_template:
      message: |
        {% for item in items|sort %}{% set type, name = item.split('|') %}
        fridge_{{ type }}_{{ name|lower|replace(' ', '_') }}:
          name: {{ name }}
          icon: mdi:food-steak
          min: -1
          max: 20
          step: 1
        freezer_{{ type }}_{{ name|lower|replace(' ', '_') }}:
          name: {{ name }}
          icon: mdi:food-steak
          min: 0
          max: 20
          step: 1{% endfor %}
  - service: notify.scriptfreezer
    data_template:
      message: |
        {% for item in items|sort %}{% set type, name = item.split('|') %}
              - input_number.freezer_{{ type }}_{{ name|lower|replace(' ', '_') }}{% endfor %}
  - service: notify.scriptfridge
    data_template:
      message: |
        {% for item in items|sort %}{% set type, name = item.split('|') %}
              - input_number.fridge_{{ type }}_{{ name|lower|replace(' ', '_') }}{% endfor %}
  - service: shell_command.fix_frozen
    data: {}
    enabled: true
  - service: homeassistant.restart
    data: {}
    enabled: true
mode: single

The three output files are below. The notify.file service adds the first two lines to each file. Also, I got tired of trying to get the first line in the two script files to stop starting at the left. So, the following shell script fixes those files using sed.

inputnumber_frozenfoods.yaml

Home Assistant notifications (Log started: 2023-07-08T13:38:30.676722+00:00)
--------------------------------------------------------------------------------
fridge_breakfast_item_1:
  name: Item 1
  icon: mdi:food-steak
  min: -1
  max: 20
  step: 1
freezer_breakfast_item_1:
  name: Item 1
  icon: mdi:food-steak
  min: 0
  max: 20
  step: 1
fridge_dinner_item_2:
  name: Item 2
  icon: mdi:food-steak
  min: -1
  max: 20
  step: 1
freezer_dinner_item_2:
  name: Item 2
  icon: mdi:food-steak
  min: 0
  max: 20
  step: 1
fridge_dinner_item_3:
  name: Item 3
  icon: mdi:food-steak
  min: -1
  max: 20
  step: 1
freezer_dinner_item_3:
  name: Item 3
  icon: mdi:food-steak
  min: 0
  max: 20
  step: 1
fridge_frozen_item_4:
  name: Item 4
  icon: mdi:food-steak
  min: -1
  max: 20
  step: 1
freezer_frozen_item_4:
  name: Item 4
  icon: mdi:food-steak
  min: 0
  max: 20
  step: 1
fridge_frozen_item_5:
  name: Item 5
  icon: mdi:food-steak
  min: -1
  max: 20
  step: 1
freezer_frozen_item_5:
  name: Item 5
  icon: mdi:food-steak
  min: 0
  max: 20
  step: 1

scriptfridge.txt

Home Assistant notifications (Log started: 2023-07-08T13:38:30.683458+00:00)
--------------------------------------------------------------------------------
- input_number.fridge_breakfast_item_1
      - input_number.fridge_dinner_item_2
      - input_number.fridge_dinner_item_3
      - input_number.fridge_frozen_item_4
      - input_number.fridge_frozen_item_5

scriptfreezer.txt

Home Assistant notifications (Log started: 2023-07-08T13:38:30.679060+00:00)
--------------------------------------------------------------------------------
- input_number.freezer_breakfast_item_1
      - input_number.freezer_dinner_item_2
      - input_number.freezer_dinner_item_3
      - input_number.freezer_frozen_item_4
      - input_number.freezer_frozen_item_5

fixfrozen.sh - Removes the top two lines from each of the files created above. Also, shifts the first two lines of the script files characters to line up as they should.

cd "${0%/*}"
sed -i -e '1,2d' inputnumber_frozenfoods.yaml
sed -i -e '1,2d' -e 's/^-/      -/' scriptfridge.txt
sed -i -e '1,2d' -e 's/^-/      -/' scriptfreezer.txt

After the script is complete, the yaml and two text files are ready. The yaml file is included in the configuration.yaml file for the input_numbers.

The script then restarts HA.

The two following automations are triggered off of each input_number changing. Use the above script*.txt files to copy/paste the list into the trigger entity_ids in each script below. If we are ever able to template the entities, this will go away.

Adjust Fridge Automation

alias: Adjust Fridge
description: ""
trigger:
  - platform: state
    entity_id:
      - input_number.fridge_breakfast_item_1
      - input_number.fridge_dinner_item_2
      - input_number.fridge_dinner_item_3
      - input_number.fridge_frozen_item_4
      - input_number.fridge_frozen_item_5
condition: []
action:
  - if:
      - condition: and
        conditions:
          - "{{ trigger.from_state.state|int == 0 }}"
          - "{{ trigger.to_state.state|int == -1 }}"
    then:
      - service: input_number.decrement
        data: {}
        target:
          entity_id: input_number.freezer_{{ trigger.entity_id[20:] }}
      - delay:
          hours: 0
          minutes: 0
          seconds: 1
          milliseconds: 0
      - service: input_number.set_value
        data:
          value: 0
        target:
          entity_id: input_number.fridge_{{ trigger.entity_id[20:] }}
mode: queued

Adjust Freezer Automation

alias: Adjust Freezer
description: ""
trigger:
  - platform: state
    entity_id:
      - input_number.freezer_breakfast_item_1
      - input_number.freezer_dinner_item_2
      - input_number.freezer_dinner_item_3
      - input_number.freezer_frozen_item_4
      - input_number.freezer_frozen_item_5
condition: []
action:
  - if:
      - condition: and
        conditions:
          - "{{ trigger.to_state.state|int < trigger.from_state.state|int }}"
          - "{{ fridge_state|int != -1 }}"
    then:
      - service: input_number.increment
        data: {}
        target:
          entity_id: input_number.fridge_{{ trigger.entity_id[21:] }}
mode: queued
variables:
  fridge_state: "{{ states('input_number.fridge_' ~ trigger.entity_id[21:]) }}"

And, finally, the dashboard has two views. Screenshots below.

  • The first view uses auto-entities, decluttering-card, card-templater, and numberbox-card to select all of the fridge input_numbers. This allows us to select all of the foods in one view, separate them by type, and fill in both the fridge and the freezer numberbox-cards for each item.
  • The second view uses auto-entities and numberbox-card. Here we only select the fridge input_numbers that are greater than zero. We then show only what is in the fridge freezer and is already upstairs.

Dashboard

decluttering_templates:
  frozen_foods:
    card:
      type: custom:card-templater
      card:
        type: horizontal-stack
        title_template: '{{ state_attr(''[[entity]]'', ''friendly_name'') }}'
        cards:
          - type: custom:numberbox-card
            icon_plus: mdi:void
            picture: mdi:null
            entity: '[[entity]]'
            name: false
          - type: custom:numberbox-card
            picture: mdi:null
            entity_template: '{{ ''[[entity]]'' | replace(''fridge'', ''freezer'') }}'
            name: false
      entities:
        - '[[entity]]'
        - '{{ ''[[entity]]'' | replace(''fridge'', ''freezer'') }}'
views:
  - title: Frozen Food
    path: frozen-food
    icon: mdi:food-steak
    badges: []
    cards:
      - type: vertical-stack
        cards:
          - type: markdown
            content: |
              ## Breakfast
              ---
          - type: custom:auto-entities
            card:
              type: entities
            card_parm: cards
            filter:
              include:
                - entity_id: input_number.fridge_breakfast*
                  options:
                    type: custom:decluttering-card
                    template: frozen_foods
                    variables:
                      - entity: this.entity_id
            sort:
              method: name
          - type: markdown
            content: |
              ## Dinner
              ---
          - type: custom:auto-entities
            card:
              type: entities
            card_parm: cards
            filter:
              include:
                - entity_id: input_number.fridge_dinner*
                  options:
                    type: custom:decluttering-card
                    template: frozen_foods
                    variables:
                      - entity: this.entity_id
            sort:
              method: name
          - type: markdown
            content: |
              ## Frozen Foods
              ---
          - type: custom:auto-entities
            card:
              type: entities
            card_parm: cards
            filter:
              include:
                - entity_id: input_number.fridge_frozen*
                  options:
                    type: custom:decluttering-card
                    template: frozen_foods
                    variables:
                      - entity: this.entity_id
            show_empty: true
            sort:
              method: name
  - title: Fridge
    path: fridge
    icon: mdi:fridge-outline
    badges: []
    cards:
      - type: vertical-stack
        cards:
          - type: markdown
            content: |
              ## Breakfast
              ---
          - type: custom:auto-entities
            card:
              type: entities
            card_parm: cards
            filter:
              include:
                - entity_id: input_number.fridge_breakfast*
                  state: '> 0'
                  options:
                    type: custom:numberbox-card
            sort:
              method: name
          - type: markdown
            content: |
              ## Dinner
              ---
          - type: custom:auto-entities
            card:
              type: entities
            card_parm: cards
            filter:
              include:
                - entity_id: input_number.fridge_dinner*
                  state: '> 0'
                  options:
                    type: custom:numberbox-card
            sort:
              method: name
          - type: markdown
            content: |
              ## Frozen Foods
              ---
          - type: custom:auto-entities
            card:
              type: entities
            card_parm: cards
            filter:
              include:
                - entity_id: input_number.fridge_frozen*
                  state: '> 0'
                  options:
                    type: custom:numberbox-card
            sort:
              method: name


Wow. That was a lot. I hope I remembered everything and it all makes sense.

Hopefully, this will give some ideas on how to do some things that you have always wanted to do, but didn’t know how.

Of course, feel free to ask any questions and I’ll try to expand on anything that doesn’t make sense.

2 Likes

Very cool! A lot of work went into this :slight_smile:

Just wondering, did you check out Grocy?

Thanks.

I did check out Grocy. But, we were already using Mealie and are very happy with it. So, instead, I went and learned a bunch of HA.

Man, this was frustrating and absolutely FUN!!!

4 Likes