feat: add Generic OOK Signal Decoder module#178
Conversation
…cation fix: improve pager message display and mute visibility
New 'OOK Decoder' mode for capturing and decoding arbitrary OOK/ASK signals using rtl_433's flex decoder with fully configurable pulse timing. Covers PWM, PPM, and Manchester encoding schemes. Backend (utils/ook.py, routes/ook.py): - Configurable modulation: OOK_PWM, OOK_PPM, OOK_MC_ZEROBIT - Full rtl_433 flex spec builder with user-supplied pulse timings - Bit-inversion fallback for transmitters with swapped short/long mapping - Optional frame deduplication for repeated transmissions - SSE streaming via /ook/stream Frontend (static/js/modes/ook.js, templates/partials/modes/ook.html): - Live MSB/LSB bit-order toggle — re-renders all stored frames instantly without restarting the decoder - Full-detail frame display: timestamp, bit count, hex, dotted ASCII - Modulation selector buttons with encoding hint text - Full timing grid: short, long, gap/reset, tolerance, min bits - CSV export of captured frames - Global SDR device panel injection (device, SDR type, rtl_tcp, bias-T) Integration (app.py, routes/__init__.py, templates/): - Globals: ook_process, ook_queue, ook_lock - Registered blueprint, nav entries (desktop + mobile), welcome card - ookOutputPanel in visuals area with bit-order toolbar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r, TSCM link - Timing presets: five quick-fill buttons (300/600, 300/900, 400/800, 500/1500, 500 MC) that populate all six pulse-timing fields at once — maps to CTF flag timing profiles - RSSI per frame: add -M level to rtl_433 command; parse snr/rssi/level from JSON; display dB SNR inline with each frame; include rssi_db column in CSV export - Auto bit-order suggest: "Suggest" button counts printable chars across all stored frames for MSB vs LSB, selects the winner, shows count — no decoder restart needed - Pattern filter: live hex/ASCII filter input above the frame log; hides non-matching frames and highlights matches in green; respects current bit order - TSCM integration: "Decode (OOK)" button in RF signal device details panel switches to OOK mode and pre-fills frequency — frontend-only, no backend changes needed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… bar - Fix double-scroll by switching ookOutputPanel to flex layout - Keep decoded frames visible after stopping (persist for review) - Wire global Clear/CSV/JSON status bar buttons to OOK functions - Hide default output pane in OOK mode (uses own panel) - Add command display showing the active rtl_433 command - Add JSON export and auto-scroll support - Fix 0x prefix stripping in OOK hex decoder - Fix PWM encoding hint text Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers identifying modulation type (PWM/PPM/Manchester), finding pulse timing via rtl_433 -A, common ISM frequencies and timings, and troubleshooting tips for tolerance and bit order. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded frequency buttons with localStorage-backed presets. Default presets are standard ISM frequencies (433.920, 315, 868, 915 MHz). Users can add custom frequencies, right-click to remove, and reset to defaults — matching the pager module pattern. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix XSS: escape ASCII output in innerHTML via escapeHtml() - Fix deadlock: use put_nowait() for queue ops under ook_lock - Fix SSE leak: add ook to moduleDestroyMap so switching modes closes the EventSource - Fix RSSI: explicit null check preserves valid zero values in JSON export - Add frame cap: trim oldest frames at 5000 to prevent unbounded memory growth on busy bands - Validate timing params: wrap int() casts in try/except, return 400 instead of 500 on invalid input - Fix PWM hint: correct to short=0/long=1 matching rtl_433 OOK_PWM convention (UI, JS hints, and cheat sheet) - Fix inversion docstring: clarify fallback only applies when primary hex parse fails, not for valid decoded frames Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Feature/ook decoder
- Add kill_all() handler for OOK process cleanup on global reset - Fix stop_ook() to close pipes and join parser thread (prevents hangs) - Add ook.css with CSS classes, replace inline styles in ook.html - Register ook.css in lazy-load style map (INTERCEPT_MODE_STYLE_MAP) - Fix frontend frequency min=24 to match backend validation - Add 22 unit tests for decode_ook_frame, ook_parser_thread, and routes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
smittix
left a comment
There was a problem hiding this comment.
Code Review: Generic OOK Signal Decoder
Well-structured PR that follows existing patterns closely. A few issues to address before merge:
Critical
1. Missing sdr_type in claim_sdr_device() / release_sdr_device()
routes/ook.py doesn't pass sdr_type_str to device claim/release calls, unlike every other module (e.g. sensor.py). Non-RTL-SDR devices (HackRF, LimeSDR) won't be tracked or released correctly. Also needs an ook_active_sdr_type module-level variable.
2. No range validation on timing parameters
short_pulse, long_pulse, reset_limit, gap_limit, tolerance, min_bits are cast to int() but have no bounds checks. HTML min/max attributes are trivially bypassed. Should add server-side range validation.
Major
3. kill_all() doesn't fully clean up OOK state
app.py terminates the process but doesn't release the SDR device, signal the parser thread's stop_event, close pipes, or reset ook_active_device. The device stays "claimed" in the registry after a global reset.
4. Monkey-patching subprocess.Popen
Custom attrs (_stop_parser, _parser_thread) attached directly to the Popen object. Fragile and makes kill_all() unable to do proper cleanup since it doesn't know about these attributes. Consider a small wrapper class or storing these as separate module-level variables.
5. XSS fallback in ook.js
appendFrameEntry falls back to raw innerHTML if escapeHtml is undefined. Crafted OOK payloads could produce <script> in ASCII interpretation. Should always use escapeHtml or switch to textContent.
6. Inverted bit fallback is dead code
In utils/ook.py, the inversion path runs bytes.fromhex() on the same string that already failed decode_ook_frame(), so it can never produce a result. Either fix the logic or remove the dead code.
Minor
- Status event uses
msg.statuskey instead ofmsg.text— inconsistent with other modules (e.g.sensor.py) - Parser thread errors logged at
debuglevel — should bewarningorerror - Parser thread doesn't emit a
status: stoppedevent when rtl_433 crashes unexpectedly — frontend stays in "Listening" state with no notification - No cache-busting query param on JS/CSS includes in
index.html(other modules use?v={{ version }}) - Gain comparison uses
!= '0'(string) instead of!= 0(number) —validate_gainreturns a float
Tests
22 tests covering decoder, parser thread, and routes — solid coverage. Gaps worth filling:
stop_ookwith a running process (mocked)start_ooksuccess path- SSE stream endpoint
- Inversion logic (currently dead code, so untestable as-is)
…nup, XSS
Critical:
- Pass sdr_type_str to claim/release_sdr_device (was missing 3rd arg)
- Add ook_active_sdr_type module-level var for proper device registry tracking
- Add server-side range validation on all timing params via validate_positive_int
Major:
- Extract cleanup_ook() function for full teardown (stop_event, pipes, process,
SDR release) — called from both stop_ook() and kill_all()
- Replace Popen monkey-patching with module-level _ook_stop_event/_ook_parser_thread
- Fix XSS: define local _esc() fallback in ook.js, never use raw innerHTML
- Remove dead inversion code path in utils/ook.py (bytes.fromhex on same
string that already failed decode — could never produce a result)
Minor:
- Status event key 'status' → 'text' for consistency with other modules
- Parser thread logging: debug → warning for missing code field and errors
- Parser thread emits status:stopped on exit (normal EOF or crash)
- Add cache-busting ?v={{ version }}&r=ook1 to ook.js script include
- Fix gain/ppm comparison: != '0' (string) → != 0 (number)
Tests: 22 → 33 (added start success, stop with process, SSE stream,
timing range validation, stopped-on-exit event)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Hey — really appreciate the thorough review! Addressed everything in 7b4ad20. Oh man, sorry about the Here's the full breakdown: Critical1. Missing
2. No range validation on timing parameters — Fixed.
Major3.
4. Monkey-patching
5. XSS fallback in
6. Inverted bit fallback is dead code — Removed.
Minor
Tests22 → 33 tests. New coverage:
All 33 pass. Let me know if anything else needs attention! |
There was a problem hiding this comment.
Pull request overview
Adds a new “Generic OOK Signal Decoder” mode that runs rtl_433 flex decoding on the backend and streams decoded frames to a new frontend UI for live bit/hex/ASCII analysis.
Changes:
- Introduces OOK frame decoding/parsing utilities and a new Flask blueprint with start/stop/status/SSE stream endpoints.
- Integrates the OOK mode into the UI (navigation, mode panel, output panel, cheat sheet, and lazy-loaded CSS/JS).
- Adds unit tests covering frame decoding, parser behavior, and route handlers.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
utils/ook.py |
Implements hex→bits decoding and a parser thread for rtl_433 JSON output. |
routes/ook.py |
Adds OOK blueprint endpoints and subprocess/thread lifecycle management. |
static/js/modes/ook.js |
Frontend module for controls, SSE, frame display, filtering, presets, and export. |
static/css/modes/ook.css |
Styling for the OOK mode sidebar controls and command panel. |
templates/partials/modes/ook.html |
Sidebar UI for frequency/modulation/timing controls and status/command display. |
templates/index.html |
Adds OOK mode integration points (style map, mode catalog, panels, init/destroy hooks, exports). |
templates/partials/nav.html |
Adds OOK mode entries to desktop and mobile navigation. |
static/js/core/cheat-sheets.js |
Adds an OOK-specific cheat sheet entry. |
routes/__init__.py |
Registers the new OOK blueprint. |
app.py |
Adds global OOK process/queue/lock and hooks OOK cleanup into kill_all(). |
tests/test_ook.py |
Adds tests for decoder utilities, parser thread, and key OOK routes. |
.gitignore |
Ignores local reset-sdr.* utility scripts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…torage - Detect crashed rtl_433 process via poll() and clean up stale state instead of permanently blocking restarts with 409 - Replace innerHTML+onclick preset rendering with createElement/addEventListener to prevent XSS via crafted localStorage frequency values - Normalize preset frequencies to toFixed(3) on save and render - Add try/catch + shape validation to loadPresets() for corrupted localStorage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot Review — AddressedReviewed all 4 Copilot suggestions. Fixed 3, rejected 1: Fixed:
Rejected:
Commit: 91989a0 |
Follow-up ReviewNice work addressing all the feedback — the cleanup is solid. A few final observations: Minor1. 2. Confirm Otherwise this looks good to merge. Clean architecture, good test coverage (33 tests), and follows existing patterns well. |
…rameEntry Addresses final upstream review — all backend-derived values (timestamp, bit_count, rssi, hex, ascii) now use DOM methods instead of innerHTML interpolation, closing the last XSS surface. Bumps cache-buster to ook2. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks for the follow-up! Rebased on current main and addressed both items: 1. 2. Cache-buster bumped to |
|
Great work on this — clean implementation, thorough test coverage, and really responsive to feedback throughout. Thanks for the contribution! 🎉 |
Summary
Adds a new Generic OOK Signal Decoder mode for decoding raw OOK (On-Off Keying) signals using rtl_433's flex decoder. This provides a configurable interface for capturing and analyzing unknown ISM-band protocols with live bit/hex/ASCII display.
Key features
Architecture
kill_all()properly handles OOK process cleanupstop_ook()closes pipes and joins parser thread to prevent hangsescapeHtml()on user-derived ASCII contentput_nowait()under lock to prevent deadlocksook.cssusing project CSS variables, lazy-loaded viaINTERCEPT_MODE_STYLE_MAPFiles added
routes/ook.py— Flask blueprint with start/stop/status/stream endpointsutils/ook.py— Frame decoder and parser thread (handles codes/code/data fields, 0x prefix, brace prefix, inversion fallback)static/js/modes/ook.js— Frontend IIFE modulestatic/css/modes/ook.css— Scoped styles using CSS variablestemplates/partials/modes/ook.html— Sidebar controlstests/test_ook.py— 22 unit tests covering decoder, parser thread, and routesFiles modified
app.py— OOK globals (process, queue, lock) andkill_all()cleanuproutes/__init__.py— Blueprint registrationtemplates/index.html— Mode integration (12 touch points), CSS lazy-load entry, output panel layouttemplates/partials/nav.html— Navigation entrystatic/js/core/cheat-sheets.js— OOK cheat sheet content.gitignore— Local utility script exclusionTest plan
kill_all()with OOK running → process terminates cleanlypytest tests/test_ook.py→ all 22 tests passgetModuleDestroyFn()🤖 Generated with Claude Code