Integrate and interact with your Grocy shopping list directly from your Home Assistant dashboard.
:warning: This is still an early release. It may not be stable and it may have bugs. :warning:
See the Issues page to report a bug or to add a feature request.
The image above was generated using Auto Entities Card, Card Mods Card and Custom Button Card.
This integration uses MQTT with auto discovery:
Installation is a multi-step process. Follow each of the following steps.
This integration is available in HACS (Home Assistant Community Store). You can click on
or install it manually as follows:
You have to provide an url
and an API key
to use this integration.
https://<url-of-your-grocy-installation>/
and your newly generated API keyClik on
or install it manually as follows:
Depending on the number of products you have in your Grocy instance, the sensors may take a while to be created and populated.
:warning: If you want to use Grocy's add-on from Home assistant, please configure a port/web interface in grocy addon config at the bottom, f.e. 9192 then use your HA address like this: https://192.168.1.1:9192 and uncheck the Verify SSL certificate checkbox. You SHOULD use https if your Grocy's module is configured to use SSL (even if you don't have any certificate). Use http:// if it's not checked :warning:
This integration will create as much sensors as you have products configured in your Grocy instance, but will also create 3 other sensors:
This sensor (sensor.products_shopping_list_with_grocy
) state is the current number of products you have in your Grocy instance.
If you checked the option during the configuration of the module to include more informations, you'll have all your products here too.
This sensor (sensor.shopping_list_shopping_list_with_grocy
) state is the current number of products you have in your shopping list on Grocy.
If you checked the option during the configuration of the module to include more informations, you'll have all your products here too.
This sensor (binary_sensor.updating_shopping_list_with_grocy
) show current status of list update.
This switch (switch.pause_update_shopping_list_with_grocy
) will prevent any updates from your Grocy instance to your Home Assistant.
It could be useful if you want to update several products at once or run a long script.
This integration provides 4 services
service: shopping_list_with_grocy.add_product
data:
product_id: sensor.shopping_list_with_grocy_<your product>
shopping_list_id: <id of your shopping list on Grocy> # Optional, default is list 1
note: "This is the note of the shopping list item..."
service: shopping_list_with_grocy.remove_product
data:
product_id: sensor.shopping_list_with_grocy_<your product>
shopping_list_id: <id of your shopping list on Grocy> # Optional, default is list 1
service: shopping_list_with_grocy.update_note
data:
product_id: sensor.shopping_list_with_grocy_<your product>
shopping_list_id: <id of your shopping list on Grocy> # Optional, default is list 1
note: "This is the note of the shopping list item..."
service: shopping_list_with_grocy.refresh_products
data: {}
In Grocy -> Manage master data -> Userfields, you can add custom fields on your products. You can now use that!
For example, if you want to create a custom sort, create a custom field in Grocy:
Entity: products
Name: customsort
Caption: Custom sort
Type: Number(decimal)
Show as column in tables: checked
then modify your dashboard to use that sort by replacing
sort:
method: friendly_name
with
sort:
method: attribute
attribute: userfields:customsort
numeric: true
There is probably an issue with your MQTT configuration, you have to create a user, MQTT no longer allows anonymous connections, please check this link
If you want to build the same example as on the screenshot above, this is an example of dashboard UI
type: custom:bootstrap-grid-card
cards:
- type: row
class: justify-content-center
cards:
- type: custom:auto-entities
class: col-12 col-lg-9 p-0
card:
type: custom:layout-card
layout_type: grid
layout_options:
grid-template-columns: 20% 20% 20% 20% 20%
mediaquery:
'(max-width: 600px)':
grid-template-columns: 50% 50%
'(max-width: 800px)':
grid-template-columns: 25% 25% 25% 25%
filter:
include:
- entity_id: sensor.shopping_list_with_grocy_.*
sort:
method: friendly_name
options:
type: custom:button-card
entity: this.entity_id
aspect_ratio: 4/3
show_icon: false
show_label: true
show_state: false
label: |
[[[
return `(${entity.attributes.location})`
]]]
tap_action:
action: call-service
service: shopping_list_with_grocy.add_product
service_data:
product_id: this.entity_id
double_tap_action:
action: call-service
service: shopping_list_with_grocy.remove_product
service_data:
product_id: this.entity_id
hold_action:
action: call-service
service: shopping_list_with_grocy.remove_product
service_data:
product_id: this.entity_id
styles:
label:
- z-index: 1
- font-size: small
- margin-top: 1vh
name:
- z-index: 1
- font-size: large
- font-weight: bold
- margin-bottom: 1vh
card:
- background-image: |
[[[
return 'url("data:image/jpg;base64,' + `${entity.attributes.product_image}` + '")';
]]]
- background-repeat: no-repeat
- background-position: center
- background-size: cover
- border-width: |
[[[
if (entity.attributes.product_image)
return "0";
else
return "var(--ha-card-border-width, 1px)";
]]]
custom_fields:
gradient:
- display: |
[[[
if (entity.attributes.product_image)
return "block";
else
return "none";
]]]
- height: |
[[[
if (entity.attributes.product_image)
return "100%";
else
return "0px";
]]]
- width: |
[[[
if (entity.attributes.product_image)
return "100%";
else
return "0px";
]]]
- font-size: |
[[[
if (entity.attributes.product_image)
return "auto";
else
return "0px";
]]]
- line-height: |
[[[
if (entity.attributes.product_image)
return "auto";
else
return "0px";
]]]
qty_in_list:
- background-color: green
- border-radius: 50%
- position: absolute
- right: 5%
- top: 5%
- height: |
[[[
if (entity.state > 0) return '20px';
else return '0px';
]]]
- width: |
[[[
if (entity.state > 0) return '20px';
else return '0px';
]]]
- font-size: |
[[[
if (entity.state > 0) return '14px';
else return '0px';
]]]
- line-height: |
[[[
if (entity.state > 0) return '20px';
else return '0px';
]]]
custom_fields:
gradient: ' '
qty_in_list: >-
[[[
if ('list_1_qty' in entity.attributes)
return `${entity.attributes.list_1_qty}`;
else
return "0";
]]]
card_mod:
style: |
ha-card {
height: 100%;
height: -moz-available; /* WebKit-based browsers will ignore this. */
height: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
height: fill-available;
}
ha-card #gradient {
position: absolute !important;
top: 0%;
left: 0;
z-index: 0;
background-image: linear-gradient(
0deg,
hsla(0, 0%, 0%, 0.8) 0%,
hsla(0, 0%, 0%, 0.79) 8.3%,
hsla(0, 0%, 0%, 0.761) 16.3%,
hsla(0, 0%, 0%, 0.717) 24.1%,
hsla(0, 0%, 0%, 0.66) 31.7%,
hsla(0, 0%, 0%, 0.593) 39%,
hsla(0, 0%, 0%, 0.518) 46.1%,
hsla(0, 0%, 0%, 0.44) 53%,
hsla(0, 0%, 0%, 0.36) 59.7%,
hsla(0, 0%, 0%, 0.282) 66.1%,
hsla(0, 0%, 0%, 0.207) 72.3%,
hsla(0, 0%, 0%, 0.14) 78.3%,
hsla(0, 0%, 0%, 0.083) 84%,
hsla(0, 0%, 0%, 0.039) 89.6%,
hsla(0, 0%, 0%, 0.01) 94.9%,
hsla(0, 0%, 0%, 0) 100%
);
}
ha-card .ellipsis {
white-space: normal
}
exclude: []
sort:
method: attribute
attribute: location
- type: col
class: col-12 col-lg-3 p-0 pt-2 pb-2
cards:
- type: row
cards:
- type: custom:button-card
entity: sensor.products_shopping_list_with_grocy
class: col
icon: mdi:cart-remove
show_name: false
tap_action:
action: call-service
service: script.grocy_clear_shopping_list
hold_action: none
double_tap_action: none
confirmation:
text: This will clear your shopping list, are you sure?
- type: custom:button-card
entity: binary_sensor.updating_shopping_list_with_grocy
class: col
show_name: false
lock:
enabled: '[[[ return entity.state === "on"; ]]]'
unlock: hold
tap_action:
action: call-service
service: shopping_list_with_grocy.refresh_products
hold_action: none
double_tap_action: none
color: var(--primary-text-color)
state:
- value: 'on'
styles:
icon:
- animation: rotating 1s linear infinite
- type: custom:auto-entities
filter:
include:
- entity_id: sensor.shopping_list_with_grocy_.*
attributes:
list_1_qty: '>0'
not:
attributes:
list_1_note: out_of_stock
sort:
method: friendly_name
options:
type: custom:collapsable-cards
cards:
- type: custom:bootstrap-grid-card
cards:
- type: row
cards:
- type: custom:button-card
class: col-3
icon: mdi:cart-minus
tap_action:
action: call-service
service: shopping_list_with_grocy.remove_product
service_data:
product_id: this.entity_id
shopping_list_id: 1
- type: custom:button-card
class: col-3
icon: mdi:cart-plus
tap_action:
action: call-service
service: shopping_list_with_grocy.add_product
service_data:
product_id: this.entity_id
shopping_list_id: 1
- type: custom:button-card
class: col-3
icon: mdi:cart-off
tap_action:
action: call-service
service: shopping_list_with_grocy.update_note
service_data:
product_id: this.entity_id
shopping_list_id: 1
note: out_of_stock
title_card:
type: custom:button-card
entity: this.entity_id
tap_action:
action: none
styles:
grid:
- grid-template-rows: 1fr
- grid-template-areas: '"i n qty_in_list"'
- grid-template-columns: min-content 1fr min-content
card:
- padding: calc(var(--bs-gutter-x) * 0.5)
- background-color: |
[[[
if (entity.attributes.list_1_note == 'out_of_stock')
return "red";
else
return "var( --ha-card-background, var(--card-background-color, white) )";
]]]
icon:
- padding: 8px
- width: 24px
- height: 24px
name:
- align-self: center
- text-align: left
- width: 100%
- margin-left: 46px
- margin-right: 38px
- font-size: 1rem;
- white-space: nowrap
- overflow: hidden
- text-overflow: ellipsis
custom_fields:
qty_in_list:
- align-self: center
- justify-self: start
- padding-left: 8px
custom_fields:
qty_in_list: >-
[[[
if ('list_1_qty' in entity.attributes)
return `${entity.attributes.list_1_qty}`;
else
return "0";
]]]
card_mod:
style: |
:host #qty_in_list {
padding: calc(var(--bs-gutter-x) * 0.5)
}
exclude: []
card:
type: vertical-stack
card_param: cards
sort:
method: friendly_name
- type: entities
title: Out of stock
entities:
- type: divider
- type: custom:auto-entities
filter:
include:
- entity_id: sensor.shopping_list_with_grocy_.*
attributes:
list_1_qty: '>0'
list_1_note: out_of_stock
options:
type: custom:button-card
entity: this.entity_id
icon: mdi:cart-arrow-up
color: disabled
tap_action:
action: call-service
service: shopping_list_with_grocy.update_note
service_data:
product_id: this.entity_id
note: ''
styles:
grid:
- grid-template-rows: 1fr
- grid-template-areas: '"i n qty_in_list"'
- grid-template-columns: min-content 1fr min-content
card:
- padding: calc(var(--bs-gutter-x) * 0.5)
icon:
- padding: 8px
- width: 24px
- height: 24px
name:
- align-self: center
- text-align: left
- width: 100%
- margin-left: 46px
- margin-right: 38px
- font-size: 1rem;
- white-space: nowrap
- overflow: hidden
- text-overflow: ellipsis
custom_fields:
qty_in_list:
- align-self: center
- justify-self: start
- padding-left: 8px
custom_fields:
qty_in_list: >-
[[[
if ('list_1_qty' in entity.attributes)
return `${entity.attributes.list_1_qty}`;
else
return "0";
]]]
card_mod:
style: |
:host #qty_in_list {
padding: calc(var(--bs-gutter-x) * 0.5)
}
card:
type: vertical-stack
card_param: cards
sort:
method: friendly_name
In the code above, you can see references on some scripts, here is the configuration:
grocy_clear_shopping_list:
alias: Clear Shopping list
sequence:
- alias: Set a templated variable
variables:
in_shopping_list: '{{ states.sensor | select("search", ".shopping_list_with_grocy_.+")
| selectattr("state", "gt", "0") | selectattr("attributes.list_1_note", "eq", "")
| map(attribute="entity_id") | list }}'
default_products: [
'sensor.shopping_list_with_grocy_<product_1>',
'sensor.shopping_list_with_grocy_<product_4>',
# list of products you'd like to add by default
]
- repeat:
count: '{{ in_shopping_list | count }}'
sequence:
- variables:
entity_id: '{{ in_shopping_list[repeat.index - 1] }}'
- repeat:
count: '{{ states(entity_id) }}'
sequence:
- service: shopping_list_with_grocy.remove_product
data:
product_id: '{{ entity_id }}'
- repeat:
count: '{{ default_products | count }}'
sequence:
- variables:
entity_id: '{{ default_products[repeat.index - 1] }}'
- service: shopping_list_with_grocy.add_product
data:
product_id: '{{ entity_id }}'
mode: single
Don't hesitate to ask for features or contribute your own pull request. ⭐