Mealie in HA

Hey @shredder_guitarist. The sensor should only show the last item; essentially it reads the list, pulls the last item into that sensor, and then automation.mealie_transfer_shopping_list should delete it from Mealie after it has saved it to Todoist. Once deleted from mealie, the rest sensor will be reading a different value for the last item on the list from mealie (ie. no longer the deleted one), which triggers the automation to occur again. This repeats until there are no items on the Mealie list any more, giving the sensor the value “Empty List”, which stops the automation from running further.

For the issue you’re seeing, are there any errors logged under System > Logs which might hint as to why the rest_command.mealie_delete_from_shopping_list isn’t working for you?

One thing I can think of which could cause it is if your mealie api endpoint url in your secrets.yaml contains a trailing slash? The value should be something like:

https://my-mealie-url.com/api/groups/shopping/items

But if it has a trailing slash, then the rest_command yaml won’t work, resulting in it trying to call the API endpoint with a double slash, like:

https://my-mealie-url.com/api/groups/shopping/items//9c1f9552-36e2-4a45-a989-dd55e160f807
1 Like

I’m glad it’s working well for you @ih8gates!

Perhaps the description field is only available for certain todo list integrations. I use Todoist as my target todo list, which supports description, but perhaps your one doesn’t.

This has worked fantastically for me, and hugely helps with meal planning and shopping lists! So a huge thanks. It has caused a persistent warning in my logs though - “JSON result was not a dictionary or list with 0th element a dictionary”.

Any ideas on a fix? I’m transferring mealie’s shopping list into HA’s local to-do list.

Ok, After starting over again from scratch i was able to get it working after a lot of swearing, cursing, and a good cry in a cold shower.
This will be a really handy addition to automating shopping lists.

There are still some loose ends though…

  1. The notification message doesn’t work in the way that it’s written in your example. I’m not sure if those are literal quotes and brackets or placeholders; there are quite a few places in your example where I can’t quite tell if the brackets are supposed to be there or not…like ‘{# If distinct food is not defined use display name instead #}’ for example…it seems like this is a comment, but also a piece of code. I removed these occurrences and it still seems to work fine. One thing I’m sure of is that the message: “{{ mealie_delete_response[‘content’] }}” is not liked in my instance for some reason, as a workaround, I just created a “Failed to delete item” message and left it at that.

  2. Also, I’ll share that there is a slight modification to the existing code that can be done to obtain items from a specific shopping list

note the query filter and name notes

- platform: rest
  name: Mealie Shopping List Items #insert preferred shopping list name
  resource_template: !secret mealie_shopping_items_api_endpoint
  headers:
    Authorization: !secret mealie_api_token
  params:
    perPage: 1
    queryFilter: checked=false AND shoppingListId= XXXX-XXXX-XXXXXXXXX #Obtain shopping list id from the end of browser url while navigating to the shopping list in mealie 
    orderBy: food.name
    orderDirection: asc
  #As long as the request contains "total", the API was successfully reached
  availability: >
    {{ value_json['total'] }}
  #Extract the ingredient data in the following format: "food - quantity unit" eg. "apples - 1.5 kilogram", or "Empty List" if no items are left in the list
  value_template: >
    {% if value_json['total'] == 0 %}
      Empty List
    {% elif value_json['items'][0]['food'] == none %}
      {{ value_json['items'][0]['display'] }}
    {% elif value_json['items'][0]['quantity'] > 0 %}
      {{ value_json['items'][0]['food']['name'] }} - {{ (value_json['items'][0]['quantity'] | round(3) | string).rstrip('0').rstrip('.') }} {{ value_json['items'][0]['unit']['name'] }}
    {% else %}
      {{ value_json['items'][0]['food']['name'] }}  
    {% endif %}
  #Extract the ingredient's ID. Required to delete through the API later on.
  json_attributes_path: "$.items.0"
  json_attributes:
    - id

and a modification to the delete command, note the URL includes the specific shopping list id

rest_command:
  #Command to delete a Mealing shopping list item
  mealie_delete_from_shopping_list:
    url: >
      https://###mealieurl###/api/groups/shopping/items/{{ state_attr('sensor.mealie_shopping_list_items', 'id') }}?group_id=XXX-XXXXX-XXXXXX #group id is the preferred shopping list ID
    method: delete
    headers:
      Authorization: !secret mealie_api_token

Great addition with filtering down to a specific shopping list! I don’t use the Mealie in-built shopping lists for anything else, but I could see that being very useful if you do, to not have everything sucked away by HA. :slight_smile:

It’s interesting the comments in the jinja template don’t work for you - they seem supported fine in my configuration. But you’re right, they’re not required - was just using them to try and explain what the template was doing in my example.

UPDATE AUGUST 2024
Home Assistant 2024.7 introduced an official Mealie integration, so I’d recommend using that to get the data into Home Assistant rather than my custom rest sensor based solution below. :slight_smile:

ORIGINAL POST:
I noticed this in my logs as well the other day, I didn’t realise it was from this Mealie setup! I’ll have a play around with it and let you know if I can prevent those log messages. I’m sure I’m doing something dodgy somewhere. :slight_smile:

EDIT: I think it’s the json_attributes_path in the rest sensor that’s causing it. Not sure how to work around that yet.

EDIT 2:
I got this resolved, but it required a fair few changes, essentially to avoid using the rest sensor and instead use the restful platform to extract the ingredient ID and the item name in different sensors. This way I can use a jinja template to handle the null scenario where item[0] in the array doesn’t exist, which occurs when there’s nothing on the Mealie shopping list.

TLDR with complete config at bottom of this post.

First, you need to replace the entire sensor section with this rest section instead:

rest:
  - resource_template: !secret mealie_shopping_items_api_endpoint
    headers:
      Authorization: !secret mealie_api_token
    params:
      perPage: 1
      queryFilter: checked=false
      orderBy: food.name
      orderDirection: asc
    sensor:
      - name: Mealie Shopping List Items
        availability: >
          {{ value_json['total'] }}
        #Sensor to view items which are on any Mealie shopping list
        #Extract the ingredient data in the following format: "food - quantity unit" eg. "apples - 1.5 kilogram", or "Empty List" if no items are left in the list
        value_template: >
          {# If no items in list, return "Empty List" #}
          {% if value_json['total'] == 0 %}
            Empty List

          {# If distinct food is not defined use display name instead #}
          {% elif value_json['items'][0]['food'] == none %}
            {{ value_json['items'][0]['display'] }}

          {# If quantity is available, include it in the output #}
          {% elif value_json['items'][0]['quantity'] > 0 %}
            {{ value_json['items'][0]['food']['name'] }} - {{ (value_json['items'][0]['quantity'] | round(3) | string).rstrip('0').rstrip('.') }} {{ value_json['items'][0]['unit']['name'] if value_json['items'][0]['unit'] is not none }}

          {# Otherwise, print just the food name #}
          {% else %}
            {{ value_json['items'][0]['food']['name'] }}
            
          {% endif %}
      - name: Mealie Shopping List Item To Delete
        availability: >
          {{ value_json['total'] }}
        #Extract the ingredient's ID. Required to delete through the API later on.
        value_template: >
          {{ value_json['items'][0]['id'] }}

Then you need to update the mealie_delete_from_shopping_list rest command to refer to the new sensor’s state, rather than the old sensor’s attribute:

rest_command:
  #Command to delete a Mealing shopping list item
  mealie_delete_from_shopping_list:
    url: >
      {{ url }}/{{ states('sensor.mealie_shopping_list_item_to_delete') }}
    method: delete
    headers:
      Authorization:  !secret mealie_api_token

And finally, update automation.mealie_update_rest_sensors to include the new sensor in the homeassistant.update_entity step:

automation:
  #Automation to regularly refresh the "Mealie Shopping List Items" sensor 
  - id: mealie_update_rest_sensors
    alias: "Mealie - Update Rest Sensors"
    initial_state: "on"
    trigger:
      - platform: time_pattern
        seconds: "/10" #How often do you want to check for updates in Mealie?
    action:
      - service: homeassistant.update_entity
        data:
          entity_id:
            - sensor.mealie_shopping_list_items
            - sensor.mealie_shopping_list_item_to_delete

Looks like this has done the trick for me to remove that log statement - hopefully it does the same for you!

TLDR - here’s the final complete configuration, which fixes the “JSON result was not a dictionary or list with 0th element a dictionary” log statement:

rest:
  - resource_template: !secret mealie_shopping_items_api_endpoint
    headers:
      Authorization: !secret mealie_api_token
    params:
      perPage: 1
      queryFilter: checked=false
      orderBy: food.name
      orderDirection: asc
    sensor:
      - name: Mealie Shopping List Items
        availability: >
          {{ value_json['total'] }}
        #Sensor to view items which are on any Mealie shopping list
        #Extract the ingredient data in the following format: "food - quantity unit" eg. "apples - 1.5 kilogram", or "Empty List" if no items are left in the list
        value_template: >
          {# If no items in list, return "Empty List" #}
          {% if value_json['total'] == 0 %}
            Empty List

          {# If distinct food is not defined use display name instead #}
          {% elif value_json['items'][0]['food'] == none %}
            {{ value_json['items'][0]['display'] }}

          {# If quantity is available, include it in the output #}
          {% elif value_json['items'][0]['quantity'] > 0 %}
            {{ value_json['items'][0]['food']['name'] }} - {{ (value_json['items'][0]['quantity'] | round(3) | string).rstrip('0').rstrip('.') }} {{ value_json['items'][0]['unit']['name'] if value_json['items'][0]['unit'] is not none }}

          {# Otherwise, print just the food name #}
          {% else %}
            {{ value_json['items'][0]['food']['name'] }}
            
          {% endif %}
      - name: Mealie Shopping List Item To Delete
        availability: >
          {{ value_json['total'] }}
        #Extract the ingredient's ID. Required to delete through the API later on.
        value_template: >
          {{ value_json['items'][0]['id'] }}

rest_command:
  #Command to delete a Mealing shopping list item
  mealie_delete_from_shopping_list:
    url: >
      {{ url }}/{{ states('sensor.mealie_shopping_list_item_to_delete') }}
    method: delete
    headers:
      Authorization:  !secret mealie_api_token

automation:
  #Automation to regularly refresh the "Mealie Shopping List Items" sensor 
  - id: mealie_update_rest_sensors
    alias: "Mealie - Update Rest Sensors"
    initial_state: "on"
    trigger:
      - platform: time_pattern
        seconds: "/10" #How often do you want to check for updates in Mealie?
    action:
      - service: homeassistant.update_entity
        data:
          entity_id:
            - sensor.mealie_shopping_list_items
            - sensor.mealie_shopping_list_item_to_delete

  #Automation to transfer items from the Mealie shopping list to another list of your choice
  - id: mealie_transfer_shopping_list
    alias: "Mealie - Transfer Shopping List"
    description: "When shopping list items are added to the Mealie shopping list, transfer them to an alternative list."
    mode: queued
    max: 10
    initial_state: "on"
    variables:
      target_shopping_list: todo.groceries_list #CHANGEME: entity ID of your shopping list
      notify_service: notify.mobile_app_michael_s_galaxy_s10 #CHANGEME: where to be notified if items fail to delete from Mealie shopping list
    trigger:
      - id: "regular_trigger"
        platform: state
        entity_id: sensor.mealie_shopping_list_items
      - id: "fallback_trigger"
        platform: time_pattern
        minutes: "/10"
    condition:
      - condition: not
        conditions:
          - condition: state
            entity_id: sensor.mealie_shopping_list_items
            state: "Empty List"
    action:
      - alias: "Ensure shopping list item isn't now 'Empty List'"
        condition: not
        conditions:
          - condition: state
            entity_id: sensor.mealie_shopping_list_items
            state: "Empty List"

      - alias: "Add shopping list item to target list"
        service: todo.add_item
        target:
          entity_id: "{{ target_shopping_list }}"
        data:
          item: "{{ states('sensor.mealie_shopping_list_items') }}"
          description: Imported From Mealie

      - alias: "Delete shopping list item from Mealie"
        service: rest_command.mealie_delete_from_shopping_list
        data:
          url: !secret mealie_shopping_items_api_endpoint
        response_variable: mealie_delete_response

      - alias: "Notify if item failed to delete from Mealie"
        if: "{{ mealie_delete_response['status'] != 200 }}"
        then:
          - service: "{{ notify_service }}"
            data:
              title: "Failed to delete item from Mealie list"
              message: "{{ mealie_delete_response['content'] }}"
              data:
                notification_icon: "mdi:food-off"
                
      - alias: "Refresh Mealie shopping list items sensor, in case more items need processing"
        service: homeassistant.update_entity
        data:
          entity_id:
            - sensor.mealie_shopping_list_items
3 Likes

I’ve also realised another issue with this shopping list setup the other day. It’s occasionally adding “Unavailable” as an item to my todoist shopping list. I think it happens when I restart my home assistant instance, most likely because the automation isn’t handling the unavailable state properly in the trigger. I’ll let you know if I fix that, but if anybody beats me to it then I’ll happily steal your work! :wink:

Working like a dream. Huge thanks! And phenomenal effort to recode so quickly!

1 Like

Thanks for updating this, I was able to get it to work also (after several hours)…one thing, i’ve noticed a lot of additional sensors being created…and I can’t explain ‘why’
While admitedly i did make some modifications for specific grocery list, nothing that i modified would result in an items_2,3,4, being created. I’ve searched my yaml, there is no “_items_2…3…4.”. anything, so I don’t know why that’s happening.

Otherwise, the update seems to be transferring fine… I’ve noticed that it’s a slower transfer than with the first iteration of code…the first iteration was basically a rapid fire of transfers this iteration is more of a 10-15 seconds in between each item getting moved. Not sure if that’s happening with anyone else or just me.

update disregard the multiple entries mention, I think they cleared up after restarting.

This would bo perfect for my new dashboard (wall tablet) in the kitchen.
I tried the last config of @my.spider.died but cannot get it al to work, the rest command wont work.
The modifications that did to make it somewhat work, are offcourse not working (newbie in all of this) maybe somebody can point me in the right direction.

- platform: rest
    resource: "http://****/api/groups/mealplans/today"
    method: GET
    name: Mealie todays meal
    headers:
      Authorization: Bearer ****
    value_template: "{{ value_json[0].recipe.name }}"
    force_update: true
    scan_interval: 30 

  - platform: rest
    resource: "http://****/api/groups/mealplans/today"
    method: GET
    name: Mealie todays meal ID
    headers:
      Authorization: Bearer ****
    value_template: "{{ value_json[0].recipe.id }}"
    force_update: true
    scan_interval: 30
    
  #Sensor to view items which are on any Mealie shopping list
  - platform: rest
    name: Mealie Shopping List Items
    resource_template: "http://****/api/groups/shopping/items"
    headers:
      Authorization: Bearer ****
    params:
      perPage: 1
      queryFilter: checked=false
      orderBy: food.name
      orderDirection: asc
    # As long as the request contains "total", the API was successfully reached
    availability: >
      {{ value_json['total'] }}
    
    value_template: >
      {# If no items in list, return "Empty List" #}
      {% if value_json['total'] == 0 %}
        Empty List

      {# If distinct food is not defined use display name instead #}
      {% elif value_json['items'][0]['food'] == none %}
        {{ value_json['items'][0]['display'] }}

      {# If quantity is available, include it in the output #}
      {% elif value_json['items'][0]['quantity'] > 0 %}
        {{ value_json['items'][0]['food']['name'] }} - {{ (value_json['items'][0]['quantity'] | round(3) | string).rstrip('0').rstrip('.') }} {{ value_json['items'][0]['unit']['name'] if value_json['items'][0]['unit'] is not none }}

      {# Otherwise, print just the food name #}
      {% else %}
        {{ value_json['items'][0]['food']['name'] }}
        
      {% endif %}

No logs = no issue

@edi023 You can try first in a tool like Postman if all your call config works correct.

To get the rest commands to work, I made my own rests.yaml file and put “rest: !include rests.yaml” in the config file.

my rests file looks like this…(bare in mind that my code is modified from the original for a specific shopping list in mealie)

#Mealie rest call for grocery store
- resource_template: !secret mealie_shopping_items_api_endpoint
  headers:
    Authorization: !secret mealie_api_token
  params:
    perPage: 1
    queryFilter: checked=false AND shoppingListId=[shoppinglistid]
    orderBy: food.name
    orderDirection: asc
  sensor:
    - name: Mealie grocery store Shopping List Items
      availability: >
        {{ value_json['total'] }}
      #Sensor to view items which are on any Mealie shopping list
      #Extract the ingredient data in the following format: "food - quantity unit" eg. "apples - 1.5 kilogram", or "Empty List" if no items are left in the list
      value_template: >
        {# If no items in list, return "Empty List" #}
        {% if value_json['total'] == 0 %}
          Empty List

        {# If distinct food is not defined use display name instead #}
        {% elif value_json['items'][0]['food'] == none %}
          {{ value_json['items'][0]['display'] }}

        {# If quantity is available, include it in the output #}
        {% elif value_json['items'][0]['quantity'] > 0 %}
          {{ value_json['items'][0]['food']['name'] }} - {{ (value_json['items'][0]['quantity'] | round(3) | string).rstrip('0').rstrip('.') }} {{ value_json['items'][0]['unit']['name'] if value_json['items'][0]['unit'] is not none }}

        {# Otherwise, print just the food name #}
        {% else %}
          {{ value_json['items'][0]['food']['name'] }}
          
        {% endif %}
    - name: Mealie grocery store Shopping List Item To Delete
      availability: >
        {{ value_json['total'] }}
      #Extract the ingredient's ID. Required to delete through the API later on.
      value_template: >
        {{ value_json['items'][0]['id'] }}

it’s easier to test the api with https://[mealiedomanname]/docs (assuming mealie is behind a proxy)

i’m using a split config setup, in configuration.yaml i got
rest: !include rests.yaml
rest_command: !include rest_commands.yaml
automation: !include automations.yaml

using the scripts from my.spider.died and splitting this up to the appropriate files gives errors.

2024-05-08 17:13:00.419 ERROR (MainThread) [homeassistant.components.automation.mealie_transfer_shopping_list] Mealie - Transfer Shopping List: Notify if item failed to delete from Mealie: Error executing script. Error for call_service at pos 1: Template rendered invalid service: 
2024-05-08 17:13:00.419 ERROR (MainThread) [homeassistant.components.automation.mealie_transfer_shopping_list] Mealie - Transfer Shopping List: Error executing script. Error for if at pos 4: Template rendered invalid service: 

it looks like the correct parameters are not passed between the automation, rest and rest_command.

does anyone know how to make this work ?

adding 1 item in the mealie shoppinglist, now gets repeated over and over as ‘unknown’ in my todolist

rests.yaml

- resource_template: !secret mealie_shopping_items_api_endpoint
  headers:
    Authorization: !secret mealie_api_token
  params:
    perPage: 1
    queryFilter: checked=false
    orderBy: food.name
    orderDirection: asc
  sensor:
    - name: Mealie Shopping List Items
      availability: >
        {{ value_json['total'] }}
      #Sensor to view items which are on any Mealie shopping list
      #Extract the ingredient data in the following format: "food - quantity unit" eg. "apples - 1.5 kilogram", or "Empty List" if no items are left in the list
      value_template: >
        {# If no items in list, return "Empty List" #}
        {% if value_json['total'] == 0 %}
          Empty List

        {# If distinct food is not defined use display name instead #}
        {% elif value_json['items'][0]['food'] == none %}
          {{ value_json['items'][0]['display'] }}

        {# If quantity is available, include it in the output #}
        {% elif value_json['items'][0]['quantity'] > 0 %}
          {{ value_json['items'][0]['food']['name'] }} - {{ (value_json['items'][0]['quantity'] | round(3) | string).rstrip('0').rstrip('.') }} {{ value_json['items'][0]['unit']['name'] if value_json['items'][0]['unit'] is not none }}

        {# Otherwise, print just the food name #}
        {% else %}
          {{ value_json['items'][0]['food']['name'] }}
          
        {% endif %}
    - name: Mealie Shopping List Item To Delete
      availability: >
        {{ value_json['total'] }}
      #Extract the ingredient's ID. Required to delete through the API later on.
      value_template: >
        {{ value_json['items'][0]['id'] }}

rest_commands.yaml

#Command to delete a Mealing shopping list item
mealie_delete_from_shopping_list:
  url: >
    {{ url }}/{{ states('sensor.mealie_shopping_list_item_to_delete') }}
  method: delete
  headers:
    Authorization:  !secret mealie_api_token

automations.yaml

- id: mealie_transfer_shopping_list
  alias: Mealie - Transfer Shopping List
  description: When shopping list items are added to the Mealie shopping list, transfer
    them to an alternative list.
  trigger:
  - id: regular_trigger
    platform: state
    entity_id: sensor.mealie_shopping_list_items
  - id: fallback_trigger
    platform: time_pattern
    minutes: /1
  condition:
  - condition: not
    conditions:
    - condition: state
      entity_id: sensor.mealie_shopping_list_items
      state: Empty List
  action:
  - alias: Ensure shopping list item isn't now 'Empty List'
    condition: not
    conditions:
    - condition: state
      entity_id: sensor.mealie_shopping_list_items
      state: Empty List
  - alias: Add shopping list item to target list
    service: todo.add_item
    target:
      entity_id: '{{ target_shopping_list }}'
    data:
      item: '{{ states(''sensor.mealie_shopping_list_items'') }}'
  - alias: Delete shopping list item from Mealie
    service: rest_command.mealie_delete_from_shopping_list
    data:
      url: "https://xxx/api/groups/shopping/items"
    response_variable: mealie_delete_response
  - alias: Notify if item failed to delete from Mealie
    if:
    - condition: template
      value_template: '{{ mealie_delete_response[''status''] != 200 }}'
    then:
    - service: '{{ notify_service }}'
      data:
        title: Failed to delete item from Mealie list
        message: '{{ mealie_delete_response[''content''] }}'
        data:
          notification_icon: mdi:food-off
  - alias: Refresh Mealie shopping list items sensor, in case more items need processing
    service: homeassistant.update_entity
    data:
      entity_id:
      - sensor.mealie_shopping_list_items
  mode: queued
  max: 10
  initial_state: 'on'
  variables:
    target_shopping_list: todo.shopping_list
- id: mealie_update_rest_sensors
  alias: Mealie - Update Rest Sensors
  initial_state: 'on'
  trigger:
  - platform: time_pattern
    seconds: /10
  action:
  - service: homeassistant.update_entity
    data:
      entity_id:
      - sensor.mealie_shopping_list_items
      - sensor.mealie_shopping_list_item_to_delete

solved.
in secrets.yaml the mealie_api_token needs to start with "Bearer "

1 Like

Hi there, I have built a custom docker image, so that Mealie can now run as HA add-on with ingress support! You can find it in my GitHub repository.

2 Likes

Thx for your blog.
I followed it.
I do have problems at the end, with your list…
I copied the code, but its not showing right…

@skank I see it works for you!
This is indeed what you see if you check the code in the Developer tool > Template editor OR in the Markdown Card configuration but when you paste the code in the Visual editor state in stead of the Code editor state. Press on the blue text below to switch editor mode and paste you code again. Or check your line indent for each line.

This is how the code in the code editor mode looks like.

(This evening I will also add to my blog page how you make the text clickable)