Menu Refresh and Rendering
Audience: User, Org Admin, Deployment Administrator, Developer Related: Overview · Eink Rendering · Configuration · Background Jobs And Schedules
Menu mode renders an organization’s configured menu source for supported displays. Scheduled refresh performs a lightweight source probe, skips expensive browser rendering when unchanged, groups compatible display work, and publishes display-specific output.
A missing menu URL is a configuration condition, not a reason to launch Playwright. Noun Project footer rules and e-ink render settings are applied through the current organization and render profile.
Blackcap Pi supports two menu-source styles:
- published Google Sheets, especially
pubhtmlmenu sheets; - compatible rich HTML webpages that can be captured by Playwright.
Playwright remains the authoritative image-export path, but the scheduled hourly refresh now performs a lightweight source probe before launching a browser. This keeps Raspberry Pi Zero 2 deployments responsive while preserving accurate rendering when a menu actually changes.
Scheduled refresh flow
Unbound inky_menu.py is the menu refresh dispatcher. It no longer assumes the default organization or the default display.
- Find active displays whose current content is Menu.
- Group them by organization.
- Probe the organization's configured menu URL once.
- If the source fingerprint matches the last successfully rendered baseline, skip that organization without launching Playwright.
- If the source changed, the probe is uncertain, or
--full-refreshis used, render by organization + effective render profile. - Compare/update each display using its own display-preview artifacts.
A successful unchanged run should show 0 browser capture(s) in the summary.
Source probe state
The source probe records hidden org-scoped JSON under menu.source_probe_state. The setting is registered by migration 065_menu_source_probe_state.sql and is accessed through the settings/DB service layer.
The JSON stores details such as:
- menu URL;
- probe URL when a stable alternate is available;
- HTTP status/ETag/Last-Modified/Content-Length when provided;
- body length/hash;
- normalized source fingerprint;
- check timestamps;
- last rendered baseline.
Published Google Sheet pubhtml URLs use a stable spreadsheet-style fingerprint where possible because Google's raw published HTML wrapper can change between requests even when the visible menu does not. Generic HTML pages use normalized visible text so layout wrapper noise is less likely to trigger a false refresh.
Full refresh
--full-refresh bypasses the skip decision and runs the Playwright/render path, but it still records the current source-probe JSON. This gives the next smart refresh a current baseline.
/home/pi/inky_env/bin/python3 /home/pi/Blackcap-Pi/inky_menu.py --full-refresh
Grouped rendering
When rendering is required, displays are grouped by organization and effective render profile. Displays that differ in resolution, color support, image support, display type, or output profile do not share the same final image.
Examples:
960x680B&W e-ink target;1920x1080color hosted receiver;1280x720color mock/rich preview.
The browser capture and image processing honor the target profile so a 1920x1080 color display does not receive a small B&W e-ink image.
Noun Project footer rules
Menu footer matching uses organization-scoped rows in noun_project_rules. The old noun_cache/NounProjectWords.csv file is no longer active configuration. noun_cache/ remains a filesystem cache for downloaded icon assets only, with child organizations using folders such as noun_cache/org_<organization_id>/. Schema migration 201 backfills the standard rule set when the Default organization has no rows, covering installations that had already advanced beyond the earlier foundation-seed migration.
Footer matching example
For a menu such as:
Wednesday: Meatball Sandwich with Salad
Saturday: Swedish Meatballs and Egg Noodles with Salad
Sunday: Chicken Wraps with Chips
OCR and normalization may identify useful concepts such as chicken, bread, sandwich, noodles, and salad. Organization-scoped rules map approved words or patterns to Noun Project search terms. Unknown/noise words are skipped, icons are cached after first use, and the footer is rebuilt from the current matching rules.
Footer placement can be Menu + Meal Plan, Menu only, Meal Plan only, or None. Organization admins can manage their organization’s keyword rules without seeing the shared platform API credential. The Default organization uses the root noun_cache/; child organizations use scoped folders and may reuse compatible cached icons from the Default cache.
Menu assignment versus menu refresh
Assigning Menu content from the UI behaves like Return to Default when Menu is the configured default content. It restores the last rendered menu image for that display instead of running smart refresh. Scheduled inky_menu.py is responsible for checking whether the menu source changed and updating displays currently showing Menu.
Locking
Top-level inky_menu.py runs create a menu-priority request and acquire the shared scheduler lock. This includes cron/unbound runs and UI/manual bound runs such as:
/home/pi/inky_env/bin/python3 /home/pi/Blackcap-Pi/inky_menu.py --org-id org_... --display-id disp_...
Child workers launched by a locked parent run with BLACKCAP_SCHEDULER_LOCK_HELD=1 so they do not deadlock their parent. The display-content refresh runner yields while menu refresh is pending or active. The e-ink hardware lock remains separate and is still required around local display writes.