diff --git a/automations/update_delivery_times.yaml b/automations/update_delivery_times.yaml
new file mode 100644
index 0000000..6d19edd
--- /dev/null
+++ b/automations/update_delivery_times.yaml
@@ -0,0 +1,25 @@
+alias: Track Rohlik delivery ETA closely before delivery
+description: >-
+ The delivery ETA of an upcoming order shifts a lot, so the regular 10-minute
+ refresh can be off right before the courier arrives. This automation polls
+ the lightweight update_delivery_times action every minute during the last
+ 30 minutes before the expected delivery, keeping the delivery time sensor
+ close to the real ETA.
+mode: single
+triggers:
+ - trigger: time_pattern
+ minutes: "/1"
+conditions:
+ # Only poll when a delivery is expected within the next 30 minutes (also
+ # keeps polling up to 10 minutes past the ETA in case the courier is late).
+ # Replace sensor.rohlik_cz_delivery_time with your own Delivery Time sensor
+ # if it is named differently (e.g. with multiple accounts).
+ - condition: template
+ value_template: >-
+ {% set eta = states('sensor.rohlik_cz_delivery_time') %}
+ {{ eta not in ['unknown', 'unavailable']
+ and -600 <= (as_datetime(eta) - now()).total_seconds() <= 1800 }}
+actions:
+ - action: rohlikcz.update_delivery_times
+ data:
+ config_entry_id: XXXXXXXXXXXXXXXX # Replace with your Rohlik.cz config entry ID
diff --git a/custom_components/rohlikcz/const.py b/custom_components/rohlikcz/const.py
index 09ed0da..7c2d956 100644
--- a/custom_components/rohlikcz/const.py
+++ b/custom_components/rohlikcz/const.py
@@ -59,6 +59,7 @@
SERVICE_FETCH_ORDER_HISTORY = "fetch_order_history"
SERVICE_ENRICH_ORDERS = "enrich_orders"
SERVICE_REFRESH_SLOTS = "refresh_slots"
+SERVICE_UPDATE_DELIVERY_TIMES = "update_delivery_times"
""" Analytics options """
CONF_ANALYTICS = "analytics"
diff --git a/custom_components/rohlikcz/hub.py b/custom_components/rohlikcz/hub.py
index b08e94d..e77e285 100644
--- a/custom_components/rohlikcz/hub.py
+++ b/custom_components/rohlikcz/hub.py
@@ -751,6 +751,22 @@ async def refresh_slots(self) -> None:
self.data["next_delivery_slot"] = result
self.async_update_listeners()
+ async def refresh_delivery_times(self) -> None:
+ """Cheaply refresh only the delivery announcements (delivery-time ETA).
+
+ The announcement carries the shifting delivery ETA for an upcoming
+ order, so this is light enough to poll every minute shortly before a
+ delivery. Updates self.data["delivery_announcements"] in place and
+ notifies entities, without disturbing the rest of the data or the
+ regular refresh cycle.
+ """
+ if not self.data:
+ return
+ result = await self._client.delivery.get_announcements()
+ if result is not None:
+ self.data["delivery_announcements"] = result
+ self.async_update_listeners()
+
async def async_close(self) -> None:
"""Release resources held by the API client (called on unload)."""
try:
diff --git a/custom_components/rohlikcz/icons.json b/custom_components/rohlikcz/icons.json
index a2ac3d8..82df06f 100644
--- a/custom_components/rohlikcz/icons.json
+++ b/custom_components/rohlikcz/icons.json
@@ -4,6 +4,7 @@
"search_product": {"service": "mdi:magnify"},
"get_shopping_list": {"service": "mdi:clipboard-list"},
"get_cart_content": {"service": "mdi:cart"},
- "search_and_add_to_cart": {"service": "mdi:cart-arrow-right"}
+ "search_and_add_to_cart": {"service": "mdi:cart-arrow-right"},
+ "update_delivery_times": {"service": "mdi:truck-fast-outline"}
}
-}
\ No newline at end of file
+}
diff --git a/custom_components/rohlikcz/manifest.json b/custom_components/rohlikcz/manifest.json
index 329dfa1..79221bc 100644
--- a/custom_components/rohlikcz/manifest.json
+++ b/custom_components/rohlikcz/manifest.json
@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/dvejsada/HA-RohlikCZ/issues",
"requirements": ["rohlik-api==0.2.0"],
- "version": "0.6.0"
+ "version": "0.6.1"
}
diff --git a/custom_components/rohlikcz/services.py b/custom_components/rohlikcz/services.py
index a6f322d..8f5be51 100644
--- a/custom_components/rohlikcz/services.py
+++ b/custom_components/rohlikcz/services.py
@@ -12,7 +12,7 @@
from .const import DOMAIN, ATTR_CONFIG_ENTRY_ID, ATTR_PRODUCT_ID, ATTR_QUANTITY, ATTR_PRODUCT_NAME, \
ATTR_SHOPPING_LIST_ID, ATTR_LIMIT, ATTR_FAVOURITE_ONLY, SERVICE_ADD_TO_CART, SERVICE_SEARCH_PRODUCT, SERVICE_GET_SHOPPING_LIST, \
SERVICE_GET_CART_CONTENT, SERVICE_SEARCH_AND_ADD_PRODUCT, SERVICE_UPDATE_DATA, SERVICE_FETCH_ORDER_HISTORY, SERVICE_ENRICH_ORDERS, \
- SERVICE_REFRESH_SLOTS
+ SERVICE_REFRESH_SLOTS, SERVICE_UPDATE_DELIVERY_TIMES
_LOGGER = logging.getLogger(__name__)
@@ -137,6 +137,16 @@ async def async_refresh_slots(call: ServiceCall) -> None:
except Exception as err:
raise HomeAssistantError(f"Failed to refresh slots: {err}")
+ async def async_update_delivery_times(call: ServiceCall) -> None:
+ """Cheaply refresh only the delivery-time announcement (ETA polling)."""
+ config_entry_id = call.data[ATTR_CONFIG_ENTRY_ID]
+
+ account = _get_account(hass, config_entry_id)
+ try:
+ await account.refresh_delivery_times()
+ except Exception as err:
+ raise HomeAssistantError(f"Failed to update delivery times: {err}")
+
async def async_fetch_order_history(call: ServiceCall) -> None:
"""Fetch complete order history from Rohlik."""
config_entry_id = call.data[ATTR_CONFIG_ENTRY_ID]
@@ -241,6 +251,16 @@ async def async_enrich_orders(call: ServiceCall) -> None:
supports_response=SupportsResponse.NONE
)
+ hass.services.async_register(
+ DOMAIN,
+ SERVICE_UPDATE_DELIVERY_TIMES,
+ async_update_delivery_times,
+ schema=vol.Schema({
+ vol.Required(ATTR_CONFIG_ENTRY_ID): cv.string
+ }),
+ supports_response=SupportsResponse.NONE
+ )
+
hass.services.async_register(
DOMAIN,
SERVICE_FETCH_ORDER_HISTORY,
diff --git a/custom_components/rohlikcz/services.yaml b/custom_components/rohlikcz/services.yaml
index 8cd77ab..66c4fa8 100644
--- a/custom_components/rohlikcz/services.yaml
+++ b/custom_components/rohlikcz/services.yaml
@@ -120,6 +120,18 @@ refresh_slots:
config_entry:
integration: rohlikcz
+update_delivery_times:
+ name: Update delivery times
+ description: Cheaply refresh only the delivery-time announcement (one request). The delivery ETA of an upcoming order shifts often, so this is light enough to poll every minute shortly before a delivery to keep the delivery time sensor accurate.
+ fields:
+ config_entry_id:
+ name: Account
+ description: The Rohlik account to use
+ required: true
+ selector:
+ config_entry:
+ integration: rohlikcz
+
search_and_add_to_cart:
name: Search and add to cart
description: Search for a product and add to a shopping cart
diff --git a/readme.md b/readme.md
index 833fd0b..edfdd3f 100644
--- a/readme.md
+++ b/readme.md
@@ -152,19 +152,23 @@ Each sensor's attributes contain the top N items (configurable, default 10) sort
| **`rohlikcz.get_cart_content`** | Get the current contents of your shopping cart |
| **`rohlikcz.update_data`** | Force an immediate full data refresh from Rohlík.cz |
| **`rohlikcz.refresh_slots`** | Cheaply refresh only the delivery-slot data with a single request — light enough to poll every few seconds to catch express availability |
+| **`rohlikcz.update_delivery_times`** | Cheaply refresh only the delivery-time announcement with a single request — light enough to poll every minute to track the shifting delivery ETA |
| **`rohlikcz.fetch_order_history`** | Download your complete order history and store it locally (backfill) |
| **`rohlikcz.enrich_orders`** | Enrich stored orders with item details and product categories to populate the spending sensors |
> [!TIP]
> Want a notification the moment express delivery opens up? See [`automations/refresh_slots.yaml`](automations/refresh_slots.yaml) for an example that polls `refresh_slots` every 15 seconds while armed and notifies you when the **Express Available** sensor turns on.
+> [!TIP]
+> Delivery ETA shifting around before the courier arrives? See [`automations/update_delivery_times.yaml`](automations/update_delivery_times.yaml) for an example that polls `update_delivery_times` every minute during the last 30 minutes before delivery so the **Delivery Time** sensor stays accurate.
+
---
## 🔄 Data Updates
Data is refreshed from Rohlík.cz **every 10 minutes** automatically. The update covers account details, premium status, delivery slots, shopping cart, and order history.
-You can trigger an immediate refresh at any time using the **`rohlikcz.update_data`** action, or refresh just the delivery slots more frequently with **`rohlikcz.refresh_slots`**.
+You can trigger an immediate refresh at any time using the **`rohlikcz.update_data`** action, refresh just the delivery slots more frequently with **`rohlikcz.refresh_slots`**, or refresh just the delivery-time announcement with **`rohlikcz.update_delivery_times`**.
---
diff --git a/tests/test_init.py b/tests/test_init.py
index c0a0a7c..8cb303c 100644
--- a/tests/test_init.py
+++ b/tests/test_init.py
@@ -3,8 +3,9 @@
import json
import os
-from datetime import timedelta
+from datetime import datetime, timedelta
from unittest.mock import AsyncMock, patch
+from zoneinfo import ZoneInfo
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
@@ -209,6 +210,51 @@ async def test_refresh_slots_updates_express_sensor(hass: HomeAssistant) -> None
assert account.data["login"]["data"]["user"]["id"] == 123456
+async def test_update_delivery_times_updates_delivery_time_sensor(hass: HomeAssistant) -> None:
+ """refresh_delivery_times merges fresh announcements and the delivery time sensor reflects it."""
+ data = sample_api_data()
+
+ entry = _entry()
+ entry.add_to_hass(hass)
+ with _patch_get_data(return_value=data):
+ assert await hass.config_entries.async_setup(entry.entry_id)
+ await hass.async_block_till_done()
+
+ ent_reg = er.async_get(hass)
+ delivery_time_id = ent_reg.async_get_entity_id("sensor", DOMAIN, "123456_delivery_time")
+ assert hass.states.get(delivery_time_id).state == "unknown"
+
+ account = entry.runtime_data
+ fresh_announcements = {
+ "data": {
+ "announcements": [
+ {
+ "id": 9002,
+ "title": "Delivery",
+ "updatedAt": "2026-04-26T07:30:00+02:00",
+ "content": (
+ 'Doručíme 26.4.'
+ ' v 08:00'
+ ),
+ }
+ ]
+ }
+ }
+ account._client.delivery.get_announcements = AsyncMock(return_value=fresh_announcements)
+
+ await account.refresh_delivery_times()
+ await hass.async_block_till_done()
+
+ # Only the announcement data changed; the delivery time sensor picks up the ETA.
+ assert account.data["delivery_announcements"] == fresh_announcements
+ state = hass.states.get(delivery_time_id).state
+ assert dt_util.parse_datetime(state) == datetime(
+ datetime.now().year, 4, 26, 8, 0, tzinfo=ZoneInfo("Europe/Prague")
+ )
+ # Other data is untouched.
+ assert account.data["login"]["data"]["user"]["id"] == 123456
+
+
async def test_slot_sensors_registered(hass: HomeAssistant) -> None:
"""The three preselected-slot sensors register and parse their data."""
data = sample_api_data()