Sync Alexa Shopping List with Home Assistant

Sync Alexa Shopping List with Home Assistant

This method explains how to display your Alexa Shopping list on a Home Assistant dashboard, ensuring it updates instantly whenever items are added or removed. You can manage the list either by voice (“Alexa, add eggs to the shopping list”) or through the Alexa app, which I now use for grocery shopping. In both cases, the list stays synchronized with the Home Assistant dashboard card.

Requirements

Although you’re more than welcome to just read this post for general knowledge, check out my recommendations for what you need to have, in order to be able to fully use the provided information.

A Little Background

In the past, I used AnyList for managing my shopping lists. It had an excellent integration with Alexa, where any updates to the Alexa shopping list would instantly sync with the AnyList app. I loved the user interface of AnyList; it was easy to use while grocery shopping, and the synchronization was seamless. It was two-way, meaning when I checked off an item in AnyList, it would also be removed from the Alexa shopping list. So, when you asked Alexa, "What's on my shopping list?", it would know you had already purchased that item. Life was great.

Then, in a frustrating move, Amazon decided to remove the API that allowed its shopping list to sync with third-party apps like AnyList, Grocy, and others. As a compromise, Amazon changed the integration so that you could still use apps like AnyList, but you now had to say, "Alexa, ask AnyList to add eggs to the shopping list." This process is so cumbersome and inconvenient that I doubt many people bother to use it anymore.

The solution I needed had to let me simply say, "Alexa, add/remove eggs from the shopping list," without requiring any extra commands or explanations. Things should just work seamlessly. Trying to teach yourself—or your household—non-intuitive processes is nearly impossible, especially in the long run when ease of use becomes the top priority.

I spent a long time searching for a good solution. Home Assistant has an Alexa integration, but it doesn't (or no longer) supports shopping lists. There’s an AnyList integration, but it’s useless if I can't sync the shopping list directly from Alexa.

The solution I ultimately found involved using Node-RED.

If you're unfamiliar with Node-RED, it’s a powerful tool that allows you to create automations in a visual, block-based interface, without needing programming skills or specific knowledge of NodeJS (the platform it’s built on). You simply drag and drop blocks to create flows or sequences of actions. It can also run as a standalone program on any computer, even a Windows PC. At one point, all of my home automations were running through Node-RED.

However, after I started using Home Assistant, I transitioned to its built-in automation tools, which I felt provided more flexibility without relying on additional software. That said, Node-RED can be installed as an Add-on within Home Assistant, and I found myself needing to return to it for this project.

As with many open-source projects, Node-RED has a vast collection of community-created extensions called "Nodes." In this tutorial, I’ll demonstrate how to use the Alexa Node, which is essential for this solution. Unfortunately, there’s no alternative for this within Home Assistant, nor can it be installed natively.

The Solution: A High-Level Overview

  1. Node-RED: I used an Alexa Node in Node-RED to fetch the shopping list. This Node doesn’t rely on the official API. Instead, I believe it mimics an Echo device or works through another unofficial method. This means the solution functions currently, but it depends on the ongoing efforts of open-source contributors who continually adapt the Alexa Node to Amazon's frequent, undocumented changes.
  2. Fetching the Shopping List: Node-RED can retrieve the shopping list in several ways: by detecting the word "Shopping List" in any sentence spoken to an Echo device, on a scheduled interval, or manually via a button on the Home Assistant dashboard. I opted to use all of these methods.
  3. Optional: After fetching the shopping list, I integrated Node-RED with OpenAI’s API to sort the items by supermarket aisles. For example, on the Home Assistant dashboard, dairy items will be grouped together, as will vegetables, etc. This makes the list more organized and visually appealing, though it’s not necessary for the solution to work.
  4. Optional: The code can also check whether an item is marked as checked or unchecked. Even if an item remains on the list, it may be crossed off (checked). You can highlight crossed-off items in a special way on the Home Assistant dashboard, such as by displaying the name with a strikethrough.
  5. Sending the Shopping List to Home Assistant: Node-RED sends the shopping list to Home Assistant, storing it in an input_text helper. Unfortunately, this helper has a 255-character limit, but my average shopping list is typically below that. If you need more space, additional work can be done. One option is to save the shopping list to a text file in Node-RED and then read it into Home Assistant, or store the data in a sensor attribute, which isn’t subject to the 255-character limit. I haven’t pursued either of these yet.
  6. Displaying the Shopping List: The input_text helper is updated in Home Assistant and can now be displayed on the dashboard. I spent considerable time optimizing this because a long list of items can take up a lot of space on my kitchen's wall-mounted tablet. I designed a more compact layout that works perfectly for my needs. Feel free to suggest alternative layouts if you have any ideas!

Sneak Preview

Notice how the dairy products are grouped together and how Pasta is checked off because it is marked as such in the Alexa app. The first button in the list is the Shopping List Refresh button, a manual button that can be used to fetch the items from Alexa, in case the automatic measures fail.

The Process in Detail:

  1. Install the Node-RED Add-on in Home Assistant (if you haven’t already).
    • To install, go to Settings -> Add-ons -> Add-on Store, search for Node-RED, and install it. In the Configuration section of the Add-on, enter a password in the credential_secret field, then start the Add-on.
  2. Install the Node-RED Companion from HACS.
    • In HACS, search for Node-RED Companion and install it. Restart Home Assistant, then go to Settings -> Integrations, search for Node-RED Companion, and install it.
  3. Add Alexa and OpenAI Nodes in Node-RED.
    • In Node-RED, go to Menu -> Manage Palette -> Install. Find and install "node-red-contrib-alexa-remote2-applestrudel". Optionally, you can also install "@inductiv/node-red-openai-api" if you want the OpenAI feature for sorting your shopping list.
  4. Create a Manual Sync Button in Node-RED.
    • In Node-RED, you’ll see a set of code blocks on the left. Find the one labeled "button" and drag it to the blank canvas. Double-click on the button block to edit its properties. Under Entity Config, click the Plus icon to create a new configuration and replicate my setup from the screenshot provided.
    • Once done, click Deploy in the top-right corner. This creates a button in Home Assistant within the Node-RED Companion integration, which we’ll use to manually sync the shopping list.
  5. Optional: Set up OpenAI for List Sorting.
    • If you installed the OpenAI node, drag the OpenAI API block onto the canvas. In its configuration screen, enter your OpenAI API key (which you can get from OpenAI). If you don’t have an OpenAI account and prefer not to set this up now, feel free to skip this step—although sorting the list by aisle is a neat feature!
  6. Set up Alexa in Node-RED.
  7. Clean Up.
    • Once Alexa is set up, you can remove all the blocks you've placed on the canvas, as they were only needed for the initial setup.
  8. Import the Code.
    • In Node-RED, go to Menu -> Import -> From Clipboard, and paste the following code
[{"id":"a05564b7cf7ef451","type":"inject","z":"034cfc0a1c0aba93","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"0 7-21 * * *","once":false,"onceDelay":"10","topic":"","payload":"","payloadType":"date","x":510,"y":340,"wires":[["804b50e3d1730656"]]},{"id":"369bb2070d279d0f","type":"alexa-remote-event","z":"034cfc0a1c0aba93","name":"","account":"5134235e3c4bb88d","event":"ws-device-activity","x":130,"y":380,"wires":[["8f6552836a7b1a0d","f9ad5951b230d8b9"]]},{"id":"8f6552836a7b1a0d","type":"switch","z":"034cfc0a1c0aba93","name":"Success?","property":"payload.data.utteranceType","propertyType":"msg","rules":[{"t":"eq","v":"GENERAL","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":320,"y":380,"wires":[["4e529c4e3e2ec9d6"]]},{"id":"4e529c4e3e2ec9d6","type":"switch","z":"034cfc0a1c0aba93","name":"Shopping list?","property":"payload.description.summary","propertyType":"msg","rules":[{"t":"cont","v":"shopping list","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":500,"y":380,"wires":[["804b50e3d1730656"]]},{"id":"804b50e3d1730656","type":"alexa-remote-list","z":"034cfc0a1c0aba93","name":"","account":"5134235e3c4bb88d","config":{"option":"getListItems","value":{"list":{"type":"str","value":"YW16bjEuYWNjb3VudC5BRURXNTRVS0E3U0dNQk5YREdXMlVWUU1NQ1RBLVNIT1BQSU5HX0lURU0="}}},"x":700,"y":380,"wires":[["97a28f2f7a335291","b3326780c90e8168"]]},{"id":"97a28f2f7a335291","type":"debug","z":"034cfc0a1c0aba93","name":"debug 5","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":700,"y":340,"wires":[]},{"id":"9ab9efc95be86e20","type":"ha-button","z":"034cfc0a1c0aba93","name":"Update Shopping List","version":0,"debugenabled":false,"outputs":1,"entityConfig":"493ff69ce7a88be3","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"x":480,"y":420,"wires":[["804b50e3d1730656"]]},{"id":"b3326780c90e8168","type":"function","z":"034cfc0a1c0aba93","name":"function 1","func":"// Extract the array from the input payload\nlet items = msg.payload;\n\n// Map through the items to process the value based on the \"completed\" field\nlet values = items.map(item => {\n    // Check if the item is completed, and prepend \"!\" if true\n    return item.completed ? '!' + item.value : item.value;\n});\n\n// Concatenate the values into a single string\nlet concatenatedValues = values.join(',');\n\n// Set the concatenated string as the new payload\nmsg.payload = {\n    model: 'gpt-4o-mini',\n    messages: [\n        {\n            content: 'Following is a list of items in a shopping list. Sort the items by supermarket aisles, for example rearange so that all the dairy items will be together. Return the sorted list, comma separated like the list you received, without extra spaces, without any prefix or suffix, just return the list of sorted items. Keep the names of the items without changing them. If an item starts with a \"!\" then keep that character, it is a special char used for something else. The list: ' + concatenatedValues,\n            role: 'user'\n        }\n    ]\n};\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":880,"y":380,"wires":[["9884e8e8a18fa8b0","a695509aa4d396a3"]]},{"id":"9884e8e8a18fa8b0","type":"debug","z":"034cfc0a1c0aba93","name":"debug 7","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":880,"y":340,"wires":[]},{"id":"0c2a8fa89c4f2d82","type":"api-call-service","z":"034cfc0a1c0aba93","name":"Transfer value to HASS","server":"48c9541e.53a2fc","version":5,"debugenabled":false,"domain":"input_text","service":"set_value","areaId":[],"deviceId":[],"entityId":["input_text.shoppingliststring"],"data":"{\"value\":payload.choices[0].message.content}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1330,"y":380,"wires":[[]]},{"id":"a695509aa4d396a3","type":"OpenAI API","z":"034cfc0a1c0aba93","name":"","property":"payload","propertyType":"msg","service":"e8f2c1a964b803e8","method":"createChatCompletion","x":1080,"y":380,"wires":[["3f5b56ab7fc208dc","0c2a8fa89c4f2d82"]]},{"id":"3f5b56ab7fc208dc","type":"debug","z":"034cfc0a1c0aba93","name":"debug 8","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1080,"y":340,"wires":[]},{"id":"f9ad5951b230d8b9","type":"debug","z":"034cfc0a1c0aba93","name":"debug 9","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":120,"y":340,"wires":[]},{"id":"5134235e3c4bb88d","type":"alexa-remote-account","name":"AlexaHome","authMethod":"proxy","proxyOwnIp":"192.168.1.229","proxyPort":"3456","cookieFile":"/config/alexacookie.txt","refreshInterval":"3","alexaServiceHost":"pitangui.amazon.com","pushDispatchHost":"","amazonPage":"amazon.com","acceptLanguage":"en-US","onKeywordInLanguage":"on","userAgent":"","usePushConnection":"on","autoInit":"on","autoQueryActivityOnTrigger":"on"},{"id":"493ff69ce7a88be3","type":"ha-entity-config","server":"48c9541e.53a2fc","deviceConfig":"e89ac03c8f552ff3","name":"Update Shopping List","version":"6","entityType":"button","haConfig":[{"property":"name","value":"Update Shopping List"},{"property":"icon","value":""},{"property":"entity_picture","value":""},{"property":"entity_category","value":"config"},{"property":"device_class","value":"update"}],"resend":false,"debugEnabled":false},{"id":"48c9541e.53a2fc","type":"server","name":"Home Assistant","addon":true},{"id":"e8f2c1a964b803e8","type":"Service Host","apiBase":"https://api.openai.com/v1","secureApiKeyHeaderOrQueryName":"Authorization","organizationId":"","name":""},{"id":"e89ac03c8f552ff3","type":"ha-device-config","name":"Node Red Buttons","hwVersion":"","manufacturer":"Node-RED","model":"","swVersion":""}]

This will import my entire flow into your Node-RED setup, and ideally, everything should work smoothly. If not, you may need to go into some of the nodes and ensure they are using the configurations you created in the previous steps.

  1. Create the input_text Helper in Home Assistant.
    • In Home Assistant, go to Settings -> Devices & Services -> Helpers, and add an input_text helper named "shoppingliststring". Ensure it’s configured to use the maximum allowed characters (255).

At this point, you should have a Node-RED flow that is triggered either by mentioning "Shopping List" to one of your Echo devices or by pressing the newly created button in the Node-RED Companion integration. Now, let’s make it look good:

  1. Create the Shopping List Card in Home Assistant.
    • I'm using a HACS-provided card called tailwindcss-template-card, which allows for a nice visual layout.
    • To install this card, follow the instructions under the "With HACS" section here: GitHub - tailwindcss-template-card.
    • Once installed, add the card to one of your dashboards.
    • You can use my YAML code to recreate my exact setup:
  - entity: ''
    content: |-

      <div class="flex flex-wrap gap-2 justify-center p-2">
        {% set items = states('input_text.shoppingliststring').split(',')%}
        <div class="bg-accent rounded-lg p-3">
              <span style="font-size:22px">
                
                
                <div onClick="hass.callService('button', 'press', {entity_id: 'button.update_shopping_list_nodered'})" class="hover:scale-105 transition-all cursor-pointer">
                  🛒📋🔃 
                </div>
            </span>
          </div>
      {% for i in range(0, items | count, 1) %}
        <div class="bg-accent rounded-lg p-3">
          <span style="font-size:22px;
          {% if items[i][0] == '!' %}
            text-decoration:line-through;
          {% endif %}">
            {{ items[i][1:] if items[i][0] == '!' else items[i] }}
          </span>
        </div>
      {% endfor %}
        
      </div>
    ignore_line_breaks: true
    always_update: false
    parse_jinja: true
    code_editor: Ace
    entities: []
    bindings: []
    actions: []
    debounceChangePeriod: 100
    plugins:
      daisyui:
        enabled: true
        url: https://fastly.jsdelivr.net/npm/daisyui@latest/dist/full.css
        theme: dark - dark
        overrideCardBackground: false
      tailwindElements:
        enabled: false
    type: custom:tailwindcss-template-card

Bottom Line

Am I happy with this solution? Not really. It would have been much better if I could still use AnyList for grocery shopping. Technically, this is possible since there’s an AnyList node in Node-RED, and you could write a flow that syncs the Alexa shopping list with AnyList. However, this would require two-way synchronization (removing an item from AnyList should also remove it from Alexa), which seems a bit more complex to implement. In the end, I decided to stop using AnyList for shopping lists (though I still love its recipe feature) and switched to using the Alexa app to keep track of what I need when I go shopping.

Next on my to-do list is to make the items on the Home Assistant dashboard "clickable", so you can check off an item by simply tapping it. Additionally, since OpenAI is already sorting the items by aisles, I’d like to find a way to neatly separate the list into aisles on the dashboard. The data is there; it's just a matter of displaying it in a more user-friendly way.

Am I proud of this solution? No, not really. It’s still a bit complicated and convoluted. However, after the lengthy setup process, it does work, and it works perfectly. It’s a hack that relies on another hack in Node-RED to fetch information from Alexa, which could break in the future. But for now, it’s functional, and I’m satisfied with the result.

If you find a better way to do this, or notice any inaccuracies in my description, please let me know. Writing this was particularly challenging due to all the steps involved—many of which are done once and then forgotten. I’ve retraced my steps as best as I could, but if I missed something, feel free to reach out.


2 Comments

  1. m.m

    Hi,
    Is it just me, or is there a screenshot missing after the phrase “and replicate my setup from the screenshot provided.” in step 4?

Leave a Reply