mirror of
https://github.com/smittix/intercept.git
synced 2026-07-08 01:28:13 -07:00
chore: commit all pending changes
This commit is contained in:
+44
-29
@@ -96,7 +96,7 @@ def parse_multimon_output(line: str) -> dict[str, str] | None:
|
||||
return None
|
||||
|
||||
|
||||
def log_message(msg: dict[str, Any]) -> None:
|
||||
def log_message(msg: dict[str, Any]) -> None:
|
||||
"""Log a message to file if logging is enabled."""
|
||||
if not app_module.logging_enabled:
|
||||
return
|
||||
@@ -104,25 +104,39 @@ def log_message(msg: dict[str, Any]) -> None:
|
||||
with open(app_module.log_file_path, 'a') as f:
|
||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
f.write(f"{timestamp} | {msg.get('protocol', 'UNKNOWN')} | {msg.get('address', '')} | {msg.get('message', '')}\n")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to log message: {e}")
|
||||
|
||||
|
||||
def audio_relay_thread(
|
||||
rtl_stdout,
|
||||
multimon_stdin,
|
||||
output_queue: queue.Queue,
|
||||
stop_event: threading.Event,
|
||||
) -> None:
|
||||
"""Relay audio from rtl_fm to multimon-ng while computing signal levels.
|
||||
|
||||
Reads raw 16-bit LE PCM from *rtl_stdout*, writes every chunk straight
|
||||
through to *multimon_stdin*, and every ~100 ms pushes an RMS / peak scope
|
||||
event onto *output_queue*.
|
||||
"""
|
||||
CHUNK = 4096 # bytes – 2048 samples at 16-bit mono
|
||||
INTERVAL = 0.1 # seconds between scope updates
|
||||
last_scope = time.monotonic()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to log message: {e}")
|
||||
|
||||
|
||||
def _encode_scope_waveform(samples: tuple[int, ...], window_size: int = 256) -> list[int]:
|
||||
"""Compress recent PCM samples into a signed 8-bit waveform for SSE."""
|
||||
if not samples:
|
||||
return []
|
||||
|
||||
window = samples[-window_size:] if len(samples) > window_size else samples
|
||||
waveform: list[int] = []
|
||||
for sample in window:
|
||||
# Convert int16 PCM to int8 range for lightweight transport.
|
||||
packed = int(round(sample / 256))
|
||||
waveform.append(max(-127, min(127, packed)))
|
||||
return waveform
|
||||
|
||||
|
||||
def audio_relay_thread(
|
||||
rtl_stdout,
|
||||
multimon_stdin,
|
||||
output_queue: queue.Queue,
|
||||
stop_event: threading.Event,
|
||||
) -> None:
|
||||
"""Relay audio from rtl_fm to multimon-ng while computing signal levels.
|
||||
|
||||
Reads raw 16-bit LE PCM from *rtl_stdout*, writes every chunk straight
|
||||
through to *multimon_stdin*, and every ~100 ms pushes an RMS / peak scope
|
||||
event plus a compact waveform sample onto *output_queue*.
|
||||
"""
|
||||
CHUNK = 4096 # bytes – 2048 samples at 16-bit mono
|
||||
INTERVAL = 0.1 # seconds between scope updates
|
||||
last_scope = time.monotonic()
|
||||
|
||||
try:
|
||||
while not stop_event.is_set():
|
||||
@@ -146,15 +160,16 @@ def audio_relay_thread(
|
||||
if n_samples == 0:
|
||||
continue
|
||||
samples = struct.unpack(f'<{n_samples}h', data[:n_samples * 2])
|
||||
peak = max(abs(s) for s in samples)
|
||||
rms = int(math.sqrt(sum(s * s for s in samples) / n_samples))
|
||||
output_queue.put_nowait({
|
||||
'type': 'scope',
|
||||
'rms': rms,
|
||||
'peak': peak,
|
||||
})
|
||||
except (struct.error, ValueError, queue.Full):
|
||||
pass
|
||||
peak = max(abs(s) for s in samples)
|
||||
rms = int(math.sqrt(sum(s * s for s in samples) / n_samples))
|
||||
output_queue.put_nowait({
|
||||
'type': 'scope',
|
||||
'rms': rms,
|
||||
'peak': peak,
|
||||
'waveform': _encode_scope_waveform(samples),
|
||||
})
|
||||
except (struct.error, ValueError, queue.Full):
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.debug(f"Audio relay error: {e}")
|
||||
finally:
|
||||
|
||||
+64
-25
@@ -1,14 +1,15 @@
|
||||
"""RTL_433 sensor monitoring routes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import queue
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Generator
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
import queue
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any, Generator
|
||||
|
||||
from flask import Blueprint, jsonify, request, Response
|
||||
|
||||
@@ -28,12 +29,42 @@ sensor_bp = Blueprint('sensor', __name__)
|
||||
# Track which device is being used
|
||||
sensor_active_device: int | None = None
|
||||
|
||||
# RSSI history per device (model_id -> list of (timestamp, rssi))
|
||||
sensor_rssi_history: dict[str, list[tuple[float, float]]] = {}
|
||||
_MAX_RSSI_HISTORY = 60
|
||||
|
||||
|
||||
def stream_sensor_output(process: subprocess.Popen[bytes]) -> None:
|
||||
# RSSI history per device (model_id -> list of (timestamp, rssi))
|
||||
sensor_rssi_history: dict[str, list[tuple[float, float]]] = {}
|
||||
_MAX_RSSI_HISTORY = 60
|
||||
|
||||
|
||||
def _build_scope_waveform(rssi: float, snr: float, noise: float, points: int = 256) -> list[int]:
|
||||
"""Synthesize a compact waveform from rtl_433 level metrics."""
|
||||
points = max(32, min(points, 512))
|
||||
|
||||
# rssi is usually negative; stronger signals are closer to 0 dBm.
|
||||
rssi_norm = min(max(abs(rssi) / 40.0, 0.0), 1.0)
|
||||
snr_norm = min(max((snr + 5.0) / 35.0, 0.0), 1.0)
|
||||
noise_norm = min(max(abs(noise) / 40.0, 0.0), 1.0)
|
||||
|
||||
amplitude = max(0.06, min(1.0, (0.6 * rssi_norm + 0.4 * snr_norm) - (0.22 * noise_norm)))
|
||||
cycles = 3.0 + (snr_norm * 8.0)
|
||||
harmonic = 0.25 + (0.35 * snr_norm)
|
||||
hiss = 0.08 + (0.18 * noise_norm)
|
||||
phase = (time.monotonic() * (1.4 + (snr_norm * 2.2))) % (2.0 * math.pi)
|
||||
|
||||
waveform: list[int] = []
|
||||
for i in range(points):
|
||||
t = i / (points - 1)
|
||||
base = math.sin((2.0 * math.pi * cycles * t) + phase)
|
||||
overtone = math.sin((2.0 * math.pi * (cycles * 2.4) * t) + (phase * 0.7))
|
||||
noise_wobble = math.sin((2.0 * math.pi * (cycles * 7.0) * t) + (phase * 2.1))
|
||||
|
||||
sample = amplitude * (base + (harmonic * overtone) + (hiss * noise_wobble))
|
||||
sample /= (1.0 + harmonic + hiss)
|
||||
packed = int(round(max(-1.0, min(1.0, sample)) * 127.0))
|
||||
waveform.append(max(-127, min(127, packed)))
|
||||
|
||||
return waveform
|
||||
|
||||
|
||||
def stream_sensor_output(process: subprocess.Popen[bytes]) -> None:
|
||||
"""Stream rtl_433 JSON output to queue."""
|
||||
try:
|
||||
app_module.sensor_queue.put({'type': 'status', 'text': 'started'})
|
||||
@@ -64,16 +95,24 @@ def stream_sensor_output(process: subprocess.Popen[bytes]) -> None:
|
||||
rssi = data.get('rssi')
|
||||
snr = data.get('snr')
|
||||
noise = data.get('noise')
|
||||
if rssi is not None or snr is not None:
|
||||
try:
|
||||
app_module.sensor_queue.put_nowait({
|
||||
'type': 'scope',
|
||||
'rssi': rssi if rssi is not None else 0,
|
||||
'snr': snr if snr is not None else 0,
|
||||
'noise': noise if noise is not None else 0,
|
||||
})
|
||||
except queue.Full:
|
||||
pass
|
||||
if rssi is not None or snr is not None:
|
||||
try:
|
||||
rssi_value = float(rssi) if rssi is not None else 0.0
|
||||
snr_value = float(snr) if snr is not None else 0.0
|
||||
noise_value = float(noise) if noise is not None else 0.0
|
||||
app_module.sensor_queue.put_nowait({
|
||||
'type': 'scope',
|
||||
'rssi': rssi_value,
|
||||
'snr': snr_value,
|
||||
'noise': noise_value,
|
||||
'waveform': _build_scope_waveform(
|
||||
rssi=rssi_value,
|
||||
snr=snr_value,
|
||||
noise=noise_value,
|
||||
),
|
||||
})
|
||||
except (TypeError, ValueError, queue.Full):
|
||||
pass
|
||||
|
||||
# Log if enabled
|
||||
if app_module.logging_enabled:
|
||||
|
||||
Reference in New Issue
Block a user