Location based shopping lists with Bring!, Node-Red and Home Assistant - a small write up

Hello fellow home assistant users,

I made a small write up of a project I am working on in the last time and wanted to share my progress with you. As a disclaimer, this is my first trial on this and i tried to get everything together because there is no equivalent running integration or official addon for this - so be warned, perhaps this wont work at your place or isnt the best solution at all. This is supposed to give a brief overview and should guide as a starting point and im happy for every improvement :slight_smile:

Purpose
The aim of this approach is to get location-based shopping list notifications showing only the necessary shopping items for specific stores when entering the prespecified store zone.

I wanted to realize a easy to use and convenient way to have multiple shared shopping lists, that sync and push to different devices. For this use case I personally experienced Bring! the best choice. Further I wanted to sync my Bring! shopping lists to home assistant to have the possibility to include and delete items from there too.
This approach is specifically generated for my use case, where only iOS devices are used. Further I wanted to have different single items on my screen, which could also be replaced with a fleshed out list of all the items.

But lets start at its end, following picture shows my final result when entering a store zone:

As you can see on this picture there is a general shopping list and specific lists for the different stores that gets pushed while entering the zone. But let me guide you through this step by step:

Prerequisits
Home Assistant
Node-Red
Node-Red Nodes for Bring!
Bring! App
Bring! Home Assistant Integration

You first of all have to install the Bring! integration to home assistant and the Bring! nodes for Node-Red to have the necessary sensors and interaction possibilities with home assistant. After the installation of those parts the inclusion of the node-red flow should enable the rest.

It is important to notice, that you have to include your credentials for the Bring! nodes as well as the ‘List UUID’ for the specific Bring! lists to sync with node-red. For this the use of the debug node in node-red seems fruitful. Further within home assistant you have to setup your zones so they match the shopping markets in the node-red switch node.

At this point there should be multiple zones for the shopping markets in home assistant, the Bring! integration should be installed through HACS, node-red should be prepared with the package for Bring! and finally the Bring! App installed on your mobile phone.

Organization of shopping lists
Within Bring! I have different shopping list for each market in my area corresponding to the zones in home assistant. As examples I have shopping lists for the Rewe, Lidl, Ikea and Dm and also set up specific zones for those. Further I also include an generic shopping list.

The idea behind this structure is to provide market specific lists for items on sale and specific preferences like getting vegetables at the good store and some items in every store, like sugar or butter.

Workflow
Items can be placed with Bring! and home assistant’ shopping lists. On entering predefined zones for the shopping market, node-red calls the Bring! API and pushes the included items as notification to the user. One downside of this approach is, that home assistant can only hold one list in the shopping list, but the integration provides a service to change these lists. I always have the generic shopping list synced to home assistant and mainly use the apps to fill in things to the lists.

Node Red Codes
location based push notifications

[{"id":"d2a12fc55e5c2ffd","type":"server-state-changed","z":"cb1ca7e7ad21ce40","name":"Standort PErson","server":"XXXXXX","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"person.XXXXX","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":720,"y":1260,"wires":[["2ea28fd11c5bcf4f"]]},{"id":"2ea28fd11c5bcf4f","type":"switch","z":"cb1ca7e7ad21ce40","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"Dm","vt":"str"},{"t":"eq","v":"Lidl","vt":"str"},{"t":"eq","v":"Dm LA","vt":"str"},{"t":"eq","v":"Real","vt":"str"},{"t":"eq","v":"Kaufland","vt":"str"},{"t":"eq","v":"Aldi","vt":"str"},{"t":"eq","v":"Rewe LA","vt":"str"},{"t":"eq","v":"Dehner","vt":"str"},{"t":"eq","v":"Lidl LA","vt":"str"},{"t":"eq","v":"not_home","vt":"str"},{"t":"eq","v":"away","vt":"str"},{"t":"eq","v":"Metro","vt":"str"},{"t":"eq","v":"Ikea","vt":"str"},{"t":"eq","v":"Lidl 2","vt":"str"},{"t":"eq","v":"Lambsheim","vt":"str"}],"checkall":"true","repair":false,"outputs":15,"x":910,"y":1300,"wires":[["caf11babf23a5898","a1a02759ffb8b503"],["0f3866b663a2797c","a1a02759ffb8b503"],["caf11babf23a5898","a1a02759ffb8b503"],[],["2b25fb765b938a88","a1a02759ffb8b503"],["7eff2a3ddb5906d4","a1a02759ffb8b503"],["f96a8bfc2d48ab23","a1a02759ffb8b503"],["ca76838b8ef23e73"],["0f3866b663a2797c","a1a02759ffb8b503"],[],[],["65c9f9b0961eaf1d","a1a02759ffb8b503"],["fbfe28243384da8e"],["0f3866b663a2797c","a1a02759ffb8b503"],["2f26bb2804a6f181"]]},{"id":"a1a02759ffb8b503","type":"getItems","z":"cb1ca7e7ad21ce40","name":"","bringCredentials":"XXXXXXXXXX","listUUID":"XXXXX-XXXXX-XXXXXX-XXXXXX-XXXXXXXXX","x":1280,"y":1120,"wires":[["9c9d4144fed78e5a","42ed24e57fc7ee4c"]]},{"id":"9c9d4144fed78e5a","type":"function","z":"cb1ca7e7ad21ce40","name":"umwandeln","func":"msg.payload = {\n    \"payload\": msg.items.purchase,\n    \"topic\": msg.items.uuid\n}\nreturn msg.payload;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1430,"y":1120,"wires":[["8609b4dab885c502"]]},{"id":"8609b4dab885c502","type":"split","z":"cb1ca7e7ad21ce40","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":1540,"y":1120,"wires":[["d8d9221188f7f816"]]},{"id":"d8d9221188f7f816","type":"function","z":"cb1ca7e7ad21ce40","name":"umwandeln","func":"msg.payload = {\n    \"itemName\": msg.payload.name,\n    \"listUUID\": msg.topic,\n    \"specification\": msg.payload.specification\n}\nreturn msg.payload;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1650,"y":1120,"wires":[["17e20c6bb357e91a"]]},{"id":"17e20c6bb357e91a","type":"function","z":"cb1ca7e7ad21ce40","name":"Einkauf","func":"let payload = msg.specification;\nif (payload == 0){\n        msg.payload = { \n            \"data\": { \n            \"message\":  msg.itemName,\n            \"title\": \"Einkauf\",\n            \"data\": {\n                \"push\": {\n                    \"category\": \"1\"\n                },\n                \"action_data\": {\n                    \"listUUID\": msg.listUUID,\n                    \"itemName\": msg.itemName\n                },\n                \"group\": \"Einkauf\",\n                },  \n                }};\n            }\n\nelse if (payload == 1){\n        msg.payload = { \n            \"data\": { \n            \"message\":  msg.itemName,\n            \"title\": \"Einkauf\",\n            \"data\": {\n                \"push\": {\n                    \"category\": \"1\"\n                },\n                \"action_data\": {\n                    \"listUUID\": msg.listUUID,\n                    \"itemName\": msg.itemName\n                },\n                \"group\": \"Einkauf\",\n                },  \n                },\n            };\n     } else {\n            msg.payload = { \n                    \"data\": { \n                    \"message\": msg.specification +\"x \"+ msg.itemName,\n                    \"title\": \"Einkauf\",\n                    \"data\": {\n                        \"push\": {\n                            \"category\": \"1\"\n                        },\n                        \"action_data\": {\n                            \"listUUID\": msg.listUUID,\n                            \"itemName\": msg.itemName\n                        },\n                        \"group\": \"Einkauf\",\n                        },  \n                        },\n                    };\n        }\n        \n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1800,"y":1120,"wires":[["ffe20ca49c628c3b"]]},{"id":"ffe20ca49c628c3b","type":"api-call-service","z":"cb1ca7e7ad21ce40","name":"Notify Person","server":"XXXXXX","version":5,"debugenabled":false,"domain":"notify","service":"mobile_app_XXXXX_iphone","areaId":[],"deviceId":[],"entityId":[],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":2080,"y":1280,"wires":[[]]},{"id":"0f3866b663a2797c","type":"getItems","z":"cb1ca7e7ad21ce40","name":"","bringCredentials":"XXXXXXXXXXXX","listUUID":"XXXXX-XXXXXX-XXXX-XXX-XXXXXXXXXX","x":1280,"y":1160,"wires":[["2ad4801a87653e08","42ed24e57fc7ee4c"]]},{"id":"2ad4801a87653e08","type":"function","z":"cb1ca7e7ad21ce40","name":"umwandeln","func":"msg.payload = {\n    \"payload\": msg.items.purchase,\n    \"topic\": msg.items.uuid\n}\nreturn msg.payload;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1430,"y":1160,"wires":[["59078603327229fc"]]},{"id":"59078603327229fc","type":"split","z":"cb1ca7e7ad21ce40","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":1540,"y":1160,"wires":[["8204f3c33355f0ca"]]},{"id":"8204f3c33355f0ca","type":"function","z":"cb1ca7e7ad21ce40","name":"umwandeln","func":"msg.payload = {\n    \"itemName\": msg.payload.name,\n    \"listUUID\": msg.topic,\n    \"specification\": msg.payload.specification\n}\nreturn msg.payload;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1650,"y":1160,"wires":[["1f47b660ddb47dd9"]]},{"id":"1f47b660ddb47dd9","type":"function","z":"cb1ca7e7ad21ce40","name":"einkauf lidl","func":"let payload = msg.specification;\nif (payload == 0){\n        msg.payload = { \n            \"data\": { \n            \"message\":  msg.itemName,\n            \"title\": \"Lidl\",\n            \"data\": {\n                \"push\": {\n                    \"category\": \"1\"\n                },\n                \"action_data\": {\n                    \"listUUID\": msg.listUUID,\n                    \"itemName\": msg.itemName\n                },\n                \"group\": \"Lidl\",\n                },  \n                }};\n            }\nelse if (payload == 1){\n        msg.payload = { \n            \"data\": { \n            \"message\":  msg.itemName,\n            \"title\": \"Lidl\",\n            \"data\": {\n                \"push\": {\n                    \"category\": \"1\"\n                },\n                \"action_data\": {\n                    \"listUUID\": msg.listUUID,\n                    \"itemName\": msg.itemName\n                },\n                \"group\": \"lidl\",\n                },  \n                },\n            };\n     } else {\n            msg.payload = { \n                    \"data\": { \n                    \"message\": msg.specification +\"x \"+ msg.itemName,\n                    \"title\": \"Lidl\",\n                    \"data\": {\n                        \"push\": {\n                            \"category\": \"1\"\n                        },\n                        \"action_data\": {\n                            \"listUUID\": msg.listUUID,\n                            \"itemName\": msg.itemName\n                        },\n                        \"group\": \"lidl\",\n                        },  \n                        },\n                    };\n        }\n        \n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1810,"y":1160,"wires":[["ffe20ca49c628c3b","42ed24e57fc7ee4c"]]},{"id":"1265227d.1efcae","type":"server","name":"Home Assistant","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":false,"cacheJson":true,"heartbeat":false,"heartbeatInterval":"30","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true},{"id":"a87b192439b4ba18","type":"bringCredentials","name":"XXXX"}]

Sync Bring! with home assistant shopping list

[{"id":"d3de4bea5216592e","type":"server-state-changed","z":"cb1ca7e7ad21ce40","name":"","server":"XXXXXXX","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.date_time","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":230,"y":2080,"wires":[["9b510c7a308f5b61"]]},{"id":"9b510c7a308f5b61","type":"delay","z":"cb1ca7e7ad21ce40","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"minutes","rate":"1","nbRateUnits":"10","rateUnits":"minute","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"allowrate":false,"outputs":1,"x":470,"y":2080,"wires":[["f442925892efcffa"]]},{"id":"f442925892efcffa","type":"api-call-service","z":"cb1ca7e7ad21ce40","name":"","server":"XXXXXXXXXX","version":5,"debugenabled":false,"domain":"shopping_list","service":"bring_sync","areaId":[],"deviceId":[],"entityId":[],"data":"{}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":690,"y":2080,"wires":[[]]},{"id":"1265227d.1efcae","type":"server","name":"Home Assistant","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":false,"cacheJson":true,"heartbeat":false,"heartbeatInterval":"30","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]

feedback from iOS

[{"id":"16342025153a7c29","type":"server-events","z":"cb1ca7e7ad21ce40","name":"","server":"XXXXXXXXX","version":2,"eventType":"ios.notification_action_fired","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"x":240,"y":2140,"wires":[["474bb5fc4d9cf96f"]]},{"id":"474bb5fc4d9cf96f","type":"switch","z":"cb1ca7e7ad21ce40","name":"","property":"payload.event.actionName","propertyType":"msg","rules":[{"t":"eq","v":"OK","vt":"str"},{"t":"eq","v":"ALARM","vt":"str"},{"t":"eq","v":"com.apple.UNNotificationDismissActionIdentifier","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":430,"y":2140,"wires":[[],["501635db63f57c9e"],["501635db63f57c9e"]]},{"id":"501635db63f57c9e","type":"function","z":"cb1ca7e7ad21ce40","name":"set data value for delete ","func":"msg.payload = { \n    \"listUUID\": msg.payload.event.action_data.listUUID,\n    \"itemName\": msg.payload.event.action_data.itemName\n\n    };\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":670,"y":2140,"wires":[["80e7fe856cb58723"]]},{"id":"80e7fe856cb58723","type":"moveToRecently","z":"cb1ca7e7ad21ce40","name":"","bringCredentials":"XXXXXXXXXX","x":890,"y":2140,"wires":[[]]},{"id":"1265227d.1efcae","type":"server","name":"Home Assistant","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":false,"cacheJson":true,"heartbeat":false,"heartbeatInterval":"30","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true},{"id":"a87b192439b4ba18","type":"bringCredentials","name":"XXXXX"}]

For the sake of simplicity I only included two path in the node-red flow, but the adaption would be to expand the switch node to your specific zones, set your list UUIDs and the mobile phone desired to receive the notifications.

Further use cases, limitations and bugs in the current setup
There are also different possible other ways to use this combination. To mention two of those I experimented on, one would be an overview and selection of different meal plans for weeks and another use case is in location based reminders. For the first I have setup a Bring! list with different meals out of which I randomly choose 7 for a weekly meal plan. For the second I just setup a Bring! list for relevant task and the location on my office, so I get my reminder of important task when arriving at my office.
At the end, everything is somewhat neatly but I have to mention some limitations and bugs of my approach, that I didn’t figured out until now:

  • Sometimes the Node-red nodes holding the credentials for Bring! must be updated, if not no API contact will be established
  • The Bring! Home Assistant integration works perfectly fine, if you use home assistant shopping list to add and remove things, if you add things over Bring! App these items only get updated, when the next action on the shopping list in home assistant is made or the automation runs.
  • There should be easier and more parsimonious ways to get this realized and I would be happy if someone would step in and get this running in a more elegant way, but for now this works quiet reliable and is a convenient way to organize my shopping lists based on location.

    There are also unpretty things in my approach so the icons do not change and stays at this home assistant thing – it would be awesome to set those on a per item basis with icons for the shopping list’s items. And not to mention that the total node red flow is a mess - im happy that it works but dont know why :smiley: Further it would be awesome to swipe the items and delete with this single action the item from the shopping list – this is at the moment only possible with android phones not with iOS. In my approach swipe to access the options and then select delete will remove the item from the Bring! list but needs one additional step that I usually never take.

    I hope this helps anyone,

    Bests :slight_smile: