# Количка + Home Assistant — споделена кошница

> Обновено: 2026-07-16 · Каноничен адрес: https://kolichka.gotvach.com/home-assistant

Свържи споделената семейна кошница на приложението **Количка** с вградения списък
за пазаруване (**todo.shopping_list**) в Home Assistant. Синхронизацията е двупосочна:
добавяне, премахване и отметки „купено“ текат и в двете посоки. Приложението е водещо.

## Стъпки

1. **Вземи код за семейна кошница** — Отвори приложението Количка → Кошница → иконата „Семейна кошница“. Натисни „Създай нова“, за да получиш код (напр. 442834), или „Влез“ с код, който вече ползвате в семейството. Всеки с този код вижда и редактира същия списък.
2. **Разреши пакети в Home Assistant** — Ако още го нямаш, добави в configuration.yaml (веднъж): под ключа homeassistant добави „packages: !include_dir_named packages“. Създай папка config/packages/.
3. **Добави пакета kolichka.yaml** — Създай config/packages/kolichka.yaml със съдържанието по-долу и замени ВАШИЯТ_КОД с твоя код от Стъпка 1. Пакетът добавя REST сензор (чете кошницата на всеки 30 сек), rest_command (записва обратно) и две автоматизации за двупосочна синхронизация с вградения списък за пазаруване (todo.shopping_list).
4. **Провери конфигурацията и рестартирай** — Настройки → Разработчик → „Провери конфигурацията“ (или командата за check_config). Ако е ОК, рестартирай Home Assistant. След рестарта списъкът за пазаруване се попълва от кошницата до 30 секунди.
5. **Готово — двупосочна синхронизация** — Добавиш ли продукт в приложението, той се появява в HA до ~30 сек. Отметка/промяна в списъка на HA се връща в приложението за няколко секунди. Приложението е водещо (source of truth).

## Пакет за Home Assistant (config/packages/kolichka.yaml)

Замени `ВАШИЯТ_КОД` с твоя код:

```yaml
# Количка споделена кошница <-> Home Assistant списък за пазаруване.
# Постави този файл в config/packages/kolichka.yaml
# и добави в configuration.yaml (веднъж):
#   homeassistant:
#     packages: !include_dir_named packages
input_boolean:
  kolichka_syncing:
    name: Kolichka syncing guard

rest_command:
  kolichka_basket_push:
    url: "https://kolichka.gotvach.com/api/fambasket/ВАШИЯТ_КОД"
    method: PUT
    content_type: "application/json"
    payload: "{{ payload }}"

sensor:
  - platform: rest
    name: Kolichka Family Basket
    unique_id: kolichka_family_basket
    resource: "https://kolichka.gotvach.com/api/fambasket/ВАШИЯТ_КОД"
    scan_interval: 30
    value_template: "{{ value_json.remaining }}"
    json_attributes: [items, remaining_names, count, updated_at]

automation:
  # PULL: кошница от приложението -> списък в HA (приложението е водещо)
  - id: kolichka_pull_to_ha
    alias: "Количка: кошница -> списък за пазаруване"
    mode: single
    max_exceeded: silent
    trigger:
      - platform: state
        entity_id: sensor.kolichka_family_basket
      - platform: homeassistant
        event: start
      - platform: time_pattern
        minutes: "/2"
    action:
      - service: input_boolean.turn_on
        target: { entity_id: input_boolean.kolichka_syncing }
      - variables:
          want: "{{ state_attr('sensor.kolichka_family_basket','items') | default([], true) }}"
          want_names: "{{ (state_attr('sensor.kolichka_family_basket','items') | default([], true)) | map(attribute='n') | list }}"
      - service: todo.get_items
        target: { entity_id: todo.shopping_list }
        data: { status: [needs_action, completed] }
        response_variable: cur
      - variables:
          have: "{{ cur['todo.shopping_list']['items'] | default([], true) }}"
          have_names: "{{ (cur['todo.shopping_list']['items'] | default([], true)) | map(attribute='summary') | list }}"
      - repeat:
          for_each: "{{ want }}"
          sequence:
            - if: "{{ repeat.item.n not in have_names }}"
              then:
                - service: todo.add_item
                  target: { entity_id: todo.shopping_list }
                  data: { item: "{{ repeat.item.n }}" }
      - repeat:
          for_each: "{{ have }}"
          sequence:
            - if: "{{ repeat.item.summary not in want_names }}"
              then:
                - service: todo.remove_item
                  target: { entity_id: todo.shopping_list }
                  data: { item: "{{ repeat.item.summary }}" }
      - repeat:
          for_each: "{{ want }}"
          sequence:
            - variables:
                m: "{{ have | selectattr('summary','eq',repeat.item.n) | list }}"
            - if: "{{ (m | length > 0) and ((m[0].status == 'completed') != (repeat.item.b | bool)) }}"
              then:
                - service: todo.update_item
                  target: { entity_id: todo.shopping_list }
                  data:
                    item: "{{ repeat.item.n }}"
                    status: "{{ 'completed' if repeat.item.b else 'needs_action' }}"
      - delay: "00:00:02"
      - service: input_boolean.turn_off
        target: { entity_id: input_boolean.kolichka_syncing }

  # PUSH: промяна в списъка на HA -> кошницата в приложението
  - id: kolichka_push_to_app
    alias: "Количка: списък за пазаруване -> кошница"
    mode: single
    max_exceeded: silent
    trigger:
      - platform: state
        entity_id: todo.shopping_list
    condition:
      - "{{ is_state('input_boolean.kolichka_syncing','off') }}"
    action:
      - service: todo.get_items
        target: { entity_id: todo.shopping_list }
        data: { status: [needs_action, completed] }
        response_variable: cur
      - variables:
          items: "{{ cur['todo.shopping_list']['items'] | default([], true) }}"
          payload: >-
            {"items":[{% for it in items %}{"n":{{ it.summary | to_json }},"b":{{ (it.status == 'completed') | lower }}}{{ "," if not loop.last else "" }}{% endfor %}]}
      - service: rest_command.kolichka_basket_push
        data:
          payload: "{{ payload }}"
```

И веднъж в `configuration.yaml`:

```yaml
homeassistant:
  packages: !include_dir_named packages
```

## Как работи

- **Pull:** REST сензор чете `/api/fambasket/{code}` на всеки 30 сек; автоматизация нанася разликите.
- **Push:** промяна в списъка на HA праща PUT обратно към кошницата.
- input_boolean пазач предотвратява цикъл между двете посоки.

## Family Basket API (публично, без токен)

- `GET /api/fambasket/{code} → {"code","items":[{"n":name,"b":bought}],"count","remaining","bought","remaining_names","updated_at"}`
- `PUT /api/fambasket/{code}  body {"items":[{"n":name,"b":bought}]} → {"ok":true,"updated_at"}`
- `POST /api/fambasket  body {"items":[...]} → creates a new basket, returns {"code"}`

code = нормализира се до малки букви + [a-z0-9]. Отговорът е no-store (не се кешира). Няма нужда от ключ/токен.

Пример:
```bash
curl https://kolichka.gotvach.com/api/fambasket/442834
curl -X PUT https://kolichka.gotvach.com/api/fambasket/442834 \
  -H "Content-Type: application/json" \
  -d '{"items":[{"n":"мляко","b":false},{"n":"хляб","b":true}]}'
```

## ЧЗВ

### Трябва ли ми акаунт или токен?
Не. Кошницата се ползва само с код. API-то е публично и без токен — код-ът е достъпът.

### Колко бързо се синхронизира?
Приложение → Home Assistant: до 30 секунди (REST сензорът чете на всеки 30 сек). Home Assistant → приложение: до няколко секунди на сървъра; в приложението се вижда при следващото отваряне/опресняване на екрана Кошница.

### Списъкът в HA е празен, какво да проверя?
Увери се, че обектът todo.shopping_list не е изключен (Настройки → Устройства и услуги → Обекти) и че кодът в kolichka.yaml съвпада с този в приложението. Провери и че интеграцията „Shopping list“ е добавена.

### Мога ли да ползвам своя код вместо да редактирам файлове?
Да — просто замени ВАШИЯТ_КОД с твоя код в примера. Всичко останало остава същото.

### Работи ли с гласов асистент?
Да. Понеже ползва вградения todo.shopping_list, всичко, което добавиш през Assist/гласов асистент, се синхронизира обратно към приложението.
