Skip to content

feat: add refresh_slots action for lightweight express-slot polling#68

Open
MangelSpec wants to merge 2 commits into
dvejsada:masterfrom
MangelSpec:master
Open

feat: add refresh_slots action for lightweight express-slot polling#68
MangelSpec wants to merge 2 commits into
dvejsada:masterfrom
MangelSpec:master

Conversation

@MangelSpec

@MangelSpec MangelSpec commented Jun 18, 2026

Copy link
Copy Markdown

Summary

Express availability (the IsExpressAvailable binary sensor) only updates on the 600s SCAN_INTERVAL, so it can be up to ~10 minutes stale.

The concrete motivation: in my Home Assistant setup I have a "notify when express available" toggle that pushes a notification to my phone the moment express becomes available, so I can grab a slot. Express slots go fast, and a refresh cadence of up to 10 minutes was far too slow; the notification often arrived after the slots were already gone.

The existing update_data service can force a refresh, but it does a full login, ~10 endpoint GETs, the cart, and a logout (about 12 requests), so polling it every few seconds to catch express in time would hammer the server.

This adds a dedicated rohlikcz.refresh_slots action that fetches only the timeslot endpoint, cheap enough to poll every ~15s (in my case only while the alert is armed) so the notification fires within seconds instead of minutes. The included example automation shows exactly this pattern.

What changed

Feature:

  • RohlikCZAPI.get_timeslots(): a single GET to the timeslots-api endpoint (the same URL get_data already builds for next_delivery_slot).
  • A reused, logged-in requests.Session, so a slot check is one request instead of login + GET + logout. It re-authenticates only on HTTP 401 and retries once on a transient connection reset (a stale keep-alive connection closed by the server between polls). Access is serialized with an asyncio.Lock, and the session is closed on unload.
  • RohlikAccount.refresh_slots(): merges the result into self.data["next_delivery_slot"] (it does not replace self.data) and publishes updates, so the express sensor refreshes while every other sensor keeps its value.
  • Service registration (config_entry_id required, SupportsResponse.NONE), a services.yaml entry, and an example automation in automations/refresh_slots.yaml.

get_data() is left unchanged and stays stateless; only refresh_slots uses the persistent session. No DataUpdateCoordinator refactor; this is intentionally minimal and follows existing patterns.

Small fixes found along the way (kept in a separate commit):

  • tests/test_order_store.py: asserts were indented outside the TemporaryDirectory block, so the test raised FileNotFoundError; re-indented and dropped the redundant manual cleanup.
  • rohlik_api.py: search_product called login() twice.
  • services.py: "Failed to get get cart content" doubled-word typo.
  • hub.py: removed unused cast and List imports.
  • sensor.py: removed a dead, shadowed import datetime.
  • errors.py: APIRequestFailedError docstring was copied verbatim from AddressNotSetError.
  • todo.py: completed a truncated add-to-cart error log and lowered a successful-delete log from error to debug.

Test plan

  • Integration loads with no errors and all entities populate (confirms the removed imports were unused).
  • rohlikcz.refresh_slots appears in Developer Tools and runs with no error.
  • With urllib3.connectionpool debug logging, refresh_slots performs a single GET to timeslots-api, versus update_data doing the full login + endpoints + logout.
  • After refresh_slots, the express binary sensor reflects current availability while cart/premium/other sensors keep their values (confirms the data merge, not a replace).
  • Reloaded the integration with no leaked-session errors (confirms the session is closed on unload).
  • Observed a transient "Connection reset by peer" from a stale keep-alive connection; the added one-shot retry recovers without surfacing it.

- tests/test_order_store.py: re-indent the persistence and dedup asserts back inside the TemporaryDirectory block so the temp file still exists when written, and drop the redundant manual cleanup the context manager already handles (the test raised FileNotFoundError before)
- rohlik_api.py: remove the duplicate login() call in search_product, which authenticated twice on every search
- services.py: fix the doubled word in the "get get cart content" error message
- hub.py: drop the unused cast and List typing imports
- sensor.py: remove the dead, shadowed "import datetime" (the name is rebound by "from datetime import ... datetime")
- errors.py: fix APIRequestFailedError docstring, which was copied verbatim from AddressNotSetError
- todo.py: complete the truncated add-to-cart error log and lower the successful-delete log from error to debug level
- rohlik_api.py: add get_timeslots() that fetches only the timeslot endpoint, plus a reusable logged-in session (_ensure_session) that re-authenticates only on HTTP 401, so a slot check is a single request instead of login + GET + logout
- rohlik_api.py: serialize access to the reused session with an asyncio.Lock and add close_session(); get_data() stays stateless to avoid sharing a Session across the slow full refresh and the fast poll
- hub.py: add RohlikAccount.refresh_slots(), which merges into self.data (does not replace it) and publishes updates so the express binary sensor refreshes without a full data cycle; add async_close() to release the session
- __init__.py: close the reused session on config-entry unload
- const.py / services.py / services.yaml: register the rohlikcz.refresh_slots service (config_entry_id required, SupportsResponse.NONE)
- automations/refresh_slots.yaml: example automation showing on-demand express polling with the new action
@dvejsada

Copy link
Copy Markdown
Owner

@MangelSpec Thanks for the PR, looks good. I will review and merge if no issues found.

@dvejsada

Copy link
Copy Markdown
Owner

@claude review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants