• v0.7.0 8bdcc17799

    LedGrab v0.7.0
    Build Release / create-release (push) Successful in 3s
    Build Android APK / build-android (push) Failing after 11s
    Build Release / build-linux (push) Successful in 2m54s
    Build Release / build-docker (push) Successful in 3m50s
    Build Release / build-windows (push) Successful in 4m36s
    Stable

    alexei.dolgolyov released this 2026-05-26 00:35:38 +03:00 | 76 commits to master since this release

    v0.7.0 (2026-05-26)

    A device-support release: seven new device families, a unified pairing UX,
    a brand-new HTTP-endpoint output type, multi-broker MQTT + Zigbee2MQTT
    support, a major shutdown / data-safety fix, and a deep architectural
    refactor pass that landed registry patterns for every dispatch hot path.

    Features

    New device types

    • DDP — standalone Open-Pixel-Control-style target for Pixelblaze / ESPixelStick / xLights / Falcon endpoints, port 4048 (8f1140a)
    • Yeelight — Xiaomi/Yeelight bulbs and lightstrips over JSON-RPC on port 55443, SSDP discovery (4b65005)
    • WiZ Connected — Philips WiZ smart bulbs over UDP on port 38899, broadcast discovery (ede627b)
    • LIFX — LIFX bulbs and lightstrips over the binary LIFX LAN protocol on port 56700 (8f9d490)
    • Govee LAN — Govee Wi-Fi bulbs and ambient kits, multicast discovery (requires "LAN Control" enabled in the Govee Home app) (887131d)
    • Open Pixel Control (OPC) — Fadecandy boards, xLights/Falcon, OPC bridges, port 7890 with channel addressing (31c6c3a)
    • Nanoleaf — Light Panels / Canvas / Shapes / Lines / Elements over the documented HTTP REST API on port 16021 (426484a)

    New output type

    • HTTP endpoint output target — POST live strip frames to any user-configured HTTP endpoint, alongside WLED / MQTT / Hue. Full editor + storage + routes (d6cc800)

    Pairing flow

    • Generic pairing UX scaffold — 30-second SVG ring + countdown, instructions, retry/cancel. First concrete consumer is Nanoleaf; Tuya/Twinkly slot into the same shape later (2f31680)

    MQTT / Zigbee2MQTT

    • Multi-broker MQTT + new Zigbee2MQTT light output target sharing the HA-Light editor. Legacy single-broker YAML/env config auto-migrates to a "Default Broker" MQTTSource on startup (530316c)

    Editor experience

    • Live preview for color-strip sources of every type that can render without external calibration (audio, math_wave, weather, game_event, api_input, mapped, composite, processed) (337984c)
    • Expanded automations — new rule shapes + matching UI inputs + 285 lines of dispatch coverage (3fe66d8)
    • Expanded value sources — storage + schema + UI for the new value-source kinds the per-type factory refactor introduced (737fd72)
    • Card icon picker expanded from 44 → 120 icons across 5 new categories (weather, nature, controls, status, office) (cdf7d94)
    • closeIfPristine modal save-guard — editing an unchanged entity now silently closes the modal instead of firing a misleading "updated" toast (f03cb30)
    • New MiniSelect primitive for compact dropdowns that don't justify the full IconSelect grid; IconSelect gains a defence-in-depth XSS sanitiser on the icon channel (9ff83bd, 507e138)

    Updater

    • SSRF-validated redirect chain in the update service so a hostile mirror can't bounce the updater to a private IP. Stricter restart.ps1 argument handling + clearer logs (45d12b2)

    Bug Fixes

    • Survive PC restart — SQLite was running WAL with synchronous=NORMAL and Database.close() was never called, so an unclean Windows shutdown rolled the DB back to the last checkpoint and silently lost recent edits. Now uses synchronous=FULL + wal_autocheckpoint=100 + explicit wal_checkpoint(TRUNCATE) on close, and a hidden WM_QUERYENDSESSION / WM_ENDSESSION window keeps Windows from force-killing the process before the lifespan can finish (e24f9d3)
    • Devices PATCH preserves URL — PATCH-without-url (rename / icon-only) used to drop the address into the processor as None (0dd8d43)
    • HA Light brightness scale_send_entity_color was double-applying brightness_scale below 1 (quartered output for a half-scale) and skipping it above 1 (boost lost). Now one clamp(max(r,g,b) * bs * vs, 0, 255) pass with regression coverage (ad84b60)
    • Dashboard "MODIFIED" badge no longer fires retroactively on un-edited legacy layouts — userModified is now driven by actual edits, not deep-equal drift from defaults (e4bf58d)
    • Transport-bar uptime repaints on /health response instead of waiting up to ~10 s for the next poll (f1b0f0e)
    • Pre-merge device-support review passupdate_device no longer double-encrypts secrets in memory; GET /devices strips paired-only secrets behind boolean flags; SSRF validation on every new driver; corrupt-envelope decrypt returns "" instead of deleting the device row; update_device URL trim matches create; Govee discovery port-4002 collision serialised behind a module lock; Nanoleaf mDNS scan cleans up tasks on cancel; pair endpoint stops logging userinfo / exception bodies (0e3ae78)
    • value_source factory contract_build_game_event raises NotImplementedError (preserves the historical store contract) and create_source runs build_source before _check_name_unique so an invalid source_type raises the right error (c1aa2eb)
    • utils/url_scheme + utils/net_classify were referenced but untracked on a clean checkout — server failed to start with ModuleNotFoundError. Now committed (7736bc6)

    Performance

    • Capture hot paths vectorised — WGC swaps per-frame ~30 MB BGRA→RGB fancy-index allocations for cv2.cvtColor into a 3-slot pre-allocated pool; MSS uses screenshot.raw + cv2.cvtColor with 256-byte change-detection; DXcam/BetterCam fixes a silent name-mangling factory leak; dominant-colour reduction is ~10× faster via packed-RGB np.bincount (f184ef0)
    • Event-driven frame hand-offLiveStream gains a frame_id + Condition, consumers wait instead of polling, ring buffer grows 3 → 5 slots, _blend_u16 uses cv2.addWeighted. Up to one frame_time of glass-to-LED latency saved at matched FPS (ee4fa81)
    • WLED brightness threshold caches per-frame np.max keyed on frame identity instead of reducing the LED array every loop (6e4c1b6)
    • Dashboard FPS charts now diff target ids and only recreate added/removed/detached charts (skipping the history fetch when local samples already exist), and spark SVGs are mutated in place instead of innerHTML-rewritten every poll. Memoised patches/devices rendering by content signature so unchanged ticks no longer restart CSS animations (f6486f9)

    Development / Internal

    Architecture audit — registry patterns everywhere

    • Color-strip stream dispatchColorStripStreamManager.acquire() and ws_stream._create_stream() now share a STREAM_BUILDERS registry keyed by source type, with import-time coverage assertion against _SOURCE_TYPE_MAP. CSS response builder gets the same treatment (563cbac)
    • Value-source create / updateValueSourceStore.create_source shrinks from ~260 → ~25 lines via per-type builder/applier functions in a new storage.value_source_factories module (3b8f00e)
    • SystemMetricsValueStream — three parallel if/elif chains collapse into a MetricSpec(name, read_psutil, read_fallback, normalize, prime) registry in core.processing.metric_readers (9f3f346)
    • Automation engine — per-rule-type bodies become _handle_<kind> methods, dispatch table built once at class-creation, unknown-type fallback logs instead of silently returning False (98fb61d)
    • Effect renderer dispatch@_effect_renderer("fire") decorators + class-level _RENDERERS dict replace per-frame dict-rebuild + silent fire fallback (97dae2c)
    • Output-target response buildersisinstance ladder + silent fabricated-LED fallback replaced with _TARGET_RESPONSE_BUILDERS dict and a runtime RuntimeError for unknown subclasses (2f15fbb)
    • Versioned data migrations — replaces a naked blob.replace(...) migration with storage.data_migrations.MigrationRunner backed by a data_migrations audit table and atomic transactions (563cbac)

    Dedup / refactor

    • Edge-to-LED kernels in PixelMapper + AdvancedPixelMapper deduped into a shared core.capture.edge_interpolation module (5fec8db)
    • HA/Z2M _swap_color_source unified behind a shared light_target_helpers.swap_color_source helper (29bdacf)
    • Single-pixel _average_color lifted out of 6 LED drivers into core.devices.pixel_reduce.average_color (cc87fba)
    • Static → single rename for the color-strip source kind. Storage keeps backward-compatible serialisation (826e680)
    • Bindable types extracted into types/bindable.ts; the wider types.ts god-module split is staged for a follow-up frontend sprint (05f73ee)
    • WebSocket auth — 11 except Exception sites around handshake replaced with a narrow _WS_SEND_BENIGN_EXC tuple; receive path adds explicit observability (ea7ee88)
    • Backend hardening bundle — MQTT task tracking + drain resilience, credential encryption with auto-migration, devices watcher task tracking, WLED scheme inference at boundaries, streaming-upload caps, asyncio.gather(return_exceptions=True) on broadcast loops, WebSocket Origin allow-list, /docs auth-gate (898912f)
    • Frontend infra — inbound-event allowlist mirroring the server side, closeIfPristine adoption across editors, MiniSelect markup for filter pickers (ddae571)
    • PEP-604 union sweepruff --select UP007,UP045 --fix converted ~1760 sites from Optional[T] / Union[X, Y] to T | None / X | Y. Hooks bumped to ruff v0.15.12 to recognise UP045 (888f8fd)
    • Typed window globals — 59 (window as any).foo sites across 19 feature modules switched to typed window.foo against global-types.d.ts (0035172)
    • Processing magic numbers lifted to named module constants so tests can monkeypatch them (d38021f)
    • Database.ensure_open() — module-level singleton reopens cleanly across lifespan cycles, fixing 65 spurious sqlite3.ProgrammingError setup failures on Windows pytest aggregate runs (f591e25)

    Tests

    • WLED URL scheme integration + IPv6 regression coverage (907bdaf)
    • Lifespan reopen invariants on Database (f591e25)
    • Hundreds of new tests covering every registry / factory / migration introduced above

    Tooling / docs

    • .vex.toml makes vex the project's primary code-search backend with auto-update + semantic embeddings (06273ba)
    • REVIEW_TODO.md captures audit items deliberately deferred; TODO.md records the architecture-audit remainder (06273ba, 628c6b2)
    • Locale + CLAUDE.md upkeep alongside the new features (fd46c51, 48dbdb9, 17684af, 390d2b4)

    All Commits (55)
    Hash Message
    f591e25 fix(storage/database): reopen connection on lifespan restart
    f6486f9 perf(dashboard): diff FPS charts + cache spark SVG nodes; i18n perf strings
    48dbdb9 docs(review-todo): check off items addressed in 2026-05-23 autonomous pass
    0035172 refactor(types): migrate (window as any) statics to typed window globals
    888f8fd refactor(types): PEP-604 union sweep + UP007/UP045 enforcement
    ea7ee88 refactor(api/auth): narrow WS exception catches + observability log
    d38021f refactor(processing): hot-path magic numbers -> named module constants
    507e138 feat(ui/icon-select): defence-in-depth XSS sanitiser on icon channel
    907bdaf test(url-scheme): WLED route-level integration + IPv6 regression
    0dd8d43 fix(devices): preserve existing URL on PATCH-without-url
    fd46c51 docs: TODO + CLAUDE.md notes + locale keys for new features
    ddae571 chore(frontend-infra): inbound-event allowlist + storage/state touch-ups
    898912f chore(backend): MQTT/WLED/devices/capture/utils + api routes hardening
    45d12b2 feat(update-service): SSRF-validated redirects + restart hardening
    826e680 refactor(color-strip): rename static -> single + frontend follow-through
    737fd72 feat(value-sources): extend storage + schema + UI alongside new kinds
    3fe66d8 feat(automations): expand automation rules + UI + engine coverage
    f03cb30 feat(modal): closeIfPristine save-guard + per-editor adoption
    9ff83bd feat(ui): MiniSelect primitive + IconSelect XSS hardening + typed globals
    d6cc800 feat(http-endpoints): introduce HTTP endpoint output target stack
    06273ba chore(tooling): vex semantic-search config + REVIEW_TODO backlog
    628c6b2 docs: capture architecture-audit remainder for follow-up sessions
    2f15fbb refactor(output-targets): registry + coverage assertion for response builders
    c1aa2eb fix(value-source): preserve store contract for game_event + error precedence
    3b8f00e refactor(value-source): per-type factories for create / update dispatch
    05f73ee refactor(types): extract bindable primitives into types/bindable.ts (H6 partial)
    9f3f346 refactor(value-source): MetricSpec registry for SystemMetricsValueStream
    98fb61d refactor(automations): rule dispatch via class-level handler table
    5fec8db refactor(capture): lift duplicated edge-to-LED kernels into shared module
    97dae2c refactor(processing): replace inline effect dispatch with @_effect_renderer registry
    29bdacf refactor(processing): dedupe HA/Z2M _swap_color_source via shared helper
    563cbac refactor(storage,processing): kind registries + versioned data migrations
    e24f9d3 fix(shutdown): survive PC restart with WAL fsync + Win32 session-end guard
    e4bf58d fix(dashboard): stop showing perpetual MODIFIED for un-edited legacy layouts
    f1b0f0e fix(ui): repaint transport-bar uptime as soon as /health responds
    17684af docs: record review-fix pass in TODO.md
    0e3ae78 fix(devices): address pre-merge review findings
    7736bc6 fix(utils): commit url_scheme + net_classify dependencies
    390d2b4 docs: mark expand-device-support branch ready for merge
    cc87fba refactor(devices): extract _average_color to pixel_reduce
    426484a feat(devices): Nanoleaf OpenAPI target type + first pair-flow user
    2f31680 feat(devices): pairing-UX scaffold (Phase 2)
    31c6c3a feat(devices): Open Pixel Control (OPC) target type
    887131d feat(devices): Govee LAN target type
    8f9d490 feat(devices): LIFX LAN target type
    ede627b feat(devices): WiZ Connected LAN target type
    4b65005 feat(devices): Yeelight LAN target type
    8f1140a feat(devices): standalone DDP target type
    337984c feat(color-strips): in-editor live preview for all viable source types
    530316c feat(mqtt): multi-broker MQTT + Zigbee2MQTT light target
    6e4c1b6 perf(wled): cache per-frame max-pixel for brightness threshold
    ee4fa81 perf(processing): event-driven frame hand-off and scheduling fixes
    f184ef0 perf(capture): vectorize hot paths and fix engine bugs
    ad84b60 fix(ha-light): apply brightness_scale once and respect boost multipliers
    cdf7d94 feat(ui): expand card icon picker (44 -> 120 icons, +5 categories)

    Downloads

    Platform File Description
    Windows (installer) LedGrab-v0.7.0-setup.exe Install with Start Menu shortcut, optional autostart, uninstaller
    Windows (portable) LedGrab-v0.7.0-win-x64.zip Unzip anywhere, run LedGrab.bat
    Linux LedGrab-v0.7.0-linux-x64.tar.gz Extract, run ./run.sh
    Android LedGrab-v0.7.0-android-release.apk Sideload on Android 7.0+ (API 24+) — TV boxes, Fire TV, phones, tablets. arm64-v8a / x86_64 / x86
    Docker See below docker pull + docker run

    After starting, open http://localhost:8080 in your browser.

    Docker

    docker pull git.dolgolyov-family.by/alexei.dolgolyov/ledgrab:v0.7.0
    docker run -d --name ledgrab -p 8080:8080 -v ledgrab-data:/app/data git.dolgolyov-family.by/alexei.dolgolyov/ledgrab:v0.7.0
    

    First-time setup

    1. Change the default API key in .
    2. Open http://localhost:8080 and add your LED devices.
    3. See for detailed configuration.
    Downloads