diff --git a/routes/ook.py b/routes/ook.py
index 75258e3..2fb6bdb 100644
--- a/routes/ook.py
+++ b/routes/ook.py
@@ -152,7 +152,7 @@ def start_ook() -> Response:
continue
filtered_cmd.append(arg)
- filtered_cmd.extend(['-R', '0', '-X', flex_spec])
+ filtered_cmd.extend(['-M', 'level', '-R', '0', '-X', flex_spec])
full_cmd = ' '.join(filtered_cmd)
logger.info(f'OOK decoder running: {full_cmd}')
diff --git a/static/js/modes/ook.js b/static/js/modes/ook.js
index 9fca3ba..45a6da8 100644
--- a/static/js/modes/ook.js
+++ b/static/js/modes/ook.js
@@ -16,6 +16,7 @@ var OokMode = (function () {
frames: [], // raw frame objects from SSE
frameCount: 0,
bitOrder: 'msb', // 'msb' | 'lsb'
+ filterQuery: '', // active hex/ascii filter
};
// ---- Initialization ----
@@ -209,7 +210,7 @@ var OokMode = (function () {
var div = document.createElement('div');
div.className = 'ook-frame';
- div.dataset.bits = msg.bits;
+ div.dataset.bits = msg.bits || '';
div.dataset.bitCount = msg.bit_count;
div.dataset.inverted = msg.inverted ? '1' : '0';
@@ -217,10 +218,14 @@ var OokMode = (function () {
var suffix = '';
if (msg.inverted) suffix += ' (inv)';
+ var rssiStr = (msg.rssi !== undefined && msg.rssi !== null)
+ ? ' ' + msg.rssi.toFixed(1) + ' dB SNR'
+ : '';
+
div.innerHTML =
'' + msg.timestamp + '' +
' [' + msg.bit_count + 'b]' +
- suffix +
+ rssiStr + suffix +
' ' +
'' +
'hex: ' + interp.hex +
@@ -232,6 +237,16 @@ var OokMode = (function () {
div.style.cssText = 'font-size:11px; padding: 4px 0; border-bottom: 1px solid #1a1a1a; line-height:1.6;';
+ // Apply current filter
+ if (state.filterQuery) {
+ var q = state.filterQuery;
+ if (!interp.hex.includes(q) && !interp.ascii.toLowerCase().includes(q)) {
+ div.style.display = 'none';
+ } else {
+ div.style.background = 'rgba(0,255,136,0.05)';
+ }
+ }
+
panel.appendChild(div);
panel.scrollTop = panel.scrollHeight;
}
@@ -272,12 +287,13 @@ var OokMode = (function () {
}
function exportLog() {
- var lines = ['timestamp,bit_count,hex_msb,ascii_msb,inverted'];
+ var lines = ['timestamp,bit_count,rssi_db,hex_msb,ascii_msb,inverted'];
state.frames.forEach(function (msg) {
var interp = interpretBits(msg.bits, 'msb');
lines.push([
msg.timestamp,
msg.bit_count,
+ msg.rssi !== undefined && msg.rssi !== null ? msg.rssi : '',
interp.hex,
'"' + interp.ascii.replace(/"/g, '""') + '"',
msg.inverted,
@@ -325,6 +341,80 @@ var OokMode = (function () {
if (el) el.value = mhz;
}
+ /**
+ * Apply a timing preset — fills all six pulse timing fields at once.
+ * @param {number} s Short pulse (µs)
+ * @param {number} l Long pulse (µs)
+ * @param {number} r Reset/gap limit (µs)
+ * @param {number} g Gap limit (µs)
+ * @param {number} t Tolerance (µs)
+ * @param {number} b Min bits
+ */
+ function setTiming(s, l, r, g, t, b) {
+ var fields = {
+ ookShortPulse: s,
+ ookLongPulse: l,
+ ookResetLimit: r,
+ ookGapLimit: g,
+ ookTolerance: t,
+ ookMinBits: b,
+ };
+ Object.keys(fields).forEach(function (id) {
+ var el = document.getElementById(id);
+ if (el) el.value = fields[id];
+ });
+ }
+
+ // ---- Auto bit-order suggestion ----
+
+ /**
+ * Count printable chars for MSB and LSB across all stored frames,
+ * then switch to whichever produces more readable output.
+ */
+ function suggestBitOrder() {
+ if (state.frames.length === 0) return;
+ var msbCount = 0, lsbCount = 0;
+ state.frames.forEach(function (msg) {
+ msbCount += interpretBits(msg.bits, 'msb').printable.length;
+ lsbCount += interpretBits(msg.bits, 'lsb').printable.length;
+ });
+ var best = msbCount >= lsbCount ? 'msb' : 'lsb';
+ setBitOrder(best);
+ var label = document.getElementById('ookSuggestLabel');
+ if (label) {
+ var winner = best === 'msb' ? msbCount : lsbCount;
+ label.textContent = best.toUpperCase() + ' (' + winner + ' printable)';
+ label.style.color = '#00ff88';
+ }
+ }
+
+ // ---- Pattern search / filter ----
+
+ /**
+ * Show only frames whose hex or ASCII interpretation contains the query.
+ * Clears filter when query is empty.
+ * @param {string} query
+ */
+ function filterFrames(query) {
+ state.filterQuery = query.toLowerCase().trim();
+ var q = state.filterQuery;
+ var panel = document.getElementById('ookOutput');
+ if (!panel) return;
+ var divs = panel.querySelectorAll('.ook-frame');
+ divs.forEach(function (div) {
+ if (!q) {
+ div.style.display = '';
+ div.style.background = '';
+ return;
+ }
+ var bits = div.dataset.bits || '';
+ var interp = interpretBits(bits, state.bitOrder);
+ var match = interp.hex.includes(q) || interp.ascii.toLowerCase().includes(q);
+ div.style.display = match ? '' : 'none';
+ div.style.background = match ? 'rgba(0,255,136,0.05)' : '';
+ });
+ }
+
// ---- UI ----
function updateUI(running) {
@@ -351,7 +441,10 @@ var OokMode = (function () {
stop: stop,
setFreq: setFreq,
setEncoding: setEncoding,
+ setTiming: setTiming,
setBitOrder: setBitOrder,
+ suggestBitOrder: suggestBitOrder,
+ filterFrames: filterFrames,
clearOutput: clearOutput,
exportLog: exportLog,
};
diff --git a/templates/index.html b/templates/index.html
index 96fb6af..467c4ed 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -3294,6 +3294,7 @@
+
Decoded Frames
@@ -3303,10 +3304,21 @@
style="background: var(--accent); color: #000;">MSB
+
+
+
+
+
@@ -13091,7 +13103,7 @@
`;
- // Add "Listen" button for RF signals
+ // Add "Listen" and "Decode (OOK)" buttons for RF signals
if (protocol === 'rf' && device.frequency) {
const freq = device.frequency;
html += `
@@ -13101,6 +13113,10 @@
+
`;
}
@@ -13114,7 +13130,7 @@
- ${protocol === 'rf' ? 'Listen buttons open Spectrum Waterfall. ' : ''}Known devices are excluded from threat scoring in future sweeps.
+ ${protocol === 'rf' ? 'Listen opens Spectrum Waterfall. Decode (OOK) opens the OOK decoder tuned to this frequency. ' : ''}Known devices are excluded from threat scoring in future sweeps.
`;
@@ -13910,6 +13926,17 @@
}, 300);
}
+ function decodeWithOok(frequency) {
+ // Close the TSCM modal and switch to OOK decoder with the detected frequency pre-filled
+ closeTscmDeviceModal();
+ switchMode('ook');
+ setTimeout(function () {
+ if (typeof OokMode !== 'undefined' && typeof OokMode.setFreq === 'function') {
+ OokMode.setFreq(parseFloat(frequency).toFixed(3));
+ }
+ }, 300);
+ }
+
async function showDevicesByCategory(category) {
const modal = document.getElementById('tscmDeviceModal');
const content = document.getElementById('tscmDeviceModalContent');
diff --git a/templates/partials/modes/ook.html b/templates/partials/modes/ook.html
index 98c86a9..ab89738 100644
--- a/templates/partials/modes/ook.html
+++ b/templates/partials/modes/ook.html
@@ -95,6 +95,21 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/utils/ook.py b/utils/ook.py
index 4f784ac..8374562 100644
--- a/utils/ook.py
+++ b/utils/ook.py
@@ -126,6 +126,17 @@ def ook_parser_thread(
if raw_data:
codes = [str(raw_data)]
+ # Extract signal level if rtl_433 was invoked with -M level
+ rssi: float | None = None
+ for _rssi_key in ('snr', 'rssi', 'level', 'noise'):
+ _rssi_val = data.get(_rssi_key)
+ if _rssi_val is not None:
+ try:
+ rssi = round(float(_rssi_val), 1)
+ except (TypeError, ValueError):
+ pass
+ break
+
if not codes:
logger.debug(
f'[rtl_433/ook] no code field — keys: {list(data.keys())}'
@@ -176,7 +187,7 @@ def ook_parser_thread(
continue
try:
- output_queue.put_nowait({
+ event: dict[str, Any] = {
'type': 'ook_frame',
'hex': frame['hex'],
'bits': frame['bits'],
@@ -185,7 +196,10 @@ def ook_parser_thread(
'inverted': inverted,
'encoding': encoding,
'timestamp': timestamp,
- })
+ }
+ if rssi is not None:
+ event['rssi'] = rssi
+ output_queue.put_nowait(event)
except queue.Full:
pass