bin/pwn_gqrx_scanner in pwn-0.5.52 vs bin/pwn_gqrx_scanner in pwn-0.5.53

- old
+ new

@@ -13,121 +13,225 @@ options.on('-tFREQ', '--target-freq=FREQ', '<Required - Frequency to Conclude Scanning (e.g. 900000000 == 900 mHz>') do |e| opts[:target_freq] = e end + options.on('-sFREQ', '--start-freq=FREQ', '<Optional - Frequency to Set when Scanning Begins (Defaults to last known frequency)>') do |s| + opts[:start_freq] = s + end + options.on('-hHOST', '--host=HOST', '<Optional - GQRX Host (Defaults to 127.0.0.1)>') do |h| opts[:host] = h end options.on('-pPORT', '--port=PORT', '<Optional - GQRX Port (Defaults to 7356)>') do |p| opts[:port] = p end - options.on('-dMODE', '--demodulator-mode=MODE', '<Optional - Set Demodulator ModeOFF | RAW | AM | FM | WFM | WFM_ST | WFM_ST_OIRT | LSB |USB | CW | CWL | CWU (Defaults to AM)>') do |d| + options.on('-AFLOAT', '--audio-gain=FLOAT', '<Optional - Set audio gain -80.0 to 50.0 (Defaults to 1.0)>') do |a| + opts[:audio_gain_db] = a + end + + options.on('-BHZ', '--bandwidth=HZ', '<Optional - Set Bandwidth (Defaults to 240.000)>') do |b| + opts[:bandwidth] = b + end + + options.on('-DMODE', '--demodulator-mode=MODE', '<Optional - Set Demodulator ModeOFF | RAW | AM | FM | WFM | WFM_ST | WFM_ST_OIRT | LSB |USB | CW | CWL | CWU (Defaults to WFM_ST)>') do |d| opts[:demodulator_mode] = d end - options.on('-sFREQ', '--start-freq=FREQ', '<Optional - Frequency to Set when Scanning Begins (Defaults to last known frequency)>') do |s| - opts[:start_freq] = s + options.on('-LFLOAT', '--lock-freq-duration=FLOAT', '<Optional - Duration to lock onto Freqency when Strength < --strength value (Defaults to 3)>') do |l| + opts[:lock_on_freq_duration] = l end - options.on('-qFLOAT', '--squelch=FLOAT', '<Optional - Squelch Threshold (Defaults to -63)>') do |q| + options.on('-QFLOAT', '--squelch=FLOAT', '<Optional - Squelch Threshold -150 to 0(Defaults to -63.0)>') do |q| opts[:squelch] = q end - options.on('-PPLACE', '--precision=PLACE', '<Optional - Precision of Frequency 1-12 (Defaults to 3)>') do |p| + options.on('-PINT', '--precision=INT', '<Optional - Precision of Frequency 1-12 (Defaults to 6)>') do |p| opts[:precision] = p end - options.on('-LFLOAT', '--lock-on-freq-duration=FLOAT', '<Optional - Duration to lock onto Freqency when Strength < --strength value (Defaults to -63)>') do |l| - opts[:lock_on_freq_duration] = l - end - - options.on('-SFLOAT', '--strength=FLOAT', '<Optional - Strength to trigger sleep (Defaults to 0)>') do |s| + options.on('-SFLOAT', '--strength=FLOAT', '<Optional - Strength to trigger sleep (Defaults to -63.3)>') do |s| opts[:strength] = s end end.parse! if opts.empty? puts `#{$PROGRAM_NAME} --help` exit 1 end def gqrx_cmd(opts = {}) - # f - Get frequency [Hz] - # F - Set frequency [Hz] - # m - Get demodulator mode - # M - Set demodulator mode (OFF, RAW, AM, FM, WFM, WFM_ST, - # WFM_ST_OIRT, LSB, USB, CW, CWL, CWU) - # l STRENGTH - Get signal strength [dBFS] - # l SQL - Get squelch threshold [dBFS] - # L SQL <sql> - Set squelch threshold to <sql> [dBFS] - # u RECORD - Get status of audio recorder - # U RECORD <status> - Set status of audio recorder to <status> - # c - Close connection - # AOS - Acquisition of signal (AOS) event, start audio recording - # LOS - Loss of signal (LOS) event, stop audio recording - # \dump_state - Dump state (only usable for compatibility) gqrx_sock = opts[:gqrx_sock] cmd = opts[:cmd] + resp_ok = opts[:resp_ok] + # Most Recent GQRX Command Set: + # https://raw.githubusercontent.com/gqrx-sdr/gqrx/master/resources/remote-control.txt + # Supported commands: + # f Get frequency [Hz] + # F <frequency> Set frequency [Hz] + # m Get demodulator mode and passband + # M <mode> [passband] + # Set demodulator mode and passband [Hz] + # Passing a '?' as the first argument instead of 'mode' will return + # a space separated list of radio backend supported modes. + # l|L ? + # Get a space separated list of settings available for reading (l) or writing (L). + # l STRENGTH + # Get signal strength [dBFS] + # l SQL + # Get squelch threshold [dBFS] + # L SQL <sql> + # Set squelch threshold to <sql> [dBFS] + # l AF + # Get audio gain [dB] + # L AF <gain> + # Set audio gain to <gain> [dB] + # l <gain_name>_GAIN + # Get the value of the gain setting with the name <gain_name> + # L <gain_name>_GAIN <value> + # Set the value of the gain setting with the name <gain_name> to <value> + # p RDS_PI + # Get the RDS PI code (in hexadecimal). Returns 0000 if not applicable. + # u RECORD + # Get status of audio recorder + # U RECORD <status> + # Set status of audio recorder to <status> + # u DSP + # Get DSP (SDR receiver) status + # U DSP <status> + # Set DSP (SDR receiver) status to <status> + # u RDS + # Get RDS decoder to <status>. Only functions in WFM mode. + # U RDS <status> + # Set RDS decoder to <status>. Only functions in WFM mode. + # q|Q + # Close connection + # AOS + # Acquisition of signal (AOS) event, start audio recording + # LOS + # Loss of signal (LOS) event, stop audio recording + # LNB_LO [frequency] + # If frequency [Hz] is specified set the LNB LO frequency used for + # display. Otherwise print the current LNB LO frequency [Hz]. + # \chk_vfo + # Get VFO option status (only usable for hamlib compatibility) + # \dump_state + # Dump state (only usable for hamlib compatibility) + # \get_powerstat + # Get power status (only usable for hamlib compatibility) + # v + # Get 'VFO' (only usable for hamlib compatibility) + # V + # Set 'VFO' (only usable for hamlib compatibility) + # s + # Get 'Split' mode (only usable for hamlib compatibility) + # S + # Set 'Split' mode (only usable for hamlib compatibility) + # _ + # Get version + # + # Reply: + # RPRT 0 + # Command successful + # RPRT 1 + # Command failed + gqrx_sock.write("#{cmd}\n") - does_respond = gqrx_sock.wait_readable - gqrx_sock.readline.chomp if does_respond + response = [] + got_freq = false + # Read all responses from gqrx_sock.write + timeout = 0.01 if timeout.nil? + begin + response.push(gqrx_sock.readline.chomp) while gqrx_sock.wait_readable(timeout) + raise IOError if response.empty? + rescue IOError + timeout += 0.001 + retry + end + + got_int_value_in_resp = true if response.first.to_i.positive? + response = response.first if response.length == 1 + + raise "ERROR!!! Command: #{cmd} Expected Resp: #{resp_ok}, Got: #{response}" if resp_ok && response != resp_ok + + if got_int_value_in_resp + fixed_len_freq = format('%0.12d', response.to_i) + freq_segments = fixed_len_freq.scan(/.{3}/) + first_non_zero_index = freq_segments.index { |s| s.to_i.positive? } + freq_segments = freq_segments[first_non_zero_index..-1] + freq_segments[0] = freq_segments.first.to_i.to_s + response = freq_segments.join('.') + end + + # DEBUG + # puts response.inspect + # puts response.length + + response end def init_freq(opts = {}) gqrx_sock = opts[:gqrx_sock] + demodulator_mode = opts[:demodulator_mode] + bandwidth = opts[:bandwidth] this_freq = opts[:this_freq] lock_on_freq_duration = opts[:lock_on_freq_duration] strength = opts[:strength] - change_frreq_resp = gqrx_cmd( + demod_n_passband = gqrx_cmd( gqrx_sock: gqrx_sock, - cmd: "F #{this_freq}" + cmd: 'm' ) - raise "ERROR: Failed to set frequency to #{this_freq}" unless change_frreq_resp == 'RPRT 0' - raw_freq = gqrx_cmd( + change_freq_resp = gqrx_cmd( gqrx_sock: gqrx_sock, + cmd: "F #{this_freq}", + resp_ok: 'RPRT 0' + ) + + current_freq = gqrx_cmd( + gqrx_sock: gqrx_sock, cmd: 'f' ) - # Split the response from NNNNNNNNN - # to NNN.NNN.NNN - this_freq = raw_freq.to_s.chars.insert(-4, '.').insert(-8, '.').join - - strength_resp = gqrx_cmd( + current_strength = gqrx_cmd( gqrx_sock: gqrx_sock, cmd: 'l STRENGTH' - ) - current_strength = strength_resp.to_f + ).to_f - squelch_resp = gqrx_cmd( + current_squelch = gqrx_cmd( gqrx_sock: gqrx_sock, cmd: 'l SQL' - ) - current_squelch = squelch_resp.to_f + ).to_f + audio_gain_db = gqrx_cmd( + gqrx_sock: gqrx_sock, + cmd: 'l AF' + ).to_f + init_freq_hash = { - frequency: this_freq, + demod_mode_n_passband: demod_n_passband, + frequency: current_freq, + audio_gain_db: audio_gain_db, + squelch: current_squelch, strength: current_strength, - squelch: current_squelch + trigger_lock_on_freq_strength: strength } - # puts JSON.parse(init_freq_hash) - puts init_freq_hash.to_json + puts JSON.pretty_generate(init_freq_hash) sleep lock_on_freq_duration if current_strength < strength - # This helps level out strength - sleep 0.03 init_freq_hash end def scan_range(opts = {}) gqrx_sock = opts[:gqrx_sock] + demodulator_mode = opts[:demodulator_mode] + bandwidth = opts[:bandwidth] start_freq = opts[:start_freq] target_freq = opts[:target_freq] precision = opts[:precision] lock_on_freq_duration = opts[:lock_on_freq_duration] strength = opts[:strength] @@ -137,21 +241,24 @@ start_freq.downto(target_freq) do |this_freq| next unless (i % multiplier).zero? init_freq_hash = init_freq( gqrx_sock: gqrx_sock, + demodulator_mode: demodulator_mode, + bandwidth: bandwidth, this_freq: this_freq, lock_on_freq_duration: lock_on_freq_duration, strength: strength ) - puts init_freq_hash.to_json end else this_freq = start_freq while this_freq <= target_freq init_freq_hash = init_freq( gqrx_sock: gqrx_sock, + demodulator_mode: demodulator_mode, + bandwidth: bandwidth, this_freq: this_freq, lock_on_freq_duration: lock_on_freq_duration, strength: strength ) @@ -163,57 +270,82 @@ begin pwn_provider = 'ruby-gem' pwn_provider = ENV.fetch('PWN_PROVIDER') if ENV.keys.any? { |s| s == 'PWN_PROVIDER' } target_freq = opts[:target_freq] - target_freq = target_freq.to_s.delete('.').to_i unless target_freq.nil? - raise 'ERROR: Invalid end frequency' if target_freq.nil? + target_freq = target_freq.to_s.delete('.') unless target_freq.nil? + target_freq = target_freq.to_i + raise "ERROR: Invalid target frequency #{target_freq}" if target_freq.zero? host = opts[:host] ||= '127.0.0.1' port = opts[:port] ||= 7356 puts "Connecting to GQRX at #{host}:#{port}..." gqrx_sock = PWN::Plugins::Sock.connect(target: host, port: port) - demodulator_mode = opts[:demodulator_mode] ||= 'AM' + start_freq = opts[:start_freq] + start_freq = start_freq.to_s.delete('.') unless start_freq.nil? + start_freq = start_freq.to_i + start_freq = gqrx_cmd(gqrx_sock: gqrx_sock, cmd: 'f', resp_ok: 'RPRT 0').to_i if start_freq.zero? + + demodulator_mode = opts[:demodulator_mode] ||= 'WFM_ST' demodulator_mode.upcase! raise "ERROR: Invalid demodulator mode: #{demodulator_mode}" unless %w[OFF RAW AM FM WFM WFM_ST WFM_ST_OIRT LSB USB CW CWL CWU].include?(demodulator_mode) - puts "Setting demodulator mode to #{demodulator_mode}..." - demod_resp = gqrx_cmd(gqrx_sock: gqrx_sock, cmd: "M #{demodulator_mode}") - raise "ERROR: Failed to set demodulator mode to #{demodulator_mode}" unless demod_resp == 'RPRT 0' + bandwidth = opts[:bandwidth] ||= '240.000' - start_freq = opts[:start_freq] - start_freq = start_freq.to_s.delete('.').to_i unless start_freq.nil? - start_freq = gqrx_cmd(gqrx_sock: gqrx_sock, cmd: 'f').to_i if start_freq.nil? + puts "Setting demodulator mode to #{demodulator_mode} and bandwidth to #{bandwidth}..." + bandwidth = bandwidth.to_s.delete('.').to_i unless bandwidth.nil? + demod_resp = gqrx_cmd( + gqrx_sock: gqrx_sock, + cmd: "M #{demodulator_mode} #{bandwidth}", + resp_ok: 'RPRT 0' + ) - squelch = opts[:squelch] ||= -63 + squelch = opts[:squelch] ||= -63.0 squelch = squelch.to_f - squelch_resp = gqrx_cmd(gqrx_sock: gqrx_sock, cmd: "L SQL #{squelch}") - raise "ERROR: Failed to set squelch to #{squelch}" unless squelch_resp == 'RPRT 0' + squelch_resp = gqrx_cmd( + gqrx_sock: gqrx_sock, + cmd: "L SQL #{squelch}", + resp_ok: 'RPRT 0' + ) - precision = opts[:precision] ||= 3 + precision = opts[:precision] ||= 6 precision = precision.to_i raise "ERROR: Invalid precision: #{precision}" unless (1..12).include?(precision) - lock_on_freq_duration = opts[:lock_on_freq_duration] ||= 0 + lock_on_freq_duration = opts[:lock_on_freq_duration] ||= 3 lock_on_freq_duration = lock_on_freq_duration.to_f - strength = opts[:strength] ||= -63 + strength = opts[:strength] ||= -63.3 strength = strength.to_f - puts "Scanning from #{start_freq} to #{target_freq}..." + audio_gain_db = opts[:audio_gain_db] ||= 1.0 + audio_gain_db = audio_gain_db.to_f + audio_gain_db_resp = gqrx_cmd( + gqrx_sock: gqrx_sock, + cmd: "L AF #{audio_gain_db}", + resp_ok: 'RPRT 0' + ) + s_freq_pretty = start_freq.to_s.chars.insert(-4, '.').insert(-8, '.').join + t_freq_pretty = target_freq.to_s.chars.insert(-4, '.').insert(-8, '.').join + puts "*** Scanning from #{s_freq_pretty} to #{t_freq_pretty}" + scan_range( gqrx_sock: gqrx_sock, + demodulator_mode: demodulator_mode, start_freq: start_freq, target_freq: target_freq, precision: precision, lock_on_freq_duration: lock_on_freq_duration, - strength: strength + strength: strength, + squelch: squelch ) -rescue SystemExit, Interrupt + puts 'Scan Complete.' +rescue StandardError => e + raise e +rescue Interrupt, SystemExit puts "\nGoodbye." ensure - resp = gqrx_cmd(gqrx_sock: gqrx_sock, cmd: 'c') gqrx_sock = PWN::Plugins::Sock.disconnect(sock_obj: gqrx_sock) end