Merge "Addition of Signal Analyzer tool"
diff --git a/signal_generator/README b/signal_generator/README
index 691bbda..a422a75 100644
--- a/signal_generator/README
+++ b/signal_generator/README
@@ -9,17 +9,9 @@
A general db-gain can also be specified by using the '-g' flag to amplify the
combined signal.
-Options:
- -h, --help Show this help message and exit
- -f FILENAME,FACTOR, --file=FILENAME,FACTOR
- Add signal from file (raw 64 bit complex) which can be
- generated using the 'bandpass_recorder.py' script. The
- raw file can be read using scipy.fromfile.
- -u FREQUENCY, --frequency=FREQUENCY
- Specify the desired frequency in KHz, defaults
- to 2412KHz (Wifi Channel 1)
- -g DB_GAIN, --gain=DB_GAIN
- Specify the db-gain amplification, defaults to 50db
+Usage: signal_generator.py [options]
+
+To print usage info use "./signal_generator.py -h" option.
Note: If you are playing from a file and are getting Underruns (U), try moving
the file to local storage.
diff --git a/signal_generator/signal_generator.py b/signal_generator/signal_generator.py
index a693e6a..cd17bcc 100755
--- a/signal_generator/signal_generator.py
+++ b/signal_generator/signal_generator.py
@@ -1,101 +1,279 @@
#!/usr/bin/python
-from optparse import OptionParser
+"""Signal Generator Tool.
+
+This module uses the Gnuradio framework to take in multiple signals as
+specified on command line and adds them together and broadcasts them using a
+USRP software defined radio.
+
+"""
+from collections import namedtuple
+import ConfigParser
+import math
+import os
+import re
import sys
+import tempfile
from gnuradio import blocks
from gnuradio import gr
from gnuradio import uhd
+import numpy as np
+from scipy.interpolate import interp1d
+
+import options
+
+optspec = """
+signal_generator [options...]
+--
+ Radio Parameters:
+F,frequency= Specify the frequency of the signal generated in KHz [2412000]
+G,gain= Specify the Db gain of the radio when it broadcasts. [50.0]
+C,config= Specify the config file for signal parameters.
+o,filename= Specify filename to save signal instead of recording.
+ Signal Types:
+f,file= e.g "-f foo.dat^10.0 to play foo.dat at 10x signal strength
+p,periodic= e.g "-p 1000%0.5@1000000^10.0" to repeat a sine signal every 1000us with 50% duty centered at 1000000Hz and 10x amplification
+b,bluetooth= e.g "-b 100" to broadcast a bluetooth-like signal at specified power.
+w,wifi= e.g "-w 100" to broadcast a wifi-like signal at specified power.
+"""
+SAMPLE_RATE = 30e6
+PROFILE_SIZE = 30000
+
+SignalParameters = namedtuple('ParseSignal', ['prefix', 'amplification',
+ 'frequency', 'duty'])
+
+
+def parse_signal(parameters):
+ """Parse the command line signal parameters.
+
+ Parameter notation corresponds special symbols with parameter values
+ ^: amplification
+ %: duty cycle
+ @: frequency
+
+ Args:
+ parameters: parameter string to be parsed
+ Returns:
+ ParseSignal object with parsed parameter
+ """
+
+ split_params = re.split(r'([%@^])', parameters.strip('"\''))
+ param_dict = {'%': 0.0, '@': 0.0, '^': 0.0, 'filename': '', 'period': 0.0}
+ param_dict['prefix'] = split_params[0]
+ for i in range(1, len(split_params) - 1, 2):
+ param_dict[split_params[i]] = float(split_params[i + 1])
+
+ return SignalParameters(param_dict['prefix'], param_dict['^'],
+ param_dict['@'], param_dict['%'])
+
+
+def interpolate(data, factor):
+ x_old = range(0, len(data) * factor, factor)
+ f = interp1d(x_old, data)
+
+ xnew = range(0, len(data) * (factor - 1))
+ ynew = f(xnew)
+ return ynew
class SignalGenerator(gr.top_block):
+ """Signal Generation Class.
- def __init__(self):
+ Class to create a GnuRadio top-block which takes multiple signals and
+ broadcasts the sum of the signals. The power and frequency of the signal
+ can be changed. Different types of signals can be added by calling the
+ corresponding method.
+ """
+
+ def __init__(self, filename):
+ """Initialize the top block and create the URSP sink block."""
super(SignalGenerator, self).__init__('Signal Generator')
- sample_rate = 30e6
-
- # Initialize the SDR
stream_args = uhd.stream_args(cpu_format='fc32', otw_format='sc8',
channels=range(1))
- self.usrp_sink = uhd.usrp_sink(',', stream_args)
+ if filename:
+ self.sink = blocks.file_sink(gr.sizeof_gr_complex, filename, False)
+ else:
+ self.sink = uhd.usrp_sink(',', stream_args)
+ self.sink.set_samp_rate(SAMPLE_RATE)
+ self.sink.set_bandwidth(SAMPLE_RATE)
# Create a list of the file source streams so we can add them as we read
# arguments.
self.sources = []
+ self.temp_files = []
- # Adjust parameters
- self.usrp_sink.set_samp_rate(sample_rate)
- self.usrp_sink.set_bandwidth(sample_rate)
-
- # Initialize and connect the signal adder.
self.add_block = blocks.add_vcc(1)
- self.connect((self.add_block, 0), (self.usrp_sink, 0))
+ self.connect((self.add_block, 0), (self.sink, 0))
- # Adjust gain value on the USRP.
- def set_gain(self, gain):
- self.usrp_sink.set_gain(gain)
-
- # Adjust center frequency value of the USRP.
- def set_freq(self, freq):
- self.usrp_sink.set_center_freq(freq, 0)
-
- # Add a new signal to be broadcasted.
def add_file_source(self, filename, factor):
- # Create and add our file sources paired with their multiplier block to the
- # sources list.
+ """Create and add a file source to the sources list.
+
+ Args:
+ filename: Path to the file to be broadcasted
+ factor: Amplitude is multiplied by this
+ """
+
src = blocks.file_source(gr.sizeof_gr_complex, filename, True)
multiply_block = blocks.multiply_const_vcc((factor,))
self.connect((src, 0), (multiply_block, 0))
self.sources.append(multiply_block)
+ def add_periodic_signal(self, freq, length, duty, factor):
+ """Add an oscillating parameterized source.
+
+ Args:
+ freq: Frequency in KHz of the peak
+ length: Duration in micro-seconds of one period
+ duty: Proportion of time that the signal is high (0-1)
+ factor: Amplitude is multiplied by this value
+ """
+ up_time = int(length * duty * 30)
+ signal = np.zeros(shape=30 * length, dtype=np.complex64)
+ up_sig = freq * math.pi / 15e6 * np.array(range(up_time))
+ signal.imag[0 : up_time] = np.sin(up_sig)
+ signal.real[0 : up_time] = np.cos(up_sig)
+
+ # Write the signal to the file so it can be read by the file source.
+ f = tempfile.NamedTemporaryFile(mode='w+b', bufsize=0)
+ signal.tofile(f.name)
+ self.temp_files.append(f)
+
+ self.add_file_source(f.name, factor)
+
+ def add_periodic_profile(self, length, duty, factor, profile):
+ """Add an oscillating parameterized source with a frequency profile.
+
+ Args:
+ length: Duration in micro-seconds of one period
+ duty: Proportion of time that the signal is high (0-1)
+ factor: Amplitude is multiplied by this value
+ profile: Vector of the frequency profile
+ """
+ up_time = int(length * duty * 30)
+ signal = np.zeros(shape=30 * length, dtype=np.complex64)
+
+ profile_path = os.path.abspath(os.path.expanduser(profile.strip('\'"')))
+ fourier_down = np.load(profile_path)
+ fourier = interpolate(fourier_down, int(SAMPLE_RATE) / len(fourier_down))
+ interference_sample = np.fft.ifft(fourier, int(SAMPLE_RATE))
+
+ signal[0 : up_time] = interference_sample[0 : up_time]
+ f = tempfile.NamedTemporaryFile(mode='w+b', bufsize=0)
+
+ signal.tofile(f.name)
+ self.temp_files.append(f)
+
+ self.add_file_source(f.name, factor)
+
def run(self):
- # Connect all the sources to the adder.
+ """Connect all the sources to the adder and run the system."""
for i in range(len(self.sources)):
self.connect((self.sources[i], 0), (self.add_block, i))
gr.top_block.run(self)
-if __name__ == '__main__':
- # Get command line arguments.
- parser = OptionParser()
- parser.add_option('-f', '--file', dest='file', help='Add signal from file '
- '(raw 64 bit complex) which can be generated using the '
- '\'bandpass_recorder.py\' script. The raw file can be read '
- 'using scipy.fromfile. If factor is not included, it defaults '
- 'to 50.0', metavar='FILENAME,FACTOR', action='append')
- parser.add_option('-u', '--frequency', dest='frequency',
- help='Specify the desired frequency in KHz, defaults to'
- '2412000KHz (Wifi Channel 1)', metavar='FREQUENCY',
- default='2412000')
- parser.add_option('-g', '--gain', dest='gain',
- help='Specify the db-gain amplification, defaults to 50db',
- metavar='DB_GAIN', default='50')
- options, args = parser.parse_args()
- # Initialize the top_block
- signal_gen_block = SignalGenerator()
- signal_gen_block.set_freq(float(options.frequency) * 1000.0)
- signal_gen_block.set_gain(float(options.gain))
+def parse_config(filename):
+ """Parse the config file.
+
+ Args:
+ filename: The filename of the configuration file.
+ Returns:
+ Dictionary of the parameters dividied into signal types and parameter type.
+ """
+ config = ConfigParser.ConfigParser()
+ config.read(filename)
+
+ params = {}
+ for section in config.sections():
+ params[section] = {'duty': config.getfloat(section, 'duty'),
+ 'amplitude': config.getfloat(section, 'amplitude'),
+ 'length': config.getint(section, 'length'),
+ 'profile': config.get(section, 'profile')
+ }
+ return params
+
+
+def main():
+ o = options.Options(optspec)
+ opt, flags, _ = o.parse(sys.argv[1:])
+
+ signal_gen_block = SignalGenerator(opt.filename)
+ if not opt.filename:
+ signal_gen_block.sink.set_center_freq(float(opt.frequency) * 1000.0)
+ signal_gen_block.sink.set_gain(float(opt.gain))
+
+ if opt.config:
+ signal_params = parse_config(opt.config)
+ else:
+ signal_params = []
# Add the signals to the top_block
- if options.file is not None:
- for signal in options.file:
- args = signal.split(',')
+ # 'signal_count' is used to keep track of the number of signals being
+ # broadcasted for error checking.
+ signal_count = 0
+ print 'Processing Signals...'
+ for flag, value in flags:
+ parameter = parse_signal(value)
+ if flag == '--file' or flag == '-f':
+ # Parse out the parameters from the syntax.
+ factor = parameter.amplification
+ filename = os.path.expanduser(parameter.prefix)
- # Check for number of arguments
- if len(args) > 1:
- # Verify that the second argument is a float.
- try:
- factor = float(args[1])
- except:
- print 'Invalid arguments: {0}'.format(signal)
- sys.exit(1)
- else:
- factor = 50.0
+ signal_gen_block.add_file_source(filename, factor)
+ signal_count += 1
- # Add the signal to the top_block.
- signal_gen_block.add_file_source(args[0], factor)
- else:
- print '\nAt least 1 signal is required to run\n'
- sys.exit(1)
+ if flag == '--periodic' or flag == '-p':
+ length = int(parameter.prefix)
+ duty = parameter.duty
+ frequency = parameter.frequency
+ factor = parameter.amplification
- signal_gen_block.run()
+ signal_gen_block.add_periodic_signal(frequency, length, duty, factor)
+ signal_count += 1
+
+ if flag == '--bluetooth' or flag == '-b':
+ if not signal_params:
+ o.fatal('Config file required in order to use bluetooth option')
+
+ bt_params = signal_params['bluetooth']
+ # Round to nearest micro-second
+ signal_power_fraction = float(parameter.prefix)
+
+ length = bt_params['length']
+ duty = bt_params['duty']
+ factor = bt_params['amplitude'] * signal_power_fraction
+ profile = bt_params['profile']
+
+ signal_gen_block.add_periodic_profile(length, duty, factor, profile)
+ signal_count += 1
+
+ if flag == '--wifi' or flag == '-w':
+ if not signal_params:
+ o.fatal('Config file required in order to use wifi option')
+
+ params = signal_params['wifi']
+ # Round to nearest micro-second
+ signal_power_fraction = float(parameter.prefix)
+
+ length = params['length']
+ duty = params['duty']
+ factor = params['amplitude'] * signal_power_fraction
+ profile = params['profile']
+
+ signal_gen_block.add_periodic_profile(length, duty, factor, profile)
+ signal_count += 1
+
+ # If there are no inputted signals throw an error.
+ if signal_count == 0:
+ o.fatal('At least 1 signal is required to run')
+
+ try:
+ print 'Running...'
+ signal_gen_block.run()
+ except KeyboardInterrupt:
+ # Remove the Keyboard Interrupt Error from displaying
+ pass
+if __name__ == '__main__':
+ main()
+
diff --git a/udp_test/options.py b/udp_test/options.py
new file mode 120000
index 0000000..3508154
--- /dev/null
+++ b/udp_test/options.py
@@ -0,0 +1 @@
+../options.py
\ No newline at end of file
diff --git a/udp_test/udp_test_client.py b/udp_test/udp_test_client.py
new file mode 100755
index 0000000..789fc68
--- /dev/null
+++ b/udp_test/udp_test_client.py
@@ -0,0 +1,109 @@
+#!/usr/bin/python
+"""UDP test client.
+
+This implements the client side of the UDP connection testing tool to evaluate
+the effective speed of a link using bursts of UDP traffic.
+"""
+import select
+import socket
+import struct
+import sys
+import time
+
+import options
+
+optspec = """
+udp_test_client.py [options...] host_ip
+--
+i,interval= Specify the cycle interval in seconds [2.0]
+t,time= Specify the test length in seconds; zero means unlimited. [0]
+"""
+
+try:
+ import monotime
+except ImportError:
+ pass
+try:
+ monotime = time.monotime
+except AttributeError:
+ monotime = time.time
+
+
+def parse_address(raw_address):
+ port = 5001
+
+ raw_host_addr = raw_address.split(':')
+ if len(raw_host_addr) > 1:
+ port = int(raw_host_addr[1])
+
+ return (raw_host_addr[0], port)
+
+
+def main():
+ o = options.Options(optspec)
+ opt, _, extra = o.parse(sys.argv[1:])
+
+ interval_length = float(opt.interval)
+
+ host_addr = parse_address(extra[0])
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+
+ packet_counter = 0
+ packets_sent = 0
+ packets_recv = 0
+ respond_time = monotime()
+ start_time = monotime()
+ server_count = 0
+ last_heard = monotime()
+
+ print 'Host Address: %s' % host_addr[0]
+
+ while True:
+ wait_time = interval_length - (monotime() - respond_time)
+ if wait_time < 0:
+ wait_time = 0
+ sock_ready, _, _ = select.select([sock], [], [], wait_time)
+ if sock_ready:
+ data = sock.recv(65536)
+ magic, pack_type = struct.unpack('!4s4s', data[0:8])
+
+ if magic != 'TPDO':
+ continue
+
+ if pack_type == 'RNNG':
+ packet_counter += 1
+ _, _, server_count = struct.unpack('!4s4si', data[0:12])
+ last_heard = monotime()
+ if monotime() - respond_time > interval_length:
+ temp_time = monotime()
+ counter_msg = struct.pack('!4s4siid', 'TPDO', 'UPDT', server_count,
+ packet_counter, temp_time)
+ sock.sendto(counter_msg, host_addr)
+
+ if packet_counter > 0:
+ cycle_time = temp_time - respond_time
+ send_rate = server_count - packets_sent
+ recv_rate = packet_counter - packets_recv
+ if send_rate == 0:
+ success_rate = 0
+ else:
+ success_rate = float(recv_rate) / send_rate
+
+ print ('Send Rate: %.2fMbps, Receive Rate: %.2fMbps, '
+ 'Success Rate: %.2f%%' %
+ (send_rate * len(data) * 8 / 1000000 / cycle_time,
+ recv_rate * len(data) * 8 / 1000000 / cycle_time,
+ success_rate * 100))
+
+ packets_sent = server_count
+ packets_recv = packet_counter
+ respond_time = monotime()
+ if monotime() - start_time > float(opt.time) and opt.time:
+ break
+
+ if monotime() - last_heard > interval_length * 4:
+ sys.stderr.write('Failed, Connection Timed out')
+ sys.exit(1)
+
+if __name__ == '__main__':
+ main()
diff --git a/udp_test/udp_test_host.py b/udp_test/udp_test_host.py
new file mode 100755
index 0000000..b89e864
--- /dev/null
+++ b/udp_test/udp_test_host.py
@@ -0,0 +1,140 @@
+#!/usr/bin/python
+"""UDP test host.
+
+This is the host-side implementation of a UDP test tool to evaluate the speed
+of a connection using bursts of UDP traffic
+"""
+import select
+import socket
+import struct
+import sys
+import time
+
+import options
+
+try:
+ import monotime
+except ImportError:
+ pass
+try:
+ monotime = time.monotime
+except AttributeError:
+ monotime = time.time
+
+optspec = """
+udp_test_host.py [options...]
+--
+p,port= Specify a port to use. [5001]
+s,size= Specify the packet size [1472]
+"""
+
+
+def main():
+ o = options.Options(optspec)
+ opt, _, _ = o.parse(sys.argv[1:])
+
+ port = int(opt.port)
+ send_rate = 1000.0 # Hz
+ current_index = 0
+ timeout_val = 10
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.bind(('', port))
+
+ client_addr = ('', 0)
+ last_client_time = 0
+ last_packets_sent = 0
+ last_packets_recv = 0
+ remainder_time = 0
+
+ cycle_counter = 0
+ cycle_time = monotime()
+ msg_length = int(opt.size)
+ counter_msg = bytearray('7' * msg_length)
+ while True:
+ # Account for processing time
+ sock_status, _, _ = select.select([sock], [], [], remainder_time)
+ if sock_status:
+ host_time = monotime()
+ data, addr = sock.recvfrom(65536)
+ magic, pack_type, server_count, client_count, new_time = struct.unpack(
+ '!4s4siid', data)
+ if magic != 'TPDO' or pack_type != 'UPDT':
+ print 'Invalid Packet'
+ continue
+ if addr != client_addr:
+ current_index = 0
+ send_rate = 1000.0 # Hz
+
+ client_addr = addr
+ cycle_time = monotime()
+ cycle_counter = 0
+ # Keeps track of last time client was heard from
+ last_packets_sent = last_packets_recv = 0
+ last_client_time = new_time
+ host_time = monotime()
+ print 'connection established with %s\n' % client_addr[0]
+ else:
+ # Calculate metrics for user
+ cycle_length = new_time - last_client_time
+ measured_pack_sent = (server_count - last_packets_sent) / cycle_length
+ measured_send_rate = measured_pack_sent * msg_length * 8 / 1000000
+ measured_recv_rate = ((client_count - last_packets_recv) /
+ cycle_length * msg_length * 8 / 1000000)
+ if server_count <= last_packets_sent:
+ success_rate = 0
+ else:
+ success_rate = (float(client_count - last_packets_recv) /
+ (server_count - last_packets_sent))
+
+ print ('\nAttempted Rate: %.2fMbps, Sending Rate: %.2fMbps, '
+ 'Receive Rate: %.2fMbps, Success Rate: %.2f%%'
+ % (send_rate * msg_length * 8 / 1000000,
+ measured_send_rate,
+ measured_recv_rate,
+ success_rate * 100))
+
+ cycle_time = monotime()
+ cycle_counter = 0
+ if float(success_rate) > 0.9:
+ send_rate = 1.1 * measured_pack_sent
+ elif float(success_rate) < 0.8:
+ send_rate = 0.9 * measured_pack_sent
+ else:
+ send_rate = measured_pack_sent
+
+ # Prevent the host from not sending any packets.
+ if send_rate <= 0:
+ send_rate = 10
+ last_packets_recv = client_count
+ last_packets_sent = server_count
+ last_client_time = new_time
+
+ elif client_addr != ('', 0):
+ # No packets from client received
+ burst_counter = 0
+ while True:
+ remainder_time = (cycle_counter + 1) / send_rate - (monotime() -
+ cycle_time)
+ if remainder_time > 0:
+ break
+ cycle_counter += 1
+ current_index += 1
+ counter_msg[0:12] = struct.pack('!4s4si', 'TPDO', 'RNNG', current_index)
+ sock.sendto(counter_msg, client_addr)
+ burst_counter += 1
+ if not current_index % (int(send_rate/10) + 1):
+ sys.stdout.write('.')
+ sys.stdout.flush()
+ if monotime() - host_time > timeout_val:
+ client_addr = ('', 0)
+ sys.stderr.write('\nconnection lost with client\n')
+ remainder_time = 0
+ break
+ if burst_counter > 1000:
+ remainder_time = 0
+ break
+
+if __name__ == '__main__':
+ main()
+