Remove waterfall from all modes except listening post

Reverts IQ pipeline and removes syncWaterfallToFrequency calls from
pager, sensor, rtlamr, DMR, SSTV, and SSTV general modes. Waterfall
is now exclusive to listening post mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-02-07 23:29:56 +00:00
parent f04ba7f143
commit beb38b6b98
13 changed files with 172 additions and 838 deletions
-17
View File
@@ -1725,16 +1725,6 @@ def start_waterfall() -> Response:
"""Start the waterfall/spectrogram display."""
global waterfall_thread, waterfall_running, waterfall_config, waterfall_active_device
# Check if a decoder is already producing FFT data via IQ pipeline
if app_module.waterfall_source in ('pager', 'sensor'):
# Decoder-driven waterfall: data is already flowing into waterfall_queue
waterfall_running = True
return jsonify({
'status': 'started',
'source': 'decoder',
'decoder': app_module.waterfall_source,
})
with waterfall_lock:
if waterfall_running:
return jsonify({'status': 'error', 'message': 'Waterfall already running'}), 409
@@ -1789,13 +1779,6 @@ def start_waterfall() -> Response:
@listening_post_bp.route('/waterfall/stop', methods=['POST'])
def stop_waterfall() -> Response:
"""Stop the waterfall display."""
global waterfall_running
# If waterfall is decoder-driven, just disconnect (don't stop the decoder)
if app_module.waterfall_source in ('pager', 'sensor'):
waterfall_running = False
return jsonify({'status': 'stopped', 'source': 'decoder'})
_stop_waterfall_internal()
return jsonify({'status': 'stopped'})
+99 -210
View File
@@ -22,8 +22,8 @@ from utils.validation import (
validate_frequency, validate_device_index, validate_gain, validate_ppm,
validate_rtl_tcp_host, validate_rtl_tcp_port
)
from utils.sse import format_sse
from utils.event_pipeline import process_event
from utils.sse import format_sse
from utils.event_pipeline import process_event
from utils.process import safe_terminate, register_process, unregister_process
from utils.sdr import SDRFactory, SDRType, SDRValidationError
from utils.dependencies import get_tool_path
@@ -32,8 +32,6 @@ pager_bp = Blueprint('pager', __name__)
# Track which device is being used
pager_active_device: int | None = None
# IQ pipeline stop event (set to signal IQ processor thread to exit)
pager_iq_stop_event: threading.Event | None = None
def parse_multimon_output(line: str) -> dict[str, str] | None:
@@ -149,18 +147,12 @@ def stream_decoder(master_fd: int, process: subprocess.Popen[bytes]) -> None:
except Exception as e:
app_module.output_queue.put({'type': 'error', 'text': str(e)})
finally:
global pager_active_device, pager_iq_stop_event
global pager_active_device
try:
os.close(master_fd)
except OSError:
pass
# Stop IQ pipeline if running
if pager_iq_stop_event is not None:
pager_iq_stop_event.set()
pager_iq_stop_event = None
if app_module.waterfall_source == 'pager':
app_module.waterfall_source = None
# Cleanup companion rtl_sdr/rtl_fm process and decoder
# Cleanup companion rtl_fm process and decoder
with app_module.process_lock:
rtl_proc = getattr(app_module.current_process, '_rtl_process', None)
for proc in [rtl_proc, process]:
@@ -183,28 +175,6 @@ def stream_decoder(master_fd: int, process: subprocess.Popen[bytes]) -> None:
pager_active_device = None
def _cleanup_failed_start(rtl_process: subprocess.Popen | None) -> None:
"""Clean up after a failed start attempt."""
global pager_active_device, pager_iq_stop_event
if rtl_process:
try:
rtl_process.terminate()
rtl_process.wait(timeout=2)
except Exception:
try:
rtl_process.kill()
except Exception:
pass
if pager_iq_stop_event is not None:
pager_iq_stop_event.set()
pager_iq_stop_event = None
if app_module.waterfall_source == 'pager':
app_module.waterfall_source = None
if pager_active_device is not None:
app_module.release_sdr_device(pager_active_device)
pager_active_device = None
@pager_bp.route('/start', methods=['POST'])
def start_decoding() -> Response:
global pager_active_device
@@ -302,196 +272,115 @@ def start_decoding() -> Response:
builder = SDRFactory.get_builder(sdr_device.sdr_type)
# Build FM demodulation command
bias_t = data.get('bias_t', False)
rtl_cmd = builder.build_fm_demod_command(
device=sdr_device,
frequency_mhz=freq,
sample_rate=22050,
gain=float(gain) if gain and gain != '0' else None,
ppm=int(ppm) if ppm and ppm != '0' else None,
modulation='fm',
squelch=squelch if squelch and squelch != 0 else None,
bias_t=bias_t
)
multimon_path = get_tool_path('multimon-ng')
if not multimon_path:
if pager_active_device is not None:
app_module.release_sdr_device(pager_active_device)
pager_active_device = None
return jsonify({'status': 'error', 'message': 'multimon-ng not found'}), 400
multimon_cmd = [multimon_path, '-t', 'raw'] + decoders + ['-f', 'alpha', '-']
bias_t = data.get('bias_t', False)
gain_val = float(gain) if gain and gain != '0' else None
ppm_val = int(ppm) if ppm and ppm != '0' else None
full_cmd = ' '.join(rtl_cmd) + ' | ' + ' '.join(multimon_cmd)
logger.info(f"Running: {full_cmd}")
# Determine if we can use IQ pipeline for live waterfall
use_iq_pipeline = (
sdr_type == SDRType.RTL_SDR
and not rtl_tcp_host
and get_tool_path('rtl_sdr') is not None
)
if use_iq_pipeline:
# IQ pipeline: rtl_sdr -> Python IQ processor -> multimon-ng
iq_sample_rate = 220500 # 22050 * 10 for exact decimation
rtl_cmd = builder.build_raw_capture_command(
device=sdr_device,
frequency_mhz=freq,
sample_rate=iq_sample_rate,
gain=gain_val,
ppm=ppm_val,
bias_t=bias_t,
try:
# Create pipe: rtl_fm | multimon-ng
rtl_process = subprocess.Popen(
rtl_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
register_process(rtl_process)
full_cmd = ' '.join(rtl_cmd) + ' | [iq_processor] | ' + ' '.join(multimon_cmd)
logger.info(f"Running (IQ pipeline): {full_cmd}")
# Start a thread to monitor rtl_fm stderr for errors
def monitor_rtl_stderr():
for line in rtl_process.stderr:
err_text = line.decode('utf-8', errors='replace').strip()
if err_text:
logger.debug(f"[RTL_FM] {err_text}")
app_module.output_queue.put({'type': 'raw', 'text': f'[rtl_fm] {err_text}'})
try:
rtl_process = subprocess.Popen(
rtl_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
register_process(rtl_process)
rtl_stderr_thread = threading.Thread(target=monitor_rtl_stderr)
rtl_stderr_thread.daemon = True
rtl_stderr_thread.start()
# Monitor rtl_sdr stderr
def monitor_rtl_stderr():
for line in rtl_process.stderr:
err_text = line.decode('utf-8', errors='replace').strip()
if err_text:
logger.debug(f"[rtl_sdr] {err_text}")
app_module.output_queue.put({'type': 'raw', 'text': f'[rtl_sdr] {err_text}'})
# Create a pseudo-terminal for multimon-ng output
master_fd, slave_fd = pty.openpty()
rtl_stderr_thread = threading.Thread(target=monitor_rtl_stderr, daemon=True)
rtl_stderr_thread.start()
# Create PTY for multimon-ng output
master_fd, slave_fd = pty.openpty()
multimon_process = subprocess.Popen(
multimon_cmd,
stdin=subprocess.PIPE,
stdout=slave_fd,
stderr=slave_fd,
close_fds=True,
)
register_process(multimon_process)
os.close(slave_fd)
# Start IQ processor thread
from routes.listening_post import waterfall_queue
from utils.iq_processor import run_fm_iq_pipeline
stop_event = threading.Event()
pager_iq_stop_event = stop_event
app_module.waterfall_source = 'pager'
iq_thread = threading.Thread(
target=run_fm_iq_pipeline,
args=(
rtl_process.stdout,
multimon_process.stdin,
waterfall_queue,
freq,
iq_sample_rate,
stop_event,
),
daemon=True,
)
iq_thread.start()
app_module.current_process = multimon_process
app_module.current_process._rtl_process = rtl_process
app_module.current_process._master_fd = master_fd
# Start decoder output thread
thread = threading.Thread(target=stream_decoder, args=(master_fd, multimon_process), daemon=True)
thread.start()
app_module.output_queue.put({'type': 'info', 'text': f'Command: {full_cmd}'})
return jsonify({'status': 'started', 'command': full_cmd, 'waterfall_source': 'pager'})
except FileNotFoundError as e:
_cleanup_failed_start(rtl_process)
return jsonify({'status': 'error', 'message': f'Tool not found: {e.filename}'})
except Exception as e:
_cleanup_failed_start(rtl_process)
return jsonify({'status': 'error', 'message': str(e)})
else:
# Legacy pipeline: rtl_fm | multimon-ng
rtl_cmd = builder.build_fm_demod_command(
device=sdr_device,
frequency_mhz=freq,
sample_rate=22050,
gain=gain_val,
ppm=ppm_val,
modulation='fm',
squelch=squelch if squelch and squelch != 0 else None,
bias_t=bias_t,
multimon_process = subprocess.Popen(
multimon_cmd,
stdin=rtl_process.stdout,
stdout=slave_fd,
stderr=slave_fd,
close_fds=True
)
register_process(multimon_process)
full_cmd = ' '.join(rtl_cmd) + ' | ' + ' '.join(multimon_cmd)
logger.info(f"Running: {full_cmd}")
os.close(slave_fd)
rtl_process.stdout.close()
app_module.current_process = multimon_process
app_module.current_process._rtl_process = rtl_process
app_module.current_process._master_fd = master_fd
# Start output thread with PTY master fd
thread = threading.Thread(target=stream_decoder, args=(master_fd, multimon_process))
thread.daemon = True
thread.start()
app_module.output_queue.put({'type': 'info', 'text': f'Command: {full_cmd}'})
return jsonify({'status': 'started', 'command': full_cmd})
except FileNotFoundError as e:
# Kill orphaned rtl_fm process
try:
rtl_process = subprocess.Popen(
rtl_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
register_process(rtl_process)
# Start a thread to monitor rtl_fm stderr for errors
def monitor_rtl_stderr():
for line in rtl_process.stderr:
err_text = line.decode('utf-8', errors='replace').strip()
if err_text:
logger.debug(f"[RTL_FM] {err_text}")
app_module.output_queue.put({'type': 'raw', 'text': f'[rtl_fm] {err_text}'})
rtl_stderr_thread = threading.Thread(target=monitor_rtl_stderr, daemon=True)
rtl_stderr_thread.start()
# Create a pseudo-terminal for multimon-ng output
master_fd, slave_fd = pty.openpty()
multimon_process = subprocess.Popen(
multimon_cmd,
stdin=rtl_process.stdout,
stdout=slave_fd,
stderr=slave_fd,
close_fds=True,
)
register_process(multimon_process)
os.close(slave_fd)
rtl_process.stdout.close()
app_module.current_process = multimon_process
app_module.current_process._rtl_process = rtl_process
app_module.current_process._master_fd = master_fd
# Start output thread with PTY master fd
thread = threading.Thread(target=stream_decoder, args=(master_fd, multimon_process), daemon=True)
thread.start()
app_module.output_queue.put({'type': 'info', 'text': f'Command: {full_cmd}'})
return jsonify({'status': 'started', 'command': full_cmd})
except FileNotFoundError as e:
_cleanup_failed_start(rtl_process)
return jsonify({'status': 'error', 'message': f'Tool not found: {e.filename}'})
except Exception as e:
_cleanup_failed_start(rtl_process)
return jsonify({'status': 'error', 'message': str(e)})
rtl_process.terminate()
rtl_process.wait(timeout=2)
except Exception:
try:
rtl_process.kill()
except Exception:
pass
# Release device on failure
if pager_active_device is not None:
app_module.release_sdr_device(pager_active_device)
pager_active_device = None
return jsonify({'status': 'error', 'message': f'Tool not found: {e.filename}'})
except Exception as e:
# Kill orphaned rtl_fm process if it was started
try:
rtl_process.terminate()
rtl_process.wait(timeout=2)
except Exception:
try:
rtl_process.kill()
except Exception:
pass
# Release device on failure
if pager_active_device is not None:
app_module.release_sdr_device(pager_active_device)
pager_active_device = None
return jsonify({'status': 'error', 'message': str(e)})
@pager_bp.route('/stop', methods=['POST'])
def stop_decoding() -> Response:
global pager_active_device, pager_iq_stop_event
global pager_active_device
with app_module.process_lock:
if app_module.current_process:
# Stop IQ pipeline if running
if pager_iq_stop_event is not None:
pager_iq_stop_event.set()
pager_iq_stop_event = None
if app_module.waterfall_source == 'pager':
app_module.waterfall_source = None
# Kill rtl_sdr/rtl_fm process first
# Kill rtl_fm process first
if hasattr(app_module.current_process, '_rtl_process'):
try:
app_module.current_process._rtl_process.terminate()
@@ -580,14 +469,14 @@ def stream() -> Response:
keepalive_interval = 30.0 # Send keepalive every 30 seconds instead of 1 second
while True:
try:
msg = app_module.output_queue.get(timeout=1)
last_keepalive = time.time()
try:
process_event('pager', msg, msg.get('type'))
except Exception:
pass
yield format_sse(msg)
try:
msg = app_module.output_queue.get(timeout=1)
last_keepalive = time.time()
try:
process_event('pager', msg, msg.get('type'))
except Exception:
pass
yield format_sse(msg)
except queue.Empty:
now = time.time()
if now - last_keepalive >= keepalive_interval:
+57 -221
View File
@@ -18,20 +18,15 @@ from utils.validation import (
validate_frequency, validate_device_index, validate_gain, validate_ppm,
validate_rtl_tcp_host, validate_rtl_tcp_port
)
from utils.sse import format_sse
from utils.event_pipeline import process_event
from utils.sse import format_sse
from utils.event_pipeline import process_event
from utils.process import safe_terminate, register_process, unregister_process
from utils.sdr import SDRFactory, SDRType
from utils.dependencies import get_tool_path
sensor_bp = Blueprint('sensor', __name__)
# Track which device is being used
sensor_active_device: int | None = None
# IQ pipeline stop event
sensor_iq_stop_event: threading.Event | None = None
# Companion rtl_sdr process when using IQ pipeline
sensor_rtl_process: subprocess.Popen | None = None
def stream_sensor_output(process: subprocess.Popen[bytes]) -> None:
@@ -65,26 +60,8 @@ def stream_sensor_output(process: subprocess.Popen[bytes]) -> None:
except Exception as e:
app_module.sensor_queue.put({'type': 'error', 'text': str(e)})
finally:
global sensor_active_device, sensor_iq_stop_event, sensor_rtl_process
# Stop IQ pipeline if running
if sensor_iq_stop_event is not None:
sensor_iq_stop_event.set()
sensor_iq_stop_event = None
if app_module.waterfall_source == 'sensor':
app_module.waterfall_source = None
# Terminate companion rtl_sdr process
if sensor_rtl_process is not None:
try:
sensor_rtl_process.terminate()
sensor_rtl_process.wait(timeout=2)
except Exception:
try:
sensor_rtl_process.kill()
except Exception:
pass
unregister_process(sensor_rtl_process)
sensor_rtl_process = None
# Ensure decoder process is terminated
global sensor_active_device
# Ensure process is terminated
try:
process.terminate()
process.wait(timeout=2)
@@ -103,32 +80,9 @@ def stream_sensor_output(process: subprocess.Popen[bytes]) -> None:
sensor_active_device = None
def _cleanup_sensor_failed_start(rtl_process: subprocess.Popen | None) -> None:
"""Clean up after a failed sensor start attempt."""
global sensor_active_device, sensor_iq_stop_event, sensor_rtl_process
if rtl_process:
try:
rtl_process.terminate()
rtl_process.wait(timeout=2)
except Exception:
try:
rtl_process.kill()
except Exception:
pass
if sensor_iq_stop_event is not None:
sensor_iq_stop_event.set()
sensor_iq_stop_event = None
if app_module.waterfall_source == 'sensor':
app_module.waterfall_source = None
sensor_rtl_process = None
if sensor_active_device is not None:
app_module.release_sdr_device(sensor_active_device)
sensor_active_device = None
@sensor_bp.route('/start_sensor', methods=['POST'])
def start_sensor() -> Response:
global sensor_active_device, sensor_iq_stop_event, sensor_rtl_process
global sensor_active_device
with app_module.sensor_lock:
if app_module.sensor_process:
@@ -190,187 +144,69 @@ def start_sensor() -> Response:
sdr_device = SDRFactory.create_default_device(sdr_type, index=device)
builder = SDRFactory.get_builder(sdr_device.sdr_type)
bias_t = data.get('bias_t', False)
gain_val = float(gain) if gain and gain != 0 else None
ppm_val = int(ppm) if ppm and ppm != 0 else None
# Determine if we can use IQ pipeline for live waterfall
use_iq_pipeline = (
sdr_type == SDRType.RTL_SDR
and not rtl_tcp_host
and get_tool_path('rtl_sdr') is not None
# Build ISM band decoder command
bias_t = data.get('bias_t', False)
cmd = builder.build_ism_command(
device=sdr_device,
frequency_mhz=freq,
gain=float(gain) if gain and gain != 0 else None,
ppm=int(ppm) if ppm and ppm != 0 else None,
bias_t=bias_t
)
if use_iq_pipeline:
# IQ pipeline: rtl_sdr -> Python IQ tee -> rtl_433 -r -
iq_sample_rate = 250000 # rtl_433 default
full_cmd = ' '.join(cmd)
logger.info(f"Running: {full_cmd}")
rtl_cmd = builder.build_raw_capture_command(
device=sdr_device,
frequency_mhz=freq,
sample_rate=iq_sample_rate,
gain=gain_val,
ppm=ppm_val,
bias_t=bias_t,
try:
app_module.sensor_process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
register_process(app_module.sensor_process)
rtl_433_path = get_tool_path('rtl_433') or 'rtl_433'
decoder_cmd = [rtl_433_path, '-r', '-', '-s', str(iq_sample_rate), '-F', 'json']
# Start output thread
thread = threading.Thread(target=stream_sensor_output, args=(app_module.sensor_process,))
thread.daemon = True
thread.start()
full_cmd = ' '.join(rtl_cmd) + ' | [iq_processor] | ' + ' '.join(decoder_cmd)
logger.info(f"Running (IQ pipeline): {full_cmd}")
# Monitor stderr
def monitor_stderr():
for line in app_module.sensor_process.stderr:
err = line.decode('utf-8', errors='replace').strip()
if err:
logger.debug(f"[rtl_433] {err}")
app_module.sensor_queue.put({'type': 'info', 'text': f'[rtl_433] {err}'})
try:
rtl_process = subprocess.Popen(
rtl_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
register_process(rtl_process)
sensor_rtl_process = rtl_process
stderr_thread = threading.Thread(target=monitor_stderr)
stderr_thread.daemon = True
stderr_thread.start()
# Monitor rtl_sdr stderr
def monitor_rtl_stderr():
for line in rtl_process.stderr:
err = line.decode('utf-8', errors='replace').strip()
if err:
logger.debug(f"[rtl_sdr] {err}")
app_module.sensor_queue.put({'type': 'info', 'text': f'[rtl_sdr] {err}'})
app_module.sensor_queue.put({'type': 'info', 'text': f'Command: {full_cmd}'})
threading.Thread(target=monitor_rtl_stderr, daemon=True).start()
return jsonify({'status': 'started', 'command': full_cmd})
# Start rtl_433 reading from stdin
decoder_process = subprocess.Popen(
decoder_cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
register_process(decoder_process)
# Start IQ processor thread
from routes.listening_post import waterfall_queue
from utils.iq_processor import run_passthrough_iq_pipeline
stop_event = threading.Event()
sensor_iq_stop_event = stop_event
app_module.waterfall_source = 'sensor'
iq_thread = threading.Thread(
target=run_passthrough_iq_pipeline,
args=(
rtl_process.stdout,
decoder_process.stdin,
waterfall_queue,
freq,
iq_sample_rate,
stop_event,
),
daemon=True,
)
iq_thread.start()
app_module.sensor_process = decoder_process
# Monitor rtl_433 stderr
def monitor_decoder_stderr():
for line in decoder_process.stderr:
err = line.decode('utf-8', errors='replace').strip()
if err:
logger.debug(f"[rtl_433] {err}")
app_module.sensor_queue.put({'type': 'info', 'text': f'[rtl_433] {err}'})
threading.Thread(target=monitor_decoder_stderr, daemon=True).start()
# Start output thread
thread = threading.Thread(target=stream_sensor_output, args=(decoder_process,), daemon=True)
thread.start()
app_module.sensor_queue.put({'type': 'info', 'text': f'Command: {full_cmd}'})
return jsonify({'status': 'started', 'command': full_cmd, 'waterfall_source': 'sensor'})
except FileNotFoundError:
_cleanup_sensor_failed_start(rtl_process)
return jsonify({'status': 'error', 'message': 'rtl_sdr or rtl_433 not found'})
except Exception as e:
_cleanup_sensor_failed_start(rtl_process)
return jsonify({'status': 'error', 'message': str(e)})
else:
# Legacy pipeline: rtl_433 directly
cmd = builder.build_ism_command(
device=sdr_device,
frequency_mhz=freq,
gain=gain_val,
ppm=ppm_val,
bias_t=bias_t,
)
full_cmd = ' '.join(cmd)
logger.info(f"Running: {full_cmd}")
try:
app_module.sensor_process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
register_process(app_module.sensor_process)
# Start output thread
thread = threading.Thread(target=stream_sensor_output, args=(app_module.sensor_process,), daemon=True)
thread.start()
# Monitor stderr
def monitor_stderr():
for line in app_module.sensor_process.stderr:
err = line.decode('utf-8', errors='replace').strip()
if err:
logger.debug(f"[rtl_433] {err}")
app_module.sensor_queue.put({'type': 'info', 'text': f'[rtl_433] {err}'})
threading.Thread(target=monitor_stderr, daemon=True).start()
app_module.sensor_queue.put({'type': 'info', 'text': f'Command: {full_cmd}'})
return jsonify({'status': 'started', 'command': full_cmd})
except FileNotFoundError:
if sensor_active_device is not None:
app_module.release_sdr_device(sensor_active_device)
sensor_active_device = None
return jsonify({'status': 'error', 'message': 'rtl_433 not found. Install with: brew install rtl_433'})
except Exception as e:
if sensor_active_device is not None:
app_module.release_sdr_device(sensor_active_device)
sensor_active_device = None
return jsonify({'status': 'error', 'message': str(e)})
except FileNotFoundError:
# Release device on failure
if sensor_active_device is not None:
app_module.release_sdr_device(sensor_active_device)
sensor_active_device = None
return jsonify({'status': 'error', 'message': 'rtl_433 not found. Install with: brew install rtl_433'})
except Exception as e:
# Release device on failure
if sensor_active_device is not None:
app_module.release_sdr_device(sensor_active_device)
sensor_active_device = None
return jsonify({'status': 'error', 'message': str(e)})
@sensor_bp.route('/stop_sensor', methods=['POST'])
def stop_sensor() -> Response:
global sensor_active_device, sensor_iq_stop_event, sensor_rtl_process
global sensor_active_device
with app_module.sensor_lock:
if app_module.sensor_process:
# Stop IQ pipeline if running
if sensor_iq_stop_event is not None:
sensor_iq_stop_event.set()
sensor_iq_stop_event = None
if app_module.waterfall_source == 'sensor':
app_module.waterfall_source = None
# Kill companion rtl_sdr process
if sensor_rtl_process is not None:
try:
sensor_rtl_process.terminate()
sensor_rtl_process.wait(timeout=2)
except (subprocess.TimeoutExpired, OSError):
try:
sensor_rtl_process.kill()
except OSError:
pass
sensor_rtl_process = None
app_module.sensor_process.terminate()
try:
app_module.sensor_process.wait(timeout=2)
@@ -396,13 +232,13 @@ def stream_sensor() -> Response:
while True:
try:
msg = app_module.sensor_queue.get(timeout=1)
last_keepalive = time.time()
try:
process_event('sensor', msg, msg.get('type'))
except Exception:
pass
yield format_sse(msg)
msg = app_module.sensor_queue.get(timeout=1)
last_keepalive = time.time()
try:
process_event('sensor', msg, msg.get('type'))
except Exception:
pass
yield format_sse(msg)
except queue.Empty:
now = time.time()
if now - last_keepalive >= keepalive_interval: