""" RTL-SDR command builder implementation. Uses native rtl_* tools (rtl_fm, rtl_433) and dump1090 for maximum compatibility with existing RTL-SDR installations. No SoapySDR dependency required. """ from __future__ import annotations from typing import Optional from .base import CommandBuilder, SDRCapabilities, SDRDevice, SDRType from utils.dependencies import get_tool_path class RTLSDRCommandBuilder(CommandBuilder): """RTL-SDR command builder using native rtl_* tools.""" CAPABILITIES = SDRCapabilities( sdr_type=SDRType.RTL_SDR, freq_min_mhz=24.0, freq_max_mhz=1766.0, gain_min=0.0, gain_max=49.6, sample_rates=[250000, 1024000, 1800000, 2048000, 2400000], supports_bias_t=True, supports_ppm=True, tx_capable=False ) def _get_device_arg(self, device: SDRDevice) -> str: """Get device argument for rtl_* tools. Returns rtl_tcp connection string for network devices, or device index for local devices. """ if device.is_network: return f"rtl_tcp:{device.rtl_tcp_host}:{device.rtl_tcp_port}" return str(device.index) def build_fm_demod_command( self, device: SDRDevice, frequency_mhz: float, sample_rate: int = 22050, gain: Optional[float] = None, ppm: Optional[int] = None, modulation: str = "fm", squelch: Optional[int] = None, bias_t: bool = False ) -> list[str]: """ Build rtl_fm command for FM demodulation. Used for pager decoding. Supports local devices and rtl_tcp connections. """ rtl_fm_path = get_tool_path('rtl_fm') or 'rtl_fm' cmd = [ rtl_fm_path, '-d', self._get_device_arg(device), '-f', f'{frequency_mhz}M', '-M', modulation, '-s', str(sample_rate), ] if gain is not None and gain > 0: cmd.extend(['-g', str(gain)]) if ppm is not None and ppm != 0: cmd.extend(['-p', str(ppm)]) if squelch is not None and squelch > 0: cmd.extend(['-l', str(squelch)]) if bias_t: cmd.extend(['-T']) # Output to stdout for piping cmd.append('-') return cmd def build_adsb_command( self, device: SDRDevice, gain: Optional[float] = None, bias_t: bool = False ) -> list[str]: """ Build dump1090 command for ADS-B decoding. Uses dump1090 with network output for SBS data streaming. Note: dump1090 does not support rtl_tcp. For remote SDR, connect to a remote dump1090's SBS output (port 30003) instead. """ if device.is_network: raise ValueError( "dump1090 does not support rtl_tcp. " "For remote ADS-B, run dump1090 on the remote machine and " "connect to its SBS output (port 30003)." ) dump1090_path = get_tool_path('dump1090') or 'dump1090' cmd = [ dump1090_path, '--net', '--device-index', str(device.index), '--quiet' ] if gain is not None: cmd.extend(['--gain', str(int(gain))]) if bias_t: cmd.extend(['--enable-bias-t']) return cmd def build_ism_command( self, device: SDRDevice, frequency_mhz: float = 433.92, gain: Optional[float] = None, ppm: Optional[int] = None, bias_t: bool = False ) -> list[str]: """ Build rtl_433 command for ISM band sensor decoding. Outputs JSON for easy parsing. Supports local devices and rtl_tcp connections. Note: rtl_433's -T flag is for timeout, NOT bias-t. Bias-t is enabled via the device string suffix :biast=1 """ rtl_433_path = get_tool_path('rtl_433') or 'rtl_433' # Build device argument with optional bias-t suffix # rtl_433 uses :biast=1 suffix on device string, not -T flag # (-T is timeout in rtl_433) device_arg = self._get_device_arg(device) if bias_t: device_arg = f'{device_arg}:biast=1' cmd = [ rtl_433_path, '-d', device_arg, '-f', f'{frequency_mhz}M', '-F', 'json' ] if gain is not None and gain > 0: cmd.extend(['-g', str(int(gain))]) if ppm is not None and ppm != 0: cmd.extend(['-p', str(ppm)]) return cmd def build_ais_command( self, device: SDRDevice, gain: Optional[float] = None, bias_t: bool = False, tcp_port: int = 10110 ) -> list[str]: """ Build AIS-catcher command for AIS vessel tracking. Uses AIS-catcher with TCP JSON output for real-time vessel data. AIS operates on 161.975 MHz and 162.025 MHz (handled automatically). """ if device.is_network: raise ValueError( "AIS-catcher does not support rtl_tcp. " "For remote AIS, run AIS-catcher on the remote machine." ) cmd = [ 'AIS-catcher', f'-d:{device.index}', # Device index (colon format required) '-S', str(tcp_port), # TCP server with JSON output '-o', '5', # JSON output format '-q', # Quiet mode (less console output) ] if gain is not None and gain > 0: cmd.extend(['-gr', 'TUNER', str(int(gain))]) if bias_t: cmd.extend(['-gr', 'BIASTEE', 'on']) return cmd def get_capabilities(self) -> SDRCapabilities: """Return RTL-SDR capabilities.""" return self.CAPABILITIES @classmethod def get_sdr_type(cls) -> SDRType: """Return SDR type.""" return SDRType.RTL_SDR