pwn 0.5.529 → 0.5.531
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +12 -12
- data/README.md +3 -3
- data/bin/pwn_gqrx_scanner +36 -29
- data/lib/pwn/sdr/decoder/pocsag.rb +130 -0
- data/lib/pwn/sdr/decoder/rds.rb +13 -13
- data/lib/pwn/sdr/decoder.rb +1 -0
- data/lib/pwn/sdr/frequency_allocation.rb +286 -170
- data/lib/pwn/sdr/gqrx.rb +177 -136
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/sdr/decoder/pocsag_spec.rb +15 -0
- metadata +27 -25
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: db0acc490e9b6e0dca25ce2a5b5bcd2b19f435f129ae0221857842c267941c32
|
|
4
|
+
data.tar.gz: 53374f569fcd0484a59c1c74e1d160872c6fab8afb375b7a7ac19fdc7d4682d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 447ff1c3aa6d7118145b4a20f555f3c5c541423f0b8799b486cf1c3ef8cb5ef2181e0676608a7aa164da72be87d8f515d1f9c7af3fdf792ddd26c95b4edc6234
|
|
7
|
+
data.tar.gz: 99ac0586844bb9b0693fa83eb406b35419d940bbff7cace73df0aa0c0da50c8bf059137cbe9aed40d63a68b9bfb7a44457fe54103fc81204b060f1e2a2dce713
|
data/Gemfile
CHANGED
|
@@ -11,7 +11,7 @@ gemspec
|
|
|
11
11
|
# In some circumstances custom flags are passed to gems in order
|
|
12
12
|
# to build appropriately. Defer to ./reinstall_pwn_gemset.sh
|
|
13
13
|
# to review these custom flags (e.g. pg, serialport, etc).
|
|
14
|
-
gem 'activesupport', '<8.1.
|
|
14
|
+
gem 'activesupport', '<8.1.2'
|
|
15
15
|
gem 'anemone', '0.7.2'
|
|
16
16
|
gem 'authy', '3.0.1'
|
|
17
17
|
gem 'aws-sdk', '3.3.0'
|
|
@@ -24,14 +24,14 @@ gem 'bundler', '>=4.0.3'
|
|
|
24
24
|
gem 'bundler-audit', '>=0.9.3'
|
|
25
25
|
gem 'bunny', '2.24.0'
|
|
26
26
|
gem 'colorize', '1.1.0'
|
|
27
|
-
gem 'credit_card_validations', '7.
|
|
27
|
+
gem 'credit_card_validations', '7.1.0'
|
|
28
28
|
gem 'curses', '1.5.3'
|
|
29
29
|
gem 'diffy', '3.4.4'
|
|
30
30
|
gem 'eventmachine', '1.2.7'
|
|
31
31
|
gem 'executable-hooks', '1.7.1'
|
|
32
32
|
gem 'faker', '3.5.3'
|
|
33
33
|
gem 'faye-websocket', '0.12.0'
|
|
34
|
-
gem 'ffi', '1.17.
|
|
34
|
+
gem 'ffi', '1.17.3'
|
|
35
35
|
# gem 'fftw3', '0.3'
|
|
36
36
|
gem 'gdb', '1.0.0'
|
|
37
37
|
gem 'gem-wrappers', '1.4.0'
|
|
@@ -49,7 +49,7 @@ gem 'jwt', '3.1.2'
|
|
|
49
49
|
gem 'libusb', '0.7.2'
|
|
50
50
|
gem 'luhn', '3.0.0'
|
|
51
51
|
gem 'mail', '2.9.0'
|
|
52
|
-
gem 'meshtastic', '0.0.
|
|
52
|
+
gem 'meshtastic', '0.0.151'
|
|
53
53
|
gem 'metasm', '1.0.5'
|
|
54
54
|
gem 'mongo', '2.22.0'
|
|
55
55
|
gem 'msfrpc-client', '1.1.2'
|
|
@@ -58,7 +58,7 @@ gem 'net-ldap', '0.20.0'
|
|
|
58
58
|
gem 'net-openvpn', '0.8.7'
|
|
59
59
|
gem 'net-smtp', '0.5.1'
|
|
60
60
|
gem 'nexpose', '7.3.0'
|
|
61
|
-
gem 'nokogiri', '1.
|
|
61
|
+
gem 'nokogiri', '1.19.0'
|
|
62
62
|
gem 'nokogiri-diff', '0.3.0'
|
|
63
63
|
gem 'oily_png', '1.2.1'
|
|
64
64
|
gem 'open3', '0.2.1'
|
|
@@ -66,9 +66,9 @@ gem 'os', '1.1.4'
|
|
|
66
66
|
gem 'ostruct', '0.6.3'
|
|
67
67
|
gem 'packetfu', '2.0.0'
|
|
68
68
|
gem 'packetgen', '4.1.1'
|
|
69
|
-
gem 'pdf-reader', '2.15.
|
|
70
|
-
gem 'pg', '1.6.
|
|
71
|
-
gem 'pry', '0.
|
|
69
|
+
gem 'pdf-reader', '2.15.1'
|
|
70
|
+
gem 'pg', '1.6.3'
|
|
71
|
+
gem 'pry', '0.16.0'
|
|
72
72
|
gem 'pry-doc', '1.6.0'
|
|
73
73
|
gem 'rake', '13.3.1'
|
|
74
74
|
gem 'rb-readline', '0.5.5'
|
|
@@ -76,13 +76,13 @@ gem 'rbvmomi2', '3.8.0'
|
|
|
76
76
|
gem 'rdoc', '7.0.3'
|
|
77
77
|
gem 'rest-client', '2.1.0'
|
|
78
78
|
gem 'rex', '2.0.13'
|
|
79
|
-
gem 'rmagick', '6.1.
|
|
80
|
-
gem 'rqrcode', '3.
|
|
79
|
+
gem 'rmagick', '6.1.5'
|
|
80
|
+
gem 'rqrcode', '3.2.0'
|
|
81
81
|
gem 'rspec', '3.13.2'
|
|
82
82
|
gem 'rtesseract', '3.1.4'
|
|
83
83
|
gem 'rubocop', '1.82.1'
|
|
84
84
|
gem 'rubocop-rake', '0.7.1'
|
|
85
|
-
gem 'rubocop-rspec', '3.
|
|
85
|
+
gem 'rubocop-rspec', '3.9.0'
|
|
86
86
|
gem 'ruby-audio', '1.6.1'
|
|
87
87
|
gem 'ruby-nmap', '1.0.3'
|
|
88
88
|
gem 'ruby-saml', '1.18.1'
|
|
@@ -93,7 +93,7 @@ gem 'selenium-webdriver', '4.39.0'
|
|
|
93
93
|
gem 'slack-ruby-client', '3.1.0'
|
|
94
94
|
gem 'socksify', '1.8.1'
|
|
95
95
|
gem 'spreadsheet', '1.3.4'
|
|
96
|
-
gem 'sqlite3', '2.
|
|
96
|
+
gem 'sqlite3', '2.9.0'
|
|
97
97
|
gem 'thin', '2.0.1'
|
|
98
98
|
gem 'tty-prompt', '0.23.1'
|
|
99
99
|
gem 'tty-spinner', '0.9.3'
|
data/README.md
CHANGED
|
@@ -37,7 +37,7 @@ $ cd /opt/pwn
|
|
|
37
37
|
$ ./install.sh
|
|
38
38
|
$ ./install.sh ruby-gem
|
|
39
39
|
$ pwn
|
|
40
|
-
pwn[v0.5.
|
|
40
|
+
pwn[v0.5.531]:001 >>> PWN.help
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
[](https://youtu.be/G7iLUY4FzsI)
|
|
@@ -52,7 +52,7 @@ $ rvm use ruby-4.0.0@pwn
|
|
|
52
52
|
$ gem uninstall --all --executables pwn
|
|
53
53
|
$ gem install --verbose pwn
|
|
54
54
|
$ pwn
|
|
55
|
-
pwn[v0.5.
|
|
55
|
+
pwn[v0.5.531]:001 >>> PWN.help
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
If you're using a multi-user install of RVM do:
|
|
@@ -62,7 +62,7 @@ $ rvm use ruby-4.0.0@pwn
|
|
|
62
62
|
$ rvmsudo gem uninstall --all --executables pwn
|
|
63
63
|
$ rvmsudo gem install --verbose pwn
|
|
64
64
|
$ pwn
|
|
65
|
-
pwn[v0.5.
|
|
65
|
+
pwn[v0.5.531]:001 >>> PWN.help
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
PWN periodically upgrades to the latest version of Ruby which is reflected in `/opt/pwn/.ruby-version`. The easiest way to upgrade to the latest version of Ruby from a previous PWN installation is to run the following script:
|
data/bin/pwn_gqrx_scanner
CHANGED
|
@@ -5,20 +5,16 @@ require 'pwn'
|
|
|
5
5
|
|
|
6
6
|
opts = PWN::Env[:driver_opts]
|
|
7
7
|
PWN::Driver::Parser.new do |options|
|
|
8
|
-
options.on('-aPROFILE', '--assume-
|
|
8
|
+
options.on('-aPROFILE', '--assume-band-plan=PROFILE', '<Required if "--target-freq" is Nil - Band Plan Profile to assume for common radio protocols. Use "--list-band-plans" to display supported protocols (Defaults to nil)') do |p|
|
|
9
9
|
opts[:profile] = p
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
options.on('-l', '--list-
|
|
13
|
-
opts[:
|
|
12
|
+
options.on('-l', '--list-band-plans', '<Optional - List supported band plan profiles and exit>') do |l|
|
|
13
|
+
opts[:list_band_plans] = l
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
options.on('-
|
|
17
|
-
opts[:
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
options.on('-tFREQ', '--target-freq=FREQ', '<Required if "--assume-profile" is Nil - Frequency to Conclude Scanning (e.g. 900.000.000 == 900 mHz>') do |e|
|
|
21
|
-
opts[:target_freq] = e
|
|
16
|
+
options.on('-sFREQS', '--scan-ranges=FREQS', '<Required if "--assume-band-plan" is Nil - Comma-Delimited List of Frequency Ranges to Scan (e.g. 800.000.000 == 800 mHz>') do |s|
|
|
17
|
+
opts[:scan_ranges] = s
|
|
22
18
|
end
|
|
23
19
|
|
|
24
20
|
options.on('-hHOST', '--host=HOST', '<Optional - GQRX Host (Defaults to 127.0.0.1)>') do |h|
|
|
@@ -82,26 +78,36 @@ begin
|
|
|
82
78
|
pwn_provider = 'ruby-gem'
|
|
83
79
|
pwn_provider = ENV.fetch('PWN_PROVIDER') if ENV.keys.any? { |s| s == 'PWN_PROVIDER' }
|
|
84
80
|
|
|
85
|
-
|
|
81
|
+
band_plans = PWN::SDR::FrequencyAllocation.band_plans
|
|
86
82
|
|
|
87
|
-
|
|
88
|
-
if
|
|
89
|
-
puts JSON.pretty_generate(
|
|
83
|
+
list_band_plans = opts[:list_band_plans]
|
|
84
|
+
if list_band_plans
|
|
85
|
+
puts JSON.pretty_generate(band_plans)
|
|
90
86
|
exit 0
|
|
91
87
|
end
|
|
92
88
|
|
|
93
89
|
profile = opts[:profile]
|
|
94
|
-
opts
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
90
|
+
# Merge opts with profile values if profile provided
|
|
91
|
+
opts.merge!(band_plans[profile.to_sym]) unless profile.nil?
|
|
92
|
+
|
|
93
|
+
scan_ranges = opts[:scan_ranges]
|
|
94
|
+
raise 'ERROR: --assume-band-plan || --scan-ranges is required.' if scan_ranges.nil? && profile.nil?
|
|
95
|
+
|
|
96
|
+
ranges = band_plans[profile.to_sym][:ranges] unless profile.nil?
|
|
97
|
+
if ranges.nil?
|
|
98
|
+
# Convert comma-delimited string to array of hashes
|
|
99
|
+
ranges = scan_ranges.split(',').map do |r|
|
|
100
|
+
range = r.split('-')
|
|
101
|
+
{ start_freq: range.first, target_freq: range.last }
|
|
102
|
+
end
|
|
103
|
+
elsif scan_ranges
|
|
104
|
+
# Append scan_ranges to profile ranges if both provided
|
|
105
|
+
additional_ranges = scan_ranges.split(',').map do |r|
|
|
106
|
+
range = r.split('-')
|
|
107
|
+
{ start_freq: range.first, target_freq: range.last }
|
|
108
|
+
end
|
|
109
|
+
ranges.concat(additional_ranges)
|
|
110
|
+
end
|
|
105
111
|
|
|
106
112
|
host = opts[:host]
|
|
107
113
|
port = opts[:port]
|
|
@@ -137,11 +143,12 @@ begin
|
|
|
137
143
|
decoder = opts[:decoder]
|
|
138
144
|
location = opts[:location]
|
|
139
145
|
|
|
140
|
-
# Merge opts
|
|
146
|
+
# Merge opts one last time to ensure parameters passed to the driver are included
|
|
147
|
+
opts.merge!(band_plans[profile.to_sym]) unless profile.nil?
|
|
148
|
+
|
|
141
149
|
PWN::SDR::GQRX.scan_range(
|
|
142
150
|
gqrx_sock: gqrx_sock,
|
|
143
|
-
|
|
144
|
-
target_freq: target_freq,
|
|
151
|
+
ranges: ranges,
|
|
145
152
|
demodulator_mode: demodulator_mode,
|
|
146
153
|
bandwidth: bandwidth,
|
|
147
154
|
audio_gain_db: audio_gain_db,
|
|
@@ -159,8 +166,8 @@ begin
|
|
|
159
166
|
puts 'Scan Complete.'
|
|
160
167
|
rescue StandardError => e
|
|
161
168
|
raise e
|
|
162
|
-
rescue Interrupt
|
|
169
|
+
rescue Interrupt
|
|
163
170
|
puts "\nCTRL+C detected - goodbye."
|
|
164
171
|
ensure
|
|
165
|
-
PWN::SDR::GQRX.disconnect(gqrx_sock: gqrx_sock) unless gqrx_sock.
|
|
172
|
+
PWN::SDR::GQRX.disconnect(gqrx_sock: gqrx_sock) unless gqrx_sock.nil?
|
|
166
173
|
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'tty-spinner'
|
|
5
|
+
|
|
6
|
+
module PWN
|
|
7
|
+
module SDR
|
|
8
|
+
module Decoder
|
|
9
|
+
# POCSAG Decoder Module for Pagers
|
|
10
|
+
module POCSAG
|
|
11
|
+
# Supported Method Parameters::
|
|
12
|
+
# pocsag_resp = PWN::SDR::Decoder::POCSAG.decode(
|
|
13
|
+
# freq_obj: 'required - GQRX socket object returned from #connect method'
|
|
14
|
+
# )
|
|
15
|
+
|
|
16
|
+
public_class_method def self.decode(opts = {})
|
|
17
|
+
freq_obj = opts[:freq_obj]
|
|
18
|
+
gqrx_sock = freq_obj[:gqrx_sock]
|
|
19
|
+
record_path = freq_obj[:record_path]
|
|
20
|
+
freq_obj = freq_obj.dup
|
|
21
|
+
freq_obj.delete(:gqrx_sock)
|
|
22
|
+
skip_freq_char = "\n"
|
|
23
|
+
puts JSON.pretty_generate(freq_obj)
|
|
24
|
+
puts "\n*** Pager POCSAG Decoder ***"
|
|
25
|
+
puts 'Press [ENTER] to continue...'
|
|
26
|
+
|
|
27
|
+
# Toggle POCSAG off and on to reset the decoder
|
|
28
|
+
PWN::SDR::GQRX.cmd(
|
|
29
|
+
gqrx_sock: gqrx_sock,
|
|
30
|
+
cmd: 'U RECORD 0',
|
|
31
|
+
resp_ok: 'RPRT 0'
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
PWN::SDR::GQRX.cmd(
|
|
35
|
+
gqrx_sock: gqrx_sock,
|
|
36
|
+
cmd: 'U RECORD 1',
|
|
37
|
+
resp_ok: 'RPRT 0'
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Spinner setup with dynamic terminal width awareness
|
|
41
|
+
spinner = TTY::Spinner.new(
|
|
42
|
+
'[:spinner] :decoding',
|
|
43
|
+
format: :arrow_pulse,
|
|
44
|
+
clear: true,
|
|
45
|
+
hide_cursor: true
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
record_header_size = 44
|
|
49
|
+
wav_header = File.binread(record_path, record_header_size)
|
|
50
|
+
raise 'ERROR: WAV file header is invalid!' unless wav_header[0, 4] == 'RIFF' && wav_header[8, 4] == 'WAVE'
|
|
51
|
+
|
|
52
|
+
bytes_read = wav_header_size
|
|
53
|
+
|
|
54
|
+
# Conservative overhead for spinner animation, colors, and spacing
|
|
55
|
+
spinner_overhead = 12
|
|
56
|
+
max_title_length = [TTY::Screen.width - spinner_overhead, 50].max
|
|
57
|
+
|
|
58
|
+
initial_title = 'INFO: Decoding Pager POCSAG data...'
|
|
59
|
+
initial_title = initial_title[0...max_title_length] if initial_title.length > max_title_length
|
|
60
|
+
spinner.update(title: initial_title)
|
|
61
|
+
spinner.auto_spin
|
|
62
|
+
|
|
63
|
+
last_resp = {}
|
|
64
|
+
|
|
65
|
+
loop do
|
|
66
|
+
current_wav_size = File.size?(record_path) ||= 0
|
|
67
|
+
pocsag_resp = {}
|
|
68
|
+
|
|
69
|
+
# Only update when we have valid new data
|
|
70
|
+
if current_wav_size > bytes_read
|
|
71
|
+
new_bytes = current_wav_size - bytes_read
|
|
72
|
+
# Ensure full I/Q pairs (8 bytes each)
|
|
73
|
+
new_bytes -= new_bytes % 8
|
|
74
|
+
data = File.binread(record_path, new_bytes, bytes_read)
|
|
75
|
+
|
|
76
|
+
msg = "DECODED DATA"
|
|
77
|
+
|
|
78
|
+
spinner.update(decoding: msg)
|
|
79
|
+
last_resp = pocsag_resp.dup
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Non-blocking check for ENTER key to exit
|
|
83
|
+
if $stdin.wait_readable(0)
|
|
84
|
+
begin
|
|
85
|
+
char = $stdin.read_nonblock(1)
|
|
86
|
+
break if char == skip_freq_char
|
|
87
|
+
rescue IO::WaitReadable, EOFError
|
|
88
|
+
# No-op
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
sleep 0.01
|
|
93
|
+
end
|
|
94
|
+
rescue StandardError => e
|
|
95
|
+
spinner.error('Decoding failed') if defined?(spinner)
|
|
96
|
+
raise e
|
|
97
|
+
ensure
|
|
98
|
+
File.unlink(record_path) if File.exist?(record_path)
|
|
99
|
+
PWN::SDR::GQRX.cmd(
|
|
100
|
+
gqrx_sock: gqrx_sock,
|
|
101
|
+
cmd: 'U RECORD 0',
|
|
102
|
+
resp_ok: 'RPRT 0'
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
spinner.stop if defined?(spinner) && spinner
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
109
|
+
|
|
110
|
+
public_class_method def self.authors
|
|
111
|
+
"AUTHOR(S):
|
|
112
|
+
0day Inc. <support@0dayinc.com>
|
|
113
|
+
"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Display Usage for this Module
|
|
117
|
+
|
|
118
|
+
public_class_method def self.help
|
|
119
|
+
puts "USAGE:
|
|
120
|
+
#{self}.decode(
|
|
121
|
+
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq method'
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
#{self}.authors
|
|
125
|
+
"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
data/lib/pwn/sdr/decoder/rds.rb
CHANGED
|
@@ -9,7 +9,7 @@ module PWN
|
|
|
9
9
|
# RDS Decoder Module for FM Radio Signals
|
|
10
10
|
module RDS
|
|
11
11
|
# Supported Method Parameters::
|
|
12
|
-
# rds_resp = PWN::SDR::
|
|
12
|
+
# rds_resp = PWN::SDR::Decoder::RDS.decode(
|
|
13
13
|
# freq_obj: 'required - GQRX socket object returned from #connect method'
|
|
14
14
|
# )
|
|
15
15
|
|
|
@@ -19,19 +19,19 @@ module PWN
|
|
|
19
19
|
|
|
20
20
|
freq_obj = freq_obj.dup
|
|
21
21
|
freq_obj.delete(:gqrx_sock)
|
|
22
|
-
|
|
22
|
+
skip_freq_char = "\n"
|
|
23
23
|
puts JSON.pretty_generate(freq_obj)
|
|
24
24
|
puts "\n*** FM Radio RDS Decoder ***"
|
|
25
25
|
puts 'Press [ENTER] to continue...'
|
|
26
26
|
|
|
27
27
|
# Toggle RDS off and on to reset the decoder
|
|
28
|
-
PWN::SDR::GQRX.
|
|
28
|
+
PWN::SDR::GQRX.cmd(
|
|
29
29
|
gqrx_sock: gqrx_sock,
|
|
30
30
|
cmd: 'U RDS 0',
|
|
31
31
|
resp_ok: 'RPRT 0'
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
PWN::SDR::GQRX.
|
|
34
|
+
PWN::SDR::GQRX.cmd(
|
|
35
35
|
gqrx_sock: gqrx_sock,
|
|
36
36
|
cmd: 'U RDS 1',
|
|
37
37
|
resp_ok: 'RPRT 0'
|
|
@@ -58,9 +58,9 @@ module PWN
|
|
|
58
58
|
|
|
59
59
|
loop do
|
|
60
60
|
rds_resp = {
|
|
61
|
-
rds_pi: PWN::SDR::GQRX.
|
|
62
|
-
rds_ps_name: PWN::SDR::GQRX.
|
|
63
|
-
rds_radiotext: PWN::SDR::GQRX.
|
|
61
|
+
rds_pi: PWN::SDR::GQRX.cmd(gqrx_sock: gqrx_sock, cmd: 'p RDS_PI').to_s.strip.chomp.delete('.'),
|
|
62
|
+
rds_ps_name: PWN::SDR::GQRX.cmd(gqrx_sock: gqrx_sock, cmd: 'p RDS_PS_NAME').to_s.strip.chomp,
|
|
63
|
+
rds_radiotext: PWN::SDR::GQRX.cmd(gqrx_sock: gqrx_sock, cmd: 'p RDS_RADIOTEXT').to_s.strip.chomp
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
# Only update when we have valid new data
|
|
@@ -81,13 +81,13 @@ module PWN
|
|
|
81
81
|
prefix = "Program ID: #{rds_pi} | Station Name: #{rds_ps} | Radio Txt: "
|
|
82
82
|
|
|
83
83
|
# minimum visibility
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
available_for_term = max_title_length - prefix.length
|
|
85
|
+
available_for_term = [available_for_term, 10].max
|
|
86
86
|
|
|
87
87
|
rt_display = rds_rt
|
|
88
|
-
rt_display = "#{rt_display[0...
|
|
88
|
+
rt_display = "#{rt_display[0...available_for_term]}..." if rt_display.length > available_for_term
|
|
89
89
|
|
|
90
|
-
msg = prefix
|
|
90
|
+
msg = "#{prefix}#{rt_display}"
|
|
91
91
|
spinner.update(decoding: msg)
|
|
92
92
|
last_resp = rds_resp.dup
|
|
93
93
|
end
|
|
@@ -96,7 +96,7 @@ module PWN
|
|
|
96
96
|
if $stdin.wait_readable(0)
|
|
97
97
|
begin
|
|
98
98
|
char = $stdin.read_nonblock(1)
|
|
99
|
-
break if char ==
|
|
99
|
+
break if char == skip_freq_char
|
|
100
100
|
rescue IO::WaitReadable, EOFError
|
|
101
101
|
# No-op
|
|
102
102
|
end
|
|
@@ -124,7 +124,7 @@ module PWN
|
|
|
124
124
|
public_class_method def self.help
|
|
125
125
|
puts "USAGE:
|
|
126
126
|
#{self}.decode(
|
|
127
|
-
freq_obj: 'required - freq_obj returned from PWN::SDR::
|
|
127
|
+
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq method'
|
|
128
128
|
)
|
|
129
129
|
|
|
130
130
|
#{self}.authors
|
data/lib/pwn/sdr/decoder.rb
CHANGED