Weekly Dinner menu choices, auto-rotating, with random selection

Weekly Dinner menu choices, auto-rotating, with random selection

I got extremely tired of the mental load of picking menu options for my family’s dinners, creating a grocery list, and then repeating this for every evening until i perish.

I am using Home Assistant’s capabilities for a to-do list to create a grocery list, as well as menu for each week.

I chose to create 4 dinners here, but you can certainly add more.

I have yet to figure out how to create a list to avoid weekly repeats, but will update when I’ve solved that portion.
Here is a list of the steps I performed, with examples below of each step:

  1. Create input_select sensors for each recipe I’ll use in the menu options list.
  2. Create a menu list from each of the recipe input_select sensors.
  3. Create a menu selection from random picks of the menu list.
  4. Create a schedule for the menu selection to update.
  5. Create a script to clear previous ingredient lists from the prior week, and create new entries based off the new menu selections after they’ve been updated.
  6. Create an entry into your configuration.yaml, as well as a custom folder for the python script to properly function.
  7. Create an automation based off the schedule trigger in step 4, that uses the script in step 5 targeting the grocery list entity.

  1. Create the following input_select entries:

in input_select.yaml:

##recipe input selects##
	recipe_menu_name:
		name: This is The Menu Item Name
		options:
		- "ingredient 1"
		- "ingredient 2"
		- "ingredient 3"

example:

	recipe_tuna_salad:
		name: Tuna salad
		options:
			- "2 cans tuna"
			- "6 eggs"
			- "mayonnaise"
			- "yellow mustard"
			- "sweet relish"
			- "onion powder"
			- "garlic powder"
			- "smoked paprika"
			- "black pepper"
	recipe_shepherds_pie:
		name: Shepherds pie
		options:
			- "2 lbs ground beef"
			- "1 bag frozen mixed vegetables"
			- "mushroom broth"
			- "cascade mushroom mix"
			- "6 russet potatoes"
			- "4 tbsp butter"
  1. Once all recipes are added into unique input select items, create a new input select sensor with the following options:
	##menu options for dinners##
	menu_options:
		name: Weekly Dinner Menu Items
		icon: mdi:silverware
		options:
			- "Name exactly as the friendly name is displayed for each input_select"

example:

	##menu options for dinners##
	menu_options:
		name: Weekly Dinner Menu Items
		icon: mdi:silverware
		options:
			- "Tuna salad"
			- "Shepherd's pie"
			- "Chicken pot pie"
  1. Then, create a sensor under template.yaml to randomly select the menu options from your initially created input_selects:
	##Dinner Menu Items
		- name: updated_dinner_menu
		  unique_id: updated_dinner_menu
		  state: "OK"
		  attributes:
			Monday: >
			  {% set menu = state_attr('input_select.menu_options','options')|list |random %}
			  {{menu}}
			Tuesday: >
			  {% set menu2 = state_attr('input_select.menu_options','options')|list |random %}
			  {% for menu2 in 'attributes.sensor.updated_dinner_menu' |rejectattr(menu) %}
			  {%endfor%}
			  {{menu2}}
			Wednesday: >
			  {% set menu3 = state_attr('input_select.menu_options','options')|list |random %}
			  {% for menu3 in 'attributes.sensor.updated_dinner_menu' |rejectattr(menu) |rejectattr(menu2) %}
			  {%endfor%}
			  {{menu3}}
			Thursday: >
			  {% set menu4 = state_attr('input_select.menu_options','options')|list |random %}
			  {% for menu4 in 'attributes.sensor.updated_dinner_menu' |rejectattr(menu) | rejectattr(menu2) | rejectattr(menu3) %}
			  {%endfor%}
			  {{menu4}}
  1. Navigate to Settings>Devices & Services>Helpers, and create a Schedule Helper:
    Name: Dinner Menu Update
    Entity ID: schedule.dinner_menu_update
    Event is set to reset every Saturday at 12:00AM. You can customize this portion to refresh whenever you need.

  2. Create an entry in scripts.yaml with the below details. You’ll need to customize days and entities to add your specific entries:
    Example:

	add_to_shopping_list:
	  alias: Shopping List additions
	  sequence:
		- variables:
			days: ['add your day options here from your updated menu selection options','each entry will be a unique single quotation']
			item_counts: {}
		- repeat:
			count: "{{ days | length }}"
			sequence:
			  - variables:
				  day: "{{ days[repeat.index - 1] }}"
				  menu: "{{ state_attr('sensor.updated_dinner_menu', day) }}"
				  entity_id: >
					{% set entities = 'comma separated values for each recipe input_select entity' %}
					{% set entityList = entities.split(",") %}
					{% set querymatch = entityList | map('state_attr', 'friendly_name') | list %}
					{% set matchers = querymatch | select('search', menu) | list %}
					{% if matchers %}
					  {{ entityList[querymatch.index(matchers[0])] }}
					{% else %}
					  none
					{% endif %}
				  options: >
					{% if entity_id != 'none' %}
					  {{ state_attr(entity_id, 'options') }}
					{% else %}
					  []
					{% endif %}
			  - repeat:
				  count: "{{ options | length }}"
				  sequence:
					- variables:
						item: "{{ options[repeat.index - 1] }}"
						count: "{{ item_counts[item] | default(0) + 1 }}"
					- service: python_script.update_item_counts
					  data:
						item: "{{ item }}"
						count: "{{ count }}"
					- service: shopping_list.add_item
					  data:
						name: >
						  {% if count > 1 %}
							{{ count }}x {{ item }}
						  {% else %}
							{{ item }}
						  {% endif %}
`						  

`	##Query to add ingredients from dynamic menu items, to grocery list. automation clears all items and adds refreshed items##
	add_to_shopping_list:
	  alias: Shopping List additions
	  sequence:
		- variables:
			days: ['Monday', 'Tuesday', 'Wednesday', 'Thursday']
			item_counts: {}
		- repeat:
			count: "{{ days | length }}"
			sequence:
			  - variables:
				  day: "{{ days[repeat.index - 1] }}"
				  menu: "{{ state_attr('sensor.updated_dinner_menu', day) }}"
				  entity_id: >
					{% set entities = 'input_select.recipe_tuna_salad,input_select.recipe_shepherds_pie,input_select.recipe_chicken_pot_pie,input_select.recipe_enchilada_casserole,input_select.recipe_spaghetti,input_select.recipe_sausage_and_cabbage,input_select.recipe_breakfast_burrito,input_select.recipe_steak_fingers,input_select.recipe_chicken_nuggets,input_select.recipe_waffles,input_select.recipe_fried_rice,input_select.recipe_pork_chops,input_select.recipe_stroganoff,input_select.recipe_risotto,input_select.recipe_butternut_squash_soup,input_select.recipe_chicken_and_dumplings,input_select.recipe_hamburgers,input_select.recipe_chili,input_select.recipe_taco_salad,input_select.recipe_pot_roast,input_select.recipe_chicken_fried_steak,input_select.recipe_loaded_nachos,input_select.recipe_eggs_biscuits_and_gravy,input_select.recipe_ravioli,input_select.recipe_quesadillas,input_select.recipe_pizza,input_select.recipe_parmesan_chicken,input_select.recipe_salmon_caesar_salad,input_select.recipe_manicotti,input_select.recipe_pan_fried_chicken_thighs,input_select.recipe_meatloaf,input_select.recipe_green_pea_falafel,input_select.recipe_baked_potatoes,input_select.recipe_pulled_pork,input_select.recipe_pork_tenderloin,input_select.recipe_teriyaki_steak' %}
					{% set entityList = entities.split(",") %}
					{% set querymatch = entityList | map('state_attr', 'friendly_name') | list %}
					{% set matchers = querymatch | select('search', menu) | list %}
					{% if matchers %}
					  {{ entityList[querymatch.index(matchers[0])] }}
					{% else %}
					  none
					{% endif %}
				  options: >
					{% if entity_id != 'none' %}
					  {{ state_attr(entity_id, 'options') }}
					{% else %}
					  []
					{% endif %}
			  - repeat:
				  count: "{{ options | length }}"
				  sequence:
					- variables:
						item: "{{ options[repeat.index - 1] }}"
						count: "{{ item_counts[item] | default(0) + 1 }}"
					- service: python_script.update_item_counts
					  data:
						item: "{{ item }}"
						count: "{{ count }}"
					- service: shopping_list.add_item
					  data:
						name: >
						  {% if count > 1 %}
							{{ count }}x {{ item }}
						  {% else %}
							{{ item }}
						  {% endif %}
						  
  1. Create the following entry into your configuration.yaml:
    python_script:

Then, create a folder under HASS’s root config folder, named python_scripts. Add a file named update_item_counts.py with the following contents:

item = data.get('item')
count = data.get('count')
item_counts = hass.states.get('script.add_to_shopping_list').attributes.get('item_counts')
item_counts[item] = count
hass.states.set('script.add_to_shopping_list', 'running', {'item_counts': item_counts})
  1. Finally, create an automation based off all these components with the following config. Navigate to Settings>Automations & Scenes>Automations. Create:
    Event Trigger: Entity: Dinner Menu Update
    Attribute Next Event changes
    Then Do:
    Shopping List"Complete All"
    Shopping List “Clear completed Items”
    Script ‘Shopping list additions’

Save and name your automation.

For my instance, I created a page view in the dashboard to centralize my outputted dinner items, as well as my ingredients added to the list.

Entities Card Congifuration:
entity: sensor.updated_dinner_menu
type: attribute
attribute: Monday
entity: sensor.updated_dinner_menu
type: attribute
attribute: Tuesday
entity: sensor.updated_dinner_menu
type: attribute
attribute: Wednesday
entity: sensor.updated_dinner_menu
type: attribute
attribute: Thursday

To-Do List card configuration:
Entity: Shopping List

And, that’s all! I’ve found a good amount of success here.

Known limitations in this config:

I haven’t yet created a “re-roll” button in case there’s a duplicate of last week’s menu items. More to come, there.
I have not integrated this to any additional automations to add grocery list ingredients to external add-ins. YMMV here.

November 2024 Update:

I had gotten a few different options that I’ve tested. I’ve found a way to show previous week’s historical choices, along with a way to dynamically customize your menu if there’s something you have in mind for 1 night, but not the others.

First, an updated list with assurance there will be no duplicates, with latest HASS code:

Summary
  - name: "Rotating Dinner Menu"
    unique_id: updated_dinner_menu
    state: "OK"
    attributes:
      Monday: "{{state_attr('input_select.menu_options', 'options') |random }}"
      Tuesday: >-
        {% set options = state_attr('input_select.menu_options', 'options') %} 
        {% set monday = state_attr('sensor.updated_dinner_menu', 'Monday')%}
        {% set opt1 = options | reject('equalto',monday) | list %}
        {{opt1 |random }}
      Wednesday: >-
        {% set options = state_attr('input_select.menu_options', 'options') %} 
        {% set monday = state_attr('sensor.updated_dinner_menu', 'Monday')%}
        {% set tuesday = state_attr('sensor.updated_dinner_menu', 'Tuesday')%}
        {% set opt1 = options | reject('equalto',monday) | list %}
        {% set opt2 = opt1 | reject('equalto',tuesday) | list %}
        {{opt2 |random}}
      Thursday: >-
        {% set options = state_attr('input_select.menu_options', 'options') %} 
        {% set monday = state_attr('sensor.updated_dinner_menu', 'Monday')%}
        {% set tuesday = state_attr('sensor.updated_dinner_menu', 'Tuesday')%}
        {% set wednesday = state_attr('sensor.updated_dinner_menu', 'Wednesday')%}
        {% set opt1 = options | reject('equalto',monday) | list %}
        {% set opt2 = opt1 | reject('equalto',tuesday) | list %}
        {% set opt3 = opt2 | reject('equalto',wednesday) | list %}
        {{opt3 |random}}
      Friday: >-
        {% set options = state_attr('input_select.menu_options', 'options') %} 
        {% set monday = state_attr('sensor.updated_dinner_menu', 'Monday')%}
        {% set tuesday = state_attr('sensor.updated_dinner_menu', 'Tuesday')%}
        {% set wednesday = state_attr('sensor.updated_dinner_menu', 'Wednesday')%}
        {% set thursday = state_attr('sensor.updated_dinner_menu', 'Thursday')%}
        {% set opt1 = options | reject('equalto',monday) | list %}
        {% set opt2 = opt1 | reject('equalto',tuesday) | list %}
        {% set opt3 = opt2 | reject('equalto',wednesday) | list %}
        {% set opt4 = opt3 | reject('equalto',thursday) | list %}
        {{opt4 |random}}

Next, the upgrades:

I kept the recipe templates, as well as the initial input_select showing all the menu options available.

Next, I created a set of input_selects, for manually updating a menu, and input_texts, to read selections that are automatically picked. Both will be used later in the sequence.

input_selects.yaml:

Summary

##menu options for dinners## monday_menu_manual: name: Monday's Manual Menu Pick initial: "Placeholder" options: - "Placeholder" tuesday_menu_manual: name: Tuesday's Manual Menu Pick initial: "Placeholder" options: - "Placeholder" wednesday_menu_manual: name: Wednesday's Manual Menu Pick initial: "Placeholder" options: - "Placeholder" thursday_menu_manual: name: Thursday's Manual Menu Pick initial: "Placeholder" options: - "Placeholder" friday_menu_manual: name: Friday's Manual Menu Pick initial: "Placeholder" options: - "Placeholder"

input_texts.yaml:

Summary

monday_menu: name: Monday Menu initial: "" tuesday_menu: name: Tuesday Menu initial: "" wednesday_menu: name: Wednesday Menu initial: "" thursday_menu: name: Thursday Menu initial: "" friday_menu: name: Friday Menu initial: "" previous_monday_menu: name: Previous Monday Menu initial: "" previous_tuesday_menu: name: Previous Tuesday Menu initial: "" previous_wednesday_menu: name: Previous Wednesday Menu initial: "" previous_thursday_menu: name: Previous Thursday Menu initial: "" previous_friday_menu: name: Previous Friday Menu initial: ""

Then, I created a template sensor for the current week’s menu, as well as last week’s menu.

templates.yaml:

Summary

` - name: “Rotating Dinner Menu”
unique_id: updated_dinner_menu
state: “OK”
attributes:
Monday: >
{% set manual = states(‘input_select.monday_menu_manual’) %}
{% if manual != ‘Placeholder’ %}
{{ manual }}
{% else %}
{{ states(‘input_text.monday_menu’) }}
{% endif %}
Tuesday: >
{% set manual = states(‘input_select.tuesday_menu_manual’) %}
{% if manual != ‘Placeholder’ %}
{{ manual }}
{% else %}
{{ states(‘input_text.tuesday_menu’) }}
{% endif %}
Wednesday: >
{% set manual = states(‘input_select.wednesday_menu_manual’) %}
{% if manual != ‘Placeholder’ %}
{{ manual }}
{% else %}
{{ states(‘input_text.wednesday_menu’) }}
{% endif %}
Thursday: >
{% set manual = states(‘input_select.thursday_menu_manual’) %}
{% if manual != ‘Placeholder’ %}
{{ manual }}
{% else %}
{{ states(‘input_text.thursday_menu’) }}
{% endif %}
Friday: >
{% set manual = states(‘input_select.friday_menu_manual’) %}
{% if manual != ‘Placeholder’ %}
{{ manual }}
{% else %}
{{ states(‘input_text.friday_menu’) }}
{% endif %}

  • name: “Last Week’s Dinner Menu”
    unique_id: last_week_menu
    state: “OK”
    attributes:
    Monday: “{{states(‘input_text.previous_monday_menu’)}}”
    Tuesday: “{{states(‘input_text.previous_tuesday_menu’)}}”
    Wednesday: “{{states(‘input_text.previous_wednesday_menu’)}}”
    Thursday: “{{states(‘input_text.previous_thursday_menu’)}}”
    Friday: “{{states(‘input_text.previous_friday_menu’)}}”`

A script to create the menu every week on schedule:

scripts.yaml:

Summary

assign_weekly_menu: sequence: - variables: options: "{{ state_attr('input_select.menu_options', 'options') | list }}" previous_monday: "{{ states('input_text.previous_monday_menu') }}" previous_tuesday: "{{ states('input_text.previous_tuesday_menu') }}" previous_wednesday: "{{ states('input_text.previous_wednesday_menu') }}" previous_thursday: "{{ states('input_text.previous_thursday_menu') }}" previous_friday: "{{ states('input_text.previous_friday_menu') }}" - service: input_select.set_options target: entity_id: input_select.monday_menu_manual data: options: "{{ ['Placeholder'] + state_attr('input_select.menu_options', 'options') }}" - service: input_select.set_options target: entity_id: input_select.tuesday_menu_manual data: options: "{{ ['Placeholder'] + state_attr('input_select.menu_options', 'options') }}" - service: input_select.set_options target: entity_id: input_select.wednesday_menu_manual data: options: "{{ ['Placeholder'] + state_attr('input_select.menu_options', 'options') }}" - service: input_select.set_options target: entity_id: input_select.thursday_menu_manual data: options: "{{ ['Placeholder'] + state_attr('input_select.menu_options', 'options') }}" - service: input_select.set_options target: entity_id: input_select.friday_menu_manual data: options: "{{ ['Placeholder'] + state_attr('input_select.menu_options', 'options') }}" - service: input_text.set_value target: entity_id: input_text.monday_menu data: value: > {% set options = options | reject('equalto', previous_monday) | list %} {{ options | random }} - service: input_text.set_value target: entity_id: input_text.tuesday_menu data: value: > {% set options = options | reject('equalto', previous_tuesday) | list %} {{ options | random }} - service: input_text.set_value target: entity_id: input_text.wednesday_menu data: value: > {% set options = options | reject('equalto', previous_wednesday) | list %} {{ options | random }} - service: input_text.set_value target: entity_id: input_text.thursday_menu data: value: > {% set options = options | reject('equalto', previous_thursday) | list %} {{ options | random }} - service: input_text.set_value target: entity_id: input_text.friday_menu data: value: > {% set options = options | reject('equalto', previous_friday) | list %} {{ options | random }} - service: input_text.set_value target: entity_id: input_text.previous_monday_menu data: value: "{{ states('input_text.monday_menu') }}" - service: input_text.set_value target: entity_id: input_text.previous_tuesday_menu data: value: "{{ states('input_text.tuesday_menu') }}" - service: input_text.set_value target: entity_id: input_text.previous_wednesday_menu data: value: "{{ states('input_text.wednesday_menu') }}" - service: input_text.set_value target: entity_id: input_text.previous_thursday_menu data: value: "{{ states('input_text.thursday_menu') }}" - service: input_text.set_value target: entity_id: input_text.previous_friday_menu data: value: "{{ states('input_text.friday_menu') }}"

After this, I referenced all within an AppDaemon, that listens for a manual event to be fired thru automation on an iterative schedule:

Summary

`import appdaemon.plugins.hass.hassapi as hass
from collections import Counter

class GenerateShoppingList(hass.Hass):

def initialize(self):
    self.log("GenerateShoppingList initialized")
    self.listen_event(self.create_shopping_list, "FIRE_SHOPPING_LIST_EVENT")

def create_shopping_list(self, event_name, data, kwargs):
    self.log("Received FIRE_SHOPPING_LIST_EVENT")

    # Gather data from rotating dinner menu
    menu = self.get_state("sensor.rotating_dinner_menu", attribute="all")
    if not menu:
        self.log("No menu found.")
        return

    details = menu["attributes"].values()

    # Gather recipe data from input_select entities
    entities = self.get_state("input_select")
    recipe_entities = [entity for entity in entities if entity.startswith("input_select.recipe_")]

    # Process and count ingredients
    ingredient_counter = Counter()
    for recipe_entity in recipe_entities:
        friendly_name = self.get_state(recipe_entity, attribute="friendly_name")
        if friendly_name in details:
            options = self.get_state(recipe_entity, attribute="options")
            if options:
                ingredient_counter.update(options)

    formatted_ingredients = [f"{count}x {item}" for item, count in ingredient_counter.items()]

    # Log the formatted ingredients list
    self.log(f"Ingredients to add: {formatted_ingredients}")

    # Set the state and attributes of the sensor
    attributes = {"ingredients": formatted_ingredients}
    self.set_state("sensor.shopping_list", state="updated", attributes=attributes)
    self.log("Updated sensor.shopping_list with ingredients.")

`

Finally, the automations:

Summary

`alias: Weekly dinner menu build
description: “”
triggers:

  • trigger: time
    at: “00:00:00”
    conditions:
  • condition: time
    after: “00:00:00”
    weekday:
    • sat
      actions:
  • action: script.assign_weekly_menu
    data: {}
    mode: single
    `
Summary

`alias: Update Shopping List from Sensor
triggers:

  • entity_id: sensor.shopping_list
    to: updated
    trigger: state
    actions:
  • action: shopping_list.complete_all
    data: {}
  • action: shopping_list.clear_completed_items
    data: {}
  • repeat:
    count: “{{ state_attr(‘sensor.shopping_list’, ‘ingredients’) | length }}”
    sequence:
    - data:
    name: >-
    {{ state_attr(‘sensor.shopping_list’, ‘ingredients’)[repeat.index
    - 1] }}
    action: shopping_list.add_item
    `

Now, when you use the dashboard, you’d add both the Entities card to show your current menu, as well as an Entities card to allow for changes manually:

Summary

`type: entities
entities:

  • entity: sensor.rotating_dinner_menu
    icon: fas:m
    name: “Monday’s Pick:”
    type: attribute
    attribute: Monday
  • entity: sensor.rotating_dinner_menu
    icon: fas:t
    name: “Tuesday’s Pick:”
    type: attribute
    attribute: Tuesday
  • entity: sensor.rotating_dinner_menu
    icon: fas:w
    name: “Wednesday’s Pick:”
    type: attribute
    attribute: Wednesday
  • entity: sensor.rotating_dinner_menu
    icon: fas:t
    name: “Thursday’s Pick:”
    type: attribute
    attribute: Thursday
  • entity: sensor.rotating_dinner_menu
    icon: fas:f
    name: “Friday’s Pick:”
    type: attribute
    attribute: Friday
    `

menuview

Summary

`type: entities
entities:

  • entity: input_select.monday_menu_manual
  • entity: input_select.tuesday_menu_manual
  • entity: input_select.wednesday_menu_manual
  • entity: input_select.thursday_menu_manual
  • entity: input_select.friday_menu_manual
    title: “Don’t like the menu? Change it here:”
    `

menumanual

and finally, the live updateable view for harmony of both manual and automated menu options!

3 Likes

This deserves more credit! Love these kind of solutions. Hope it works well! I’ll think about integrating this sometime in the future

1 Like