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:
- Create input_select sensors for each recipe I’ll use in the menu options list.
- Create a menu list from each of the recipe input_select sensors.
- Create a menu selection from random picks of the menu list.
- Create a schedule for the menu selection to update.
- 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.
- Create an entry into your configuration.yaml, as well as a custom folder for the python script to properly function.
- Create an automation based off the schedule trigger in step 4, that uses the script in step 5 targeting the grocery list entity.
- 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"
- 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"
- 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}}
-
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. -
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 %}
- 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})
- 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:
- sat
- 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
`
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:”
`
and finally, the live updateable view for harmony of both manual and automated menu options!