Anrolosia / Shopping-List-with-Grocy

A Shopping list integration with Grocy for Home Assistant
MIT License
41 stars 1 forks source link
custom grocy hacs home-assistant integration

Shopping List with Grocy Integration

Release Last Commit HACS HACS Donate Coffee

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.

Showcase Example

The image above was generated using Auto Entities Card, Card Mods Card and Custom Button Card.


Requirements 💡

This integration uses MQTT with auto discovery:

Installation 🏠

Installation is a multi-step process. Follow each of the following steps.

1. Add HACS Integration

This integration is available in HACS (Home Assistant Community Store). You can click on

Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.

or install it manually as follows:

2. Prepare Grocy

You have to provide an url and an API key to use this integration.

3. Add Home Assistant Integration

Clik on

Open your Home Assistant instance and start setting up a new integration.

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:

Grocy add-on SSL

Available Sensors

This integration will create as much sensors as you have products configured in your Grocy instance, but will also create 3 other sensors:

Product sensor

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.

Shopping list sensor

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.

Updating sensor

This sensor (binary_sensor.updating_shopping_list_with_grocy) show current status of list update.

Available Switch

Pause 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.

Available Services

This integration provides 4 services

shopping_list_with_grocy.add_product
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..."
shopping_list_with_grocy.remove_product
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
shopping_list_with_grocy.update_note
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..."
shopping_list_with_grocy.refresh_products
service: shopping_list_with_grocy.refresh_products
data: {}

Custom products UserFields

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

Known issues / FAQ 💡

binary_sensor.updating_shopping_list_with_grocy is not created

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

Examples

Example of dashboard UI

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: '&nbsp;'
                  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:

scripts.yaml
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

Additional Information ℹ️

Feature Requests and Contributions

Don't hesitate to ask for features or contribute your own pull request. ⭐