- HA sensor: expiring_list now includes full product details (location, brand, category, days_remaining, opened_at, vacuum_sealed, default_quantity, etc.) - HA sensor: new expired_list attribute with full product details per expired item - HA sensor: new low_stock_list attribute (items with quantity ≤ 1, full details) - HA sensor: new sensor=product endpoint (?action=ha_sensor&sensor=product) with optional filters: &id=, &name=, &location= - HA cron webhook: expiry alert items now carry full product details - Inventory edit: confirm dialog when quantity exceeds unit-specific threshold (prevents data loss from unit-confusion typos, e.g. 183 conf instead of 0.183) - Recipe AI: explicit rule against ingredient form substitution (fresh tomatoes ≠ passata, fresh milk ≠ UHT ≠ cream, etc.) - Shelf-life: opened bread rules (piadina 2d, bauletto/pancarrè 4d, pane 3d) - docs/wiki: HA page updated with new schema, examples, product endpoint Closes #125
11 KiB
Home Assistant Integration
EverShelf integrates natively with Home Assistant to bring your pantry data into your smart-home automations.
Capabilities:
- 📡 REST sensors — expose pantry counts as HA sensor entities (expiring, expired, shopping list, total items)
- 🔔 Webhooks — trigger HA automations on pantry events (expiry alerts, shopping additions, stock updates)
- 📣 Push notifications — send alerts to your phone via any HA
notify.*service - 🔊 TTS on smart speakers — read recipe steps aloud on any HA
media_playerentity - ⚙️ In-app config panel — configure everything from Settings → 🏠 tab (no need to edit
.envmanually)
Quick Setup
-
Generate a Long-Lived Access Token in Home Assistant:
- Open HA → your Profile (bottom-left avatar) → Security → Long-Lived Access Tokens → Create Token
- Copy the generated token — you won't see it again.
-
Open EverShelf Settings → tab 🏠 Home Assistant.
-
Fill in Home Assistant URL (e.g.
http://homeassistant.local:8123) and paste the token. -
Click Test connection — you should see ✅.
-
Enable the features you want (TTS, Webhooks, REST Sensors) and click Save HA settings.
REST Sensors
Add EverShelf pantry data as native HA sensor entities that update automatically.
Endpoints
| URL | Returns | Sensor |
|---|---|---|
/api/?action=ha_sensor |
Items expiring soon (≤HA_EXPIRY_DAYS days) |
sensor.evershelf_overview |
/api/?action=ha_sensor&sensor=expired |
Expired items count | sensor.evershelf_expired |
/api/?action=ha_sensor&sensor=shopping |
Shopping list item count | sensor.evershelf_shopping |
/api/?action=ha_sensor&sensor=total |
Total pantry items | sensor.evershelf_total |
/api/?action=ha_sensor&sensor=product |
Full inventory — all items with complete details | sensor.evershelf_products |
/api/?action=ha_sensor&sensor=product&id=42 |
Full details for inventory row id=42 |
— |
/api/?action=ha_sensor&sensor=product&name=milk |
Full details for items whose name contains "milk" | — |
/api/?action=ha_sensor&sensor=product&location=frigo |
All items in a specific location | — |
Generate & Copy YAML
In Settings → 🏠 Home Assistant → REST Sensors card, click Copy YAML to get a ready-to-paste configuration.yaml block that already contains your EverShelf URL.
Manual YAML example
# configuration.yaml
sensor:
- platform: rest
name: "EverShelf Overview"
unique_id: evershelf_overview
resource: "http://YOUR_EVERSHELF_URL/api/?action=ha_sensor"
scan_interval: 300 # seconds
value_template: "{{ value_json.state }}"
json_attributes:
- expiring_soon
- expiring_3d
- expired_items
- total_items
- shopping_items
- expiring_list # full product details for expiring items
- expired_list # full product details for expired items
- low_stock_list # full product details for items with quantity ≤ 1
- next_expiry_name
- next_expiry_date
- days_to_next_expiry
- last_updated
unit_of_measurement: "items"
- platform: rest
name: "EverShelf Shopping Count"
unique_id: evershelf_shopping
resource: "http://YOUR_EVERSHELF_URL/api/?action=ha_sensor&sensor=shopping"
scan_interval: 180
value_template: "{{ value_json.state }}"
unit_of_measurement: "items"
# Full product inventory — each item includes all details (location, brand, category, …)
- platform: rest
name: "EverShelf Products"
unique_id: evershelf_products
resource: "http://YOUR_EVERSHELF_URL/api/?action=ha_sensor&sensor=product"
scan_interval: 600
value_template: "{{ value_json.state }}"
json_attributes:
- items
- last_updated
unit_of_measurement: "items"
Restart Home Assistant after editing configuration.yaml.
Every product entry inside expiring_list, expired_list, low_stock_list, and sensor=product responses follows the same schema:
{
"product_id": 42,
"inventory_id": 7,
"name": "Latte intero",
"brand": "Parmalat",
"category": "Lattiero-caseari",
"quantity": 2.0,
"unit": "conf",
"default_quantity": 1000.0,
"package_unit": "ml",
"location": "frigo",
"expiry_date": "2025-06-15",
"days_remaining": 3,
"opened_at": "2025-06-10",
"vacuum_sealed": false
}
Field details:
| Field | Type | Description |
|---|---|---|
product_id |
int | Products table ID |
inventory_id |
int | Inventory row ID |
name |
string | Product name |
brand |
string|null | Brand (if set) |
category |
string|null | Category (if set) |
quantity |
float | Current quantity in inventory |
unit |
string | Unit (conf, g, ml, pz, …) |
default_quantity |
float | Default package size (e.g. 1000 for 1-litre carton) |
package_unit |
string|null | Unit of the default package (g, ml) |
location |
string|null | Storage location (frigo, freezer, dispensa, …) |
expiry_date |
string|null | ISO date YYYY-MM-DD |
days_remaining |
int|null | Days until expiry (negative = already expired) |
opened_at |
string|null | ISO date when the package was opened |
vacuum_sealed |
bool | Whether the item is vacuum-sealed |
Webhook Automations
EverShelf fires an HTTP POST to your HA webhook URL when pantry events occur.
Create the HA Webhook Automation
- HA → Settings → Automations & Scenes → Create Automation
- Click Add Trigger → choose Webhook
- HA generates a Webhook ID — copy it
- Paste the ID into Settings → 🏠 Home Assistant → Webhook ID
- Select which events should trigger the webhook
Supported Events
| Event key | When it fires |
|---|---|
expiry |
Daily cron — items expiring within HA_EXPIRY_DAYS days |
shopping_add |
Item added to the shopping list |
stock_update |
Inventory quantity changed |
barcode_scan |
(reserved for future use) |
Webhook Payload (POST body)
{
"event": "expiry_alert",
"timestamp": "2025-06-12T08:00:00+00:00",
"data": {
"type": "expiring_soon",
"count": 3,
"days": 3,
"summary": "Milk, Yogurt, Butter",
"items": [
{
"product_id": 42,
"inventory_id": 7,
"name": "Milk",
"brand": "Parmalat",
"category": "Dairy",
"quantity": 2.0,
"unit": "conf",
"default_quantity": 1000.0,
"package_unit": "ml",
"location": "frigo",
"expiry_date": "2025-06-14",
"days_remaining": 2,
"opened_at": "2025-06-10",
"vacuum_sealed": false
}
]
}
}
Example: Expiry Alert → Telegram
alias: EverShelf Expiry Alert
trigger:
- platform: webhook
webhook_id: "evershelf_webhook_abc123" # ← your Webhook ID
action:
- service: notify.telegram_bot
data:
message: >
🥫 EverShelf: {{ trigger.json.data.count }} product(s) expiring soon
{% for item in trigger.json.data.items %}
— {{ item.name }}{% if item.brand %} ({{ item.brand }}){% endif %} ·
{{ item.quantity }} {{ item.unit }} · 📍 {{ item.location }} ·
expires {{ item.expiry_date }} ({{ item.days_remaining }} days)
{% endfor %}
Example: Automation on location
You can filter by location in the automation template to only alert for fridge items:
condition:
- condition: template
value_template: >
{{ trigger.json.data.items | selectattr('location','eq','frigo') | list | length > 0 }}
Push Notifications
If you prefer to receive push alerts without using webhooks, configure a HA notify service directly:
- Find your notify service name in HA: Developer Tools → Services → search
notify - Paste it into Settings → 🏠 → Notify service (e.g.
notify.mobile_app_my_phone) - Save
EverShelf will call this service from the cron job whenever expiry alerts fire.
TTS on Smart Speakers
Read recipe steps aloud on an Amazon Echo, Google Home, Sonos, or any HA media_player.
Configuration
- Enter the Entity ID of your media player (e.g.
media_player.kitchen_display)- Find it in HA: Developer Tools → States
- Click Apply HA preset to TTS tab — this auto-fills the TTS tab with the correct HA endpoint and auth headers
- Save settings
How it Works
When recipe step TTS is triggered, EverShelf calls:
POST /api/services/tts/speak
Authorization: Bearer <HA_TOKEN>
{
"entity_id": "media_player.kitchen_display",
"message": "Add 200 g of flour and mix well."
}
The request is proxied through the EverShelf PHP backend (avoids CORS / mixed-content issues).
Environment Variables Reference
All settings are configurable from .env or from the in-app Settings panel.
| Variable | Default | Description |
|---|---|---|
HA_ENABLED |
false |
Master switch for all HA features |
HA_URL |
(empty) | Base URL of HA instance, no trailing slash |
HA_TOKEN |
(empty) | Long-Lived Access Token |
HA_TTS_ENTITY |
(empty) | media_player entity for TTS |
HA_WEBHOOK_ID |
(empty) | Webhook trigger ID from HA automation |
HA_WEBHOOK_EVENTS |
expiry,shopping_add,stock_update |
Comma-separated list of events |
HA_NOTIFY_SERVICE |
(empty) | HA notify service (e.g. notify.mobile_app_phone) |
HA_EXPIRY_DAYS |
3 |
Days before expiry to trigger the daily alert |
Troubleshooting
Test shows ❌ "Connection failed"
- Verify the URL is reachable from the EverShelf server (not just your browser)
- If using HTTPS with a self-signed certificate, the server-side cURL request may fail — use HTTP on the local network instead
- Check that port 8123 (or your custom port) is open on the HA host
Test shows ❌ "bad_token"
- The Long-Lived Access Token may have expired or been revoked — generate a new one in HA Profile
Webhook not firing
- Confirm HA_ENABLED=true and the Webhook ID is exactly as shown in HA
- Check the EverShelf cron is running (
/api/cron_smart_shopping.phpevery 5 minutes) - For shopping/stock events: verify the event name is in
HA_WEBHOOK_EVENTS
TTS not speaking
- Ensure the media player entity is online in HA (check its state in Developer Tools)
- Try the "Apply HA preset to TTS tab" button and send a test from the TTS tab
- Check HA logs for
tts.speakerrors (some platforms requiretts_options)
Sensors show unavailable in HA
- The EverShelf URL must be reachable from the HA host
- If running EverShelf behind a reverse proxy, ensure
/api/is accessible - Use
scan_interval≥ 60 to avoid hammering the server