Releases: dennisadvani/metixel-photoframe
Releases · dennisadvani/metixel-photoframe
Release list
v1.1.0
Image Download Links Below
- Instructions
- IMG can be used for Raspberry Pi 3+
- For Raspberry Pi 2, follow the manual installation instructions as Trixie 13 Lite 32-bit is required
[1.1.0]
Added
- GPU memory introspection —
DisplayBackend.gpu_memory_info()and
flush_gpu()methods.Pi3dBackendreadsvcgencmd get_memand
DRMbo_statsdebugfs for V3D buffer object counts and heap usage.
Periodic GPU memory logged every 30 s alongside CPU/memory stats.
GPU state logged on texture allocation failure for diagnostics. - Guaranteed no‑black‑screen video architecture — the last‑frame texture
is fully loaded, uploaded to the GPU, and verified BEFORE VLC is launched.
SeeARCHITECTURE.md→ "Video Playback Architecture" for the 8‑step
state machine diagram. - VLC RC TCP playback detection — VLC is launched with
--extraintf rc --rc‑host localhost:<port>(LUA CLI).is_playingnow queries VLC's
TCP interface for a real "is rendering" signal instead of guessing with
timers. Supports Pi 2's slow VLC startup without premature swap. - Centralised timeout configuration — new
timeoutssection in
config.jsonwithConfig.timeout(key, fallback)helper. All
critical timeouts (ffprobe, frame extraction, thumbnail generation,
transcode, VLC start) now editable in one place.
Changed
- GPU memory raised to 128 MB for Pi 2/3 — setup script now detects Pi
model and setsgpu_mem=128for Pi ≤3 (static GPU partition needs room
for framebuffer ~8 MB + pi3d textures ~4 MB each). Pi ≥4 stay at
gpu_mem=16(CMA dynamic allocation). - Timeout increases across the board for CPU‑starved Pi 2/3 hardware:
ffprobe metadata probe 30→120 s, cached‑video validation 15→60 s,
thumbnail extraction 120→300 s, first‑frame extract 60→180 s, HW codec
detection 10→30 s, VLC start 5→30 s. - Last‑frame swap timer now starts from VLC's confirmed playback time
(via RC interface), not subprocess launch. Eliminates the swap‑before‑
VLC‑appears race on slow hardware.
Fixed
- Black last‑frame screen (root cause) — pi3d
Texture(file_path)does
NOT eagerly create the GL texture (opengl_loaded=False). You must
calltex.load_opengl()followed byglFinish()(ctypes → libGLESv2)
to drain the VideoCore IV DMA pipeline before pi3d'sfree_after_load
releases the CPU buffer. Without the flush, DMA reads freed memory →
black pixels. _load_texture_for_slotunload‑before‑load — the old texture was
destroyed before the new one was confirmed loaded. If the new load
failed (GPU memory full), the slot went permanently black. Now loads
first, only unloads old on success.- Cache hash mismatch —
_cleanup_cached_videoused a path‑based
hash to find frame files, but_extract_video_framesnamed them with
a content‑based hash. Frame files were never cleaned up on re‑transcode.
Both now use the content hash (file_hash). - Orphaned frame files never deleted — folder watcher's
_cleanup_cached_for_deletedwas missing the.jpgextension when
looking for{hash}.1.framefiles (should be{hash}.1.frame.jpg). - Thumbnails deleted on cache invalidation — folder watcher cleanup
was deleting thumbnails alongside cached videos. Thumbnails now survive
cleanup; they're only ~50 KB and regenerating them on every re‑transcode
wastes CPU. _validate_cached_videocrash — still decorated@staticmethod
after addingself._timeout()call, causingNameErroron every
invocation. All three precached videos silently failed validation and
never reached the playlist.free_after_loadkwarg conflict —load_texture()hardcoded
free_after_load=Truewhile the engine passedfree_after_load=False
via**kwargs, causingTypeError: multiple values.load_texture
now pops the kwarg to let callers override the default.gpu_mem=16on Pi 3 — the setup script was applying 16 MB GPU
memory to Pi 3 (which uses a static partition), leaving only 8 MB for
textures after the framebuffer. Videos rendered as black because the
GPU couldn't allocate texture memory.- Keyboard defaults — KEY_UP / KEY_DOWN removed (redundant), KEY_RIGHT
corrected fromprev→next, KEY_SPACE and KEY_POWER removed.
Defaults now: KEY_LEFT → prev, KEY_RIGHT → next, KEY_ENTER → toggle.
Changed (2026-08-10)
gpu_mem=128for all Pi models — setup script simplified: Pi 4/5
ignoregpu_memvia CMA dynamic allocation, so a single value avoids
model‑detection complexity and keeps the base image portable across all
Pi generations.requirements-system.txtnow complete — lists all 24 apt packages
from the setup script (grouped by purpose) so the OTA updater can
install any missing system dependencies after an update.
Added
- Per‑profile CRF field —
crfis now a first‑class profile setting
(Pi 2/3:28for software decode, Pi 4/5:23for hardware decode).
Exposed in the API, Web UI profile fields (locked for built‑in profiles,
editable in Custom mode), and config astranscode_crf. - Diagnostic logging in
needs_optimisation()— every check that
triggers a transcode now logs exactly which limit was exceeded (codec,
width, height, fps, bitrate, color depth, HDR, H.264 level) at INFO
level for easy troubleshooting. - Workstation precache script —
scripts/precache_videos.py
transcodes videos on a fast desktop using the exact same profile,
hash, and encoding logic as the Pi, then pushes results via SSH.
Supports--hostto pull media,--pushto deploy. _VIDEO_WAITINGstate in the video state machine — the 50 %
last‑frame swap timer now starts after VLC confirms it has begun
rendering (MediaPlayerPlayingevent), not at launch. Prevents
black frames when VLC startup is delayed by CPU contention.
Changed
- CRF replaces bitrate‑targeted encoding — Pi 2/3 profiles now use
-crf 28(was-b:v 8MABR). CRF distributes bits intelligently
across simple and complex scenes, producing more decode‑friendly output
than constant‑bitrate ABR. - Pi 2/3 max bitrate lowered from
8 → 7Mbps for more headroom
below the ~8 Mbps ARM software decode ceiling. - FPS always explicit —
-ris now always set to
min(source_fps, max_fps), preventing ffmpeg from silently
upscaling 23.98 fps → 29.97 fps. - B‑frames restored — removed
-bf 0from transcode command.
B‑frames break the P‑frame dependency chain and are actually easier
to decode in software than a chain of pure P‑frames. - Max bitrate capped to source quality —
-maxratenow uses
min(source_bitrate, profile_max)so a 5.5 Mbps source never
gets upscaled to the 7 Mbps profile cap. - Frame extraction downscaled — thumbnails, first frames, and last
frames are now downscaled to the display resolution (same as image
optimisation) instead of being extracted at the source's native 4K
resolution, saving ~20 MB GPU memory per texture on low‑RAM Pis. - Frame file extension —
.1.frame/.2.frame→.1.frame.jpg/
.2.frame.jpgfor consistency with JPEG content. - Last‑frame swap at 50 % of video duration (was 20 %, then 80 %)
— balances VLC startup time with completing before VLC exits. - Web UI quality slider removed — replaced by the per‑profile CRF
numeric field in the profile settings section.
Fixed
- H.264 level comparison — ffprobe returns level as integer (
40
for Level 4.0) but the profile stored it as string"4.0", causing
float(40) > float("4.0")to always be true. Probe now normalises
to float (40 → 4.0). - Folder watcher silently dropping videos —
ffprobetimeout was
10s, too short for a CPU‑starved Pi 2; raised to120s.
Failures now log at WARNING level instead of DEBUG. - Thumbnail cache deleted on re‑transcode —
_cleanup_cached_video
was deleting thumbnails alongside corrupt cached videos; thumbnails
now survive cache invalidation. - Frame extraction throttling reverted — single‑frame extraction
(thumbnails, first/last frames) now usesniceonly (no
cpulimit) to avoid 120 s timeouts on slow hardware. - Last‑frame texture load race — the old GPU texture is now kept
until the new one is confirmed loaded, preventing a black screen if
the upload fails. - ffmpeg 8.x compatibility —
-vframesordering (after-i),
-update 1as muxer option (after-f image2),-f mjpeg
for single‑frame output. - Media route filter —
.frame.jpgextension recognised for
exclusion from media listings.
v1.0.12-beta.5
[1.0.13-beta.6]
Added
- GPU memory introspection —
DisplayBackend.gpu_memory_info()and
flush_gpu()methods.Pi3dBackendreadsvcgencmd get_memand
DRMbo_statsdebugfs for V3D buffer object counts and heap usage.
Periodic GPU memory logged every 30 s alongside CPU/memory stats.
GPU state logged on texture allocation failure for diagnostics. - Guaranteed no‑black‑screen video architecture — the last‑frame texture
is fully loaded, uploaded to the GPU, and verified BEFORE VLC is launched.
SeeARCHITECTURE.md→ "Video Playback Architecture" for the 8‑step
state machine diagram. - VLC RC TCP playback detection — VLC is launched with
--extraintf rc --rc‑host localhost:<port>(LUA CLI).is_playingnow queries VLC's
TCP interface for a real "is rendering" signal instead of guessing with
timers. Supports Pi 2's slow VLC startup without premature swap. - Centralised timeout configuration — new
timeoutssection in
config.jsonwithConfig.timeout(key, fallback)helper. All
critical timeouts (ffprobe, frame extraction, thumbnail generation,
transcode, VLC start) now editable in one place.
Changed
- GPU memory raised to 128 MB for Pi 2/3 — setup script now detects Pi
model and setsgpu_mem=128for Pi ≤3 (static GPU partition needs room
for framebuffer ~8 MB + pi3d textures ~4 MB each). Pi ≥4 stay at
gpu_mem=16(CMA dynamic allocation). - Timeout increases across the board for CPU‑starved Pi 2/3 hardware:
ffprobe metadata probe 30→120 s, cached‑video validation 15→60 s,
thumbnail extraction 120→300 s, first‑frame extract 60→180 s, HW codec
detection 10→30 s, VLC start 5→30 s. - Last‑frame swap timer now starts from VLC's confirmed playback time
(via RC interface), not subprocess launch. Eliminates the swap‑before‑
VLC‑appears race on slow hardware.
Fixed
- Black last‑frame screen (root cause) — pi3d
Texture(file_path)does
NOT eagerly create the GL texture (opengl_loaded=False). You must
calltex.load_opengl()followed byglFinish()(ctypes → libGLESv2)
to drain the VideoCore IV DMA pipeline before pi3d'sfree_after_load
releases the CPU buffer. Without the flush, DMA reads freed memory →
black pixels. _load_texture_for_slotunload‑before‑load — the old texture was
destroyed before the new one was confirmed loaded. If the new load
failed (GPU memory full), the slot went permanently black. Now loads
first, only unloads old on success.- Cache hash mismatch —
_cleanup_cached_videoused a path‑based
hash to find frame files, but_extract_video_framesnamed them with
a content‑based hash. Frame files were never cleaned up on re‑transcode.
Both now use the content hash (file_hash). - Orphaned frame files never deleted — folder watcher's
_cleanup_cached_for_deletedwas missing the.jpgextension when
looking for{hash}.1.framefiles (should be{hash}.1.frame.jpg). - Thumbnails deleted on cache invalidation — folder watcher cleanup
was deleting thumbnails alongside cached videos. Thumbnails now survive
cleanup; they're only ~50 KB and regenerating them on every re‑transcode
wastes CPU. _validate_cached_videocrash — still decorated@staticmethod
after addingself._timeout()call, causingNameErroron every
invocation. All three precached videos silently failed validation and
never reached the playlist.free_after_loadkwarg conflict —load_texture()hardcoded
free_after_load=Truewhile the engine passedfree_after_load=False
via**kwargs, causingTypeError: multiple values.load_texture
now pops the kwarg to let callers override the default.gpu_mem=16on Pi 3 — the setup script was applying 16 MB GPU
memory to Pi 3 (which uses a static partition), leaving only 8 MB for
textures after the framebuffer. Videos rendered as black because the
GPU couldn't allocate texture memory.
v1.0.11-beta.4
[1.0.11-beta.4]
Changed
- Software video decode strategy — Pi 2/3 now use CPU software decode
(libVLC) instead of GPU hardware decode.gpu_memreturned to
16MB (1.0.10-beta.3 briefly raised it to128) so more RAM is
available to the ARM cores. Pi 4/5 are unaffected — they continue to
use hardware decode viarpi-hevc-dec/drm_avcodec. - Pi 3 transcode bitrate —
max_bitratelowered from20→
8Mbps, matching the ~8 Mbps software decode ceiling of the
Cortex‑A53 cores (measured: 5.6 & 7.7 Mbps play smoothly, ≥10.9 Mbps
drops frames). - Bitrate‑targeted encoding for Pi 2/3 — profiles with
bitrate_target: truenow use-b:v {max_bitrate}M(target
average bitrate) instead of-crf. CRF with-maxrateonly caps
peaks — the average can still overshoot by 30–50 %, exceeding the
software decode ceiling.-b:vproduces predictable output at the
target rate. - Setup script —
gpu_mem=16for all Pi models, enforced on both
fresh installs and existing configs regardless of prior value.
Fixed
- Pi 3 choppy video at 10–22 Mbps — CRF‑encoded files for
14947567(10.9 Mbps) and17815074(21.5 Mbps) regularly
exceeded the ARM software decode ceiling (~8 Mbps), causing frame
drops. Bitrate‑targeted encoding reliably produces ≤8 Mbps output. - Inconsistent
gpu_memon upgraded installs — the 1.0.10-beta.3
setup script would upgradegpu_memto128on existing installs;
now reverted to16.
v1.0.10-beta.3
[1.0.10-beta.3]
Fixed
- Pi 3 hardware video decode broken by low GPU memory — the setup
script was settinggpu_mem=16MB which prevented VideoCore IV from
loading the H.264 codec firmware, forcing VLC into 100 % CPU software
decode even with correctly‑transcoded Level 4.0 files. Raised to
gpu_mem=128MB for Pi 2/3 (Pi 4/5 use kernel‑managed CMA and don't
need this). - Infinite re‑transcode loop on Pi 5 — CRF encoding produced files
~3 Mbps above the profile bitrate limit, triggering a re‑transcode on
every reboot that produced the same overshoot.needs_optimisation()
now allows 10 % tolerance on bitrate checks. - Frame‑extraction ffmpeg processes not throttled — first‑frame,
last‑frame, and thumbnail extraction ran underniceonly, ignoring
the CPU throttle setting. Now uses_wrap_with_throttle()so they
also getcpulimitwhen CPU throttling is enabled. - Missing
gpu_memon Pi 4/5 Trixie images — some images ship
without anygpu_memline inconfig.txt; the setup script now
addsgpu_mem=128when the setting is absent.
Changed
- Setup script GPU memory —
gpu_mem=128(was16) for new
installs; existing installs are auto‑upgraded from<128or have
the setting added if missing entirely
v1.0.9-beta.2
[1.0.9-beta.2]
Added
- USB keyboard / wireless remote input handler — evdev-based listener
with learn mode for custom key mapping; supportsnext,prev,
pause,resume,toggle_pause,screen_on, and
screen_offcommands; mappings persisted inconfig.jsonunder
input.keyboard_map - Keyboard/Remote Control card in Web UI Advanced page — per‑command
learn and clear buttons with live key‑code display; learn mode polls
for the next keypress and persists the mapping automatically toggle_pauseIPC command — single‑key pause/resume toggle for
keyboard remotes and the Web UI control endpoint; frontend shows a
brief "Paused" / "Resumed" feedback popup on the frame display_show_feedback()helper in the frontend renderer — on‑screen
popup messages for pause, resume, and toggle_pause actions using the
existing message layer- OTA system package support — new
requirements-system.txtlists
required system packages (python3-evdev);UpdateManager
installs any missing packages before the pip step during OTA updates inputconfig section —keyboard_enabledtoggle and
keyboard_mapdictionary for storing learned key bindings
Changed
- Web UI polling —
dashboard.jsswitched fromsetIntervalto
setTimeoutchains for dashboard refresh, sync status, log viewer,
and processing progress; prevents request‑queue buildup when the
browser tab is backgrounded on mobile screen_on/screen_offrenamed — display power commands
renamed from legacy names across all input handlers (CEC, IR,
keyboard, MQTT), IPC protocol, frontend renderer, and the Web UI
control endpoint for consistent naming- Pi 3 H.264 Level lowered —
h264_levelreduced from4.2to
4.0in the Pi 3 transcoding profile to stay within VideoCore IV
hardware decode limits (max Level 4.1) - Keyboard handler thread — started by the backend daemon alongside
other input handlers; usesselectors‑based blocking I/O instead of
busy‑polling so CPU usage is near‑zero when no keys are pressed
Fixed
- Pi 3 video stuttering / 100 % CPU — videos transcoded at H.264
Level 4.2 exceeded VideoCore IV hardware decoder capabilities, causing
VLC to fall back to software decode on all 4 cores; re‑transcoding at
Level 4.0 enables hardware decode (~5–10 % CPU) - Keyboard learn clear button — clearing a key mapping via the Web UI
now properly removes the defaults for that command instead of wiping
all default key bindings;set_key_map()merges config overrides
with defaults, treating an empty list as "clear this command"
v1.0.8-beta.1
[1.0.8-beta.8]
Added
- Transcoding profiles — four Pi‑model‑specific profiles (Pi 2, Pi 3,
Pi 4, Pi 5) with optimal codec, resolution, framerate, bitrate, H.264
profile/level, colour depth, and HDR support limits. Profile is
auto‑detected on first run from/proc/device-tree/model. - Custom profile mode — allows overriding every transcode parameter
individually; all profile fields visible in the Web UI, editable only
when Custom is selected - Keep Audio global setting — preserves the audio track when enabled
(stripped by default) - Profile‑based cached‑video re‑validation — switching profiles
re‑probes existing cached videos and re‑transcodes any that exceed the
new limits - Extended video metadata extraction — ffprobe now captures framerate,
bitrate, colour depth, H.264 profile/level, and HDR colour info - Profile‑based optimisation gating —
needs_optimisation()checks
all profile limits (codec, resolution, fps, bitrate, colour depth, HDR,
H.264 level) instead of only H.264 + resolution - Web UI Video Optimisation card — profile dropdown with auto‑detect,
custom parameter fields, keep‑audio checkbox, and global quality/encoder/
timeout/CPU‑limit controls
Changed
- Transcode encodes to profile target codec — Pi 4/5 target H.265
(HEVC) for hardware decode; Pi 2/3 target H.264 - Transcode enforces H.264 level/profile — adds
-level,
-profile:v,-refs 2,-bf 0,-g 30for smooth Pi
playback - Transcode framerate cap —
-ronly applied when source FPS
exceeds the profile limit; never upscales 30 fps → 60 fps - Transcode colour depth cap — output depth is
min(src, profile);
never upscales 8‑bit → 10‑bit - Transcode HDR → SDR downgrade — forces BT.709 colour space on
non‑HDR‑capable Pi models - Transcode max bitrate enforcement —
-maxrate+-bufsize
applied from profile limits - libx265 RAM optimisation — uses
ultrafastpreset on ≤3 GB
devices (Pi 4/5 with 2 GB) to avoid OOM;superfaston >3 GB - CPU throttle default — reduced from 200 % to 100 % (1 core)
- Playlist hot‑reload refreshes metadata — frontend now updates
width,height,first_frame_path,last_frame_path, and
cached_pathfor existing items when the backend updates the
playlist, fixing aspect‑ratio mismatches and black frame glitches after
re‑transcode
Fixed
- Next‑item video playback — pressing Next to a video now launches VLC
immediately instead of sitting on the first frame until the slide timer
expires config.example.jsonvideo defaults —playback_enabled
corrected totrue,max_duration_secondsto0, CPU throttle
to100
v1.0.7
Image Download Links Below
- Instructions
- IMG can be used for Raspberry Pi 3+
- For Raspberry Pi 2, follow the manual installation instructions as Trixie 13 Lite 32-bit is required
[1.0.7]
Fixed
- Welcome messages not auto-dismissing — the message layer timer was
being reset every frame while a video played, preventing the 2‑minute
auto-dismiss from ever firing; now tracks accumulated visible time with
proper pause/resume during video playback - Web UI welcome dismiss not clearing on‑screen messages — dismissing
the welcome banner in the dashboard now sendsdismiss_all_messages
IPC to the frontend so the frame display popups disappear immediately config.example.jsonvideo defaults —playback_enabled
corrected fromfalsetotrueandmax_duration_secondsfrom
120to0(unlimited), matching the Python defaults
Added
- User Guide — new
docs/USER_GUIDE.mdcovering first‑time setup,
getting online, adding media, the dashboard, customisation, updates, and
troubleshooting
v1.0.6
Image Download Links Below
- Instructions
- IMG can be used for Raspberry Pi 3+
- For Raspberry Pi 2, follow the manual installation instructions as Trixie 13 Lite 32-bit is required
Key Fixes
- Fixed WiFi, Captive Portal and Access Point issues.
Change Log
[1.0.5-beta.5]
Added
- Pi 2 / Ethernet-only support —
is_wifi_hardware_present()check prevents the controller from attempting AP activation on devices without WiFi hardware; the controller stays inCLIENT_CONNECTEDorCLIENT_DISCONNECTEDbased on Ethernet state and never retries AP - Stale AP cleanup on boot — the controller now kills any leftover hostapd instance on initialisation, preventing the captive portal from blocking the web dashboard after an unclean shutdown
Changed
- AP startup delay eliminated — removed the daemon's forced
ap_timeout_secondswait on boot; the controller now owns all timing (immediate AP when no saved networks, 5‑minute grace period when saved WiFi exists) - Boot screen message timing — 10 s delay after slideshow start ensures the boot screen fade-out completes before welcome or PIN messages appear
- Setup script prompts before install — channel and WiFi country questions are now asked before git is installed; answers flow through to Phase 1 via environment variables so the user is never re‑prompted
- Beta channel pins to pre-release tags — the setup script now checks out the latest
v*-beta.*tag onmainfor beta channel installs instead of tracking thedevbranch, matching the OTA updater's behaviour
Fixed
- Captive portal blocking dashboard after reboot — hostapd left running from a previous AP session is now stopped at controller init
[1.0.4-beta.4]
Added
- WiFi connection failed popup — when the captive portal WiFi connection fails (wrong password, out of range, etc.), an error message now appears on the photo frame display with guidance to check the password or try a different network (30 s auto-dismiss)
- WiFi connection profile fallback — if the one-shot
nmcli device wifi connectfails with a "key-mgmt property is missing" error (common on routers with mixed WPA2/WPA3 or certain TP-Link/ASUS models), the code now creates an explicit connection profile with WPA2-PSK settings and retries automatically - Setup script channel prompt — asks for
stableorbetachannel before installing; stable pins to the latest non-prereleasev*tag, beta tracks thedevbranch - Setup script WiFi country prompt — asks for the regulatory domain (e.g.
AU,US,GB) upfront alongside the channel choice; both answers are written toconfig.jsonfor the Web UI
Changed
- Boot welcome delay eliminated — the network monitor no longer waits
ap_timeout_secondswhen a network is already connected at boot; the welcome message appears as soon as the slideshow is ready instead of 60+ seconds later - Captive portal error messaging — the WiFi failure popup on the frame display uses a single clean message instead of concatenating the raw nmcli error with boilerplate text
Fixed
- Setup script CRLF line endings —
.gitattributesnow enforceseol=lffor*.shfiles; the setup script was renormalized so it runs correctly when downloaded from GitHub raw on a Pi
[1.0.3-beta.3]
Changed
- Network Controller rewritten —
NetworkPhaseflag-based state machine replaced withNetworkStateenum (CLIENT_CONNECTED,CLIENT_DISCONNECTED,AP_ACTIVE,AP_EXHAUSTED); all transitions go through a single_transition_to()method under lock; monitor loop drains a pending-actions queue instead of comparing phase snapshots. Ethernet connectivity is checked independently from WiFi and is always safe (different radio) — nmcli is never queried for WiFi while the AP is active, preventing hostapd beacon disruption. - WiFi connection deferred to background thread — the captive portal's
/api/network/connectendpoint now returns a response immediately (before the AP is torn down) and spawns a background thread for the actual AP-stop + scan + nmcli-connect sequence. The phone receives the HTTP response while still associated with the AP. - WiFi scan delay after AP stop —
connect_to_network()now performs anmcli device wifi rescanand waits 5 s after stopping the AP before attempting the connection. Without a fresh scan wlan0 has no visible SSID list and nmcli fails with "No network with SSID 'X' found." - Controller connection guard — new
begin_connection()/end_connection()methods prevent the monitor thread from treating an intentionally-stopped AP as "unexpectedly dead" during the scan gap - AP_EXHAUSTED sudo reduction —
_stop_ap()now guarded byis_ap_mode_active()check; no longer runs unnecessary sudo commands on every monitor tick when the AP is already down
Fixed
- WiFi connection failing after captive portal — the AP was torn down during the HTTP request, severing the client's TCP connection before the response arrived; the phone showed a network error instead of success
- "WiFi Offline" popup flashing during WiFi connection — the monitor tick saw the AP was down during the scan delay and marked it as
AP_EXHAUSTED, triggering an on-screen warning that was dismissed seconds later when the connection succeeded - Captive portal error messages removed — the password field no longer displays error text on failure; all feedback is shown on the photo frame display where it is always visible even after the phone disconnects from the AP
Removed
- Legacy PIN state —
_pin_statemodule-level dict and all legacy PIN functions (generate_ap_pin,clear_ap_pin,validate_ap_pin,is_pin_required,get_active_pin) removed fromnetwork_manager.py; PIN management is now exclusively owned byNetworkControllerwith proper thread safety SCAN_CACHE_TTLconstant andforce_liveparameter — scan cache is now served indefinitely while the AP is active; live scan toggle no longer exposed to the web API- Legacy PIN fallbacks in web routes — all
is_pin_required()andvalidate_ap_pin()fallback paths removed; the controller is the sole source of truth
[1.0.2-beta.2]
Added
- CPU temperature tile — sparkline graph on the System Status dashboard card using
vcgencmd measure_temp; scaled 0–85°C in red libopenblas0added to setup script package list — resolves NumPy import failures on Pi 2 (32-bit) where the shared library was missing- Hardware documentation — separated Models table from RAM Requirements table in README; added 64‑bit vs 32‑bit image availability per model; Pi 4 promoted to Phase 1 (untested); Pi Zero 2 W marked as untested
Changed
- Video transcode CPU limit default — reduced from 300 % to 200 %
- Pause button — switched from Unicode characters (⏸/▶) to Material Symbols icons (
pause/play_arrow) for consistent rendering - Progress bar colours — optimising images and transcoding bars now use
var(--primary)(theme red), matching the scanning bar - WiFi Country Code — hint text repositioned below the input field using
form-group--stacklayout - Connected button — vertically centered in network list rows via
display:inline-flex;align-items:center - Background processing bars — hidden when their respective feature is disabled (image optimisation off → hide optimising bar; video transcoding off → hide transcoding bar)
- README — hardware section restructured with separate Supported Models and RAM Requirements tables; Pi 2 marked active (32-bit manual install); Pi 4 marked untested; Pi Zero 2 W marked untested
Fixed
- Pipeline reset on sync changes — enabling/disabling local watch folders now triggers a full pipeline reset (
"sync"re‑added to the config route trigger list) - Hardware docs consistency —
CLAUDE.md,HARDWARE.md,ARCHITECTURE.md,GETTING_STARTED.md,INSTALLATION.md, andFEATURES.mdupdated to match README hardware tables (Pi 4 in Phase 1, Pi Zero 2 W untested, 32‑bit vs 64‑bit, manual install notes)
[1.0.1-beta.1]
Added
first_frame_pathandlast_frame_pathfields onMediaItem— backend-generated video frame caches referenced directly by the presentation engineVideoProcessor._extract_video_frames()— extracts first frame (t=0) and last frame (sseof) during Phase 2 OPTIMISE; cached as.1.frame/.2.frameJPEGs- Image optimisation worker subprocess (
worker.py) — PIL operations (load, transpose, resize, save) run in an isolated process withcpulimit -l 50(hard 50 % CPU cap) andnice -n 19(lowest priority); OS reclaims all memory on exit - Adaptive CPU throttling — sleep between image optimisations scales with 1‑minute load average (
load1 × 0.2, capped at 1.0 s); folder watcher also yields between thumbnail generations when the optimiser is busy - Boot screen progress bar — red fill bar below the spinner showing optimisation progress (1/6 → 6/6); uses
draw_imagewith 1×1 pixel textures to avoid pi3d colour-space issues - Per‑phase progress bars in the Web UI Background Processing card — separate persistent bars for scanning, image optimisation, and video transcoding; each retains its last position when the active phase switches
- Frontend→backend slideshow‑started signal —
POST /api/slideshow-starteddefers the network monitor's AP‑fallback countdown until the first sl...
v1.0.5-beta.5
[1.0.5-beta.5]
Added
- Pi 2 / Ethernet-only support —
is_wifi_hardware_present()check
prevents the controller from attempting AP activation on devices without
WiFi hardware; the controller stays inCLIENT_CONNECTEDor
CLIENT_DISCONNECTEDbased on Ethernet state and never retries AP - Stale AP cleanup on boot — the controller now kills any leftover
hostapd instance on initialisation, preventing the captive portal from
blocking the web dashboard after an unclean shutdown
Changed
- AP startup delay eliminated — removed the daemon's forced
ap_timeout_secondswait on boot; the controller now owns all timing
(immediate AP when no saved networks, 5‑minute grace period when saved
WiFi exists) - Boot screen message timing — 10 s delay after slideshow start ensures
the boot screen fade-out completes before welcome or PIN messages appear - Setup script prompts before install — channel and WiFi country
questions are now asked before git is installed; answers flow through
to Phase 1 via environment variables so the user is never re‑prompted - Beta channel pins to pre-release tags — the setup script now checks
out the latestv*-beta.*tag onmainfor beta channel installs
instead of tracking thedevbranch, matching the OTA updater's
behaviour
Fixed
- Captive portal blocking dashboard after reboot — hostapd left running
from a previous AP session is now stopped at controller init
v1.0.4-beta.4
[1.0.4-beta.4]
Added
- WiFi connection failed popup — when the captive portal WiFi connection
fails (wrong password, out of range, etc.), an error message now appears on
the photo frame display with guidance to check the password or try a
different network (30 s auto-dismiss) - WiFi connection profile fallback — if the one-shot
nmcli device wifi connectfails with a "key-mgmt property is missing" error (common on
routers with mixed WPA2/WPA3 or certain TP-Link/ASUS models), the code
now creates an explicit connection profile with WPA2-PSK settings and
retries automatically - Setup script channel prompt — asks for
stableorbetachannel
before installing; stable pins to the latest non-prereleasev*tag,
beta tracks thedevbranch - Setup script WiFi country prompt — asks for the regulatory domain
(e.g.AU,US,GB) upfront alongside the channel choice; both
answers are written toconfig.jsonfor the Web UI
Changed
- Boot welcome delay eliminated — the network monitor no longer waits
ap_timeout_secondswhen a network is already connected at boot; the
welcome message appears as soon as the slideshow is ready instead of
60+ seconds later - Captive portal error messaging — the WiFi failure popup on the frame
display uses a single clean message instead of concatenating the raw
nmcli error with boilerplate text
Fixed
- Setup script CRLF line endings —
.gitattributesnow enforces
eol=lffor*.shfiles; the setup script was renormalized so it
runs correctly when downloaded from GitHub raw on a Pi