Improved Shopping List

Hey guys, adding a Shopping List was on my to do list for a long time. This weekend I took a look at it and added it to our house dashboards so now even kids can add items to the list. I went a bit fancy and added a couple of buttons to quickly add usual items to the list. Basically we have categories like Dairy, Deli, etc. and items like milk, eggs, … under each category.
Here’s a few screenshots.

shopping-list (1)

Images
The time consuming part was to find the related photos on google image. I used a few rules for naming the images. A) name should be the same as the item’s name. B) it should be lower case, C) replace spaces to -, and D) it should be a PNG file (no background). For example, Dairy becomes dairy.png or Meat & Fish becomes meat-&-fish.png and I put them in /config/www/images/shopping-list/ which can be referred to as /local/images/shopping-list/.

Cards
I used custom:button-card, custom:layout-card, custom:auto-entities, browser_mod, and of course shopping-list card.

For button cards I made two templates, one for the categories and one for the items:

shopping_list_card:
  entity: 
  variables:
    category: Dairy
    name: Milk
    quantity: ''
    unit: ''
  show_icon: false
  show_name: true
  name: '[[[return variables.name]]]'
  icon: mdi:cart
  aspect_ratio: 4/3
  entity_picture: '[[[return "/local/images/shopping-list/" + variables.name.toLowerCase().replaceAll(" ", "-") + ".png?v5"]]]'
  show_entity_picture: true
  tap_action:
    action: call-service
    service: shopping_list.add_item
    data:
      name: '[[[return variables.category + " - " + variables.name + variables.quantity + variables.unit ]]]'
      # ToDo: above string concatenation needs space in between (if quantity is not '')

shopping_list_category_card:
  entity: 
  variables:
    name: Fruits & Vegetables
  show_icon: false
  show_name: true
  aspect_ratio: 4/3
  name: '[[[return variables.name]]]'
  icon: mdi:cart
  entity_picture: '[[[return "/local/images/shopping-list/" + variables.name.toLowerCase().replaceAll(" ", "-") + ".png?v5"]]]'
  show_entity_picture: true
  style:
    - padding: 0%
  styles:
    entity_picture:
      - width: 60%

And here’s the dashboard:

title: Shopping
path: shopping
icon: mdi:cart-outline
panel: true
badges: []
cards:
  - type: custom:layout-card
    layout_type: grid
    layout:
      grid-template-columns: 50% 50%
      grid-template-rows: auto
    cards:
      - type: shopping-list
        title: Shopping List
      - type: custom:auto-entities
        filter:
          template: |
            {% set ns = namespace(result = [], categories = {}, items = []) %}
            {% set ns.categories = 
              {
                "Dairy": ["Milk","Eggs", "Yoghurt", "Butter", "Ice-cream", "Cheese"],
                "Fruits & Vegetables": ["Apples", "Bananas", "Strawberries", "avocados", "Tomatoes", "Cucumbers", "Carrots", "Onions", "Broccoli", "Lettuce"],
                "Drink": ["Milk", "Coke", "Beer"],
                "Bread & Bakery": ["Toast", "Gluten-free", "Flatbread", "Burger Rolls", "Muffins", "Cookies"],
                "Meat & Fish": ["Mince Beef", "Mince Lamb", "Salmon", "Chicken"],
                "Deli": ["Cheese", "Salami", "Ham", "Turkey"],
                "Snacks": ["Chips", "Pretzels", "Popcorn", "Crackers", "Nuts"],
                "Canned Goods": ["Tuna", "Beans", "Diced Tomatoes"],
                "Condiments & Spices": ["Sugar", "Olive oil", "Tomato Sauce", "Mayonnaise"],
                "Baking": ["Flour", "Sugar"],
                "Household & Cleaning": ["Detergent", "Paper Towels", "Tissues", "Bin Bags", "Aluminum Foil", "Zip Bags"],
                "Health Care": ["Panadol", "Toothpaste"],
              }
            %}
            {% for cat in ns.categories %} 
              {% set ns.items = [] %}
              {% for item in ns.categories[cat] %}
                  {% set ns.items = ns.items + [
                    {
                        "type": "custom:button-card",
                        "template": "shopping_list_card",
                        "variables":
                        {
                          "category": cat,
                          "name": item,
                        },
                    }
                  ]%}
              {%endfor%}
            {% set ns.result = ns.result + [
              {
                "type": "custom:button-card",
                "template": "shopping_list_category_card",
                "variables":{
                  "name": cat,
                },
                "tap_action":
                {
                  "action": "fire-dom-event",
                  "browser_mod": 
                  {
                    "service": "browser_mod.popup",
                    "data":
                    {
                      "title": cat,
                      "content":
                      {
                        "type": "custom:layout-card",
                        "layout_type": "grid",
                        "layout":
                        {
                          "grid-template-columns": "25% 25% 25% 25%",
                          "grid-template-rows": "auto",
                        },
                        "cards": ns.items
                      },
                    },
                  },
                },
              }
            ]%}
            {% endfor %}
            {{ns.result}}
        card_param: cards
        card:
            type: custom:layout-card
            layout_type: grid
            layout:
              grid-template-columns: 33% 33% 33%
              grid-template-rows: auto

Since I didn’t like copy/paste a lot of cards I used above method which I found a good example here. Basically what I did was to make a dictionary of categories and items (under the name ns.categories above) which you may want to edit based on your needs. For example you may need to add another category named Pets or Gluten-Free - In that case you also need to add photos to the collection following the naming rules mentioned above. This dictionary is looped over by using custom:auto-entities and using template as its filter. The key point was to make another dictionary-style section (I’m talking about {% set ns.result = ns.result + [ onwards) which would be passed to custom:layout-card's cards section at the bottom. Yeah I know looks so confusing…

That’s it.

Automation
Well without automation what’s HA for, right?
Added an automation to send a notification to our phones when we arrive to our local supermarkets. I added three zones for our three supermarkets in our neighbourhood (don’t make them passive). Also added two minutes latency to the trigger to be sure we are not just passing by.

# ------------------------------------------------------
# notification
# ------------------------------------------------------
- id: shopping_list_someone_enters_a_shop_show_notification
  alias: Shopping List - Someone enters a shop - show notification
  description: ''
  mode: single
  trigger:
    - platform: numeric_state
      entity_id: 
        - zone.coles
        - zone.woolworths
        - zone.aldi
      above: "0"
      for: 120
  condition: 
    condition: template
    value_template: "{{states('sensor.shopping_list_items') != ''}}"
  action:
    - variables:
        persons: "{{ state_attr(trigger.entity_id, 'persons') | list }}"
    - repeat: 
        count: "{{ persons | count }}"
        sequence:
          # get e.g. "person.jack" and convert to "notify.mobile_app_jack_mobile"
          - service: "{{ 'notify.mobile_app_' + persons[repeat.index - 1].replace('person.', '') + '_mobile' }}"
            data:
              title: Shopping List
              message: "Click to view the list"
              data:
                persistent: false
                sticky: true
                tag: "shopping-list-zone-entered"
                color: yellow
                icon: mdi:cart-outline
                # iOS URL
                url: /dashboard-mobile/shopping-list
                # Android URL
                clickAction: /dashboard-mobile/shopping-list

Note that I named our phones simply jack_mobile if we have a person named jack. You may need to edit above.
I created a sensor that reads the shopping list and makes a text sensor that has everything uncompleted in a long text. I use that A) as a condition above and B) as a text to show on mobile’s notification (for that to work you may need to generate a Long-Lived-Token). If it’s too much, just remove the condition above and also change the message to

message: "Click to view the list"

Either way, if you click on the notification it will go to shopping list dashboard url (my case is /dashboard-mobile/shopping-list).

here’s the sensor (source) (in configuration.yaml):

sensor:
  # -------------------------------------------
  # Shopping List
  # access through HA API
  - platform: rest
    name: Shopping List Items
    headers:
      authorization: !secret shopping_list
      content-type: 'application/json'
    resource: !secret shopping_list_api
    value_template: "{{ value_json | selectattr('complete', 'false') | map(attribute='name') | list | join(', ') |  truncate(255) }}"
    method: GET
    scan_interval: 60  

authorization is “Bearer ABCDEFGH” and ABCDEFGH is your Long-Lived-Token that you need to generate. shopping_list_api is something like http://192.168.1.100:8123/api/shopping_list

I hope it could help someone. I edited a few bits and pieces as I was writing up here. I hope I didn’t make mistakes. I will edit and fix the issues if I saw it’s not working for me.

11 Likes

Fantastic!
Can you provide the images also? :smiley:

1 Like

The images are partially local brands that may not look like what you buy for your household.

Hi manunited10,
how do you exactly created the templates? Im running into the following error when Im using your code:

Log Details (ERROR)
Logger: homeassistant.config
Source: config.py:978
First occurred: 1:06:48 PM (4 occurrences)
Last logged: 1:19:06 PM

Invalid config for [template]: [shopping_list_card] is an invalid option for [template]. Check: template->shopping_list_card. (See /config/template.yaml, line 46).
Invalid config for [template]: [shopping_list_category_card] is an invalid option for [template]. Check: template->shopping_list_category_card. (See /config/template.yaml, line 67).

Please take a look at the link below. Basically templates are for avoiding repeating a button-card and reuse them. We put them in a yaml file, and include them in the dashboard.

1 Like

Hi manuited10,
sorry for this really stupid newbie questions, but I really dont understand how to handle this. I followed your link and read the complete article. I understand that im using the managed lovelace ui and I have to add the templates to the raw editor. I added the following complete code into it:

button_card_templates:
  header:
    styles:
      card:
        - padding: 5px 15px
        - background-color: var(--paper-item-icon-active-color)
      name:
        - text-transform: uppercase
        - color: var(--primary-background-color)
        - justify-self: start
        - font-weight: bold
      label:
        - text-transform: uppercase
        - color: var(--primary-background-color)
        - justify-self: start
        - font-weight: bold
  header_red:
    template: header
    styles:
      card:
        - background-color: '#FF0000'
shopping_list_card:
  entity: null
  variables:
    category: Dairy
    name: Milk
    quantity: ''
    unit: ''
  show_icon: false
  show_name: true
  name: '[[[return variables.name]]]'
  icon: mdi:cart
  aspect_ratio: 4/3
  entity_picture: >-
    [[[return "/local/images/shopping-list/" +
    variables.name.toLowerCase().replaceAll(" ", "-") + ".png?v5"]]]
  show_entity_picture: true
  tap_action:
    action: call-service
    service: shopping_list.add_item
    data:
      name: >-
        [[[return variables.category + " - " + variables.name +
        variables.quantity + variables.unit ]]]
shopping_list_category_card:
  entity: null
  variables:
    name: Fruits & Vegetables
  show_icon: false
  show_name: true
  aspect_ratio: 4/3
  name: '[[[return variables.name]]]'
  icon: mdi:cart
  entity_picture: >-
    [[[return "/local/images/shopping-list/" +
    variables.name.toLowerCase().replaceAll(" ", "-") + ".png?v5"]]]
  show_entity_picture: true
  style:
    - padding: 0%
  styles:
    entity_picture:
      - width: 60%

I was trying to add this code to the bottom, to the top and between the shopping cart dashboard and the rest. It ends always in “missing template card”:

Do you know what Im doing wrong?
Thanks a lot!

OK no problem at all. You’re on the right track.
First that header: and header_red are some examples that you could get rid of if you are not using them.
Second, you just need to add space (indent) to shopping_list_card: and the other one. So it should look like:

button_card_templates:
  shopping_list_card:
    entity: 
    variables:
      category: Dairy
      name: Milk
      quantity: ''
      unit: ''
    show_icon: false
    show_name: true
    name: '[[[return variables.name]]]'
    icon: mdi:cart
    aspect_ratio: 4/3
    entity_picture: '[[[return "/local/images/shopping-list/" + variables.name.toLowerCase().replaceAll(" ", "-") + ".png?v5"]]]'
    show_entity_picture: true
    tap_action:
      action: call-service
      service: shopping_list.add_item
      data:
        name: '[[[return variables.category + " - " + variables.name + variables.quantity + variables.unit ]]]'
        # ToDo: above string concatenation needs space in between (if quantity is not '')

  shopping_list_category_card:
    entity: 
    variables:
      name: Fruits & Vegetables
    show_icon: false
    show_name: true
    aspect_ratio: 4/3
    name: '[[[return variables.name]]]'
    icon: mdi:cart
    entity_picture: '[[[return "/local/images/shopping-list/" + variables.name.toLowerCase().replaceAll(" ", "-") + ".png?v5"]]]'
    show_entity_picture: true
    style:
      - padding: 0%
    styles:
      entity_picture:
        - width: 60%

This helps alot. Thank you @manunited10 ! Its working perfectly and my wife is also happy . :slight_smile:

We are using home assistant mostly from our mobile phones and therefore I switched the dashboard to a vertical layout card. I used a conditional card to show and hide the items:


Now Im just have to fill all products with pictures :slight_smile:
Thanks again!

1 Like

Looks great. I’m happy it worked for you.
Does the automation also work for you?

Next I will do some digging to find a way to tell google assistant and/or alexa to add items to this list. I saw some examples like this and this here and there but not exactly what I wanted.

1 Like

I didn’t tried it yet. Our home assistant is public reachable and we normally open the app to check in the products we had put into the cart. I don’t think we’ll need the automation.

There’s another interesting project from a German dude. He build a custom barcode scanner and used an public available api to scan any product he like and put them to the shopping list. :grinning:

2 Likes

Ok, that’s pretty awesome, and a bit of a pain to maintain at first until you eventually add in the few hundred items you would normally purchase. I still think, however, that the biggest weakness of the shopping list is still there though. Once you have everything you want to buy listed, there may be 40+ items which are a pain to keep sorted since they will not autosort, and a pain to track visually since the shopping list has just one single solitary column. It would be better if that shopping list contained multiple columns, namely Qty (0 = checked off), Class (dairy, drinks, flesh, fresh, grain, frozen, etc), Supplier (grocery, costco, walmart, homedepot, pharmacy, etc), product (jarlsberg, pelegrino, etc), Pkg (block, jug, tub, flat, pack, bag, each, jar, can, bottle, kg, etc), detail (“make sure it’s pre-washed”, etc), and link (maybe an url pointer to an image or a specific product).

I looked at grocy for some of this, but grocy is built more like an ERP system. It’s great, but a huge overhead to maintain, i just need a shopping list that isn’t volatile and that doesn’t have a sort button that delete everything that is checked off so i’m left scratching my head on what i had on that list next time i want to replenish my stock.

Your solution is a great way to stabilize the shopping list against the weekly accidental deletion (glad i have backups), but it still presumes you shop at only one store and that you have only a few items at a time that fit on one screen and won’t need some secondary organization. I think the shopping list is useable for other purposes (it’s my to-do list for my server, i wish i could name it that way though) but as a shopping list i’m still waiting for it to evolve a bit.

-z

1 Like

Yeah agreed. The shopping list in home assistant is pretty basic and we can’t have more than one.
Maybe someone gives a bit of attention to it one day and adds more features.

Thank you for sharing this! Got it working quickly and will start to dive into creating categories and assigning pictures. I haven’t got to the automation side of it it but I have already setup the Alexa/todoist integration with shopping list and it all works perfectly together.

1 Like

Did you get it working to add custom items to a list from Google Assistant?

1 Like

Hi manunited10!

Good to know that more people are working on the same project :slight_smile:

I also have my improved shopping list. I just want to share it with you, since you may want to get some new funcionalities. I will get your categories section to add products, nice!!

1 Like

No not yet. looks like the new update on home assistant has some good features and added some interesting feature as well as google sync. I’ll look into it.

Great :+1:

I am sorry, I am a newbi here… I try again and again but when I press the category image nothing happens…could some one provide any help ?
Thanks.

Solved!!!

1 Like