Add urh
This commit is contained in:
0
Software/Universal Radio Hacker/src/urh/__init__.py
Normal file
0
Software/Universal Radio Hacker/src/urh/__init__.py
Normal file
@@ -0,0 +1,434 @@
|
||||
import fractions
|
||||
import itertools
|
||||
import math
|
||||
import sys
|
||||
from collections import Counter
|
||||
|
||||
import numpy as np
|
||||
|
||||
from urh.ainterpretation import Wavelet
|
||||
from urh.cythonext import auto_interpretation as c_auto_interpretation
|
||||
from urh.cythonext import signal_functions
|
||||
from urh.cythonext import util
|
||||
from urh.signalprocessing.IQArray import IQArray
|
||||
|
||||
|
||||
def max_without_outliers(data: np.ndarray, z=3):
|
||||
if len(data) == 0:
|
||||
return None
|
||||
|
||||
return np.max(data[abs(data - np.mean(data)) <= z * np.std(data)])
|
||||
|
||||
|
||||
def min_without_outliers(data: np.ndarray, z=2):
|
||||
if len(data) == 0:
|
||||
return None
|
||||
|
||||
return np.min(data[abs(data - np.mean(data)) <= z * np.std(data)])
|
||||
|
||||
def get_most_frequent_value(values: list):
|
||||
"""
|
||||
Return the most frequent value in list.
|
||||
If there is no unique one, return the maximum of the most frequent values
|
||||
|
||||
:param values:
|
||||
:return:
|
||||
"""
|
||||
if len(values) == 0:
|
||||
return None
|
||||
|
||||
most_common = Counter(values).most_common()
|
||||
result, max_count = most_common[0]
|
||||
for value, count in most_common:
|
||||
if count < max_count:
|
||||
return result
|
||||
else:
|
||||
result = value
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def most_common(values: list):
|
||||
"""
|
||||
Return the most common value in a list. In case of ties, return the value that appears first in list
|
||||
:param values:
|
||||
:return:
|
||||
"""
|
||||
counter = Counter(values)
|
||||
return max(values, key=counter.get)
|
||||
|
||||
|
||||
def detect_noise_level(magnitudes):
|
||||
if len(magnitudes) <= 3:
|
||||
return 0
|
||||
|
||||
# 1% for best accuracy and performance for large signals
|
||||
chunksize_percent = 1
|
||||
chunksize = max(1, int(len(magnitudes) * chunksize_percent / 100))
|
||||
|
||||
chunks = [magnitudes[i - chunksize:i] for i in range(len(magnitudes), 0, -chunksize) if i - chunksize >= 0]
|
||||
|
||||
mean_values = np.fromiter((np.mean(chunk) for chunk in chunks), dtype=np.float32, count=len(chunks))
|
||||
minimum, maximum = util.minmax(mean_values)
|
||||
if maximum == 0 or minimum / maximum > 0.9:
|
||||
# Mean values are very close to each other, so there is probably no noise in the signal
|
||||
return 0
|
||||
|
||||
# Get all indices for values which are in range of 10% of minimum mean value
|
||||
indices = np.nonzero(mean_values <= 1.1 * np.min(mean_values))[0]
|
||||
|
||||
try:
|
||||
result = np.max([np.max(chunks[i]) for i in indices if len(chunks[i]) > 0])
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
# Round up to fourth digit
|
||||
return math.ceil(result * 10000) / 10000
|
||||
|
||||
|
||||
def segment_messages_from_magnitudes(magnitudes: np.ndarray, noise_threshold: float):
|
||||
"""
|
||||
Get the list of start, end indices of messages
|
||||
|
||||
:param magnitudes: Magnitudes of samples
|
||||
:param noise_threshold: Threshold for noise
|
||||
:return:
|
||||
"""
|
||||
return c_auto_interpretation.segment_messages_from_magnitudes(magnitudes, noise_threshold)
|
||||
|
||||
|
||||
def merge_message_segments_for_ook(segments: list):
|
||||
if len(segments) <= 1:
|
||||
return segments
|
||||
|
||||
result = []
|
||||
# Get a array of pauses for comparison
|
||||
pauses = np.fromiter(
|
||||
(segments[i + 1][0] - segments[i][1] for i in range(len(segments) - 1)),
|
||||
count=len(segments) - 1,
|
||||
dtype=np.uint64
|
||||
)
|
||||
|
||||
pulses = np.fromiter(
|
||||
(segments[i][1] - segments[i][0] for i in range(len(segments))),
|
||||
count=len(segments),
|
||||
dtype=np.uint64
|
||||
)
|
||||
|
||||
# Find relatively large pauses, these mark new messages
|
||||
min_pulse_length = min_without_outliers(pulses, z=1)
|
||||
large_pause_indices = np.nonzero(pauses >= 8 * min_pulse_length)[0]
|
||||
|
||||
# Merge Pulse Lengths between long pauses
|
||||
for i in range(0, len(large_pause_indices) + 1):
|
||||
if i == 0:
|
||||
start, end = 0, large_pause_indices[i] + 1 if len(large_pause_indices) >= 1 else len(segments)
|
||||
elif i == len(large_pause_indices):
|
||||
start, end = large_pause_indices[i - 1] + 1, len(segments)
|
||||
else:
|
||||
start, end = large_pause_indices[i - 1] + 1, large_pause_indices[i] + 1
|
||||
|
||||
msg_begin = segments[start][0]
|
||||
msg_length = sum(segments[j][1] - segments[j][0] for j in range(start, end))
|
||||
msg_length += sum(segments[j][0] - segments[j - 1][1] for j in range(start + 1, end))
|
||||
|
||||
result.append((msg_begin, msg_begin + msg_length))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def detect_modulation(data: np.ndarray, wavelet_scale=4, median_filter_order=11) -> str:
|
||||
n_data = len(data)
|
||||
data = data[np.abs(data) > 0]
|
||||
if len(data) == 0:
|
||||
return None
|
||||
|
||||
if n_data - len(data) > 3:
|
||||
return "OOK"
|
||||
|
||||
data = data / np.abs(np.max(data))
|
||||
mag_wavlt = np.abs(Wavelet.cwt_haar(data, scale=wavelet_scale))
|
||||
if len(mag_wavlt) == 0:
|
||||
return None
|
||||
|
||||
norm_mag_wavlt = np.abs(Wavelet.cwt_haar(data / np.abs(data), scale=wavelet_scale))
|
||||
|
||||
var_mag = np.var(mag_wavlt)
|
||||
var_norm_mag = np.var(norm_mag_wavlt)
|
||||
|
||||
var_filtered_mag = np.var(c_auto_interpretation.median_filter(mag_wavlt, k=median_filter_order))
|
||||
var_filtered_norm_mag = np.var(c_auto_interpretation.median_filter(norm_mag_wavlt, k=median_filter_order))
|
||||
|
||||
if all(v < 0.15 for v in (var_mag, var_norm_mag, var_filtered_mag, var_filtered_norm_mag)):
|
||||
return "OOK"
|
||||
|
||||
if var_mag > 1.5 * var_norm_mag:
|
||||
# ASK or QAM
|
||||
# todo: consider qam, compare filtered mag and filtered norm mag
|
||||
return "ASK"
|
||||
else:
|
||||
# FSK or PSK
|
||||
if var_mag > 10 * var_filtered_mag:
|
||||
return "PSK"
|
||||
else:
|
||||
# Now we either have a FSK signal or we a have OOK single pulse
|
||||
# If we have an FSK, there should be at least two peaks in FFT
|
||||
fft = np.fft.fft(data[0:2 ** int(np.log2(len(data)))])
|
||||
fft = np.abs(np.fft.fftshift(fft))
|
||||
ten_greatest_indices = np.argsort(fft)[::-1][0:10]
|
||||
greatest_index = ten_greatest_indices[0]
|
||||
min_distance = 10
|
||||
min_freq = 100 # 100 seems to be magnitude of noise frequency
|
||||
|
||||
if any(abs(i - greatest_index) >= min_distance and fft[i] >= min_freq for i in ten_greatest_indices):
|
||||
return "FSK"
|
||||
else:
|
||||
return "OOK"
|
||||
|
||||
|
||||
def detect_modulation_for_messages(signal: IQArray, message_indices: list) -> str:
|
||||
max_messages = 100
|
||||
|
||||
modulations_for_messages = []
|
||||
complex = signal.as_complex64()
|
||||
for start, end in message_indices[0:max_messages]:
|
||||
mod = detect_modulation(complex[start:end])
|
||||
if mod is not None:
|
||||
modulations_for_messages.append(mod)
|
||||
|
||||
if len(modulations_for_messages) == 0:
|
||||
return None
|
||||
|
||||
return most_common(modulations_for_messages)
|
||||
|
||||
|
||||
def detect_center(rectangular_signal: np.ndarray, max_size=None):
|
||||
rect = rectangular_signal[rectangular_signal > -4] # do not consider noise
|
||||
|
||||
# Ignore the first and last 5% of samples,
|
||||
# because there tends to be an overshoot at start/end of rectangular signal
|
||||
rect = rect[int(0.05*len(rect)):int(0.95*len(rect))]
|
||||
|
||||
if max_size is not None and len(rect) > max_size:
|
||||
rect = rect[0:max_size]
|
||||
|
||||
hist_min, hist_max = util.minmax(rect)
|
||||
|
||||
# The step size of histogram is set to variance of the rectangular signal
|
||||
# If a signal has low variance we need to be more accurate at center detection
|
||||
hist_step = float(np.var(rect))
|
||||
|
||||
try:
|
||||
y, x = np.histogram(rect, bins=np.arange(hist_min, hist_max + hist_step, hist_step))
|
||||
except (ZeroDivisionError, ValueError):
|
||||
# For a segment with zero variance (constant line) it is not possible to find a center
|
||||
return None
|
||||
|
||||
num_values = 2
|
||||
most_common_levels = []
|
||||
|
||||
window_size = max(2, int(0.05*len(y)) + 1)
|
||||
|
||||
def get_elem(arr, index: int, default):
|
||||
if 0 <= index < len(arr):
|
||||
return arr[index]
|
||||
else:
|
||||
return default
|
||||
|
||||
for index in np.argsort(y)[::-1]:
|
||||
# check if we have a local maximum in histogram, if yes, append the value
|
||||
if all(y[index] > get_elem(y, index+i, 0) and
|
||||
y[index] > get_elem(y, index-i, 0)
|
||||
for i in range(1, window_size)):
|
||||
most_common_levels.append(x[index])
|
||||
|
||||
if len(most_common_levels) == num_values:
|
||||
break
|
||||
|
||||
if len(most_common_levels) == 0:
|
||||
return None
|
||||
|
||||
# todo if num values greater two return more centers
|
||||
return np.mean(most_common_levels)
|
||||
|
||||
|
||||
def estimate_tolerance_from_plateau_lengths(plateau_lengths, relative_max=0.05) -> int:
|
||||
if len(plateau_lengths) <= 1:
|
||||
return None
|
||||
|
||||
unique = np.unique(plateau_lengths)
|
||||
maximum = max_without_outliers(unique, z=2)
|
||||
|
||||
limit = relative_max * maximum
|
||||
# limit = np.mean(plateau_lengths) - 1 * np.std(plateau_lengths)
|
||||
if unique[0] > 1 and unique[0] >= limit:
|
||||
return 0
|
||||
|
||||
result = 0
|
||||
for value in unique:
|
||||
if value > 1 and value >= limit:
|
||||
break
|
||||
result = value
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def merge_plateau_lengths(plateau_lengths, tolerance=None) -> list:
|
||||
if tolerance is None:
|
||||
tolerance = estimate_tolerance_from_plateau_lengths(plateau_lengths)
|
||||
|
||||
if tolerance == 0 or tolerance is None:
|
||||
return plateau_lengths
|
||||
|
||||
return c_auto_interpretation.merge_plateaus(plateau_lengths, tolerance, max_count=10000)
|
||||
|
||||
|
||||
def round_plateau_lengths(plateau_lengths: list):
|
||||
"""
|
||||
Round plateau lengths to next divisible number of digit count e.g. 99 -> 100, 293 -> 300
|
||||
|
||||
:param plateau_lengths:
|
||||
:return:
|
||||
"""
|
||||
# round to n_digits of most common value
|
||||
digit_counts = [len(str(p)) for p in plateau_lengths]
|
||||
n_digits = min(3, int(np.percentile(digit_counts, 50)))
|
||||
f = 10 ** (n_digits - 1)
|
||||
|
||||
for i, plateau_len in enumerate(plateau_lengths):
|
||||
plateau_lengths[i] = int(round(plateau_len / f)) * f
|
||||
|
||||
|
||||
def get_tolerant_greatest_common_divisor(numbers):
|
||||
"""
|
||||
Get the greatest common divisor of the numbers in a tolerant manner:
|
||||
Calculate each gcd of each pair of numbers and return the most common one
|
||||
|
||||
"""
|
||||
gcd = math.gcd if sys.version_info >= (3, 5) else fractions.gcd
|
||||
|
||||
gcds = [gcd(x, y) for x, y in itertools.combinations(numbers, 2) if gcd(x, y) != 1]
|
||||
if len(gcds) == 0:
|
||||
return 1
|
||||
|
||||
return get_most_frequent_value(gcds)
|
||||
|
||||
|
||||
def get_bit_length_from_plateau_lengths(merged_plateau_lengths) -> int:
|
||||
if len(merged_plateau_lengths) == 0:
|
||||
return 0
|
||||
|
||||
if len(merged_plateau_lengths) == 1:
|
||||
return int(merged_plateau_lengths[0])
|
||||
|
||||
round_plateau_lengths(merged_plateau_lengths)
|
||||
histogram = c_auto_interpretation.get_threshold_divisor_histogram(merged_plateau_lengths)
|
||||
|
||||
if len(histogram) == 0:
|
||||
return 0
|
||||
else:
|
||||
# Can't return simply argmax, since this could be a multiple of result (e.g. 2 1s are transmitted often)
|
||||
sorted_indices = np.argsort(histogram)[::-1]
|
||||
max_count = histogram[sorted_indices[0]]
|
||||
result = sorted_indices[0]
|
||||
|
||||
for i in range(1, len(sorted_indices)):
|
||||
if histogram[sorted_indices[i]] < 0.25 * max_count:
|
||||
break
|
||||
if sorted_indices[i] <= 0.5 * result:
|
||||
result = sorted_indices[i]
|
||||
|
||||
return int(result)
|
||||
|
||||
|
||||
def estimate(iq_array: IQArray, noise: float = None, modulation: str = None) -> dict:
|
||||
if isinstance(iq_array, np.ndarray):
|
||||
iq_array = IQArray(iq_array)
|
||||
|
||||
magnitudes = iq_array.magnitudes
|
||||
# find noise threshold
|
||||
noise = detect_noise_level(magnitudes) if noise is None else noise
|
||||
|
||||
# segment messages
|
||||
message_indices = segment_messages_from_magnitudes(magnitudes, noise_threshold=noise)
|
||||
|
||||
# detect modulation
|
||||
modulation = detect_modulation_for_messages(iq_array, message_indices) if modulation is None else modulation
|
||||
if modulation is None:
|
||||
return None
|
||||
|
||||
if modulation == "OOK":
|
||||
message_indices = merge_message_segments_for_ook(message_indices)
|
||||
|
||||
if modulation == "OOK" or modulation == "ASK":
|
||||
data = signal_functions.afp_demod(iq_array.data, noise, "ASK", 2)
|
||||
elif modulation == "FSK":
|
||||
data = signal_functions.afp_demod(iq_array.data, noise, "FSK", 2)
|
||||
elif modulation == "PSK":
|
||||
data = signal_functions.afp_demod(iq_array.data, noise, "PSK", 2)
|
||||
else:
|
||||
raise ValueError("Unsupported Modulation")
|
||||
|
||||
centers = []
|
||||
bit_lengths = []
|
||||
tolerances = []
|
||||
for start, end in message_indices:
|
||||
msg_rect_data = data[start:end]
|
||||
|
||||
center = detect_center(msg_rect_data)
|
||||
if center is None:
|
||||
continue
|
||||
|
||||
plateau_lengths = c_auto_interpretation.get_plateau_lengths(msg_rect_data, center, percentage=25)
|
||||
tolerance = estimate_tolerance_from_plateau_lengths(plateau_lengths)
|
||||
if tolerance is None:
|
||||
tolerance = 0
|
||||
else:
|
||||
tolerances.append(tolerance)
|
||||
|
||||
merged_lengths = merge_plateau_lengths(plateau_lengths, tolerance=tolerance)
|
||||
if len(merged_lengths) < 2:
|
||||
continue
|
||||
|
||||
bit_length = get_bit_length_from_plateau_lengths(merged_lengths)
|
||||
|
||||
min_bit_length = tolerance + 1
|
||||
|
||||
if bit_length > min_bit_length:
|
||||
# only add to score if found bit length surpasses minimum bit length
|
||||
centers.append(center)
|
||||
bit_lengths.append(bit_length)
|
||||
|
||||
# Since we cannot have different centers per message (yet) we need to combine them to return a common center
|
||||
if modulation == "OOK" or modulation == "ASK":
|
||||
# for ask modulations the center tends to be the minimum of all found centers
|
||||
center = min_without_outliers(np.array(centers), z=2)
|
||||
if center is None:
|
||||
# did not find any centers at all so we cannot return a valid estimation
|
||||
return None
|
||||
elif len(centers) > 0:
|
||||
# for other modulations it is a better strategy to take the mean of found centers
|
||||
center = np.mean(centers)
|
||||
else:
|
||||
# did not find any centers at all so we cannot return a valid estimation
|
||||
return None
|
||||
|
||||
bit_length = get_most_frequent_value(bit_lengths)
|
||||
if bit_length is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
tolerance = np.percentile(tolerances, 50)
|
||||
except IndexError:
|
||||
# no tolerances found, default to 5% of bit length
|
||||
tolerance = max(1, int(0.05 * bit_length))
|
||||
|
||||
result = {
|
||||
"modulation_type": "ASK" if modulation == "OOK" else modulation,
|
||||
"bit_length": bit_length,
|
||||
"center": center,
|
||||
"tolerance": int(tolerance),
|
||||
"noise": noise
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,134 @@
|
||||
import numpy as np
|
||||
|
||||
from urh.cythonext import auto_interpretation as cy_auto_interpretation
|
||||
from urh.signalprocessing.Modulator import Modulator
|
||||
|
||||
|
||||
def normalized_haar_wavelet(omega, scale):
|
||||
omega_cpy = omega[:] / scale
|
||||
omega_cpy[0] = 1.0 # first element always zero, so prevent division by zero later
|
||||
|
||||
result = (1j * np.square(-1 + np.exp(0.5j * omega))) / omega_cpy
|
||||
return result
|
||||
|
||||
|
||||
def cwt_haar(x: np.ndarray, scale=10):
|
||||
"""
|
||||
continuous haar wavelet transform based on the paper
|
||||
"A practical guide to wavelet analysis" by Christopher Torrence and Gilbert P Compo
|
||||
|
||||
"""
|
||||
next_power_two = 2 ** int(np.log2(len(x)))
|
||||
|
||||
x = x[0:next_power_two]
|
||||
num_data = len(x)
|
||||
|
||||
# get FFT of x (eq. (3) in paper)
|
||||
x_hat = np.fft.fft(x)
|
||||
|
||||
# Get omega (eq. (5) in paper)
|
||||
f = (2.0 * np.pi / num_data)
|
||||
omega = f * np.concatenate((np.arange(0, num_data // 2), np.arange(num_data // 2, num_data) * -1))
|
||||
|
||||
# get psi hat (eq. (6) in paper)
|
||||
psi_hat = np.sqrt(2.0 * np.pi * scale) * normalized_haar_wavelet(scale * omega, scale)
|
||||
|
||||
# get W (eq. (4) in paper)
|
||||
W = np.fft.ifft(x_hat * psi_hat)
|
||||
|
||||
return W[2 * scale:-2 * scale]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
# data = np.fromfile("/home/joe/GIT/urh/tests/data/fsk.complex", dtype=np.complex64)[5:15000]
|
||||
# data = np.fromfile("/home/joe/GIT/urh/tests/data/ask.complex", dtype=np.complex64)[462:754]
|
||||
# data = np.fromfile("/home/joe/GIT/urh/tests/data/enocean.complex", dtype=np.complex64)[9724:10228]
|
||||
data = np.fromfile("/home/joe/GIT/publications/ainterpretation/experiments/signals/esaver_test4on.complex",
|
||||
dtype=np.complex64)[86452:115541]
|
||||
|
||||
# data = np.fromfile("/home/joe/GIT/urh/tests/data/action_ook.complex", dtype=np.complex64)[3780:4300]
|
||||
|
||||
# data = np.fromfile("/home/joe/GIT/urh/tests/data/ask50.complex", dtype=np.complex64)
|
||||
# Wavelet transform the data
|
||||
# data = np.fromfile("/home/joe/GIT/urh/tests/data/ask.complex", dtype=np.complex64)[0:2 ** 13]
|
||||
|
||||
# data = np.fromfile("/tmp/generated.complex", dtype=np.complex64)
|
||||
|
||||
# data = np.fromfile("/tmp/psk.complex", dtype=np.complex64)
|
||||
# data = np.fromfile("/home/joe/GIT/urh/tests/data/psk_generated.complex", dtype=np.complex64)[0:8000]
|
||||
modulator = Modulator("")
|
||||
modulator.modulation_type = "PSK"
|
||||
modulator.parameters[0] = 0
|
||||
modulator.parameters[1] = 180
|
||||
modulator.carrier_freq_hz = 5e3
|
||||
modulator.sample_rate = 200e3
|
||||
# data = modulator.modulate("1010", pause=0)
|
||||
|
||||
# data = np.fromfile("/tmp/ask25.complex", dtype=np.complex64)
|
||||
# data = np.fromfile("/tmp/ask1080.complex", dtype=np.complex64)
|
||||
|
||||
scale = 4
|
||||
median_filter_order = 11
|
||||
data = data[np.abs(data) > 0]
|
||||
|
||||
# Normalize with max of data to prevent increasing variance for signals with lower amplitude
|
||||
data = data / np.abs(np.max(data))
|
||||
|
||||
mag_wvlt = np.abs(cwt_haar(data, scale=scale))
|
||||
|
||||
norm_mag_wvlt = np.abs(cwt_haar(data / np.abs(data), scale=scale))
|
||||
|
||||
median_filter = cy_auto_interpretation.median_filter
|
||||
|
||||
filtered_mag_wvlt = median_filter(mag_wvlt, k=median_filter_order)
|
||||
|
||||
filtered_mag_norm_wvlt = median_filter(norm_mag_wvlt, k=median_filter_order)
|
||||
|
||||
plt.subplot(421)
|
||||
plt.title("Original data")
|
||||
plt.plot(data)
|
||||
|
||||
plt.subplot(422)
|
||||
plt.title("Amplitude normalized data")
|
||||
plt.plot(data / np.abs(data))
|
||||
|
||||
plt.subplot(423)
|
||||
plt.title("CWT ({0:.4f})".format(np.var(mag_wvlt)))
|
||||
plt.plot(mag_wvlt)
|
||||
|
||||
plt.subplot(424)
|
||||
plt.title("Filtered CWT ({0:.4f})".format(np.var(filtered_mag_wvlt)))
|
||||
plt.plot(filtered_mag_wvlt)
|
||||
|
||||
plt.subplot(425)
|
||||
plt.title("Norm CWT ({0:.4f})".format(np.var(norm_mag_wvlt)))
|
||||
plt.plot(norm_mag_wvlt)
|
||||
|
||||
plt.subplot(426)
|
||||
plt.title("Filtered Norm CWT ({0:.4f})".format(np.var(filtered_mag_norm_wvlt)))
|
||||
plt.plot(filtered_mag_norm_wvlt)
|
||||
|
||||
plt.subplot(427)
|
||||
plt.title("FFT magnitude")
|
||||
fft = np.fft.fft(data)
|
||||
fft = np.abs(fft)
|
||||
ten_greatest_indices = np.argsort(fft)[::-1][0:10]
|
||||
print(ten_greatest_indices)
|
||||
print(fft[ten_greatest_indices])
|
||||
plt.plot(np.fft.fftshift(fft))
|
||||
|
||||
plt.subplot(428)
|
||||
fft = np.fft.fftshift(np.fft.fft(data))
|
||||
fft[np.abs(fft) < 0.2 * np.max(np.abs(fft))] = 0
|
||||
fft_phase = np.angle(fft)
|
||||
ten_greatest_indices = np.argsort(np.abs(fft_phase))[::-1][0:10]
|
||||
print("FFT phases:")
|
||||
print(ten_greatest_indices)
|
||||
print(fft_phase[ten_greatest_indices])
|
||||
|
||||
plt.title("FFT phase ({:.2f})".format(np.var(fft_phase)))
|
||||
plt.plot(fft_phase)
|
||||
|
||||
plt.show()
|
||||
65
Software/Universal Radio Hacker/src/urh/awre/AutoAssigner.py
Normal file
65
Software/Universal Radio Hacker/src/urh/awre/AutoAssigner.py
Normal file
@@ -0,0 +1,65 @@
|
||||
import numpy as np
|
||||
|
||||
from urh.cythonext import util
|
||||
from urh.signalprocessing.Message import Message
|
||||
|
||||
|
||||
def auto_assign_participants(messages, participants):
|
||||
"""
|
||||
|
||||
:type messages: list of Message
|
||||
:type participants: list of Participant
|
||||
:return:
|
||||
"""
|
||||
if len(participants) == 0:
|
||||
return
|
||||
|
||||
if len(participants) == 1:
|
||||
for message in messages: # type: Message
|
||||
message.participant = participants[0]
|
||||
return
|
||||
|
||||
# Try to assign participants based on SRC_ADDRESS label and participant address
|
||||
for msg in filter(lambda m: m.participant is None, messages):
|
||||
src_address = msg.get_src_address_from_data()
|
||||
if src_address:
|
||||
try:
|
||||
msg.participant = next(p for p in participants if p.address_hex == src_address)
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
# Assign remaining participants based on RSSI of messages
|
||||
rssis = np.array([msg.rssi for msg in messages], dtype=np.float32)
|
||||
min_rssi, max_rssi = util.minmax(rssis)
|
||||
center_spacing = (max_rssi - min_rssi) / (len(participants) - 1)
|
||||
centers = [min_rssi + i * center_spacing for i in range(0, len(participants))]
|
||||
rssi_assigned_centers = []
|
||||
|
||||
for rssi in rssis:
|
||||
center_index = np.argmin(np.abs(rssi - centers))
|
||||
rssi_assigned_centers.append(int(center_index))
|
||||
|
||||
participants.sort(key=lambda participant: participant.relative_rssi)
|
||||
for message, center_index in zip(messages, rssi_assigned_centers):
|
||||
if message.participant is None:
|
||||
message.participant = participants[center_index]
|
||||
|
||||
|
||||
def auto_assign_participant_addresses(messages, participants):
|
||||
"""
|
||||
|
||||
:type messages: list of Message
|
||||
:type participants: list of Participant
|
||||
:return:
|
||||
"""
|
||||
participants_without_address = [p for p in participants if not p.address_hex]
|
||||
|
||||
if len(participants_without_address) == 0:
|
||||
return
|
||||
|
||||
for msg in messages:
|
||||
if msg.participant in participants_without_address:
|
||||
src_address = msg.get_src_address_from_data()
|
||||
if src_address:
|
||||
participants_without_address.remove(msg.participant)
|
||||
msg.participant.address_hex = src_address
|
||||
296
Software/Universal Radio Hacker/src/urh/awre/CommonRange.py
Normal file
296
Software/Universal Radio Hacker/src/urh/awre/CommonRange.py
Normal file
@@ -0,0 +1,296 @@
|
||||
import copy
|
||||
import itertools
|
||||
|
||||
import numpy as np
|
||||
|
||||
from urh.util import util
|
||||
from urh.util.GenericCRC import GenericCRC
|
||||
|
||||
|
||||
class CommonRange(object):
|
||||
def __init__(self, start, length, value: np.ndarray = None, score=0, field_type="Generic", message_indices=None,
|
||||
range_type="bit", byte_order="big"):
|
||||
"""
|
||||
|
||||
:param start:
|
||||
:param length:
|
||||
:param value: Value for this common range as string
|
||||
"""
|
||||
self.start = start
|
||||
self.length = length
|
||||
|
||||
self.__byte_order = byte_order
|
||||
self.sync_end = 0
|
||||
|
||||
if isinstance(value, str):
|
||||
value = np.array(list(map(lambda x: int(x, 16), value)), dtype=np.uint8)
|
||||
|
||||
self.values = [value] if value is not None else []
|
||||
self.score = score
|
||||
self.field_type = field_type # can also be length, address etc.
|
||||
|
||||
self.range_type = range_type.lower() # one of bit/hex/byte
|
||||
|
||||
self.message_indices = set() if message_indices is None else set(message_indices)
|
||||
"""
|
||||
Set of message indices, this range applies to
|
||||
"""
|
||||
|
||||
@property
|
||||
def end(self):
|
||||
return self.start + self.length - 1
|
||||
|
||||
@property
|
||||
def bit_start(self):
|
||||
return self.__convert_number(self.start) + self.sync_end
|
||||
|
||||
@property
|
||||
def bit_end(self):
|
||||
return self.__convert_number(self.start) + self.__convert_number(self.length) - 1 + self.sync_end
|
||||
|
||||
@property
|
||||
def length_in_bits(self):
|
||||
return self.bit_end - self.bit_start - 1
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
if len(self.values) == 0:
|
||||
return None
|
||||
elif len(self.values) == 1:
|
||||
return self.values[0]
|
||||
else:
|
||||
raise ValueError("This range has multiple values!")
|
||||
|
||||
@value.setter
|
||||
def value(self, val):
|
||||
if len(self.values) == 0:
|
||||
self.values = [val]
|
||||
elif len(self.values) == 1:
|
||||
self.values[0] = val
|
||||
else:
|
||||
raise ValueError("This range has multiple values!")
|
||||
|
||||
@property
|
||||
def byte_order(self):
|
||||
if self.byte_order_is_unknown:
|
||||
return "big"
|
||||
return self.__byte_order
|
||||
|
||||
@byte_order.setter
|
||||
def byte_order(self, val: str):
|
||||
self.__byte_order = val
|
||||
|
||||
@property
|
||||
def byte_order_is_unknown(self) -> bool:
|
||||
return self.__byte_order is None
|
||||
|
||||
def matches(self, start: int, value: np.ndarray):
|
||||
return self.start == start and \
|
||||
self.length == len(value) and \
|
||||
self.value.tobytes() == value.tobytes()
|
||||
|
||||
def __convert_number(self, n):
|
||||
if self.range_type == "bit":
|
||||
return n
|
||||
elif self.range_type == "hex":
|
||||
return n * 4
|
||||
elif self.range_type == "byte":
|
||||
return n * 8
|
||||
else:
|
||||
raise ValueError("Unknown range type {}".format(self.range_type))
|
||||
|
||||
def __repr__(self):
|
||||
result = "{} {}-{} ({} {})".format(self.field_type, self.bit_start,
|
||||
self.bit_end, self.length, self.range_type)
|
||||
|
||||
result += " Values: " + " ".join(map(util.convert_numbers_to_hex_string, self.values))
|
||||
if self.score is not None:
|
||||
result += " Score: " + str(self.score)
|
||||
result += " Message indices: {" + ",".join(map(str, sorted(self.message_indices))) + "}"
|
||||
return result
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, CommonRange):
|
||||
return False
|
||||
|
||||
return self.bit_start == other.bit_start and \
|
||||
self.bit_end == other.bit_end and \
|
||||
self.field_type == other.field_type
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.start, self.length, self.field_type))
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.bit_start < other.bit_start
|
||||
|
||||
def overlaps_with(self, other) -> bool:
|
||||
if not isinstance(other, CommonRange):
|
||||
raise ValueError("Need another bit range to compare")
|
||||
return any(i in range(self.bit_start, self.bit_end)
|
||||
for i in range(other.bit_start, other.bit_end))
|
||||
|
||||
def ensure_not_overlaps(self, start: int, end: int):
|
||||
"""
|
||||
|
||||
:param start:
|
||||
:param end:
|
||||
:rtype: list of CommonRange
|
||||
"""
|
||||
if end < self.start or start > self.end:
|
||||
# Other range is right or left of our range -> no overlapping
|
||||
return [copy.deepcopy(self)]
|
||||
|
||||
if start <= self.start < end < self.end:
|
||||
# overlaps on the left
|
||||
result = copy.deepcopy(self)
|
||||
result.length -= end - result.start
|
||||
result.start = end
|
||||
result.value = result.value[result.start-self.start:(result.start-self.start)+result.length]
|
||||
return [result]
|
||||
|
||||
if self.start < start <= self.end <= end:
|
||||
# overlaps on the right
|
||||
result = copy.deepcopy(self)
|
||||
result.length -= self.end + 1 - start
|
||||
result.value = result.value[:result.length]
|
||||
return [result]
|
||||
|
||||
if self.start < start and self.end > end:
|
||||
# overlaps in the middle
|
||||
left = copy.deepcopy(self)
|
||||
right = copy.deepcopy(self)
|
||||
|
||||
left.length -= (left.end + 1 - start)
|
||||
left.value = self.value[:left.length]
|
||||
|
||||
right.start = end + 1
|
||||
right.length = self.end - end
|
||||
right.value = self.value[right.start-self.start:(right.start-self.start)+right.length]
|
||||
return [left, right]
|
||||
|
||||
return []
|
||||
|
||||
|
||||
class ChecksumRange(CommonRange):
|
||||
def __init__(self, start, length, crc: GenericCRC, data_range_start, data_range_end, value: np.ndarray = None,
|
||||
score=0, field_type="Generic", message_indices=None, range_type="bit"):
|
||||
super().__init__(start, length, value, score, field_type, message_indices, range_type)
|
||||
self.data_range_start = data_range_start
|
||||
self.data_range_end = data_range_end
|
||||
self.crc = crc
|
||||
|
||||
@property
|
||||
def data_range_bit_start(self):
|
||||
return self.data_range_start + self.sync_end
|
||||
|
||||
@property
|
||||
def data_range_bit_end(self):
|
||||
return self.data_range_end + self.sync_end
|
||||
|
||||
def __eq__(self, other):
|
||||
return super().__eq__(other) \
|
||||
and self.data_range_start == other.data_range_start \
|
||||
and self.data_range_end == other.data_range_end \
|
||||
and self.crc == other.crc
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.start, self.length, self.data_range_start, self.data_range_end, self.crc))
|
||||
|
||||
def __repr__(self):
|
||||
return super().__repr__() + " \t" + \
|
||||
"{}".format(self.crc.caption) + \
|
||||
" Datarange: {}-{} ".format(self.data_range_start, self.data_range_end)
|
||||
|
||||
|
||||
class EmptyCommonRange(CommonRange):
|
||||
"""
|
||||
Empty Common Bit Range, to indicate, that no common Bit Range was found
|
||||
"""
|
||||
|
||||
def __init__(self, field_type="Generic"):
|
||||
super().__init__(0, 0, "")
|
||||
self.field_type = field_type
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, EmptyCommonRange) \
|
||||
and other.field_type == self.field_type
|
||||
|
||||
def __repr__(self):
|
||||
return "No " + self.field_type
|
||||
|
||||
def __hash__(self):
|
||||
return hash(super)
|
||||
|
||||
|
||||
class CommonRangeContainer(object):
|
||||
"""
|
||||
This is the raw equivalent of a Message Type:
|
||||
A container of common ranges
|
||||
"""
|
||||
|
||||
def __init__(self, ranges: list, message_indices: set = None):
|
||||
|
||||
assert isinstance(ranges, list)
|
||||
|
||||
self.__ranges = ranges # type: list[CommonRange]
|
||||
self.__ranges.sort()
|
||||
|
||||
if message_indices is None:
|
||||
self.update_message_indices()
|
||||
else:
|
||||
self.message_indices = message_indices
|
||||
|
||||
@property
|
||||
def ranges_overlap(self) -> bool:
|
||||
return self.has_overlapping_ranges(self.__ranges)
|
||||
|
||||
def update_message_indices(self):
|
||||
if len(self) == 0:
|
||||
self.message_indices = set()
|
||||
else:
|
||||
self.message_indices = set(self[0].message_indices)
|
||||
for i in range(1, len(self)):
|
||||
self.message_indices.intersection_update(self[i].message_indices)
|
||||
|
||||
def add_range(self, rng: CommonRange):
|
||||
self.__ranges.append(rng)
|
||||
self.__ranges.sort()
|
||||
|
||||
def add_ranges(self, ranges: list):
|
||||
self.__ranges.extend(ranges)
|
||||
self.__ranges.sort()
|
||||
|
||||
def has_same_ranges(self, ranges: list) -> bool:
|
||||
return self.__ranges == ranges
|
||||
|
||||
def has_same_ranges_as_container(self, container):
|
||||
if not isinstance(container, CommonRangeContainer):
|
||||
return False
|
||||
|
||||
return self.__ranges == container.__ranges
|
||||
|
||||
@staticmethod
|
||||
def has_overlapping_ranges(ranges: list) -> bool:
|
||||
for rng1, rng2 in itertools.combinations(ranges, 2):
|
||||
if rng1.overlaps_with(rng2):
|
||||
return True
|
||||
return False
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__ranges)
|
||||
|
||||
def __iter__(self):
|
||||
return self.__ranges.__iter__()
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.__ranges[item]
|
||||
|
||||
def __repr__(self):
|
||||
from pprint import pformat
|
||||
return pformat(self.__ranges)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, CommonRangeContainer):
|
||||
return False
|
||||
|
||||
return self.__ranges == other.__ranges and self.message_indices == other.message_indices
|
||||
435
Software/Universal Radio Hacker/src/urh/awre/FormatFinder.py
Normal file
435
Software/Universal Radio Hacker/src/urh/awre/FormatFinder.py
Normal file
@@ -0,0 +1,435 @@
|
||||
import copy
|
||||
import math
|
||||
from collections import defaultdict
|
||||
|
||||
import numpy as np
|
||||
|
||||
from urh.awre import AutoAssigner
|
||||
from urh.awre.CommonRange import CommonRange, EmptyCommonRange, CommonRangeContainer, ChecksumRange
|
||||
from urh.awre.Preprocessor import Preprocessor
|
||||
from urh.awre.engines.AddressEngine import AddressEngine
|
||||
from urh.awre.engines.ChecksumEngine import ChecksumEngine
|
||||
from urh.awre.engines.LengthEngine import LengthEngine
|
||||
from urh.awre.engines.SequenceNumberEngine import SequenceNumberEngine
|
||||
from urh.cythonext import awre_util
|
||||
from urh.signalprocessing.ChecksumLabel import ChecksumLabel
|
||||
from urh.signalprocessing.FieldType import FieldType
|
||||
from urh.signalprocessing.Message import Message
|
||||
from urh.signalprocessing.MessageType import MessageType
|
||||
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
|
||||
from urh.util.WSPChecksum import WSPChecksum
|
||||
|
||||
|
||||
class FormatFinder(object):
|
||||
MIN_MESSAGES_PER_CLUSTER = 2
|
||||
|
||||
def __init__(self, messages, participants=None, shortest_field_length=None):
|
||||
"""
|
||||
|
||||
:type messages: list of Message
|
||||
:param participants:
|
||||
"""
|
||||
if participants is not None:
|
||||
AutoAssigner.auto_assign_participants(messages, participants)
|
||||
|
||||
existing_message_types_by_msg = {i: msg.message_type for i, msg in enumerate(messages)}
|
||||
self.existing_message_types = defaultdict(list)
|
||||
for i, message_type in existing_message_types_by_msg.items():
|
||||
self.existing_message_types[message_type].append(i)
|
||||
|
||||
preprocessor = Preprocessor(self.get_bitvectors_from_messages(messages), existing_message_types_by_msg)
|
||||
self.preamble_starts, self.preamble_lengths, sync_len = preprocessor.preprocess()
|
||||
self.sync_ends = self.preamble_starts + self.preamble_lengths + sync_len
|
||||
|
||||
n = shortest_field_length
|
||||
if n is None:
|
||||
# 0 = no sync found
|
||||
n = 8 if sync_len >= 8 else 4 if sync_len >= 4 else 1 if sync_len >= 1 else 0
|
||||
|
||||
for i, value in enumerate(self.sync_ends):
|
||||
# In doubt it is better to under estimate the sync end
|
||||
if n > 0:
|
||||
self.sync_ends[i] = n * max(int(math.floor((value - self.preamble_starts[i]) / n)), 1) + \
|
||||
self.preamble_starts[i]
|
||||
else:
|
||||
self.sync_ends[i] = self.preamble_starts[i]
|
||||
|
||||
if self.sync_ends[i] - self.preamble_starts[i] < self.preamble_lengths[i]:
|
||||
self.preamble_lengths[i] = self.sync_ends[i] - self.preamble_starts[i]
|
||||
|
||||
self.bitvectors = self.get_bitvectors_from_messages(messages, self.sync_ends)
|
||||
self.hexvectors = self.get_hexvectors(self.bitvectors)
|
||||
self.current_iteration = 0
|
||||
|
||||
participants = list(sorted(set(msg.participant for msg in messages if msg.participant is not None)))
|
||||
self.participant_indices = [participants.index(msg.participant) if msg.participant is not None else -1
|
||||
for msg in messages]
|
||||
self.known_participant_addresses = {
|
||||
participants.index(p): np.array([int(h, 16) for h in p.address_hex], dtype=np.uint8)
|
||||
for p in participants if p and p.address_hex
|
||||
}
|
||||
|
||||
@property
|
||||
def message_types(self):
|
||||
"""
|
||||
|
||||
:rtype: list of MessageType
|
||||
"""
|
||||
return sorted(self.existing_message_types.keys(), key=lambda x: x.name)
|
||||
|
||||
def perform_iteration_for_message_type(self, message_type: MessageType):
|
||||
"""
|
||||
Perform a field inference iteration for messages of the given message type
|
||||
This routine will return newly found fields as a set of Common Ranges
|
||||
|
||||
:param message_type:
|
||||
:rtype: set of CommonRange
|
||||
"""
|
||||
indices = self.existing_message_types[message_type]
|
||||
engines = []
|
||||
|
||||
# We can take an arbitrary sync end to correct the already labeled fields for this message type,
|
||||
# because if the existing labels would have different sync positions,
|
||||
# they would not belong to the same message type in the first place
|
||||
sync_end = self.sync_ends[indices[0]] if indices else 0
|
||||
already_labeled = [(lbl.start - sync_end, lbl.end - sync_end) for lbl in message_type if lbl.start >= sync_end]
|
||||
|
||||
if not message_type.get_first_label_with_type(FieldType.Function.LENGTH):
|
||||
engines.append(LengthEngine([self.bitvectors[i] for i in indices], already_labeled=already_labeled))
|
||||
|
||||
if not message_type.get_first_label_with_type(FieldType.Function.SRC_ADDRESS):
|
||||
engines.append(AddressEngine([self.hexvectors[i] for i in indices],
|
||||
[self.participant_indices[i] for i in indices],
|
||||
self.known_participant_addresses,
|
||||
already_labeled=already_labeled))
|
||||
elif not message_type.get_first_label_with_type(FieldType.Function.DST_ADDRESS):
|
||||
engines.append(AddressEngine([self.hexvectors[i] for i in indices],
|
||||
[self.participant_indices[i] for i in indices],
|
||||
self.known_participant_addresses,
|
||||
already_labeled=already_labeled,
|
||||
src_field_present=True))
|
||||
|
||||
if not message_type.get_first_label_with_type(FieldType.Function.SEQUENCE_NUMBER):
|
||||
engines.append(SequenceNumberEngine([self.bitvectors[i] for i in indices], already_labeled=already_labeled))
|
||||
if not message_type.get_first_label_with_type(FieldType.Function.CHECKSUM):
|
||||
# If checksum was not found in first iteration, it will also not be found in next one
|
||||
if self.current_iteration == 0:
|
||||
engines.append(ChecksumEngine([self.bitvectors[i] for i in indices], already_labeled=already_labeled))
|
||||
|
||||
result = set()
|
||||
for engine in engines:
|
||||
high_scored_ranges = engine.find() # type: list[CommonRange]
|
||||
high_scored_ranges = self.retransform_message_indices(high_scored_ranges, indices, self.sync_ends)
|
||||
merged_ranges = self.merge_common_ranges(high_scored_ranges)
|
||||
result.update(merged_ranges)
|
||||
return result
|
||||
|
||||
def perform_iteration(self) -> bool:
|
||||
new_field_found = False
|
||||
|
||||
for message_type in self.existing_message_types.copy():
|
||||
new_fields_for_message_type = self.perform_iteration_for_message_type(message_type)
|
||||
new_fields_for_message_type.update(
|
||||
self.get_preamble_and_sync(self.preamble_starts, self.preamble_lengths, self.sync_ends,
|
||||
message_type_indices=self.existing_message_types[message_type])
|
||||
)
|
||||
|
||||
self.remove_overlapping_fields(new_fields_for_message_type, message_type)
|
||||
containers = self.create_common_range_containers(new_fields_for_message_type)
|
||||
|
||||
# Store addresses of participants if we found a SRC address field
|
||||
participants_with_unknown_address = set(self.participant_indices) - set(self.known_participant_addresses)
|
||||
participants_with_unknown_address.discard(-1)
|
||||
|
||||
if participants_with_unknown_address:
|
||||
for container in containers:
|
||||
src_range = next((rng for rng in container if rng.field_type == "source address"), None)
|
||||
if src_range is None:
|
||||
continue
|
||||
for msg_index in src_range.message_indices:
|
||||
if len(participants_with_unknown_address) == 0:
|
||||
break
|
||||
p = self.participant_indices[msg_index]
|
||||
if p not in self.known_participant_addresses:
|
||||
hex_vector = self.hexvectors[msg_index]
|
||||
self.known_participant_addresses[p] = hex_vector[src_range.start:src_range.end + 1]
|
||||
participants_with_unknown_address.discard(p)
|
||||
|
||||
new_field_found |= len(containers) > 0
|
||||
|
||||
if len(containers) == 1:
|
||||
for rng in containers[0]:
|
||||
self.add_range_to_message_type(rng, message_type)
|
||||
elif len(containers) > 1:
|
||||
del self.existing_message_types[message_type]
|
||||
|
||||
for i, container in enumerate(containers):
|
||||
new_message_type = copy.deepcopy(message_type) # type: MessageType
|
||||
|
||||
if i > 0:
|
||||
new_message_type.name = "Message Type {}.{}".format(self.current_iteration+1, i)
|
||||
new_message_type.give_new_id()
|
||||
|
||||
for rng in container:
|
||||
self.add_range_to_message_type(rng, new_message_type)
|
||||
|
||||
self.existing_message_types[new_message_type].extend(sorted(container.message_indices))
|
||||
|
||||
return new_field_found
|
||||
|
||||
def run(self, max_iterations=10):
|
||||
self.current_iteration = 0
|
||||
while self.perform_iteration() and self.current_iteration < max_iterations:
|
||||
self.current_iteration += 1
|
||||
|
||||
if len(self.message_types) > 0:
|
||||
messages_without_message_type = set(range(len(self.bitvectors))) - set(
|
||||
i for l in self.existing_message_types.values() for i in l)
|
||||
|
||||
# add to default message type
|
||||
self.existing_message_types[self.message_types[0]].extend(list(messages_without_message_type))
|
||||
|
||||
|
||||
@staticmethod
|
||||
def remove_overlapping_fields(common_ranges, message_type: MessageType):
|
||||
"""
|
||||
Remove all fields from a set of CommonRanges which overlap with fields of the existing message type
|
||||
|
||||
:type common_ranges: set of CommonRange
|
||||
:param message_type:
|
||||
:return:
|
||||
"""
|
||||
if len(message_type) == 0:
|
||||
return
|
||||
|
||||
for rng in common_ranges.copy():
|
||||
for lbl in message_type: # type: ProtocolLabel
|
||||
if any(i in range(rng.bit_start, rng.bit_end) for i in range(lbl.start, lbl.end)):
|
||||
common_ranges.discard(rng)
|
||||
break
|
||||
|
||||
@staticmethod
|
||||
def merge_common_ranges(common_ranges):
|
||||
"""
|
||||
Merge common ranges if possible
|
||||
|
||||
:type common_ranges: list of CommonRange
|
||||
:rtype: list of CommonRange
|
||||
"""
|
||||
merged_ranges = []
|
||||
for common_range in common_ranges:
|
||||
assert isinstance(common_range, CommonRange)
|
||||
try:
|
||||
same_range = next(rng for rng in merged_ranges
|
||||
if rng.bit_start == common_range.bit_start
|
||||
and rng.bit_end == common_range.bit_end
|
||||
and rng.field_type == common_range.field_type)
|
||||
same_range.values.extend(common_range.values)
|
||||
same_range.message_indices.update(common_range.message_indices)
|
||||
except StopIteration:
|
||||
merged_ranges.append(common_range)
|
||||
|
||||
return merged_ranges
|
||||
|
||||
@staticmethod
|
||||
def add_range_to_message_type(common_range: CommonRange, message_type: MessageType):
|
||||
field_type = FieldType.from_caption(common_range.field_type)
|
||||
label = message_type.add_protocol_label(name=common_range.field_type,
|
||||
start=common_range.bit_start, end=common_range.bit_end,
|
||||
auto_created=True,
|
||||
type=field_type
|
||||
)
|
||||
label.display_endianness = common_range.byte_order
|
||||
|
||||
if field_type.function == FieldType.Function.CHECKSUM:
|
||||
assert isinstance(label, ChecksumLabel)
|
||||
assert isinstance(common_range, ChecksumRange)
|
||||
label.data_ranges = [[common_range.data_range_bit_start, common_range.data_range_bit_end]]
|
||||
|
||||
if isinstance(common_range.crc, WSPChecksum):
|
||||
label.category = ChecksumLabel.Category.wsp
|
||||
else:
|
||||
label.checksum = copy.copy(common_range.crc)
|
||||
|
||||
@staticmethod
|
||||
def get_hexvectors(bitvectors: list):
|
||||
result = awre_util.get_hexvectors(bitvectors)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_bitvectors_from_messages(messages: list, sync_ends: np.ndarray = None):
|
||||
if sync_ends is None:
|
||||
sync_ends = defaultdict(lambda: None)
|
||||
|
||||
return [np.array(msg.decoded_bits[sync_ends[i]:], dtype=np.uint8, order="C") for i, msg in enumerate(messages)]
|
||||
|
||||
@staticmethod
|
||||
def create_common_range_containers(label_set: set, num_messages: int = None):
|
||||
"""
|
||||
Create message types from set of labels.
|
||||
Handle overlapping conflicts and create multiple message types if needed
|
||||
|
||||
:param label_set:
|
||||
:param num_messages:
|
||||
:return:
|
||||
:rtype: list of CommonRangeContainer
|
||||
"""
|
||||
if num_messages is None:
|
||||
message_indices = sorted(set(i for rng in label_set for i in rng.message_indices))
|
||||
else:
|
||||
message_indices = range(num_messages)
|
||||
|
||||
result = []
|
||||
for i in message_indices:
|
||||
labels = sorted(set(rng for rng in label_set if i in rng.message_indices
|
||||
and not isinstance(rng, EmptyCommonRange)))
|
||||
|
||||
container = next((container for container in result if container.has_same_ranges(labels)), None)
|
||||
if container is None:
|
||||
result.append(CommonRangeContainer(labels, message_indices={i}))
|
||||
else:
|
||||
container.message_indices.add(i)
|
||||
|
||||
result = FormatFinder.handle_overlapping_conflict(result)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def handle_overlapping_conflict(containers):
|
||||
"""
|
||||
Handle overlapping conflicts for a list of CommonRangeContainers
|
||||
|
||||
:type containers: list of CommonRangeContainer
|
||||
:return:
|
||||
"""
|
||||
result = []
|
||||
for container in containers:
|
||||
if container.ranges_overlap:
|
||||
conflicted_handled = FormatFinder.__handle_container_overlapping_conflict(container)
|
||||
else:
|
||||
conflicted_handled = container
|
||||
|
||||
try:
|
||||
same_rng_container = next(c for c in result if c.has_same_ranges_as_container(conflicted_handled))
|
||||
same_rng_container.message_indices.update(conflicted_handled.message_indices)
|
||||
except StopIteration:
|
||||
result.append(conflicted_handled)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def __handle_container_overlapping_conflict(container: CommonRangeContainer):
|
||||
"""
|
||||
Handle overlapping conflict for a CommRangeContainer.
|
||||
We can assert that all labels in the container share the same message indices
|
||||
because we partitioned them in a step before.
|
||||
If two or more labels overlap we have three ways to resolve the conflict:
|
||||
|
||||
1. Choose the range with the highest score
|
||||
2. If multiple ranges overlap choose the ranges that maximize the overall (cumulated) score
|
||||
3. If the overlapping is very small i.e. only 1 or 2 bits we can adjust the start/end of the conflicting ranges
|
||||
|
||||
The ranges inside the container _must_ be sorted i.e. the range with lowest start must be at front
|
||||
|
||||
:param container:
|
||||
:return:
|
||||
"""
|
||||
partitions = [] # type: list[list[CommonRange]]
|
||||
# partition the container into overlapping partitions
|
||||
# results in something like [[A], [B,C], [D], [E,F,G]]] where B and C and E, F, G are overlapping
|
||||
for cur_rng in container:
|
||||
if len(partitions) == 0:
|
||||
partitions.append([cur_rng])
|
||||
continue
|
||||
|
||||
last_rng = partitions[-1][-1] # type: CommonRange
|
||||
if cur_rng.overlaps_with(last_rng):
|
||||
partitions[-1].append(cur_rng)
|
||||
else:
|
||||
partitions.append([cur_rng])
|
||||
|
||||
# Todo: Adjust start/end of conflicting ranges if overlapping is very small (i.e. 1 or 2 bits)
|
||||
|
||||
result = []
|
||||
# Go through these partitions and handle overlapping conflicts
|
||||
for partition in partitions:
|
||||
possible_solutions = []
|
||||
for i, rng in enumerate(partition):
|
||||
# Append every range to this solution that does not overlap with current rng
|
||||
solution = [rng] + [r for r in partition[i + 1:] if not rng.overlaps_with(r)]
|
||||
possible_solutions.append(solution)
|
||||
|
||||
# Take solution that maximizes score. In case of tie, choose solution with shorter total length.
|
||||
# if there is still a tie prefer solution that contains a length field as is is very likely to be correct
|
||||
# if nothing else helps break tie by names of field types to prevent randomness
|
||||
best_solution = max(possible_solutions,
|
||||
key=lambda sol: (sum(r.score for r in sol),
|
||||
-sum(r.length_in_bits for r in sol),
|
||||
"length" in {r.field_type for r in sol},
|
||||
"".join(r.field_type[0] for r in sol)))
|
||||
result.extend(best_solution)
|
||||
|
||||
return CommonRangeContainer(result, message_indices=container.message_indices)
|
||||
|
||||
@staticmethod
|
||||
def retransform_message_indices(common_ranges, message_type_indices: list, sync_ends) -> list:
|
||||
"""
|
||||
Retransform the found message indices of an engine to the original index space
|
||||
based on the message indices of the message type.
|
||||
|
||||
Furthermore, set the sync_end of the common ranges so bit_start and bit_end
|
||||
match the position in the original space
|
||||
|
||||
:type common_ranges: list of CommonRange
|
||||
:param message_type_indices: Messages belonging to the message type the engine ran for
|
||||
:type sync_ends: np.ndarray
|
||||
:return:
|
||||
"""
|
||||
result = []
|
||||
for common_range in common_ranges:
|
||||
# Retransform message indices into original space
|
||||
message_indices = np.fromiter((message_type_indices[i] for i in common_range.message_indices),
|
||||
dtype=int, count=len(common_range.message_indices))
|
||||
|
||||
# If we have different sync_ends we need to create a new common range for each different sync_length
|
||||
matching_sync_ends = sync_ends[message_indices]
|
||||
for sync_end in np.unique(matching_sync_ends):
|
||||
rng = copy.deepcopy(common_range)
|
||||
rng.sync_end = sync_end
|
||||
rng.message_indices = set(message_indices[np.nonzero(matching_sync_ends == sync_end)])
|
||||
result.append(rng)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_preamble_and_sync(preamble_starts, preamble_lengths, sync_ends, message_type_indices):
|
||||
"""
|
||||
Get preamble and sync common ranges based on the data
|
||||
|
||||
:type preamble_starts: np.ndarray
|
||||
:type preamble_lengths: np.ndarray
|
||||
:type sync_ends: np.ndarray
|
||||
:type message_type_indices: list
|
||||
:rtype: set of CommonRange
|
||||
"""
|
||||
assert len(preamble_starts) == len(preamble_lengths) == len(sync_ends)
|
||||
|
||||
result = set() # type: set[CommonRange]
|
||||
for i in message_type_indices:
|
||||
preamble = CommonRange(preamble_starts[i], preamble_lengths[i], field_type="preamble", message_indices={i})
|
||||
existing_preamble = next((rng for rng in result if preamble == rng), None)
|
||||
if existing_preamble is not None:
|
||||
existing_preamble.message_indices.add(i)
|
||||
elif preamble_lengths[i] > 0:
|
||||
result.add(preamble)
|
||||
|
||||
preamble_end = preamble_starts[i] + preamble_lengths[i]
|
||||
sync_end = sync_ends[i]
|
||||
sync = CommonRange(preamble_end, sync_end - preamble_end, field_type="synchronization", message_indices={i})
|
||||
existing_sync = next((rng for rng in result if sync == rng), None)
|
||||
if existing_sync is not None:
|
||||
existing_sync.message_indices.add(i)
|
||||
elif sync_end - preamble_end > 0:
|
||||
result.add(sync)
|
||||
|
||||
return result
|
||||
116
Software/Universal Radio Hacker/src/urh/awre/Histogram.py
Normal file
116
Software/Universal Radio Hacker/src/urh/awre/Histogram.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from collections import defaultdict
|
||||
|
||||
import numpy as np
|
||||
|
||||
from urh.awre.CommonRange import CommonRange
|
||||
from urh.cythonext import awre_util
|
||||
|
||||
|
||||
class Histogram(object):
|
||||
"""
|
||||
Create a histogram based on the equalness of vectors
|
||||
"""
|
||||
|
||||
def __init__(self, vectors, indices=None, normalize=True, debug=False):
|
||||
"""
|
||||
|
||||
:type vectors: list of np.ndarray
|
||||
:param indices: Indices of vectors for which the Histogram shall be created.
|
||||
This is useful for clustering.
|
||||
If None Histogram will be created over all bitvectors
|
||||
:type: list of int
|
||||
:param normalize:
|
||||
"""
|
||||
self.__vectors = vectors # type: list[np.ndarray]
|
||||
self.__active_indices = list(range(len(vectors))) if indices is None else indices
|
||||
|
||||
self.normalize = normalize
|
||||
self.data = self.__create_histogram()
|
||||
|
||||
def __create_histogram(self):
|
||||
return awre_util.create_difference_histogram(self.__vectors, self.__active_indices)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.data.tolist())
|
||||
|
||||
def find_common_ranges(self, alpha=0.95, range_type="bit"):
|
||||
"""
|
||||
Find all common ranges where at least alpha percent of numbers are equal
|
||||
|
||||
:param range_type: on of bit/hex/byte
|
||||
:param alpha:
|
||||
:return:
|
||||
"""
|
||||
data_indices = np.argwhere(self.data >= alpha).flatten()
|
||||
|
||||
if len(data_indices) < 2:
|
||||
return []
|
||||
|
||||
result = []
|
||||
start, length = None, 0
|
||||
for i in range(1, len(data_indices)):
|
||||
if start is None:
|
||||
start = data_indices[i - 1]
|
||||
length = 1
|
||||
|
||||
if data_indices[i] - data_indices[i - 1] == 1:
|
||||
length += 1
|
||||
else:
|
||||
if length >= 2:
|
||||
value = self.__get_value_for_common_range(start, length)
|
||||
result.append(CommonRange(start, length, value, message_indices=set(self.__active_indices),
|
||||
range_type=range_type))
|
||||
|
||||
start, length = None, 0
|
||||
|
||||
if i == len(data_indices) - 1 and length >= 2:
|
||||
value = self.__get_value_for_common_range(start, length)
|
||||
result.append(CommonRange(start, length, value, message_indices=set(self.__active_indices),
|
||||
range_type=range_type))
|
||||
|
||||
return result
|
||||
|
||||
def __get_value_for_common_range(self, start: int, length: int):
|
||||
"""
|
||||
Get the value for a range of common numbers. This is the value that appears most.
|
||||
|
||||
:param start: Start of the common bit range
|
||||
:param length: Length of the common bit range
|
||||
:return:
|
||||
"""
|
||||
values = defaultdict(list)
|
||||
for i in self.__active_indices:
|
||||
vector = self.__vectors[i]
|
||||
values[vector[start:start + length].tostring()].append(i)
|
||||
value = max(values, key=lambda x: len(x))
|
||||
indices = values[value]
|
||||
return self.__vectors[indices[0]][start:start + length]
|
||||
|
||||
def __vector_to_string(self, data_vector) -> str:
|
||||
lut = {i: "{0:x}".format(i) for i in range(16)}
|
||||
return "".join(lut[x] if x in lut else " {} ".format(x) for x in data_vector)
|
||||
|
||||
def plot(self):
|
||||
import matplotlib.pyplot as plt
|
||||
self.subplot_on(plt)
|
||||
plt.show()
|
||||
|
||||
def subplot_on(self, plt):
|
||||
plt.grid()
|
||||
plt.plot(self.data)
|
||||
plt.xticks(np.arange(4, len(self.data), 4))
|
||||
plt.xlabel("Bit position")
|
||||
if self.normalize:
|
||||
plt.ylabel("Number common bits (normalized)")
|
||||
else:
|
||||
plt.ylabel("Number common bits")
|
||||
plt.ylim(ymin=0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bv1 = np.array([1, 0, 1, 0, 1, 1, 1, 1], dtype=np.int8)
|
||||
bv2 = np.array([1, 0, 1, 0, 1, 0, 0, 0], dtype=np.int8)
|
||||
bv3 = np.array([1, 0, 1, 0, 1, 1, 1, 1], dtype=np.int8)
|
||||
bv4 = np.array([1, 0, 1, 0, 0, 0, 0, 0], dtype=np.int8)
|
||||
h = Histogram([bv1, bv2, bv3, bv4])
|
||||
h.plot()
|
||||
@@ -0,0 +1,55 @@
|
||||
from urh.signalprocessing.ChecksumLabel import ChecksumLabel
|
||||
|
||||
from urh.signalprocessing.FieldType import FieldType
|
||||
from urh.signalprocessing.MessageType import MessageType
|
||||
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
|
||||
|
||||
|
||||
class MessageTypeBuilder(object):
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.message_type = MessageType(name)
|
||||
|
||||
def add_label(self, label_type: FieldType.Function, length: int, name: str=None):
|
||||
try:
|
||||
start = self.message_type[-1].end
|
||||
color_index = self.message_type[-1].color_index + 1
|
||||
except IndexError:
|
||||
start, color_index = 0, 0
|
||||
|
||||
if name is None:
|
||||
name = label_type.value
|
||||
|
||||
lbl = ProtocolLabel(name, start, start+length-1, color_index, field_type=FieldType(label_type.name, label_type))
|
||||
self.message_type.append(lbl)
|
||||
|
||||
def add_checksum_label(self, length, checksum, data_start=None, data_end=None, name: str=None):
|
||||
label_type = FieldType.Function.CHECKSUM
|
||||
try:
|
||||
start = self.message_type[-1].end
|
||||
color_index = self.message_type[-1].color_index + 1
|
||||
except IndexError:
|
||||
start, color_index = 0, 0
|
||||
|
||||
if name is None:
|
||||
name = label_type.value
|
||||
|
||||
if data_start is None:
|
||||
# End of sync or preamble
|
||||
sync_label = self.message_type.get_first_label_with_type(FieldType.Function.SYNC)
|
||||
if sync_label:
|
||||
data_start = sync_label.end
|
||||
else:
|
||||
preamble_label = self.message_type.get_first_label_with_type(FieldType.Function.PREAMBLE)
|
||||
if preamble_label:
|
||||
data_start = preamble_label.end
|
||||
else:
|
||||
data_start = 0
|
||||
|
||||
if data_end is None:
|
||||
data_end = start
|
||||
|
||||
lbl = ChecksumLabel(name, start, start+length-1, color_index, field_type=FieldType(label_type.name, label_type))
|
||||
lbl.data_ranges = [(data_start, data_end)]
|
||||
lbl.checksum = checksum
|
||||
self.message_type.append(lbl)
|
||||
271
Software/Universal Radio Hacker/src/urh/awre/Preprocessor.py
Normal file
271
Software/Universal Radio Hacker/src/urh/awre/Preprocessor.py
Normal file
@@ -0,0 +1,271 @@
|
||||
import itertools
|
||||
import math
|
||||
import os
|
||||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
import numpy as np
|
||||
|
||||
from urh.cythonext import awre_util
|
||||
from urh.signalprocessing.FieldType import FieldType
|
||||
|
||||
|
||||
class Preprocessor(object):
|
||||
"""
|
||||
This class preprocesses the messages in the following ways
|
||||
1) Identify preamble / length of preamble
|
||||
2) Identify sync word(s)
|
||||
3) Align all given messages on the identified preamble information
|
||||
"""
|
||||
|
||||
_DEBUG_ = False
|
||||
|
||||
def __init__(self, bitvectors: list, existing_message_types: dict = None):
|
||||
self.bitvectors = bitvectors # type: list[np.ndarray]
|
||||
self.existing_message_types = existing_message_types if existing_message_types is not None else dict()
|
||||
|
||||
def preprocess(self) -> (np.ndarray, int):
|
||||
raw_preamble_positions = self.get_raw_preamble_positions()
|
||||
existing_sync_words = self.__get_existing_sync_words()
|
||||
if len(existing_sync_words) == 0:
|
||||
sync_words = self.find_possible_syncs(raw_preamble_positions)
|
||||
else:
|
||||
# NOTE: This does not cover the case if protocol has multiple sync words and not all of them were labeled
|
||||
sync_words = existing_sync_words
|
||||
|
||||
preamble_starts = raw_preamble_positions[:, 0]
|
||||
preamble_lengths = self.get_preamble_lengths_from_sync_words(sync_words, preamble_starts=preamble_starts)
|
||||
sync_len = len(sync_words[0]) if len(sync_words) > 0 else 0
|
||||
return preamble_starts, preamble_lengths, sync_len
|
||||
|
||||
def get_preamble_lengths_from_sync_words(self, sync_words: list, preamble_starts: np.ndarray):
|
||||
"""
|
||||
Get the preamble lengths based on the found sync words for all messages.
|
||||
If there should be more than one sync word in a message, use the first one.
|
||||
|
||||
:param sync_words:
|
||||
:param preamble_starts:
|
||||
:return:
|
||||
"""
|
||||
# If there should be varying sync word lengths we need to return an array of sync lengths per message
|
||||
assert all(len(sync_word) == len(sync_words[0]) for sync_word in sync_words)
|
||||
|
||||
byte_sync_words = [bytes(map(int, sync_word)) for sync_word in sync_words]
|
||||
|
||||
result = np.zeros(len(self.bitvectors), dtype=np.uint32)
|
||||
|
||||
for i, bitvector in enumerate(self.bitvectors):
|
||||
preamble_lengths = []
|
||||
bits = bitvector.tobytes()
|
||||
|
||||
for sync_word in byte_sync_words:
|
||||
sync_start = bits.find(sync_word)
|
||||
if sync_start != -1:
|
||||
if sync_start - preamble_starts[i] >= 2:
|
||||
preamble_lengths.append(sync_start - preamble_starts[i])
|
||||
|
||||
# Consider case where sync word starts with preamble pattern
|
||||
sync_start = bits.find(sync_word, sync_start + 1, sync_start + 2 * len(sync_word))
|
||||
|
||||
if sync_start != -1:
|
||||
if sync_start - preamble_starts[i] >= 2:
|
||||
preamble_lengths.append(sync_start - preamble_starts[i])
|
||||
|
||||
preamble_lengths.sort()
|
||||
|
||||
if len(preamble_lengths) == 0:
|
||||
result[i] = 0
|
||||
elif len(preamble_lengths) == 1:
|
||||
result[i] = preamble_lengths[0]
|
||||
else:
|
||||
# consider all indices not more than one byte before first one
|
||||
preamble_lengths = list(filter(lambda x: x < preamble_lengths[0] + 7, preamble_lengths))
|
||||
|
||||
# take the smallest preamble_length, but prefer a greater one if it is divisible by 8 (or 4)
|
||||
preamble_length = next((pl for pl in preamble_lengths if pl % 8 == 0), None)
|
||||
if preamble_length is None:
|
||||
preamble_length = next((pl for pl in preamble_lengths if pl % 4 == 0), None)
|
||||
if preamble_length is None:
|
||||
preamble_length = preamble_lengths[0]
|
||||
result[i] = preamble_length
|
||||
|
||||
return result
|
||||
|
||||
def find_possible_syncs(self, raw_preamble_positions=None):
|
||||
difference_matrix = self.get_difference_matrix()
|
||||
if raw_preamble_positions is None:
|
||||
raw_preamble_positions = self.get_raw_preamble_positions()
|
||||
return self.determine_sync_candidates(raw_preamble_positions, difference_matrix, n_gram_length=4)
|
||||
|
||||
@staticmethod
|
||||
def merge_possible_sync_words(possible_sync_words: dict, n_gram_length: int):
|
||||
"""
|
||||
Merge possible sync words by looking for common prefixes
|
||||
|
||||
:param possible_sync_words: dict of possible sync words and their frequencies
|
||||
:return:
|
||||
"""
|
||||
result = defaultdict(int)
|
||||
if len(possible_sync_words) < 2:
|
||||
return possible_sync_words.copy()
|
||||
|
||||
for sync1, sync2 in itertools.combinations(possible_sync_words, 2):
|
||||
common_prefix = os.path.commonprefix([sync1, sync2])
|
||||
if len(common_prefix) > n_gram_length:
|
||||
result[common_prefix] += possible_sync_words[sync1] + possible_sync_words[sync2]
|
||||
else:
|
||||
result[sync1] += possible_sync_words[sync1]
|
||||
result[sync2] += possible_sync_words[sync2]
|
||||
return result
|
||||
|
||||
def determine_sync_candidates(self,
|
||||
raw_preamble_positions: np.ndarray,
|
||||
difference_matrix: np.ndarray,
|
||||
n_gram_length=4) -> list:
|
||||
|
||||
possible_sync_words = awre_util.find_possible_sync_words(difference_matrix, raw_preamble_positions,
|
||||
self.bitvectors, n_gram_length)
|
||||
|
||||
self.__debug("Possible sync words", possible_sync_words)
|
||||
if len(possible_sync_words) == 0:
|
||||
return []
|
||||
|
||||
possible_sync_words = self.merge_possible_sync_words(possible_sync_words, n_gram_length)
|
||||
self.__debug("Merged sync words", possible_sync_words)
|
||||
|
||||
scores = self.__score_sync_lengths(possible_sync_words)
|
||||
|
||||
sorted_scores = sorted(scores, reverse=True, key=scores.get)
|
||||
estimated_sync_length = sorted_scores[0]
|
||||
if estimated_sync_length % 8 != 0:
|
||||
for other in filter(lambda x: 0 < estimated_sync_length-x < 7, sorted_scores):
|
||||
if other % 8 == 0:
|
||||
estimated_sync_length = other
|
||||
break
|
||||
|
||||
# Now we look at all possible sync words with this length
|
||||
sync_words = {word: frequency for word, frequency in possible_sync_words.items()
|
||||
if len(word) == estimated_sync_length}
|
||||
self.__debug("Sync words", sync_words)
|
||||
|
||||
additional_syncs = self.__find_additional_sync_words(estimated_sync_length, sync_words, possible_sync_words)
|
||||
|
||||
if additional_syncs:
|
||||
self.__debug("Found additional sync words", additional_syncs)
|
||||
sync_words.update(additional_syncs)
|
||||
|
||||
result = []
|
||||
for sync_word in sorted(sync_words, key=sync_words.get, reverse=True):
|
||||
# Convert bytes back to string
|
||||
result.append("".join(str(c) for c in sync_word))
|
||||
|
||||
return result
|
||||
|
||||
def __find_additional_sync_words(self, sync_length: int, present_sync_words, possible_sync_words) -> dict:
|
||||
"""
|
||||
Look for additional sync words, in case we had varying preamble lengths and multiple sync words
|
||||
(see test_with_three_syncs_different_preamble_lengths for an example)
|
||||
|
||||
:param sync_length:
|
||||
:type present_sync_words: dict
|
||||
:type possible_sync_words: dict
|
||||
:return:
|
||||
"""
|
||||
np_syn = [np.fromiter(map(int, sync_word), dtype=np.uint8, count=len(sync_word))
|
||||
for sync_word in present_sync_words]
|
||||
|
||||
messages_without_sync = [i for i, bv in enumerate(self.bitvectors)
|
||||
if not any(awre_util.find_occurrences(bv, s, return_after_first=True) for s in np_syn)]
|
||||
|
||||
result = dict()
|
||||
if len(messages_without_sync) == 0:
|
||||
return result
|
||||
|
||||
# Is there another sync word that applies to all messages without sync?
|
||||
additional_candidates = {word: score for word, score in possible_sync_words.items()
|
||||
if len(word) > sync_length and not any(s in word for s in present_sync_words)}
|
||||
|
||||
for sync in sorted(additional_candidates, key=additional_candidates.get, reverse=True):
|
||||
if len(messages_without_sync) == 0:
|
||||
break
|
||||
|
||||
score = additional_candidates[sync]
|
||||
s = sync[:sync_length]
|
||||
np_s = np.fromiter(s, dtype=np.uint8, count=len(s))
|
||||
matching = [i for i in messages_without_sync
|
||||
if awre_util.find_occurrences(self.bitvectors[i], np_s, return_after_first=True)]
|
||||
if matching:
|
||||
result[s] = score
|
||||
for m in matching:
|
||||
messages_without_sync.remove(m)
|
||||
|
||||
return result
|
||||
|
||||
def get_raw_preamble_positions(self) -> np.ndarray:
|
||||
"""
|
||||
Return a 2D numpy array where first column is the start of preamble
|
||||
second and third columns are lower and upper bound for preamble length by message, respectively
|
||||
"""
|
||||
result = np.zeros((len(self.bitvectors), 3), dtype=np.uint32)
|
||||
|
||||
for i, bitvector in enumerate(self.bitvectors):
|
||||
if i in self.existing_message_types:
|
||||
preamble_label = self.existing_message_types[i].get_first_label_with_type(FieldType.Function.PREAMBLE)
|
||||
else:
|
||||
preamble_label = None
|
||||
|
||||
if preamble_label is None:
|
||||
start, lower, upper = awre_util.get_raw_preamble_position(bitvector)
|
||||
else:
|
||||
# If this message is already labeled with a preamble we just use it's values
|
||||
start, lower, upper = preamble_label.start, preamble_label.end, preamble_label.end
|
||||
|
||||
result[i, 0] = start
|
||||
result[i, 1] = lower - start
|
||||
result[i, 2] = upper - start
|
||||
|
||||
return result
|
||||
|
||||
def get_difference_matrix(self) -> np.ndarray:
|
||||
"""
|
||||
Return a matrix of the first difference index between all messages
|
||||
:return:
|
||||
"""
|
||||
return awre_util.get_difference_matrix(self.bitvectors)
|
||||
|
||||
def __score_sync_lengths(self, possible_sync_words: dict):
|
||||
sync_lengths = defaultdict(int)
|
||||
for sync_word, score in possible_sync_words.items():
|
||||
sync_lengths[len(sync_word)] += score
|
||||
|
||||
self.__debug("Sync lengths", sync_lengths)
|
||||
|
||||
return sync_lengths
|
||||
|
||||
def __get_existing_sync_words(self) -> list:
|
||||
result = []
|
||||
for i, bitvector in enumerate(self.bitvectors):
|
||||
if i in self.existing_message_types:
|
||||
sync_label = self.existing_message_types[i].get_first_label_with_type(FieldType.Function.SYNC)
|
||||
else:
|
||||
sync_label = None
|
||||
|
||||
if sync_label is not None:
|
||||
result.append("".join(map(str, bitvector[sync_label.start:sync_label.end])))
|
||||
return result
|
||||
|
||||
def __debug(self, *args):
|
||||
if self._DEBUG_:
|
||||
print("[PREPROCESSOR]", *args)
|
||||
|
||||
@staticmethod
|
||||
def get_next_multiple_of_n(number: int, n: int):
|
||||
return n * int(math.ceil(number / n))
|
||||
|
||||
@staticmethod
|
||||
def lower_multiple_of_n(number: int, n: int):
|
||||
return n * int(math.floor(number / n))
|
||||
|
||||
@staticmethod
|
||||
def get_next_lower_multiple_of_two(number: int):
|
||||
return number if number % 2 == 0 else number - 1
|
||||
@@ -0,0 +1,260 @@
|
||||
import math
|
||||
import struct
|
||||
from array import array
|
||||
from collections import defaultdict
|
||||
|
||||
from urh.util import util
|
||||
|
||||
from urh.awre.MessageTypeBuilder import MessageTypeBuilder
|
||||
from urh.signalprocessing.ChecksumLabel import ChecksumLabel
|
||||
from urh.signalprocessing.FieldType import FieldType
|
||||
from urh.signalprocessing.Message import Message
|
||||
from urh.signalprocessing.MessageType import MessageType
|
||||
from urh.signalprocessing.Participant import Participant
|
||||
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
|
||||
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
|
||||
|
||||
|
||||
class ProtocolGenerator(object):
|
||||
DEFAULT_PREAMBLE = "10101010"
|
||||
DEFAULT_SYNC = "1001"
|
||||
BROADCAST_ADDRESS = "0xffff"
|
||||
|
||||
def __init__(self, message_types: list, participants: list = None, preambles_by_mt=None,
|
||||
syncs_by_mt=None, little_endian=False, length_in_bytes=True, sequence_numbers=None,
|
||||
sequence_number_increment=1, message_type_codes=None):
|
||||
"""
|
||||
|
||||
:param message_types:
|
||||
:param participants:
|
||||
:param preambles_by_mt:
|
||||
:param syncs_by_mt:
|
||||
:param byte_order:
|
||||
:param length_in_bytes: If false length will be given in bit
|
||||
"""
|
||||
self.participants = participants if participants is not None else []
|
||||
|
||||
self.protocol = ProtocolAnalyzer(None)
|
||||
self.protocol.message_types = message_types
|
||||
|
||||
self.length_in_bytes = length_in_bytes
|
||||
self.little_endian = little_endian
|
||||
|
||||
preambles_by_mt = dict() if preambles_by_mt is None else preambles_by_mt
|
||||
|
||||
self.preambles_by_message_type = defaultdict(lambda: self.DEFAULT_PREAMBLE)
|
||||
for mt, preamble in preambles_by_mt.items():
|
||||
self.preambles_by_message_type[mt] = self.to_bits(preamble)
|
||||
|
||||
syncs_by_mt = dict() if syncs_by_mt is None else syncs_by_mt
|
||||
|
||||
self.syncs_by_message_type = defaultdict(lambda: self.DEFAULT_SYNC)
|
||||
for mt, sync in syncs_by_mt.items():
|
||||
self.syncs_by_message_type[mt] = self.to_bits(sync)
|
||||
|
||||
sequence_numbers = dict() if sequence_numbers is None else sequence_numbers
|
||||
self.sequence_numbers = defaultdict(lambda: 0)
|
||||
self.sequence_number_increment = sequence_number_increment
|
||||
|
||||
for mt, seq in sequence_numbers.items():
|
||||
self.sequence_numbers[mt] = seq
|
||||
|
||||
if message_type_codes is None:
|
||||
message_type_codes = dict()
|
||||
for i, mt in enumerate(self.message_types):
|
||||
message_type_codes[mt] = i
|
||||
self.message_type_codes = message_type_codes
|
||||
|
||||
|
||||
@property
|
||||
def messages(self):
|
||||
return self.protocol.messages
|
||||
|
||||
@property
|
||||
def message_types(self):
|
||||
return self.protocol.message_types
|
||||
|
||||
def __get_address_for_participant(self, participant: Participant):
|
||||
if participant is None:
|
||||
return self.to_bits(self.BROADCAST_ADDRESS)
|
||||
|
||||
address = "0x" + participant.address_hex if not participant.address_hex.startswith(
|
||||
"0x") else participant.address_hex
|
||||
return self.to_bits(address)
|
||||
|
||||
@staticmethod
|
||||
def to_bits(bit_or_hex_str: str):
|
||||
if bit_or_hex_str.startswith("0x"):
|
||||
lut = {"{0:x}".format(i): "{0:04b}".format(i) for i in range(16)}
|
||||
return "".join(lut[c] for c in bit_or_hex_str[2:])
|
||||
else:
|
||||
return bit_or_hex_str
|
||||
|
||||
def decimal_to_bits(self, number: int, num_bits: int) -> str:
|
||||
len_formats = {8: "B", 16: "H", 32: "I", 64: "Q"}
|
||||
if num_bits not in len_formats:
|
||||
raise ValueError("Invalid length for length field: {} bits".format(num_bits))
|
||||
|
||||
struct_format = "<" if self.little_endian else ">"
|
||||
struct_format += len_formats[num_bits]
|
||||
|
||||
byte_length = struct.pack(struct_format, number)
|
||||
return "".join("{0:08b}".format(byte) for byte in byte_length)
|
||||
|
||||
def generate_message(self, message_type=None, data="0x00", source: Participant = None,
|
||||
destination: Participant = None):
|
||||
for participant in (source, destination):
|
||||
if isinstance(participant, Participant) and participant not in self.participants:
|
||||
self.participants.append(participant)
|
||||
|
||||
if isinstance(message_type, MessageType):
|
||||
message_type_index = self.protocol.message_types.index(message_type)
|
||||
elif isinstance(message_type, int):
|
||||
message_type_index = message_type
|
||||
else:
|
||||
message_type_index = 0
|
||||
|
||||
data = self.to_bits(data)
|
||||
|
||||
mt = self.protocol.message_types[message_type_index] # type: MessageType
|
||||
mt.sort()
|
||||
|
||||
bits = []
|
||||
|
||||
start = 0
|
||||
|
||||
data_label_present = mt.get_first_label_with_type(FieldType.Function.DATA) is not None
|
||||
|
||||
if data_label_present:
|
||||
message_length = mt[-1].end - 1
|
||||
else:
|
||||
message_length = mt[-1].end - 1 + len(data)
|
||||
|
||||
checksum_labels = []
|
||||
|
||||
for lbl in mt: # type: ProtocolLabel
|
||||
bits.append("0" * (lbl.start - start))
|
||||
len_field = lbl.end - lbl.start # in bits
|
||||
|
||||
if isinstance(lbl, ChecksumLabel):
|
||||
checksum_labels.append(lbl)
|
||||
continue # processed last
|
||||
|
||||
if lbl.field_type.function == FieldType.Function.PREAMBLE:
|
||||
preamble = self.preambles_by_message_type[mt]
|
||||
assert len(preamble) == len_field
|
||||
bits.append(preamble)
|
||||
message_length -= len(preamble)
|
||||
elif lbl.field_type.function == FieldType.Function.SYNC:
|
||||
sync = self.syncs_by_message_type[mt]
|
||||
assert len(sync) == len_field
|
||||
bits.append(sync)
|
||||
message_length -= len(sync)
|
||||
elif lbl.field_type.function == FieldType.Function.LENGTH:
|
||||
value = int(math.ceil(message_length / 8))
|
||||
|
||||
if not self.length_in_bytes:
|
||||
value *= 8
|
||||
|
||||
bits.append(self.decimal_to_bits(value, len_field))
|
||||
elif lbl.field_type.function == FieldType.Function.TYPE:
|
||||
bits.append(self.decimal_to_bits(self.message_type_codes[mt] % (2 ** len_field), len_field))
|
||||
elif lbl.field_type.function == FieldType.Function.SEQUENCE_NUMBER:
|
||||
bits.append(self.decimal_to_bits(self.sequence_numbers[mt] % (2 ** len_field), len_field))
|
||||
elif lbl.field_type.function == FieldType.Function.DST_ADDRESS:
|
||||
dst_bits = self.__get_address_for_participant(destination)
|
||||
|
||||
if len(dst_bits) != len_field:
|
||||
raise ValueError(
|
||||
"Length of dst ({0} bits) != length dst field ({1} bits)".format(len(dst_bits), len_field))
|
||||
|
||||
bits.append(dst_bits)
|
||||
elif lbl.field_type.function == FieldType.Function.SRC_ADDRESS:
|
||||
src_bits = self.__get_address_for_participant(source)
|
||||
|
||||
if len(src_bits) != len_field:
|
||||
raise ValueError(
|
||||
"Length of src ({0} bits) != length src field ({1} bits)".format(len(src_bits), len_field))
|
||||
|
||||
bits.append(src_bits)
|
||||
elif lbl.field_type.function == FieldType.Function.DATA:
|
||||
if len(data) != len_field:
|
||||
raise ValueError(
|
||||
"Length of data ({} bits) != length data field ({} bits)".format(len(data), len_field))
|
||||
bits.append(data)
|
||||
|
||||
start = lbl.end
|
||||
|
||||
if not data_label_present:
|
||||
bits.append(data)
|
||||
|
||||
msg = Message.from_plain_bits_str("".join(bits))
|
||||
msg.message_type = mt
|
||||
msg.participant = source
|
||||
self.sequence_numbers[mt] += self.sequence_number_increment
|
||||
|
||||
for checksum_label in checksum_labels:
|
||||
msg[checksum_label.start:checksum_label.end] = checksum_label.calculate_checksum_for_message(msg, False)
|
||||
|
||||
self.protocol.messages.append(msg)
|
||||
|
||||
def to_file(self, filename: str):
|
||||
self.protocol.to_xml_file(filename, [], self.participants, write_bits=True)
|
||||
|
||||
def export_to_latex(self, filename: str, number: int):
|
||||
def export_message_type_to_latex(message_type, f):
|
||||
f.write(" \\begin{itemize}\n")
|
||||
for lbl in message_type: # type: ProtocolLabel
|
||||
if lbl.field_type.function == FieldType.Function.SYNC:
|
||||
sync = array("B", map(int, self.syncs_by_message_type[message_type]))
|
||||
f.write(" \\item {}: \\texttt{{0x{}}}\n".format(lbl.name, util.bit2hex(sync)))
|
||||
elif lbl.field_type.function == FieldType.Function.PREAMBLE:
|
||||
preamble = array("B", map(int, self.preambles_by_message_type[message_type]))
|
||||
f.write(" \\item {}: \\texttt{{0x{}}}\n".format(lbl.name, util.bit2hex(preamble)))
|
||||
elif lbl.field_type.function == FieldType.Function.CHECKSUM:
|
||||
f.write(" \\item {}: {}\n".format(lbl.name, lbl.checksum.caption))
|
||||
elif lbl.field_type.function in (FieldType.Function.LENGTH, FieldType.Function.SEQUENCE_NUMBER) and lbl.length > 8:
|
||||
f.write(" \\item {}: {} bit (\\textbf{{{} endian}})\n".format(lbl.name, lbl.length, "little" if self.little_endian else "big"))
|
||||
elif lbl.field_type.function == FieldType.Function.DATA:
|
||||
f.write(" \\item payload: {} byte\n".format(lbl.length // 8))
|
||||
else:
|
||||
f.write(" \\item {}: {} bit\n".format(lbl.name, lbl.length))
|
||||
f.write(" \\end{itemize}\n")
|
||||
|
||||
with open(filename, "a") as f:
|
||||
f.write("\\subsection{{Protocol {}}}\n".format(number))
|
||||
|
||||
if len(self.participants) > 1:
|
||||
f.write("There were {} participants involved in communication: ".format(len(self.participants)))
|
||||
f.write(", ".join("{} (\\texttt{{0x{}}})".format(p.name, p.address_hex) for p in self.participants[:-1]))
|
||||
f.write(" and {} (\\texttt{{0x{}}})".format(self.participants[-1].name, self.participants[-1].address_hex))
|
||||
f.write(".\n")
|
||||
|
||||
if len(self.message_types) == 1:
|
||||
f.write("The protocol has one message type with the following fields:\n")
|
||||
export_message_type_to_latex(self.message_types[0], f)
|
||||
else:
|
||||
f.write("The protocol has {} message types with the following fields:\n".format(len(self.message_types)))
|
||||
f.write("\\begin{itemize}\n")
|
||||
for mt in self.message_types:
|
||||
f.write(" \\item \\textbf{{{}}}\n".format(mt.name))
|
||||
export_message_type_to_latex(mt, f)
|
||||
f.write("\\end{itemize}\n")
|
||||
|
||||
f.write("\n")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
mb = MessageTypeBuilder("test")
|
||||
mb.add_label(FieldType.Function.PREAMBLE, 8)
|
||||
mb.add_label(FieldType.Function.SYNC, 4)
|
||||
mb.add_label(FieldType.Function.LENGTH, 8)
|
||||
mb.add_label(FieldType.Function.SEQUENCE_NUMBER, 16)
|
||||
mb.add_label(FieldType.Function.SRC_ADDRESS, 16)
|
||||
mb.add_label(FieldType.Function.DST_ADDRESS, 16)
|
||||
pg = ProtocolGenerator([mb.message_type], [], little_endian=False)
|
||||
pg.generate_message(data="1" * 8)
|
||||
pg.generate_message(data="1" * 16)
|
||||
pg.generate_message(data="0xab", source=Participant("Alice", "A", "1234"),
|
||||
destination=Participant("Bob", "B", "4567"))
|
||||
pg.to_file("/tmp/test.proto")
|
||||
@@ -0,0 +1,399 @@
|
||||
import itertools
|
||||
import math
|
||||
from array import array
|
||||
from collections import defaultdict, Counter
|
||||
|
||||
import numpy as np
|
||||
|
||||
from urh.awre.CommonRange import CommonRange
|
||||
from urh.awre.engines.Engine import Engine
|
||||
from urh.cythonext import awre_util
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class AddressEngine(Engine):
|
||||
def __init__(self, msg_vectors, participant_indices, known_participant_addresses: dict = None,
|
||||
already_labeled: list = None, src_field_present=False):
|
||||
"""
|
||||
|
||||
:param msg_vectors: Message data behind synchronization
|
||||
:type msg_vectors: list of np.ndarray
|
||||
:param participant_indices: list of participant indices
|
||||
where ith position holds participants index for ith messages
|
||||
:type participant_indices: list of int
|
||||
"""
|
||||
assert len(msg_vectors) == len(participant_indices)
|
||||
|
||||
self.minimum_score = 0.1
|
||||
|
||||
self.msg_vectors = msg_vectors
|
||||
self.participant_indices = participant_indices
|
||||
self.already_labeled = []
|
||||
|
||||
self.src_field_present = src_field_present
|
||||
|
||||
if already_labeled is not None:
|
||||
for start, end in already_labeled:
|
||||
# convert it to hex
|
||||
self.already_labeled.append((int(math.ceil(start / 4)), int(math.ceil(end / 4))))
|
||||
|
||||
self.message_indices_by_participant = defaultdict(list)
|
||||
for i, participant_index in enumerate(self.participant_indices):
|
||||
self.message_indices_by_participant[participant_index].append(i)
|
||||
|
||||
if known_participant_addresses is None:
|
||||
self.known_addresses_by_participant = dict() # type: dict[int, np.ndarray]
|
||||
else:
|
||||
self.known_addresses_by_participant = known_participant_addresses # type: dict[int, np.ndarray]
|
||||
|
||||
@staticmethod
|
||||
def cross_swap_check(rng1: CommonRange, rng2: CommonRange):
|
||||
return (rng1.start == rng2.start + rng1.length or rng1.start == rng2.start - rng1.length) \
|
||||
and rng1.value.tobytes() == rng2.value.tobytes()
|
||||
|
||||
@staticmethod
|
||||
def ack_check(rng1: CommonRange, rng2: CommonRange):
|
||||
return rng1.start == rng2.start and rng1.length == rng2.length and rng1.value.tobytes() != rng2.value.tobytes()
|
||||
|
||||
def find(self):
|
||||
addresses_by_participant = {p: [addr.tostring()] for p, addr in self.known_addresses_by_participant.items()}
|
||||
addresses_by_participant.update(self.find_addresses())
|
||||
self._debug("Addresses by participant", addresses_by_participant)
|
||||
|
||||
# Find the address candidates by participant in messages
|
||||
ranges_by_participant = defaultdict(list) # type: dict[int, list[CommonRange]]
|
||||
|
||||
addresses = [np.array(np.frombuffer(a, dtype=np.uint8))
|
||||
for address_list in addresses_by_participant.values()
|
||||
for a in address_list]
|
||||
|
||||
already_labeled_cols = array("L", [e for rng in self.already_labeled for e in range(*rng)])
|
||||
|
||||
# Find occurrences of address candidates in messages and create common ranges over matching positions
|
||||
for i, msg_vector in enumerate(self.msg_vectors):
|
||||
participant = self.participant_indices[i]
|
||||
for address in addresses:
|
||||
for index in awre_util.find_occurrences(msg_vector, address, already_labeled_cols):
|
||||
common_ranges = ranges_by_participant[participant]
|
||||
rng = next((cr for cr in common_ranges if cr.matches(index, address)), None) # type: CommonRange
|
||||
if rng is not None:
|
||||
rng.message_indices.add(i)
|
||||
else:
|
||||
common_ranges.append(CommonRange(index, len(address), address,
|
||||
message_indices={i},
|
||||
range_type="hex"))
|
||||
|
||||
num_messages_by_participant = defaultdict(int)
|
||||
for participant in self.participant_indices:
|
||||
num_messages_by_participant[participant] += 1
|
||||
|
||||
# Look for cross swapped values between participant clusters
|
||||
for p1, p2 in itertools.combinations(ranges_by_participant, 2):
|
||||
ranges1_set, ranges2_set = set(ranges_by_participant[p1]), set(ranges_by_participant[p2])
|
||||
|
||||
for rng1, rng2 in itertools.product(ranges_by_participant[p1], ranges_by_participant[p2]):
|
||||
if rng1 in ranges2_set and rng2 in ranges1_set:
|
||||
if self.cross_swap_check(rng1, rng2):
|
||||
rng1.score += len(rng2.message_indices) / num_messages_by_participant[p2]
|
||||
rng2.score += len(rng1.message_indices) / num_messages_by_participant[p1]
|
||||
elif self.ack_check(rng1, rng2):
|
||||
# Add previous score in divisor to add bonus to ranges that apply to all messages
|
||||
rng1.score += len(rng2.message_indices) / (num_messages_by_participant[p2] + rng1.score)
|
||||
rng2.score += len(rng1.message_indices) / (num_messages_by_participant[p1] + rng2.score)
|
||||
|
||||
if len(ranges_by_participant) == 1 and not self.src_field_present:
|
||||
for p, ranges in ranges_by_participant.items():
|
||||
for rng in sorted(ranges):
|
||||
try:
|
||||
if np.array_equal(rng.value, self.known_addresses_by_participant[p]):
|
||||
# Only one participant in this iteration and address already known -> Highscore
|
||||
rng.score = 1
|
||||
break # Take only the first (leftmost) range
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
high_scored_ranges_by_participant = defaultdict(list)
|
||||
|
||||
address_length = self.__estimate_address_length(ranges_by_participant)
|
||||
|
||||
# Get highscored ranges by participant
|
||||
for participant, common_ranges in ranges_by_participant.items():
|
||||
# Sort by negative score so ranges with highest score appear first
|
||||
# Secondary sort by tuple to ensure order when ranges have same score
|
||||
sorted_ranges = sorted(filter(lambda cr: cr.score > self.minimum_score, common_ranges),
|
||||
key=lambda cr: (-cr.score, cr))
|
||||
if len(sorted_ranges) == 0:
|
||||
addresses_by_participant[participant] = dict()
|
||||
continue
|
||||
|
||||
addresses_by_participant[participant] = {a for a in addresses_by_participant.get(participant, [])
|
||||
if len(a) == address_length}
|
||||
|
||||
for rng in filter(lambda r: r.length == address_length, sorted_ranges):
|
||||
rng.score = min(rng.score, 1.0)
|
||||
high_scored_ranges_by_participant[participant].append(rng)
|
||||
|
||||
# Now we find the most probable address for all participants
|
||||
self.__assign_participant_addresses(addresses_by_participant, high_scored_ranges_by_participant)
|
||||
|
||||
# Eliminate participants for which we could not assign an address
|
||||
for participant, address in addresses_by_participant.copy().items():
|
||||
if address is None:
|
||||
del addresses_by_participant[participant]
|
||||
|
||||
# Now we can separate SRC and DST
|
||||
for participant, ranges in high_scored_ranges_by_participant.items():
|
||||
try:
|
||||
address = addresses_by_participant[participant]
|
||||
except KeyError:
|
||||
high_scored_ranges_by_participant[participant] = []
|
||||
continue
|
||||
|
||||
result = []
|
||||
|
||||
for rng in sorted(ranges, key=lambda r: r.score, reverse=True):
|
||||
rng.field_type = "source address" if rng.value.tostring() == address else "destination address"
|
||||
if len(result) == 0:
|
||||
result.append(rng)
|
||||
else:
|
||||
subset = next((r for r in result if rng.message_indices.issubset(r.message_indices)), None)
|
||||
if subset is not None:
|
||||
if rng.field_type == subset.field_type:
|
||||
# Avoid adding same address type twice
|
||||
continue
|
||||
|
||||
if rng.length != subset.length or (rng.start != subset.end + 1 and rng.end + 1 != subset.start):
|
||||
# Ensure addresses are next to each other
|
||||
continue
|
||||
|
||||
result.append(rng)
|
||||
|
||||
high_scored_ranges_by_participant[participant] = result
|
||||
|
||||
self.__find_broadcast_fields(high_scored_ranges_by_participant, addresses_by_participant)
|
||||
|
||||
result = [rng for ranges in high_scored_ranges_by_participant.values() for rng in ranges]
|
||||
# If we did not find a SRC address, lower the score a bit,
|
||||
# so DST fields do not win later e.g. again length fields in case of tie
|
||||
if not any(rng.field_type == "source address" for rng in result):
|
||||
for rng in result:
|
||||
rng.score *= 0.95
|
||||
|
||||
return result
|
||||
|
||||
def __estimate_address_length(self, ranges_by_participant: dict):
|
||||
"""
|
||||
Estimate the address length which is assumed to be the same for all participants
|
||||
|
||||
:param ranges_by_participant:
|
||||
:return:
|
||||
"""
|
||||
address_lengths = []
|
||||
for participant, common_ranges in ranges_by_participant.items():
|
||||
sorted_ranges = sorted(filter(lambda cr: cr.score > self.minimum_score, common_ranges),
|
||||
key=lambda cr: (-cr.score, cr))
|
||||
|
||||
max_scored = [r for r in sorted_ranges if r.score == sorted_ranges[0].score]
|
||||
|
||||
# Prevent overestimation of address length by looking for substrings
|
||||
for rng in max_scored[:]:
|
||||
same_message_rng = [r for r in sorted_ranges
|
||||
if r not in max_scored and r.score > 0 and r.message_indices == rng.message_indices]
|
||||
|
||||
if len(same_message_rng) > 1 and all(
|
||||
r.value.tobytes() in rng.value.tobytes() for r in same_message_rng):
|
||||
# remove the longer range and add the smaller ones
|
||||
max_scored.remove(rng)
|
||||
max_scored.extend(same_message_rng)
|
||||
|
||||
possible_address_lengths = [r.length for r in max_scored]
|
||||
|
||||
# Count possible address lengths.
|
||||
frequencies = Counter(possible_address_lengths)
|
||||
# Take the most common one. On tie, take the shorter one
|
||||
try:
|
||||
addr_len = max(frequencies, key=lambda x: (frequencies[x], -x))
|
||||
address_lengths.append(addr_len)
|
||||
except ValueError: # max() arg is an empty sequence
|
||||
pass
|
||||
|
||||
# Take most common address length of participants, to ensure they all have same address length
|
||||
counted = Counter(address_lengths)
|
||||
try:
|
||||
address_length = max(counted, key=lambda x: (counted[x], -x))
|
||||
return address_length
|
||||
except ValueError: # max() arg is an empty sequence
|
||||
return 0
|
||||
|
||||
def __assign_participant_addresses(self, addresses_by_participant, high_scored_ranges_by_participant):
|
||||
scored_participants_addresses = dict()
|
||||
for participant in addresses_by_participant:
|
||||
scored_participants_addresses[participant] = defaultdict(int)
|
||||
|
||||
for participant, addresses in addresses_by_participant.items():
|
||||
if participant in self.known_addresses_by_participant:
|
||||
address = self.known_addresses_by_participant[participant].tostring()
|
||||
scored_participants_addresses[participant][address] = 9999999999
|
||||
continue
|
||||
|
||||
for i in self.message_indices_by_participant[participant]:
|
||||
matching = [rng for rng in high_scored_ranges_by_participant[participant]
|
||||
if i in rng.message_indices and rng.value.tostring() in addresses]
|
||||
|
||||
if len(matching) == 1:
|
||||
address = matching[0].value.tostring()
|
||||
# only one address, so probably a destination and not a source
|
||||
scored_participants_addresses[participant][address] *= 0.9
|
||||
|
||||
# Since this is probably an ACK, the address is probably SRC of participant of previous message
|
||||
if i > 0 and self.participant_indices[i - 1] != participant:
|
||||
prev_participant = self.participant_indices[i - 1]
|
||||
prev_matching = [rng for rng in high_scored_ranges_by_participant[prev_participant]
|
||||
if i - 1 in rng.message_indices and rng.value.tostring() in addresses]
|
||||
if len(prev_matching) > 1:
|
||||
for prev_rng in filter(lambda r: r.value.tostring() == address, prev_matching):
|
||||
scored_participants_addresses[prev_participant][address] += prev_rng.score
|
||||
|
||||
elif len(matching) > 1:
|
||||
# more than one address, so there must be a source address included
|
||||
for rng in matching:
|
||||
scored_participants_addresses[participant][rng.value.tostring()] += rng.score
|
||||
|
||||
minimum_score = 0.5
|
||||
taken_addresses = set()
|
||||
self._debug("Scored addresses", scored_participants_addresses)
|
||||
|
||||
# If all participants have exactly one possible address and they all differ, we can assign them right away
|
||||
if all(len(addresses) == 1 for addresses in scored_participants_addresses.values()):
|
||||
all_addresses = [list(addresses)[0] for addresses in scored_participants_addresses.values()]
|
||||
if len(all_addresses) == len(set(all_addresses)): # ensure all addresses are different
|
||||
for p, addresses in scored_participants_addresses.items():
|
||||
addresses_by_participant[p] = list(addresses)[0]
|
||||
return
|
||||
|
||||
for participant, addresses in sorted(scored_participants_addresses.items()):
|
||||
try:
|
||||
# sort filtered results to prevent randomness for equal scores
|
||||
found_address = max(sorted(
|
||||
filter(lambda a: a not in taken_addresses and addresses[a] >= minimum_score, addresses),
|
||||
reverse=True
|
||||
), key=addresses.get)
|
||||
except ValueError:
|
||||
# Could not assign address for this participant
|
||||
addresses_by_participant[participant] = None
|
||||
continue
|
||||
|
||||
addresses_by_participant[participant] = found_address
|
||||
taken_addresses.add(found_address)
|
||||
|
||||
def __find_broadcast_fields(self, high_scored_ranges_by_participant, addresses_by_participant: dict):
|
||||
"""
|
||||
Last we check for messages that were sent to broadcast
|
||||
1. we search for messages that have a SRC address but no DST address
|
||||
2. we look at other messages that have this SRC field and find the corresponding DST position
|
||||
3. we evaluate the value of message without DST from 1 and compare these values with each other.
|
||||
if they match, we found the broadcast address
|
||||
:param high_scored_ranges_by_participant:
|
||||
:return:
|
||||
"""
|
||||
if -1 in addresses_by_participant:
|
||||
# broadcast address is already known
|
||||
return
|
||||
|
||||
broadcast_bag = defaultdict(list) # type: dict[CommonRange, list[int]]
|
||||
for common_ranges in high_scored_ranges_by_participant.values():
|
||||
src_address_fields = sorted(filter(lambda r: r.field_type == "source address", common_ranges))
|
||||
dst_address_fields = sorted(filter(lambda r: r.field_type == "destination address", common_ranges))
|
||||
msg_with_dst = {i for dst_address_field in dst_address_fields for i in dst_address_field.message_indices}
|
||||
|
||||
for src_address_field in src_address_fields: # type: CommonRange
|
||||
msg_without_dst = {i for i in src_address_field.message_indices if i not in msg_with_dst}
|
||||
if len(msg_without_dst) == 0:
|
||||
continue
|
||||
try:
|
||||
matching_dst = next(dst for dst in dst_address_fields
|
||||
if all(i in dst.message_indices
|
||||
for i in src_address_field.message_indices - msg_without_dst))
|
||||
except StopIteration:
|
||||
continue
|
||||
for msg in msg_without_dst:
|
||||
broadcast_bag[matching_dst].append(msg)
|
||||
|
||||
if len(broadcast_bag) == 0:
|
||||
return
|
||||
|
||||
broadcast_address = None
|
||||
for dst, messages in broadcast_bag.items():
|
||||
for msg_index in messages:
|
||||
value = self.msg_vectors[msg_index][dst.start:dst.end + 1]
|
||||
if broadcast_address is None:
|
||||
broadcast_address = value
|
||||
elif value.tobytes() != broadcast_address.tobytes():
|
||||
# Address is not common across messages so it can't be a broadcast address
|
||||
return
|
||||
|
||||
addresses_by_participant[-1] = broadcast_address.tobytes()
|
||||
for dst, messages in broadcast_bag.items():
|
||||
dst.values.append(broadcast_address)
|
||||
dst.message_indices.update(messages)
|
||||
|
||||
def find_addresses(self) -> dict:
|
||||
already_assigned = list(self.known_addresses_by_participant.keys())
|
||||
if len(already_assigned) == len(self.message_indices_by_participant):
|
||||
self._debug("Skipping find addresses as already known.")
|
||||
return dict()
|
||||
|
||||
common_ranges_by_participant = dict()
|
||||
for participant, message_indices in self.message_indices_by_participant.items():
|
||||
# Cluster by length
|
||||
length_clusters = defaultdict(list)
|
||||
for i in message_indices:
|
||||
length_clusters[len(self.msg_vectors[i])].append(i)
|
||||
|
||||
common_ranges_by_length = self.find_common_ranges_by_cluster(self.msg_vectors, length_clusters, range_type="hex")
|
||||
common_ranges_by_participant[participant] = []
|
||||
for ranges in common_ranges_by_length.values():
|
||||
common_ranges_by_participant[participant].extend(self.ignore_already_labeled(ranges,
|
||||
self.already_labeled))
|
||||
|
||||
self._debug("Common ranges by participant:", common_ranges_by_participant)
|
||||
|
||||
result = defaultdict(set)
|
||||
participants = sorted(common_ranges_by_participant) # type: list[int]
|
||||
|
||||
if len(participants) < 2:
|
||||
return result
|
||||
|
||||
# If we already know the address length we do not need to bother with other candidates
|
||||
if len(already_assigned) > 0:
|
||||
addr_len = len(self.known_addresses_by_participant[already_assigned[0]])
|
||||
if any(len(self.known_addresses_by_participant[i]) != addr_len for i in already_assigned):
|
||||
logger.warning("Addresses do not have a common length. Assuming length of {}".format(addr_len))
|
||||
else:
|
||||
addr_len = None
|
||||
|
||||
for p1, p2 in itertools.combinations(participants, 2):
|
||||
p1_already_assigned = p1 in already_assigned
|
||||
p2_already_assigned = p2 in already_assigned
|
||||
|
||||
if p1_already_assigned and p2_already_assigned:
|
||||
continue
|
||||
|
||||
# common ranges are not merged yet, so there is only one element in values
|
||||
values1 = [cr.value for cr in common_ranges_by_participant[p1]]
|
||||
values2 = [cr.value for cr in common_ranges_by_participant[p2]]
|
||||
for seq1, seq2 in itertools.product(values1, values2):
|
||||
lcs = self.find_longest_common_sub_sequences(seq1, seq2)
|
||||
vals = lcs if len(lcs) > 0 else [seq1, seq2]
|
||||
# Address candidate must be at least 2 values long
|
||||
for val in filter(lambda v: len(v) >= 2, vals):
|
||||
if addr_len is not None and len(val) != addr_len:
|
||||
continue
|
||||
if not p1_already_assigned and not p2_already_assigned:
|
||||
result[p1].add(val.tostring())
|
||||
result[p2].add(val.tostring())
|
||||
elif p1_already_assigned and val.tostring() != self.known_addresses_by_participant[p1].tostring():
|
||||
result[p2].add(val.tostring())
|
||||
elif p2_already_assigned and val.tostring() != self.known_addresses_by_participant[p2].tostring():
|
||||
result[p1].add(val.tostring())
|
||||
return result
|
||||
@@ -0,0 +1,121 @@
|
||||
import copy
|
||||
import math
|
||||
from collections import defaultdict
|
||||
|
||||
import numpy as np
|
||||
from urh.util.WSPChecksum import WSPChecksum
|
||||
|
||||
from urh.awre.CommonRange import ChecksumRange
|
||||
from urh.awre.engines.Engine import Engine
|
||||
from urh.cythonext import awre_util
|
||||
from urh.util.GenericCRC import GenericCRC
|
||||
|
||||
|
||||
class ChecksumEngine(Engine):
|
||||
def __init__(self, bitvectors, n_gram_length=8, minimum_score=0.9, already_labeled: list = None):
|
||||
"""
|
||||
:type bitvectors: list of np.ndarray
|
||||
:param bitvectors: bitvectors behind the synchronization
|
||||
"""
|
||||
self.bitvectors = bitvectors
|
||||
self.n_gram_length = n_gram_length
|
||||
self.minimum_score = minimum_score
|
||||
if already_labeled is None:
|
||||
self.already_labeled_cols = set()
|
||||
else:
|
||||
self.already_labeled_cols = {e for rng in already_labeled for e in range(*rng)}
|
||||
|
||||
def find(self):
|
||||
result = list()
|
||||
bitvectors_by_n_gram_length = defaultdict(list)
|
||||
for i, bitvector in enumerate(self.bitvectors):
|
||||
bin_num = int(math.ceil(len(bitvector) / self.n_gram_length))
|
||||
bitvectors_by_n_gram_length[bin_num].append(i)
|
||||
|
||||
crc = GenericCRC()
|
||||
for length, message_indices in bitvectors_by_n_gram_length.items():
|
||||
checksums_for_length = []
|
||||
for index in message_indices:
|
||||
bits = self.bitvectors[index]
|
||||
data_start, data_stop, crc_start, crc_stop = WSPChecksum.search_for_wsp_checksum(bits)
|
||||
if (data_start, data_stop, crc_start, crc_stop) != (0, 0, 0, 0):
|
||||
checksum_range = ChecksumRange(start=crc_start, length=crc_stop-crc_start,
|
||||
data_range_start=data_start, data_range_end=data_stop,
|
||||
crc=WSPChecksum(), score=1/len(message_indices),
|
||||
field_type="checksum", message_indices={index})
|
||||
try:
|
||||
present = next(c for c in checksums_for_length if c == checksum_range)
|
||||
present.message_indices.add(index)
|
||||
except StopIteration:
|
||||
checksums_for_length.append(checksum_range)
|
||||
continue
|
||||
|
||||
crc_object, data_start, data_stop, crc_start, crc_stop = crc.guess_all(bits,
|
||||
ignore_positions=self.already_labeled_cols)
|
||||
|
||||
if (crc_object, data_start, data_stop, crc_start, crc_stop) != (0, 0, 0, 0, 0):
|
||||
checksum_range = ChecksumRange(start=crc_start, length=crc_stop - crc_start,
|
||||
data_range_start=data_start, data_range_end=data_stop,
|
||||
crc=copy.copy(crc_object), score=1 / len(message_indices),
|
||||
field_type="checksum", message_indices={index}
|
||||
)
|
||||
|
||||
try:
|
||||
present = next(rng for rng in checksums_for_length if rng == checksum_range)
|
||||
present.message_indices.add(index)
|
||||
continue
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
checksums_for_length.append(checksum_range)
|
||||
|
||||
matching = awre_util.check_crc_for_messages(message_indices, self.bitvectors,
|
||||
data_start, data_stop,
|
||||
crc_start, crc_stop,
|
||||
*crc_object.get_parameters())
|
||||
|
||||
checksum_range.message_indices.update(matching)
|
||||
|
||||
# Score ranges
|
||||
for rng in checksums_for_length:
|
||||
rng.score = len(rng.message_indices) / len(message_indices)
|
||||
|
||||
try:
|
||||
result.append(max(checksums_for_length, key=lambda x: x.score))
|
||||
except ValueError:
|
||||
pass # no checksums found for this length
|
||||
|
||||
self._debug("Found Checksums", result)
|
||||
try:
|
||||
max_scored = max(filter(lambda x: len(x.message_indices) >= 2 and x.score >= self.minimum_score, result),
|
||||
key=lambda x: x.score)
|
||||
except ValueError:
|
||||
return []
|
||||
|
||||
result = list(filter(lambda x: x.crc == max_scored.crc, result))
|
||||
self._debug("Filtered Checksums", result)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def calc_score(diff_frequencies: dict) -> float:
|
||||
"""
|
||||
Calculate the score based on the distribution of differences
|
||||
1. high if one constant (!= zero) dominates
|
||||
2. Other constants (!= zero) should lower the score, zero means sequence number stays same for some messages
|
||||
|
||||
:param diff_frequencies: Frequencies of decimal differences between columns of subsequent messages
|
||||
e.g. {-255: 3, 1: 1020} means -255 appeared 3 times and 1 appeared 1020 times
|
||||
:return: a score between 0 and 1
|
||||
"""
|
||||
total = sum(diff_frequencies.values())
|
||||
num_zeros = sum(v for k, v in diff_frequencies.items() if k == 0)
|
||||
if num_zeros == total:
|
||||
return 0
|
||||
|
||||
try:
|
||||
most_frequent = ChecksumEngine.get_most_frequent(diff_frequencies)
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
return diff_frequencies[most_frequent] / (total - num_zeros)
|
||||
@@ -0,0 +1,85 @@
|
||||
from urh.awre.CommonRange import CommonRange
|
||||
from urh.awre.Histogram import Histogram
|
||||
import numpy as np
|
||||
from urh.cythonext import awre_util
|
||||
import itertools
|
||||
|
||||
|
||||
class Engine(object):
|
||||
_DEBUG_ = False
|
||||
|
||||
def _debug(self, *args):
|
||||
if self._DEBUG_:
|
||||
print("[{}]".format(self.__class__.__name__), *args)
|
||||
|
||||
@staticmethod
|
||||
def find_common_ranges_by_cluster(msg_vectors, clustered_bitvectors, alpha=0.95, range_type="bit"):
|
||||
"""
|
||||
|
||||
:param alpha: How many percent of values must be equal per range?
|
||||
:param range_type: Describes what kind of range this is: bit, hex or byte.
|
||||
Needed for conversion of range start / end later
|
||||
:type msg_vectors: list of np.ndarray
|
||||
:type clustered_bitvectors: dict
|
||||
:rtype: dict[int, list of CommonRange]
|
||||
"""
|
||||
histograms = {
|
||||
cluster: Histogram(msg_vectors, message_indices)
|
||||
for cluster, message_indices in clustered_bitvectors.items()
|
||||
}
|
||||
|
||||
common_ranges_by_cluster = {
|
||||
cluster: histogram.find_common_ranges(alpha=alpha, range_type=range_type)
|
||||
for cluster, histogram in histograms.items()
|
||||
}
|
||||
|
||||
return common_ranges_by_cluster
|
||||
|
||||
@staticmethod
|
||||
def find_common_ranges_exhaustive(msg_vectors, msg_indices, range_type="bit") -> list:
|
||||
result = []
|
||||
|
||||
for i, j in itertools.combinations(msg_indices, 2):
|
||||
for rng in Histogram(msg_vectors, indices=[i, j]).find_common_ranges(alpha=1, range_type=range_type):
|
||||
try:
|
||||
common_range = next(cr for cr in result if cr.start == rng.start and cr.value.tobytes() == rng.value.tobytes())
|
||||
common_range.message_indices.update({i, j})
|
||||
except StopIteration:
|
||||
result.append(rng)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def ignore_already_labeled(common_ranges, already_labeled):
|
||||
"""
|
||||
Shrink the common ranges so that they not overlap with already labeled ranges.
|
||||
Empty common ranges are removed after shrinking
|
||||
|
||||
:type common_ranges: list of CommonRange
|
||||
:type already_labeled: list of tuple
|
||||
:return: list of CommonRange
|
||||
"""
|
||||
result = []
|
||||
for common_range in common_ranges:
|
||||
range_result = [common_range]
|
||||
for start, end in already_labeled:
|
||||
for rng in range_result[:]:
|
||||
range_result.remove(rng)
|
||||
range_result.extend(rng.ensure_not_overlaps(start, end))
|
||||
result.extend(range_result)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def find_longest_common_sub_sequences(seq1, seq2) -> list:
|
||||
result = []
|
||||
if seq1 is None or seq2 is None:
|
||||
return result
|
||||
|
||||
indices = awre_util.find_longest_common_sub_sequence_indices(seq1, seq2)
|
||||
for ind in indices:
|
||||
s = seq1[slice(*ind)]
|
||||
if len(s) > 0:
|
||||
result.append(s)
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,193 @@
|
||||
import math
|
||||
from collections import defaultdict
|
||||
|
||||
import numpy as np
|
||||
|
||||
from urh.awre.CommonRange import CommonRange
|
||||
from urh.awre.engines.Engine import Engine
|
||||
from urh.cythonext import util
|
||||
|
||||
|
||||
class LengthEngine(Engine):
|
||||
def __init__(self, bitvectors, already_labeled=None):
|
||||
"""
|
||||
|
||||
:type bitvectors: list of np.ndarray
|
||||
:param bitvectors: bitvectors behind the synchronization
|
||||
"""
|
||||
self.bitvectors = bitvectors
|
||||
self.already_labeled = [] if already_labeled is None else already_labeled
|
||||
|
||||
def find(self, n_gram_length=8, minimum_score=0.1):
|
||||
# Consider the n_gram_length
|
||||
bitvectors_by_n_gram_length = defaultdict(list)
|
||||
for i, bitvector in enumerate(self.bitvectors):
|
||||
bin_num = int(math.ceil(len(bitvector) / n_gram_length))
|
||||
bitvectors_by_n_gram_length[bin_num].append(i)
|
||||
|
||||
common_ranges_by_length = self.find_common_ranges_by_cluster(self.bitvectors,
|
||||
bitvectors_by_n_gram_length,
|
||||
alpha=0.7)
|
||||
|
||||
for length, ranges in common_ranges_by_length.items():
|
||||
common_ranges_by_length[length] = self.ignore_already_labeled(ranges, self.already_labeled)
|
||||
|
||||
self.filter_common_ranges(common_ranges_by_length)
|
||||
self._debug("Common Ranges:", common_ranges_by_length)
|
||||
|
||||
scored_ranges = self.score_ranges(common_ranges_by_length, n_gram_length)
|
||||
self._debug("Scored Ranges", scored_ranges)
|
||||
|
||||
# Take the ranges with highest score per cluster if it's score surpasses the minimum score
|
||||
high_scores_by_length = self.choose_high_scored_ranges(scored_ranges, bitvectors_by_n_gram_length,
|
||||
minimum_score)
|
||||
self._debug("Highscored Ranges", high_scores_by_length)
|
||||
return high_scores_by_length.values()
|
||||
|
||||
@staticmethod
|
||||
def filter_common_ranges(common_ranges_by_length: dict):
|
||||
"""
|
||||
Ranges must be common along length clusters
|
||||
but their values must differ, so now we rule out all ranges that are
|
||||
1. common across clusters AND
|
||||
2. have same value
|
||||
|
||||
:return:
|
||||
"""
|
||||
|
||||
ranges = [r for rng in common_ranges_by_length.values() for r in rng]
|
||||
for rng in ranges:
|
||||
count = len([r for r in ranges if rng.start == r.start
|
||||
and rng.length == r.length
|
||||
and rng.value.tobytes() == r.value.tobytes()]
|
||||
)
|
||||
if count < 2:
|
||||
continue
|
||||
|
||||
for length in common_ranges_by_length:
|
||||
try:
|
||||
common_ranges_by_length[length].remove(rng)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def score_ranges(common_ranges_by_length: dict, n_gram_length: int):
|
||||
"""
|
||||
Calculate score for the common ranges
|
||||
|
||||
:param common_ranges_by_length:
|
||||
:param n_gram_length:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# The window length must be smaller than common range's length
|
||||
# and is something like 8 in case of on 8 bit integer.
|
||||
# We make this generic so e.g. 4 bit integers are supported as well
|
||||
if n_gram_length == 8:
|
||||
window_lengths = [8, 16, 32, 64]
|
||||
else:
|
||||
window_lengths = [n_gram_length * i for i in range(1, 5)]
|
||||
|
||||
scored_ranges = dict()
|
||||
for length in common_ranges_by_length:
|
||||
scored_ranges[length] = dict()
|
||||
for window_length in window_lengths:
|
||||
scored_ranges[length][window_length] = []
|
||||
|
||||
byteorders = ["big", "little"] if n_gram_length == 8 else ["big"]
|
||||
for window_length in window_lengths:
|
||||
for length, common_ranges in common_ranges_by_length.items():
|
||||
for common_range in filter(lambda cr: cr.length >= window_length, common_ranges):
|
||||
bits = common_range.value
|
||||
rng_byte_order = "big"
|
||||
|
||||
max_score = max_start = -1
|
||||
for start in range(0, len(bits) + 1 - window_length, n_gram_length):
|
||||
for byteorder in byteorders:
|
||||
score = LengthEngine.score_bits(bits[start:start + window_length],
|
||||
length, position=start, byteorder=byteorder)
|
||||
|
||||
if score > max_score:
|
||||
max_score = score
|
||||
max_start = start
|
||||
rng_byte_order = byteorder
|
||||
|
||||
rng = CommonRange(common_range.start + max_start, window_length,
|
||||
common_range.value[max_start:max_start + window_length],
|
||||
score=max_score, field_type="length",
|
||||
message_indices=common_range.message_indices,
|
||||
range_type=common_range.range_type,
|
||||
byte_order=rng_byte_order)
|
||||
scored_ranges[length][window_length].append(rng)
|
||||
|
||||
return scored_ranges
|
||||
|
||||
def choose_high_scored_ranges(self, scored_ranges: dict, bitvectors_by_n_gram_length: dict, minimum_score: float):
|
||||
|
||||
# Set for every window length the highest scored range as candidate
|
||||
possible_window_lengths = defaultdict(int)
|
||||
for length, ranges_by_window_length in scored_ranges.items():
|
||||
for window_length, ranges in ranges_by_window_length.items():
|
||||
try:
|
||||
ranges_by_window_length[window_length] = max(filter(lambda x: x.score >= minimum_score, ranges),
|
||||
key=lambda x: x.score)
|
||||
possible_window_lengths[window_length] += 1
|
||||
except ValueError:
|
||||
ranges_by_window_length[window_length] = None
|
||||
|
||||
try:
|
||||
# Choose window length -> window length that has a result most often and choose greater on tie
|
||||
chosen_window_length = max(possible_window_lengths, key=lambda x: (possible_window_lengths[x], x))
|
||||
except ValueError:
|
||||
return dict()
|
||||
|
||||
high_scores_by_length = dict()
|
||||
|
||||
# Choose all ranges with highest score per cluster if score surpasses the minimum score
|
||||
for length, ranges_by_window_length in scored_ranges.items():
|
||||
try:
|
||||
if ranges_by_window_length[chosen_window_length]:
|
||||
high_scores_by_length[length] = ranges_by_window_length[chosen_window_length]
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
# If there are length clusters with only one message see if we can assign a range from other clusters
|
||||
for length, msg_indices in bitvectors_by_n_gram_length.items():
|
||||
if len(msg_indices) != 1:
|
||||
continue
|
||||
|
||||
msg_index = msg_indices[0]
|
||||
bitvector = self.bitvectors[msg_index]
|
||||
max_score, best_match = 0, None
|
||||
|
||||
for rng in high_scores_by_length.values():
|
||||
bits = bitvector[rng.start:rng.end + 1]
|
||||
if len(bits) > 0:
|
||||
score = self.score_bits(bits, length, rng.start)
|
||||
if score > max_score:
|
||||
best_match, max_score = rng, score
|
||||
|
||||
if best_match is not None:
|
||||
high_scores_by_length[length] = CommonRange(best_match.start, best_match.length,
|
||||
value=bitvector[best_match.start:best_match.end + 1],
|
||||
score=max_score, field_type="length",
|
||||
message_indices={msg_index}, range_type="bit")
|
||||
|
||||
return high_scores_by_length
|
||||
|
||||
@staticmethod
|
||||
def score_bits(bits: np.ndarray, target_length: int, position: int, byteorder="big"):
|
||||
value = util.bit_array_to_number(bits, len(bits))
|
||||
if byteorder == "little":
|
||||
if len(bits) > 8 and len(bits) % 8 == 0:
|
||||
n = len(bits) // 8
|
||||
value = int.from_bytes(value.to_bytes(n, byteorder="big"), byteorder="little", signed=False)
|
||||
|
||||
# Length field should be at front, so we give lower scores for large starts
|
||||
f = (1 / (1 + 0.25 * position))
|
||||
|
||||
return f * LengthEngine.gauss(value, target_length)
|
||||
|
||||
@staticmethod
|
||||
def gauss(x, mu, sigma=2):
|
||||
return np.exp(-0.5 * np.power((x - mu) / sigma, 2))
|
||||
@@ -0,0 +1,137 @@
|
||||
import numpy as np
|
||||
|
||||
from urh.awre.CommonRange import CommonRange
|
||||
from urh.awre.engines.Engine import Engine
|
||||
from urh.cythonext import awre_util
|
||||
|
||||
|
||||
class SequenceNumberEngine(Engine):
|
||||
def __init__(self, bitvectors, n_gram_length=8, minimum_score=0.75, already_labeled: list = None):
|
||||
"""
|
||||
|
||||
:type bitvectors: list of np.ndarray
|
||||
:param bitvectors: bitvectors behind the synchronization
|
||||
"""
|
||||
self.bitvectors = bitvectors
|
||||
self.n_gram_length = n_gram_length
|
||||
self.minimum_score = minimum_score
|
||||
if already_labeled is None:
|
||||
self.already_labeled_cols = set()
|
||||
else:
|
||||
self.already_labeled_cols = {e // n_gram_length for rng in already_labeled for e in range(*rng)}
|
||||
|
||||
def find(self):
|
||||
n = self.n_gram_length
|
||||
|
||||
if len(self.bitvectors) < 3:
|
||||
# We need at least 3 bitvectors to properly find a sequence number
|
||||
return []
|
||||
|
||||
diff_matrix = self.create_difference_matrix(self.bitvectors, self.n_gram_length)
|
||||
diff_frequencies_by_column = dict()
|
||||
|
||||
for j in range(diff_matrix.shape[1]):
|
||||
unique, counts = np.unique(diff_matrix[:, j], return_counts=True)
|
||||
diff_frequencies_by_column[j] = dict(zip(unique, counts))
|
||||
|
||||
self._debug("Diff_frequencies_by_column", diff_frequencies_by_column)
|
||||
scores_by_column = dict()
|
||||
for column, frequencies in diff_frequencies_by_column.items():
|
||||
if column not in self.already_labeled_cols:
|
||||
scores_by_column[column] = self.calc_score(frequencies)
|
||||
else:
|
||||
scores_by_column[column] = 0
|
||||
|
||||
self._debug("Scores by column", scores_by_column)
|
||||
result = []
|
||||
for candidate_column in sorted(scores_by_column, key=scores_by_column.get, reverse=True):
|
||||
score = scores_by_column[candidate_column]
|
||||
if score < self.minimum_score:
|
||||
continue
|
||||
|
||||
most_common_diff = self.get_most_frequent(diff_frequencies_by_column[candidate_column])
|
||||
message_indices = np.flatnonzero(
|
||||
# get all rows that have the most common difference or zero
|
||||
(diff_matrix[:, candidate_column] == most_common_diff) | (diff_matrix[:, candidate_column] == 0)
|
||||
)
|
||||
|
||||
# For example, index 1 in diff matrix corresponds to index 1 and 2 of messages
|
||||
message_indices = set(message_indices) | set(message_indices + 1)
|
||||
values = set()
|
||||
for i in message_indices:
|
||||
values.add(self.bitvectors[i][candidate_column * n:(candidate_column + 1) * n].tobytes())
|
||||
|
||||
matching_ranges = [r for r in result if r.message_indices == message_indices]
|
||||
|
||||
try:
|
||||
matching_range = next(r for r in matching_ranges if r.start == (candidate_column - 1) * n
|
||||
and (r.byte_order_is_unknown or r.byte_order == "big"))
|
||||
matching_range.length += n
|
||||
matching_range.byte_order = "big"
|
||||
matching_range.values.extend(list(values))
|
||||
continue
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
try:
|
||||
matching_range = next(r for r in matching_ranges if r.start == (candidate_column + 1) * n
|
||||
and (r.byte_order_is_unknown or r.byte_order == "little"))
|
||||
matching_range.start -= n
|
||||
matching_range.length += n
|
||||
matching_range.byte_order = "little"
|
||||
matching_range.values.extend(list(values))
|
||||
continue
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
new_range = CommonRange(start=candidate_column * n, length=n, score=score,
|
||||
field_type="sequence number", message_indices=message_indices,
|
||||
byte_order=None)
|
||||
new_range.values.extend(list(values))
|
||||
result.append(new_range)
|
||||
|
||||
# At least three different values needed to reliably identify a sequence number
|
||||
return [rng for rng in result if len(set(rng.values)) > 2]
|
||||
|
||||
@staticmethod
|
||||
def get_most_frequent(diff_frequencies: dict):
|
||||
return max(filter(lambda x: x not in (0, -1), diff_frequencies), key=diff_frequencies.get)
|
||||
|
||||
@staticmethod
|
||||
def calc_score(diff_frequencies: dict) -> float:
|
||||
"""
|
||||
Calculate the score based on the distribution of differences
|
||||
1. high if one constant (!= zero) dominates
|
||||
2. Other constants (!= zero) should lower the score, zero means sequence number stays same for some messages
|
||||
|
||||
:param diff_frequencies: Frequencies of decimal differences between columns of subsequent messages
|
||||
e.g. {0: 3, 1: 1020} means 0 appeared 3 times and 1 appeared 1020 times
|
||||
:return: a score between 0 and 1
|
||||
"""
|
||||
total = sum(diff_frequencies.values())
|
||||
num_zeros = sum(v for k, v in diff_frequencies.items() if k == 0)
|
||||
if num_zeros == total:
|
||||
return 0
|
||||
|
||||
try:
|
||||
most_frequent = SequenceNumberEngine.get_most_frequent(diff_frequencies)
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
return diff_frequencies[most_frequent] / (total - num_zeros)
|
||||
|
||||
@staticmethod
|
||||
def create_difference_matrix(bitvectors, n_gram_length: int):
|
||||
"""
|
||||
Create the difference matrix e.g.
|
||||
10 20 0
|
||||
1 2 3
|
||||
4 5 6
|
||||
|
||||
means first eight bits of messages 1 and 2 (row 1) differ by 10 if they are considered as decimal number
|
||||
|
||||
:type bitvectors: list of np.ndarray
|
||||
:type n_gram_length: int
|
||||
:rtype: np.ndarray
|
||||
"""
|
||||
return awre_util.create_seq_number_difference_matrix(bitvectors, n_gram_length)
|
||||
516
Software/Universal Radio Hacker/src/urh/cli/urh_cli.py
Normal file
516
Software/Universal Radio Hacker/src/urh/cli/urh_cli.py
Normal file
@@ -0,0 +1,516 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
import numpy as np
|
||||
|
||||
DEFAULT_CARRIER_FREQUENCY = 1e3
|
||||
DEFAULT_CARRIER_AMPLITUDE = 1
|
||||
DEFAULT_CARRIER_PHASE = 0
|
||||
|
||||
DEFAULT_SAMPLES_PER_SYMBOL = 100
|
||||
DEFAULT_NOISE = 0.1
|
||||
DEFAULT_CENTER = 0
|
||||
DEFAULT_CENTER_SPACING = 0.1
|
||||
DEFAULT_TOLERANCE = 5
|
||||
|
||||
cli_exe = sys.executable if hasattr(sys, 'frozen') else sys.argv[0]
|
||||
cur_dir = os.path.realpath(os.path.dirname(os.path.realpath(cli_exe)))
|
||||
SRC_DIR = os.path.realpath(os.path.join(cur_dir, "..", ".."))
|
||||
|
||||
if os.path.isdir(SRC_DIR):
|
||||
sys.path.insert(0, SRC_DIR)
|
||||
|
||||
from urh.signalprocessing.IQArray import IQArray
|
||||
from urh.util import util
|
||||
|
||||
util.set_shared_library_path()
|
||||
|
||||
try:
|
||||
import urh.cythonext.signal_functions
|
||||
import urh.cythonext.path_creator
|
||||
import urh.cythonext.util
|
||||
except ImportError:
|
||||
if hasattr(sys, "frozen"):
|
||||
print("C++ Extensions not found. Exiting...")
|
||||
sys.exit(1)
|
||||
print("Could not find C++ extensions, trying to build them.")
|
||||
old_dir = os.path.realpath(os.curdir)
|
||||
os.chdir(os.path.join(SRC_DIR, "urh", "cythonext"))
|
||||
|
||||
from urh.cythonext import build
|
||||
|
||||
build.main()
|
||||
|
||||
os.chdir(old_dir)
|
||||
|
||||
from urh.dev.BackendHandler import BackendHandler
|
||||
from urh.signalprocessing.Modulator import Modulator
|
||||
from urh.dev.VirtualDevice import VirtualDevice
|
||||
from urh.signalprocessing.ProtocolSniffer import ProtocolSniffer
|
||||
from urh.util import Logger
|
||||
from urh.settings import PAUSE_SEP
|
||||
from urh.util.Logger import logger
|
||||
from urh.signalprocessing.Encoding import Encoding
|
||||
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
|
||||
|
||||
DEVICES = BackendHandler.DEVICE_NAMES
|
||||
MODULATIONS = Modulator.MODULATION_TYPES
|
||||
|
||||
|
||||
def cli_progress_bar(value, end_value, bar_length=20, title="Percent"):
|
||||
percent = value / end_value
|
||||
hashes = '#' * int(round(percent * bar_length))
|
||||
spaces = ' ' * (bar_length - len(hashes))
|
||||
sys.stdout.write("\r{0}:\t[{1}] {2}%".format(title, hashes + spaces, int(round(percent * 100))))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def on_fatal_device_error_occurred(error: str):
|
||||
logger.critical(error.strip())
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def build_modulator_from_args(arguments: argparse.Namespace):
|
||||
if arguments.raw:
|
||||
return None
|
||||
if arguments.bits_per_symbol is None:
|
||||
arguments.bits_per_symbol = 1
|
||||
|
||||
n = 2 ** int(arguments.bits_per_symbol)
|
||||
if arguments.parameters is None or len(arguments.parameters) != n:
|
||||
raise ValueError("You need to give {} parameters for {} bits per symbol".format(n, int(arguments.bits_per_symbol)))
|
||||
|
||||
result = Modulator("CLI Modulator")
|
||||
result.carrier_freq_hz = float(arguments.carrier_frequency)
|
||||
result.carrier_amplitude = float(arguments.carrier_amplitude)
|
||||
result.carrier_phase_deg = float(arguments.carrier_phase)
|
||||
result.samples_per_symbol = int(arguments.samples_per_symbol)
|
||||
result.bits_per_symbol = int(arguments.bits_per_symbol)
|
||||
result.modulation_type = arguments.modulation_type
|
||||
result.sample_rate = arguments.sample_rate
|
||||
|
||||
for i, param in enumerate(arguments.parameters):
|
||||
if result.is_amplitude_based and param.endswith("%"):
|
||||
result.parameters[i] = float(param[:-1])
|
||||
elif result.is_amplitude_based and not param.endswith("%"):
|
||||
result.parameters[i] = float(param) * 100
|
||||
else:
|
||||
result.parameters[i] = float(param)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def build_backend_handler_from_args(arguments: argparse.Namespace):
|
||||
from urh.dev.BackendHandler import Backends
|
||||
bh = BackendHandler()
|
||||
if arguments.device_backend == "native":
|
||||
bh.device_backends[arguments.device.lower()].selected_backend = Backends.native
|
||||
elif arguments.device_backend == "gnuradio":
|
||||
bh.device_backends[arguments.device.lower()].selected_backend = Backends.grc
|
||||
else:
|
||||
raise ValueError("Unsupported device backend")
|
||||
return bh
|
||||
|
||||
|
||||
def build_device_from_args(arguments: argparse.Namespace):
|
||||
from urh.dev.VirtualDevice import Mode
|
||||
bh = build_backend_handler_from_args(arguments)
|
||||
|
||||
bandwidth = arguments.sample_rate if arguments.bandwidth is None else arguments.bandwidth
|
||||
result = VirtualDevice(bh, name=arguments.device, mode=Mode.receive if arguments.receive else Mode.send,
|
||||
freq=arguments.frequency, sample_rate=arguments.sample_rate,
|
||||
bandwidth=bandwidth,
|
||||
gain=arguments.gain, if_gain=arguments.if_gain, baseband_gain=arguments.baseband_gain)
|
||||
|
||||
result.freq_correction = arguments.frequency_correction
|
||||
if arguments.device_identifier is not None:
|
||||
try:
|
||||
result.device_number = int(arguments.device_identifier)
|
||||
except ValueError:
|
||||
result.device_serial = arguments.device_identifier
|
||||
|
||||
result.fatal_error_occurred.connect(on_fatal_device_error_occurred)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def build_protocol_sniffer_from_args(arguments: argparse.Namespace):
|
||||
bh = build_backend_handler_from_args(arguments)
|
||||
|
||||
result = ProtocolSniffer(arguments.samples_per_symbol, arguments.center, arguments.center_spacing,
|
||||
arguments.noise, arguments.tolerance,
|
||||
arguments.modulation_type, arguments.bits_per_symbol,
|
||||
arguments.device.lower(), bh)
|
||||
result.rcv_device.frequency = arguments.frequency
|
||||
result.rcv_device.sample_rate = arguments.sample_rate
|
||||
result.rcv_device.bandwidth = arguments.sample_rate if arguments.bandwidth is None else arguments.bandwidth
|
||||
result.rcv_device.freq_correction = arguments.frequency_correction
|
||||
if arguments.gain is not None:
|
||||
result.rcv_device.gain = arguments.gain
|
||||
if arguments.if_gain is not None:
|
||||
result.rcv_device.if_gain = arguments.if_gain
|
||||
if arguments.baseband_gain is not None:
|
||||
result.rcv_device.baseband_gain = arguments.baseband_gain
|
||||
|
||||
if arguments.device_identifier is not None:
|
||||
try:
|
||||
result.rcv_device.device_number = int(arguments.device_identifier)
|
||||
except ValueError:
|
||||
result.rcv_device.device_serial = arguments.device_identifier
|
||||
|
||||
if arguments.encoding:
|
||||
result.decoder = build_encoding_from_args(arguments)
|
||||
|
||||
result.rcv_device.fatal_error_occurred.connect(on_fatal_device_error_occurred)
|
||||
result.adaptive_noise = arguments.adaptive_noise
|
||||
return result
|
||||
|
||||
|
||||
def build_encoding_from_args(arguments: argparse.Namespace):
|
||||
if arguments.encoding is None:
|
||||
return None
|
||||
|
||||
primitives = arguments.encoding.split(",")
|
||||
return Encoding(list(filter(None, map(str.strip, primitives))))
|
||||
|
||||
|
||||
def read_messages_to_send(arguments: argparse.Namespace):
|
||||
if not arguments.transmit:
|
||||
return None
|
||||
|
||||
if arguments.messages is not None and arguments.filename is not None:
|
||||
print("Either give messages (-m) or a file to read from (-file) not both.")
|
||||
sys.exit(1)
|
||||
elif arguments.messages is not None:
|
||||
#support for calls from external tools e.g. metasploit
|
||||
if len(arguments.messages) == 1:
|
||||
message_strings = arguments.messages[0].split(' ')
|
||||
else:
|
||||
message_strings = arguments.messages
|
||||
elif arguments.filename is not None:
|
||||
with open(arguments.filename) as f:
|
||||
message_strings = list(map(str.strip, f.readlines()))
|
||||
else:
|
||||
print("You need to give messages to send either with (-m) or a file (-file) to read them from.")
|
||||
sys.exit(1)
|
||||
|
||||
encoding = build_encoding_from_args(arguments)
|
||||
|
||||
result = ProtocolAnalyzer.get_protocol_from_string(message_strings, is_hex=arguments.hex,
|
||||
default_pause=arguments.pause,
|
||||
sample_rate=arguments.sample_rate).messages
|
||||
if encoding:
|
||||
for msg in result:
|
||||
msg.decoder = encoding
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def modulate_messages(messages, modulator):
|
||||
if len(messages) == 0:
|
||||
return None
|
||||
|
||||
cli_progress_bar(0, len(messages), title="Modulating")
|
||||
nsamples = sum(int(len(msg.encoded_bits) * modulator.samples_per_symbol + msg.pause) for msg in messages)
|
||||
buffer = IQArray(None, dtype=np.float32, n=nsamples)
|
||||
pos = 0
|
||||
for i, msg in enumerate(messages):
|
||||
# We do not need to modulate the pause extra, as result is already initialized with zeros
|
||||
modulated = modulator.modulate(start=0, data=msg.encoded_bits, pause=0)
|
||||
buffer[pos:pos + len(modulated)] = modulated
|
||||
pos += len(modulated) + msg.pause
|
||||
cli_progress_bar(i + 1, len(messages), title="Modulating")
|
||||
print("\nSuccessfully modulated {} messages".format(len(messages)))
|
||||
return buffer
|
||||
|
||||
|
||||
def parse_project_file(file_path: str):
|
||||
import xml.etree.ElementTree as ET
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
result = defaultdict(lambda: None)
|
||||
if not file_path or not os.path.isfile(file_path):
|
||||
return result
|
||||
|
||||
try:
|
||||
tree = ET.parse(file_path)
|
||||
root = tree.getroot()
|
||||
except Exception as e:
|
||||
logger.error("Could not read project file {}: {}".format(file_path, e))
|
||||
return result
|
||||
|
||||
ProjectManager.read_device_conf_dict(root.find("device_conf"), target_dict=result)
|
||||
result["device"] = result["name"]
|
||||
|
||||
modulators = Modulator.modulators_from_xml_tag(root)
|
||||
if len(modulators) > 0:
|
||||
modulator = modulators[0]
|
||||
result["carrier_frequency"] = modulator.carrier_freq_hz
|
||||
result["carrier_amplitude"] = modulator.carrier_amplitude
|
||||
result["carrier_phase"] = modulator.carrier_phase_deg
|
||||
result["parameters"] = " ".join(map(str, modulator.parameters))
|
||||
result["modulation_type"] = modulator.modulation_type
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def create_parser():
|
||||
parser = argparse.ArgumentParser(description='This is the Command Line Interface for the Universal Radio Hacker.',
|
||||
add_help=False)
|
||||
parser.add_argument('project_file', nargs='?', default=None)
|
||||
|
||||
group1 = parser.add_argument_group('Software Defined Radio Settings', "Configure Software Defined Radio options")
|
||||
group1.add_argument("-d", "--device", choices=DEVICES, metavar="DEVICE",
|
||||
help="Choose a Software Defined Radio. Allowed values are " + ", ".join(DEVICES))
|
||||
group1.add_argument("-di", "--device-identifier")
|
||||
group1.add_argument("-db", "--device-backend", choices=["native", "gnuradio"], default="native")
|
||||
group1.add_argument("-f", "--frequency", type=float,
|
||||
help="Center frequency the SDR shall be tuned to")
|
||||
group1.add_argument("-s", "--sample-rate", type=float, help="Sample rate to use")
|
||||
group1.add_argument("-b", "--bandwidth", type=float, help="Bandwidth to use (defaults to sample rate)")
|
||||
group1.add_argument("-g", "--gain", type=int, help="RF gain the SDR shall use")
|
||||
group1.add_argument("-if", "--if-gain", type=int, help="IF gain to use (only supported for some SDRs)")
|
||||
group1.add_argument("-bb", "--baseband-gain", type=int, help="Baseband gain to use (only supported for some SDRs)")
|
||||
group1.add_argument("-a", "--adaptive-noise", action="store_true", help="Use adaptive noise when receiving.")
|
||||
group1.add_argument("-fcorr", "--frequency-correction", default=1, type=int,
|
||||
help="Set the frequency correction for SDR (if supported)")
|
||||
|
||||
group2 = parser.add_argument_group('Modulation/Demodulation settings',
|
||||
"Configure the Modulator/Demodulator. Not required in raw mode."
|
||||
"In case of RX there are additional demodulation options.")
|
||||
group2.add_argument("-cf", "--carrier-frequency", type=float,
|
||||
help="Carrier frequency in Hertz (default: {})".format(DEFAULT_CARRIER_FREQUENCY))
|
||||
group2.add_argument("-ca", "--carrier-amplitude", type=float,
|
||||
help="Carrier amplitude (default: {})".format(DEFAULT_CARRIER_AMPLITUDE))
|
||||
group2.add_argument("-cp", "--carrier-phase", type=float,
|
||||
help="Carrier phase in degree (default: {})".format(DEFAULT_CARRIER_PHASE))
|
||||
group2.add_argument("-mo", "--modulation-type", choices=MODULATIONS, metavar="MOD_TYPE", default="FSK",
|
||||
help="Modulation type must be one of " + ", ".join(MODULATIONS) + " (default: %(default)s)")
|
||||
group2.add_argument("-bps", "--bits-per-symbol", type=int,
|
||||
help="Bits per symbol e.g. 1 means binary modulation (default: 1).")
|
||||
group2.add_argument("-pm", "--parameters", nargs='+', help="Parameters for modulation. Separate with spaces")
|
||||
|
||||
# Legacy
|
||||
group2.add_argument("-p0", "--parameter-zero", help=argparse.SUPPRESS)
|
||||
group2.add_argument("-p1", "--parameter-one", help=argparse.SUPPRESS)
|
||||
|
||||
group2.add_argument("-sps", "--samples-per-symbol", type=int,
|
||||
help="Length of a symbol in samples (default: {}).".format(DEFAULT_SAMPLES_PER_SYMBOL))
|
||||
group2.add_argument("-bl", "--bit-length", type=int,
|
||||
help="Same as samples per symbol, just there for legacy support (default: {}).".format(DEFAULT_SAMPLES_PER_SYMBOL))
|
||||
|
||||
group2.add_argument("-n", "--noise", type=float,
|
||||
help="Noise threshold (default: {}). Used for RX only.".format(DEFAULT_NOISE))
|
||||
group2.add_argument("-c", "--center", type=float,
|
||||
help="Center between symbols for demodulation (default: {}). "
|
||||
"Used for RX only.".format(DEFAULT_CENTER))
|
||||
group2.add_argument("-cs", "--center-spacing", type=float,
|
||||
help="Center spacing between symbols for demodulation (default: {}). "
|
||||
"Value has only effect for modulations with more than 1 bit per symbol. "
|
||||
"Used only for RX.".format(DEFAULT_CENTER_SPACING))
|
||||
group2.add_argument("-t", "--tolerance", type=float,
|
||||
help="Tolerance for demodulation in samples (default: {}). "
|
||||
"Used for RX only.".format(DEFAULT_TOLERANCE))
|
||||
|
||||
group3 = parser.add_argument_group('Data configuration', "Configure which data to send or where to receive it.")
|
||||
group3.add_argument("--hex", action='store_true', help="Give messages as hex instead of bits")
|
||||
group3.add_argument("-e", "--encoding", help="Specify encoding")
|
||||
group3.add_argument("-m", "--messages", nargs='+', help="Messages to send. Give pauses after with a {0}. "
|
||||
"Separate with spaces e.g. "
|
||||
"1001{0}42ms 1100{0}3ns 0001 1111{0}200. "
|
||||
"If you give no time suffix after a pause "
|
||||
"it is assumed to be in samples. ".format(PAUSE_SEP))
|
||||
|
||||
group3.add_argument("-file", "--filename", help="Filename to read messages from in send mode. "
|
||||
"In receive mode messages will be written to this file "
|
||||
"instead to STDOUT.")
|
||||
group3.add_argument("-p", "--pause", default="250ms",
|
||||
help="The default pause which is inserted after a every message "
|
||||
"which does not have a pause configured. (default: %(default)s) "
|
||||
"Supported time units: s (second), ms (millisecond), µs (microsecond), ns (nanosecond) "
|
||||
"If you do not give a time suffix the pause is assumed to be in samples.")
|
||||
group3.add_argument("-rx", "--receive", action="store_true", help="Enter RX mode")
|
||||
group3.add_argument("-tx", "--transmit", action="store_true", help="Enter TX mode")
|
||||
group3.add_argument("-rt", "--receive-time", default="3.0", type=float,
|
||||
help="How long to receive messages. (default: %(default)s) "
|
||||
"Any negative value means infinite.")
|
||||
group3.add_argument("-r", "--raw", action="store_true",
|
||||
help="Use raw mode i.e. send/receive IQ data instead of bits.")
|
||||
|
||||
group4 = parser.add_argument_group("Miscellaneous options")
|
||||
group4.add_argument("-h", "--help", action="help", help="show this help message and exit")
|
||||
group4.add_argument("-v", "--verbose", action="count")
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main():
|
||||
def get_val(value, project_params: dict, project_param_key: str, default):
|
||||
if value is not None:
|
||||
return value
|
||||
elif project_param_key in project_params:
|
||||
return project_params[project_param_key]
|
||||
else:
|
||||
return default
|
||||
|
||||
import multiprocessing as mp
|
||||
mp.set_start_method("spawn") # allow usage of prange (OpenMP) in Processes
|
||||
mp.freeze_support()
|
||||
|
||||
parser = create_parser()
|
||||
args = parser.parse_args()
|
||||
if args.parameter_zero is not None or args.parameter_one is not None:
|
||||
print("Options -p0 (--parameter-zero) and -p1 (--parameter-one) are not supported anymore.\n"
|
||||
"Use --parameters instead e.g. --parameters 20K 40K for a binary FSK.")
|
||||
sys.exit(1)
|
||||
|
||||
project_params = parse_project_file(args.project_file)
|
||||
for argument in ("device", "frequency", "sample_rate"):
|
||||
if getattr(args, argument):
|
||||
continue
|
||||
|
||||
if project_params[argument] is not None:
|
||||
setattr(args, argument, project_params[argument])
|
||||
else:
|
||||
print("You must specify a {}.".format(argument))
|
||||
sys.exit(1)
|
||||
|
||||
if args.receive and args.transmit:
|
||||
print("You cannot use receive and transmit mode at the same time.")
|
||||
sys.exit(1)
|
||||
if not args.receive and not args.transmit:
|
||||
print("You must choose a mode either RX (-rx, --receive) or TX (-tx, --transmit)")
|
||||
sys.exit(1)
|
||||
|
||||
args.bandwidth = get_val(args.bandwidth, project_params, "bandwidth", None)
|
||||
rx_tx_prefix = "rx_" if args.receive else "tx_"
|
||||
args.gain = get_val(args.gain, project_params, rx_tx_prefix + "gain", None)
|
||||
args.if_gain = get_val(args.if_gain, project_params, rx_tx_prefix + "if_gain", None)
|
||||
args.baseband_gain = get_val(args.baseband_gain, project_params, rx_tx_prefix + "baseband_gain", None)
|
||||
|
||||
if args.modulation_type is None:
|
||||
try:
|
||||
if project_params["modulation_type"] is None:
|
||||
args.modulation_type = MODULATIONS[int(project_params["modulation_index"])]
|
||||
else:
|
||||
args.modulation_type = project_params["modulation_type"]
|
||||
except:
|
||||
pass
|
||||
|
||||
if args.bit_length is not None and args.samples_per_symbol is None:
|
||||
args.samples_per_symbol = args.bit_length # legacy
|
||||
else:
|
||||
args.samples_per_symbol = get_val(args.samples_per_symbol, project_params,
|
||||
"samples_per_symbol", DEFAULT_SAMPLES_PER_SYMBOL)
|
||||
|
||||
args.center = get_val(args.center, project_params, "center", DEFAULT_CENTER)
|
||||
args.center_spacing = get_val(args.center_spacing, project_params, "center_spacing", DEFAULT_CENTER_SPACING)
|
||||
args.noise = get_val(args.noise, project_params, "noise", DEFAULT_NOISE)
|
||||
args.tolerance = get_val(args.tolerance, project_params, "tolerance", DEFAULT_TOLERANCE)
|
||||
args.bits_per_symbol = get_val(args.bits_per_symbol, project_params, "bits_per_symbol", 1)
|
||||
|
||||
args.carrier_frequency = get_val(args.carrier_frequency, project_params, "carrier_frequency",
|
||||
DEFAULT_CARRIER_FREQUENCY)
|
||||
args.carrier_amplitude = get_val(args.carrier_amplitude, project_params, "carrier_amplitude",
|
||||
DEFAULT_CARRIER_AMPLITUDE)
|
||||
args.carrier_phase = get_val(args.carrier_phase, project_params, "carrier_phase", DEFAULT_CARRIER_PHASE)
|
||||
args.parameters = get_val(args.parameters, project_params, "parameters", None)
|
||||
if args.parameters is None:
|
||||
print("You must give modulation parameters (--parameters)")
|
||||
sys.exit(0)
|
||||
|
||||
if args.verbose is None:
|
||||
logger.setLevel(logging.ERROR)
|
||||
elif args.verbose == 1:
|
||||
logger.setLevel(logging.INFO)
|
||||
else:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
Logger.save_log_level()
|
||||
|
||||
argument_string = "\n".join("{} {}".format(arg, getattr(args, arg)) for arg in vars(args))
|
||||
logger.debug("Using these parameters\n" + argument_string)
|
||||
|
||||
if args.transmit:
|
||||
device = build_device_from_args(args)
|
||||
if args.raw:
|
||||
if args.filename is None:
|
||||
print("You need to give a file (-file, --filename) where to read samples from.")
|
||||
sys.exit(1)
|
||||
samples_to_send = np.fromfile(args.filename, dtype=np.complex64)
|
||||
else:
|
||||
modulator = build_modulator_from_args(args)
|
||||
messages_to_send = read_messages_to_send(args)
|
||||
samples_to_send = modulate_messages(messages_to_send, modulator)
|
||||
device.samples_to_send = samples_to_send
|
||||
device.start()
|
||||
|
||||
while not device.sending_finished:
|
||||
try:
|
||||
time.sleep(0.1)
|
||||
device.read_messages()
|
||||
if device.current_index > 0:
|
||||
cli_progress_bar(device.current_index, len(device.samples_to_send), title="Sending")
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
print()
|
||||
device.stop("Sending finished")
|
||||
elif args.receive:
|
||||
if args.raw:
|
||||
if args.filename is None:
|
||||
print("You need to give a file (-file, --filename) to receive into when using raw RX mode.")
|
||||
sys.exit(1)
|
||||
|
||||
receiver = build_device_from_args(args)
|
||||
receiver.start()
|
||||
else:
|
||||
receiver = build_protocol_sniffer_from_args(args)
|
||||
receiver.sniff()
|
||||
|
||||
total_time = 0
|
||||
|
||||
if args.receive_time >= 0:
|
||||
print("Receiving for {} seconds...".format(args.receive_time))
|
||||
else:
|
||||
print("Receiving forever...")
|
||||
|
||||
f = None if args.filename is None else open(args.filename, "w")
|
||||
kwargs = dict() if f is None else {"file": f}
|
||||
|
||||
dev = receiver.rcv_device if hasattr(receiver, "rcv_device") else receiver
|
||||
|
||||
while total_time < abs(args.receive_time):
|
||||
try:
|
||||
dev.read_messages()
|
||||
time.sleep(0.1)
|
||||
if args.receive_time >= 0:
|
||||
# smaller zero means infinity
|
||||
total_time += 0.1
|
||||
|
||||
if not args.raw:
|
||||
num_messages = len(receiver.messages)
|
||||
for msg in receiver.messages[:num_messages]:
|
||||
print(msg.decoded_hex_str if args.hex else msg.decoded_bits_str, **kwargs)
|
||||
del receiver.messages[:num_messages]
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
print("\nStopping receiving...")
|
||||
if args.raw:
|
||||
receiver.stop("Receiving finished")
|
||||
receiver.data[:receiver.current_index].tofile(f)
|
||||
else:
|
||||
receiver.stop()
|
||||
|
||||
if f is not None:
|
||||
f.close()
|
||||
print("Received data written to {}".format(args.filename))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1067
Software/Universal Radio Hacker/src/urh/colormaps.py
Normal file
1067
Software/Universal Radio Hacker/src/urh/colormaps.py
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,710 @@
|
||||
import locale
|
||||
|
||||
import numpy
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QFontMetrics
|
||||
from PyQt5.QtWidgets import QInputDialog, QWidget, QUndoStack, QApplication
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.CompareFrameController import CompareFrameController
|
||||
from urh.controller.dialogs.ContinuousSendDialog import ContinuousSendDialog
|
||||
from urh.controller.dialogs.FuzzingDialog import FuzzingDialog
|
||||
from urh.controller.dialogs.ModulatorDialog import ModulatorDialog
|
||||
from urh.controller.dialogs.SendDialog import SendDialog
|
||||
from urh.models.GeneratorListModel import GeneratorListModel
|
||||
from urh.models.GeneratorTableModel import GeneratorTableModel
|
||||
from urh.models.GeneratorTreeModel import GeneratorTreeModel
|
||||
from urh.plugins.NetworkSDRInterface.NetworkSDRInterfacePlugin import NetworkSDRInterfacePlugin
|
||||
from urh.plugins.PluginManager import PluginManager
|
||||
from urh.plugins.RfCat.RfCatPlugin import RfCatPlugin
|
||||
from urh.signalprocessing.IQArray import IQArray
|
||||
from urh.signalprocessing.Message import Message
|
||||
from urh.signalprocessing.MessageType import MessageType
|
||||
from urh.signalprocessing.Modulator import Modulator
|
||||
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
|
||||
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
|
||||
from urh.ui.actions.Fuzz import Fuzz
|
||||
from urh.ui.ui_generator import Ui_GeneratorTab
|
||||
from urh.util import FileOperator, util
|
||||
from urh.util.Errors import Errors
|
||||
from urh.util.Formatter import Formatter
|
||||
from urh.util.Logger import logger
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class GeneratorTabController(QWidget):
|
||||
def __init__(self, compare_frame_controller: CompareFrameController, project_manager: ProjectManager, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_GeneratorTab()
|
||||
self.ui.setupUi(self)
|
||||
util.set_splitter_stylesheet(self.ui.splitter)
|
||||
|
||||
self.project_manager = project_manager
|
||||
|
||||
self.ui.treeProtocols.setHeaderHidden(True)
|
||||
self.tree_model = GeneratorTreeModel(compare_frame_controller)
|
||||
self.tree_model.set_root_item(compare_frame_controller.proto_tree_model.rootItem)
|
||||
self.tree_model.controller = self
|
||||
self.ui.treeProtocols.setModel(self.tree_model)
|
||||
|
||||
self.table_model = GeneratorTableModel(compare_frame_controller.proto_tree_model.rootItem,
|
||||
compare_frame_controller.decodings)
|
||||
self.table_model.controller = self
|
||||
self.ui.tableMessages.setModel(self.table_model)
|
||||
|
||||
self.label_list_model = GeneratorListModel(None)
|
||||
self.ui.listViewProtoLabels.setModel(self.label_list_model)
|
||||
|
||||
self.network_sdr_button_orig_tooltip = self.ui.btnNetworkSDRSend.toolTip()
|
||||
self.set_network_sdr_send_button_visibility()
|
||||
self.set_rfcat_button_visibility()
|
||||
self.network_sdr_plugin = NetworkSDRInterfacePlugin()
|
||||
self.rfcat_plugin = RfCatPlugin()
|
||||
self.init_rfcat_plugin()
|
||||
|
||||
self.modulation_msg_indices = []
|
||||
|
||||
self.refresh_modulators()
|
||||
self.on_selected_modulation_changed()
|
||||
self.set_fuzzing_ui_status()
|
||||
self.ui.prBarGeneration.hide()
|
||||
self.create_connects(compare_frame_controller)
|
||||
|
||||
self.set_modulation_profile_status()
|
||||
|
||||
def __get_modulator_of_message(self, message: Message) -> Modulator:
|
||||
if message.modulator_index > len(self.modulators) - 1:
|
||||
message.modulator_index = 0
|
||||
return self.modulators[message.modulator_index]
|
||||
|
||||
@property
|
||||
def selected_message_index(self) -> int:
|
||||
min_row, _, _, _ = self.ui.tableMessages.selection_range()
|
||||
return min_row #
|
||||
|
||||
@property
|
||||
def selected_message(self) -> Message:
|
||||
selected_msg_index = self.selected_message_index
|
||||
if selected_msg_index == -1 or selected_msg_index >= len(self.table_model.protocol.messages):
|
||||
return None
|
||||
|
||||
return self.table_model.protocol.messages[selected_msg_index]
|
||||
|
||||
@property
|
||||
def active_groups(self):
|
||||
return self.tree_model.groups
|
||||
|
||||
@property
|
||||
def modulators(self):
|
||||
return self.project_manager.modulators
|
||||
|
||||
@property
|
||||
def total_modulated_samples(self) -> int:
|
||||
return sum(int(len(msg.encoded_bits) * self.__get_modulator_of_message(msg).samples_per_symbol + msg.pause)
|
||||
for msg in self.table_model.protocol.messages)
|
||||
|
||||
@modulators.setter
|
||||
def modulators(self, value):
|
||||
assert type(value) == list
|
||||
self.project_manager.modulators = value
|
||||
|
||||
def create_connects(self, compare_frame_controller):
|
||||
compare_frame_controller.proto_tree_model.modelReset.connect(self.refresh_tree)
|
||||
compare_frame_controller.participant_changed.connect(self.table_model.refresh_vertical_header)
|
||||
self.ui.btnEditModulation.clicked.connect(self.show_modulation_dialog)
|
||||
self.ui.cBoxModulations.currentIndexChanged.connect(self.on_selected_modulation_changed)
|
||||
self.ui.tableMessages.selectionModel().selectionChanged.connect(self.on_table_selection_changed)
|
||||
self.ui.tableMessages.encodings_updated.connect(self.on_table_selection_changed)
|
||||
self.table_model.undo_stack.indexChanged.connect(self.on_undo_stack_index_changed)
|
||||
self.table_model.protocol.qt_signals.line_duplicated.connect(self.refresh_pause_list)
|
||||
self.table_model.protocol.qt_signals.fuzzing_started.connect(self.on_fuzzing_started)
|
||||
self.table_model.protocol.qt_signals.current_fuzzing_message_changed.connect(
|
||||
self.on_current_fuzzing_message_changed)
|
||||
self.table_model.protocol.qt_signals.fuzzing_finished.connect(self.on_fuzzing_finished)
|
||||
self.table_model.first_protocol_added.connect(self.on_first_protocol_added)
|
||||
self.label_list_model.protolabel_fuzzing_status_changed.connect(self.set_fuzzing_ui_status)
|
||||
self.ui.cbViewType.currentIndexChanged.connect(self.on_view_type_changed)
|
||||
self.ui.btnSend.clicked.connect(self.on_btn_send_clicked)
|
||||
self.ui.btnSave.clicked.connect(self.on_btn_save_clicked)
|
||||
self.ui.btnOpen.clicked.connect(self.on_btn_open_clicked)
|
||||
|
||||
self.project_manager.project_updated.connect(self.on_project_updated)
|
||||
|
||||
self.table_model.vertical_header_color_status_changed.connect(
|
||||
self.ui.tableMessages.on_vertical_header_color_status_changed)
|
||||
|
||||
self.label_list_model.protolabel_removed.connect(self.handle_proto_label_removed)
|
||||
|
||||
self.ui.lWPauses.item_edit_clicked.connect(self.edit_pause_item)
|
||||
self.ui.lWPauses.edit_all_items_clicked.connect(self.edit_all_pause_items)
|
||||
self.ui.lWPauses.itemSelectionChanged.connect(self.on_lWpauses_selection_changed)
|
||||
self.ui.lWPauses.lost_focus.connect(self.on_lWPauses_lost_focus)
|
||||
self.ui.lWPauses.doubleClicked.connect(self.on_lWPauses_double_clicked)
|
||||
self.ui.btnGenerate.clicked.connect(self.generate_file)
|
||||
self.label_list_model.protolabel_fuzzing_status_changed.connect(self.handle_plabel_fuzzing_state_changed)
|
||||
self.ui.btnFuzz.clicked.connect(self.on_btn_fuzzing_clicked)
|
||||
self.ui.tableMessages.create_label_triggered.connect(self.create_fuzzing_label)
|
||||
self.ui.tableMessages.edit_label_triggered.connect(self.show_fuzzing_dialog)
|
||||
self.ui.listViewProtoLabels.selection_changed.connect(self.handle_label_selection_changed)
|
||||
self.ui.listViewProtoLabels.edit_on_item_triggered.connect(self.show_fuzzing_dialog)
|
||||
|
||||
self.ui.btnNetworkSDRSend.clicked.connect(self.on_btn_network_sdr_clicked)
|
||||
self.ui.btnRfCatSend.clicked.connect(self.on_btn_rfcat_clicked)
|
||||
|
||||
self.network_sdr_plugin.sending_status_changed.connect(self.on_network_sdr_sending_status_changed)
|
||||
self.network_sdr_plugin.sending_stop_requested.connect(self.on_network_sdr_sending_stop_requested)
|
||||
self.network_sdr_plugin.current_send_message_changed.connect(self.on_send_message_changed)
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_tree(self):
|
||||
self.tree_model.beginResetModel()
|
||||
self.tree_model.endResetModel()
|
||||
self.ui.treeProtocols.expandAll()
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_table(self):
|
||||
self.table_model.update()
|
||||
self.ui.tableMessages.resize_columns()
|
||||
is_data_there = self.table_model.display_data is not None and len(self.table_model.display_data) > 0
|
||||
self.ui.btnSend.setEnabled(is_data_there)
|
||||
self.ui.btnGenerate.setEnabled(is_data_there)
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_label_list(self):
|
||||
self.label_list_model.message = self.selected_message
|
||||
self.label_list_model.update()
|
||||
|
||||
@property
|
||||
def generator_undo_stack(self) -> QUndoStack:
|
||||
return self.table_model.undo_stack
|
||||
|
||||
@pyqtSlot()
|
||||
def on_selected_modulation_changed(self):
|
||||
cur_ind = self.ui.cBoxModulations.currentIndex()
|
||||
min_row, max_row, _, _ = self.ui.tableMessages.selection_range()
|
||||
if min_row > -1:
|
||||
# set modulation for selected messages
|
||||
for row in range(min_row, max_row + 1):
|
||||
try:
|
||||
self.table_model.protocol.messages[row].modulator_index = cur_ind
|
||||
except IndexError:
|
||||
continue
|
||||
|
||||
self.show_modulation_info()
|
||||
|
||||
def refresh_modulators(self):
|
||||
current_index = 0
|
||||
if type(self.sender()) == ModulatorDialog:
|
||||
current_index = self.sender().ui.comboBoxCustomModulations.currentIndex()
|
||||
self.ui.cBoxModulations.clear()
|
||||
for modulator in self.modulators:
|
||||
self.ui.cBoxModulations.addItem(modulator.name)
|
||||
|
||||
self.ui.cBoxModulations.setCurrentIndex(current_index)
|
||||
|
||||
def bootstrap_modulator(self, protocol: ProtocolAnalyzer):
|
||||
"""
|
||||
Set initial parameters for default modulator if it was not edited by user previously
|
||||
:return:
|
||||
"""
|
||||
if len(self.modulators) != 1 or len(self.table_model.protocol.messages) == 0:
|
||||
return
|
||||
|
||||
modulator = self.modulators[0]
|
||||
modulator.samples_per_symbol = protocol.messages[0].samples_per_symbol
|
||||
modulator.bits_per_symbol = protocol.messages[0].bits_per_symbol
|
||||
|
||||
if protocol.signal:
|
||||
modulator.sample_rate = protocol.signal.sample_rate
|
||||
modulator.modulation_type = protocol.signal.modulation_type
|
||||
auto_freq = modulator.estimate_carrier_frequency(protocol.signal, protocol)
|
||||
if auto_freq is not None and auto_freq != 0:
|
||||
modulator.carrier_freq_hz = auto_freq
|
||||
|
||||
modulator.parameters = modulator.get_default_parameters()
|
||||
self.show_modulation_info()
|
||||
|
||||
def show_modulation_info(self):
|
||||
cur_ind = self.ui.cBoxModulations.currentIndex()
|
||||
mod = self.modulators[cur_ind]
|
||||
self.ui.lCarrierFreqValue.setText(mod.carrier_frequency_str)
|
||||
self.ui.lCarrierPhaseValue.setText(mod.carrier_phase_str)
|
||||
self.ui.lBitLenValue.setText(mod.samples_per_symbol_str)
|
||||
self.ui.lSampleRateValue.setText(mod.sample_rate_str)
|
||||
mod_type = mod.modulation_type
|
||||
self.ui.lModTypeValue.setText(mod_type)
|
||||
|
||||
self.ui.lParamCaption.setText(mod.parameter_type_str)
|
||||
self.ui.labelParameterValues.setText(mod.parameters_string)
|
||||
self.ui.labelBitsPerSymbol.setText(str(mod.bits_per_symbol))
|
||||
|
||||
def prepare_modulation_dialog(self) -> (ModulatorDialog, Message):
|
||||
preselected_index = self.ui.cBoxModulations.currentIndex()
|
||||
|
||||
min_row, max_row, start, end = self.ui.tableMessages.selection_range()
|
||||
if min_row > -1:
|
||||
try:
|
||||
selected_message = self.table_model.protocol.messages[min_row]
|
||||
preselected_index = selected_message.modulator_index
|
||||
except IndexError:
|
||||
selected_message = Message([1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0], 0, [], MessageType("empty"))
|
||||
else:
|
||||
selected_message = Message([1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0], 0, [], MessageType("empty"))
|
||||
if len(self.table_model.protocol.messages) > 0:
|
||||
selected_message.samples_per_symbol = self.table_model.protocol.messages[0].samples_per_symbol
|
||||
|
||||
for m in self.modulators:
|
||||
m.default_sample_rate = self.project_manager.device_conf["sample_rate"]
|
||||
|
||||
modulator_dialog = ModulatorDialog(self.modulators, tree_model=self.tree_model, parent=self.parent())
|
||||
modulator_dialog.ui.comboBoxCustomModulations.setCurrentIndex(preselected_index)
|
||||
|
||||
modulator_dialog.finished.connect(self.refresh_modulators)
|
||||
modulator_dialog.finished.connect(self.refresh_pause_list)
|
||||
|
||||
return modulator_dialog, selected_message
|
||||
|
||||
def set_modulation_profile_status(self):
|
||||
visible = settings.read("multiple_modulations", False, bool)
|
||||
self.ui.cBoxModulations.setVisible(visible)
|
||||
|
||||
def init_rfcat_plugin(self):
|
||||
self.set_rfcat_button_visibility()
|
||||
self.rfcat_plugin = RfCatPlugin()
|
||||
self.rfcat_plugin.current_send_message_changed.connect(self.on_send_message_changed)
|
||||
self.ui.btnRfCatSend.setEnabled(self.rfcat_plugin.rfcat_is_found)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_undo_stack_index_changed(self):
|
||||
self.refresh_table()
|
||||
self.refresh_pause_list()
|
||||
self.refresh_label_list()
|
||||
self.refresh_estimated_time()
|
||||
self.set_fuzzing_ui_status()
|
||||
|
||||
@pyqtSlot()
|
||||
def show_modulation_dialog(self):
|
||||
modulator_dialog, message = self.prepare_modulation_dialog()
|
||||
modulator_dialog.showMaximized()
|
||||
|
||||
modulator_dialog.initialize(message.encoded_bits_str[0:16])
|
||||
self.project_manager.modulation_was_edited = True
|
||||
|
||||
@pyqtSlot()
|
||||
def on_table_selection_changed(self):
|
||||
min_row, max_row, start, end = self.ui.tableMessages.selection_range()
|
||||
|
||||
if min_row == -1:
|
||||
self.ui.lEncodingValue.setText("-") #
|
||||
self.ui.lEncodingValue.setToolTip("")
|
||||
self.label_list_model.message = None
|
||||
return
|
||||
|
||||
container = self.table_model.protocol
|
||||
message = container.messages[min_row]
|
||||
self.label_list_model.message = message
|
||||
decoder_name = message.decoder.name
|
||||
metrics = QFontMetrics(self.ui.lEncodingValue.font())
|
||||
elidedName = metrics.elidedText(decoder_name, Qt.ElideRight, self.ui.lEncodingValue.width())
|
||||
self.ui.lEncodingValue.setText(elidedName)
|
||||
self.ui.lEncodingValue.setToolTip(decoder_name)
|
||||
self.ui.cBoxModulations.blockSignals(True)
|
||||
self.ui.cBoxModulations.setCurrentIndex(message.modulator_index)
|
||||
self.show_modulation_info()
|
||||
self.ui.cBoxModulations.blockSignals(False)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def edit_pause_item(self, index: int):
|
||||
message = self.table_model.protocol.messages[index]
|
||||
cur_len = message.pause
|
||||
new_len, ok = QInputDialog.getInt(self, self.tr("Enter new Pause Length"),
|
||||
self.tr("Pause Length:"), cur_len, 0)
|
||||
if ok:
|
||||
message.pause = new_len
|
||||
self.refresh_pause_list()
|
||||
|
||||
@pyqtSlot()
|
||||
def edit_all_pause_items(self):
|
||||
message = self.table_model.protocol.messages[0]
|
||||
cur_len = message.pause
|
||||
new_len, ok = QInputDialog.getInt(self, self.tr("Enter new Pause Length"),
|
||||
self.tr("Pause Length:"), cur_len, 0)
|
||||
if ok:
|
||||
for message in self.table_model.protocol.messages:
|
||||
message.pause = new_len
|
||||
|
||||
self.refresh_pause_list()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_lWPauses_double_clicked(self):
|
||||
sel_indexes = [index.row() for index in self.ui.lWPauses.selectedIndexes()]
|
||||
if len(sel_indexes) > 0:
|
||||
self.edit_pause_item(sel_indexes[0])
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_pause_list(self):
|
||||
self.ui.lWPauses.clear()
|
||||
|
||||
fmt_str = "Pause ({1:d}-{2:d}) <{0:d} samples ({3})>"
|
||||
for i, pause in enumerate(self.table_model.protocol.pauses):
|
||||
sr = self.__get_modulator_of_message(self.table_model.protocol.messages[i]).sample_rate
|
||||
item = fmt_str.format(pause, i + 1, i + 2, Formatter.science_time(pause / sr))
|
||||
self.ui.lWPauses.addItem(item)
|
||||
|
||||
self.refresh_estimated_time()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_lWpauses_selection_changed(self):
|
||||
rows = [index.row() for index in self.ui.lWPauses.selectedIndexes()]
|
||||
if len(rows) == 0:
|
||||
return
|
||||
self.ui.tableMessages.show_pause_active = True
|
||||
self.ui.tableMessages.pause_row = rows[0]
|
||||
self.ui.tableMessages.viewport().update()
|
||||
self.ui.tableMessages.scrollTo(self.table_model.index(rows[0], 0))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_lWPauses_lost_focus(self):
|
||||
self.ui.tableMessages.show_pause_active = False
|
||||
self.ui.tableMessages.viewport().update()
|
||||
|
||||
@pyqtSlot()
|
||||
def generate_file(self):
|
||||
try:
|
||||
total_samples = self.total_modulated_samples
|
||||
buffer = self.prepare_modulation_buffer(total_samples, show_error=False)
|
||||
if buffer is None:
|
||||
Errors.generic_error(self.tr("File too big"), self.tr("This file would get too big to save."))
|
||||
self.unsetCursor()
|
||||
return
|
||||
modulated_samples = self.modulate_data(buffer)
|
||||
try:
|
||||
sample_rate = self.modulators[0].sample_rate
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
sample_rate = 1e6
|
||||
FileOperator.ask_signal_file_name_and_save("generated", modulated_samples, sample_rate=sample_rate, parent=self)
|
||||
except Exception as e:
|
||||
Errors.exception(e)
|
||||
self.unsetCursor()
|
||||
|
||||
def prepare_modulation_buffer(self, total_samples: int, show_error=True) -> IQArray:
|
||||
dtype = Modulator.get_dtype()
|
||||
n = 2 if dtype == np.int8 else 4 if dtype == np.int16 else 8
|
||||
|
||||
memory_size_for_buffer = total_samples * n
|
||||
logger.debug("Allocating {0:.2f}MB for modulated samples".format(memory_size_for_buffer / (1024 ** 2)))
|
||||
try:
|
||||
# allocate it three times as we need the same amount for the sending process
|
||||
IQArray(None, dtype=dtype, n=3*total_samples)
|
||||
except MemoryError:
|
||||
# will go into continuous mode in this case
|
||||
if show_error:
|
||||
Errors.not_enough_ram_for_sending_precache(3*memory_size_for_buffer)
|
||||
return None
|
||||
|
||||
return IQArray(None, dtype=dtype, n=total_samples)
|
||||
|
||||
def modulate_data(self, buffer: IQArray) -> IQArray:
|
||||
"""
|
||||
|
||||
:param buffer: Buffer in which the modulated data shall be written, initialized with zeros
|
||||
:return:
|
||||
"""
|
||||
self.ui.prBarGeneration.show()
|
||||
self.ui.prBarGeneration.setValue(0)
|
||||
self.ui.prBarGeneration.setMaximum(self.table_model.row_count)
|
||||
self.modulation_msg_indices.clear()
|
||||
|
||||
pos = 0
|
||||
for i in range(0, self.table_model.row_count):
|
||||
message = self.table_model.protocol.messages[i]
|
||||
modulator = self.__get_modulator_of_message(message)
|
||||
# We do not need to modulate the pause extra, as result is already initialized with zeros
|
||||
modulated = modulator.modulate(start=0, data=message.encoded_bits, pause=0)
|
||||
buffer[pos:pos + len(modulated)] = modulated
|
||||
pos += len(modulated) + message.pause
|
||||
self.modulation_msg_indices.append(pos)
|
||||
self.ui.prBarGeneration.setValue(i + 1)
|
||||
QApplication.instance().processEvents()
|
||||
|
||||
self.ui.prBarGeneration.hide()
|
||||
return buffer
|
||||
|
||||
@pyqtSlot(int)
|
||||
def show_fuzzing_dialog(self, label_index: int):
|
||||
view = self.ui.cbViewType.currentIndex()
|
||||
|
||||
if self.label_list_model.message is not None:
|
||||
msg_index = self.table_model.protocol.messages.index(self.label_list_model.message)
|
||||
fdc = FuzzingDialog(protocol=self.table_model.protocol, label_index=label_index,
|
||||
msg_index=msg_index, proto_view=view, parent=self)
|
||||
fdc.show()
|
||||
fdc.finished.connect(self.on_fuzzing_dialog_finished)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_fuzzing_dialog_finished(self):
|
||||
self.refresh_label_list()
|
||||
self.refresh_table()
|
||||
self.set_fuzzing_ui_status()
|
||||
self.ui.tabWidget.setCurrentIndex(2)
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_plabel_fuzzing_state_changed(self):
|
||||
self.refresh_table()
|
||||
self.label_list_model.update()
|
||||
|
||||
@pyqtSlot(ProtocolLabel)
|
||||
def handle_proto_label_removed(self, plabel: ProtocolLabel):
|
||||
self.refresh_label_list()
|
||||
self.refresh_table()
|
||||
self.set_fuzzing_ui_status()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_fuzzing_clicked(self):
|
||||
fuz_mode = "Successive"
|
||||
if self.ui.rbConcurrent.isChecked():
|
||||
fuz_mode = "Concurrent"
|
||||
elif self.ui.rBExhaustive.isChecked():
|
||||
fuz_mode = "Exhaustive"
|
||||
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
fuzz_action = Fuzz(self.table_model.protocol, fuz_mode)
|
||||
self.table_model.undo_stack.push(fuzz_action)
|
||||
for row in fuzz_action.added_message_indices:
|
||||
self.table_model.update_checksums_for_row(row)
|
||||
self.unsetCursor()
|
||||
self.ui.tableMessages.setFocus()
|
||||
|
||||
@pyqtSlot()
|
||||
def set_fuzzing_ui_status(self):
|
||||
btn_was_enabled = self.ui.btnFuzz.isEnabled()
|
||||
fuzz_active = any(lbl.active_fuzzing for msg in self.table_model.protocol.messages for lbl in msg.message_type)
|
||||
self.ui.btnFuzz.setEnabled(fuzz_active)
|
||||
if self.ui.btnFuzz.isEnabled() and not btn_was_enabled:
|
||||
font = self.ui.btnFuzz.font()
|
||||
font.setBold(True)
|
||||
self.ui.btnFuzz.setFont(font)
|
||||
else:
|
||||
font = self.ui.btnFuzz.font()
|
||||
font.setBold(False)
|
||||
self.ui.btnFuzz.setFont(font)
|
||||
self.ui.btnFuzz.setStyleSheet("")
|
||||
|
||||
has_same_message = self.table_model.protocol.multiple_fuzz_labels_per_message
|
||||
self.ui.rBSuccessive.setEnabled(has_same_message)
|
||||
self.ui.rBExhaustive.setEnabled(has_same_message)
|
||||
self.ui.rbConcurrent.setEnabled(has_same_message)
|
||||
|
||||
def refresh_existing_encodings(self, encodings_from_file):
|
||||
"""
|
||||
Refresh existing encodings for messages, when encoding was changed by user in dialog
|
||||
|
||||
:return:
|
||||
"""
|
||||
update = False
|
||||
|
||||
for msg in self.table_model.protocol.messages:
|
||||
i = next((i for i, d in enumerate(encodings_from_file) if d.name == msg.decoder.name), 0)
|
||||
if msg.decoder != encodings_from_file[i]:
|
||||
update = True
|
||||
msg.decoder = encodings_from_file[i]
|
||||
msg.clear_decoded_bits()
|
||||
msg.clear_encoded_bits()
|
||||
|
||||
if update:
|
||||
self.refresh_table()
|
||||
self.refresh_estimated_time()
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_estimated_time(self):
|
||||
c = self.table_model.protocol
|
||||
if c.num_messages == 0:
|
||||
self.ui.lEstimatedTime.setText("Estimated Time: ")
|
||||
return
|
||||
|
||||
avg_msg_len = numpy.mean([len(msg.encoded_bits) for msg in c.messages])
|
||||
avg_samples_per_symbol = numpy.mean([m.samples_per_symbol for m in self.modulators])
|
||||
avg_sample_rate = numpy.mean([m.sample_rate for m in self.modulators])
|
||||
pause_samples = sum(c.pauses)
|
||||
nsamples = c.num_messages * avg_msg_len * avg_samples_per_symbol + pause_samples
|
||||
|
||||
self.ui.lEstimatedTime.setText(
|
||||
locale.format_string("Estimated Time: %.04f seconds", nsamples / avg_sample_rate))
|
||||
|
||||
@pyqtSlot(int, int, int)
|
||||
def create_fuzzing_label(self, msg_index: int, start: int, end: int):
|
||||
con = self.table_model.protocol
|
||||
start, end = con.convert_range(start, end - 1, self.ui.cbViewType.currentIndex(), 0, False, msg_index)
|
||||
lbl = con.create_fuzzing_label(start, end, msg_index)
|
||||
self.show_fuzzing_dialog(con.protocol_labels.index(lbl))
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_label_selection_changed(self):
|
||||
rows = [index.row() for index in self.ui.listViewProtoLabels.selectedIndexes()]
|
||||
if len(rows) == 0:
|
||||
return
|
||||
|
||||
maxrow = numpy.max(rows)
|
||||
|
||||
try:
|
||||
label = self.table_model.protocol.protocol_labels[maxrow]
|
||||
except IndexError:
|
||||
return
|
||||
if label.show and self.selected_message:
|
||||
start, end = self.selected_message.get_label_range(lbl=label, view=self.table_model.proto_view,
|
||||
decode=False)
|
||||
indx = self.table_model.index(0, int((start + end) / 2))
|
||||
self.ui.tableMessages.scrollTo(indx)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_view_type_changed(self):
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
self.table_model.proto_view = self.ui.cbViewType.currentIndex()
|
||||
self.ui.tableMessages.resize_columns()
|
||||
self.unsetCursor()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_send_clicked(self):
|
||||
try:
|
||||
total_samples = self.total_modulated_samples
|
||||
buffer = self.prepare_modulation_buffer(total_samples)
|
||||
if buffer is not None:
|
||||
modulated_data = self.modulate_data(buffer)
|
||||
else:
|
||||
# Enter continuous mode
|
||||
modulated_data = None
|
||||
|
||||
try:
|
||||
if modulated_data is not None:
|
||||
try:
|
||||
dialog = SendDialog(self.project_manager, modulated_data=modulated_data,
|
||||
modulation_msg_indices=self.modulation_msg_indices, parent=self)
|
||||
except MemoryError:
|
||||
# Not enough memory for device buffer so we need to create a continuous send dialog
|
||||
del modulated_data
|
||||
Errors.not_enough_ram_for_sending_precache(None)
|
||||
dialog = ContinuousSendDialog(self.project_manager,
|
||||
self.table_model.protocol.messages,
|
||||
self.modulators, total_samples, parent=self)
|
||||
else:
|
||||
dialog = ContinuousSendDialog(self.project_manager, self.table_model.protocol.messages,
|
||||
self.modulators, total_samples, parent=self)
|
||||
except OSError as e:
|
||||
logger.exception(e)
|
||||
return
|
||||
if dialog.has_empty_device_list:
|
||||
Errors.no_device()
|
||||
dialog.close()
|
||||
return
|
||||
|
||||
dialog.device_parameters_changed.connect(self.project_manager.set_device_parameters)
|
||||
dialog.show()
|
||||
dialog.graphics_view.show_full_scene(reinitialize=True)
|
||||
except Exception as e:
|
||||
Errors.exception(e)
|
||||
self.unsetCursor()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_save_clicked(self):
|
||||
filename = FileOperator.ask_save_file_name("profile.fuzz.xml", caption="Save fuzzing profile")
|
||||
if filename:
|
||||
self.table_model.protocol.to_xml_file(filename,
|
||||
decoders=self.project_manager.decodings,
|
||||
participants=self.project_manager.participants,
|
||||
modulators=self.modulators)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_open_clicked(self):
|
||||
dialog = FileOperator.get_open_dialog(directory_mode=False, parent=self, name_filter="fuzz")
|
||||
if dialog.exec_():
|
||||
for filename in dialog.selectedFiles():
|
||||
self.load_from_file(filename)
|
||||
|
||||
def load_from_file(self, filename: str):
|
||||
try:
|
||||
self.modulators = ProjectManager.read_modulators_from_file(filename)
|
||||
self.table_model.protocol.from_xml_file(filename)
|
||||
self.refresh_pause_list()
|
||||
self.refresh_estimated_time()
|
||||
self.refresh_modulators()
|
||||
self.show_modulation_info()
|
||||
self.refresh_table()
|
||||
self.set_fuzzing_ui_status()
|
||||
except:
|
||||
logger.error("You done something wrong to the xml fuzzing profile.")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_project_updated(self):
|
||||
self.table_model.refresh_vertical_header()
|
||||
|
||||
def set_network_sdr_send_button_visibility(self):
|
||||
is_plugin_enabled = PluginManager().is_plugin_enabled("NetworkSDRInterface")
|
||||
self.ui.btnNetworkSDRSend.setVisible(is_plugin_enabled)
|
||||
|
||||
def set_rfcat_button_visibility(self):
|
||||
is_plugin_enabled = PluginManager().is_plugin_enabled("RfCat")
|
||||
self.ui.btnRfCatSend.setVisible(is_plugin_enabled)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_network_sdr_clicked(self):
|
||||
if not self.network_sdr_plugin.is_sending:
|
||||
messages = self.table_model.protocol.messages
|
||||
sample_rates = [self.__get_modulator_of_message(msg).sample_rate for msg in messages]
|
||||
self.network_sdr_plugin.start_message_sending_thread(messages, sample_rates)
|
||||
else:
|
||||
self.network_sdr_plugin.stop_sending_thread()
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_network_sdr_sending_status_changed(self, is_sending: bool):
|
||||
self.ui.btnNetworkSDRSend.setChecked(is_sending)
|
||||
self.ui.btnNetworkSDRSend.setEnabled(True)
|
||||
self.ui.btnNetworkSDRSend.setToolTip(
|
||||
"Sending in progress" if is_sending else self.network_sdr_button_orig_tooltip)
|
||||
if not is_sending:
|
||||
self.ui.tableMessages.clearSelection()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_network_sdr_sending_stop_requested(self):
|
||||
self.ui.btnNetworkSDRSend.setToolTip("Stopping sending")
|
||||
self.ui.btnNetworkSDRSend.setEnabled(False)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_send_message_changed(self, message_index: int):
|
||||
self.ui.tableMessages.selectRow(message_index)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_rfcat_clicked(self):
|
||||
if not self.rfcat_plugin.is_sending:
|
||||
messages = self.table_model.protocol.messages
|
||||
sample_rates = [self.__get_modulator_of_message(msg).sample_rate for msg in messages]
|
||||
self.rfcat_plugin.start_message_sending_thread(messages, sample_rates, self.modulators,
|
||||
self.project_manager)
|
||||
else:
|
||||
self.rfcat_plugin.stop_sending_thread()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_fuzzing_started(self, num_values: int):
|
||||
self.ui.stackedWidgetFuzzing.setCurrentWidget(self.ui.pageFuzzingProgressBar)
|
||||
self.ui.progressBarFuzzing.setMaximum(num_values)
|
||||
self.ui.progressBarFuzzing.setValue(0)
|
||||
QApplication.instance().processEvents()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_fuzzing_finished(self):
|
||||
self.ui.stackedWidgetFuzzing.setCurrentWidget(self.ui.pageFuzzingUI)
|
||||
# Calculate Checksums for Fuzzed Messages
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
|
||||
self.unsetCursor()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_current_fuzzing_message_changed(self, current_message: int):
|
||||
self.ui.progressBarFuzzing.setValue(current_message)
|
||||
QApplication.instance().processEvents()
|
||||
|
||||
@pyqtSlot(ProtocolAnalyzer)
|
||||
def on_first_protocol_added(self, protocol: ProtocolAnalyzer):
|
||||
if not self.project_manager.modulation_was_edited:
|
||||
self.bootstrap_modulator(protocol)
|
||||
@@ -0,0 +1,967 @@
|
||||
import copy
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QDir, Qt, pyqtSlot, QTimer
|
||||
from PyQt5.QtGui import QIcon, QCloseEvent, QKeySequence
|
||||
from PyQt5.QtWidgets import QMainWindow, QUndoGroup, QActionGroup, QHeaderView, QAction, QMessageBox, QApplication, qApp
|
||||
|
||||
from urh import settings, version
|
||||
from urh.controller.CompareFrameController import CompareFrameController
|
||||
from urh.controller.GeneratorTabController import GeneratorTabController
|
||||
from urh.controller.SignalTabController import SignalTabController
|
||||
from urh.controller.SimulatorTabController import SimulatorTabController
|
||||
from urh.controller.dialogs.CSVImportDialog import CSVImportDialog
|
||||
from urh.controller.dialogs.DecoderDialog import DecoderDialog
|
||||
from urh.controller.dialogs.OptionsDialog import OptionsDialog
|
||||
from urh.controller.dialogs.ProjectDialog import ProjectDialog
|
||||
from urh.controller.dialogs.ProtocolSniffDialog import ProtocolSniffDialog
|
||||
from urh.controller.dialogs.ReceiveDialog import ReceiveDialog
|
||||
from urh.controller.dialogs.SpectrumDialogController import SpectrumDialogController
|
||||
from urh.controller.widgets.SignalFrame import SignalFrame
|
||||
from urh.models.FileFilterProxyModel import FileFilterProxyModel
|
||||
from urh.models.FileIconProvider import FileIconProvider
|
||||
from urh.models.FileSystemModel import FileSystemModel
|
||||
from urh.models.ParticipantLegendListModel import ParticipantLegendListModel
|
||||
from urh.plugins.PluginManager import PluginManager
|
||||
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
|
||||
from urh.signalprocessing.Signal import Signal
|
||||
from urh.ui.ui_main import Ui_MainWindow
|
||||
from urh.util import FileOperator, util
|
||||
from urh.util.Errors import Errors
|
||||
from urh.util.Logger import logger
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class MainController(QMainWindow):
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
self.ui = Ui_MainWindow()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
util.set_splitter_stylesheet(self.ui.splitter)
|
||||
|
||||
OptionsDialog.write_default_options()
|
||||
|
||||
self.project_save_timer = QTimer()
|
||||
self.project_manager = ProjectManager(self)
|
||||
self.plugin_manager = PluginManager()
|
||||
self.signal_tab_controller = SignalTabController(self.project_manager,
|
||||
parent=self.ui.tab_interpretation)
|
||||
self.ui.tab_interpretation.layout().addWidget(self.signal_tab_controller)
|
||||
self.compare_frame_controller = CompareFrameController(parent=self.ui.tab_protocol,
|
||||
plugin_manager=self.plugin_manager,
|
||||
project_manager=self.project_manager)
|
||||
self.compare_frame_controller.ui.splitter.setSizes([1, 1000000])
|
||||
|
||||
self.ui.tab_protocol.layout().addWidget(self.compare_frame_controller)
|
||||
|
||||
self.generator_tab_controller = GeneratorTabController(self.compare_frame_controller,
|
||||
self.project_manager,
|
||||
parent=self.ui.tab_generator)
|
||||
|
||||
self.simulator_tab_controller = SimulatorTabController(parent=self.ui.tab_simulator,
|
||||
compare_frame_controller=self.compare_frame_controller,
|
||||
generator_tab_controller=self.generator_tab_controller,
|
||||
project_manager=self.project_manager)
|
||||
|
||||
self.ui.tab_simulator.layout().addWidget(self.simulator_tab_controller)
|
||||
|
||||
self.undo_group = QUndoGroup()
|
||||
self.undo_group.addStack(self.signal_tab_controller.signal_undo_stack)
|
||||
self.undo_group.addStack(self.compare_frame_controller.protocol_undo_stack)
|
||||
self.undo_group.addStack(self.generator_tab_controller.generator_undo_stack)
|
||||
self.undo_group.setActiveStack(self.signal_tab_controller.signal_undo_stack)
|
||||
|
||||
self.cancel_action = QAction(self.tr("Cancel"), self)
|
||||
self.cancel_action.setShortcut(QKeySequence.Cancel if hasattr(QKeySequence, "Cancel") else "Esc")
|
||||
self.cancel_action.triggered.connect(self.on_cancel_triggered)
|
||||
self.cancel_action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
|
||||
self.cancel_action.setIcon(QIcon.fromTheme("dialog-cancel"))
|
||||
self.addAction(self.cancel_action)
|
||||
|
||||
self.ui.actionAuto_detect_new_signals.setChecked(settings.read("auto_detect_new_signals", True, bool))
|
||||
|
||||
self.participant_legend_model = ParticipantLegendListModel(self.project_manager.participants)
|
||||
self.ui.listViewParticipants.setModel(self.participant_legend_model)
|
||||
|
||||
gtc = self.generator_tab_controller
|
||||
gtc.ui.splitter.setSizes([int(gtc.width() / 0.7), int(gtc.width() / 0.3)])
|
||||
|
||||
self.ui.tab_generator.layout().addWidget(self.generator_tab_controller)
|
||||
|
||||
self.signal_protocol_dict = {} # type: dict[SignalFrame, ProtocolAnalyzer]
|
||||
|
||||
self.ui.lnEdtTreeFilter.setClearButtonEnabled(True)
|
||||
|
||||
group = QActionGroup(self)
|
||||
self.ui.actionFSK.setActionGroup(group)
|
||||
self.ui.actionOOK.setActionGroup(group)
|
||||
self.ui.actionNone.setActionGroup(group)
|
||||
self.ui.actionPSK.setActionGroup(group)
|
||||
|
||||
noise_threshold_setting = settings.read("default_noise_threshold", "automatic")
|
||||
noise_threshold_group = QActionGroup(self)
|
||||
self.ui.actionAutomaticNoiseThreshold.setActionGroup(noise_threshold_group)
|
||||
self.ui.actionAutomaticNoiseThreshold.setChecked(noise_threshold_setting == "automatic")
|
||||
self.ui.action1NoiseThreshold.setActionGroup(noise_threshold_group)
|
||||
self.ui.action1NoiseThreshold.setChecked(noise_threshold_setting == "1")
|
||||
self.ui.action5NoiseThreshold.setActionGroup(noise_threshold_group)
|
||||
self.ui.action5NoiseThreshold.setChecked(noise_threshold_setting == "5")
|
||||
self.ui.action10NoiseThreshold.setActionGroup(noise_threshold_group)
|
||||
self.ui.action10NoiseThreshold.setChecked(noise_threshold_setting == "10")
|
||||
self.ui.action100NoiseThreshold.setActionGroup(noise_threshold_group)
|
||||
self.ui.action100NoiseThreshold.setChecked(noise_threshold_setting == "100")
|
||||
|
||||
self.recentFileActionList = []
|
||||
self.create_connects()
|
||||
self.init_recent_file_action_list(settings.read("recentFiles", [], list))
|
||||
|
||||
self.filemodel = FileSystemModel(self)
|
||||
path = QDir.homePath()
|
||||
|
||||
self.filemodel.setIconProvider(FileIconProvider())
|
||||
self.filemodel.setRootPath(path)
|
||||
self.file_proxy_model = FileFilterProxyModel(self)
|
||||
self.file_proxy_model.setSourceModel(self.filemodel)
|
||||
self.ui.fileTree.setModel(self.file_proxy_model)
|
||||
|
||||
self.ui.fileTree.setRootIndex(self.file_proxy_model.mapFromSource(self.filemodel.index(path)))
|
||||
self.ui.fileTree.setToolTip(path)
|
||||
self.ui.fileTree.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)
|
||||
self.ui.fileTree.header().setSectionResizeMode(1, QHeaderView.Stretch)
|
||||
self.ui.fileTree.setFocus()
|
||||
|
||||
self.generator_tab_controller.table_model.cfc = self.compare_frame_controller
|
||||
|
||||
self.ui.actionConvert_Folder_to_Project.setEnabled(False)
|
||||
|
||||
undo_action = self.undo_group.createUndoAction(self)
|
||||
undo_action.setIcon(QIcon.fromTheme("edit-undo"))
|
||||
undo_action.setShortcut(QKeySequence.Undo)
|
||||
self.ui.menuEdit.insertAction(self.ui.actionDecoding, undo_action)
|
||||
|
||||
redo_action = self.undo_group.createRedoAction(self)
|
||||
redo_action.setIcon(QIcon.fromTheme("edit-redo"))
|
||||
redo_action.setShortcut(QKeySequence.Redo)
|
||||
self.ui.menuEdit.insertAction(self.ui.actionDecoding, redo_action)
|
||||
self.ui.menuEdit.insertSeparator(self.ui.actionDecoding)
|
||||
|
||||
self.ui.actionAbout_Qt.setIcon(QIcon(":/qt-project.org/qmessagebox/images/qtlogo-64.png"))
|
||||
|
||||
self.__set_non_project_warning_visibility()
|
||||
|
||||
self.ui.splitter.setSizes([0, 1])
|
||||
self.refresh_main_menu()
|
||||
|
||||
self.apply_default_view(settings.read('default_view', type=int))
|
||||
self.project_save_timer.start(ProjectManager.AUTOSAVE_INTERVAL_MINUTES * 60 * 1000)
|
||||
|
||||
self.ui.actionProject_settings.setVisible(False)
|
||||
self.ui.actionSave_project.setVisible(False)
|
||||
self.ui.actionClose_project.setVisible(False)
|
||||
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
def __set_non_project_warning_visibility(self):
|
||||
show = settings.read("show_non_project_warning", True, bool) and not self.project_manager.project_loaded
|
||||
self.ui.labelNonProjectMode.setVisible(show)
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.actionFullscreen_mode.setShortcut(QKeySequence.FullScreen)
|
||||
self.ui.actionOpen.setShortcut(QKeySequence(QKeySequence.Open))
|
||||
self.ui.actionOpen_directory.setShortcut(QKeySequence("Ctrl+Shift+O"))
|
||||
|
||||
self.ui.menuEdit.aboutToShow.connect(self.on_edit_menu_about_to_show)
|
||||
|
||||
self.ui.actionNew_Project.triggered.connect(self.on_new_project_action_triggered)
|
||||
self.ui.actionNew_Project.setShortcut(QKeySequence.New)
|
||||
self.ui.actionProject_settings.triggered.connect(self.on_project_settings_action_triggered)
|
||||
self.ui.actionSave_project.triggered.connect(self.save_project)
|
||||
self.ui.actionClose_project.triggered.connect(self.close_project)
|
||||
|
||||
self.ui.actionAbout_AutomaticHacker.triggered.connect(self.on_show_about_clicked)
|
||||
self.ui.actionRecord.triggered.connect(self.on_show_record_dialog_action_triggered)
|
||||
|
||||
self.ui.actionFullscreen_mode.triggered.connect(self.on_fullscreen_action_triggered)
|
||||
self.ui.actionSaveAllSignals.triggered.connect(self.signal_tab_controller.save_all)
|
||||
self.ui.actionCloseAllFiles.triggered.connect(self.on_close_all_files_action_triggered)
|
||||
self.ui.actionOpen.triggered.connect(self.on_open_file_action_triggered)
|
||||
self.ui.actionOpen_directory.triggered.connect(self.on_open_directory_action_triggered)
|
||||
self.ui.actionDecoding.triggered.connect(self.on_show_decoding_dialog_triggered)
|
||||
self.ui.actionSpectrum_Analyzer.triggered.connect(self.on_show_spectrum_dialog_action_triggered)
|
||||
self.ui.actionOptions.triggered.connect(self.show_options_dialog_action_triggered)
|
||||
self.ui.actionSniff_protocol.triggered.connect(self.show_proto_sniff_dialog)
|
||||
self.ui.actionAbout_Qt.triggered.connect(QApplication.instance().aboutQt)
|
||||
self.ui.actionSamples_from_csv.triggered.connect(self.on_import_samples_from_csv_action_triggered)
|
||||
self.ui.actionAuto_detect_new_signals.triggered.connect(self.on_auto_detect_new_signals_action_triggered)
|
||||
|
||||
self.ui.actionAutomaticNoiseThreshold.triggered.connect(self.on_action_automatic_noise_threshold_triggered)
|
||||
self.ui.action1NoiseThreshold.triggered.connect(self.on_action_1_noise_threshold_triggered)
|
||||
self.ui.action5NoiseThreshold.triggered.connect(self.on_action_5_noise_threshold_triggered)
|
||||
self.ui.action10NoiseThreshold.triggered.connect(self.on_action_10_noise_threshold_triggered)
|
||||
self.ui.action100NoiseThreshold.triggered.connect(self.on_action_100_noise_threshold_triggered)
|
||||
|
||||
self.ui.btnFileTreeGoUp.clicked.connect(self.on_btn_file_tree_go_up_clicked)
|
||||
self.ui.fileTree.directory_open_wanted.connect(self.project_manager.set_project_folder)
|
||||
|
||||
self.signal_tab_controller.frame_closed.connect(self.close_signal_frame)
|
||||
self.signal_tab_controller.signal_created.connect(self.on_signal_created)
|
||||
self.signal_tab_controller.ui.scrollArea.files_dropped.connect(self.on_files_dropped)
|
||||
self.signal_tab_controller.files_dropped.connect(self.on_files_dropped)
|
||||
self.signal_tab_controller.frame_was_dropped.connect(self.set_frame_numbers)
|
||||
|
||||
self.simulator_tab_controller.open_in_analysis_requested.connect(self.on_simulator_open_in_analysis_requested)
|
||||
self.simulator_tab_controller.rx_file_saved.connect(self.adjust_for_current_file)
|
||||
|
||||
self.compare_frame_controller.show_interpretation_clicked.connect(
|
||||
self.show_protocol_selection_in_interpretation)
|
||||
self.compare_frame_controller.files_dropped.connect(self.on_files_dropped)
|
||||
self.compare_frame_controller.show_decoding_clicked.connect(self.on_show_decoding_dialog_triggered)
|
||||
self.compare_frame_controller.ui.treeViewProtocols.files_dropped_on_group.connect(
|
||||
self.on_files_dropped_on_group)
|
||||
self.compare_frame_controller.participant_changed.connect(self.signal_tab_controller.on_participant_changed)
|
||||
self.compare_frame_controller.ui.treeViewProtocols.close_wanted.connect(self.on_cfc_close_wanted)
|
||||
self.compare_frame_controller.show_config_field_types_triggered.connect(
|
||||
self.on_show_field_types_config_action_triggered)
|
||||
|
||||
self.compare_frame_controller.load_protocol_clicked.connect(self.on_compare_frame_controller_load_protocol_clicked)
|
||||
self.compare_frame_controller.ui.listViewParticipants.doubleClicked.connect(self.on_project_settings_action_triggered)
|
||||
|
||||
self.ui.lnEdtTreeFilter.textChanged.connect(self.on_file_tree_filter_text_changed)
|
||||
|
||||
self.ui.tabWidget.currentChanged.connect(self.on_selected_tab_changed)
|
||||
self.project_save_timer.timeout.connect(self.save_project)
|
||||
|
||||
self.ui.actionConvert_Folder_to_Project.triggered.connect(self.project_manager.convert_folder_to_project)
|
||||
self.project_manager.project_loaded_status_changed.connect(self.on_project_loaded_status_changed)
|
||||
self.project_manager.project_updated.connect(self.on_project_updated)
|
||||
|
||||
self.ui.textEditProjectDescription.textChanged.connect(self.on_text_edit_project_description_text_changed)
|
||||
self.ui.tabWidget_Project.tabBarDoubleClicked.connect(self.on_project_tab_bar_double_clicked)
|
||||
|
||||
self.ui.listViewParticipants.doubleClicked.connect(self.on_project_settings_action_triggered)
|
||||
|
||||
self.ui.actionShowFileTree.triggered.connect(self.on_action_show_filetree_triggered)
|
||||
self.ui.actionShowFileTree.setShortcut(QKeySequence("F10"))
|
||||
|
||||
self.ui.labelNonProjectMode.linkActivated.connect(self.on_label_non_project_mode_link_activated)
|
||||
|
||||
self.ui.menuFile.addSeparator()
|
||||
for i in range(settings.MAX_RECENT_FILE_NR):
|
||||
recent_file_action = QAction(self)
|
||||
recent_file_action.setVisible(False)
|
||||
recent_file_action.triggered.connect(self.on_open_recent_action_triggered)
|
||||
self.recentFileActionList.append(recent_file_action)
|
||||
self.ui.menuFile.addAction(self.recentFileActionList[i])
|
||||
|
||||
def add_plain_bits_from_txt(self, filename: str):
|
||||
with open(filename) as f:
|
||||
protocol = ProtocolAnalyzer.get_protocol_from_string(f.readlines())
|
||||
|
||||
protocol.filename = filename
|
||||
protocol.name = util.get_name_from_filename(filename)
|
||||
|
||||
self.compare_frame_controller.add_protocol(protocol)
|
||||
self.compare_frame_controller.refresh()
|
||||
self.__add_empty_frame_for_filename(protocol, filename)
|
||||
|
||||
def __add_empty_frame_for_filename(self, protocol: ProtocolAnalyzer, filename: str):
|
||||
sf = self.signal_tab_controller.add_empty_frame(filename, protocol)
|
||||
self.signal_protocol_dict[sf] = protocol
|
||||
self.set_frame_numbers()
|
||||
self.file_proxy_model.open_files.add(filename)
|
||||
|
||||
def add_protocol_file(self, filename):
|
||||
proto = self.compare_frame_controller.add_protocol_from_file(filename)
|
||||
if proto:
|
||||
self.__add_empty_frame_for_filename(proto, filename)
|
||||
self.ui.tabWidget.setCurrentWidget(self.ui.tab_protocol)
|
||||
|
||||
def add_fuzz_profile(self, filename):
|
||||
self.ui.tabWidget.setCurrentIndex(2)
|
||||
self.generator_tab_controller.load_from_file(filename)
|
||||
|
||||
def add_simulator_profile(self, filename):
|
||||
self.ui.tabWidget.setCurrentIndex(3)
|
||||
self.simulator_tab_controller.load_simulator_file(filename)
|
||||
|
||||
def add_signalfile(self, filename: str, group_id=0, enforce_sample_rate=None):
|
||||
if not os.path.exists(filename):
|
||||
QMessageBox.critical(self, self.tr("File not Found"),
|
||||
self.tr("The file {0} could not be found. Was it moved or renamed?").format(
|
||||
filename))
|
||||
return
|
||||
|
||||
sig_name = os.path.splitext(os.path.basename(filename))[0]
|
||||
|
||||
# Use default sample rate for signal
|
||||
# Sample rate will be overridden in case of a project later
|
||||
if enforce_sample_rate is not None:
|
||||
sample_rate = enforce_sample_rate
|
||||
else:
|
||||
sample_rate = self.project_manager.device_conf["sample_rate"]
|
||||
|
||||
signal = Signal(filename, sig_name, sample_rate=sample_rate)
|
||||
|
||||
self.file_proxy_model.open_files.add(filename)
|
||||
self.add_signal(signal, group_id)
|
||||
|
||||
def add_signal(self, signal, group_id=0, index=-1):
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
pa = ProtocolAnalyzer(signal)
|
||||
sig_frame = self.signal_tab_controller.add_signal_frame(pa, index=index)
|
||||
pa = self.compare_frame_controller.add_protocol(pa, group_id)
|
||||
|
||||
signal.blockSignals(True)
|
||||
has_entry = self.project_manager.read_project_file_for_signal(signal)
|
||||
|
||||
if self.ui.actionAuto_detect_new_signals.isChecked() and not has_entry and not signal.changed:
|
||||
sig_frame.ui.stackedWidget.setCurrentWidget(sig_frame.ui.pageLoading)
|
||||
qApp.processEvents()
|
||||
if not signal.already_demodulated:
|
||||
signal.auto_detect(detect_modulation=True, detect_noise=False)
|
||||
sig_frame.ui.stackedWidget.setCurrentWidget(sig_frame.ui.pageSignal)
|
||||
|
||||
signal.blockSignals(False)
|
||||
|
||||
self.signal_protocol_dict[sig_frame] = pa
|
||||
|
||||
sig_frame.refresh_signal(draw_full_signal=True)
|
||||
sig_frame.refresh_signal_information(block=True)
|
||||
|
||||
qApp.processEvents()
|
||||
sig_frame.show_protocol(refresh=True)
|
||||
|
||||
if self.project_manager.read_participants_for_signal(signal, pa.messages):
|
||||
sig_frame.ui.gvSignal.redraw_view()
|
||||
|
||||
sig_frame.ui.gvSignal.auto_fit_view()
|
||||
self.set_frame_numbers()
|
||||
|
||||
self.compare_frame_controller.filter_search_results()
|
||||
self.refresh_main_menu()
|
||||
self.unsetCursor()
|
||||
|
||||
def close_protocol(self, protocol):
|
||||
self.compare_frame_controller.remove_protocol(protocol)
|
||||
# Needs to be removed in generator also, otherwise program crashes,
|
||||
# if item from tree in generator is selected and corresponding signal is closed
|
||||
self.generator_tab_controller.tree_model.remove_protocol(protocol)
|
||||
protocol.eliminate()
|
||||
|
||||
def close_signal_frame(self, signal_frame: SignalFrame):
|
||||
try:
|
||||
self.project_manager.write_signal_information_to_project_file(signal_frame.signal)
|
||||
try:
|
||||
proto = self.signal_protocol_dict[signal_frame]
|
||||
except KeyError:
|
||||
proto = None
|
||||
|
||||
if proto is not None:
|
||||
self.close_protocol(proto)
|
||||
del self.signal_protocol_dict[signal_frame]
|
||||
|
||||
if self.signal_tab_controller.ui.scrlAreaSignals.minimumHeight() > signal_frame.height():
|
||||
self.signal_tab_controller.ui.scrlAreaSignals.setMinimumHeight(
|
||||
self.signal_tab_controller.ui.scrlAreaSignals.minimumHeight() - signal_frame.height())
|
||||
|
||||
if signal_frame.signal is not None:
|
||||
# Non-Empty Frame (when a signal and not a protocol is opened)
|
||||
self.file_proxy_model.open_files.discard(signal_frame.signal.filename)
|
||||
|
||||
signal_frame.eliminate()
|
||||
|
||||
self.compare_frame_controller.ui.treeViewProtocols.expandAll()
|
||||
self.set_frame_numbers()
|
||||
self.refresh_main_menu()
|
||||
except Exception as e:
|
||||
Errors.exception(e)
|
||||
self.unsetCursor()
|
||||
|
||||
def add_files(self, filepaths, group_id=0, enforce_sample_rate=None):
|
||||
num_files = len(filepaths)
|
||||
if num_files == 0:
|
||||
return
|
||||
|
||||
for i, filename in enumerate(filepaths):
|
||||
if not os.path.exists(filename):
|
||||
continue
|
||||
|
||||
if os.path.isdir(filename):
|
||||
for f in self.signal_tab_controller.signal_frames:
|
||||
self.close_signal_frame(f)
|
||||
|
||||
FileOperator.RECENT_PATH = filename
|
||||
self.project_manager.set_project_folder(filename)
|
||||
return
|
||||
|
||||
FileOperator.RECENT_PATH = os.path.split(filename)[0]
|
||||
|
||||
if filename.endswith(".complex"):
|
||||
self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate)
|
||||
elif filename.endswith(".coco"):
|
||||
self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate)
|
||||
elif filename.endswith(".proto") or filename.endswith(".proto.xml") or filename.endswith(".bin"):
|
||||
self.add_protocol_file(filename)
|
||||
elif filename.endswith(".wav"):
|
||||
try:
|
||||
import wave
|
||||
w = wave.open(filename)
|
||||
w.close()
|
||||
except wave.Error as e:
|
||||
Errors.generic_error("Unsupported WAV type", "Only uncompressed WAVs (PCM) are supported.", str(e))
|
||||
continue
|
||||
self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate)
|
||||
elif filename.endswith(".fuzz") or filename.endswith(".fuzz.xml"):
|
||||
self.add_fuzz_profile(filename)
|
||||
elif filename.endswith(".sim") or filename.endswith(".sim.xml"):
|
||||
self.add_simulator_profile(filename)
|
||||
elif filename.endswith(".txt"):
|
||||
self.add_plain_bits_from_txt(filename)
|
||||
elif filename.endswith(".csv"):
|
||||
self.__import_csv(filename, group_id)
|
||||
continue
|
||||
elif os.path.basename(filename) == settings.PROJECT_FILE:
|
||||
self.project_manager.set_project_folder(os.path.split(filename)[0])
|
||||
else:
|
||||
self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate)
|
||||
|
||||
if self.project_manager.project_file is None:
|
||||
self.adjust_for_current_file(filename)
|
||||
|
||||
self.refresh_main_menu()
|
||||
|
||||
def set_frame_numbers(self):
|
||||
self.signal_tab_controller.set_frame_numbers()
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
self.save_project()
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
def close_all_files(self):
|
||||
self.signal_tab_controller.close_all()
|
||||
self.compare_frame_controller.reset()
|
||||
self.generator_tab_controller.table_model.protocol.clear()
|
||||
self.generator_tab_controller.refresh_tree()
|
||||
self.generator_tab_controller.refresh_table()
|
||||
self.generator_tab_controller.refresh_label_list()
|
||||
|
||||
self.signal_tab_controller.signal_undo_stack.clear()
|
||||
self.compare_frame_controller.protocol_undo_stack.clear()
|
||||
self.generator_tab_controller.generator_undo_stack.clear()
|
||||
|
||||
self.simulator_tab_controller.close_all()
|
||||
|
||||
def show_options_dialog_specific_tab(self, tab_index: int):
|
||||
op = OptionsDialog(self.plugin_manager.installed_plugins, parent=self)
|
||||
op.values_changed.connect(self.on_options_changed)
|
||||
op.ui.tabWidget.setCurrentIndex(tab_index)
|
||||
op.show()
|
||||
|
||||
def refresh_main_menu(self):
|
||||
enable = len(self.signal_protocol_dict) > 0
|
||||
self.ui.actionSaveAllSignals.setEnabled(enable)
|
||||
self.ui.actionCloseAllFiles.setEnabled(enable)
|
||||
|
||||
def apply_default_view(self, view_index: int):
|
||||
self.compare_frame_controller.ui.cbProtoView.setCurrentIndex(view_index)
|
||||
self.generator_tab_controller.ui.cbViewType.setCurrentIndex(view_index)
|
||||
self.simulator_tab_controller.ui.cbViewType.setCurrentIndex(view_index)
|
||||
for sig_frame in self.signal_tab_controller.signal_frames:
|
||||
sig_frame.ui.cbProtoView.setCurrentIndex(view_index)
|
||||
|
||||
def show_project_settings(self):
|
||||
pdc = ProjectDialog(new_project=False, project_manager=self.project_manager, parent=self)
|
||||
pdc.finished.connect(self.on_project_dialog_finished)
|
||||
pdc.show()
|
||||
|
||||
def collapse_project_tab_bar(self):
|
||||
self.ui.tabParticipants.hide()
|
||||
self.ui.tabDescription.hide()
|
||||
self.ui.tabWidget_Project.setMaximumHeight(self.ui.tabWidget_Project.tabBar().height())
|
||||
|
||||
def expand_project_tab_bar(self):
|
||||
self.ui.tabDescription.show()
|
||||
self.ui.tabParticipants.show()
|
||||
self.ui.tabWidget_Project.setMaximumHeight(9000)
|
||||
|
||||
def save_project(self):
|
||||
self.project_manager.save_project(simulator_config=self.simulator_tab_controller.simulator_config)
|
||||
|
||||
def close_project(self):
|
||||
self.save_project()
|
||||
self.close_all_files()
|
||||
self.compare_frame_controller.proto_analyzer.message_types.clear()
|
||||
self.compare_frame_controller.active_message_type.clear()
|
||||
self.compare_frame_controller.updateUI()
|
||||
self.project_manager.participants.clear()
|
||||
self.participant_legend_model.update()
|
||||
|
||||
self.filemodel.setRootPath(QDir.homePath())
|
||||
self.ui.fileTree.setRootIndex(self.file_proxy_model.mapFromSource(self.filemodel.index(QDir.homePath())))
|
||||
self.hide_file_tree()
|
||||
|
||||
self.project_manager.project_path = ""
|
||||
self.project_manager.project_file = None
|
||||
|
||||
@pyqtSlot()
|
||||
def on_project_tab_bar_double_clicked(self):
|
||||
if self.ui.tabParticipants.isVisible():
|
||||
self.collapse_project_tab_bar()
|
||||
else:
|
||||
self.expand_project_tab_bar()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_project_updated(self):
|
||||
self.participant_legend_model.update()
|
||||
self.compare_frame_controller.refresh()
|
||||
self.ui.textEditProjectDescription.setText(self.project_manager.description)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_fullscreen_action_triggered(self):
|
||||
if self.ui.actionFullscreen_mode.isChecked():
|
||||
self.showFullScreen()
|
||||
else:
|
||||
self.showMaximized()
|
||||
|
||||
def adjust_for_current_file(self, file_path):
|
||||
if file_path is None:
|
||||
return
|
||||
|
||||
if file_path in FileOperator.archives.keys():
|
||||
file_path = copy.copy(FileOperator.archives[file_path])
|
||||
|
||||
recent_file_paths = settings.read("recentFiles", [], list)
|
||||
recent_file_paths = [] if recent_file_paths is None else recent_file_paths # check None for OSX
|
||||
recent_file_paths = [p for p in recent_file_paths if p != file_path and p is not None and os.path.exists(p)]
|
||||
recent_file_paths.insert(0, file_path)
|
||||
recent_file_paths = recent_file_paths[:settings.MAX_RECENT_FILE_NR]
|
||||
|
||||
self.init_recent_file_action_list(recent_file_paths)
|
||||
|
||||
settings.write("recentFiles", recent_file_paths)
|
||||
|
||||
def init_recent_file_action_list(self, recent_file_paths: list):
|
||||
for i in range(len(self.recentFileActionList)):
|
||||
self.recentFileActionList[i].setVisible(False)
|
||||
|
||||
if recent_file_paths is None:
|
||||
return
|
||||
|
||||
for i, file_path in enumerate(recent_file_paths):
|
||||
if os.path.isfile(file_path):
|
||||
display_text = os.path.basename(file_path)
|
||||
self.recentFileActionList[i].setIcon(QIcon())
|
||||
elif os.path.isdir(file_path):
|
||||
head, tail = os.path.split(file_path)
|
||||
display_text = tail
|
||||
head, tail = os.path.split(head)
|
||||
if tail:
|
||||
display_text = tail + "/" + display_text
|
||||
|
||||
self.recentFileActionList[i].setIcon(QIcon.fromTheme("folder"))
|
||||
else:
|
||||
continue
|
||||
|
||||
self.recentFileActionList[i].setText(display_text)
|
||||
self.recentFileActionList[i].setData(file_path)
|
||||
self.recentFileActionList[i].setVisible(True)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_show_field_types_config_action_triggered(self):
|
||||
self.show_options_dialog_specific_tab(tab_index=2)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_open_recent_action_triggered(self):
|
||||
action = self.sender()
|
||||
try:
|
||||
if os.path.isdir(action.data()):
|
||||
self.project_manager.set_project_folder(action.data())
|
||||
elif os.path.isfile(action.data()):
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
self.add_files(FileOperator.uncompress_archives([action.data()], QDir.tempPath()))
|
||||
self.unsetCursor()
|
||||
except Exception as e:
|
||||
Errors.exception(e)
|
||||
self.unsetCursor()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_show_about_clicked(self):
|
||||
descr = "<b><h2>Universal Radio Hacker</h2></b>Version: {0}<br />" \
|
||||
"GitHub: <a href='https://github.com/jopohl/urh'>https://github.com/jopohl/urh</a><br /><br />" \
|
||||
"Creators:<i><ul><li>" \
|
||||
"Johannes Pohl <<a href='mailto:joahnnes.pohl90@gmail.com'>johannes.pohl90@gmail.com</a>></li>" \
|
||||
"<li>Andreas Noack <<a href='mailto:andreas.noack@hochschule-stralsund.de'>andreas.noack@hochschule-stralsund.de</a>></li>" \
|
||||
"</ul></i>".format(version.VERSION)
|
||||
|
||||
QMessageBox.about(self, self.tr("About"), self.tr(descr))
|
||||
|
||||
@pyqtSlot(int, int, int, int)
|
||||
def show_protocol_selection_in_interpretation(self, start_message, start, end_message, end):
|
||||
try:
|
||||
cfc = self.compare_frame_controller
|
||||
msg_total = 0
|
||||
last_sig_frame = None
|
||||
for protocol in cfc.protocol_list:
|
||||
if not protocol.show:
|
||||
continue
|
||||
n = protocol.num_messages
|
||||
view_type = cfc.ui.cbProtoView.currentIndex()
|
||||
messages = [i - msg_total for i in range(msg_total, msg_total + n) if start_message <= i <= end_message]
|
||||
if len(messages) > 0:
|
||||
try:
|
||||
signal_frame = next((sf for sf, pf in self.signal_protocol_dict.items() if pf == protocol))
|
||||
except StopIteration:
|
||||
QMessageBox.critical(self, self.tr("Error"),
|
||||
self.tr("Could not find corresponding signal frame."))
|
||||
return
|
||||
signal_frame.set_roi_from_protocol_analysis(min(messages), start, max(messages), end + 1, view_type)
|
||||
last_sig_frame = signal_frame
|
||||
msg_total += n
|
||||
focus_frame = last_sig_frame
|
||||
if last_sig_frame is not None:
|
||||
self.signal_tab_controller.ui.scrollArea.ensureWidgetVisible(last_sig_frame, 0, 0)
|
||||
|
||||
QApplication.instance().processEvents()
|
||||
self.ui.tabWidget.setCurrentIndex(0)
|
||||
if focus_frame is not None:
|
||||
focus_frame.ui.txtEdProto.setFocus()
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_file_tree_filter_text_changed(self, text: str):
|
||||
if len(text) > 0:
|
||||
self.filemodel.setNameFilters(["*" + text + "*"])
|
||||
else:
|
||||
self.filemodel.setNameFilters(["*"])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_show_decoding_dialog_triggered(self):
|
||||
signals = [sf.signal for sf in self.signal_tab_controller.signal_frames]
|
||||
decoding_controller = DecoderDialog(
|
||||
self.compare_frame_controller.decodings, signals,
|
||||
self.project_manager, parent=self)
|
||||
decoding_controller.finished.connect(self.update_decodings)
|
||||
decoding_controller.show()
|
||||
decoding_controller.decoder_update()
|
||||
|
||||
@pyqtSlot()
|
||||
def update_decodings(self):
|
||||
self.project_manager.load_decodings()
|
||||
self.compare_frame_controller.fill_decoding_combobox()
|
||||
self.compare_frame_controller.refresh_existing_encodings()
|
||||
|
||||
self.generator_tab_controller.refresh_existing_encodings(self.compare_frame_controller.decodings)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_selected_tab_changed(self, index: int):
|
||||
if index == 0:
|
||||
self.undo_group.setActiveStack(self.signal_tab_controller.signal_undo_stack)
|
||||
elif index == 1:
|
||||
self.undo_group.setActiveStack(self.compare_frame_controller.protocol_undo_stack)
|
||||
self.compare_frame_controller.ui.tblViewProtocol.resize_columns()
|
||||
self.compare_frame_controller.ui.tblViewProtocol.resize_vertical_header()
|
||||
h = max(self.compare_frame_controller.ui.btnSaveProto.height(),
|
||||
self.generator_tab_controller.ui.btnSave.height())
|
||||
self.compare_frame_controller.ui.btnSaveProto.setMinimumHeight(h)
|
||||
|
||||
th = self.compare_frame_controller.ui.tabWidget.tabBar().height()
|
||||
for i in range(self.compare_frame_controller.ui.tabWidget.count()):
|
||||
self.compare_frame_controller.ui.tabWidget.widget(i).layout().setContentsMargins(0, 7 + h - th, 0, 0)
|
||||
|
||||
elif index == 2:
|
||||
self.undo_group.setActiveStack(self.generator_tab_controller.generator_undo_stack)
|
||||
h = max(self.compare_frame_controller.ui.btnSaveProto.height(),
|
||||
self.generator_tab_controller.ui.btnSave.height())
|
||||
self.generator_tab_controller.ui.btnSave.setMinimumHeight(h)
|
||||
th = self.generator_tab_controller.ui.tabWidget.tabBar().height()
|
||||
for i in range(self.generator_tab_controller.ui.tabWidget.count()):
|
||||
self.generator_tab_controller.ui.tabWidget.widget(i).layout().setContentsMargins(0, 7 + h - th, 0, 0)
|
||||
# Modulators may got changed from Simulator Dialog
|
||||
self.generator_tab_controller.refresh_modulators()
|
||||
# Signals may got reordered in analysis
|
||||
self.generator_tab_controller.tree_model.update()
|
||||
self.generator_tab_controller.ui.treeProtocols.expandAll()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_show_record_dialog_action_triggered(self):
|
||||
pm = self.project_manager
|
||||
try:
|
||||
r = ReceiveDialog(pm, parent=self)
|
||||
except OSError as e:
|
||||
logger.error(repr(e))
|
||||
return
|
||||
|
||||
if r.has_empty_device_list:
|
||||
Errors.no_device()
|
||||
r.close()
|
||||
return
|
||||
|
||||
r.device_parameters_changed.connect(pm.set_device_parameters)
|
||||
r.files_recorded.connect(self.on_signals_recorded)
|
||||
r.show()
|
||||
|
||||
def create_protocol_sniff_dialog(self, testing_mode=False):
|
||||
pm = self.project_manager
|
||||
signal = next((proto.signal for proto in self.compare_frame_controller.protocol_list), None)
|
||||
signals = [f.signal for f in self.signal_tab_controller.signal_frames if f.signal]
|
||||
|
||||
psd = ProtocolSniffDialog(project_manager=pm, signal=signal, signals=signals,
|
||||
testing_mode=testing_mode, parent=self)
|
||||
|
||||
if psd.has_empty_device_list:
|
||||
Errors.no_device()
|
||||
psd.close()
|
||||
return None
|
||||
else:
|
||||
psd.device_parameters_changed.connect(pm.set_device_parameters)
|
||||
psd.protocol_accepted.connect(self.compare_frame_controller.add_sniffed_protocol_messages)
|
||||
return psd
|
||||
|
||||
@pyqtSlot()
|
||||
def show_proto_sniff_dialog(self):
|
||||
psd = self.create_protocol_sniff_dialog()
|
||||
if psd:
|
||||
psd.show()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_show_spectrum_dialog_action_triggered(self):
|
||||
pm = self.project_manager
|
||||
r = SpectrumDialogController(pm, parent=self)
|
||||
if r.has_empty_device_list:
|
||||
Errors.no_device()
|
||||
r.close()
|
||||
return
|
||||
|
||||
r.device_parameters_changed.connect(pm.set_device_parameters)
|
||||
r.show()
|
||||
|
||||
@pyqtSlot(list, float)
|
||||
def on_signals_recorded(self, file_names: list, sample_rate: float):
|
||||
QApplication.instance().setOverrideCursor(Qt.WaitCursor)
|
||||
for filename in file_names:
|
||||
self.add_signalfile(filename, enforce_sample_rate=sample_rate)
|
||||
QApplication.instance().restoreOverrideCursor()
|
||||
|
||||
@pyqtSlot()
|
||||
def show_options_dialog_action_triggered(self):
|
||||
self.show_options_dialog_specific_tab(tab_index=4)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_new_project_action_triggered(self):
|
||||
pdc = ProjectDialog(parent=self)
|
||||
pdc.finished.connect(self.on_project_dialog_finished)
|
||||
pdc.show()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_project_settings_action_triggered(self):
|
||||
self.show_project_settings()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_edit_menu_about_to_show(self):
|
||||
self.ui.actionShowFileTree.setChecked(self.ui.splitter.sizes()[0] > 0)
|
||||
|
||||
def hide_file_tree(self):
|
||||
self.ui.splitter.setSizes([0, 1])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_action_show_filetree_triggered(self):
|
||||
if self.ui.splitter.sizes()[0] > 0:
|
||||
self.hide_file_tree()
|
||||
else:
|
||||
self.ui.splitter.setSizes([1, 1])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_project_dialog_finished(self):
|
||||
if self.sender().committed:
|
||||
if self.sender().new_project:
|
||||
self.close_project()
|
||||
self.project_manager.from_dialog(self.sender())
|
||||
else:
|
||||
self.project_manager.project_updated.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_open_file_action_triggered(self):
|
||||
self.show_open_dialog(directory=False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_open_directory_action_triggered(self):
|
||||
self.show_open_dialog(directory=True)
|
||||
|
||||
def show_open_dialog(self, directory=False):
|
||||
dialog = FileOperator.get_open_dialog(directory_mode=directory, parent=self, name_filter="full")
|
||||
if dialog.exec_():
|
||||
try:
|
||||
file_names = dialog.selectedFiles()
|
||||
folders = [folder for folder in file_names if os.path.isdir(folder)]
|
||||
|
||||
if len(folders) > 0:
|
||||
folder = folders[0]
|
||||
for f in self.signal_tab_controller.signal_frames:
|
||||
self.close_signal_frame(f)
|
||||
|
||||
self.project_manager.set_project_folder(folder)
|
||||
else:
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
file_names = FileOperator.uncompress_archives(file_names, QDir.tempPath())
|
||||
self.add_files(file_names)
|
||||
self.unsetCursor()
|
||||
except Exception as e:
|
||||
Errors.exception(e)
|
||||
self.unsetCursor()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_close_all_files_action_triggered(self):
|
||||
self.close_all_files()
|
||||
|
||||
@pyqtSlot(list)
|
||||
def on_files_dropped(self, files):
|
||||
"""
|
||||
:type files: list of QtCore.QUrl
|
||||
"""
|
||||
self.__add_urls_to_group(files, group_id=0)
|
||||
|
||||
@pyqtSlot(list, int)
|
||||
def on_files_dropped_on_group(self, files, group_id: int):
|
||||
"""
|
||||
:param group_id:
|
||||
:type files: list of QtCore.QUrl
|
||||
"""
|
||||
self.__add_urls_to_group(files, group_id=group_id)
|
||||
|
||||
def __add_urls_to_group(self, file_urls, group_id=0):
|
||||
local_files = [file_url.toLocalFile() for file_url in file_urls if file_url.isLocalFile()]
|
||||
if len(local_files) > 0:
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
self.add_files(FileOperator.uncompress_archives(local_files, QDir.tempPath()), group_id=group_id)
|
||||
self.unsetCursor()
|
||||
|
||||
@pyqtSlot(list)
|
||||
def on_cfc_close_wanted(self, protocols: list):
|
||||
frame_protos = {sframe: protocol for sframe, protocol in self.signal_protocol_dict.items() if
|
||||
protocol in protocols}
|
||||
|
||||
for frame in frame_protos:
|
||||
self.close_signal_frame(frame)
|
||||
|
||||
for proto in (proto for proto in protocols if proto not in frame_protos.values()):
|
||||
# close protocols without associated signal frame
|
||||
self.close_protocol(proto)
|
||||
|
||||
@pyqtSlot(dict)
|
||||
def on_options_changed(self, changed_options: dict):
|
||||
refresh_protocol_needed = "show_pause_as_time" in changed_options
|
||||
|
||||
if refresh_protocol_needed:
|
||||
for sf in self.signal_tab_controller.signal_frames:
|
||||
sf.refresh_protocol()
|
||||
|
||||
self.project_manager.reload_field_types()
|
||||
|
||||
self.compare_frame_controller.refresh_field_types_for_labels()
|
||||
self.compare_frame_controller.set_shown_protocols()
|
||||
self.generator_tab_controller.set_network_sdr_send_button_visibility()
|
||||
self.generator_tab_controller.init_rfcat_plugin()
|
||||
self.generator_tab_controller.set_modulation_profile_status()
|
||||
self.simulator_tab_controller.refresh_field_types_for_labels()
|
||||
|
||||
if "num_sending_repeats" in changed_options:
|
||||
self.project_manager.device_conf["num_sending_repeats"] = changed_options["num_sending_repeats"]
|
||||
|
||||
if "default_view" in changed_options:
|
||||
self.apply_default_view(int(changed_options["default_view"]))
|
||||
|
||||
if "spectrogram_colormap" in changed_options:
|
||||
self.signal_tab_controller.redraw_spectrograms()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_text_edit_project_description_text_changed(self):
|
||||
self.project_manager.description = self.ui.textEditProjectDescription.toPlainText()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_file_tree_go_up_clicked(self):
|
||||
cur_dir = self.filemodel.rootDirectory()
|
||||
if cur_dir.cdUp():
|
||||
path = cur_dir.path()
|
||||
self.filemodel.setRootPath(path)
|
||||
self.ui.fileTree.setRootIndex(self.file_proxy_model.mapFromSource(self.filemodel.index(path)))
|
||||
|
||||
@pyqtSlot(int, Signal)
|
||||
def on_signal_created(self, index: int, signal: Signal):
|
||||
self.add_signal(signal, index=index)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_cancel_triggered(self):
|
||||
for signal_frame in self.signal_tab_controller.signal_frames:
|
||||
signal_frame.cancel_filtering()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_import_samples_from_csv_action_triggered(self):
|
||||
self.__import_csv(file_name="")
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_auto_detect_new_signals_action_triggered(self, checked: bool):
|
||||
settings.write("auto_detect_new_signals", bool(checked))
|
||||
|
||||
def __import_csv(self, file_name, group_id=0):
|
||||
def on_data_imported(complex_file, sample_rate):
|
||||
sample_rate = None if sample_rate == 0 else sample_rate
|
||||
self.add_files([complex_file], group_id=group_id, enforce_sample_rate=sample_rate)
|
||||
|
||||
dialog = CSVImportDialog(file_name, parent=self)
|
||||
dialog.data_imported.connect(on_data_imported)
|
||||
dialog.exec_()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_label_non_project_mode_link_activated(self, link: str):
|
||||
if link == "dont_show_non_project_again":
|
||||
self.ui.labelNonProjectMode.hide()
|
||||
settings.write("show_non_project_warning", False)
|
||||
elif link == "open_new_project_dialog":
|
||||
self.on_new_project_action_triggered()
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_project_loaded_status_changed(self, project_loaded: bool):
|
||||
self.ui.actionProject_settings.setVisible(project_loaded)
|
||||
self.ui.actionSave_project.setVisible(project_loaded)
|
||||
self.ui.actionClose_project.setVisible(project_loaded)
|
||||
self.ui.actionConvert_Folder_to_Project.setDisabled(project_loaded)
|
||||
self.__set_non_project_warning_visibility()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_compare_frame_controller_load_protocol_clicked(self):
|
||||
dialog = FileOperator.get_open_dialog(directory_mode=False, parent=self, name_filter="proto")
|
||||
if dialog.exec_():
|
||||
for filename in dialog.selectedFiles():
|
||||
self.add_protocol_file(filename)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_simulator_open_in_analysis_requested(self, text: str):
|
||||
protocol = ProtocolAnalyzer.get_protocol_from_string(text.split("\n"))
|
||||
protocol.name = "Transcript"
|
||||
|
||||
self.ui.tabWidget.setCurrentIndex(1)
|
||||
self.compare_frame_controller.add_protocol(protocol)
|
||||
self.compare_frame_controller.refresh()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_action_automatic_noise_threshold_triggered(self):
|
||||
settings.write("default_noise_threshold", "automatic")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_action_1_noise_threshold_triggered(self):
|
||||
settings.write("default_noise_threshold", "1")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_action_5_noise_threshold_triggered(self):
|
||||
settings.write("default_noise_threshold", "5")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_action_10_noise_threshold_triggered(self):
|
||||
settings.write("default_noise_threshold", "10")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_action_100_noise_threshold_triggered(self):
|
||||
settings.write("default_noise_threshold", "100")
|
||||
@@ -0,0 +1,249 @@
|
||||
from PyQt5.QtCore import QPoint, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtWidgets import QWidget, QSizePolicy, QUndoStack, QCheckBox, QMessageBox
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.widgets.SignalFrame import SignalFrame
|
||||
from urh.signalprocessing.Signal import Signal
|
||||
from urh.ui.ui_tab_interpretation import Ui_Interpretation
|
||||
from urh.util import util
|
||||
|
||||
|
||||
class SignalTabController(QWidget):
|
||||
frame_closed = pyqtSignal(SignalFrame)
|
||||
not_show_again_changed = pyqtSignal()
|
||||
signal_created = pyqtSignal(int, Signal)
|
||||
files_dropped = pyqtSignal(list)
|
||||
frame_was_dropped = pyqtSignal(int, int)
|
||||
|
||||
@property
|
||||
def num_frames(self):
|
||||
return len(self.signal_frames)
|
||||
|
||||
@property
|
||||
def signal_frames(self):
|
||||
"""
|
||||
|
||||
:rtype: list of SignalFrame
|
||||
"""
|
||||
splitter = self.ui.splitter
|
||||
return [splitter.widget(i) for i in range(splitter.count())
|
||||
if isinstance(splitter.widget(i), SignalFrame)]
|
||||
|
||||
@property
|
||||
def signal_undo_stack(self):
|
||||
return self.undo_stack
|
||||
|
||||
def __init__(self, project_manager, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_Interpretation()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
util.set_splitter_stylesheet(self.ui.splitter)
|
||||
|
||||
self.ui.placeholderLabel.setVisible(False)
|
||||
self.getting_started_status = None
|
||||
self.__set_getting_started_status(True)
|
||||
|
||||
self.undo_stack = QUndoStack()
|
||||
self.project_manager = project_manager
|
||||
|
||||
self.drag_pos = None
|
||||
|
||||
def on_files_dropped(self, files):
|
||||
self.files_dropped.emit(files)
|
||||
|
||||
def close_frame(self, frame:SignalFrame):
|
||||
self.frame_closed.emit(frame)
|
||||
|
||||
def add_signal_frame(self, proto_analyzer, index=-1):
|
||||
self.__set_getting_started_status(False)
|
||||
sig_frame = SignalFrame(proto_analyzer, self.undo_stack, self.project_manager, parent=self)
|
||||
sframes = self.signal_frames
|
||||
|
||||
if len(proto_analyzer.signal.filename) == 0:
|
||||
# new signal from "create signal from selection"
|
||||
sig_frame.ui.btnSaveSignal.show()
|
||||
|
||||
self.__create_connects_for_signal_frame(signal_frame=sig_frame)
|
||||
sig_frame.signal_created.connect(self.emit_signal_created)
|
||||
sig_frame.not_show_again_changed.connect(self.not_show_again_changed.emit)
|
||||
sig_frame.ui.lineEditSignalName.setToolTip(self.tr("Sourcefile: ") + proto_analyzer.signal.filename)
|
||||
sig_frame.apply_to_all_clicked.connect(self.on_apply_to_all_clicked)
|
||||
|
||||
prev_signal_frame = sframes[-1] if len(sframes) > 0 else None
|
||||
if prev_signal_frame is not None and hasattr(prev_signal_frame, "ui"):
|
||||
sig_frame.ui.cbProtoView.setCurrentIndex(prev_signal_frame.ui.cbProtoView.currentIndex())
|
||||
|
||||
sig_frame.blockSignals(True)
|
||||
|
||||
index = self.num_frames if index == -1 else index
|
||||
self.ui.splitter.insertWidget(index, sig_frame)
|
||||
sig_frame.blockSignals(False)
|
||||
|
||||
default_view = settings.read('default_view', 0, int)
|
||||
sig_frame.ui.cbProtoView.setCurrentIndex(default_view)
|
||||
|
||||
return sig_frame
|
||||
|
||||
def add_empty_frame(self, filename: str, proto):
|
||||
self.__set_getting_started_status(False)
|
||||
sig_frame = SignalFrame(proto_analyzer=proto, undo_stack=self.undo_stack, project_manager=self.project_manager,
|
||||
parent=self)
|
||||
|
||||
sig_frame.ui.lineEditSignalName.setText(filename)
|
||||
self.__create_connects_for_signal_frame(signal_frame=sig_frame)
|
||||
|
||||
self.ui.splitter.insertWidget(self.num_frames, sig_frame)
|
||||
|
||||
return sig_frame
|
||||
|
||||
def __set_getting_started_status(self, getting_started: bool):
|
||||
if getting_started == self.getting_started_status:
|
||||
return
|
||||
|
||||
self.getting_started_status = getting_started
|
||||
self.ui.labelGettingStarted.setVisible(getting_started)
|
||||
|
||||
if not getting_started:
|
||||
w = QWidget()
|
||||
w.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
|
||||
self.ui.splitter.addWidget(w)
|
||||
|
||||
def __create_connects_for_signal_frame(self, signal_frame: SignalFrame):
|
||||
signal_frame.hold_shift = settings.read('hold_shift_to_drag', True, type=bool)
|
||||
signal_frame.drag_started.connect(self.frame_dragged)
|
||||
signal_frame.frame_dropped.connect(self.frame_dropped)
|
||||
signal_frame.files_dropped.connect(self.on_files_dropped)
|
||||
signal_frame.closed.connect(self.close_frame)
|
||||
|
||||
def set_frame_numbers(self):
|
||||
for i, f in enumerate(self.signal_frames):
|
||||
f.ui.lSignalNr.setText("{0:d}:".format(i + 1))
|
||||
|
||||
@pyqtSlot()
|
||||
def save_all(self):
|
||||
if self.num_frames == 0:
|
||||
return
|
||||
|
||||
try:
|
||||
not_show = settings.read('not_show_save_dialog', False, type=bool)
|
||||
except TypeError:
|
||||
not_show = False
|
||||
|
||||
if not not_show:
|
||||
cb = QCheckBox("Don't ask me again.")
|
||||
msg_box = QMessageBox(QMessageBox.Question, self.tr("Confirm saving all signals"),
|
||||
self.tr("All changed signal files will be overwritten. OK?"))
|
||||
msg_box.addButton(QMessageBox.Yes)
|
||||
msg_box.addButton(QMessageBox.No)
|
||||
msg_box.setCheckBox(cb)
|
||||
|
||||
reply = msg_box.exec()
|
||||
not_show_again = cb.isChecked()
|
||||
settings.write("not_show_save_dialog", not_show_again)
|
||||
self.not_show_again_changed.emit()
|
||||
|
||||
if reply != QMessageBox.Yes:
|
||||
return
|
||||
|
||||
for f in self.signal_frames:
|
||||
if f.signal is None or f.signal.filename == "":
|
||||
continue
|
||||
f.signal.save()
|
||||
|
||||
@pyqtSlot()
|
||||
def close_all(self):
|
||||
for f in self.signal_frames:
|
||||
f.my_close()
|
||||
|
||||
@pyqtSlot(Signal)
|
||||
def on_apply_to_all_clicked(self, signal: Signal):
|
||||
for frame in self.signal_frames:
|
||||
if frame.signal is not None:
|
||||
frame.signal.noise_min_plot = signal.noise_min_plot
|
||||
frame.signal.noise_max_plot = signal.noise_max_plot
|
||||
|
||||
frame.signal.block_protocol_update = True
|
||||
proto_needs_update = False
|
||||
|
||||
if frame.signal.modulation_type != signal.modulation_type:
|
||||
frame.signal.modulation_type = signal.modulation_type
|
||||
proto_needs_update = True
|
||||
|
||||
if frame.signal.center != signal.center:
|
||||
frame.signal.center = signal.center
|
||||
proto_needs_update = True
|
||||
|
||||
if frame.signal.tolerance != signal.tolerance:
|
||||
frame.signal.tolerance = signal.tolerance
|
||||
proto_needs_update = True
|
||||
|
||||
if frame.signal.noise_threshold != signal.noise_threshold:
|
||||
frame.signal.noise_threshold_relative = signal.noise_threshold_relative
|
||||
proto_needs_update = True
|
||||
|
||||
if frame.signal.samples_per_symbol != signal.samples_per_symbol:
|
||||
frame.signal.samples_per_symbol = signal.samples_per_symbol
|
||||
proto_needs_update = True
|
||||
|
||||
if frame.signal.pause_threshold != signal.pause_threshold:
|
||||
frame.signal.pause_threshold = signal.pause_threshold
|
||||
proto_needs_update = True
|
||||
|
||||
if frame.signal.message_length_divisor != signal.message_length_divisor:
|
||||
frame.signal.message_length_divisor = signal.message_length_divisor
|
||||
proto_needs_update = True
|
||||
|
||||
frame.signal.block_protocol_update = False
|
||||
|
||||
if proto_needs_update:
|
||||
frame.signal.protocol_needs_update.emit()
|
||||
|
||||
@pyqtSlot(QPoint)
|
||||
def frame_dragged(self, pos: QPoint):
|
||||
self.drag_pos = pos
|
||||
|
||||
@pyqtSlot(QPoint)
|
||||
def frame_dropped(self, pos: QPoint):
|
||||
start = self.drag_pos
|
||||
if start is None:
|
||||
return
|
||||
|
||||
end = pos
|
||||
start_index = -1
|
||||
end_index = -1
|
||||
if self.num_frames > 1:
|
||||
for i, w in enumerate(self.signal_frames):
|
||||
if w.geometry().contains(start):
|
||||
start_index = i
|
||||
|
||||
if w.geometry().contains(end):
|
||||
end_index = i
|
||||
|
||||
self.swap_frames(start_index, end_index)
|
||||
self.frame_was_dropped.emit(start_index, end_index)
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
def swap_frames(self, from_index: int, to_index: int):
|
||||
if from_index != to_index:
|
||||
start_sig_widget = self.ui.splitter.widget(from_index)
|
||||
self.ui.splitter.insertWidget(to_index, start_sig_widget)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_participant_changed(self):
|
||||
for sframe in self.signal_frames:
|
||||
sframe.on_participant_changed()
|
||||
|
||||
def redraw_spectrograms(self):
|
||||
for frame in self.signal_frames:
|
||||
if frame.ui.gvSpectrogram.width_spectrogram > 0:
|
||||
frame.draw_spectrogram(force_redraw=True)
|
||||
|
||||
@pyqtSlot(Signal)
|
||||
def emit_signal_created(self, signal):
|
||||
try:
|
||||
index = self.signal_frames.index(self.sender()) + 1
|
||||
except ValueError:
|
||||
index = -1
|
||||
|
||||
self.signal_created.emit(index, signal)
|
||||
@@ -0,0 +1,584 @@
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import numpy
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QDir, QStringListModel, pyqtSignal
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QWidget, QFileDialog, QCompleter, QMessageBox, QFrame, \
|
||||
QHBoxLayout, QToolButton, QDialog
|
||||
|
||||
from urh.controller.CompareFrameController import CompareFrameController
|
||||
from urh.controller.GeneratorTabController import GeneratorTabController
|
||||
from urh.controller.dialogs.ModulatorDialog import ModulatorDialog
|
||||
from urh.controller.dialogs.ProtocolLabelDialog import ProtocolLabelDialog
|
||||
from urh.controller.dialogs.SimulatorDialog import SimulatorDialog
|
||||
from urh.controller.widgets.ChecksumWidget import ChecksumWidget
|
||||
from urh.models.ParticipantTableModel import ParticipantTableModel
|
||||
from urh.models.SimulatorMessageFieldModel import SimulatorMessageFieldModel
|
||||
from urh.models.SimulatorMessageTableModel import SimulatorMessageTableModel
|
||||
from urh.models.SimulatorParticipantListModel import SimulatorParticipantListModel
|
||||
from urh.signalprocessing.Participant import Participant
|
||||
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
|
||||
from urh.simulator.SimulatorConfiguration import SimulatorConfiguration
|
||||
from urh.simulator.SimulatorCounterAction import SimulatorCounterAction
|
||||
from urh.simulator.SimulatorExpressionParser import SimulatorExpressionParser
|
||||
from urh.simulator.SimulatorGotoAction import SimulatorGotoAction
|
||||
from urh.simulator.SimulatorItem import SimulatorItem
|
||||
from urh.simulator.SimulatorMessage import SimulatorMessage
|
||||
from urh.simulator.SimulatorProtocolLabel import SimulatorProtocolLabel
|
||||
from urh.simulator.SimulatorRule import SimulatorRuleCondition, ConditionType
|
||||
from urh.simulator.SimulatorSleepAction import SimulatorSleepAction
|
||||
from urh.simulator.SimulatorTriggerCommandAction import SimulatorTriggerCommandAction
|
||||
from urh.ui.RuleExpressionValidator import RuleExpressionValidator
|
||||
from urh.ui.SimulatorScene import SimulatorScene
|
||||
from urh.ui.delegates.ComboBoxDelegate import ComboBoxDelegate
|
||||
from urh.ui.delegates.ProtocolValueDelegate import ProtocolValueDelegate
|
||||
from urh.ui.ui_simulator import Ui_SimulatorTab
|
||||
from urh.util import util, FileOperator
|
||||
from urh.util.Errors import Errors
|
||||
from urh.util.Logger import logger
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class SimulatorTabController(QWidget):
|
||||
open_in_analysis_requested = pyqtSignal(str)
|
||||
rx_file_saved = pyqtSignal(str)
|
||||
|
||||
def __init__(self, compare_frame_controller: CompareFrameController,
|
||||
generator_tab_controller: GeneratorTabController,
|
||||
project_manager: ProjectManager, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self.project_manager = project_manager
|
||||
self.compare_frame_controller = compare_frame_controller
|
||||
self.generator_tab_controller = generator_tab_controller
|
||||
self.proto_analyzer = compare_frame_controller.proto_analyzer
|
||||
|
||||
self.simulator_config = SimulatorConfiguration(self.project_manager)
|
||||
self.sim_expression_parser = SimulatorExpressionParser(self.simulator_config)
|
||||
SimulatorItem.simulator_config = self.simulator_config
|
||||
SimulatorItem.expression_parser = self.sim_expression_parser
|
||||
|
||||
self.ui = Ui_SimulatorTab()
|
||||
self.ui.setupUi(self)
|
||||
util.set_splitter_stylesheet(self.ui.splitter)
|
||||
util.set_splitter_stylesheet(self.ui.splitterLeftRight)
|
||||
|
||||
self.ui.splitter.setSizes([int(self.width() / 0.7), int(self.width() / 0.3)])
|
||||
|
||||
self.ui.treeProtocols.setHeaderHidden(True)
|
||||
self.tree_model = self.generator_tab_controller.tree_model
|
||||
self.ui.treeProtocols.setModel(self.tree_model)
|
||||
|
||||
self.participant_table_model = ParticipantTableModel(project_manager.participants)
|
||||
self.ui.tableViewParticipants.setModel(self.participant_table_model)
|
||||
self.participant_table_model.update()
|
||||
|
||||
self.simulator_message_field_model = SimulatorMessageFieldModel(self)
|
||||
self.ui.tblViewFieldValues.setModel(self.simulator_message_field_model)
|
||||
self.ui.tblViewFieldValues.setItemDelegateForColumn(1, ComboBoxDelegate(ProtocolLabel.DISPLAY_FORMATS,
|
||||
parent=self.ui.tblViewFieldValues))
|
||||
self.ui.tblViewFieldValues.setItemDelegateForColumn(2, ComboBoxDelegate(SimulatorProtocolLabel.VALUE_TYPES,
|
||||
parent=self.ui.tblViewFieldValues))
|
||||
self.ui.tblViewFieldValues.setItemDelegateForColumn(3, ProtocolValueDelegate(controller=self,
|
||||
parent=self.ui.tblViewFieldValues))
|
||||
self.project_manager.reload_field_types()
|
||||
self.update_field_name_column()
|
||||
|
||||
self.simulator_message_table_model = SimulatorMessageTableModel(self.project_manager, self)
|
||||
self.ui.tblViewMessage.setModel(self.simulator_message_table_model)
|
||||
|
||||
self.ui.ruleCondLineEdit.setValidator(RuleExpressionValidator(self.sim_expression_parser, is_formula=False))
|
||||
self.completer_model = QStringListModel([])
|
||||
self.ui.ruleCondLineEdit.setCompleter(QCompleter(self.completer_model, self.ui.ruleCondLineEdit))
|
||||
self.ui.ruleCondLineEdit.setToolTip(self.sim_expression_parser.rule_condition_help)
|
||||
|
||||
self.simulator_scene = SimulatorScene(mode=0, simulator_config=self.simulator_config)
|
||||
self.simulator_scene.tree_root_item = compare_frame_controller.proto_tree_model.rootItem
|
||||
self.ui.gvSimulator.setScene(self.simulator_scene)
|
||||
self.ui.gvSimulator.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
self.ui.gvSimulator.proto_analyzer = compare_frame_controller.proto_analyzer
|
||||
|
||||
self.__active_item = None
|
||||
|
||||
self.ui.listViewSimulate.setModel(SimulatorParticipantListModel(self.simulator_config))
|
||||
self.ui.spinBoxNRepeat.setValue(self.project_manager.simulator_num_repeat)
|
||||
self.ui.spinBoxTimeout.setValue(self.project_manager.simulator_timeout_ms)
|
||||
self.ui.spinBoxRetries.setValue(self.project_manager.simulator_retries)
|
||||
self.ui.comboBoxError.setCurrentIndex(self.project_manager.simulator_error_handling_index)
|
||||
|
||||
# place save/load button at corner of tab widget
|
||||
frame = QFrame(parent=self)
|
||||
frame.setLayout(QHBoxLayout())
|
||||
frame.setFrameStyle(frame.NoFrame)
|
||||
self.ui.btnSave = QToolButton(self.ui.tab)
|
||||
self.ui.btnSave.setIcon(QIcon.fromTheme("document-save"))
|
||||
frame.layout().addWidget(self.ui.btnSave)
|
||||
|
||||
self.ui.btnLoad = QToolButton(self.ui.tab)
|
||||
self.ui.btnLoad.setIcon(QIcon.fromTheme("document-open"))
|
||||
frame.layout().addWidget(self.ui.btnLoad)
|
||||
frame.layout().setContentsMargins(0, 0, 0, 0)
|
||||
self.ui.tabWidget.setCornerWidget(frame)
|
||||
|
||||
self.ui.splitterLeftRight.setSizes([int(0.2 * self.width()), int(0.8 * self.width())])
|
||||
|
||||
self.create_connects()
|
||||
|
||||
def refresh_field_types_for_labels(self):
|
||||
for msg in self.simulator_config.get_all_messages():
|
||||
for lbl in (lbl for lbl in msg.message_type if lbl.field_type is not None):
|
||||
msg.message_type.change_field_type_of_label(lbl, self.field_types_by_caption.get(lbl.field_type.caption,
|
||||
None))
|
||||
|
||||
self.update_field_name_column()
|
||||
self.update_ui()
|
||||
|
||||
@property
|
||||
def field_types(self):
|
||||
return self.project_manager.field_types
|
||||
|
||||
@property
|
||||
def field_types_by_caption(self):
|
||||
return self.project_manager.field_types_by_caption
|
||||
|
||||
def update_field_name_column(self):
|
||||
field_types = [ft.caption for ft in self.field_types]
|
||||
self.ui.tblViewFieldValues.setItemDelegateForColumn(0, ComboBoxDelegate(field_types, is_editable=True,
|
||||
return_index=False,
|
||||
parent=self.ui.tblViewFieldValues))
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.btnChooseCommand.clicked.connect(self.on_btn_choose_command_clicked)
|
||||
self.ui.lineEditTriggerCommand.textChanged.connect(self.on_line_edit_trigger_command_text_changed)
|
||||
self.ui.checkBoxPassTranscriptSTDIN.clicked.connect(self.on_check_box_pass_transcript_STDIN_clicked)
|
||||
self.ui.doubleSpinBoxSleep.editingFinished.connect(self.on_spinbox_sleep_editing_finished)
|
||||
self.ui.ruleCondLineEdit.textChanged.connect(self.on_rule_cond_line_edit_text_changed)
|
||||
self.ui.btnStartSim.clicked.connect(self.on_btn_simulate_clicked)
|
||||
self.ui.goto_combobox.currentIndexChanged.connect(self.on_goto_combobox_index_changed)
|
||||
self.ui.spinBoxRepeat.valueChanged.connect(self.on_repeat_value_changed)
|
||||
self.ui.cbViewType.currentIndexChanged.connect(self.on_view_type_changed)
|
||||
self.ui.tblViewMessage.create_label_triggered.connect(self.create_simulator_label)
|
||||
self.ui.tblViewMessage.open_modulator_dialog_clicked.connect(self.open_modulator_dialog)
|
||||
self.ui.tblViewMessage.selectionModel().selectionChanged.connect(self.on_table_selection_changed)
|
||||
self.ui.tabWidget.currentChanged.connect(self.on_selected_tab_changed)
|
||||
self.ui.btnSave.clicked.connect(self.on_btn_save_clicked)
|
||||
self.ui.btnLoad.clicked.connect(self.on_btn_load_clicked)
|
||||
|
||||
self.ui.listViewSimulate.model().participant_simulate_changed.connect(self.on_participant_simulate_changed)
|
||||
|
||||
self.ui.btnAddParticipant.clicked.connect(self.ui.tableViewParticipants.on_add_action_triggered)
|
||||
self.ui.btnRemoveParticipant.clicked.connect(self.ui.tableViewParticipants.on_remove_action_triggered)
|
||||
self.ui.btnUp.clicked.connect(self.ui.tableViewParticipants.on_move_up_action_triggered)
|
||||
self.ui.btnDown.clicked.connect(self.ui.tableViewParticipants.on_move_down_action_triggered)
|
||||
self.participant_table_model.participant_edited.connect(self.on_participant_edited)
|
||||
|
||||
self.tree_model.modelReset.connect(self.refresh_tree)
|
||||
|
||||
self.simulator_scene.selectionChanged.connect(self.on_simulator_scene_selection_changed)
|
||||
self.simulator_scene.files_dropped.connect(self.on_files_dropped)
|
||||
|
||||
self.simulator_message_field_model.protocol_label_updated.connect(self.item_updated)
|
||||
self.ui.gvSimulator.message_updated.connect(self.item_updated)
|
||||
self.ui.gvSimulator.consolidate_messages_clicked.connect(self.consolidate_messages)
|
||||
|
||||
self.simulator_config.items_added.connect(self.refresh_message_table)
|
||||
self.simulator_config.items_updated.connect(self.refresh_message_table)
|
||||
self.simulator_config.items_moved.connect(self.refresh_message_table)
|
||||
self.simulator_config.items_deleted.connect(self.refresh_message_table)
|
||||
self.simulator_config.participants_changed.connect(self.on_participants_changed)
|
||||
self.simulator_config.item_dict_updated.connect(self.on_item_dict_updated)
|
||||
self.simulator_config.active_participants_updated.connect(self.on_active_participants_updated)
|
||||
|
||||
self.ui.gvSimulator.message_updated.connect(self.on_message_source_or_destination_updated)
|
||||
|
||||
self.ui.spinBoxNRepeat.valueChanged.connect(self.on_spinbox_num_repeat_value_changed)
|
||||
self.ui.spinBoxTimeout.valueChanged.connect(self.on_spinbox_timeout_value_changed)
|
||||
self.ui.comboBoxError.currentIndexChanged.connect(self.on_combobox_error_handling_index_changed)
|
||||
self.ui.spinBoxRetries.valueChanged.connect(self.on_spinbox_retries_value_changed)
|
||||
|
||||
self.ui.tblViewFieldValues.item_link_clicked.connect(self.on_table_item_link_clicked)
|
||||
self.ui.tblViewMessage.edit_label_triggered.connect(self.on_edit_label_triggered)
|
||||
|
||||
self.ui.spinBoxCounterStart.editingFinished.connect(self.on_spinbox_counter_start_editing_finished)
|
||||
self.ui.spinBoxCounterStep.editingFinished.connect(self.on_spinbox_counter_step_editing_finished)
|
||||
|
||||
def consolidate_messages(self):
|
||||
self.simulator_config.consolidate_messages()
|
||||
|
||||
def on_repeat_value_changed(self, value):
|
||||
self.active_item.repeat = value
|
||||
self.simulator_config.items_updated.emit([self.active_item])
|
||||
|
||||
def on_item_dict_updated(self):
|
||||
self.completer_model.setStringList(self.sim_expression_parser.get_identifiers())
|
||||
|
||||
def on_selected_tab_changed(self, index: int):
|
||||
if index == 0:
|
||||
if self.active_item is not None:
|
||||
self.ui.gvSimulator.jump_to_item(self.active_item)
|
||||
else:
|
||||
self.update_ui()
|
||||
else:
|
||||
self.ui.tblViewMessage.resize_columns()
|
||||
self.update_vertical_table_header()
|
||||
|
||||
def refresh_message_table(self):
|
||||
self.simulator_message_table_model.protocol.messages[:] = self.simulator_config.get_all_messages()
|
||||
self.simulator_message_table_model.update()
|
||||
|
||||
if isinstance(self.active_item, SimulatorMessage):
|
||||
self.simulator_message_field_model.update()
|
||||
|
||||
self.ui.tblViewMessage.resize_columns()
|
||||
self.update_ui()
|
||||
|
||||
def load_config_from_xml_tag(self, xml_tag, update_before=True):
|
||||
if xml_tag is None:
|
||||
return
|
||||
|
||||
if update_before:
|
||||
self.simulator_config.on_project_updated()
|
||||
|
||||
self.simulator_config.load_from_xml(xml_tag, self.proto_analyzer.message_types)
|
||||
self.project_manager.project_updated.emit()
|
||||
|
||||
def load_simulator_file(self, filename: str):
|
||||
try:
|
||||
tree = ET.parse(filename)
|
||||
self.load_config_from_xml_tag(tree.getroot(), update_before=False)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
def save_simulator_file(self, filename: str):
|
||||
tag = self.simulator_config.save_to_xml(standalone=True)
|
||||
util.write_xml_to_file(tag, filename)
|
||||
|
||||
def close_all(self):
|
||||
self.simulator_scene.clear_all()
|
||||
|
||||
@pyqtSlot(int, int, int)
|
||||
def create_simulator_label(self, msg_index: int, start: int, end: int):
|
||||
con = self.simulator_message_table_model.protocol
|
||||
start, end = con.convert_range(start, end - 1, self.ui.cbViewType.currentIndex(), 0, False, msg_index)
|
||||
lbl = self.simulator_config.add_label(start=start, end=end, parent_item=con.messages[msg_index])
|
||||
|
||||
try:
|
||||
index = self.simulator_message_field_model.message_type.index(lbl)
|
||||
self.ui.tblViewFieldValues.edit(self.simulator_message_field_model.createIndex(index, 0))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@pyqtSlot()
|
||||
def open_modulator_dialog(self):
|
||||
selected_message = self.simulator_message_table_model.protocol.messages[self.ui.tblViewMessage.selected_rows[0]]
|
||||
preselected_index = selected_message.modulator_index
|
||||
|
||||
modulator_dialog = ModulatorDialog(self.project_manager.modulators, tree_model=self.tree_model, parent=self)
|
||||
modulator_dialog.ui.comboBoxCustomModulations.setCurrentIndex(preselected_index)
|
||||
modulator_dialog.showMaximized()
|
||||
modulator_dialog.initialize(selected_message.encoded_bits_str[0:16])
|
||||
|
||||
modulator_dialog.finished.connect(self.refresh_modulators)
|
||||
modulator_dialog.finished.connect(self.generator_tab_controller.refresh_pause_list)
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_modulators(self):
|
||||
# update Generator tab ...
|
||||
cBoxModulations = self.generator_tab_controller.ui.cBoxModulations
|
||||
current_index = cBoxModulations.currentIndex()
|
||||
cBoxModulations.clear()
|
||||
|
||||
for modulator in self.project_manager.modulators:
|
||||
cBoxModulations.addItem(modulator.name)
|
||||
|
||||
cBoxModulations.setCurrentIndex(current_index)
|
||||
|
||||
# update Simulator tab ...
|
||||
index = self.sender().ui.comboBoxCustomModulations.currentIndex()
|
||||
|
||||
for row in self.ui.tblViewMessage.selected_rows:
|
||||
self.simulator_message_table_model.protocol.messages[row].modulator_index = index
|
||||
|
||||
def update_goto_combobox(self, active_item: SimulatorGotoAction):
|
||||
assert isinstance(active_item, SimulatorGotoAction)
|
||||
goto_combobox = self.ui.goto_combobox
|
||||
|
||||
goto_combobox.blockSignals(True)
|
||||
goto_combobox.clear()
|
||||
goto_combobox.addItem("Select item ...")
|
||||
goto_combobox.addItems(active_item.get_valid_goto_targets())
|
||||
goto_combobox.setCurrentIndex(-1)
|
||||
goto_combobox.blockSignals(False)
|
||||
|
||||
index = goto_combobox.findText(self.active_item.goto_target)
|
||||
|
||||
if index == -1:
|
||||
index = 0
|
||||
|
||||
goto_combobox.setCurrentIndex(index)
|
||||
|
||||
def update_ui(self):
|
||||
if self.active_item is not None:
|
||||
text = self.tr("Detail view for item #") + self.active_item.index()
|
||||
|
||||
if isinstance(self.active_item, SimulatorMessage):
|
||||
text += " (" + self.active_item.message_type.name + ")"
|
||||
self.ui.spinBoxRepeat.setValue(self.active_item.repeat)
|
||||
self.ui.lblEncodingDecoding.setText(self.active_item.decoder.name)
|
||||
|
||||
self.ui.lblMsgFieldsValues.setText(text)
|
||||
else:
|
||||
self.ui.lblMsgFieldsValues.setText(self.tr("Detail view for item"))
|
||||
|
||||
def update_vertical_table_header(self):
|
||||
self.simulator_message_table_model.refresh_vertical_header()
|
||||
self.ui.tblViewMessage.resize_vertical_header()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_rule_cond_line_edit_text_changed(self):
|
||||
self.active_item.condition = self.ui.ruleCondLineEdit.text()
|
||||
self.item_updated(self.active_item)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_view_type_changed(self):
|
||||
self.simulator_message_table_model.proto_view = self.ui.cbViewType.currentIndex()
|
||||
self.simulator_message_table_model.update()
|
||||
self.ui.tblViewMessage.resize_columns()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_goto_combobox_index_changed(self):
|
||||
if not isinstance(self.active_item, SimulatorGotoAction):
|
||||
return
|
||||
|
||||
self.active_item.goto_target = None if self.ui.goto_combobox.currentIndex() == 0 else self.ui.goto_combobox.currentText()
|
||||
self.item_updated(self.active_item)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_simulator_scene_selection_changed(self):
|
||||
selected_items = self.simulator_scene.selectedItems()
|
||||
self.active_item = selected_items[0].model_item if selected_items else None
|
||||
|
||||
self.update_ui()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_table_selection_changed(self):
|
||||
selection = self.ui.tblViewMessage.selectionModel().selection()
|
||||
|
||||
if selection.isEmpty():
|
||||
self.active_item = None
|
||||
self.ui.lNumSelectedColumns.setText("0")
|
||||
else:
|
||||
max_row = numpy.max([rng.bottom() for rng in selection])
|
||||
self.active_item = self.simulator_message_table_model.protocol.messages[max_row]
|
||||
_, _, start, end = self.ui.tblViewMessage.selection_range()
|
||||
self.ui.lNumSelectedColumns.setText(str(end - start))
|
||||
|
||||
self.update_ui()
|
||||
|
||||
@property
|
||||
def active_item(self):
|
||||
return self.__active_item
|
||||
|
||||
@active_item.setter
|
||||
def active_item(self, value):
|
||||
self.__active_item = value
|
||||
|
||||
if isinstance(self.active_item, SimulatorGotoAction):
|
||||
self.update_goto_combobox(self.active_item)
|
||||
|
||||
self.ui.detail_view_widget.setCurrentIndex(1)
|
||||
elif isinstance(self.active_item, SimulatorMessage):
|
||||
self.simulator_message_field_model.update()
|
||||
self.ui.spinBoxRepeat.setValue(self.active_item.repeat)
|
||||
self.ui.lblEncodingDecoding.setText(self.active_item.decoder.name)
|
||||
|
||||
self.ui.detail_view_widget.setCurrentIndex(2)
|
||||
elif (isinstance(self.active_item, SimulatorRuleCondition) and
|
||||
self.active_item.type != ConditionType.ELSE):
|
||||
self.ui.ruleCondLineEdit.setText(self.active_item.condition)
|
||||
self.ui.detail_view_widget.setCurrentIndex(3)
|
||||
elif isinstance(self.active_item, SimulatorTriggerCommandAction):
|
||||
self.ui.lineEditTriggerCommand.setText(self.active_item.command)
|
||||
self.ui.checkBoxPassTranscriptSTDIN.setChecked(self.active_item.pass_transcript)
|
||||
self.ui.detail_view_widget.setCurrentIndex(4)
|
||||
elif isinstance(self.active_item, SimulatorSleepAction):
|
||||
self.ui.doubleSpinBoxSleep.setValue(self.active_item.sleep_time)
|
||||
self.ui.detail_view_widget.setCurrentIndex(5)
|
||||
elif isinstance(self.active_item, SimulatorCounterAction):
|
||||
self.ui.spinBoxCounterStart.setValue(self.active_item.start)
|
||||
self.ui.spinBoxCounterStep.setValue(self.active_item.step)
|
||||
self.ui.detail_view_widget.setCurrentIndex(6)
|
||||
else:
|
||||
self.ui.detail_view_widget.setCurrentIndex(0)
|
||||
|
||||
self.update_ui()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_simulate_clicked(self):
|
||||
if not self.simulator_config.protocol_valid():
|
||||
QMessageBox.critical(self, self.tr("Invalid protocol configuration"),
|
||||
self.tr(
|
||||
"There are some problems with your protocol configuration. Please fix them first."))
|
||||
return
|
||||
|
||||
if not len(self.simulator_config.get_all_messages()):
|
||||
QMessageBox.critical(self, self.tr("No messages found"), self.tr("Please add at least one message."))
|
||||
return
|
||||
|
||||
num_simulated = len([p for p in self.project_manager.participants if p.simulate])
|
||||
if num_simulated == 0:
|
||||
if self.ui.listViewSimulate.model().rowCount() == 0:
|
||||
QMessageBox.critical(self, self.tr("No active participants"),
|
||||
self.tr("You have no active participants.<br>"
|
||||
"Please add a participant in the <i>Participants tab</i> and "
|
||||
"assign it to at least one message as <i>source</i> or <i>destination.</i>"))
|
||||
return
|
||||
else:
|
||||
QMessageBox.critical(self, self.tr("No participant for simulation selected"),
|
||||
self.tr("Please check at least one participant from the "
|
||||
"<i>Simulate these participants</i> list."))
|
||||
return
|
||||
|
||||
try:
|
||||
self.get_simulator_dialog().exec_()
|
||||
except Exception as e:
|
||||
Errors.exception(e)
|
||||
|
||||
def get_simulator_dialog(self) -> SimulatorDialog:
|
||||
protos = [p for proto_list in self.tree_model.protocols.values() for p in proto_list]
|
||||
signals = [p.signal for p in protos if p.signal is not None]
|
||||
|
||||
s = SimulatorDialog(self.simulator_config, self.project_manager.modulators,
|
||||
self.sim_expression_parser, self.project_manager, signals=signals,
|
||||
signal_tree_model=self.tree_model, parent=self)
|
||||
|
||||
s.rx_parameters_changed.connect(self.project_manager.on_simulator_rx_parameters_changed)
|
||||
s.sniff_parameters_changed.connect(self.project_manager.on_simulator_sniff_parameters_changed)
|
||||
s.tx_parameters_changed.connect(self.project_manager.on_simulator_tx_parameters_changed)
|
||||
s.open_in_analysis_requested.connect(self.open_in_analysis_requested.emit)
|
||||
s.rx_file_saved.connect(self.rx_file_saved.emit)
|
||||
|
||||
return s
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_choose_command_clicked(self):
|
||||
file_name, ok = QFileDialog.getOpenFileName(self, self.tr("Choose program"), QDir.homePath())
|
||||
|
||||
if file_name is not None and ok:
|
||||
self.ui.lineEditTriggerCommand.setText(file_name)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_trigger_command_text_changed(self):
|
||||
self.active_item.command = self.ui.lineEditTriggerCommand.text()
|
||||
self.item_updated(self.active_item)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_check_box_pass_transcript_STDIN_clicked(self):
|
||||
self.active_item.pass_transcript = self.ui.checkBoxPassTranscriptSTDIN.isChecked()
|
||||
self.item_updated(self.active_item)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_counter_start_editing_finished(self):
|
||||
self.active_item.start = self.ui.spinBoxCounterStart.value()
|
||||
self.item_updated(self.active_item)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_counter_step_editing_finished(self):
|
||||
self.active_item.step = self.ui.spinBoxCounterStep.value()
|
||||
self.item_updated(self.active_item)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_sleep_editing_finished(self):
|
||||
self.active_item.sleep_time = self.ui.doubleSpinBoxSleep.value()
|
||||
self.item_updated(self.active_item)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_participants_changed(self):
|
||||
self.update_vertical_table_header()
|
||||
self.participant_table_model.update()
|
||||
self.ui.listViewSimulate.model().update()
|
||||
|
||||
def item_updated(self, item: SimulatorItem):
|
||||
self.simulator_config.items_updated.emit([item])
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_tree(self):
|
||||
self.ui.treeProtocols.expandAll()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_save_clicked(self):
|
||||
filename = FileOperator.ask_save_file_name(initial_name="myprofile.sim.xml", caption="Save simulator profile")
|
||||
if filename:
|
||||
self.save_simulator_file(filename)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_load_clicked(self):
|
||||
dialog = FileOperator.get_open_dialog(False, parent=self, name_filter="simulator")
|
||||
if dialog.exec_():
|
||||
self.load_simulator_file(dialog.selectedFiles()[0])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_participant_edited(self):
|
||||
self.project_manager.project_updated.emit()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_num_repeat_value_changed(self, value):
|
||||
self.project_manager.simulator_num_repeat = value
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_timeout_value_changed(self, value):
|
||||
self.project_manager.simulator_timeout_ms = value
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_retries_value_changed(self, value):
|
||||
self.project_manager.simulator_retries = value
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_error_handling_index_changed(self, index: int):
|
||||
self.project_manager.simulator_error_handling_index = index
|
||||
|
||||
@pyqtSlot()
|
||||
def on_message_source_or_destination_updated(self):
|
||||
self.simulator_config.update_active_participants()
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
def on_table_item_link_clicked(self, row: int, column: int):
|
||||
try:
|
||||
lbl = self.simulator_message_field_model.message_type[row] # type: SimulatorProtocolLabel
|
||||
assert lbl.is_checksum_label
|
||||
assert isinstance(self.active_item, SimulatorMessage)
|
||||
except (IndexError, AssertionError):
|
||||
return
|
||||
|
||||
d = QDialog(parent=self)
|
||||
layout = QHBoxLayout()
|
||||
layout.addWidget(ChecksumWidget(lbl.label, self.active_item, self.ui.cbViewType.currentIndex()))
|
||||
d.setLayout(layout)
|
||||
d.show()
|
||||
|
||||
@pyqtSlot(Participant)
|
||||
def on_participant_simulate_changed(self, participant: Participant):
|
||||
self.simulator_scene.refresh_participant(participant)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_active_participants_updated(self):
|
||||
self.ui.listViewSimulate.model().update()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_edit_label_triggered(self, label_index: int):
|
||||
view_type = self.ui.cbViewType.currentIndex()
|
||||
protocol_label_dialog = ProtocolLabelDialog(message=self.ui.tblViewMessage.selected_message,
|
||||
viewtype=view_type, selected_index=label_index, parent=self)
|
||||
protocol_label_dialog.finished.connect(self.on_protocol_label_dialog_finished)
|
||||
protocol_label_dialog.showMaximized()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_protocol_label_dialog_finished(self):
|
||||
self.simulator_message_field_model.update()
|
||||
self.simulator_message_table_model.update()
|
||||
self.update_ui()
|
||||
|
||||
@pyqtSlot(list)
|
||||
def on_files_dropped(self, file_urls: list):
|
||||
for filename in (file_url.toLocalFile() for file_url in file_urls if file_url.isLocalFile()):
|
||||
self.load_simulator_file(filename)
|
||||
@@ -0,0 +1,38 @@
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt
|
||||
from PyQt5.QtWidgets import QDialog
|
||||
|
||||
from urh.ui.ui_advanced_modulation_settings import Ui_DialogAdvancedModSettings
|
||||
|
||||
|
||||
class AdvancedModulationOptionsDialog(QDialog):
|
||||
pause_threshold_edited = pyqtSignal(int)
|
||||
message_length_divisor_edited = pyqtSignal(int)
|
||||
|
||||
def __init__(self, pause_threshold: int, message_length_divisor: int, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogAdvancedModSettings()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.pause_threshold = pause_threshold
|
||||
self.message_length_divisor = message_length_divisor
|
||||
|
||||
self.ui.spinBoxPauseThreshold.setValue(pause_threshold)
|
||||
self.ui.spinBoxMessageLengthDivisor.setValue(message_length_divisor)
|
||||
|
||||
self.create_connects()
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.buttonBox.accepted.connect(self.on_accept_clicked)
|
||||
self.ui.buttonBox.rejected.connect(self.reject)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_accept_clicked(self):
|
||||
if self.pause_threshold != self.ui.spinBoxPauseThreshold.value():
|
||||
self.pause_threshold_edited.emit(self.ui.spinBoxPauseThreshold.value())
|
||||
|
||||
if self.message_length_divisor != self.ui.spinBoxMessageLengthDivisor.value():
|
||||
self.message_length_divisor_edited.emit(self.ui.spinBoxMessageLengthDivisor.value())
|
||||
|
||||
self.accept()
|
||||
@@ -0,0 +1,236 @@
|
||||
import csv
|
||||
|
||||
import os
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtWidgets import QDialog, QInputDialog, QApplication, QCompleter, QDirModel, QFileDialog
|
||||
|
||||
from urh.ui.ui_csv_wizard import Ui_DialogCSVImport
|
||||
from urh.util import FileOperator, util
|
||||
from urh.util.Errors import Errors
|
||||
|
||||
|
||||
class CSVImportDialog(QDialog):
|
||||
data_imported = pyqtSignal(str, float) # Complex Filename + Sample Rate
|
||||
|
||||
|
||||
PREVIEW_ROWS = 100
|
||||
COLUMNS = {"T": 0, "I": 1, "Q": 2}
|
||||
|
||||
def __init__(self, filename="", parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogCSVImport()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.ui.btnAutoDefault.hide()
|
||||
|
||||
completer = QCompleter()
|
||||
completer.setModel(QDirModel(completer))
|
||||
self.ui.lineEditFilename.setCompleter(completer)
|
||||
|
||||
self.filename = None # type: str
|
||||
self.ui.lineEditFilename.setText(filename)
|
||||
self.update_file()
|
||||
|
||||
self.ui.tableWidgetPreview.setColumnHidden(self.COLUMNS["T"], True)
|
||||
self.update_preview()
|
||||
|
||||
self.create_connects()
|
||||
|
||||
def create_connects(self):
|
||||
self.accepted.connect(self.on_accepted)
|
||||
self.ui.lineEditFilename.editingFinished.connect(self.on_line_edit_filename_editing_finished)
|
||||
self.ui.btnChooseFile.clicked.connect(self.on_btn_choose_file_clicked)
|
||||
self.ui.btnAddSeparator.clicked.connect(self.on_btn_add_separator_clicked)
|
||||
self.ui.comboBoxCSVSeparator.currentIndexChanged.connect(self.on_combobox_csv_separator_current_index_changed)
|
||||
self.ui.spinBoxIDataColumn.valueChanged.connect(self.on_spinbox_i_data_column_value_changed)
|
||||
self.ui.spinBoxQDataColumn.valueChanged.connect(self.on_spinbox_q_data_column_value_changed)
|
||||
self.ui.spinBoxTimestampColumn.valueChanged.connect(self.on_spinbox_timestamp_value_changed)
|
||||
|
||||
def update_file(self):
|
||||
filename = self.ui.lineEditFilename.text()
|
||||
self.filename = filename
|
||||
|
||||
enable = util.file_can_be_opened(filename)
|
||||
if enable:
|
||||
with open(self.filename, encoding="utf-8-sig") as f:
|
||||
lines = []
|
||||
for i, line in enumerate(f):
|
||||
if i >= self.PREVIEW_ROWS:
|
||||
break
|
||||
lines.append(line.strip())
|
||||
self.ui.plainTextEditFilePreview.setPlainText("\n".join(lines))
|
||||
else:
|
||||
self.ui.plainTextEditFilePreview.clear()
|
||||
|
||||
self.ui.plainTextEditFilePreview.setEnabled(enable)
|
||||
self.ui.comboBoxCSVSeparator.setEnabled(enable)
|
||||
self.ui.spinBoxIDataColumn.setEnabled(enable)
|
||||
self.ui.spinBoxQDataColumn.setEnabled(enable)
|
||||
self.ui.spinBoxTimestampColumn.setEnabled(enable)
|
||||
self.ui.tableWidgetPreview.setEnabled(enable)
|
||||
self.ui.labelFileNotFound.setVisible(not enable)
|
||||
|
||||
def update_preview(self):
|
||||
if not util.file_can_be_opened(self.filename):
|
||||
self.update_file()
|
||||
return
|
||||
|
||||
i_data_col = self.ui.spinBoxIDataColumn.value() - 1
|
||||
q_data_col = self.ui.spinBoxQDataColumn.value() - 1
|
||||
timestamp_col = self.ui.spinBoxTimestampColumn.value() - 1
|
||||
|
||||
self.ui.tableWidgetPreview.setRowCount(self.PREVIEW_ROWS)
|
||||
|
||||
with open(self.filename, encoding="utf-8-sig") as f:
|
||||
csv_reader = csv.reader(f, delimiter=self.ui.comboBoxCSVSeparator.currentText())
|
||||
row = -1
|
||||
|
||||
for line in csv_reader:
|
||||
row += 1
|
||||
result = self.parse_csv_line(line, i_data_col, q_data_col, timestamp_col)
|
||||
if result is not None:
|
||||
for key, value in result.items():
|
||||
self.ui.tableWidgetPreview.setItem(row, self.COLUMNS[key], util.create_table_item(value))
|
||||
else:
|
||||
for col in self.COLUMNS.values():
|
||||
self.ui.tableWidgetPreview.setItem(row, col, util.create_table_item("Invalid"))
|
||||
|
||||
if row >= self.PREVIEW_ROWS - 1:
|
||||
break
|
||||
|
||||
self.ui.tableWidgetPreview.setRowCount(row + 1)
|
||||
|
||||
@staticmethod
|
||||
def parse_csv_line(csv_line: str, i_data_col: int, q_data_col: int, timestamp_col: int):
|
||||
result = dict()
|
||||
|
||||
if i_data_col >= 0:
|
||||
try:
|
||||
result["I"] = float(csv_line[i_data_col])
|
||||
except:
|
||||
return None
|
||||
else:
|
||||
result["I"] = 0.0
|
||||
|
||||
if q_data_col >= 0:
|
||||
try:
|
||||
result["Q"] = float(csv_line[q_data_col])
|
||||
except:
|
||||
return None
|
||||
else:
|
||||
result["Q"] = 0.0
|
||||
|
||||
if timestamp_col >= 0:
|
||||
try:
|
||||
result["T"] = float(csv_line[timestamp_col])
|
||||
except:
|
||||
return None
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def parse_csv_file(filename: str, separator: str, i_data_col: int, q_data_col=-1, t_data_col=-1):
|
||||
iq_data = []
|
||||
timestamps = [] if t_data_col > -1 else None
|
||||
with open(filename, encoding="utf-8-sig") as f:
|
||||
csv_reader = csv.reader(f, delimiter=separator)
|
||||
for line in csv_reader:
|
||||
parsed = CSVImportDialog.parse_csv_line(line, i_data_col, q_data_col, t_data_col)
|
||||
if parsed is None:
|
||||
continue
|
||||
|
||||
iq_data.append(complex(parsed["I"], parsed["Q"]))
|
||||
if timestamps is not None:
|
||||
timestamps.append(parsed["T"])
|
||||
|
||||
iq_data = np.asarray(iq_data, dtype=np.complex64)
|
||||
sample_rate = CSVImportDialog.estimate_sample_rate(timestamps)
|
||||
return iq_data / abs(iq_data.max()), sample_rate
|
||||
|
||||
@staticmethod
|
||||
def estimate_sample_rate(timestamps):
|
||||
if timestamps is None or len(timestamps) < 2:
|
||||
return None
|
||||
|
||||
previous_timestamp = timestamps[0]
|
||||
durations = []
|
||||
|
||||
for timestamp in timestamps[1:CSVImportDialog.PREVIEW_ROWS]:
|
||||
durations.append(abs(timestamp-previous_timestamp))
|
||||
previous_timestamp = timestamp
|
||||
|
||||
return 1 / (sum(durations) / len(durations))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_filename_editing_finished(self):
|
||||
self.update_file()
|
||||
self.update_preview()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_choose_file_clicked(self):
|
||||
filename, _ = QFileDialog.getOpenFileName(self, self.tr("Choose file"), directory=FileOperator.RECENT_PATH,
|
||||
filter="CSV files (*.csv);;All files (*.*)")
|
||||
|
||||
if filename:
|
||||
self.ui.lineEditFilename.setText(filename)
|
||||
self.ui.lineEditFilename.editingFinished.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_add_separator_clicked(self):
|
||||
sep, ok = QInputDialog.getText(self, "Enter Separator", "Separator:", text=",")
|
||||
if ok and sep not in (self.ui.comboBoxCSVSeparator.itemText(i) for i in
|
||||
range(self.ui.comboBoxCSVSeparator.count())):
|
||||
if len(sep) == 1:
|
||||
self.ui.comboBoxCSVSeparator.addItem(sep)
|
||||
else:
|
||||
Errors.generic_error("Invalid Separator", "Separator must be exactly one character.")
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_csv_separator_current_index_changed(self, index: int):
|
||||
self.update_preview()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_i_data_column_value_changed(self, value: int):
|
||||
self.update_preview()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_q_data_column_value_changed(self, value: int):
|
||||
self.update_preview()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_timestamp_value_changed(self, value: int):
|
||||
self.ui.tableWidgetPreview.setColumnHidden(self.COLUMNS["T"], value == 0)
|
||||
self.update_preview()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_accepted(self):
|
||||
QApplication.setOverrideCursor(Qt.WaitCursor)
|
||||
|
||||
iq_data, sample_rate = self.parse_csv_file(self.filename, self.ui.comboBoxCSVSeparator.currentText(),
|
||||
self.ui.spinBoxIDataColumn.value()-1,
|
||||
self.ui.spinBoxQDataColumn.value()-1,
|
||||
self.ui.spinBoxTimestampColumn.value()-1)
|
||||
|
||||
target_filename = self.filename.rstrip(".csv")
|
||||
if os.path.exists(target_filename + ".complex"):
|
||||
i = 1
|
||||
while os.path.exists(target_filename + "_" + str(i) + ".complex"):
|
||||
i += 1
|
||||
else:
|
||||
i = None
|
||||
|
||||
target_filename = target_filename if not i else target_filename + "_" + str(i)
|
||||
target_filename += ".complex"
|
||||
|
||||
iq_data.tofile(target_filename)
|
||||
|
||||
self.data_imported.emit(target_filename, sample_rate if sample_rate is not None else 0)
|
||||
QApplication.restoreOverrideCursor()
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication(["urh"])
|
||||
csv_dia = CSVImportDialog()
|
||||
csv_dia.exec_()
|
||||
@@ -0,0 +1,111 @@
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtGui import QCloseEvent
|
||||
|
||||
from urh.controller.dialogs.SendDialog import SendDialog
|
||||
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
|
||||
from urh.dev.VirtualDevice import VirtualDevice, Mode
|
||||
from urh.signalprocessing.ContinuousModulator import ContinuousModulator
|
||||
from urh.ui.painting.ContinuousSceneManager import ContinuousSceneManager
|
||||
|
||||
|
||||
class ContinuousSendDialog(SendDialog):
|
||||
def __init__(self, project_manager, messages, modulators, total_samples: int, parent, testing_mode=False):
|
||||
super().__init__(project_manager, modulated_data=None, modulation_msg_indices=None,
|
||||
continuous_send_mode=True, parent=parent, testing_mode=testing_mode)
|
||||
self.messages = messages
|
||||
self.modulators = modulators
|
||||
|
||||
self.graphics_view = self.ui.graphicsViewContinuousSend
|
||||
self.ui.stackedWidget.setCurrentWidget(self.ui.page_continuous_send)
|
||||
self.ui.progressBarSample.hide()
|
||||
self.ui.lSamplesSentText.hide()
|
||||
|
||||
self.total_samples = total_samples
|
||||
self.ui.progressBarMessage.setMaximum(len(messages))
|
||||
|
||||
num_repeats = self.device_settings_widget.ui.spinBoxNRepeat.value()
|
||||
self.continuous_modulator = ContinuousModulator(messages, modulators, num_repeats=num_repeats)
|
||||
self.scene_manager = ContinuousSceneManager(ring_buffer=self.continuous_modulator.ring_buffer, parent=self)
|
||||
self.scene_manager.init_scene()
|
||||
self.graphics_view.setScene(self.scene_manager.scene)
|
||||
self.graphics_view.scene_manager = self.scene_manager
|
||||
|
||||
self.setWindowTitle("Send Signal (continuous mode)")
|
||||
self.ui.lSamplesSentText.setText("Progress:")
|
||||
|
||||
self.create_connects()
|
||||
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
|
||||
|
||||
def create_connects(self):
|
||||
SendRecvDialog.create_connects(self)
|
||||
|
||||
def _update_send_indicator(self, width: int):
|
||||
pass
|
||||
|
||||
def update_view(self):
|
||||
super().update_view()
|
||||
self.ui.progressBarMessage.setValue(self.continuous_modulator.current_message_index.value + 1)
|
||||
self.scene_manager.init_scene()
|
||||
self.scene_manager.show_full_scene()
|
||||
self.graphics_view.update()
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
self.continuous_modulator.stop()
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_started(self):
|
||||
super().on_device_started()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_stopped(self):
|
||||
super().on_device_stopped()
|
||||
self.continuous_modulator.stop(clear_buffer=False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_stop_clicked(self):
|
||||
super().on_stop_clicked()
|
||||
self.continuous_modulator.stop()
|
||||
self.continuous_modulator.current_message_index.value = 0
|
||||
self.scene_manager.clear_path()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_start_clicked(self):
|
||||
self.device_settings_widget.ui.spinBoxNRepeat.editingFinished.emit() # inform continuous modulator
|
||||
if not self.continuous_modulator.is_running:
|
||||
self.continuous_modulator.start()
|
||||
super().on_start_clicked()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_clear_clicked(self):
|
||||
self.continuous_modulator.stop()
|
||||
self.continuous_modulator.current_message_index.value = 0
|
||||
self.scene_manager.clear_path()
|
||||
self.reset()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_num_repeats_changed(self):
|
||||
super().on_num_repeats_changed()
|
||||
self.continuous_modulator.num_repeats = self.device_settings_widget.ui.spinBoxNRepeat.value()
|
||||
|
||||
def on_selected_device_changed(self):
|
||||
self.ui.txtEditErrors.clear()
|
||||
super().on_selected_device_changed()
|
||||
|
||||
def init_device(self):
|
||||
device_name = self.selected_device_name
|
||||
num_repeats = self.device_settings_widget.ui.spinBoxNRepeat.value()
|
||||
|
||||
self.device = VirtualDevice(self.backend_handler, device_name, Mode.send,
|
||||
device_ip="192.168.10.2", sending_repeats=num_repeats, parent=self)
|
||||
self.ui.btnStart.setEnabled(True)
|
||||
|
||||
try:
|
||||
self.device.is_send_continuous = True
|
||||
self.device.continuous_send_ring_buffer = self.continuous_modulator.ring_buffer
|
||||
self.device.num_samples_to_send = self.total_samples
|
||||
|
||||
self._create_device_connects()
|
||||
except ValueError as e:
|
||||
self.ui.txtEditErrors.setText("<font color='red'>" + str(e) + "<font>")
|
||||
self.ui.btnStart.setEnabled(False)
|
||||
@@ -0,0 +1,27 @@
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtWidgets import QDialog
|
||||
|
||||
from urh.ui.ui_costa import Ui_DialogCosta
|
||||
|
||||
|
||||
class CostaOptionsDialog(QDialog):
|
||||
def __init__(self, loop_bandwidth, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogCosta()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.costas_loop_bandwidth = loop_bandwidth
|
||||
self.ui.doubleSpinBoxLoopBandwidth.setValue(self.costas_loop_bandwidth)
|
||||
|
||||
self.create_connects()
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.buttonBox.accepted.connect(self.accept)
|
||||
self.ui.buttonBox.rejected.connect(self.reject)
|
||||
self.ui.doubleSpinBoxLoopBandwidth.valueChanged.connect(self.on_spinbox_loop_bandwidth_value_changed)
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_spinbox_loop_bandwidth_value_changed(self, value):
|
||||
self.costas_loop_bandwidth = value
|
||||
@@ -0,0 +1,872 @@
|
||||
import copy
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QDir, Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QCloseEvent, QDropEvent, QDragEnterEvent, QIcon
|
||||
from PyQt5.QtWidgets import QDialog, QTableWidgetItem, QFileDialog, QInputDialog, \
|
||||
QLineEdit, QMessageBox
|
||||
|
||||
from urh import settings
|
||||
from urh.signalprocessing.Encoding import Encoding
|
||||
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
|
||||
from urh.signalprocessing.Signal import Signal
|
||||
from urh.ui.painting.SignalSceneManager import SignalSceneManager
|
||||
from urh.ui.ui_decoding import Ui_Decoder
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class DecoderDialog(QDialog):
|
||||
def __init__(self, decodings, signals, project_manager: ProjectManager,
|
||||
parent=None):
|
||||
"""
|
||||
:type decodings: list of Encoding
|
||||
:type signals: list of Signal
|
||||
"""
|
||||
# Init
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_Decoder()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
# Variables
|
||||
self.old_inpt_txt = ""
|
||||
self.old_carrier_txt = ""
|
||||
self.old_decoderchain = []
|
||||
self.active_message = ""
|
||||
self.old_cutmark = ""
|
||||
self.old_morse = (1, 3)
|
||||
|
||||
self.project_manager = project_manager
|
||||
|
||||
# Initialize encoder
|
||||
self.decodings = decodings
|
||||
self.ui.combobox_decodings.clear()
|
||||
for decoding in self.decodings:
|
||||
self.ui.combobox_decodings.addItem(decoding.name)
|
||||
self.chainstr = []
|
||||
self.chainoptions = {}
|
||||
self.set_e()
|
||||
|
||||
self.last_selected_item = ""
|
||||
|
||||
# Signals
|
||||
self.signals = signals if signals is not None else []
|
||||
for signal in signals:
|
||||
if signal:
|
||||
self.ui.combobox_signals.addItem(signal.name)
|
||||
|
||||
# Function lists
|
||||
self.ui.basefunctions.addItem(settings.DECODING_EDGE)
|
||||
self.ui.basefunctions.addItem(settings.DECODING_MORSE)
|
||||
self.ui.basefunctions.addItem(settings.DECODING_SUBSTITUTION)
|
||||
self.ui.basefunctions.addItem(settings.DECODING_EXTERNAL)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_INVERT)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_DIFFERENTIAL)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_BITORDER)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_REDUNDANCY)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_CARRIER)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_DATAWHITENING)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_ENOCEAN)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_CUT)
|
||||
|
||||
# Presets
|
||||
self.setWindowTitle("Decoding")
|
||||
self.setWindowIcon(QIcon(":/icons/icons/decoding.svg"))
|
||||
self.setAcceptDrops(True)
|
||||
self.inpt_text = "10010110"
|
||||
self.ui.inpt.setText(self.inpt_text)
|
||||
self.ui.optionWidget.setCurrentIndex(0)
|
||||
self.decoder_update()
|
||||
|
||||
self.ui.substitution.setColumnCount(2)
|
||||
self.ui.substitution.setRowCount(self.ui.substitution_rows.value())
|
||||
self.ui.substitution.setHorizontalHeaderLabels(['From', 'To'])
|
||||
self.ui.substitution.setColumnWidth(0, 190)
|
||||
self.ui.substitution.setColumnWidth(1, 190)
|
||||
|
||||
self.ui.btnAddtoYourDecoding.hide()
|
||||
self.ui.saveas.setVisible(False)
|
||||
|
||||
# Connects
|
||||
self.create_connects()
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.inpt.textChanged.connect(self.decoder_update)
|
||||
self.ui.multiple.valueChanged.connect(self.handle_multiple_changed)
|
||||
self.ui.carrier.textChanged.connect(self.handle_carrier_changed)
|
||||
self.ui.substitution_rows.valueChanged.connect(self.handle_substitution_rows_changed)
|
||||
self.ui.substitution.itemChanged.connect(self.handle_substitution_changed)
|
||||
|
||||
self.ui.btnChooseDecoder.clicked.connect(self.choose_decoder)
|
||||
self.ui.btnChooseEncoder.clicked.connect(self.choose_encoder)
|
||||
|
||||
self.ui.external_decoder.textEdited.connect(self.handle_external)
|
||||
self.ui.external_encoder.textEdited.connect(self.handle_external)
|
||||
self.ui.datawhitening_sync.textEdited.connect(self.handle_datawhitening)
|
||||
self.ui.datawhitening_polynomial.textEdited.connect(self.handle_datawhitening)
|
||||
self.ui.datawhitening_overwrite_crc.clicked.connect(self.handle_datawhitening)
|
||||
|
||||
self.ui.decoderchain.itemChanged.connect(self.decoderchainUpdate)
|
||||
self.ui.decoderchain.internalMove.connect(self.decoderchainUpdate)
|
||||
self.ui.decoderchain.deleteElement.connect(self.deleteElement)
|
||||
self.ui.decoderchain.currentRowChanged.connect(self.on_decoder_chain_current_row_changed)
|
||||
self.ui.basefunctions.currentRowChanged.connect(self.on_base_functions_current_row_changed)
|
||||
self.ui.additionalfunctions.currentRowChanged.connect(self.on_additional_functions_current_row_changed)
|
||||
self.ui.btnAddtoYourDecoding.clicked.connect(self.on_btn_add_to_your_decoding_clicked)
|
||||
|
||||
self.ui.combobox_decodings.currentIndexChanged.connect(self.set_e)
|
||||
self.ui.combobox_signals.currentIndexChanged.connect(self.set_signal)
|
||||
self.ui.saveas.clicked.connect(self.saveas)
|
||||
self.ui.delete_decoding.clicked.connect(self.delete_decoding)
|
||||
|
||||
self.ui.rB_delbefore.clicked.connect(self.handle_cut)
|
||||
self.ui.rB_delafter.clicked.connect(self.handle_cut)
|
||||
self.ui.rB_delbeforepos.clicked.connect(self.handle_cut)
|
||||
self.ui.rB_delafterpos.clicked.connect(self.handle_cut)
|
||||
self.ui.cutmark.textEdited.connect(self.handle_cut)
|
||||
self.ui.cutmark2.valueChanged.connect(self.handle_cut)
|
||||
|
||||
self.ui.morse_low.valueChanged.connect(self.handle_morse_changed)
|
||||
self.ui.morse_high.valueChanged.connect(self.handle_morse_changed)
|
||||
self.ui.morse_wait.valueChanged.connect(self.handle_morse_changed)
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
def choose_decoder(self):
|
||||
f, ok = QFileDialog.getOpenFileName(self, self.tr("Choose decoder program"), QDir.homePath())
|
||||
if f and ok:
|
||||
self.ui.external_decoder.setText(f)
|
||||
self.handle_external()
|
||||
|
||||
def choose_encoder(self):
|
||||
f, ok = QFileDialog.getOpenFileName(self, self.tr("Choose encoder program"), QDir.homePath())
|
||||
if f and ok:
|
||||
self.ui.external_encoder.setText(f)
|
||||
self.handle_external()
|
||||
|
||||
def save_to_file(self):
|
||||
if self.project_manager.project_file:
|
||||
self.project_manager.decodings = self.decodings
|
||||
else:
|
||||
prefix = os.path.realpath(os.path.join(settings.get_qt_settings_filename(), ".."))
|
||||
with open(os.path.join(prefix, settings.DECODINGS_FILE), "w") as f:
|
||||
for i in range(0, self.ui.combobox_decodings.count()):
|
||||
str = ""
|
||||
for j in self.decodings[i].get_chain():
|
||||
str += repr(j) + ", "
|
||||
str += "\n"
|
||||
f.write(str)
|
||||
|
||||
def saveas(self):
|
||||
# Ask for a name
|
||||
name, ok = QInputDialog.getText(self, self.tr("Save decoding"),
|
||||
self.tr("Please enter a name:"), QLineEdit.Normal, self.e.chain[0])
|
||||
|
||||
if ok and name != "":
|
||||
self.e.chain[0] = name
|
||||
self.decoderchainUpdate()
|
||||
|
||||
# If name is already there, overwrite existing
|
||||
for i in range(0, len(self.decodings)):
|
||||
if name == self.decodings[i].name:
|
||||
self.ui.combobox_decodings.setCurrentIndex(i)
|
||||
self.decodings[i] = Encoding(self.chainstr)
|
||||
self.set_e()
|
||||
self.ui.saveas.setVisible(False)
|
||||
self.save_to_file()
|
||||
return
|
||||
|
||||
self.decodings.append(Encoding(self.chainstr))
|
||||
self.ui.combobox_decodings.addItem(self.chainstr[0])
|
||||
self.ui.combobox_decodings.setCurrentIndex(self.ui.combobox_decodings.count() - 1)
|
||||
self.set_e()
|
||||
self.save_to_file()
|
||||
|
||||
def delete_decoding(self):
|
||||
num = self.ui.combobox_decodings.currentIndex()
|
||||
if num >= 0:
|
||||
reply = QMessageBox.question(self, self.tr("Delete Decoding?"),
|
||||
self.tr("Do you really want to delete " + "'{}'?".format(
|
||||
self.decodings[num].name)),
|
||||
QMessageBox.Yes | QMessageBox.No)
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
self.decodings.pop(num)
|
||||
self.ui.combobox_decodings.removeItem(num)
|
||||
self.save_to_file()
|
||||
|
||||
def set_e(self):
|
||||
if self.ui.combobox_decodings.count() < 1: # Empty list
|
||||
return
|
||||
|
||||
self.e = copy.deepcopy(self.decodings[self.ui.combobox_decodings.currentIndex()])
|
||||
""":type: encoding """
|
||||
chain = self.e.get_chain()
|
||||
self.ui.decoderchain.clear()
|
||||
self.chainoptions.clear()
|
||||
last_i = ""
|
||||
for i in chain:
|
||||
if i in [settings.DECODING_INVERT, settings.DECODING_ENOCEAN, settings.DECODING_DIFFERENTIAL,
|
||||
settings.DECODING_REDUNDANCY, settings.DECODING_CARRIER, settings.DECODING_BITORDER,
|
||||
settings.DECODING_EDGE, settings.DECODING_DATAWHITENING, settings.DECODING_SUBSTITUTION,
|
||||
settings.DECODING_EXTERNAL, settings.DECODING_CUT, settings.DECODING_MORSE,
|
||||
settings.DECODING_DISABLED_PREFIX]:
|
||||
self.ui.decoderchain.addItem(i)
|
||||
self.decoderchainUpdate()
|
||||
last_i = self.ui.decoderchain.item(self.ui.decoderchain.count() - 1).text()
|
||||
else:
|
||||
if any(x in last_i for x in [settings.DECODING_REDUNDANCY, settings.DECODING_CARRIER,
|
||||
settings.DECODING_SUBSTITUTION, settings.DECODING_EXTERNAL,
|
||||
settings.DECODING_DATAWHITENING, settings.DECODING_CUT,
|
||||
settings.DECODING_MORSE]):
|
||||
self.chainoptions[last_i] = i
|
||||
|
||||
self.decoderchainUpdate()
|
||||
self.decoder_update()
|
||||
self.ui.saveas.setVisible(False)
|
||||
|
||||
def decoderchainUpdate(self):
|
||||
# for i in range (0, self.ui.decoderchain.count()):
|
||||
# print(i, "->", self.ui.decoderchain.item(i).text())
|
||||
# print()
|
||||
self.ui.saveas.setVisible(True)
|
||||
self.eliminateDuplicates()
|
||||
self.chainstr = [self.e.name]
|
||||
for i in range(0, self.ui.decoderchain.count()):
|
||||
op = self.ui.decoderchain.item(i).text()
|
||||
|
||||
# Is this function disabled?
|
||||
if settings.DECODING_DISABLED_PREFIX in op:
|
||||
continue
|
||||
|
||||
self.chainstr.append(op)
|
||||
|
||||
# Add parameters to chainstr
|
||||
if settings.DECODING_REDUNDANCY in op:
|
||||
# Read Multiple Value
|
||||
if op in self.chainoptions:
|
||||
self.chainstr.append(self.chainoptions[op])
|
||||
else:
|
||||
self.chainoptions[op] = 2
|
||||
self.chainstr.append(2) # Default
|
||||
elif settings.DECODING_CARRIER in op:
|
||||
# Read Carrier Field and add string to chainstr
|
||||
if op in self.chainoptions:
|
||||
self.chainstr.append(self.chainoptions[op])
|
||||
else:
|
||||
self.chainoptions[op] = ""
|
||||
self.chainstr.append("") # Default
|
||||
elif settings.DECODING_SUBSTITUTION in op:
|
||||
# Add substitution string to chainstr: Format = src0:dst0;src1:dst1;...
|
||||
if op in self.chainoptions:
|
||||
self.chainstr.append(self.chainoptions[op])
|
||||
else:
|
||||
self.chainoptions[op] = ""
|
||||
self.chainstr.append("") # Default
|
||||
elif settings.DECODING_EXTERNAL in op:
|
||||
# Add program path's string to chainstr: Format = decoder;encoder
|
||||
if op in self.chainoptions:
|
||||
self.chainstr.append(self.chainoptions[op])
|
||||
else:
|
||||
self.chainoptions[op] = ""
|
||||
self.chainstr.append("") # Default
|
||||
elif settings.DECODING_DATAWHITENING in op:
|
||||
# Add Data Whitening Parameters
|
||||
if op in self.chainoptions:
|
||||
self.chainstr.append(self.chainoptions[op])
|
||||
else:
|
||||
self.chainoptions[op] = ""
|
||||
self.chainstr.append("0xe9cae9ca;0x21;0") # Default
|
||||
elif settings.DECODING_CUT in op:
|
||||
# Add cut parameters
|
||||
if op in self.chainoptions:
|
||||
self.chainstr.append(self.chainoptions[op])
|
||||
else:
|
||||
self.chainoptions[op] = ""
|
||||
self.chainstr.append("0;1010") # Default
|
||||
elif settings.DECODING_MORSE in op:
|
||||
# Add morse parameters
|
||||
if op in self.chainoptions:
|
||||
self.chainstr.append(self.chainoptions[op])
|
||||
else:
|
||||
self.chainoptions[op] = ""
|
||||
self.chainstr.append("1;3;1") # Default
|
||||
|
||||
self.e.set_chain(self.chainstr)
|
||||
self.decoder_update()
|
||||
|
||||
def deleteElement(self):
|
||||
if self.ui.decoderchain.count() == 0: # Clear all
|
||||
self.chainoptions.clear()
|
||||
else:
|
||||
self.chainoptions.pop(self.ui.decoderchain.active_element_text, None)
|
||||
self.decoderchainUpdate()
|
||||
|
||||
def eliminateDuplicates(self):
|
||||
decoderchain_count = self.ui.decoderchain.count()
|
||||
olddecoderchain_count = len(self.old_decoderchain)
|
||||
|
||||
# Special case for 1 element (add " ")
|
||||
if decoderchain_count == 1:
|
||||
tmp = self.ui.decoderchain.item(0).text()
|
||||
if tmp[-1] != " " and not tmp[-1].isnumeric():
|
||||
self.ui.decoderchain.takeItem(0)
|
||||
self.ui.decoderchain.insertItem(0, tmp + " ")
|
||||
|
||||
# Ignore internal move (same count()) and removed elements and lists < 2 // len(self.old_decoderchain)+1 == self.ui.decoderchain.count()
|
||||
if decoderchain_count > 1 and decoderchain_count > olddecoderchain_count:
|
||||
elem = 0
|
||||
while elem < olddecoderchain_count:
|
||||
if self.ui.decoderchain.item(elem).text() == self.old_decoderchain[elem]:
|
||||
elem += 1
|
||||
else:
|
||||
break
|
||||
|
||||
# Count number of current elements and append string "#<num>" to current text, if num > 1
|
||||
txt = self.ui.decoderchain.item(elem).text()
|
||||
num = 0
|
||||
for i in range(0, decoderchain_count):
|
||||
if txt in self.ui.decoderchain.item(i).text():
|
||||
num += 1
|
||||
if num > 1:
|
||||
tmp_txt = txt + " #" + str(num)
|
||||
else:
|
||||
tmp_txt = txt + " "
|
||||
|
||||
# Check duplicate names
|
||||
dup = False
|
||||
for i in range(0, decoderchain_count):
|
||||
if self.ui.decoderchain.item(i).text() == tmp_txt:
|
||||
dup = True
|
||||
break
|
||||
|
||||
if dup:
|
||||
for i in range(1, num):
|
||||
if i > 1:
|
||||
tmp_txt = txt + " #" + str(i)
|
||||
else:
|
||||
tmp_txt = txt + " "
|
||||
|
||||
dup = False
|
||||
for j in range(0, decoderchain_count):
|
||||
if self.ui.decoderchain.item(j).text() == tmp_txt:
|
||||
dup = True
|
||||
break
|
||||
if not dup:
|
||||
break
|
||||
|
||||
# Replace current element with new "text #<num>"
|
||||
if not dup:
|
||||
self.ui.decoderchain.takeItem(elem)
|
||||
self.ui.decoderchain.insertItem(elem, tmp_txt)
|
||||
|
||||
# Save current decoderchain to old_decoderchain
|
||||
self.old_decoderchain = []
|
||||
for i in range(0, decoderchain_count):
|
||||
self.old_decoderchain.append(self.ui.decoderchain.item(i).text())
|
||||
|
||||
def decoder_update(self):
|
||||
# Only allow {0, 1}
|
||||
signaltype = self.ui.combobox_signals.currentIndex()
|
||||
inpt_txt = self.ui.inpt.text()
|
||||
if signaltype == 0:
|
||||
if inpt_txt.count("0") + inpt_txt.count("1") < len(inpt_txt):
|
||||
self.ui.inpt.setText(self.old_inpt_txt)
|
||||
else:
|
||||
self.old_inpt_txt = inpt_txt
|
||||
|
||||
# Write decoded bits
|
||||
bit = self.e.str2bit(self.ui.inpt.text())
|
||||
decoded = self.e.bit2str(self.e.decode(bit))
|
||||
errors = "[Decoding Errors = " + str(self.e.analyze(bit)[0]) + "]"
|
||||
self.ui.decoding_errors_label.setText(errors)
|
||||
self.ui.output.setText(decoded)
|
||||
self.ui.output.setCursorPosition(0)
|
||||
|
||||
if len(decoded) > 0:
|
||||
if signaltype == 0:
|
||||
temp_signal = SignalSceneManager.create_rectangle(inpt_txt)[0]
|
||||
self.ui.graphicsView_signal.setScene(temp_signal)
|
||||
self.ui.graphicsView_signal.update()
|
||||
|
||||
temp_decoded = SignalSceneManager.create_rectangle(decoded)[0]
|
||||
self.ui.graphicsView_decoded.setScene(temp_decoded)
|
||||
self.ui.graphicsView_decoded.update()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_base_functions_current_row_changed(self, index: int):
|
||||
if self.ui.basefunctions.currentItem().text() is not None:
|
||||
self.ui.decoderchain.setCurrentRow(-1)
|
||||
self.set_information(0)
|
||||
else:
|
||||
self.ui.optionWidget.setCurrentIndex(0)
|
||||
self.ui.info.clear()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_additional_functions_current_row_changed(self, index: int):
|
||||
if self.ui.additionalfunctions.currentItem() is not None:
|
||||
self.ui.decoderchain.setCurrentRow(-1)
|
||||
self.set_information(1)
|
||||
else:
|
||||
self.ui.optionWidget.setCurrentIndex(0)
|
||||
self.ui.info.clear()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_decoder_chain_current_row_changed(self, index: int):
|
||||
if self.ui.decoderchain.currentItem() is not None:
|
||||
self.set_information(2)
|
||||
else:
|
||||
self.ui.optionWidget.setCurrentIndex(0)
|
||||
self.ui.info.clear()
|
||||
|
||||
def set_information(self, mode: int):
|
||||
# Presets
|
||||
decoderEdit = False
|
||||
self.ui.optionWidget.setCurrentIndex(0)
|
||||
txt = ""
|
||||
|
||||
# Determine selected element
|
||||
if mode == 0:
|
||||
element = self.ui.basefunctions.currentItem().text()
|
||||
txt += element + ":\n"
|
||||
self.last_selected_item = element
|
||||
self.ui.btnAddtoYourDecoding.show()
|
||||
elif mode == 1:
|
||||
element = self.ui.additionalfunctions.currentItem().text()
|
||||
txt += element + ":\n"
|
||||
self.last_selected_item = element
|
||||
self.ui.btnAddtoYourDecoding.show()
|
||||
elif mode == 2:
|
||||
decoderEdit = True
|
||||
txt = "## In Your Decoding ##\n\n"
|
||||
element = self.ui.decoderchain.currentItem().text()
|
||||
if element[-1] == " ":
|
||||
elementname = element[0:-1]
|
||||
else:
|
||||
elementname = element
|
||||
txt += elementname + ":\n"
|
||||
self.active_message = element
|
||||
self.ui.btnAddtoYourDecoding.hide()
|
||||
|
||||
# Remove "[Disabled] " for further tasks
|
||||
if settings.DECODING_DISABLED_PREFIX in element:
|
||||
element = element[len(settings.DECODING_DISABLED_PREFIX):]
|
||||
|
||||
# Write info text and show options
|
||||
if settings.DECODING_EDGE in element:
|
||||
txt += "Trigger on signal edge, i.e. the transition between low and high.\n" \
|
||||
"- Low to High (01) is 1\n" \
|
||||
"- High to Low (10) is 0"
|
||||
elif settings.DECODING_SUBSTITUTION in element:
|
||||
txt += "A set of manual defined signal sequences FROM (e.g. 110, 100) is replaced by another set of " \
|
||||
"sequences TO (e.g. 01, 10). Note that all FROM entries must have the same length, otherwise " \
|
||||
"the result is unpredictable! (For TX: all TO entries must have the same length)"
|
||||
self.ui.optionWidget.setCurrentIndex(3)
|
||||
# Values can only be changed when editing decoder, otherwise default value
|
||||
if not decoderEdit:
|
||||
self.ui.substitution_rows.setValue(4)
|
||||
self.ui.substitution.setRowCount(0)
|
||||
self.ui.substitution.setRowCount(4)
|
||||
else:
|
||||
if element in self.chainoptions:
|
||||
values = self.chainoptions[element]
|
||||
if values == "":
|
||||
self.ui.substitution_rows.setValue(4)
|
||||
self.ui.substitution.setRowCount(0)
|
||||
self.ui.substitution.setRowCount(4)
|
||||
else:
|
||||
arrs = self.e.get_subst_array(values)
|
||||
if len(arrs[0]) == len(arrs[1]):
|
||||
self.ui.substitution_rows.setValue(len(arrs[0]))
|
||||
self.ui.substitution.setRowCount(len(arrs[0]))
|
||||
for i in range(0, len(arrs[0])):
|
||||
self.ui.substitution.setItem(i, 0, QTableWidgetItem(self.e.bit2str(arrs[0][i])))
|
||||
self.ui.substitution.setItem(i, 1, QTableWidgetItem(self.e.bit2str(arrs[1][i])))
|
||||
else:
|
||||
self.ui.substitution_rows.setValue(4)
|
||||
self.ui.substitution.setRowCount(0)
|
||||
self.ui.substitution.setRowCount(4)
|
||||
self.ui.substitution.setEnabled(decoderEdit)
|
||||
self.ui.substitution_rows.setEnabled(decoderEdit)
|
||||
|
||||
elif settings.DECODING_EXTERNAL in element:
|
||||
txt += "The decoding (and encoding) process is delegated to external programs or scripts via parameter.\n" \
|
||||
"Example: Given the signal 10010110, your program is called as './decoder 10010110'. Your program " \
|
||||
"computes and prints a corresponding set of 0s and 1s which is fed back into the decoding process. "
|
||||
self.ui.optionWidget.setCurrentIndex(4)
|
||||
# Values can only be changed when editing decoder, otherwise default value
|
||||
if not decoderEdit:
|
||||
self.ui.external_decoder.setText("")
|
||||
self.ui.external_encoder.setText("")
|
||||
else:
|
||||
if element in self.chainoptions:
|
||||
value = self.chainoptions[element]
|
||||
if value == "":
|
||||
self.ui.external_decoder.setText("")
|
||||
self.ui.external_encoder.setText("")
|
||||
else:
|
||||
decstr, encstr = value.split(";")
|
||||
self.ui.external_decoder.setText(decstr)
|
||||
self.ui.external_encoder.setText(encstr)
|
||||
else:
|
||||
self.ui.external_decoder.setText("")
|
||||
self.ui.external_encoder.setText("")
|
||||
self.ui.external_decoder.setEnabled(decoderEdit)
|
||||
self.ui.external_encoder.setEnabled(decoderEdit)
|
||||
self.ui.btnChooseDecoder.setEnabled(decoderEdit)
|
||||
self.ui.btnChooseEncoder.setEnabled(decoderEdit)
|
||||
|
||||
elif settings.DECODING_INVERT in element:
|
||||
txt += "All bits are inverted, i.e. 0->1 and 1->0."
|
||||
elif settings.DECODING_ENOCEAN in element:
|
||||
txt += "Remove Wireless Short-Packet (WSP) encoding that is used by EnOcean standard."
|
||||
elif settings.DECODING_DIFFERENTIAL in element:
|
||||
txt += "Every transition between low and high (0->1 or 1->0) becomes 1, no transition (0->0 or 1->1) remains 0.\n" \
|
||||
"The first signal bit is regarded as start value and directly copied.\n" \
|
||||
"Example: 0011 becomes 0010 [0|(0->0)|(0->1)|(1->1)]."
|
||||
elif settings.DECODING_BITORDER in element:
|
||||
txt += "Every byte (8 bit) is reversed, i.e. the order of the bits 01234567 (e.g. least significant bit first) " \
|
||||
"is changed to 76543210 (e.g. most significant bit first)."
|
||||
elif settings.DECODING_REDUNDANCY in element:
|
||||
txt += "If the source signal always has multiple redundant bits for one bit (e.g. 1111=1, 0000=0), the " \
|
||||
"redundancy is removed here. You have to define the number of redundant bits."
|
||||
self.ui.optionWidget.setCurrentIndex(1)
|
||||
# Values can only be changed when editing decoder, otherwise default value
|
||||
if not decoderEdit:
|
||||
self.ui.multiple.setValue(2)
|
||||
else:
|
||||
if element in self.chainoptions:
|
||||
value = self.chainoptions[element]
|
||||
if value == "":
|
||||
self.ui.multiple.setValue(2)
|
||||
else:
|
||||
self.ui.multiple.setValue(int(value))
|
||||
else:
|
||||
self.ui.multiple.setValue(2)
|
||||
self.ui.multiple.setEnabled(decoderEdit)
|
||||
elif settings.DECODING_MORSE in element:
|
||||
txt += "If the signal is a morse code, e.g. 00111001001110011100, where information are " \
|
||||
"transported with long and short sequences of 1 (0 just for padding), then this " \
|
||||
"decoding evaluates those sequences (Example output: 1011)."
|
||||
self.ui.optionWidget.setCurrentIndex(7)
|
||||
# # Values can only be changed when editing decoder, otherwise default value
|
||||
if not decoderEdit:
|
||||
self.ui.morse_low.setValue(1)
|
||||
self.ui.morse_high.setValue(3)
|
||||
self.ui.morse_wait.setValue(1)
|
||||
else:
|
||||
if element in self.chainoptions:
|
||||
value = self.chainoptions[element]
|
||||
if value == "":
|
||||
self.ui.morse_low.setValue(1)
|
||||
self.ui.morse_high.setValue(3)
|
||||
self.ui.morse_wait.setValue(1)
|
||||
else:
|
||||
try:
|
||||
l, h, w = value.split(";")
|
||||
self.ui.morse_low.setValue(int(l))
|
||||
self.ui.morse_high.setValue(int(h))
|
||||
self.ui.morse_wait.setValue(int(w))
|
||||
except ValueError:
|
||||
self.ui.morse_low.setValue(1)
|
||||
self.ui.morse_high.setValue(3)
|
||||
self.ui.morse_wait.setValue(1)
|
||||
else:
|
||||
self.ui.morse_low.setValue(1)
|
||||
self.ui.morse_high.setValue(3)
|
||||
self.ui.morse_wait.setValue(1)
|
||||
self.ui.morse_low.setEnabled(decoderEdit)
|
||||
self.ui.morse_high.setEnabled(decoderEdit)
|
||||
self.ui.morse_wait.setEnabled(decoderEdit)
|
||||
elif settings.DECODING_CARRIER in element:
|
||||
txt += "A carrier is a fixed pattern like 1_1_1_1 where the actual data lies in between, e.g. 1a1a1b1. This " \
|
||||
"function extracts the actual bit information (here: aab) from the signal at '_'/'.' positions.\n" \
|
||||
"Examples:\n" \
|
||||
"- Carrier = '1_' means 1_1_1_...\n" \
|
||||
"- Carrier = '01_' means 01_01_01_01..."
|
||||
self.ui.optionWidget.setCurrentIndex(2)
|
||||
# Values can only be changed when editing decoder, otherwise default value
|
||||
if not decoderEdit:
|
||||
self.ui.carrier.setText("1_")
|
||||
else:
|
||||
if element in self.chainoptions:
|
||||
value = self.chainoptions[element]
|
||||
if value == "":
|
||||
self.ui.carrier.setText("1_")
|
||||
else:
|
||||
self.ui.carrier.setText(value)
|
||||
else:
|
||||
self.ui.carrier.setText("1_")
|
||||
self.ui.carrier.setEnabled(decoderEdit)
|
||||
elif settings.DECODING_DATAWHITENING in element:
|
||||
txt += "Texas Instruments CC110x chips allow a data whitening that is applied before sending the signals to HF. " \
|
||||
"After a preamble (1010...) there is a fixed 16/32 bit sync word. The following data (incl. 16 bit CRC) " \
|
||||
"is masked (XOR) with the output of a LFSR.\n" \
|
||||
"This unmasks the data."
|
||||
self.ui.optionWidget.setCurrentIndex(5)
|
||||
# Values can only be changed when editing decoder, otherwise default value
|
||||
if not decoderEdit:
|
||||
self.ui.datawhitening_sync.setText("0xe9cae9ca")
|
||||
self.ui.datawhitening_polynomial.setText("0x21")
|
||||
self.ui.datawhitening_overwrite_crc.setChecked(False)
|
||||
else:
|
||||
if element in self.chainoptions:
|
||||
value = self.chainoptions[element]
|
||||
if value == "":
|
||||
self.ui.datawhitening_sync.setText("0xe9cae9ca")
|
||||
self.ui.datawhitening_polynomial.setText("0x21")
|
||||
self.ui.datawhitening_overwrite_crc.setChecked(False)
|
||||
else:
|
||||
try:
|
||||
whitening_sync, whitening_polynomial, whitening_overwrite_crc = value.split(";")
|
||||
self.ui.datawhitening_sync.setText(whitening_sync)
|
||||
self.ui.datawhitening_polynomial.setText(whitening_polynomial)
|
||||
self.ui.datawhitening_overwrite_crc.setChecked(True if whitening_overwrite_crc == "1" else False)
|
||||
|
||||
except ValueError:
|
||||
self.ui.datawhitening_sync.setText("0xe9cae9ca")
|
||||
self.ui.datawhitening_polynomial.setText("0x21")
|
||||
self.ui.datawhitening_overwrite_crc.setChecked(False)
|
||||
|
||||
self.ui.datawhitening_sync.setEnabled(decoderEdit)
|
||||
self.ui.datawhitening_polynomial.setEnabled(decoderEdit)
|
||||
self.ui.datawhitening_overwrite_crc.setEnabled(decoderEdit)
|
||||
|
||||
elif settings.DECODING_CUT in element:
|
||||
txt += "This function enables you to cut data from your messages, in order to shorten or align them for a " \
|
||||
"better view. Note that this decoding does NOT support encoding, because cut data is gone!\n" \
|
||||
"Example:\n" \
|
||||
"- Cut before '1010' would delete everything before first '1010' bits.\n" \
|
||||
"- Cut before Position = 3 (in bit) would delete the first three bits.\n"
|
||||
self.ui.optionWidget.setCurrentIndex(6)
|
||||
# Values can only be changed when editing decoder, otherwise default value
|
||||
if not decoderEdit:
|
||||
self.ui.cutmark.setText("1010")
|
||||
self.old_cutmark = self.ui.cutmark.text()
|
||||
self.ui.cutmark2.setValue(1)
|
||||
self.ui.rB_delbefore.setChecked(False)
|
||||
self.ui.rB_delafter.setChecked(False)
|
||||
self.ui.rB_delbeforepos.setChecked(False)
|
||||
self.ui.rB_delafterpos.setChecked(False)
|
||||
self.ui.cutmark.setEnabled(False)
|
||||
self.ui.cutmark2.setEnabled(False)
|
||||
else:
|
||||
if element in self.chainoptions:
|
||||
value = self.chainoptions[element]
|
||||
if value == "":
|
||||
self.ui.cutmark.setText("1010")
|
||||
self.ui.cutmark.setEnabled(True)
|
||||
self.old_cutmark = self.ui.cutmark.text()
|
||||
self.ui.cutmark2.setValue(1)
|
||||
self.ui.cutmark2.setEnabled(False)
|
||||
self.ui.rB_delbefore.setChecked(True)
|
||||
self.ui.rB_delafter.setChecked(False)
|
||||
self.ui.rB_delbeforepos.setChecked(False)
|
||||
self.ui.rB_delafterpos.setChecked(False)
|
||||
else:
|
||||
try:
|
||||
cmode, cmark = value.split(";")
|
||||
cmode = int(cmode)
|
||||
if cmode == 0:
|
||||
self.ui.rB_delbefore.setChecked(True)
|
||||
self.ui.cutmark.setEnabled(True)
|
||||
self.ui.cutmark2.setEnabled(False)
|
||||
self.ui.cutmark.setText(cmark)
|
||||
elif cmode == 1:
|
||||
self.ui.rB_delafter.setChecked(True)
|
||||
self.ui.cutmark.setEnabled(True)
|
||||
self.ui.cutmark2.setEnabled(False)
|
||||
self.ui.cutmark.setText(cmark)
|
||||
elif cmode == 2:
|
||||
self.ui.rB_delbeforepos.setChecked(True)
|
||||
self.ui.cutmark.setEnabled(False)
|
||||
self.ui.cutmark2.setEnabled(True)
|
||||
self.ui.cutmark2.setValue(int(cmark))
|
||||
elif cmode == 3:
|
||||
self.ui.rB_delafterpos.setChecked(True)
|
||||
self.ui.cutmark.setEnabled(False)
|
||||
self.ui.cutmark2.setEnabled(True)
|
||||
self.ui.cutmark2.setValue(int(cmark))
|
||||
|
||||
except ValueError:
|
||||
self.ui.cutmark.setText("1010")
|
||||
self.old_cutmark = self.ui.cutmark.text()
|
||||
self.ui.cutmark2.setValue(1)
|
||||
self.ui.rB_delbefore.setChecked(True)
|
||||
self.ui.rB_delafter.setChecked(False)
|
||||
self.ui.rB_delbeforepos.setChecked(False)
|
||||
self.ui.rB_delafterpos.setChecked(False)
|
||||
self.ui.cutmark.setEnabled(True)
|
||||
self.ui.cutmark2.setEnabled(False)
|
||||
else:
|
||||
self.ui.cutmark.setText("1010")
|
||||
self.old_cutmark = self.ui.cutmark.text()
|
||||
self.ui.cutmark2.setValue(1)
|
||||
self.ui.rB_delbefore.setChecked(True)
|
||||
self.ui.rB_delafter.setChecked(False)
|
||||
self.ui.rB_delbeforepos.setChecked(False)
|
||||
self.ui.rB_delafterpos.setChecked(False)
|
||||
self.ui.cutmark.setEnabled(True)
|
||||
self.ui.cutmark2.setEnabled(False)
|
||||
self.ui.rB_delbefore.setEnabled(decoderEdit)
|
||||
self.ui.rB_delafter.setEnabled(decoderEdit)
|
||||
self.ui.rB_delbeforepos.setEnabled(decoderEdit)
|
||||
self.ui.rB_delafterpos.setEnabled(decoderEdit)
|
||||
|
||||
self.ui.info.setText(txt)
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_datawhitening(self):
|
||||
datawhiteningstr = self.ui.datawhitening_sync.text() + ";" + self.ui.datawhitening_polynomial.text() + ";" + \
|
||||
("1" if self.ui.datawhitening_overwrite_crc.isChecked() else "0")
|
||||
if settings.DECODING_DATAWHITENING in self.active_message:
|
||||
self.chainoptions[self.active_message] = datawhiteningstr
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_external(self):
|
||||
externalstr = self.ui.external_decoder.text() + ";" + self.ui.external_encoder.text()
|
||||
if settings.DECODING_EXTERNAL in self.active_message:
|
||||
self.chainoptions[self.active_message] = externalstr
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_substitution_changed(self):
|
||||
subststr = ""
|
||||
for i in range(0, self.ui.substitution_rows.value()):
|
||||
if self.ui.substitution.item(i, 0) and self.ui.substitution.item(i, 1):
|
||||
subststr += self.ui.substitution.item(i, 0).text() + ":" + self.ui.substitution.item(i, 1).text() + ";"
|
||||
if settings.DECODING_SUBSTITUTION in self.active_message:
|
||||
self.chainoptions[self.active_message] = subststr
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_substitution_rows_changed(self):
|
||||
# Substitution Row Spinbox
|
||||
self.ui.substitution.setRowCount(self.ui.substitution_rows.value())
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_multiple_changed(self):
|
||||
# Multiple Spinbox
|
||||
val = self.ui.multiple.value()
|
||||
if settings.DECODING_REDUNDANCY in self.active_message:
|
||||
self.chainoptions[self.active_message] = val
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_morse_changed(self):
|
||||
# Multiple Spinbox
|
||||
val_low = self.ui.morse_low.value()
|
||||
val_high = self.ui.morse_high.value()
|
||||
val_wait = self.ui.morse_wait.value()
|
||||
|
||||
if val_low >= val_high:
|
||||
self.ui.morse_low.setValue(self.old_morse[0])
|
||||
self.ui.morse_high.setValue(self.old_morse[1])
|
||||
(val_low, val_high) = self.old_morse
|
||||
else:
|
||||
self.old_morse = (val_low, val_high)
|
||||
|
||||
if settings.DECODING_MORSE in self.active_message:
|
||||
self.chainoptions[self.active_message] = "{};{};{}".format(val_low, val_high, val_wait)
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_carrier_changed(self):
|
||||
# Only allow {0, 1}
|
||||
carrier_txt = self.ui.carrier.text()
|
||||
if carrier_txt.count("0") + carrier_txt.count("1") + carrier_txt.count("_") + carrier_txt.count(
|
||||
".") + carrier_txt.count("*") < len(carrier_txt):
|
||||
self.ui.carrier.setText(self.old_carrier_txt)
|
||||
else:
|
||||
self.old_carrier_txt = carrier_txt
|
||||
# Carrier Textbox
|
||||
# self.e.carrier = self.e.str2bit(self.ui.carrier.text())
|
||||
if settings.DECODING_CARRIER in self.active_message:
|
||||
self.chainoptions[self.active_message] = carrier_txt
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_cut(self):
|
||||
cmode = 0
|
||||
cmark = ""
|
||||
if self.ui.rB_delbefore.isChecked() or self.ui.rB_delafter.isChecked():
|
||||
# Activate right cutmark field
|
||||
self.ui.cutmark.setEnabled(True)
|
||||
self.ui.cutmark2.setEnabled(False)
|
||||
# set cmode
|
||||
if self.ui.rB_delafter.isChecked():
|
||||
cmode = 1
|
||||
# check values in cutmark
|
||||
cmark = self.ui.cutmark.text()
|
||||
if cmark.count("0") + cmark.count("1") < len(cmark):
|
||||
self.ui.cutmark.setText(self.old_cutmark)
|
||||
else:
|
||||
self.old_cutmark = cmark
|
||||
else:
|
||||
# Activate right cutmark field
|
||||
self.ui.cutmark.setEnabled(False)
|
||||
self.ui.cutmark2.setEnabled(True)
|
||||
# set cmode
|
||||
if self.ui.rB_delbeforepos.isChecked():
|
||||
cmode = 2
|
||||
else:
|
||||
cmode = 3
|
||||
cmark = str(self.ui.cutmark2.value())
|
||||
|
||||
cut_text = str(cmode) + ";" + cmark
|
||||
|
||||
if settings.DECODING_CUT in self.active_message:
|
||||
self.chainoptions[self.active_message] = cut_text
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_add_to_your_decoding_clicked(self):
|
||||
if self.last_selected_item != "":
|
||||
self.ui.decoderchain.addItem(self.last_selected_item)
|
||||
self.decoderchainUpdate()
|
||||
self.ui.decoderchain.setCurrentRow(self.ui.decoderchain.count()-1)
|
||||
|
||||
def dragEnterEvent(self, event: QDragEnterEvent):
|
||||
event.accept()
|
||||
|
||||
def dropEvent(self, event: QDropEvent):
|
||||
# if not self.ui.decoderchain.geometry().contains(self.mapToGlobal(event.pos())):
|
||||
if self.ui.decoderchain.currentItem() is not None:
|
||||
self.chainoptions.pop(self.ui.decoderchain.currentItem().text(), None)
|
||||
self.ui.decoderchain.takeItem(self.ui.decoderchain.currentRow())
|
||||
self.decoderchainUpdate()
|
||||
|
||||
def set_signal(self):
|
||||
indx = self.ui.combobox_signals.currentIndex()
|
||||
if indx != 0:
|
||||
self.ui.inpt.setReadOnly(True)
|
||||
else:
|
||||
self.ui.inpt.setReadOnly(False)
|
||||
self.ui.inpt.setText("10010110")
|
||||
self.decoder_update()
|
||||
return
|
||||
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
|
||||
signal = self.signals[indx - 1]
|
||||
pa = ProtocolAnalyzer(signal)
|
||||
pa.get_protocol_from_signal()
|
||||
self.ui.inpt.setText("".join(pa.plain_bits_str))
|
||||
self.ui.inpt.setCursorPosition(0)
|
||||
|
||||
if signal is not None and pa.messages:
|
||||
last_message = pa.messages[-1]
|
||||
lookup = {i: msg.bit_sample_pos for i, msg in enumerate(pa.messages)}
|
||||
|
||||
plot_data = signal.qad[lookup[0][0]:lookup[pa.num_messages - 1][len(last_message) - 1]]
|
||||
self.ui.graphicsView_signal.plot_data(plot_data)
|
||||
|
||||
self.ui.graphicsView_signal.centerOn(0, 0)
|
||||
self.unsetCursor()
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
from PyQt5.QtCore import pyqtSlot, Qt
|
||||
from PyQt5.QtWidgets import QDialog, QLabel, QRadioButton
|
||||
|
||||
from urh import settings
|
||||
from urh.signalprocessing.Filter import Filter
|
||||
from urh.ui.ui_filter_bandwidth_dialog import Ui_DialogFilterBandwidth
|
||||
|
||||
|
||||
class FilterBandwidthDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogFilterBandwidth()
|
||||
self.ui.setupUi(self)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
bw_type = settings.read("bandpass_filter_bw_type", "Medium", str)
|
||||
custom_bw = settings.read("bandpass_filter_custom_bw", 0.1, float)
|
||||
|
||||
for item in dir(self.ui):
|
||||
item = getattr(self.ui, item)
|
||||
if isinstance(item, QLabel):
|
||||
name = item.objectName().replace("label", "")
|
||||
key = next((key for key in Filter.BANDWIDTHS.keys() if name.startswith(key.replace(" ", ""))), None)
|
||||
if key is not None and name.endswith("Bandwidth"):
|
||||
item.setText("{0:n}".format(Filter.BANDWIDTHS[key]))
|
||||
elif key is not None and name.endswith("KernelLength"):
|
||||
item.setText(str(Filter.get_filter_length_from_bandwidth(Filter.BANDWIDTHS[key])))
|
||||
elif isinstance(item, QRadioButton):
|
||||
item.setChecked(bw_type.replace(" ", "_") == item.objectName().replace("radioButton", ""))
|
||||
|
||||
self.ui.doubleSpinBoxCustomBandwidth.setValue(custom_bw)
|
||||
self.ui.spinBoxCustomKernelLength.setValue(Filter.get_filter_length_from_bandwidth(custom_bw))
|
||||
|
||||
self.create_connects()
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.doubleSpinBoxCustomBandwidth.valueChanged.connect(self.on_spin_box_custom_bandwidth_value_changed)
|
||||
self.ui.spinBoxCustomKernelLength.valueChanged.connect(self.on_spin_box_custom_kernel_length_value_changed)
|
||||
self.ui.buttonBox.accepted.connect(self.on_accepted)
|
||||
|
||||
@property
|
||||
def checked_radiobutton(self):
|
||||
for rb in dir(self.ui):
|
||||
radio_button = getattr(self.ui, rb)
|
||||
if isinstance(radio_button, QRadioButton) and radio_button.isChecked():
|
||||
return radio_button
|
||||
return None
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_spin_box_custom_bandwidth_value_changed(self, bw: float):
|
||||
self.ui.spinBoxCustomKernelLength.blockSignals(True)
|
||||
self.ui.spinBoxCustomKernelLength.setValue(Filter.get_filter_length_from_bandwidth(bw))
|
||||
self.ui.spinBoxCustomKernelLength.blockSignals(False)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spin_box_custom_kernel_length_value_changed(self, filter_len: int):
|
||||
self.ui.doubleSpinBoxCustomBandwidth.blockSignals(True)
|
||||
self.ui.doubleSpinBoxCustomBandwidth.setValue(Filter.get_bandwidth_from_filter_length(filter_len))
|
||||
self.ui.doubleSpinBoxCustomBandwidth.blockSignals(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_accepted(self):
|
||||
if self.checked_radiobutton is not None:
|
||||
bw_type = self.checked_radiobutton.objectName().replace("radioButton", "").replace("_", " ")
|
||||
settings.write("bandpass_filter_bw_type", bw_type)
|
||||
|
||||
settings.write("bandpass_filter_custom_bw", self.ui.doubleSpinBoxCustomBandwidth.value())
|
||||
@@ -0,0 +1,100 @@
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt
|
||||
from PyQt5.QtWidgets import QDialog
|
||||
|
||||
from urh.signalprocessing.Filter import Filter, FilterType
|
||||
from urh.ui.ui_filter_dialog import Ui_FilterDialog
|
||||
|
||||
|
||||
class FilterDialog(QDialog):
|
||||
filter_accepted = pyqtSignal(Filter)
|
||||
|
||||
def __init__(self, dsp_filter: Filter, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_FilterDialog()
|
||||
self.ui.setupUi(self)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.error_message = ""
|
||||
|
||||
self.set_dsp_filter_status(dsp_filter.filter_type)
|
||||
self.create_connects()
|
||||
|
||||
def set_dsp_filter_status(self, dsp_filter_type: FilterType):
|
||||
if dsp_filter_type == FilterType.moving_average:
|
||||
self.ui.radioButtonMovingAverage.setChecked(True)
|
||||
self.ui.lineEditCustomTaps.setEnabled(False)
|
||||
self.ui.spinBoxNumTaps.setEnabled(True)
|
||||
elif dsp_filter_type == FilterType.dc_correction:
|
||||
self.ui.radioButtonDCcorrection.setChecked(True)
|
||||
self.ui.lineEditCustomTaps.setEnabled(False)
|
||||
self.ui.spinBoxNumTaps.setEnabled(False)
|
||||
else:
|
||||
self.ui.radioButtonCustomTaps.setChecked(True)
|
||||
self.ui.spinBoxNumTaps.setEnabled(True)
|
||||
self.ui.lineEditCustomTaps.setEnabled(True)
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.radioButtonMovingAverage.clicked.connect(self.on_radio_button_moving_average_clicked)
|
||||
self.ui.radioButtonCustomTaps.clicked.connect(self.on_radio_button_custom_taps_clicked)
|
||||
self.ui.radioButtonDCcorrection.clicked.connect(self.on_radio_button_dc_correction_clicked)
|
||||
|
||||
self.ui.spinBoxNumTaps.valueChanged.connect(self.set_error_status)
|
||||
self.ui.lineEditCustomTaps.textEdited.connect(self.set_error_status)
|
||||
self.ui.buttonBox.accepted.connect(self.on_accept_clicked)
|
||||
self.ui.buttonBox.rejected.connect(self.reject)
|
||||
|
||||
def build_filter(self) -> Filter:
|
||||
if self.ui.radioButtonMovingAverage.isChecked():
|
||||
n = self.ui.spinBoxNumTaps.value()
|
||||
return Filter([1/n for _ in range(n)], filter_type=FilterType.moving_average)
|
||||
elif self.ui.radioButtonDCcorrection.isChecked():
|
||||
return Filter([], filter_type=FilterType.dc_correction)
|
||||
else:
|
||||
# custom filter
|
||||
try:
|
||||
taps = eval(self.ui.lineEditCustomTaps.text())
|
||||
try:
|
||||
taps = list(map(float, taps))
|
||||
self.error_message = ""
|
||||
return Filter(taps)
|
||||
except (ValueError, TypeError) as e:
|
||||
self.error_message = "Error casting taps:\n" + str(e)
|
||||
return None
|
||||
|
||||
except SyntaxError as e:
|
||||
self.error_message = "Error parsing taps:\n" + str(e)
|
||||
return None
|
||||
|
||||
def set_error_status(self):
|
||||
dsp_filter = self.build_filter()
|
||||
if dsp_filter is None:
|
||||
self.ui.lineEditCustomTaps.setStyleSheet("background: red")
|
||||
self.ui.lineEditCustomTaps.setToolTip(self.error_message)
|
||||
elif len(dsp_filter.taps) != self.ui.spinBoxNumTaps.value():
|
||||
self.ui.lineEditCustomTaps.setStyleSheet("background: yellow")
|
||||
self.ui.lineEditCustomTaps.setToolTip("The number of the filter taps does not match the configured number of taps. I will use your configured filter taps.")
|
||||
else:
|
||||
self.ui.lineEditCustomTaps.setStyleSheet("")
|
||||
self.ui.lineEditCustomTaps.setToolTip("")
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_radio_button_moving_average_clicked(self, checked: bool):
|
||||
if checked:
|
||||
self.set_dsp_filter_status(FilterType.moving_average)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_radio_button_custom_taps_clicked(self, checked: bool):
|
||||
if checked:
|
||||
self.set_dsp_filter_status(FilterType.custom)
|
||||
self.set_error_status()
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_radio_button_dc_correction_clicked(self, checked: bool):
|
||||
if checked:
|
||||
self.set_dsp_filter_status(FilterType.dc_correction)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_accept_clicked(self):
|
||||
dsp_filter = self.build_filter()
|
||||
self.filter_accepted.emit(dsp_filter)
|
||||
self.accept()
|
||||
@@ -0,0 +1,362 @@
|
||||
import math
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QCloseEvent
|
||||
from PyQt5.QtWidgets import QDialog, QInputDialog
|
||||
|
||||
from urh import settings
|
||||
from urh.models.FuzzingTableModel import FuzzingTableModel
|
||||
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
|
||||
from urh.signalprocessing.ProtocolAnalyzerContainer import ProtocolAnalyzerContainer
|
||||
from urh.ui.ui_fuzzing import Ui_FuzzingDialog
|
||||
|
||||
|
||||
class FuzzingDialog(QDialog):
|
||||
def __init__(self, protocol: ProtocolAnalyzerContainer, label_index: int, msg_index: int, proto_view: int,
|
||||
parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_FuzzingDialog()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.protocol = protocol
|
||||
msg_index = msg_index if msg_index != -1 else 0
|
||||
self.ui.spinBoxFuzzMessage.setValue(msg_index + 1)
|
||||
self.ui.spinBoxFuzzMessage.setMinimum(1)
|
||||
self.ui.spinBoxFuzzMessage.setMaximum(self.protocol.num_messages)
|
||||
|
||||
self.ui.comboBoxFuzzingLabel.addItems([l.name for l in self.message.message_type])
|
||||
self.ui.comboBoxFuzzingLabel.setCurrentIndex(label_index)
|
||||
|
||||
self.proto_view = proto_view
|
||||
self.fuzz_table_model = FuzzingTableModel(self.current_label, proto_view)
|
||||
self.fuzz_table_model.remove_duplicates = self.ui.chkBRemoveDuplicates.isChecked()
|
||||
self.ui.tblFuzzingValues.setModel(self.fuzz_table_model)
|
||||
self.fuzz_table_model.update()
|
||||
|
||||
self.ui.spinBoxFuzzingStart.setValue(self.current_label_start + 1)
|
||||
self.ui.spinBoxFuzzingEnd.setValue(self.current_label_end)
|
||||
self.ui.spinBoxFuzzingStart.setMaximum(len(self.message_data))
|
||||
self.ui.spinBoxFuzzingEnd.setMaximum(len(self.message_data))
|
||||
|
||||
self.update_message_data_string()
|
||||
self.ui.tblFuzzingValues.resize_me()
|
||||
|
||||
self.create_connects()
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
return self.protocol.messages[int(self.ui.spinBoxFuzzMessage.value() - 1)]
|
||||
|
||||
@property
|
||||
def current_label_index(self):
|
||||
return self.ui.comboBoxFuzzingLabel.currentIndex()
|
||||
|
||||
@property
|
||||
def current_label(self) -> ProtocolLabel:
|
||||
if len(self.message.message_type) == 0:
|
||||
return None
|
||||
|
||||
cur_label = self.message.message_type[self.current_label_index].get_copy()
|
||||
self.message.message_type[self.current_label_index] = cur_label
|
||||
cur_label.fuzz_values = [fv for fv in cur_label.fuzz_values if fv] # Remove empty strings
|
||||
|
||||
if len(cur_label.fuzz_values) == 0:
|
||||
cur_label.fuzz_values.append(self.message.plain_bits_str[cur_label.start:cur_label.end])
|
||||
return cur_label
|
||||
|
||||
@property
|
||||
def current_label_start(self):
|
||||
if self.current_label and self.message:
|
||||
return self.message.get_label_range(self.current_label, self.proto_view, False)[0]
|
||||
else:
|
||||
return -1
|
||||
|
||||
@property
|
||||
def current_label_end(self):
|
||||
if self.current_label and self.message:
|
||||
return self.message.get_label_range(self.current_label, self.proto_view, False)[1]
|
||||
else:
|
||||
return -1
|
||||
|
||||
@property
|
||||
def message_data(self):
|
||||
if self.proto_view == 0:
|
||||
return self.message.plain_bits_str
|
||||
elif self.proto_view == 1:
|
||||
return self.message.plain_hex_str
|
||||
elif self.proto_view == 2:
|
||||
return self.message.plain_ascii_str
|
||||
else:
|
||||
return None
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.spinBoxFuzzingStart.valueChanged.connect(self.on_fuzzing_start_changed)
|
||||
self.ui.spinBoxFuzzingEnd.valueChanged.connect(self.on_fuzzing_end_changed)
|
||||
self.ui.comboBoxFuzzingLabel.currentIndexChanged.connect(self.on_combo_box_fuzzing_label_current_index_changed)
|
||||
self.ui.btnRepeatValues.clicked.connect(self.on_btn_repeat_values_clicked)
|
||||
self.ui.btnAddRow.clicked.connect(self.on_btn_add_row_clicked)
|
||||
self.ui.btnDelRow.clicked.connect(self.on_btn_del_row_clicked)
|
||||
self.ui.tblFuzzingValues.deletion_wanted.connect(self.delete_lines)
|
||||
self.ui.chkBRemoveDuplicates.stateChanged.connect(self.on_remove_duplicates_state_changed)
|
||||
self.ui.sBAddRangeStart.valueChanged.connect(self.on_fuzzing_range_start_changed)
|
||||
self.ui.sBAddRangeEnd.valueChanged.connect(self.on_fuzzing_range_end_changed)
|
||||
self.ui.checkBoxLowerBound.stateChanged.connect(self.on_lower_bound_checked_changed)
|
||||
self.ui.checkBoxUpperBound.stateChanged.connect(self.on_upper_bound_checked_changed)
|
||||
self.ui.spinBoxLowerBound.valueChanged.connect(self.on_lower_bound_changed)
|
||||
self.ui.spinBoxUpperBound.valueChanged.connect(self.on_upper_bound_changed)
|
||||
self.ui.spinBoxRandomMinimum.valueChanged.connect(self.on_random_range_min_changed)
|
||||
self.ui.spinBoxRandomMaximum.valueChanged.connect(self.on_random_range_max_changed)
|
||||
self.ui.spinBoxFuzzMessage.valueChanged.connect(self.on_fuzz_msg_changed)
|
||||
self.ui.btnAddFuzzingValues.clicked.connect(self.on_btn_add_fuzzing_values_clicked)
|
||||
self.ui.comboBoxFuzzingLabel.editTextChanged.connect(self.set_current_label_name)
|
||||
|
||||
def update_message_data_string(self):
|
||||
fuz_start = self.current_label_start
|
||||
fuz_end = self.current_label_end
|
||||
num_proto_bits = 10
|
||||
num_fuz_bits = 16
|
||||
|
||||
proto_start = fuz_start - num_proto_bits
|
||||
preambel = "... "
|
||||
if proto_start <= 0:
|
||||
proto_start = 0
|
||||
preambel = ""
|
||||
|
||||
proto_end = fuz_end + num_proto_bits
|
||||
postambel = " ..."
|
||||
if proto_end >= len(self.message_data) - 1:
|
||||
proto_end = len(self.message_data) - 1
|
||||
postambel = ""
|
||||
|
||||
fuzamble = ""
|
||||
if fuz_end - fuz_start > num_fuz_bits:
|
||||
fuz_end = fuz_start + num_fuz_bits
|
||||
fuzamble = "..."
|
||||
|
||||
self.ui.lPreBits.setText(preambel + self.message_data[proto_start:self.current_label_start])
|
||||
self.ui.lFuzzedBits.setText(self.message_data[fuz_start:fuz_end] + fuzamble)
|
||||
self.ui.lPostBits.setText(self.message_data[self.current_label_end:proto_end] + postambel)
|
||||
self.set_add_spinboxes_maximum_on_label_change()
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_fuzzing_start_changed(self, value: int):
|
||||
self.ui.spinBoxFuzzingEnd.setMinimum(self.ui.spinBoxFuzzingStart.value())
|
||||
new_start = self.message.convert_index(value - 1, self.proto_view, 0, False)[0]
|
||||
self.current_label.start = new_start
|
||||
self.current_label.fuzz_values[:] = []
|
||||
self.update_message_data_string()
|
||||
self.fuzz_table_model.update()
|
||||
self.ui.tblFuzzingValues.resize_me()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_fuzzing_end_changed(self, value: int):
|
||||
self.ui.spinBoxFuzzingStart.setMaximum(self.ui.spinBoxFuzzingEnd.value())
|
||||
new_end = self.message.convert_index(value - 1, self.proto_view, 0, False)[1] + 1
|
||||
self.current_label.end = new_end
|
||||
self.current_label.fuzz_values[:] = []
|
||||
self.update_message_data_string()
|
||||
self.fuzz_table_model.update()
|
||||
self.ui.tblFuzzingValues.resize_me()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combo_box_fuzzing_label_current_index_changed(self, index: int):
|
||||
self.fuzz_table_model.fuzzing_label = self.current_label
|
||||
self.fuzz_table_model.update()
|
||||
self.update_message_data_string()
|
||||
self.ui.tblFuzzingValues.resize_me()
|
||||
|
||||
self.ui.spinBoxFuzzingStart.blockSignals(True)
|
||||
self.ui.spinBoxFuzzingStart.setValue(self.current_label_start + 1)
|
||||
self.ui.spinBoxFuzzingStart.blockSignals(False)
|
||||
|
||||
self.ui.spinBoxFuzzingEnd.blockSignals(True)
|
||||
self.ui.spinBoxFuzzingEnd.setValue(self.current_label_end)
|
||||
self.ui.spinBoxFuzzingEnd.blockSignals(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_add_row_clicked(self):
|
||||
self.current_label.add_fuzz_value()
|
||||
self.fuzz_table_model.update()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_del_row_clicked(self):
|
||||
min_row, max_row, _, _ = self.ui.tblFuzzingValues.selection_range()
|
||||
self.delete_lines(min_row, max_row)
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
def delete_lines(self, min_row, max_row):
|
||||
if min_row == -1:
|
||||
self.current_label.fuzz_values = self.current_label.fuzz_values[:-1]
|
||||
else:
|
||||
self.current_label.fuzz_values = self.current_label.fuzz_values[:min_row] + self.current_label.fuzz_values[
|
||||
max_row + 1:]
|
||||
|
||||
_ = self.current_label # if user deleted all, this will restore a fuzz value
|
||||
|
||||
self.fuzz_table_model.update()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_remove_duplicates_state_changed(self):
|
||||
self.fuzz_table_model.remove_duplicates = self.ui.chkBRemoveDuplicates.isChecked()
|
||||
self.fuzz_table_model.update()
|
||||
self.remove_duplicates()
|
||||
|
||||
@pyqtSlot()
|
||||
def set_add_spinboxes_maximum_on_label_change(self):
|
||||
nbits = self.current_label.end - self.current_label.start # Use Bit Start/End for maximum calc.
|
||||
if nbits >= 32:
|
||||
nbits = 31
|
||||
max_val = 2 ** nbits - 1
|
||||
self.ui.sBAddRangeStart.setMaximum(max_val - 1)
|
||||
self.ui.sBAddRangeEnd.setMaximum(max_val)
|
||||
self.ui.sBAddRangeEnd.setValue(max_val)
|
||||
self.ui.sBAddRangeStep.setMaximum(max_val)
|
||||
self.ui.spinBoxLowerBound.setMaximum(max_val - 1)
|
||||
self.ui.spinBoxUpperBound.setMaximum(max_val)
|
||||
self.ui.spinBoxUpperBound.setValue(max_val)
|
||||
self.ui.spinBoxBoundaryNumber.setMaximum(int(max_val / 2) + 1)
|
||||
self.ui.spinBoxRandomMinimum.setMaximum(max_val - 1)
|
||||
self.ui.spinBoxRandomMaximum.setMaximum(max_val)
|
||||
self.ui.spinBoxRandomMaximum.setValue(max_val)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_fuzzing_range_start_changed(self, value: int):
|
||||
self.ui.sBAddRangeEnd.setMinimum(value)
|
||||
self.ui.sBAddRangeStep.setMaximum(self.ui.sBAddRangeEnd.value() - value)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_fuzzing_range_end_changed(self, value: int):
|
||||
self.ui.sBAddRangeStart.setMaximum(value - 1)
|
||||
self.ui.sBAddRangeStep.setMaximum(value - self.ui.sBAddRangeStart.value())
|
||||
|
||||
@pyqtSlot()
|
||||
def on_lower_bound_checked_changed(self):
|
||||
if self.ui.checkBoxLowerBound.isChecked():
|
||||
self.ui.spinBoxLowerBound.setEnabled(True)
|
||||
self.ui.spinBoxBoundaryNumber.setEnabled(True)
|
||||
elif not self.ui.checkBoxUpperBound.isChecked():
|
||||
self.ui.spinBoxLowerBound.setEnabled(False)
|
||||
self.ui.spinBoxBoundaryNumber.setEnabled(False)
|
||||
else:
|
||||
self.ui.spinBoxLowerBound.setEnabled(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_upper_bound_checked_changed(self):
|
||||
if self.ui.checkBoxUpperBound.isChecked():
|
||||
self.ui.spinBoxUpperBound.setEnabled(True)
|
||||
self.ui.spinBoxBoundaryNumber.setEnabled(True)
|
||||
elif not self.ui.checkBoxLowerBound.isChecked():
|
||||
self.ui.spinBoxUpperBound.setEnabled(False)
|
||||
self.ui.spinBoxBoundaryNumber.setEnabled(False)
|
||||
else:
|
||||
self.ui.spinBoxUpperBound.setEnabled(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_lower_bound_changed(self):
|
||||
self.ui.spinBoxUpperBound.setMinimum(self.ui.spinBoxLowerBound.value())
|
||||
self.ui.spinBoxBoundaryNumber.setMaximum(math.ceil((self.ui.spinBoxUpperBound.value()
|
||||
- self.ui.spinBoxLowerBound.value()) / 2))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_upper_bound_changed(self):
|
||||
self.ui.spinBoxLowerBound.setMaximum(self.ui.spinBoxUpperBound.value() - 1)
|
||||
self.ui.spinBoxBoundaryNumber.setMaximum(math.ceil((self.ui.spinBoxUpperBound.value()
|
||||
- self.ui.spinBoxLowerBound.value()) / 2))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_random_range_min_changed(self):
|
||||
self.ui.spinBoxRandomMaximum.setMinimum(self.ui.spinBoxRandomMinimum.value())
|
||||
|
||||
@pyqtSlot()
|
||||
def on_random_range_max_changed(self):
|
||||
self.ui.spinBoxRandomMinimum.setMaximum(self.ui.spinBoxRandomMaximum.value() - 1)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_add_fuzzing_values_clicked(self):
|
||||
if self.ui.comboBoxStrategy.currentIndex() == 0:
|
||||
self.__add_fuzzing_range()
|
||||
elif self.ui.comboBoxStrategy.currentIndex() == 1:
|
||||
self.__add_fuzzing_boundaries()
|
||||
elif self.ui.comboBoxStrategy.currentIndex() == 2:
|
||||
self.__add_random_fuzzing_values()
|
||||
|
||||
def __add_fuzzing_range(self):
|
||||
start = self.ui.sBAddRangeStart.value()
|
||||
end = self.ui.sBAddRangeEnd.value()
|
||||
step = self.ui.sBAddRangeStep.value()
|
||||
self.fuzz_table_model.add_range(start, end + 1, step)
|
||||
|
||||
def __add_fuzzing_boundaries(self):
|
||||
lower_bound = -1
|
||||
if self.ui.spinBoxLowerBound.isEnabled():
|
||||
lower_bound = self.ui.spinBoxLowerBound.value()
|
||||
|
||||
upper_bound = -1
|
||||
if self.ui.spinBoxUpperBound.isEnabled():
|
||||
upper_bound = self.ui.spinBoxUpperBound.value()
|
||||
|
||||
num_vals = self.ui.spinBoxBoundaryNumber.value()
|
||||
self.fuzz_table_model.add_boundaries(lower_bound, upper_bound, num_vals)
|
||||
|
||||
def __add_random_fuzzing_values(self):
|
||||
n = self.ui.spinBoxNumberRandom.value()
|
||||
minimum = self.ui.spinBoxRandomMinimum.value()
|
||||
maximum = self.ui.spinBoxRandomMaximum.value()
|
||||
self.fuzz_table_model.add_random(n, minimum, maximum)
|
||||
|
||||
def remove_duplicates(self):
|
||||
if self.ui.chkBRemoveDuplicates.isChecked():
|
||||
for lbl in self.message.message_type:
|
||||
seq = lbl.fuzz_values[:]
|
||||
seen = set()
|
||||
add_seen = seen.add
|
||||
lbl.fuzz_values = [l for l in seq if not (l in seen or add_seen(l))]
|
||||
|
||||
@pyqtSlot()
|
||||
def set_current_label_name(self):
|
||||
self.current_label.name = self.ui.comboBoxFuzzingLabel.currentText()
|
||||
self.ui.comboBoxFuzzingLabel.setItemText(self.ui.comboBoxFuzzingLabel.currentIndex(), self.current_label.name)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_fuzz_msg_changed(self, index: int):
|
||||
self.ui.comboBoxFuzzingLabel.setDisabled(False)
|
||||
|
||||
sel_label_ind = self.ui.comboBoxFuzzingLabel.currentIndex()
|
||||
self.ui.comboBoxFuzzingLabel.blockSignals(True)
|
||||
self.ui.comboBoxFuzzingLabel.clear()
|
||||
|
||||
if len(self.message.message_type) == 0:
|
||||
self.ui.comboBoxFuzzingLabel.setDisabled(True)
|
||||
return
|
||||
|
||||
self.ui.comboBoxFuzzingLabel.addItems([lbl.name for lbl in self.message.message_type])
|
||||
self.ui.comboBoxFuzzingLabel.blockSignals(False)
|
||||
|
||||
if sel_label_ind < self.ui.comboBoxFuzzingLabel.count():
|
||||
self.ui.comboBoxFuzzingLabel.setCurrentIndex(sel_label_ind)
|
||||
else:
|
||||
self.ui.comboBoxFuzzingLabel.setCurrentIndex(0)
|
||||
|
||||
self.fuzz_table_model.fuzzing_label = self.current_label
|
||||
self.fuzz_table_model.update()
|
||||
self.update_message_data_string()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_repeat_values_clicked(self):
|
||||
num_repeats, ok = QInputDialog.getInt(self, self.tr("How many times shall values be repeated?"),
|
||||
self.tr("Number of repeats:"), 1, 1)
|
||||
if ok:
|
||||
self.ui.chkBRemoveDuplicates.setChecked(False)
|
||||
min_row, max_row, _, _ = self.ui.tblFuzzingValues.selection_range()
|
||||
if min_row == -1:
|
||||
start, end = 0, len(self.current_label.fuzz_values)
|
||||
else:
|
||||
start, end = min_row, max_row + 1
|
||||
self.fuzz_table_model.repeat_fuzzing_values(start, end, num_repeats)
|
||||
@@ -0,0 +1,111 @@
|
||||
import copy
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QCloseEvent
|
||||
from PyQt5.QtWidgets import QDialog
|
||||
|
||||
from urh import settings
|
||||
from urh.models.RulesetTableModel import RulesetTableModel
|
||||
from urh.signalprocessing import Ruleset
|
||||
from urh.signalprocessing.MessageType import MessageType
|
||||
from urh.signalprocessing.Ruleset import Rule, OPERATION_DESCRIPTION
|
||||
from urh.ui.delegates.ComboBoxDelegate import ComboBoxDelegate
|
||||
from urh.ui.ui_messagetype_options import Ui_DialogMessageType
|
||||
|
||||
|
||||
class MessageTypeDialog(QDialog):
|
||||
|
||||
def __init__(self, message_type: MessageType, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogMessageType()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
operator_descriptions = list(OPERATION_DESCRIPTION.values())
|
||||
operator_descriptions.sort()
|
||||
|
||||
self.setWindowTitle(self.tr("Rules for {}".format(message_type.name)))
|
||||
self.message_type = message_type
|
||||
self.original_ruleset = copy.deepcopy(message_type.ruleset)
|
||||
self.original_assigned_status = message_type.assigned_by_ruleset
|
||||
self.ruleset_table_model = RulesetTableModel(message_type.ruleset, operator_descriptions, parent=self)
|
||||
self.ui.tblViewRuleset.setModel(self.ruleset_table_model)
|
||||
|
||||
self.ui.btnRemoveRule.setEnabled(len(message_type.ruleset) > 0)
|
||||
self.set_ruleset_ui_status()
|
||||
|
||||
self.ui.rbAssignAutomatically.setChecked(self.message_type.assigned_by_ruleset)
|
||||
self.ui.rbAssignManually.setChecked(self.message_type.assign_manually)
|
||||
|
||||
self.ui.tblViewRuleset.setItemDelegateForColumn(2, ComboBoxDelegate(["Bit", "Hex", "ASCII"], parent=self))
|
||||
self.ui.tblViewRuleset.setItemDelegateForColumn(3, ComboBoxDelegate(operator_descriptions, parent=self))
|
||||
|
||||
for i in range(len(message_type.ruleset)):
|
||||
self.open_editors(i)
|
||||
|
||||
self.ui.cbRulesetMode.setCurrentIndex(self.message_type.ruleset.mode.value)
|
||||
|
||||
self.create_connects()
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.btnAddRule.clicked.connect(self.on_btn_add_rule_clicked)
|
||||
self.ui.btnRemoveRule.clicked.connect(self.on_btn_remove_rule_clicked)
|
||||
self.ui.rbAssignAutomatically.clicked.connect(self.on_rb_assign_automatically_clicked)
|
||||
self.ui.rbAssignManually.clicked.connect(self.on_rb_assign_manually_clicked)
|
||||
self.ui.cbRulesetMode.currentIndexChanged.connect(self.on_cb_rulesetmode_current_index_changed)
|
||||
|
||||
self.ui.buttonBox.accepted.connect(self.accept)
|
||||
self.ui.buttonBox.rejected.connect(self.on_rejected)
|
||||
|
||||
def set_ruleset_ui_status(self):
|
||||
self.ui.tblViewRuleset.setEnabled(self.message_type.assigned_by_ruleset)
|
||||
self.ui.btnRemoveRule.setEnabled(self.message_type.assigned_by_ruleset and len(self.message_type.ruleset) > 0)
|
||||
self.ui.btnAddRule.setEnabled(self.message_type.assigned_by_ruleset)
|
||||
self.ui.cbRulesetMode.setEnabled(self.message_type.assigned_by_ruleset)
|
||||
|
||||
def open_editors(self, row):
|
||||
self.ui.tblViewRuleset.openPersistentEditor(self.ruleset_table_model.index(row, 2))
|
||||
self.ui.tblViewRuleset.openPersistentEditor(self.ruleset_table_model.index(row, 3))
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
self.ui.tblViewRuleset.setItemDelegateForColumn(2, None)
|
||||
self.ui.tblViewRuleset.setItemDelegateForColumn(3, None)
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_rejected(self):
|
||||
self.message_type.ruleset = self.original_ruleset
|
||||
self.message_type.assigned_by_ruleset = self.original_assigned_status
|
||||
self.reject()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_add_rule_clicked(self):
|
||||
self.ui.btnRemoveRule.setEnabled(True)
|
||||
self.message_type.ruleset.append(Rule(start=0, end=0, operator="=", target_value="1", value_type=0))
|
||||
self.ruleset_table_model.update()
|
||||
|
||||
for i in range(len(self.message_type.ruleset)):
|
||||
self.open_editors(i)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_remove_rule_clicked(self):
|
||||
self.ruleset_table_model.ruleset.remove(self.message_type.ruleset[-1])
|
||||
self.ruleset_table_model.update()
|
||||
self.ui.btnRemoveRule.setEnabled(len(self.message_type.ruleset) > 0)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_rb_assign_automatically_clicked(self):
|
||||
self.message_type.assigned_by_ruleset = True
|
||||
self.set_ruleset_ui_status()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_rb_assign_manually_clicked(self):
|
||||
self.message_type.assigned_by_ruleset = False
|
||||
self.set_ruleset_ui_status()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_cb_rulesetmode_current_index_changed(self, index: int):
|
||||
self.message_type.ruleset.mode = Ruleset.Mode(index)
|
||||
@@ -0,0 +1,68 @@
|
||||
import math
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtWidgets import QDialog, QTableWidgetItem
|
||||
|
||||
from urh.ui.delegates.KillerSpinBoxDelegate import KillerSpinBoxDelegate
|
||||
from urh.ui.delegates.SpinBoxDelegate import SpinBoxDelegate
|
||||
from urh.ui.ui_modulation_parameters_dialog import Ui_DialogModulationParameters
|
||||
|
||||
|
||||
class ModulationParametersDialog(QDialog):
|
||||
def __init__(self, parameters: list, modulation_type: str, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogModulationParameters()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.parameters = parameters
|
||||
self.num_bits = int(math.log2(len(parameters)))
|
||||
|
||||
if "FSK" in modulation_type:
|
||||
self.ui.tblSymbolParameters.setItemDelegateForColumn(1, KillerSpinBoxDelegate(-1e12, 1e12, self))
|
||||
self.ui.tblSymbolParameters.horizontalHeaderItem(1).setText("Frequency in Hz")
|
||||
elif "ASK" in modulation_type:
|
||||
self.ui.tblSymbolParameters.horizontalHeaderItem(1).setText("Amplitude")
|
||||
self.ui.tblSymbolParameters.setItemDelegateForColumn(1, SpinBoxDelegate(0, 100, self, "%"))
|
||||
elif "PSK" in modulation_type:
|
||||
self.ui.tblSymbolParameters.setItemDelegateForColumn(1, SpinBoxDelegate(-360, 360, self, "°"))
|
||||
self.ui.tblSymbolParameters.horizontalHeaderItem(1).setText("Phase")
|
||||
|
||||
fmt = "{0:0" + str(self.num_bits) + "b}"
|
||||
self.ui.tblSymbolParameters.setRowCount(len(parameters))
|
||||
for i, parameter in enumerate(parameters):
|
||||
item = QTableWidgetItem(fmt.format(i))
|
||||
font = item.font()
|
||||
font.setBold(True)
|
||||
item.setFont(font)
|
||||
item.setFlags(Qt.ItemIsEnabled)
|
||||
self.ui.tblSymbolParameters.setItem(i, 0, item)
|
||||
|
||||
item = QTableWidgetItem()
|
||||
item.setData(Qt.DisplayRole, self.parameters[i])
|
||||
self.ui.tblSymbolParameters.setItem(i, 1, item)
|
||||
self.ui.tblSymbolParameters.openPersistentEditor(self.ui.tblSymbolParameters.item(i, 1))
|
||||
|
||||
self.create_connects()
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.buttonBox.accepted.connect(self.on_accepted)
|
||||
self.ui.buttonBox.rejected.connect(self.reject)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_accepted(self):
|
||||
for i in range(self.ui.tblSymbolParameters.rowCount()):
|
||||
self.parameters[i] = float(self.ui.tblSymbolParameters.item(i, 1).text())
|
||||
|
||||
self.accept()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
app = QApplication(["urh"])
|
||||
|
||||
dialog = ModulationParametersDialog([0, 100.0], "ASK")
|
||||
dialog.show()
|
||||
|
||||
app.exec_()
|
||||
@@ -0,0 +1,636 @@
|
||||
from array import array
|
||||
|
||||
import numpy
|
||||
from PyQt5.QtCore import Qt, pyqtSlot, QRegExp, QTimer
|
||||
from PyQt5.QtGui import QCloseEvent, QResizeEvent, QKeyEvent, QIcon, QRegExpValidator
|
||||
from PyQt5.QtWidgets import QDialog, QMessageBox, QLineEdit
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.dialogs.ModulationParametersDialog import ModulationParametersDialog
|
||||
from urh.signalprocessing.IQArray import IQArray
|
||||
from urh.signalprocessing.Modulator import Modulator
|
||||
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
|
||||
from urh.ui.ui_modulation import Ui_DialogModulation
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class ModulatorDialog(QDialog):
|
||||
def __init__(self, modulators, tree_model=None, parent=None):
|
||||
"""
|
||||
:type modulators: list of Modulator
|
||||
"""
|
||||
super().__init__(parent)
|
||||
|
||||
self.ui = Ui_DialogModulation()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.lock_samples_in_view = False
|
||||
|
||||
if tree_model is not None:
|
||||
self.ui.treeViewSignals.setModel(tree_model)
|
||||
self.ui.treeViewSignals.expandAll()
|
||||
self.ui.gVOriginalSignal.signal_tree_root = tree_model.rootItem
|
||||
|
||||
self.ui.comboBoxCustomModulations.clear()
|
||||
for modulator in modulators:
|
||||
self.ui.comboBoxCustomModulations.addItem(modulator.name)
|
||||
if len(modulators) == 1:
|
||||
self.ui.btnRemoveModulation.setDisabled(True)
|
||||
|
||||
self.modulators = modulators
|
||||
|
||||
self.set_ui_for_current_modulator()
|
||||
|
||||
self.ui.cbShowDataBitsOnly.setText(self.tr("Show Only Data Sequence\n"))
|
||||
self.ui.cbShowDataBitsOnly.setEnabled(False)
|
||||
self.protocol = None # type: ProtocolAnalyzer
|
||||
self.search_results = []
|
||||
self.ui.cbShowDataBitsOnly.setEnabled(False)
|
||||
self.ui.btnSearchNext.setEnabled(False)
|
||||
self.ui.btnSearchPrev.setEnabled(False)
|
||||
|
||||
self.ui.chkBoxLockSIV.setDisabled(True)
|
||||
|
||||
self.original_bits = ""
|
||||
|
||||
self.restore_bits_action = self.ui.linEdDataBits.addAction(QIcon.fromTheme("edit-undo"),
|
||||
QLineEdit.TrailingPosition)
|
||||
self.restore_bits_action.setEnabled(False)
|
||||
|
||||
self.configure_parameters_action = self.ui.lineEditParameters.addAction(QIcon.fromTheme("configure"),
|
||||
QLineEdit.TrailingPosition)
|
||||
|
||||
self.create_connects()
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
self.set_bits_per_symbol_enabled_status()
|
||||
self.set_modulation_profile_status()
|
||||
|
||||
# Ensure full screen is shown after resize
|
||||
QTimer.singleShot(100, self.show_full_scene)
|
||||
|
||||
def __cur_selected_mod_type(self):
|
||||
s = self.ui.comboBoxModulationType.currentText()
|
||||
return s[s.rindex("(") + 1:s.rindex(")")]
|
||||
|
||||
@staticmethod
|
||||
def __trim_number(number):
|
||||
if abs(number) >= 1e9: # giga
|
||||
return numpy.round(number / 1e9) * 1e9
|
||||
elif abs(number) >= 1e6: # mega
|
||||
return numpy.round(number / 1e6) * 1e6
|
||||
elif abs(number) >= 1e3: # Kilo
|
||||
return numpy.round(number / 1e3) * 1e3
|
||||
else:
|
||||
return number
|
||||
|
||||
@staticmethod
|
||||
def __ensure_multitude(num1, num2):
|
||||
try:
|
||||
if abs(num1) > abs(num2):
|
||||
num1 = abs(int(num1 / num2)) * num2
|
||||
else:
|
||||
num2 = abs(int(num2 / num1)) * num1
|
||||
return num1, num2
|
||||
except Exception:
|
||||
return num1, num2
|
||||
|
||||
def __set_gauss_ui_visibility(self, show: bool):
|
||||
self.ui.lGaussBT.setVisible(show)
|
||||
self.ui.lGaussWidth.setVisible(show)
|
||||
self.ui.spinBoxGaussBT.setVisible(show)
|
||||
self.ui.spinBoxGaussFilterWidth.setVisible(show)
|
||||
|
||||
self.ui.spinBoxGaussFilterWidth.setValue(self.current_modulator.gauss_filter_width)
|
||||
self.ui.spinBoxGaussBT.setValue(self.current_modulator.gauss_bt)
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
self.ui.lineEditParameters.editingFinished.emit()
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
|
||||
for gv in (self.ui.gVCarrier, self.ui.gVData, self.ui.gVModulated, self.ui.gVOriginalSignal):
|
||||
# Eliminate graphic views to prevent segfaults
|
||||
gv.eliminate()
|
||||
|
||||
super().closeEvent(event)
|
||||
|
||||
@property
|
||||
def current_modulator(self):
|
||||
return self.modulators[self.ui.comboBoxCustomModulations.currentIndex()]
|
||||
|
||||
def set_ui_for_current_modulator(self):
|
||||
index = self.ui.comboBoxModulationType.findText("*(" + self.current_modulator.modulation_type + ")",
|
||||
Qt.MatchWildcard)
|
||||
self.ui.comboBoxModulationType.setCurrentIndex(index)
|
||||
self.ui.doubleSpinBoxCarrierFreq.setValue(self.current_modulator.carrier_freq_hz)
|
||||
self.ui.doubleSpinBoxCarrierPhase.setValue(self.current_modulator.carrier_phase_deg)
|
||||
self.ui.spinBoxSamplesPerSymbol.setValue(self.current_modulator.samples_per_symbol)
|
||||
self.ui.spinBoxSampleRate.setValue(self.current_modulator.sample_rate)
|
||||
self.ui.spinBoxBitsPerSymbol.setValue(self.current_modulator.bits_per_symbol)
|
||||
|
||||
self.update_modulation_parameters()
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.doubleSpinBoxCarrierFreq.valueChanged.connect(self.on_carrier_freq_changed)
|
||||
self.ui.doubleSpinBoxCarrierPhase.valueChanged.connect(self.on_carrier_phase_changed)
|
||||
self.ui.spinBoxSamplesPerSymbol.valueChanged.connect(self.on_samples_per_symbol_changed)
|
||||
self.ui.spinBoxSampleRate.valueChanged.connect(self.on_sample_rate_changed)
|
||||
self.ui.linEdDataBits.textChanged.connect(self.on_data_bits_changed)
|
||||
self.ui.spinBoxBitsPerSymbol.valueChanged.connect(self.on_bits_per_symbol_changed)
|
||||
self.ui.comboBoxModulationType.currentIndexChanged.connect(self.on_modulation_type_changed)
|
||||
self.ui.gVOriginalSignal.zoomed.connect(self.on_orig_signal_zoomed)
|
||||
self.ui.cbShowDataBitsOnly.stateChanged.connect(self.on_show_data_bits_only_changed)
|
||||
self.ui.btnSearchNext.clicked.connect(self.on_btn_next_search_result_clicked)
|
||||
self.ui.btnSearchPrev.clicked.connect(self.on_btn_prev_search_result_clicked)
|
||||
self.ui.comboBoxCustomModulations.editTextChanged.connect(self.on_custom_modulation_name_edited)
|
||||
self.ui.comboBoxCustomModulations.currentIndexChanged.connect(self.on_custom_modulation_index_changed)
|
||||
self.ui.btnAddModulation.clicked.connect(self.add_modulator)
|
||||
self.ui.btnRemoveModulation.clicked.connect(self.on_remove_modulator_clicked)
|
||||
self.ui.gVModulated.zoomed.connect(self.on_carrier_data_modulated_zoomed)
|
||||
self.ui.gVCarrier.zoomed.connect(self.on_carrier_data_modulated_zoomed)
|
||||
self.ui.gVData.zoomed.connect(self.on_carrier_data_modulated_zoomed)
|
||||
self.ui.gVModulated.selection_width_changed.connect(self.on_modulated_selection_changed)
|
||||
self.ui.gVOriginalSignal.selection_width_changed.connect(self.on_original_selection_changed)
|
||||
self.ui.spinBoxGaussBT.valueChanged.connect(self.on_gauss_bt_changed)
|
||||
self.ui.spinBoxGaussFilterWidth.valueChanged.connect(self.on_gauss_filter_width_changed)
|
||||
|
||||
self.ui.chkBoxLockSIV.stateChanged.connect(self.on_lock_siv_changed)
|
||||
|
||||
self.ui.gVOriginalSignal.signal_loaded.connect(self.handle_signal_loaded)
|
||||
self.ui.btnAutoDetect.clicked.connect(self.on_btn_autodetect_clicked)
|
||||
|
||||
self.restore_bits_action.triggered.connect(self.on_restore_bits_action_triggered)
|
||||
self.configure_parameters_action.triggered.connect(self.on_configure_parameters_action_triggered)
|
||||
self.ui.lineEditParameters.editingFinished.connect(self.on_line_edit_parameters_editing_finished)
|
||||
|
||||
def draw_carrier(self):
|
||||
self.ui.gVCarrier.plot_data(self.current_modulator.carrier_data)
|
||||
|
||||
def draw_data_bits(self):
|
||||
self.ui.gVData.setScene(self.current_modulator.data_scene)
|
||||
self.ui.gVData.update()
|
||||
|
||||
def draw_modulated(self):
|
||||
self.ui.gVModulated.plot_data(self.current_modulator.modulate(pause=0).imag)
|
||||
if self.lock_samples_in_view:
|
||||
siv = self.ui.gVOriginalSignal.view_rect().width()
|
||||
self.adjust_samples_in_view(siv)
|
||||
else:
|
||||
self.mark_samples_in_view()
|
||||
|
||||
def draw_original_signal(self, start=0, end=-1):
|
||||
scene_manager = self.ui.gVOriginalSignal.scene_manager
|
||||
if scene_manager is None:
|
||||
return
|
||||
|
||||
if end == -1:
|
||||
end = scene_manager.signal.num_samples
|
||||
|
||||
y = self.ui.gVOriginalSignal.view_rect().y()
|
||||
h = self.ui.gVOriginalSignal.view_rect().height()
|
||||
self.ui.gVOriginalSignal.setSceneRect(start, y, end - start, h)
|
||||
self.ui.gVOriginalSignal.fitInView(self.ui.gVOriginalSignal.sceneRect())
|
||||
scene_manager.show_scene_section(start, end)
|
||||
self.ui.gVOriginalSignal.update()
|
||||
|
||||
if self.lock_samples_in_view:
|
||||
self.adjust_samples_in_view(self.ui.gVModulated.view_rect().width())
|
||||
else:
|
||||
self.mark_samples_in_view()
|
||||
|
||||
def update_views(self):
|
||||
self.ui.gVCarrier.update()
|
||||
self.ui.gVData.update()
|
||||
self.ui.gVModulated.update()
|
||||
self.ui.gVOriginalSignal.update()
|
||||
|
||||
def search_data_sequence(self):
|
||||
if not self.ui.cbShowDataBitsOnly.isEnabled() or not self.ui.cbShowDataBitsOnly.isChecked():
|
||||
return
|
||||
|
||||
search_seq = self.ui.linEdDataBits.text()
|
||||
if len(search_seq) == 0 or self.protocol is None:
|
||||
return
|
||||
|
||||
self.search_results[:] = []
|
||||
proto_bits = self.protocol.plain_bits_str
|
||||
len_seq = len(search_seq)
|
||||
|
||||
for i, message in enumerate(proto_bits):
|
||||
j = message.find(search_seq)
|
||||
while j != -1:
|
||||
self.search_results.append((i, j, j + len_seq))
|
||||
j = message.find(search_seq, j + 1)
|
||||
|
||||
self.ui.lTotalSearchresults.setText(str(len(self.search_results)))
|
||||
self.show_search_result(0)
|
||||
|
||||
def show_search_result(self, i: int):
|
||||
if len(self.search_results) == 0:
|
||||
self.ui.lCurrentSearchResult.setText("0")
|
||||
self.ui.gVOriginalSignal.scene_manager.clear_path()
|
||||
return
|
||||
|
||||
message, start_index, end_index = self.search_results[i]
|
||||
|
||||
start, nsamples = self.protocol.get_samplepos_of_bitseq(message, start_index, message, end_index, False)
|
||||
self.draw_original_signal(start=start, end=start + nsamples)
|
||||
|
||||
self.ui.lCurrentSearchResult.setText(str(i + 1))
|
||||
self.ui.btnSearchNext.setEnabled(i != len(self.search_results) - 1)
|
||||
self.ui.btnSearchPrev.setEnabled(i > 0)
|
||||
|
||||
def add_modulator(self):
|
||||
names = [m.name for m in self.modulators]
|
||||
name = "Modulation"
|
||||
number = 1
|
||||
while name in names:
|
||||
name = "Modulation " + str(number)
|
||||
number += 1
|
||||
self.modulators.append(Modulator(name))
|
||||
self.ui.comboBoxCustomModulations.addItem(name)
|
||||
self.ui.comboBoxCustomModulations.setCurrentIndex(len(self.modulators) - 1)
|
||||
self.ui.btnRemoveModulation.setEnabled(True)
|
||||
|
||||
def adjust_samples_in_view(self, target_siv: float):
|
||||
self.ui.gVOriginalSignal.scale(self.ui.gVOriginalSignal.view_rect().width() / target_siv, 1)
|
||||
mod_zoom_factor = self.ui.gVModulated.view_rect().width() / target_siv
|
||||
self.ui.gVModulated.scale(mod_zoom_factor, 1)
|
||||
self.ui.gVCarrier.scale(mod_zoom_factor, 1)
|
||||
self.ui.gVData.scale(mod_zoom_factor, 1)
|
||||
self.mark_samples_in_view()
|
||||
|
||||
def detect_fsk_frequencies(self):
|
||||
if not self.current_modulator.is_frequency_based:
|
||||
return
|
||||
|
||||
frequencies = []
|
||||
try:
|
||||
if not self.current_modulator.is_binary_modulation:
|
||||
raise NotImplementedError()
|
||||
|
||||
zero_freq = self.protocol.estimate_frequency_for_zero(self.current_modulator.sample_rate)
|
||||
one_freq = self.protocol.estimate_frequency_for_one(self.current_modulator.sample_rate)
|
||||
zero_freq = self.__trim_number(zero_freq)
|
||||
one_freq = self.__trim_number(one_freq)
|
||||
zero_freq, one_freq = self.__ensure_multitude(zero_freq, one_freq)
|
||||
|
||||
if zero_freq == one_freq:
|
||||
# If frequencies are equal, it is very likely the zero freq is negative
|
||||
zero_freq = -one_freq
|
||||
|
||||
frequencies = [zero_freq, one_freq]
|
||||
|
||||
except (AttributeError, NotImplementedError):
|
||||
frequencies = self.current_modulator.get_default_parameters()
|
||||
|
||||
self.current_modulator.parameters = array("f", frequencies)
|
||||
self.update_modulation_parameters()
|
||||
|
||||
def handle_signal_loaded(self, protocol):
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
self.ui.cbShowDataBitsOnly.setEnabled(True)
|
||||
self.ui.chkBoxLockSIV.setEnabled(True)
|
||||
self.ui.btnAutoDetect.setEnabled(True)
|
||||
self.protocol = protocol
|
||||
|
||||
# Apply bit length of original signal to current modulator
|
||||
self.ui.spinBoxSamplesPerSymbol.setValue(self.ui.gVOriginalSignal.signal.samples_per_symbol)
|
||||
|
||||
# https://github.com/jopohl/urh/issues/130
|
||||
self.ui.gVModulated.show_full_scene(reinitialize=True)
|
||||
self.ui.gVCarrier.show_full_scene(reinitialize=True)
|
||||
self.ui.gVData.show_full_scene(reinitialize=True)
|
||||
|
||||
self.unsetCursor()
|
||||
|
||||
def mark_samples_in_view(self):
|
||||
self.ui.lSamplesInViewModulated.setText(str(int(self.ui.gVModulated.view_rect().width())))
|
||||
|
||||
if self.ui.gVOriginalSignal.scene_manager is not None:
|
||||
self.ui.lSamplesInViewOrigSignal.setText(str(int(self.ui.gVOriginalSignal.view_rect().width())))
|
||||
else:
|
||||
self.ui.lSamplesInViewOrigSignal.setText("-")
|
||||
return
|
||||
|
||||
if int(self.ui.gVOriginalSignal.view_rect().width()) != int(self.ui.gVModulated.view_rect().width()):
|
||||
font = self.ui.lSamplesInViewModulated.font()
|
||||
font.setBold(False)
|
||||
self.ui.lSamplesInViewModulated.setFont(font)
|
||||
self.ui.lSamplesInViewOrigSignal.setFont(font)
|
||||
|
||||
self.ui.lSamplesInViewOrigSignal.setStyleSheet("QLabel { color : red; }")
|
||||
self.ui.lSamplesInViewModulated.setStyleSheet("QLabel { color : red; }")
|
||||
else:
|
||||
font = self.ui.lSamplesInViewModulated.font()
|
||||
font.setBold(True)
|
||||
self.ui.lSamplesInViewModulated.setFont(font)
|
||||
self.ui.lSamplesInViewOrigSignal.setFont(font)
|
||||
|
||||
self.ui.lSamplesInViewOrigSignal.setStyleSheet("")
|
||||
self.ui.lSamplesInViewModulated.setStyleSheet("")
|
||||
|
||||
def set_default_modulation_parameters(self):
|
||||
self.current_modulator.parameters = self.current_modulator.get_default_parameters()
|
||||
self.update_modulation_parameters()
|
||||
|
||||
def set_modulation_profile_status(self):
|
||||
visible = settings.read("multiple_modulations", False, bool)
|
||||
self.ui.btnAddModulation.setVisible(visible)
|
||||
self.ui.btnRemoveModulation.setVisible(visible)
|
||||
self.ui.comboBoxCustomModulations.setVisible(visible)
|
||||
|
||||
def resizeEvent(self, event: QResizeEvent):
|
||||
self.update_views()
|
||||
|
||||
def keyPressEvent(self, event: QKeyEvent):
|
||||
if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return:
|
||||
return
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def initialize(self, bits: str):
|
||||
self.on_modulation_type_changed() # for drawing modulated signal initially
|
||||
self.original_bits = bits
|
||||
self.ui.linEdDataBits.setText(bits)
|
||||
self.draw_original_signal()
|
||||
self.ui.gVModulated.show_full_scene(reinitialize=True)
|
||||
self.ui.gVModulated.auto_fit_view()
|
||||
self.ui.gVData.show_full_scene(reinitialize=True)
|
||||
self.ui.gVData.auto_fit_view()
|
||||
self.ui.gVCarrier.show_full_scene(reinitialize=True)
|
||||
self.ui.gVCarrier.auto_fit_view()
|
||||
|
||||
self.mark_samples_in_view()
|
||||
|
||||
def update_modulation_parameters(self):
|
||||
n = len(self.current_modulator.parameters) - 1
|
||||
if self.current_modulator.is_amplitude_based:
|
||||
regex = r"(100|[0-9]{1,2})"
|
||||
elif self.current_modulator.is_frequency_based:
|
||||
regex = r"((-?[0-9]+)[.,]?[0-9]*[kKmMgG]?)"
|
||||
elif self.current_modulator.is_phase_based:
|
||||
regex = r"(-?(36[0]|3[0-5][0-9]|[12][0-9][0-9]|[1-9]?[0-9]))"
|
||||
else:
|
||||
raise ValueError("Unknown modulation type")
|
||||
|
||||
full_regex = r"^(" + regex + r"/){" + str(n) + "}" + regex + r"$"
|
||||
self.ui.lineEditParameters.setValidator(QRegExpValidator(QRegExp(full_regex)))
|
||||
self.ui.lineEditParameters.setText(self.current_modulator.parameters_string)
|
||||
|
||||
def set_bits_per_symbol_enabled_status(self):
|
||||
if self.current_modulator.modulation_type == "OQPSK":
|
||||
self.ui.spinBoxBitsPerSymbol.setEnabled(False)
|
||||
self.ui.spinBoxBitsPerSymbol.setValue(2)
|
||||
else:
|
||||
self.ui.spinBoxBitsPerSymbol.setEnabled(True)
|
||||
|
||||
def show_full_scene(self):
|
||||
for graphic_view in (self.ui.gVModulated, self.ui.gVData, self.ui.gVCarrier):
|
||||
graphic_view.show_full_scene(reinitialize=True)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_carrier_freq_changed(self):
|
||||
self.current_modulator.carrier_freq_hz = self.ui.doubleSpinBoxCarrierFreq.value()
|
||||
self.draw_carrier()
|
||||
self.draw_modulated()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_carrier_phase_changed(self):
|
||||
self.current_modulator.carrier_phase_deg = self.ui.doubleSpinBoxCarrierPhase.value()
|
||||
self.draw_carrier()
|
||||
self.draw_modulated()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_samples_per_symbol_changed(self):
|
||||
self.current_modulator.samples_per_symbol = self.ui.spinBoxSamplesPerSymbol.value()
|
||||
self.draw_carrier()
|
||||
self.draw_data_bits()
|
||||
self.draw_modulated()
|
||||
self.show_full_scene()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_data_bits_changed(self):
|
||||
text = self.ui.linEdDataBits.text()
|
||||
text = ''.join(c for c in text if c == "1" or c == "0")
|
||||
self.ui.linEdDataBits.blockSignals(True)
|
||||
self.ui.linEdDataBits.setText(text)
|
||||
self.ui.linEdDataBits.blockSignals(False)
|
||||
self.current_modulator.display_bits = text
|
||||
self.draw_carrier()
|
||||
self.draw_data_bits()
|
||||
self.draw_modulated()
|
||||
if len(text) > 0:
|
||||
if len(text) > 24:
|
||||
display_text = text[0:24] + "..."
|
||||
else:
|
||||
display_text = text
|
||||
self.ui.cbShowDataBitsOnly.setToolTip(text)
|
||||
self.ui.cbShowDataBitsOnly.setText(self.tr("Show Only Data Sequence\n") + "(" + display_text + ")")
|
||||
else:
|
||||
self.ui.cbShowDataBitsOnly.setToolTip("")
|
||||
self.ui.cbShowDataBitsOnly.setText(self.tr("Show Only Data Sequence\n"))
|
||||
|
||||
self.search_data_sequence()
|
||||
self.restore_bits_action.setEnabled(text != self.original_bits)
|
||||
self.show_full_scene()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_sample_rate_changed(self):
|
||||
if int(self.ui.spinBoxSampleRate.value()) > 0:
|
||||
self.current_modulator.sample_rate = int(self.ui.spinBoxSampleRate.value())
|
||||
self.draw_carrier()
|
||||
self.draw_modulated()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_gauss_bt_changed(self):
|
||||
self.current_modulator.gauss_bt = self.ui.spinBoxGaussBT.value()
|
||||
self.draw_modulated()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_gauss_filter_width_changed(self):
|
||||
self.current_modulator.gauss_filter_width = self.ui.spinBoxGaussFilterWidth.value()
|
||||
self.draw_modulated()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_bits_per_symbol_changed(self):
|
||||
if self.current_modulator.bits_per_symbol == self.ui.spinBoxBitsPerSymbol.value():
|
||||
return
|
||||
self.current_modulator.bits_per_symbol = self.ui.spinBoxBitsPerSymbol.value()
|
||||
self.set_default_modulation_parameters()
|
||||
self.draw_modulated()
|
||||
self.show_full_scene()
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def on_modulation_type_changed(self):
|
||||
write_default_parameters = self.current_modulator.modulation_type != self.__cur_selected_mod_type()
|
||||
self.current_modulator.modulation_type = self.__cur_selected_mod_type()
|
||||
|
||||
self.__set_gauss_ui_visibility(self.__cur_selected_mod_type() == "GFSK")
|
||||
|
||||
self.ui.labelParameters.setText(self.current_modulator.parameter_type_str)
|
||||
if write_default_parameters:
|
||||
self.set_default_modulation_parameters()
|
||||
else:
|
||||
self.update_modulation_parameters()
|
||||
|
||||
self.set_bits_per_symbol_enabled_status()
|
||||
self.draw_modulated()
|
||||
self.show_full_scene()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_orig_signal_zoomed(self):
|
||||
start = self.ui.gVOriginalSignal.view_rect().x()
|
||||
end = start + self.ui.gVOriginalSignal.view_rect().width()
|
||||
|
||||
self.ui.gVOriginalSignal.centerOn(start + (end - start) / 2, 0)
|
||||
if self.lock_samples_in_view:
|
||||
self.adjust_samples_in_view(self.ui.gVOriginalSignal.view_rect().width())
|
||||
|
||||
x = self.ui.gVOriginalSignal.view_rect().x() + self.ui.gVOriginalSignal.view_rect().width() / 2
|
||||
y = 0
|
||||
|
||||
self.ui.gVModulated.centerOn(x, y)
|
||||
self.ui.gVCarrier.centerOn(x, y)
|
||||
self.ui.gVData.centerOn(x, y)
|
||||
else:
|
||||
self.mark_samples_in_view()
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_carrier_data_modulated_zoomed(self, factor: float):
|
||||
|
||||
x = self.sender().view_rect().x() + self.sender().view_rect().width() / 2
|
||||
y = 0
|
||||
for gv in (self.ui.gVCarrier, self.ui.gVData, self.ui.gVModulated):
|
||||
if gv == self.sender():
|
||||
continue
|
||||
if factor == -1:
|
||||
gv.show_full_scene()
|
||||
else:
|
||||
gv.scale(factor, 1)
|
||||
gv.centerOn(x, y)
|
||||
|
||||
if self.lock_samples_in_view:
|
||||
self.adjust_samples_in_view(self.ui.gVModulated.view_rect().width())
|
||||
self.ui.gVOriginalSignal.centerOn(x, y)
|
||||
else:
|
||||
self.mark_samples_in_view()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_custom_modulation_name_edited(self):
|
||||
self.current_modulator.name = self.ui.comboBoxCustomModulations.currentText()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_custom_modulation_index_changed(self):
|
||||
self.set_ui_for_current_modulator()
|
||||
self.draw_carrier()
|
||||
self.draw_data_bits()
|
||||
self.draw_modulated()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_next_search_result_clicked(self):
|
||||
cur_search_result = int(self.ui.lCurrentSearchResult.text()) - 1
|
||||
self.show_search_result(cur_search_result + 1)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_prev_search_result_clicked(self):
|
||||
cur_search_result = int(self.ui.lCurrentSearchResult.text()) - 1
|
||||
self.show_search_result(cur_search_result - 1)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_show_data_bits_only_changed(self, redraw=True):
|
||||
show_data_bits_only = self.ui.cbShowDataBitsOnly.isChecked()
|
||||
if not self.ui.cbShowDataBitsOnly.isEnabled() or not show_data_bits_only:
|
||||
self.ui.btnSearchPrev.setEnabled(False)
|
||||
self.ui.btnSearchNext.setEnabled(False)
|
||||
self.ui.lCurrentSearchResult.setText("-")
|
||||
self.ui.lTotalSearchresults.setText("-")
|
||||
else:
|
||||
self.search_data_sequence()
|
||||
|
||||
if not redraw:
|
||||
return
|
||||
|
||||
if self.ui.cbShowDataBitsOnly.isEnabled() and not show_data_bits_only:
|
||||
self.draw_original_signal()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_remove_modulator_clicked(self):
|
||||
index = self.ui.comboBoxCustomModulations.currentIndex()
|
||||
self.ui.comboBoxCustomModulations.removeItem(index)
|
||||
self.modulators.remove(self.modulators[index])
|
||||
|
||||
if len(self.modulators) == 1:
|
||||
self.ui.btnRemoveModulation.setDisabled(True)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_lock_siv_changed(self):
|
||||
self.lock_samples_in_view = self.ui.chkBoxLockSIV.isChecked()
|
||||
if self.lock_samples_in_view:
|
||||
self.adjust_samples_in_view(self.ui.gVModulated.view_rect().width())
|
||||
|
||||
@pyqtSlot()
|
||||
def on_restore_bits_action_triggered(self):
|
||||
self.ui.linEdDataBits.setText(self.original_bits)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_autodetect_clicked(self):
|
||||
signal = self.ui.gVOriginalSignal.scene_manager.signal
|
||||
freq = self.current_modulator.estimate_carrier_frequency(signal, self.protocol)
|
||||
|
||||
if freq is None or freq == 0:
|
||||
QMessageBox.information(self, self.tr("No results"),
|
||||
self.tr("Unable to detect parameters from current signal"))
|
||||
return
|
||||
|
||||
self.ui.doubleSpinBoxCarrierFreq.setValue(freq)
|
||||
self.detect_fsk_frequencies()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_modulated_selection_changed(self, new_width: int):
|
||||
self.ui.lModulatedSelectedSamples.setText(str(abs(new_width)))
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_original_selection_changed(self, new_width: int):
|
||||
self.ui.lOriginalSignalSamplesSelected.setText(str(abs(new_width)))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_configure_parameters_action_triggered(self):
|
||||
self.ui.lineEditParameters.editingFinished.emit()
|
||||
dialog = ModulationParametersDialog(self.current_modulator.parameters, self.current_modulator.modulation_type,
|
||||
self)
|
||||
dialog.accepted.connect(self.update_modulation_parameters)
|
||||
dialog.show()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_parameters_editing_finished(self):
|
||||
if not self.ui.lineEditParameters.hasAcceptableInput():
|
||||
return
|
||||
|
||||
text = self.ui.lineEditParameters.text()
|
||||
parameters = []
|
||||
for param in text.split("/"):
|
||||
param = param.upper().replace(",", ".")
|
||||
factor = 1
|
||||
if param.endswith("G"):
|
||||
factor = 10 ** 9
|
||||
param = param[:-1]
|
||||
elif param.endswith("M"):
|
||||
factor = 10 ** 6
|
||||
param = param[:-1]
|
||||
elif param.endswith("K"):
|
||||
factor = 10 ** 3
|
||||
param = param[:-1]
|
||||
|
||||
try:
|
||||
parameters.append(factor * float(param))
|
||||
except ValueError:
|
||||
logger.warning("Could not convert {} to number".format(param))
|
||||
return
|
||||
|
||||
self.current_modulator.parameters[:] = array("f", parameters)
|
||||
self.draw_modulated()
|
||||
self.show_full_scene()
|
||||
@@ -0,0 +1,511 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal, QSize, QAbstractTableModel, QModelIndex
|
||||
from PyQt5.QtGui import QCloseEvent, QIcon, QPixmap
|
||||
from PyQt5.QtWidgets import QDialog, QHBoxLayout, QCompleter, QDirModel, QApplication, QHeaderView, QRadioButton, \
|
||||
QFileDialog, qApp
|
||||
|
||||
from urh import settings, colormaps
|
||||
from urh.controller.widgets.PluginFrame import PluginFrame
|
||||
from urh.dev.BackendHandler import BackendHandler, Backends
|
||||
from urh.dev.native import ExtensionHelper
|
||||
from urh.models.FieldTypeTableModel import FieldTypeTableModel
|
||||
from urh.signalprocessing.FieldType import FieldType
|
||||
from urh.signalprocessing.Modulator import Modulator
|
||||
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
|
||||
from urh.signalprocessing.Spectrogram import Spectrogram
|
||||
from urh.ui.delegates.ComboBoxDelegate import ComboBoxDelegate
|
||||
from urh.ui.ui_options import Ui_DialogOptions
|
||||
from urh.util import util
|
||||
|
||||
|
||||
class DeviceOptionsTableModel(QAbstractTableModel):
|
||||
header_labels = ["Software Defined Radio", "Info", "Native backend (recommended)", "GNU Radio backend"]
|
||||
|
||||
def __init__(self, backend_handler: BackendHandler, parent=None):
|
||||
self.backend_handler = backend_handler
|
||||
|
||||
super().__init__(parent)
|
||||
|
||||
def update(self):
|
||||
self.beginResetModel()
|
||||
self.endResetModel()
|
||||
|
||||
def columnCount(self, parent: QModelIndex = None, *args, **kwargs):
|
||||
return len(self.header_labels)
|
||||
|
||||
def rowCount(self, parent: QModelIndex = None, *args, **kwargs):
|
||||
return len(self.backend_handler.DEVICE_NAMES)
|
||||
|
||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
||||
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
|
||||
return self.header_labels[section]
|
||||
return super().headerData(section, orientation, role)
|
||||
|
||||
def get_device_at(self, index: int):
|
||||
dev_key = self.backend_handler.get_key_from_device_display_text(self.backend_handler.DEVICE_NAMES[index])
|
||||
return self.backend_handler.device_backends[dev_key]
|
||||
|
||||
def data(self, index: QModelIndex, role=Qt.DisplayRole):
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
i = index.row()
|
||||
j = index.column()
|
||||
device = self.get_device_at(i)
|
||||
if role == Qt.DisplayRole:
|
||||
if j == 0:
|
||||
return self.backend_handler.DEVICE_NAMES[i]
|
||||
elif j == 1:
|
||||
if device.is_enabled:
|
||||
if device.supports_rx and device.supports_tx:
|
||||
device_info = "supports RX and TX"
|
||||
elif device.supports_rx and not device.supports_tx:
|
||||
device_info = "supports RX only"
|
||||
elif not device.supports_rx and device.supports_tx:
|
||||
device_info = "supports TX only"
|
||||
else:
|
||||
device_info = ""
|
||||
else:
|
||||
device_info = "disabled"
|
||||
|
||||
return device_info
|
||||
elif j == 2:
|
||||
return "" if device.has_native_backend else "not available"
|
||||
elif j == 3:
|
||||
return "" if device.has_gnuradio_backend else "not available"
|
||||
elif role == Qt.CheckStateRole:
|
||||
if j == 0 and (device.has_native_backend or device.has_gnuradio_backend):
|
||||
return Qt.Checked if device.is_enabled else Qt.Unchecked
|
||||
elif j == 2 and device.has_native_backend:
|
||||
return Qt.Checked if device.selected_backend == Backends.native else Qt.Unchecked
|
||||
elif j == 3 and device.has_gnuradio_backend:
|
||||
return Qt.Checked if device.selected_backend == Backends.grc else Qt.Unchecked
|
||||
|
||||
def setData(self, index: QModelIndex, value, role=None):
|
||||
if not index.isValid():
|
||||
return False
|
||||
|
||||
i, j = index.row(), index.column()
|
||||
device = self.get_device_at(i)
|
||||
if role == Qt.CheckStateRole:
|
||||
enabled = bool(value)
|
||||
if j == 0:
|
||||
device.is_enabled = enabled
|
||||
if j == 2:
|
||||
if enabled and device.has_native_backend:
|
||||
device.selected_backend = Backends.native
|
||||
elif not enabled and device.has_gnuradio_backend:
|
||||
device.selected_backend = Backends.grc
|
||||
elif j == 3:
|
||||
if enabled and device.has_gnuradio_backend:
|
||||
device.selected_backend = Backends.grc
|
||||
elif not enabled and device.has_native_backend:
|
||||
device.selected_backend = Backends.native
|
||||
|
||||
self.update()
|
||||
device.write_settings()
|
||||
return True
|
||||
|
||||
def flags(self, index: QModelIndex):
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
j = index.column()
|
||||
device = self.get_device_at(index.row())
|
||||
if j == 0 and not device.has_native_backend and not device.has_gnuradio_backend:
|
||||
return Qt.NoItemFlags
|
||||
|
||||
if j in [1, 2, 3] and not device.is_enabled:
|
||||
return Qt.NoItemFlags
|
||||
|
||||
if j == 2 and not device.has_native_backend:
|
||||
return Qt.NoItemFlags
|
||||
|
||||
if j == 3 and not device.has_gnuradio_backend:
|
||||
return Qt.NoItemFlags
|
||||
|
||||
flags = Qt.ItemIsEnabled
|
||||
|
||||
if j in [0, 2, 3]:
|
||||
flags |= Qt.ItemIsUserCheckable
|
||||
|
||||
return flags
|
||||
|
||||
|
||||
class OptionsDialog(QDialog):
|
||||
values_changed = pyqtSignal(dict)
|
||||
|
||||
def __init__(self, installed_plugins, highlighted_plugins=None, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.backend_handler = BackendHandler()
|
||||
|
||||
self.ui = Ui_DialogOptions()
|
||||
self.ui.setupUi(self)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.device_options_model = DeviceOptionsTableModel(self.backend_handler, self)
|
||||
self.device_options_model.update()
|
||||
self.ui.tblDevices.setModel(self.device_options_model)
|
||||
self.ui.tblDevices.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
|
||||
self.ui.tblDevices.setItemDelegateForColumn(1, ComboBoxDelegate(["native", "GNU Radio"]))
|
||||
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
layout = QHBoxLayout(self.ui.tab_plugins)
|
||||
self.plugin_controller = PluginFrame(installed_plugins, highlighted_plugins, parent=self)
|
||||
layout.addWidget(self.plugin_controller)
|
||||
self.ui.tab_plugins.setLayout(layout)
|
||||
|
||||
self.ui.btnViewBuildLog.hide()
|
||||
self.build_log = ""
|
||||
|
||||
# We use bundled native device backends on windows, so no need to reconfigure them
|
||||
self.ui.groupBoxNativeOptions.setVisible(sys.platform != "win32")
|
||||
self.ui.labelIconTheme.setVisible(sys.platform == "linux")
|
||||
self.ui.comboBoxIconTheme.setVisible(sys.platform == "linux")
|
||||
|
||||
self.ui.comboBoxTheme.setCurrentIndex(settings.read("theme_index", 0, int))
|
||||
self.ui.comboBoxIconTheme.setCurrentIndex(settings.read("icon_theme_index", 0, int))
|
||||
self.ui.checkBoxShowConfirmCloseDialog.setChecked(not settings.read('not_show_close_dialog', False, bool))
|
||||
self.ui.checkBoxHoldShiftToDrag.setChecked(settings.read('hold_shift_to_drag', True, bool))
|
||||
self.ui.checkBoxDefaultFuzzingPause.setChecked(settings.read('use_default_fuzzing_pause', True, bool))
|
||||
|
||||
self.ui.checkBoxAlignLabels.setChecked(settings.read('align_labels', True, bool))
|
||||
|
||||
self.ui.doubleSpinBoxRAMThreshold.setValue(100 * settings.read('ram_threshold', 0.6, float))
|
||||
|
||||
if self.backend_handler.gr_python_interpreter:
|
||||
self.ui.lineEditGRPythonInterpreter.setText(self.backend_handler.gr_python_interpreter)
|
||||
|
||||
self.ui.doubleSpinBoxFuzzingPause.setValue(settings.read("default_fuzzing_pause", 10 ** 6, int))
|
||||
self.ui.doubleSpinBoxFuzzingPause.setEnabled(settings.read('use_default_fuzzing_pause', True, bool))
|
||||
|
||||
self.ui.checkBoxMultipleModulations.setChecked(settings.read("multiple_modulations", False, bool))
|
||||
|
||||
self.ui.radioButtonLowModulationAccuracy.setChecked(Modulator.get_dtype() == np.int8)
|
||||
self.ui.radioButtonMediumModulationAccuracy.setChecked(Modulator.get_dtype() == np.int16)
|
||||
self.ui.radioButtonHighModulationAccuracy.setChecked(Modulator.get_dtype() == np.float32)
|
||||
|
||||
completer = QCompleter()
|
||||
completer.setModel(QDirModel(completer))
|
||||
self.ui.lineEditGRPythonInterpreter.setCompleter(completer)
|
||||
|
||||
self.ui.spinBoxFontSize.setValue(qApp.font().pointSize())
|
||||
|
||||
self.refresh_device_tab()
|
||||
|
||||
self.create_connects()
|
||||
self.old_show_pause_as_time = False
|
||||
|
||||
self.field_type_table_model = FieldTypeTableModel([], parent=self)
|
||||
self.ui.tblLabeltypes.setModel(self.field_type_table_model)
|
||||
self.ui.tblLabeltypes.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
|
||||
self.ui.tblLabeltypes.setItemDelegateForColumn(1, ComboBoxDelegate([f.name for f in FieldType.Function],
|
||||
return_index=False, parent=self))
|
||||
self.ui.tblLabeltypes.setItemDelegateForColumn(2, ComboBoxDelegate(ProtocolLabel.DISPLAY_FORMATS, parent=self))
|
||||
|
||||
self.read_options()
|
||||
|
||||
self.old_default_view = self.ui.comboBoxDefaultView.currentIndex()
|
||||
self.old_num_sending_repeats = self.ui.spinBoxNumSendingRepeats.value()
|
||||
self.ui.labelRebuildNativeStatus.setText("")
|
||||
|
||||
self.show_available_colormaps()
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.doubleSpinBoxFuzzingPause.valueChanged.connect(self.on_spinbox_fuzzing_pause_value_changed)
|
||||
self.ui.lineEditGRPythonInterpreter.editingFinished.connect(self.on_gr_python_interpreter_path_edited)
|
||||
self.ui.btnChooseGRPythonInterpreter.clicked.connect(self.on_btn_choose_gr_python_interpreter_clicked)
|
||||
self.ui.comboBoxTheme.currentIndexChanged.connect(self.on_combo_box_theme_index_changed)
|
||||
self.ui.checkBoxShowConfirmCloseDialog.clicked.connect(self.on_checkbox_confirm_close_dialog_clicked)
|
||||
self.ui.checkBoxHoldShiftToDrag.clicked.connect(self.on_checkbox_hold_shift_to_drag_clicked)
|
||||
self.ui.checkBoxAlignLabels.clicked.connect(self.on_checkbox_align_labels_clicked)
|
||||
self.ui.checkBoxDefaultFuzzingPause.clicked.connect(self.on_checkbox_default_fuzzing_pause_clicked)
|
||||
self.ui.btnAddLabelType.clicked.connect(self.on_btn_add_label_type_clicked)
|
||||
self.ui.btnRemoveLabeltype.clicked.connect(self.on_btn_remove_label_type_clicked)
|
||||
self.ui.radioButtonLowModulationAccuracy.clicked.connect(self.on_radio_button_low_modulation_accuracy_clicked)
|
||||
self.ui.radioButtonMediumModulationAccuracy.clicked.connect(self.on_radio_button_medium_modulation_accuracy_clicked)
|
||||
self.ui.radioButtonHighModulationAccuracy.clicked.connect(self.on_radio_button_high_modulation_accuracy_clicked)
|
||||
|
||||
self.ui.doubleSpinBoxRAMThreshold.valueChanged.connect(self.on_double_spinbox_ram_threshold_value_changed)
|
||||
self.ui.btnRebuildNative.clicked.connect(self.on_btn_rebuild_native_clicked)
|
||||
self.ui.comboBoxIconTheme.currentIndexChanged.connect(self.on_combobox_icon_theme_index_changed)
|
||||
self.ui.checkBoxMultipleModulations.clicked.connect(self.on_checkbox_multiple_modulations_clicked)
|
||||
self.ui.btnViewBuildLog.clicked.connect(self.on_btn_view_build_log_clicked)
|
||||
self.ui.labelDeviceMissingInfo.linkActivated.connect(self.on_label_device_missing_info_link_activated)
|
||||
self.ui.spinBoxFontSize.editingFinished.connect(self.on_spin_box_font_size_editing_finished)
|
||||
|
||||
def show_gnuradio_infos(self):
|
||||
self.ui.lineEditGRPythonInterpreter.setText(self.backend_handler.gr_python_interpreter)
|
||||
|
||||
if self.backend_handler.gnuradio_is_installed:
|
||||
self.ui.lineEditGRPythonInterpreter.setStyleSheet("background-color: lightgreen")
|
||||
self.ui.lineEditGRPythonInterpreter.setToolTip("GNU Radio interface is working.")
|
||||
else:
|
||||
self.ui.lineEditGRPythonInterpreter.setStyleSheet("background-color: orange")
|
||||
self.ui.lineEditGRPythonInterpreter.setToolTip("GNU Radio is not installed or incompatible with "
|
||||
"the configured python interpreter.")
|
||||
|
||||
def read_options(self):
|
||||
self.ui.comboBoxDefaultView.setCurrentIndex(settings.read('default_view', 0, type=int))
|
||||
self.ui.spinBoxNumSendingRepeats.setValue(settings.read('num_sending_repeats', 0, type=int))
|
||||
self.ui.checkBoxPauseTime.setChecked(settings.read('show_pause_as_time', False, type=bool))
|
||||
|
||||
self.old_show_pause_as_time = bool(self.ui.checkBoxPauseTime.isChecked())
|
||||
|
||||
self.field_type_table_model.field_types = FieldType.load_from_xml()
|
||||
self.field_type_table_model.update()
|
||||
|
||||
def refresh_device_tab(self):
|
||||
self.backend_handler.get_backends()
|
||||
self.show_gnuradio_infos()
|
||||
self.device_options_model.update()
|
||||
|
||||
def show_available_colormaps(self):
|
||||
height = 50
|
||||
|
||||
selected = colormaps.read_selected_colormap_name_from_settings()
|
||||
for colormap_name in sorted(colormaps.maps.keys()):
|
||||
image = Spectrogram.create_colormap_image(colormap_name, height=height)
|
||||
rb = QRadioButton(colormap_name)
|
||||
rb.setObjectName(colormap_name)
|
||||
rb.setChecked(colormap_name == selected)
|
||||
rb.setIcon(QIcon(QPixmap.fromImage(image)))
|
||||
rb.setIconSize(QSize(256, height))
|
||||
self.ui.scrollAreaWidgetSpectrogramColormapContents.layout().addWidget(rb)
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
changed_values = {}
|
||||
if bool(self.ui.checkBoxPauseTime.isChecked()) != self.old_show_pause_as_time:
|
||||
changed_values['show_pause_as_time'] = bool(self.ui.checkBoxPauseTime.isChecked())
|
||||
if self.old_default_view != self.ui.comboBoxDefaultView.currentIndex():
|
||||
changed_values['default_view'] = self.ui.comboBoxDefaultView.currentIndex()
|
||||
if self.old_num_sending_repeats != self.ui.spinBoxNumSendingRepeats.value():
|
||||
changed_values["num_sending_repeats"] = self.ui.spinBoxNumSendingRepeats.value()
|
||||
|
||||
settings.write('default_view', self.ui.comboBoxDefaultView.currentIndex())
|
||||
settings.write('num_sending_repeats', self.ui.spinBoxNumSendingRepeats.value())
|
||||
settings.write('show_pause_as_time', self.ui.checkBoxPauseTime.isChecked())
|
||||
|
||||
FieldType.save_to_xml(self.field_type_table_model.field_types)
|
||||
self.plugin_controller.save_enabled_states()
|
||||
for plugin in self.plugin_controller.model.plugins:
|
||||
plugin.destroy_settings_frame()
|
||||
|
||||
for i in range(self.ui.scrollAreaWidgetSpectrogramColormapContents.layout().count()):
|
||||
widget = self.ui.scrollAreaWidgetSpectrogramColormapContents.layout().itemAt(i).widget()
|
||||
if isinstance(widget, QRadioButton) and widget.isChecked():
|
||||
selected_colormap_name = widget.objectName()
|
||||
if selected_colormap_name != colormaps.read_selected_colormap_name_from_settings():
|
||||
colormaps.choose_colormap(selected_colormap_name)
|
||||
colormaps.write_selected_colormap_to_settings(selected_colormap_name)
|
||||
changed_values["spectrogram_colormap"] = selected_colormap_name
|
||||
break
|
||||
|
||||
self.values_changed.emit(changed_values)
|
||||
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
def set_gnuradio_status(self):
|
||||
self.backend_handler.gr_python_interpreter = self.ui.lineEditGRPythonInterpreter.text()
|
||||
self.refresh_device_tab()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_add_label_type_clicked(self):
|
||||
suffix = 1
|
||||
field_type_names = {ft.caption for ft in self.field_type_table_model.field_types}
|
||||
while "New Fieldtype #" + str(suffix) in field_type_names:
|
||||
suffix += 1
|
||||
|
||||
caption = "New Fieldtype #" + str(suffix)
|
||||
self.field_type_table_model.field_types.append(FieldType(caption, FieldType.Function.CUSTOM))
|
||||
self.field_type_table_model.update()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_remove_label_type_clicked(self):
|
||||
if self.field_type_table_model.field_types:
|
||||
selected_indices = {i.row() for i in self.ui.tblLabeltypes.selectedIndexes()}
|
||||
|
||||
if selected_indices:
|
||||
for i in reversed(sorted(selected_indices)):
|
||||
self.field_type_table_model.field_types.pop(i)
|
||||
else:
|
||||
self.field_type_table_model.field_types.pop()
|
||||
|
||||
self.field_type_table_model.update()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_double_spinbox_ram_threshold_value_changed(self):
|
||||
val = self.ui.doubleSpinBoxRAMThreshold.value()
|
||||
settings.write("ram_threshold", val / 100)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_checkbox_confirm_close_dialog_clicked(self, checked: bool):
|
||||
settings.write("not_show_close_dialog", not checked)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combo_box_theme_index_changed(self, index: int):
|
||||
settings.write('theme_index', index)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_icon_theme_index_changed(self, index: int):
|
||||
settings.write('icon_theme_index', index)
|
||||
util.set_icon_theme()
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_checkbox_hold_shift_to_drag_clicked(self, checked: bool):
|
||||
settings.write("hold_shift_to_drag", checked)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_checkbox_default_fuzzing_pause_clicked(self, checked: bool):
|
||||
settings.write('use_default_fuzzing_pause', checked)
|
||||
self.ui.doubleSpinBoxFuzzingPause.setEnabled(checked)
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_spinbox_fuzzing_pause_value_changed(self, value: float):
|
||||
settings.write("default_fuzzing_pause", int(value))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_gr_python_interpreter_path_edited(self):
|
||||
self.set_gnuradio_status()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_choose_gr_python_interpreter_clicked(self):
|
||||
if sys.platform == "win32":
|
||||
dialog_filter = "Executable (*.exe);;All files (*.*)"
|
||||
else:
|
||||
dialog_filter = ""
|
||||
filename, _ = QFileDialog.getOpenFileName(self, self.tr("Choose python interpreter"), filter=dialog_filter)
|
||||
if filename:
|
||||
self.ui.lineEditGRPythonInterpreter.setText(filename)
|
||||
self.set_gnuradio_status()
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_checkbox_align_labels_clicked(self, checked: bool):
|
||||
settings.write("align_labels", checked)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_rebuild_native_clicked(self):
|
||||
library_dirs = None if not self.ui.lineEditLibDirs.text() \
|
||||
else list(map(str.strip, self.ui.lineEditLibDirs.text().split(",")))
|
||||
include_dirs = None if not self.ui.lineEditIncludeDirs.text() \
|
||||
else list(map(str.strip, self.ui.lineEditIncludeDirs.text().split(",")))
|
||||
|
||||
extensions, _ = ExtensionHelper.get_device_extensions_and_extras(library_dirs=library_dirs, include_dirs=include_dirs)
|
||||
|
||||
self.ui.labelRebuildNativeStatus.setText(self.tr("Rebuilding device extensions..."))
|
||||
QApplication.instance().processEvents()
|
||||
build_cmd = [sys.executable, os.path.realpath(ExtensionHelper.__file__),
|
||||
"build_ext", "--inplace", "-t", tempfile.gettempdir()]
|
||||
if library_dirs:
|
||||
build_cmd.extend(["-L", ":".join(library_dirs)])
|
||||
if include_dirs:
|
||||
build_cmd.extend(["-I", ":".join(include_dirs)])
|
||||
|
||||
subprocess.call([sys.executable, os.path.realpath(ExtensionHelper.__file__), "clean", "--all"])
|
||||
p = subprocess.Popen(build_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
|
||||
num_dots = 1
|
||||
while p.poll() is None:
|
||||
self.ui.labelRebuildNativeStatus.setText(self.tr("Rebuilding device extensions" + ". " * num_dots))
|
||||
QApplication.instance().processEvents()
|
||||
time.sleep(0.1)
|
||||
num_dots %= 10
|
||||
num_dots += 1
|
||||
|
||||
rc = p.returncode
|
||||
if rc == 0:
|
||||
self.ui.labelRebuildNativeStatus.setText(self.tr("<font color=green>"
|
||||
"Rebuilt {0} device extensions. "
|
||||
"</font>"
|
||||
"Please restart URH.".format(len(extensions))))
|
||||
else:
|
||||
self.ui.labelRebuildNativeStatus.setText(self.tr("<font color='red'>"
|
||||
"Failed to rebuild {0} device extensions. "
|
||||
"</font>"
|
||||
"Run URH as root (<b>sudo urh</b>) "
|
||||
"and try again.".format(len(extensions))))
|
||||
|
||||
self.build_log = p.stdout.read().decode()
|
||||
self.ui.btnViewBuildLog.show()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_checkbox_multiple_modulations_clicked(self):
|
||||
settings.write("multiple_modulations", self.ui.checkBoxMultipleModulations.isChecked())
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_view_build_log_clicked(self):
|
||||
if not self.build_log:
|
||||
return
|
||||
|
||||
dialog = util.create_textbox_dialog(self.build_log, "Build log", parent=self)
|
||||
dialog.show()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_label_device_missing_info_link_activated(self, link: str):
|
||||
if link == "health_check":
|
||||
info = ExtensionHelper.perform_health_check()
|
||||
info += "\n" + BackendHandler.perform_soundcard_health_check()
|
||||
|
||||
if util.get_shared_library_path():
|
||||
if sys.platform == "win32":
|
||||
info += "\n\n[INFO] Used DLLs from " + util.get_shared_library_path()
|
||||
else:
|
||||
info += "\n\n[INFO] Used shared libraries from " + util.get_shared_library_path()
|
||||
|
||||
d = util.create_textbox_dialog(info, "Health check for native extensions", self)
|
||||
d.show()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spin_box_font_size_editing_finished(self):
|
||||
settings.write("font_size", self.ui.spinBoxFontSize.value())
|
||||
font = qApp.font()
|
||||
font.setPointSize(self.ui.spinBoxFontSize.value())
|
||||
qApp.setFont(font)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_radio_button_high_modulation_accuracy_clicked(self, checked):
|
||||
if checked:
|
||||
settings.write("modulation_dtype", "float32")
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_radio_button_medium_modulation_accuracy_clicked(self, checked):
|
||||
if checked:
|
||||
settings.write("modulation_dtype", "int16")
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_radio_button_low_modulation_accuracy_clicked(self, checked):
|
||||
if checked:
|
||||
settings.write("modulation_dtype", "int8")
|
||||
|
||||
@staticmethod
|
||||
def write_default_options():
|
||||
keys = settings.all_keys()
|
||||
|
||||
if 'default_view' not in keys:
|
||||
settings.write('default_view', 0)
|
||||
|
||||
if 'num_sending_repeats' not in keys:
|
||||
settings.write('num_sending_repeats', 0)
|
||||
|
||||
if 'show_pause_as_time' not in keys:
|
||||
settings.write('show_pause_as_time', False)
|
||||
|
||||
settings.sync() # Ensure conf dir is created to have field types in place
|
||||
|
||||
if not os.path.isfile(settings.FIELD_TYPE_SETTINGS):
|
||||
FieldType.save_to_xml(FieldType.default_field_types())
|
||||
|
||||
bh = BackendHandler()
|
||||
for be in bh.device_backends.values():
|
||||
be.write_settings()
|
||||
@@ -0,0 +1,187 @@
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QRegExp, Qt
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtGui import QRegExpValidator, QCloseEvent
|
||||
from PyQt5.QtWidgets import QDialog, QCompleter, QDirModel
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.dialogs.SpectrumDialogController import SpectrumDialogController
|
||||
from urh.dev import config
|
||||
from urh.models.ParticipantTableModel import ParticipantTableModel
|
||||
from urh.signalprocessing.Participant import Participant
|
||||
from urh.ui.ui_project import Ui_ProjectDialog
|
||||
from urh.util import FileOperator
|
||||
from urh.util.Errors import Errors
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class ProjectDialog(QDialog):
|
||||
def __init__(self, new_project=True, project_manager: ProjectManager = None, parent=None):
|
||||
super().__init__(parent)
|
||||
if not new_project:
|
||||
assert project_manager is not None
|
||||
|
||||
self.ui = Ui_ProjectDialog()
|
||||
self.ui.setupUi(self)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
if new_project:
|
||||
self.participant_table_model = ParticipantTableModel([])
|
||||
else:
|
||||
self.participant_table_model = ParticipantTableModel(project_manager.participants)
|
||||
|
||||
self.ui.spinBoxSampleRate.setValue(project_manager.device_conf["sample_rate"])
|
||||
self.ui.spinBoxFreq.setValue(project_manager.device_conf["frequency"])
|
||||
self.ui.spinBoxBandwidth.setValue(project_manager.device_conf["bandwidth"])
|
||||
self.ui.spinBoxGain.setValue(project_manager.device_conf.get("gain", config.DEFAULT_GAIN))
|
||||
self.ui.txtEdDescription.setPlainText(project_manager.description)
|
||||
self.ui.lineEdit_Path.setText(project_manager.project_path)
|
||||
self.ui.lineEditBroadcastAddress.setText(project_manager.broadcast_address_hex)
|
||||
|
||||
self.ui.btnSelectPath.hide()
|
||||
self.ui.lineEdit_Path.setDisabled(True)
|
||||
self.setWindowTitle("Edit project settings")
|
||||
self.ui.lNewProject.setText("Edit project")
|
||||
|
||||
self.ui.tblParticipants.setModel(self.participant_table_model)
|
||||
self.participant_table_model.update()
|
||||
|
||||
self.ui.lineEditBroadcastAddress.setValidator(QRegExpValidator(QRegExp("([a-fA-F ]|[0-9]){,}")))
|
||||
|
||||
self.sample_rate = self.ui.spinBoxSampleRate.value()
|
||||
self.freq = self.ui.spinBoxFreq.value()
|
||||
self.bandwidth = self.ui.spinBoxBandwidth.value()
|
||||
self.gain = self.ui.spinBoxGain.value()
|
||||
self.description = self.ui.txtEdDescription.toPlainText()
|
||||
self.broadcast_address_hex = self.ui.lineEditBroadcastAddress.text()
|
||||
|
||||
self.path = self.ui.lineEdit_Path.text()
|
||||
self.new_project = new_project
|
||||
self.committed = False
|
||||
self.setModal(True)
|
||||
|
||||
completer = QCompleter()
|
||||
completer.setModel(QDirModel(completer))
|
||||
self.ui.lineEdit_Path.setCompleter(completer)
|
||||
|
||||
self.create_connects()
|
||||
# add two participants
|
||||
if self.participant_table_model.rowCount() == 0 and new_project:
|
||||
self.ui.btnAddParticipant.click()
|
||||
self.ui.btnAddParticipant.click()
|
||||
|
||||
if new_project:
|
||||
self.ui.lineEdit_Path.setText(os.path.realpath(os.path.join(os.curdir, "new")))
|
||||
|
||||
self.on_line_edit_path_text_edited()
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
@property
|
||||
def participants(self):
|
||||
"""
|
||||
|
||||
:rtype: list of Participant
|
||||
"""
|
||||
return self.participant_table_model.participants
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.spinBoxFreq.valueChanged.connect(self.on_spin_box_frequency_value_changed)
|
||||
self.ui.spinBoxSampleRate.valueChanged.connect(self.on_spin_box_sample_rate_value_changed)
|
||||
self.ui.spinBoxBandwidth.valueChanged.connect(self.on_spin_box_bandwidth_value_changed)
|
||||
self.ui.spinBoxGain.valueChanged.connect(self.on_spin_box_gain_value_changed)
|
||||
self.ui.txtEdDescription.textChanged.connect(self.on_txt_edit_description_text_changed)
|
||||
self.ui.lineEditBroadcastAddress.textEdited.connect(self.on_line_edit_broadcast_address_text_edited)
|
||||
|
||||
self.ui.btnAddParticipant.clicked.connect(self.ui.tblParticipants.on_add_action_triggered)
|
||||
self.ui.btnRemoveParticipant.clicked.connect(self.ui.tblParticipants.on_remove_action_triggered)
|
||||
self.ui.btnUp.clicked.connect(self.ui.tblParticipants.on_move_up_action_triggered)
|
||||
self.ui.btnDown.clicked.connect(self.ui.tblParticipants.on_move_down_action_triggered)
|
||||
|
||||
self.ui.lineEdit_Path.textEdited.connect(self.on_line_edit_path_text_edited)
|
||||
self.ui.buttonBox.accepted.connect(self.on_button_box_accepted)
|
||||
self.ui.buttonBox.rejected.connect(self.reject)
|
||||
self.ui.btnSelectPath.clicked.connect(self.on_btn_select_path_clicked)
|
||||
self.ui.lOpenSpectrumAnalyzer.linkActivated.connect(self.on_spectrum_analyzer_link_activated)
|
||||
|
||||
def set_path(self, path):
|
||||
self.path = path
|
||||
self.ui.lineEdit_Path.setText(self.path)
|
||||
name = os.path.basename(os.path.normpath(self.path))
|
||||
self.ui.lblName.setText(name)
|
||||
|
||||
self.ui.lblNewPath.setVisible(not os.path.isdir(self.path))
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_spin_box_sample_rate_value_changed(self, value: float):
|
||||
self.sample_rate = value
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_spin_box_frequency_value_changed(self, value: float):
|
||||
self.freq = value
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_spin_box_bandwidth_value_changed(self, value: float):
|
||||
self.bandwidth = value
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spin_box_gain_value_changed(self, value: int):
|
||||
self.gain = value
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_path_text_edited(self):
|
||||
self.set_path(self.ui.lineEdit_Path.text())
|
||||
|
||||
@pyqtSlot()
|
||||
def on_txt_edit_description_text_changed(self):
|
||||
self.description = self.ui.txtEdDescription.toPlainText()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_button_box_accepted(self):
|
||||
self.path = os.path.realpath(self.path)
|
||||
if not os.path.exists(self.path):
|
||||
try:
|
||||
os.makedirs(self.path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Path should be created now, if not raise Error
|
||||
if not os.path.exists(self.path):
|
||||
Errors.invalid_path(self.path)
|
||||
return
|
||||
|
||||
self.committed = True
|
||||
self.accept()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_line_edit_broadcast_address_text_edited(self, value: str):
|
||||
self.broadcast_address_hex = value
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_select_path_clicked(self):
|
||||
directory = FileOperator.get_directory()
|
||||
if directory:
|
||||
self.set_path(directory)
|
||||
|
||||
@pyqtSlot(dict)
|
||||
def set_recording_params_from_spectrum_analyzer_link(self, args: dict):
|
||||
self.ui.spinBoxFreq.setValue(args["frequency"])
|
||||
self.ui.spinBoxSampleRate.setValue(args["sample_rate"])
|
||||
self.ui.spinBoxBandwidth.setValue(args["bandwidth"])
|
||||
self.ui.spinBoxGain.setValue(args.get("gain", config.DEFAULT_GAIN))
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_spectrum_analyzer_link_activated(self, link: str):
|
||||
if link == "open_spectrum_analyzer":
|
||||
r = SpectrumDialogController(ProjectManager(None), parent=self)
|
||||
if r.has_empty_device_list:
|
||||
Errors.no_device()
|
||||
r.close()
|
||||
return
|
||||
|
||||
r.device_parameters_changed.connect(self.set_recording_params_from_spectrum_analyzer_link)
|
||||
r.show()
|
||||
@@ -0,0 +1,128 @@
|
||||
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtGui import QKeyEvent, QCloseEvent
|
||||
from PyQt5.QtWidgets import QDialog, QHeaderView, QAbstractItemView
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.widgets.ChecksumWidget import ChecksumWidget
|
||||
from urh.models.PLabelTableModel import PLabelTableModel
|
||||
from urh.signalprocessing.ChecksumLabel import ChecksumLabel
|
||||
from urh.signalprocessing.FieldType import FieldType
|
||||
from urh.signalprocessing.Message import Message
|
||||
from urh.signalprocessing.MessageType import MessageType
|
||||
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
|
||||
from urh.simulator.SimulatorProtocolLabel import SimulatorProtocolLabel
|
||||
from urh.ui.delegates.CheckBoxDelegate import CheckBoxDelegate
|
||||
from urh.ui.delegates.ComboBoxDelegate import ComboBoxDelegate
|
||||
from urh.ui.delegates.SpinBoxDelegate import SpinBoxDelegate
|
||||
from urh.ui.ui_properties_dialog import Ui_DialogLabels
|
||||
from urh.util import util
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class ProtocolLabelDialog(QDialog):
|
||||
apply_decoding_changed = pyqtSignal(ProtocolLabel, MessageType)
|
||||
|
||||
SPECIAL_CONFIG_TYPES = [FieldType.Function.CHECKSUM]
|
||||
|
||||
def __init__(self, message: Message, viewtype: int, selected_index=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogLabels()
|
||||
self.ui.setupUi(self)
|
||||
util.set_splitter_stylesheet(self.ui.splitter)
|
||||
|
||||
field_types = FieldType.load_from_xml()
|
||||
self.model = PLabelTableModel(message, field_types)
|
||||
|
||||
self.ui.tblViewProtoLabels.setItemDelegateForColumn(0, ComboBoxDelegate([ft.caption for ft in field_types],
|
||||
is_editable=True,
|
||||
return_index=False, parent=self))
|
||||
self.ui.tblViewProtoLabels.setItemDelegateForColumn(1, SpinBoxDelegate(1, len(message), self))
|
||||
self.ui.tblViewProtoLabels.setItemDelegateForColumn(2, SpinBoxDelegate(1, len(message), self))
|
||||
self.ui.tblViewProtoLabels.setItemDelegateForColumn(3,
|
||||
ComboBoxDelegate([""] * len(settings.LABEL_COLORS),
|
||||
colors=settings.LABEL_COLORS,
|
||||
parent=self))
|
||||
self.ui.tblViewProtoLabels.setItemDelegateForColumn(4, CheckBoxDelegate(self))
|
||||
self.ui.tblViewProtoLabels.setModel(self.model)
|
||||
self.ui.tblViewProtoLabels.setEditTriggers(QAbstractItemView.AllEditTriggers)
|
||||
|
||||
self.ui.tblViewProtoLabels.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
|
||||
self.ui.tblViewProtoLabels.resizeColumnsToContents()
|
||||
self.setWindowFlags(Qt.Window)
|
||||
self.setWindowTitle(self.tr("Edit Protocol Labels From Message Type %s") % message.message_type.name)
|
||||
|
||||
self.configure_special_config_tabs()
|
||||
self.ui.splitter.setSizes([int(self.height() / 2), int(self.height() / 2)])
|
||||
|
||||
self.create_connects()
|
||||
|
||||
if selected_index is not None:
|
||||
self.ui.tblViewProtoLabels.setCurrentIndex(self.model.index(selected_index, 0))
|
||||
|
||||
self.ui.cbProtoView.setCurrentIndex(viewtype)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
for i in range(self.model.rowCount()):
|
||||
self.open_editors(i)
|
||||
|
||||
def configure_special_config_tabs(self):
|
||||
self.ui.tabWidgetAdvancedSettings.clear()
|
||||
for lbl in self.model.message_type: # type: ProtocolLabel
|
||||
if isinstance(lbl, SimulatorProtocolLabel):
|
||||
lbl = lbl.label
|
||||
|
||||
if lbl.field_type is not None and lbl.field_type.function in self.SPECIAL_CONFIG_TYPES:
|
||||
if isinstance(lbl, ChecksumLabel):
|
||||
w = ChecksumWidget(lbl, self.model.message, self.model.proto_view)
|
||||
self.ui.tabWidgetAdvancedSettings.addTab(w, lbl.name)
|
||||
else:
|
||||
logger.error("No Special Config Dialog for field type " + lbl.field_type.caption)
|
||||
|
||||
if self.ui.tabWidgetAdvancedSettings.count() > 0:
|
||||
self.ui.tabWidgetAdvancedSettings.setCurrentIndex(0)
|
||||
self.ui.tabWidgetAdvancedSettings.setFocus()
|
||||
|
||||
self.ui.groupBoxAdvancedSettings.setVisible(self.ui.tabWidgetAdvancedSettings.count() > 0)
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.btnConfirm.clicked.connect(self.confirm)
|
||||
self.ui.cbProtoView.currentIndexChanged.connect(self.set_view_index)
|
||||
self.model.apply_decoding_changed.connect(self.on_apply_decoding_changed)
|
||||
self.model.special_status_label_changed.connect(self.on_label_special_status_changed)
|
||||
|
||||
def open_editors(self, row):
|
||||
self.ui.tblViewProtoLabels.openPersistentEditor(self.model.index(row, 4))
|
||||
|
||||
def keyPressEvent(self, event: QKeyEvent):
|
||||
if event.key() == Qt.Key_Enter:
|
||||
event.ignore()
|
||||
else:
|
||||
event.accept()
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot()
|
||||
def confirm(self):
|
||||
self.close()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def set_view_index(self, ind):
|
||||
self.model.proto_view = ind
|
||||
self.model.update()
|
||||
|
||||
for i in range(self.ui.tabWidgetAdvancedSettings.count()):
|
||||
self.ui.tabWidgetAdvancedSettings.widget(i).proto_view = ind
|
||||
|
||||
@pyqtSlot(ProtocolLabel)
|
||||
def on_apply_decoding_changed(self, lbl: ProtocolLabel):
|
||||
self.apply_decoding_changed.emit(lbl, self.model.message_type)
|
||||
|
||||
@pyqtSlot(ProtocolLabel)
|
||||
def on_label_special_status_changed(self, lbl: ProtocolLabel):
|
||||
self.configure_special_config_tabs()
|
||||
@@ -0,0 +1,138 @@
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtGui import QIcon, QCloseEvent
|
||||
|
||||
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
|
||||
from urh.controller.widgets.SniffSettingsWidget import SniffSettingsWidget
|
||||
from urh.ui.painting.LiveSceneManager import LiveSceneManager
|
||||
from urh.ui.painting.SniffSceneManager import SniffSceneManager
|
||||
from urh.util import util
|
||||
|
||||
|
||||
class ProtocolSniffDialog(SendRecvDialog):
|
||||
protocol_accepted = pyqtSignal(list)
|
||||
|
||||
def __init__(self, project_manager, signal=None, signals=None, parent=None, testing_mode=False):
|
||||
super().__init__(project_manager, is_tx=False, parent=parent, testing_mode=testing_mode)
|
||||
|
||||
self.graphics_view = self.ui.graphicsView_sniff_Preview
|
||||
self.ui.stackedWidget.setCurrentWidget(self.ui.page_sniff)
|
||||
self.hide_send_ui_items()
|
||||
self.hide_receive_ui_items()
|
||||
self.ui.sliderYscale.hide()
|
||||
self.ui.label_y_scale.hide()
|
||||
|
||||
signals = [] if signals is None else signals
|
||||
|
||||
self.sniff_settings_widget = SniffSettingsWidget(project_manager=project_manager,
|
||||
device_name=self.selected_device_name,
|
||||
signal=signal, signals=signals,
|
||||
backend_handler=self.backend_handler)
|
||||
self.ui.scrollAreaWidgetContents_2.layout().insertWidget(1, self.sniff_settings_widget)
|
||||
self.sniff_settings_widget.ui.btn_sniff_use_signal.setAutoDefault(False)
|
||||
|
||||
self.sniffer = self.sniff_settings_widget.sniffer
|
||||
self.setWindowTitle(self.tr("Sniff Protocol"))
|
||||
self.setWindowIcon(QIcon.fromTheme(":/icons/icons/sniffer.svg"))
|
||||
|
||||
self.ui.txtEd_sniff_Preview.setFont(util.get_monospace_font())
|
||||
|
||||
# set really in on_device_started
|
||||
self.scene_manager = None # type: LiveSceneManager
|
||||
self.create_connects()
|
||||
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
|
||||
|
||||
@property
|
||||
def view_type(self) -> int:
|
||||
return self.sniff_settings_widget.ui.comboBox_sniff_viewtype.currentIndex()
|
||||
|
||||
@property
|
||||
def show_timestamp(self) -> bool:
|
||||
return self.sniff_settings_widget.ui.checkBox_sniff_Timestamp.isChecked()
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
self.sniff_settings_widget.emit_sniff_parameters_changed()
|
||||
super().closeEvent(event)
|
||||
|
||||
def create_connects(self):
|
||||
super().create_connects()
|
||||
self.ui.btnAccept.clicked.connect(self.on_btn_accept_clicked)
|
||||
self.sniff_settings_widget.sniff_parameters_changed.connect(self.device_parameters_changed.emit)
|
||||
|
||||
self.sniff_settings_widget.sniff_setting_edited.connect(self.on_sniff_setting_edited)
|
||||
self.sniff_settings_widget.sniff_file_edited.connect(self.on_sniff_file_edited)
|
||||
self.sniffer.message_sniffed.connect(self.on_message_sniffed)
|
||||
self.sniffer.qt_signals.sniff_device_errors_changed.connect(self.on_device_errors_changed)
|
||||
|
||||
def init_device(self):
|
||||
self.sniffer.device_name = self.selected_device_name
|
||||
self.device = self.sniffer.rcv_device
|
||||
|
||||
self._create_device_connects()
|
||||
self.scene_manager = SniffSceneManager(np.array([], dtype=self.device.data_type), parent=self)
|
||||
|
||||
def emit_editing_finished_signals(self):
|
||||
super().emit_editing_finished_signals()
|
||||
self.sniff_settings_widget.emit_editing_finished_signals()
|
||||
|
||||
def update_view(self):
|
||||
if super().update_view():
|
||||
self.scene_manager.end = self.device.current_index
|
||||
self.scene_manager.init_scene()
|
||||
self.scene_manager.show_full_scene()
|
||||
self.graphics_view.update()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_started(self):
|
||||
self.scene_manager.data_array = self.device.data.real if hasattr(self.device.data, "real") else None
|
||||
|
||||
super().on_device_started()
|
||||
|
||||
self.ui.btnStart.setEnabled(False)
|
||||
self.set_device_ui_items_enabled(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_sniff_setting_edited(self):
|
||||
self.ui.txtEd_sniff_Preview.setPlainText(self.sniffer.decoded_to_string(self.view_type,
|
||||
include_timestamps=self.show_timestamp))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_start_clicked(self):
|
||||
super().on_start_clicked()
|
||||
self.sniffer.sniff()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_stop_clicked(self):
|
||||
self.sniffer.stop()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_clear_clicked(self):
|
||||
self.ui.txtEd_sniff_Preview.clear()
|
||||
self.scene_manager.clear_path()
|
||||
self.device.current_index = 0
|
||||
self.sniffer.clear()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_message_sniffed(self, index: int):
|
||||
try:
|
||||
msg = self.sniffer.messages[index]
|
||||
except IndexError:
|
||||
return
|
||||
new_data = self.sniffer.message_to_string(msg, self.view_type, include_timestamps=self.show_timestamp)
|
||||
if new_data.strip():
|
||||
self.ui.txtEd_sniff_Preview.appendPlainText(new_data)
|
||||
self.ui.txtEd_sniff_Preview.verticalScrollBar().setValue(
|
||||
self.ui.txtEd_sniff_Preview.verticalScrollBar().maximum())
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_accept_clicked(self):
|
||||
self.protocol_accepted.emit(self.sniffer.messages)
|
||||
self.close()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_device_errors_changed(self, txt: str):
|
||||
self.ui.txtEditErrors.append(txt)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_sniff_file_edited(self):
|
||||
self.ui.btnAccept.setDisabled(bool(self.sniffer.sniff_file))
|
||||
@@ -0,0 +1,110 @@
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
|
||||
from urh.dev.VirtualDevice import Mode, VirtualDevice
|
||||
from urh.ui.painting.LiveSceneManager import LiveSceneManager
|
||||
from urh.util import FileOperator
|
||||
from urh.util.Formatter import Formatter
|
||||
from datetime import datetime
|
||||
|
||||
class ReceiveDialog(SendRecvDialog):
|
||||
files_recorded = pyqtSignal(list, float)
|
||||
|
||||
def __init__(self, project_manager, parent=None, testing_mode=False):
|
||||
try:
|
||||
super().__init__(project_manager, is_tx=False, parent=parent, testing_mode=testing_mode)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
self.graphics_view = self.ui.graphicsViewReceive
|
||||
self.ui.stackedWidget.setCurrentWidget(self.ui.page_receive)
|
||||
self.hide_send_ui_items()
|
||||
self.already_saved = True
|
||||
self.recorded_files = []
|
||||
|
||||
self.setWindowTitle("Record Signal")
|
||||
self.setWindowIcon(QIcon.fromTheme("media-record"))
|
||||
|
||||
# set really in on_device_started
|
||||
self.scene_manager = None # type: LiveSceneManager
|
||||
self.create_connects()
|
||||
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
|
||||
|
||||
def create_connects(self):
|
||||
super().create_connects()
|
||||
self.ui.btnSave.clicked.connect(self.on_save_clicked)
|
||||
|
||||
def save_before_close(self):
|
||||
if not self.already_saved and self.device.current_index > 0:
|
||||
reply = QMessageBox.question(self, self.tr("Save data?"),
|
||||
self.tr("Do you want to save the data you have captured so far?"),
|
||||
QMessageBox.Yes | QMessageBox.No | QMessageBox.Abort)
|
||||
if reply == QMessageBox.Yes:
|
||||
self.on_save_clicked()
|
||||
elif reply == QMessageBox.Abort:
|
||||
return False
|
||||
|
||||
try:
|
||||
sample_rate = self.device.sample_rate
|
||||
except:
|
||||
sample_rate = 1e6
|
||||
|
||||
self.files_recorded.emit(self.recorded_files, sample_rate)
|
||||
return True
|
||||
|
||||
def update_view(self):
|
||||
if super().update_view():
|
||||
self.scene_manager.end = self.device.current_index
|
||||
self.scene_manager.init_scene()
|
||||
self.scene_manager.show_full_scene()
|
||||
self.graphics_view.update()
|
||||
|
||||
def init_device(self):
|
||||
self.device = VirtualDevice(self.backend_handler, self.selected_device_name, Mode.receive,
|
||||
device_ip="192.168.10.2", parent=self)
|
||||
self._create_device_connects()
|
||||
self.scene_manager = LiveSceneManager(np.array([], dtype=self.device.data_type), parent=self)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_start_clicked(self):
|
||||
super().on_start_clicked()
|
||||
self.device.start()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_started(self):
|
||||
self.scene_manager.plot_data = self.device.data.real if self.device.data is not None else None
|
||||
|
||||
super().on_device_started()
|
||||
|
||||
self.already_saved = False
|
||||
self.ui.btnStart.setEnabled(False)
|
||||
self.set_device_ui_items_enabled(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_clear_clicked(self):
|
||||
self.scene_manager.clear_path()
|
||||
self.reset()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_save_clicked(self):
|
||||
data = self.device.data[:self.device.current_index]
|
||||
|
||||
dev = self.device
|
||||
big_val = Formatter.big_value_with_suffix
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
initial_name = "{0}-{1}-{2}Hz-{3}Sps".format(dev.name, timestamp,
|
||||
big_val(dev.frequency), big_val(dev.sample_rate))
|
||||
|
||||
if dev.bandwidth_is_adjustable:
|
||||
initial_name += "-{}Hz".format(big_val(dev.bandwidth))
|
||||
|
||||
initial_name = initial_name.replace(Formatter.local_decimal_seperator(), "_").replace("_000", "")
|
||||
|
||||
filename = FileOperator.ask_signal_file_name_and_save(initial_name, data,
|
||||
sample_rate=dev.sample_rate, parent=self)
|
||||
self.already_saved = True
|
||||
if filename is not None and filename not in self.recorded_files:
|
||||
self.recorded_files.append(filename)
|
||||
@@ -0,0 +1,153 @@
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QBrush, QColor, QIcon, QPen
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
|
||||
from urh.dev.VirtualDevice import VirtualDevice, Mode
|
||||
from urh.signalprocessing.IQArray import IQArray
|
||||
from urh.signalprocessing.Signal import Signal
|
||||
from urh.ui.painting.SignalSceneManager import SignalSceneManager
|
||||
from urh.util import FileOperator
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class SendDialog(SendRecvDialog):
|
||||
def __init__(self, project_manager, modulated_data, modulation_msg_indices=None, continuous_send_mode=False,
|
||||
parent=None, testing_mode=False):
|
||||
super().__init__(project_manager, is_tx=True, continuous_send_mode=continuous_send_mode,
|
||||
parent=parent, testing_mode=testing_mode)
|
||||
|
||||
self.graphics_view = self.ui.graphicsViewSend
|
||||
self.ui.stackedWidget.setCurrentWidget(self.ui.page_send)
|
||||
self.hide_receive_ui_items()
|
||||
|
||||
self.ui.btnStart.setIcon(QIcon.fromTheme("media-playback-start"))
|
||||
self.setWindowTitle("Send Signal")
|
||||
self.setWindowIcon(QIcon.fromTheme("media-playback-start"))
|
||||
self.ui.btnStart.setToolTip("Send data")
|
||||
self.ui.btnStop.setToolTip("Stop sending")
|
||||
self.device_is_sending = False
|
||||
self.modulation_msg_indices = modulation_msg_indices
|
||||
|
||||
if self.modulation_msg_indices is not None:
|
||||
self.ui.progressBarMessage.setMaximum(len(self.modulation_msg_indices))
|
||||
else:
|
||||
self.ui.progressBarMessage.hide()
|
||||
self.ui.labelCurrentMessage.hide()
|
||||
|
||||
if modulated_data is not None:
|
||||
assert isinstance(modulated_data, IQArray)
|
||||
# modulated_data is none in continuous send mode
|
||||
self.ui.progressBarSample.setMaximum(len(modulated_data))
|
||||
samp_rate = self.device_settings_widget.ui.spinBoxSampleRate.value()
|
||||
signal = Signal("", "Modulated Preview", sample_rate=samp_rate)
|
||||
signal.iq_array = modulated_data
|
||||
self.scene_manager = SignalSceneManager(signal, parent=self)
|
||||
self.send_indicator = self.scene_manager.scene.addRect(0, -2, 0, 4,
|
||||
QPen(QColor(Qt.transparent), 0),
|
||||
QBrush(settings.SEND_INDICATOR_COLOR))
|
||||
self.send_indicator.stackBefore(self.scene_manager.scene.selection_area)
|
||||
self.scene_manager.init_scene()
|
||||
self.graphics_view.set_signal(signal)
|
||||
self.graphics_view.sample_rate = samp_rate
|
||||
|
||||
self.create_connects()
|
||||
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
|
||||
|
||||
def create_connects(self):
|
||||
super().create_connects()
|
||||
|
||||
self.graphics_view.save_as_clicked.connect(self.on_graphics_view_save_as_clicked)
|
||||
self.scene_manager.signal.data_edited.connect(self.on_signal_data_edited)
|
||||
|
||||
def _update_send_indicator(self, width: int):
|
||||
y, h = self.ui.graphicsViewSend.view_rect().y(), self.ui.graphicsViewSend.view_rect().height()
|
||||
self.send_indicator.setRect(0, y - h, width, 2 * h + abs(y))
|
||||
|
||||
def set_current_message_progress_bar_value(self, current_sample: int):
|
||||
if self.modulation_msg_indices is not None:
|
||||
msg_index = next((i for i, sample in enumerate(self.modulation_msg_indices) if sample >= current_sample),
|
||||
len(self.modulation_msg_indices))
|
||||
self.ui.progressBarMessage.setValue(msg_index + 1)
|
||||
|
||||
def update_view(self):
|
||||
if super().update_view():
|
||||
self._update_send_indicator(self.device.current_index)
|
||||
self.ui.progressBarSample.setValue(self.device.current_index)
|
||||
self.set_current_message_progress_bar_value(self.device.current_index)
|
||||
|
||||
if not self.device.sending_finished:
|
||||
self.ui.lblCurrentRepeatValue.setText(str(self.device.current_iteration + 1))
|
||||
else:
|
||||
self.ui.btnStop.click()
|
||||
self.ui.lblCurrentRepeatValue.setText("Sending finished")
|
||||
|
||||
def init_device(self):
|
||||
device_name = self.selected_device_name
|
||||
num_repeats = self.device_settings_widget.ui.spinBoxNRepeat.value()
|
||||
sts = self.scene_manager.signal.iq_array
|
||||
|
||||
self.device = VirtualDevice(self.backend_handler, device_name, Mode.send, samples_to_send=sts,
|
||||
device_ip="192.168.10.2", sending_repeats=num_repeats, parent=self)
|
||||
self._create_device_connects()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_graphics_view_save_as_clicked(self):
|
||||
filename = FileOperator.ask_save_file_name("signal.complex")
|
||||
if filename:
|
||||
try:
|
||||
try:
|
||||
self.scene_manager.signal.sample_rate = self.device.sample_rate
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
self.scene_manager.signal.save_as(filename)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, self.tr("Error saving signal"), e.args[0])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_signal_data_edited(self):
|
||||
signal = self.scene_manager.signal
|
||||
self.ui.progressBarSample.setMaximum(signal.num_samples)
|
||||
self.device.samples_to_send = signal.iq_array.data
|
||||
self.scene_manager.init_scene()
|
||||
self.ui.graphicsViewSend.redraw_view()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_start_clicked(self):
|
||||
super().on_start_clicked()
|
||||
if self.ui.progressBarSample.value() >= self.ui.progressBarSample.maximum() - 1:
|
||||
self.on_clear_clicked()
|
||||
|
||||
if self.device_is_sending:
|
||||
self.device.stop("Sending paused by user")
|
||||
else:
|
||||
self.device.start()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_stop_clicked(self):
|
||||
super().on_stop_clicked()
|
||||
self.on_clear_clicked()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_stopped(self):
|
||||
super().on_device_stopped()
|
||||
self.ui.btnStart.setIcon(QIcon.fromTheme("media-playback-start"))
|
||||
self.ui.btnStart.setText("Start")
|
||||
self.ui.btnStart.setToolTip("Start sending")
|
||||
self.device_is_sending = False
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_started(self):
|
||||
super().on_device_started()
|
||||
self.device_is_sending = True
|
||||
self.ui.btnStart.setEnabled(True)
|
||||
self.ui.btnStart.setIcon(QIcon.fromTheme("media-playback-pause"))
|
||||
self.ui.btnStart.setText("Pause")
|
||||
self.set_device_ui_items_enabled(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_clear_clicked(self):
|
||||
self._update_send_indicator(0)
|
||||
self.reset()
|
||||
@@ -0,0 +1,287 @@
|
||||
import locale
|
||||
import time
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QTimer, pyqtSignal, Qt
|
||||
from PyQt5.QtGui import QCloseEvent, QTransform
|
||||
from PyQt5.QtWidgets import QDialog, QGraphicsView
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.widgets.DeviceSettingsWidget import DeviceSettingsWidget
|
||||
from urh.dev.BackendHandler import BackendHandler, Backends
|
||||
from urh.dev.VirtualDevice import VirtualDevice
|
||||
from urh.plugins.NetworkSDRInterface.NetworkSDRInterfacePlugin import NetworkSDRInterfacePlugin
|
||||
from urh.ui.ui_send_recv import Ui_SendRecvDialog
|
||||
from urh.util import util
|
||||
from urh.util.Errors import Errors
|
||||
from urh.util.Formatter import Formatter
|
||||
from urh.util.Logger import logger
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class SendRecvDialog(QDialog):
|
||||
device_parameters_changed = pyqtSignal(dict)
|
||||
|
||||
def __init__(self, project_manager: ProjectManager, is_tx: bool, continuous_send_mode=False, parent=None, testing_mode=False):
|
||||
super().__init__(parent)
|
||||
self.is_tx = is_tx
|
||||
self.update_interval = 25
|
||||
|
||||
# This flag is needed. Will cause memory leak otherwise.
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
|
||||
self.setWindowFlags(Qt.Window)
|
||||
self.testing_mode = testing_mode
|
||||
|
||||
self.ui = Ui_SendRecvDialog()
|
||||
self.ui.setupUi(self)
|
||||
util.set_splitter_stylesheet(self.ui.splitter)
|
||||
|
||||
self.ui.txtEditErrors.setFont(util.get_monospace_font())
|
||||
|
||||
self.graphics_view = None # type: QGraphicsView
|
||||
|
||||
self.backend_handler = BackendHandler()
|
||||
|
||||
self.ui.btnStop.setEnabled(False)
|
||||
self.ui.btnSave.setEnabled(False)
|
||||
|
||||
self.start = 0
|
||||
|
||||
self.device_settings_widget = DeviceSettingsWidget(project_manager, is_tx,
|
||||
backend_handler=self.backend_handler,
|
||||
continuous_send_mode=continuous_send_mode)
|
||||
self.ui.scrollAreaWidgetContents_2.layout().insertWidget(0, self.device_settings_widget)
|
||||
|
||||
if testing_mode:
|
||||
self.device_settings_widget.ui.cbDevice.setCurrentText(NetworkSDRInterfacePlugin.NETWORK_SDR_NAME)
|
||||
|
||||
self.timer = QTimer(self)
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
self.ui.splitter.setSizes([int(0.4 * self.width()), int(0.6 * self.width())])
|
||||
|
||||
self.current_y_slider_value = 1
|
||||
|
||||
@property
|
||||
def is_rx(self) -> bool:
|
||||
return not self.is_tx
|
||||
|
||||
@property
|
||||
def has_empty_device_list(self):
|
||||
return self.device_settings_widget.ui.cbDevice.count() == 0
|
||||
|
||||
@property
|
||||
def device(self) -> VirtualDevice:
|
||||
return self.device_settings_widget.device
|
||||
|
||||
@device.setter
|
||||
def device(self, value):
|
||||
self.device_settings_widget.device = value
|
||||
|
||||
@property
|
||||
def selected_device_name(self) -> str:
|
||||
return self.device_settings_widget.ui.cbDevice.currentText()
|
||||
|
||||
def _eliminate_graphic_view(self):
|
||||
if self.graphics_view is not None:
|
||||
self.graphics_view.eliminate()
|
||||
|
||||
self.graphics_view = None
|
||||
|
||||
def hide_send_ui_items(self):
|
||||
for item in ("lblCurrentRepeatValue", "progressBarMessage",
|
||||
"lblRepeatText", "lSamplesSentText", "progressBarSample", "labelCurrentMessage"):
|
||||
getattr(self.ui, item).hide()
|
||||
|
||||
def hide_receive_ui_items(self):
|
||||
for item in ("lSamplesCaptured", "lSamplesCapturedText", "lSignalSize", "lSignalSizeText",
|
||||
"lTime", "lTimeText", "btnSave", "labelReceiveBufferFull", "lReceiveBufferFullText"):
|
||||
getattr(self.ui, item).hide()
|
||||
|
||||
def set_device_ui_items_enabled(self, enabled: bool):
|
||||
self.device_settings_widget.setEnabled(enabled)
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.btnStart.clicked.connect(self.on_start_clicked)
|
||||
self.ui.btnStop.clicked.connect(self.on_stop_clicked)
|
||||
self.ui.btnClear.clicked.connect(self.on_clear_clicked)
|
||||
|
||||
self.timer.timeout.connect(self.update_view)
|
||||
self.ui.sliderYscale.valueChanged.connect(self.on_slider_y_scale_value_changed)
|
||||
|
||||
self.device_settings_widget.selected_device_changed.connect(self.on_selected_device_changed)
|
||||
self.device_settings_widget.device_parameters_changed.connect(self.device_parameters_changed.emit)
|
||||
|
||||
def _create_device_connects(self):
|
||||
self.device.stopped.connect(self.on_device_stopped)
|
||||
self.device.started.connect(self.on_device_started)
|
||||
self.device.sender_needs_restart.connect(self._restart_device_thread)
|
||||
|
||||
def reset(self):
|
||||
self.device.current_index = 0
|
||||
self.device.current_iteration = 0
|
||||
self.ui.lSamplesCaptured.setText("0")
|
||||
self.ui.lSignalSize.setText("0")
|
||||
self.ui.lTime.setText("0")
|
||||
self.ui.lblCurrentRepeatValue.setText("-")
|
||||
self.ui.progressBarSample.setValue(0)
|
||||
self.ui.progressBarMessage.setValue(0)
|
||||
self.ui.btnSave.setEnabled(False)
|
||||
|
||||
def init_device(self):
|
||||
pass
|
||||
|
||||
def save_before_close(self):
|
||||
return True
|
||||
|
||||
def emit_editing_finished_signals(self):
|
||||
self.device_settings_widget.emit_editing_finished_signals()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_selected_device_changed(self):
|
||||
if hasattr(self.scene_manager, "plot_data"):
|
||||
self.scene_manager.plot_data = None
|
||||
|
||||
self.init_device()
|
||||
|
||||
self.graphics_view.scene_manager = self.scene_manager
|
||||
self.graphics_view.setScene(self.scene_manager.scene)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_start_clicked(self):
|
||||
self.emit_editing_finished_signals()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_stop_clicked(self):
|
||||
self.device.stop("Stopped receiving: Stop button clicked")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_stopped(self):
|
||||
if self.graphics_view is not None:
|
||||
self.graphics_view.capturing_data = False
|
||||
self.set_device_ui_items_enabled(True)
|
||||
self.ui.btnStart.setEnabled(True)
|
||||
self.ui.btnStop.setEnabled(False)
|
||||
self.ui.btnSave.setEnabled(self.device.current_index > 0)
|
||||
self.device_settings_widget.ui.comboBoxDeviceIdentifier.setEnabled(True)
|
||||
self.device_settings_widget.ui.btnRefreshDeviceIdentifier.setEnabled(True)
|
||||
self.device_settings_widget.set_bandwidth_status()
|
||||
|
||||
self.timer.stop()
|
||||
self.update_view()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_started(self):
|
||||
self.ui.txtEditErrors.clear()
|
||||
if self.graphics_view is not None:
|
||||
self.graphics_view.capturing_data = True
|
||||
self.ui.btnSave.setEnabled(False)
|
||||
self.ui.btnStart.setEnabled(False)
|
||||
self.ui.btnStop.setEnabled(True)
|
||||
self.device_settings_widget.ui.comboBoxDeviceIdentifier.setEnabled(False)
|
||||
self.device_settings_widget.ui.btnRefreshDeviceIdentifier.setEnabled(False)
|
||||
|
||||
self.timer.start(self.update_interval)
|
||||
|
||||
def __parse_error_messages(self, messages):
|
||||
messages = messages.lower()
|
||||
|
||||
if "no devices found for" in messages:
|
||||
self.device.stop_on_error("Could not establish connection to USRP")
|
||||
Errors.usrp_found()
|
||||
self.on_clear_clicked()
|
||||
|
||||
elif any(e in messages for e in ("hackrf_error_not_found", "hackrf_error_libusb")):
|
||||
self.device.stop_on_error("Could not establish connection to HackRF")
|
||||
Errors.hackrf_not_found()
|
||||
self.on_clear_clicked()
|
||||
|
||||
elif "no module named gnuradio" in messages:
|
||||
self.device.stop_on_error("Did not find gnuradio.")
|
||||
Errors.gnuradio_not_installed()
|
||||
self.on_clear_clicked()
|
||||
|
||||
elif "rtlsdr-open: error code: -1" in messages:
|
||||
self.device.stop_on_error("Could not open a RTL-SDR device.")
|
||||
self.on_clear_clicked()
|
||||
|
||||
elif "rtlsdr-open: error code: -12" in messages:
|
||||
self.device.stop_on_error("Could not open a RTL-SDR device")
|
||||
Errors.rtlsdr_sdr_driver()
|
||||
self.on_clear_clicked()
|
||||
|
||||
elif "Address already in use" in messages:
|
||||
self._restart_device_thread()
|
||||
|
||||
def update_view(self):
|
||||
txt = self.ui.txtEditErrors.toPlainText()
|
||||
new_messages = self.device.read_messages()
|
||||
|
||||
self.__parse_error_messages(new_messages)
|
||||
|
||||
if len(new_messages) > 1:
|
||||
self.ui.txtEditErrors.setPlainText(txt + new_messages)
|
||||
|
||||
self.ui.lSamplesCaptured.setText(Formatter.big_value_with_suffix(self.device.current_index, decimals=1))
|
||||
self.ui.lSignalSize.setText(locale.format_string("%.2f", (8 * self.device.current_index) / (1024 ** 2)))
|
||||
self.ui.lTime.setText(locale.format_string("%.2f", self.device.current_index / self.device.sample_rate))
|
||||
|
||||
if self.is_rx and self.device.data is not None and len(self.device.data) > 0:
|
||||
self.ui.labelReceiveBufferFull.setText("{0}%".format(int(100 * self.device.current_index /
|
||||
len(self.device.data))))
|
||||
|
||||
if self.device.current_index == 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _restart_device_thread(self):
|
||||
self.device.stop("Restarting with new port")
|
||||
|
||||
if self.device.backend == Backends.grc:
|
||||
self.device.increase_gr_port()
|
||||
|
||||
self.device.start()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_clear_clicked(self):
|
||||
pass
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
if self.device.backend is not Backends.none:
|
||||
self.emit_editing_finished_signals()
|
||||
|
||||
self.timer.stop()
|
||||
|
||||
self.device.stop("Dialog closed. Killing recording process.")
|
||||
logger.debug("Device stopped successfully.")
|
||||
|
||||
if not self.testing_mode:
|
||||
if not self.save_before_close():
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
time.sleep(0.1)
|
||||
if self.device.backend not in (Backends.none, Backends.network):
|
||||
# Backend none is selected, when no device is available
|
||||
logger.debug("Cleaning up device")
|
||||
self.device.cleanup()
|
||||
logger.debug("Successfully cleaned up device")
|
||||
self.device_settings_widget.emit_device_parameters_changed()
|
||||
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
|
||||
if self.device is not None:
|
||||
self.device.free_data()
|
||||
|
||||
self.scene_manager.eliminate()
|
||||
|
||||
self._eliminate_graphic_view()
|
||||
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_slider_y_scale_value_changed(self, new_value: int):
|
||||
self.graphics_view.scale(1, new_value / self.current_y_slider_value)
|
||||
self.graphics_view.centerOn(0, 0)
|
||||
self.current_y_slider_value = new_value
|
||||
@@ -0,0 +1,54 @@
|
||||
import locale
|
||||
import os
|
||||
import time
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QCloseEvent
|
||||
from PyQt5.QtWidgets import QDialog
|
||||
|
||||
from urh import settings
|
||||
from urh.ui.ui_signal_details import Ui_SignalDetails
|
||||
from urh.util.Formatter import Formatter
|
||||
|
||||
|
||||
class SignalDetailsDialog(QDialog):
|
||||
def __init__(self, signal, parent=None):
|
||||
super().__init__(parent)
|
||||
self.signal = signal
|
||||
self.ui = Ui_SignalDetails()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
file = self.signal.filename
|
||||
|
||||
self.ui.lblName.setText(self.signal.name)
|
||||
|
||||
if os.path.isfile(file):
|
||||
self.ui.lblFile.setText(file)
|
||||
self.ui.lblFileSize.setText(locale.format_string("%.2fMB", os.path.getsize(file) / (1024 ** 2)))
|
||||
self.ui.lFileCreated.setText(time.ctime(os.path.getctime(file)))
|
||||
else:
|
||||
self.ui.lblFile.setText(self.tr("signal file not found"))
|
||||
self.ui.lblFileSize.setText("-")
|
||||
self.ui.lFileCreated.setText("-")
|
||||
|
||||
self.ui.lblSamplesTotal.setText("{0:n}".format(self.signal.num_samples).replace(",", " "))
|
||||
self.ui.dsb_sample_rate.setValue(self.signal.sample_rate)
|
||||
self.set_duration()
|
||||
|
||||
self.ui.dsb_sample_rate.valueChanged.connect(self.on_dsb_sample_rate_value_changed)
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_dsb_sample_rate_value_changed(self, value: float):
|
||||
self.signal.sample_rate = value
|
||||
self.set_duration()
|
||||
|
||||
def set_duration(self):
|
||||
dur = self.signal.num_samples / self.signal.sample_rate
|
||||
self.ui.lDuration.setText(Formatter.science_time(dur))
|
||||
@@ -0,0 +1,432 @@
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import QTimer, pyqtSlot, pyqtSignal, Qt
|
||||
from PyQt5.QtGui import QIcon, QCloseEvent
|
||||
from PyQt5.QtWidgets import QDialog, QFileDialog, QMessageBox, QGraphicsTextItem
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.dialogs.ProtocolSniffDialog import ProtocolSniffDialog
|
||||
from urh.controller.widgets.DeviceSettingsWidget import DeviceSettingsWidget
|
||||
from urh.controller.widgets.ModulationSettingsWidget import ModulationSettingsWidget
|
||||
from urh.controller.widgets.SniffSettingsWidget import SniffSettingsWidget
|
||||
from urh.dev.BackendHandler import BackendHandler
|
||||
from urh.dev.EndlessSender import EndlessSender
|
||||
from urh.signalprocessing.IQArray import IQArray
|
||||
from urh.simulator.Simulator import Simulator
|
||||
from urh.simulator.SimulatorConfiguration import SimulatorConfiguration
|
||||
from urh.ui.SimulatorScene import SimulatorScene
|
||||
from urh.ui.painting.LiveSceneManager import LiveSceneManager
|
||||
from urh.ui.painting.SniffSceneManager import SniffSceneManager
|
||||
from urh.ui.ui_simulator_dialog import Ui_DialogSimulator
|
||||
from urh.util import util, FileOperator
|
||||
from urh.util.Errors import Errors
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class SimulatorDialog(QDialog):
|
||||
rx_parameters_changed = pyqtSignal(dict)
|
||||
tx_parameters_changed = pyqtSignal(dict)
|
||||
sniff_parameters_changed = pyqtSignal(dict)
|
||||
open_in_analysis_requested = pyqtSignal(str)
|
||||
rx_file_saved = pyqtSignal(str)
|
||||
|
||||
def __init__(self, simulator_config, modulators,
|
||||
expression_parser, project_manager: ProjectManager, signals: list = None,
|
||||
signal_tree_model=None,
|
||||
parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogSimulator()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.simulator_config = simulator_config # type: SimulatorConfiguration
|
||||
self.rx_needed = self.simulator_config.rx_needed
|
||||
self.tx_needed = self.simulator_config.tx_needed
|
||||
|
||||
self.current_transcript_index = 0
|
||||
|
||||
self.simulator_scene = SimulatorScene(mode=1,
|
||||
simulator_config=self.simulator_config)
|
||||
self.ui.gvSimulator.setScene(self.simulator_scene)
|
||||
self.project_manager = project_manager
|
||||
|
||||
self.update_interval = 25
|
||||
|
||||
self.timer = QTimer(self)
|
||||
|
||||
self.backend_handler = BackendHandler()
|
||||
if self.rx_needed:
|
||||
self.device_settings_rx_widget = DeviceSettingsWidget(project_manager,
|
||||
is_tx=False,
|
||||
backend_handler=self.backend_handler)
|
||||
|
||||
self.sniff_settings_widget = SniffSettingsWidget(self.device_settings_rx_widget.ui.cbDevice.currentText(),
|
||||
project_manager,
|
||||
signal=None,
|
||||
backend_handler=self.backend_handler,
|
||||
network_raw_mode=True, signals=signals)
|
||||
|
||||
self.device_settings_rx_widget.device = self.sniff_settings_widget.sniffer.rcv_device
|
||||
|
||||
self.sniff_settings_widget.ui.lineEdit_sniff_OutputFile.hide()
|
||||
self.sniff_settings_widget.ui.label_sniff_OutputFile.hide()
|
||||
self.sniff_settings_widget.ui.label_sniff_viewtype.hide()
|
||||
self.sniff_settings_widget.ui.checkBox_sniff_Timestamp.hide()
|
||||
self.sniff_settings_widget.ui.comboBox_sniff_viewtype.hide()
|
||||
|
||||
self.ui.scrollAreaWidgetContentsRX.layout().insertWidget(0, self.device_settings_rx_widget)
|
||||
self.ui.scrollAreaWidgetContentsRX.layout().insertWidget(1, self.sniff_settings_widget)
|
||||
|
||||
sniffer = self.sniff_settings_widget.sniffer
|
||||
|
||||
self.scene_manager = SniffSceneManager(np.array([], dtype=sniffer.rcv_device.data_type), parent=self)
|
||||
self.ui.graphicsViewPreview.setScene(self.scene_manager.scene)
|
||||
else:
|
||||
self.device_settings_rx_widget = self.sniff_settings_widget = self.scene_manager = None
|
||||
self.ui.tabWidgetSimulatorSettings.setTabEnabled(1, False)
|
||||
self.ui.graphicsViewPreview.hide()
|
||||
self.ui.btnSaveRX.hide()
|
||||
self.ui.checkBoxCaptureFullRX.hide()
|
||||
|
||||
sniffer = None
|
||||
|
||||
if self.tx_needed:
|
||||
self.device_settings_tx_widget = DeviceSettingsWidget(project_manager, is_tx=True,
|
||||
backend_handler=self.backend_handler,
|
||||
continuous_send_mode=True)
|
||||
self.device_settings_tx_widget.ui.spinBoxNRepeat.hide()
|
||||
self.device_settings_tx_widget.ui.labelNRepeat.hide()
|
||||
|
||||
self.modulation_settings_widget = ModulationSettingsWidget(modulators, signal_tree_model=signal_tree_model,
|
||||
parent=None)
|
||||
|
||||
self.ui.scrollAreaWidgetContentsTX.layout().insertWidget(0, self.device_settings_tx_widget)
|
||||
self.ui.scrollAreaWidgetContentsTX.layout().insertWidget(1, self.modulation_settings_widget)
|
||||
send_device = self.device_settings_tx_widget.ui.cbDevice.currentText()
|
||||
sender = EndlessSender(self.backend_handler, send_device)
|
||||
else:
|
||||
self.device_settings_tx_widget = self.modulation_settings_widget = None
|
||||
self.ui.tabWidgetSimulatorSettings.setTabEnabled(2, False)
|
||||
|
||||
sender = None
|
||||
|
||||
self.simulator = Simulator(self.simulator_config, modulators, expression_parser, project_manager,
|
||||
sniffer=sniffer, sender=sender)
|
||||
|
||||
if self.device_settings_tx_widget:
|
||||
self.device_settings_tx_widget.device = self.simulator.sender.device
|
||||
|
||||
self.update_buttons()
|
||||
self.create_connects()
|
||||
|
||||
if self.device_settings_rx_widget:
|
||||
self.device_settings_rx_widget.bootstrap(project_manager.simulator_rx_conf)
|
||||
|
||||
if self.device_settings_tx_widget:
|
||||
self.device_settings_tx_widget.bootstrap(project_manager.simulator_tx_conf)
|
||||
|
||||
self.ui.textEditTranscript.setFont(util.get_monospace_font())
|
||||
|
||||
if settings.read('default_view', 0, int) == 1:
|
||||
self.ui.radioButtonTranscriptHex.setChecked(True)
|
||||
|
||||
def create_connects(self):
|
||||
if self.rx_needed:
|
||||
self.device_settings_rx_widget.selected_device_changed.connect(self.on_selected_rx_device_changed)
|
||||
self.device_settings_rx_widget.device_parameters_changed.connect(self.rx_parameters_changed.emit)
|
||||
|
||||
self.sniff_settings_widget.sniff_parameters_changed.connect(self.sniff_parameters_changed.emit)
|
||||
|
||||
self.ui.btnSaveRX.clicked.connect(self.on_btn_save_rx_clicked)
|
||||
self.ui.checkBoxCaptureFullRX.clicked.connect(self.on_checkbox_capture_full_rx_clicked)
|
||||
|
||||
self.ui.btnTestSniffSettings.clicked.connect(self.on_btn_test_sniff_settings_clicked)
|
||||
self.ui.btnOpenInAnalysis.clicked.connect(self.on_btn_open_in_analysis_clicked)
|
||||
|
||||
if self.tx_needed:
|
||||
self.device_settings_tx_widget.selected_device_changed.connect(self.on_selected_tx_device_changed)
|
||||
self.device_settings_tx_widget.device_parameters_changed.connect(self.tx_parameters_changed.emit)
|
||||
|
||||
self.ui.radioButtonTranscriptBit.clicked.connect(self.on_radio_button_transcript_bit_clicked)
|
||||
self.ui.radioButtonTranscriptHex.clicked.connect(self.on_radio_button_transcript_hex_clicked)
|
||||
|
||||
self.simulator_scene.selectionChanged.connect(self.update_buttons)
|
||||
self.simulator_config.items_updated.connect(self.update_buttons)
|
||||
|
||||
self.ui.btnLogAll.clicked.connect(self.on_btn_log_all_clicked)
|
||||
self.ui.btnLogNone.clicked.connect(self.on_btn_log_none_clicked)
|
||||
self.ui.btnToggleLog.clicked.connect(self.on_btn_toggle_clicked)
|
||||
|
||||
self.ui.btnStartStop.clicked.connect(self.on_btn_start_stop_clicked)
|
||||
self.ui.btnSaveLog.clicked.connect(self.on_btn_save_log_clicked)
|
||||
self.ui.btnSaveTranscript.clicked.connect(self.on_btn_save_transcript_clicked)
|
||||
self.timer.timeout.connect(self.on_timer_timeout)
|
||||
self.simulator.simulation_started.connect(self.on_simulation_started)
|
||||
self.simulator.simulation_stopped.connect(self.on_simulation_stopped)
|
||||
|
||||
def update_buttons(self):
|
||||
selectable_items = self.simulator_scene.selectable_items()
|
||||
all_items_selected = all(item.model_item.logging_active for item in selectable_items)
|
||||
any_item_selected = any(item.model_item.logging_active for item in selectable_items)
|
||||
self.ui.btnToggleLog.setEnabled(len(self.simulator_scene.selectedItems()))
|
||||
self.ui.btnLogAll.setEnabled(not all_items_selected)
|
||||
self.ui.btnLogNone.setEnabled(any_item_selected)
|
||||
|
||||
def __get_full_transcript(self) -> list:
|
||||
return self.simulator.transcript.get_for_all_participants(all_rounds=True,
|
||||
use_bit=self.ui.radioButtonTranscriptBit.isChecked())
|
||||
|
||||
def update_view(self):
|
||||
for device_message in filter(None, map(str.rstrip, self.simulator.device_messages())):
|
||||
self.ui.textEditDevices.append(device_message)
|
||||
|
||||
for log_msg in filter(None, map(str.rstrip, self.simulator.read_log_messages())):
|
||||
self.ui.textEditSimulation.append(log_msg)
|
||||
|
||||
transcript = self.__get_full_transcript()
|
||||
for line in transcript[self.current_transcript_index:]:
|
||||
self.ui.textEditTranscript.append(line)
|
||||
|
||||
self.current_transcript_index = len(transcript)
|
||||
current_repeat = str(self.simulator.current_repeat + 1) if self.simulator.is_simulating else "-"
|
||||
self.ui.lblCurrentRepeatValue.setText(current_repeat)
|
||||
|
||||
current_item = self.simulator.current_item.index() if self.simulator.is_simulating else "-"
|
||||
self.ui.lblCurrentItemValue.setText(current_item)
|
||||
|
||||
def update_rx_graphics_view(self):
|
||||
if self.scene_manager is None or not self.ui.graphicsViewPreview.isEnabled():
|
||||
return
|
||||
|
||||
self.scene_manager.end = self.simulator.sniffer.rcv_device.current_index
|
||||
self.scene_manager.init_scene()
|
||||
self.scene_manager.show_full_scene()
|
||||
self.ui.graphicsViewPreview.update()
|
||||
|
||||
def reset(self):
|
||||
self.ui.textEditDevices.clear()
|
||||
self.ui.textEditSimulation.clear()
|
||||
self.ui.textEditTranscript.clear()
|
||||
self.current_transcript_index = 0
|
||||
self.ui.lblCurrentRepeatValue.setText("-")
|
||||
self.ui.lblCurrentItemValue.setText("-")
|
||||
|
||||
def emit_editing_finished_signals(self):
|
||||
if self.device_settings_rx_widget:
|
||||
self.device_settings_rx_widget.emit_editing_finished_signals()
|
||||
|
||||
if self.device_settings_tx_widget:
|
||||
self.device_settings_tx_widget.emit_editing_finished_signals()
|
||||
|
||||
if self.sniff_settings_widget:
|
||||
self.sniff_settings_widget.emit_editing_finished_signals()
|
||||
|
||||
def update_transcript_view(self):
|
||||
self.ui.textEditTranscript.setText("\n".join(self.__get_full_transcript()))
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
self.timer.stop()
|
||||
self.simulator.stop()
|
||||
|
||||
self.simulator.cleanup()
|
||||
|
||||
self.emit_editing_finished_signals()
|
||||
if self.device_settings_rx_widget:
|
||||
self.device_settings_rx_widget.emit_device_parameters_changed()
|
||||
if self.device_settings_tx_widget:
|
||||
self.device_settings_tx_widget.emit_device_parameters_changed()
|
||||
if self.sniff_settings_widget:
|
||||
self.sniff_settings_widget.emit_sniff_parameters_changed()
|
||||
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_simulation_started(self):
|
||||
for i in range(3):
|
||||
self.ui.tabWidgetSimulatorSettings.setTabEnabled(i, False)
|
||||
self.ui.checkBoxCaptureFullRX.setDisabled(True)
|
||||
self.reset()
|
||||
self.timer.start(self.update_interval)
|
||||
self.ui.btnStartStop.setIcon(QIcon.fromTheme("media-playback-stop"))
|
||||
self.ui.btnStartStop.setText("Stop")
|
||||
|
||||
if not self.rx_needed:
|
||||
return
|
||||
|
||||
rx_device = self.simulator.sniffer.rcv_device
|
||||
for item in self.scene_manager.scene.items():
|
||||
if isinstance(item, QGraphicsTextItem):
|
||||
self.scene_manager.scene.removeItem(item)
|
||||
|
||||
if hasattr(rx_device.data, "real"):
|
||||
self.ui.graphicsViewPreview.setEnabled(True)
|
||||
if self.ui.checkBoxCaptureFullRX.isChecked():
|
||||
self.scene_manager.plot_data = rx_device.data.real
|
||||
else:
|
||||
self.scene_manager.data_array = rx_device.data.real
|
||||
else:
|
||||
self.ui.graphicsViewPreview.setEnabled(False)
|
||||
if self.ui.checkBoxCaptureFullRX.isChecked():
|
||||
self.scene_manager.plot_data = np.array([], dtype=rx_device.data_type)
|
||||
else:
|
||||
self.scene_manager.data_array = np.array([], dtype=rx_device.data_type)
|
||||
self.scene_manager.scene.addText("Could not generate RX preview.")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_simulation_stopped(self):
|
||||
self.ui.tabWidgetSimulatorSettings.setTabEnabled(0, True)
|
||||
self.ui.tabWidgetSimulatorSettings.setTabEnabled(1, self.rx_needed)
|
||||
self.ui.tabWidgetSimulatorSettings.setTabEnabled(2, self.tx_needed)
|
||||
|
||||
self.timer.stop()
|
||||
self.update_view()
|
||||
self.ui.btnStartStop.setIcon(QIcon.fromTheme("media-playback-start"))
|
||||
self.ui.btnStartStop.setText("Start")
|
||||
self.ui.checkBoxCaptureFullRX.setEnabled(True)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_log_all_clicked(self):
|
||||
self.simulator_scene.log_all_items(True)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_log_none_clicked(self):
|
||||
self.simulator_scene.log_all_items(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_toggle_clicked(self):
|
||||
self.simulator_scene.log_toggle_selected_items()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_save_log_clicked(self):
|
||||
file_path = QFileDialog.getSaveFileName(self, "Save log", "", "Log file (*.log)")
|
||||
|
||||
if file_path[0] == "":
|
||||
return
|
||||
|
||||
log_string = self.ui.textEditSimulation.toPlainText()
|
||||
|
||||
try:
|
||||
with open(str(file_path[0]), "w") as f:
|
||||
f.write(log_string)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error saving log", e.args[0])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_save_transcript_clicked(self):
|
||||
file_path = QFileDialog.getSaveFileName(self, "Save transcript", "", "Text file (*.txt)")
|
||||
|
||||
if file_path[0] == "":
|
||||
return
|
||||
|
||||
transcript = self.ui.textEditTranscript.toPlainText()
|
||||
|
||||
try:
|
||||
with open(str(file_path[0]), "w") as f:
|
||||
f.write(transcript)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error saving transcript", e.args[0])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_start_stop_clicked(self):
|
||||
if self.simulator.is_simulating:
|
||||
self.simulator.stop()
|
||||
else:
|
||||
if self.rx_needed:
|
||||
self.device_settings_rx_widget.emit_editing_finished_signals()
|
||||
self.sniff_settings_widget.emit_editing_finished_signals()
|
||||
|
||||
self.simulator.sniffer.rcv_device.current_index = 0
|
||||
self.simulator.sniffer.rcv_device.resume_on_full_receive_buffer = not self.ui.checkBoxCaptureFullRX.isChecked()
|
||||
|
||||
if self.tx_needed:
|
||||
self.device_settings_tx_widget.emit_editing_finished_signals()
|
||||
|
||||
self.simulator.start()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_timer_timeout(self):
|
||||
self.update_view()
|
||||
self.update_rx_graphics_view()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_selected_rx_device_changed(self):
|
||||
dev_name = self.device_settings_rx_widget.ui.cbDevice.currentText()
|
||||
self.simulator.sniffer.device_name = dev_name
|
||||
self.device_settings_rx_widget.device = self.simulator.sniffer.rcv_device
|
||||
self.__set_rx_scene()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_selected_tx_device_changed(self):
|
||||
old_name = self.simulator.sender.device_name
|
||||
try:
|
||||
dev_name = self.device_settings_tx_widget.ui.cbDevice.currentText()
|
||||
self.simulator.sender.device_name = dev_name
|
||||
self.device_settings_tx_widget.device = self.simulator.sender.device
|
||||
except Exception as e:
|
||||
self.device_settings_tx_widget.ui.cbDevice.setCurrentText(old_name)
|
||||
Errors.exception(e)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_test_sniff_settings_clicked(self):
|
||||
def on_dialog_finished():
|
||||
self.device_settings_rx_widget.bootstrap(self.project_manager.simulator_rx_conf)
|
||||
self.sniff_settings_widget.bootstrap(self.project_manager.device_conf)
|
||||
|
||||
self.device_settings_rx_widget.emit_device_parameters_changed()
|
||||
self.sniff_settings_widget.emit_sniff_parameters_changed()
|
||||
|
||||
psd = ProtocolSniffDialog(self.project_manager, signals=self.sniff_settings_widget.signals, parent=self)
|
||||
psd.device_settings_widget.bootstrap(self.project_manager.simulator_rx_conf)
|
||||
psd.device_settings_widget.device_parameters_changed.connect(self.rx_parameters_changed.emit)
|
||||
psd.sniff_settings_widget.sniff_parameters_changed.connect(self.sniff_parameters_changed.emit)
|
||||
psd.finished.connect(on_dialog_finished)
|
||||
psd.ui.btnAccept.hide()
|
||||
psd.show()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_radio_button_transcript_hex_clicked(self):
|
||||
self.update_transcript_view()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_radio_button_transcript_bit_clicked(self):
|
||||
self.update_transcript_view()
|
||||
|
||||
def __set_rx_scene(self):
|
||||
if not self.rx_needed:
|
||||
return
|
||||
|
||||
if self.ui.checkBoxCaptureFullRX.isChecked():
|
||||
self.scene_manager = LiveSceneManager(np.array([], dtype=self.simulator.sniffer.rcv_device.data_type),
|
||||
parent=self)
|
||||
self.ui.graphicsViewPreview.setScene(self.scene_manager.scene)
|
||||
else:
|
||||
self.scene_manager = SniffSceneManager(np.array([], dtype=self.simulator.sniffer.rcv_device.data_type),
|
||||
parent=self)
|
||||
|
||||
self.ui.graphicsViewPreview.setScene(self.scene_manager.scene)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_checkbox_capture_full_rx_clicked(self):
|
||||
self.simulator.sniffer.rcv_device.resume_on_full_receive_buffer = not self.ui.checkBoxCaptureFullRX.isChecked()
|
||||
self.__set_rx_scene()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_save_rx_clicked(self):
|
||||
rx_device = self.simulator.sniffer.rcv_device
|
||||
if isinstance(rx_device.data, np.ndarray) or isinstance(rx_device.data, IQArray):
|
||||
data = IQArray(rx_device.data[:rx_device.current_index])
|
||||
filename = FileOperator.ask_signal_file_name_and_save("simulation_capture", data,
|
||||
sample_rate=rx_device.sample_rate, parent=self)
|
||||
if filename:
|
||||
data.tofile(filename)
|
||||
self.rx_file_saved.emit(filename)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_open_in_analysis_clicked(self):
|
||||
text = self.ui.textEditTranscript.toPlainText()
|
||||
if len(text) > 0:
|
||||
self.open_in_analysis_requested.emit(text)
|
||||
self.close()
|
||||
@@ -0,0 +1,172 @@
|
||||
from PyQt5.QtCore import QTimer, pyqtSlot
|
||||
from PyQt5.QtGui import QWheelEvent, QIcon, QPixmap, QResizeEvent
|
||||
from PyQt5.QtWidgets import QGraphicsScene
|
||||
|
||||
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
|
||||
from urh.dev.VirtualDevice import VirtualDevice, Mode
|
||||
from urh.signalprocessing.Spectrogram import Spectrogram
|
||||
from urh.ui.painting.FFTSceneManager import FFTSceneManager
|
||||
|
||||
|
||||
class SpectrumDialogController(SendRecvDialog):
|
||||
def __init__(self, project_manager, parent=None, testing_mode=False):
|
||||
super().__init__(project_manager, is_tx=False, parent=parent, testing_mode=testing_mode)
|
||||
|
||||
self.graphics_view = self.ui.graphicsViewFFT
|
||||
self.update_interval = 1
|
||||
self.ui.stackedWidget.setCurrentWidget(self.ui.page_spectrum)
|
||||
self.hide_receive_ui_items()
|
||||
self.hide_send_ui_items()
|
||||
|
||||
self.setWindowTitle("Spectrum Analyzer")
|
||||
self.setWindowIcon(QIcon(":/icons/icons/spectrum.svg"))
|
||||
self.ui.btnStart.setToolTip(self.tr("Start"))
|
||||
self.ui.btnStop.setToolTip(self.tr("Stop"))
|
||||
|
||||
self.scene_manager = FFTSceneManager(parent=self, graphic_view=self.graphics_view)
|
||||
|
||||
self.graphics_view.setScene(self.scene_manager.scene)
|
||||
self.graphics_view.scene_manager = self.scene_manager
|
||||
|
||||
self.ui.graphicsViewSpectrogram.setScene(QGraphicsScene())
|
||||
self.__clear_spectrogram()
|
||||
|
||||
self.gain_timer = QTimer(self)
|
||||
self.gain_timer.setSingleShot(True)
|
||||
|
||||
self.if_gain_timer = QTimer(self)
|
||||
self.if_gain_timer.setSingleShot(True)
|
||||
|
||||
self.bb_gain_timer = QTimer(self)
|
||||
self.bb_gain_timer.setSingleShot(True)
|
||||
|
||||
self.create_connects()
|
||||
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
|
||||
|
||||
def __clear_spectrogram(self):
|
||||
self.ui.graphicsViewSpectrogram.scene().clear()
|
||||
window_size = Spectrogram.DEFAULT_FFT_WINDOW_SIZE
|
||||
self.ui.graphicsViewSpectrogram.scene().setSceneRect(0, 0, window_size, 20 * window_size)
|
||||
self.spectrogram_y_pos = 0
|
||||
self.ui.graphicsViewSpectrogram.fitInView(self.ui.graphicsViewSpectrogram.sceneRect())
|
||||
|
||||
def __update_spectrogram(self):
|
||||
spectrogram = Spectrogram(self.device.data)
|
||||
spectrogram.data_min = -80
|
||||
spectrogram.data_max = 10
|
||||
scene = self.ui.graphicsViewSpectrogram.scene()
|
||||
pixmap = QPixmap.fromImage(spectrogram.create_spectrogram_image(transpose=True))
|
||||
pixmap_item = scene.addPixmap(pixmap)
|
||||
pixmap_item.moveBy(0, self.spectrogram_y_pos)
|
||||
self.spectrogram_y_pos += pixmap.height()
|
||||
if self.spectrogram_y_pos >= scene.sceneRect().height():
|
||||
scene.setSceneRect(0, 0, Spectrogram.DEFAULT_FFT_WINDOW_SIZE, self.spectrogram_y_pos)
|
||||
self.ui.graphicsViewSpectrogram.ensureVisible(pixmap_item)
|
||||
|
||||
def _eliminate_graphic_view(self):
|
||||
super()._eliminate_graphic_view()
|
||||
if self.ui.graphicsViewSpectrogram and self.ui.graphicsViewSpectrogram.scene() is not None:
|
||||
self.ui.graphicsViewSpectrogram.scene().clear()
|
||||
self.ui.graphicsViewSpectrogram.scene().setParent(None)
|
||||
self.ui.graphicsViewSpectrogram.setScene(None)
|
||||
|
||||
self.ui.graphicsViewSpectrogram = None
|
||||
|
||||
def create_connects(self):
|
||||
super().create_connects()
|
||||
self.graphics_view.freq_clicked.connect(self.on_graphics_view_freq_clicked)
|
||||
self.graphics_view.wheel_event_triggered.connect(self.on_graphics_view_wheel_event_triggered)
|
||||
|
||||
self.device_settings_widget.ui.sliderGain.valueChanged.connect(self.on_slider_gain_value_changed)
|
||||
self.device_settings_widget.ui.sliderBasebandGain.valueChanged.connect(
|
||||
self.on_slider_baseband_gain_value_changed)
|
||||
self.device_settings_widget.ui.sliderIFGain.valueChanged.connect(self.on_slider_if_gain_value_changed)
|
||||
self.device_settings_widget.ui.spinBoxFreq.editingFinished.connect(self.on_spinbox_frequency_editing_finished)
|
||||
|
||||
self.gain_timer.timeout.connect(self.device_settings_widget.ui.spinBoxGain.editingFinished.emit)
|
||||
self.if_gain_timer.timeout.connect(self.device_settings_widget.ui.spinBoxIFGain.editingFinished.emit)
|
||||
self.bb_gain_timer.timeout.connect(self.device_settings_widget.ui.spinBoxBasebandGain.editingFinished.emit)
|
||||
|
||||
def resizeEvent(self, event: QResizeEvent):
|
||||
if self.ui.graphicsViewSpectrogram and self.ui.graphicsViewSpectrogram.sceneRect():
|
||||
self.ui.graphicsViewSpectrogram.fitInView(self.ui.graphicsViewSpectrogram.sceneRect())
|
||||
|
||||
def update_view(self):
|
||||
if super().update_view():
|
||||
x, y = self.device.spectrum
|
||||
if x is None or y is None:
|
||||
return
|
||||
self.scene_manager.scene.frequencies = x
|
||||
self.scene_manager.plot_data = y
|
||||
self.scene_manager.init_scene()
|
||||
self.scene_manager.show_full_scene()
|
||||
self.graphics_view.fitInView(self.graphics_view.sceneRect())
|
||||
|
||||
try:
|
||||
self.__update_spectrogram()
|
||||
except MemoryError:
|
||||
self.__clear_spectrogram()
|
||||
self.__update_spectrogram()
|
||||
|
||||
def init_device(self):
|
||||
self.device = VirtualDevice(self.backend_handler, self.selected_device_name,
|
||||
Mode.spectrum,
|
||||
device_ip="192.168.10.2", parent=self)
|
||||
self._create_device_connects()
|
||||
|
||||
@pyqtSlot(QWheelEvent)
|
||||
def on_graphics_view_wheel_event_triggered(self, event: QWheelEvent):
|
||||
self.ui.sliderYscale.wheelEvent(event)
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_graphics_view_freq_clicked(self, freq: float):
|
||||
self.device_settings_widget.ui.spinBoxFreq.setValue(freq)
|
||||
self.device_settings_widget.ui.spinBoxFreq.editingFinished.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_frequency_editing_finished(self):
|
||||
frequency = self.device_settings_widget.ui.spinBoxFreq.value()
|
||||
self.device.frequency = frequency
|
||||
self.scene_manager.scene.center_freq = frequency
|
||||
self.scene_manager.clear_path()
|
||||
self.scene_manager.clear_peak()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_start_clicked(self):
|
||||
super().on_start_clicked()
|
||||
self.device.start()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_started(self):
|
||||
self.ui.graphicsViewSpectrogram.fitInView(self.ui.graphicsViewSpectrogram.scene().sceneRect())
|
||||
super().on_device_started()
|
||||
self.device_settings_widget.ui.spinBoxPort.setEnabled(False)
|
||||
self.device_settings_widget.ui.lineEditIP.setEnabled(False)
|
||||
self.device_settings_widget.ui.cbDevice.setEnabled(False)
|
||||
self.ui.btnStart.setEnabled(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_stopped(self):
|
||||
self.device_settings_widget.ui.spinBoxPort.setEnabled(True)
|
||||
self.device_settings_widget.ui.lineEditIP.setEnabled(True)
|
||||
self.device_settings_widget.ui.cbDevice.setEnabled(True)
|
||||
|
||||
super().on_device_stopped()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_clear_clicked(self):
|
||||
self.__clear_spectrogram()
|
||||
self.scene_manager.clear_path()
|
||||
self.scene_manager.clear_peak()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_slider_gain_value_changed(self, value: int):
|
||||
self.gain_timer.start(250)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_slider_if_gain_value_changed(self, value: int):
|
||||
self.if_gain_timer.start(250)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_slider_baseband_gain_value_changed(self, value: int):
|
||||
self.bb_gain_timer.start(250)
|
||||
@@ -0,0 +1,321 @@
|
||||
import array
|
||||
import copy
|
||||
from collections import OrderedDict
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QAbstractTableModel, QModelIndex, Qt, QRegExp
|
||||
from PyQt5.QtGui import QRegExpValidator
|
||||
from PyQt5.QtWidgets import QWidget, QHeaderView, QAbstractItemView, QLineEdit
|
||||
|
||||
from urh.signalprocessing.ChecksumLabel import ChecksumLabel
|
||||
from urh.signalprocessing.Message import Message
|
||||
from urh.ui.delegates.SpinBoxDelegate import SpinBoxDelegate
|
||||
from urh.ui.ui_checksum_options_widget import Ui_ChecksumOptions
|
||||
from urh.util import util
|
||||
from urh.util.GenericCRC import GenericCRC
|
||||
from urh.util.Logger import logger
|
||||
from urh.util.WSPChecksum import WSPChecksum
|
||||
|
||||
|
||||
class ChecksumWidget(QWidget):
|
||||
SPECIAL_CRCS = OrderedDict([
|
||||
("CC1101", GenericCRC(polynomial="16_standard", start_value=True)),
|
||||
])
|
||||
|
||||
|
||||
class RangeTableModel(QAbstractTableModel):
|
||||
header_labels = ["Start", "End"]
|
||||
|
||||
def __init__(self, checksum_label: ChecksumLabel, message: Message, proto_view: int, parent=None):
|
||||
"""
|
||||
|
||||
:param message:
|
||||
:type field_types: list of FieldType
|
||||
:param parent:
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.checksum_label = checksum_label
|
||||
self.message = message
|
||||
self.proto_view = proto_view
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
self.beginResetModel()
|
||||
self.endResetModel()
|
||||
|
||||
def columnCount(self, parent: QModelIndex = None, *args, **kwargs):
|
||||
return len(self.header_labels)
|
||||
|
||||
def rowCount(self, parent: QModelIndex = None, *args, **kwargs):
|
||||
return len(self.checksum_label.data_ranges)
|
||||
|
||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
||||
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
|
||||
return self.header_labels[section]
|
||||
return super().headerData(section, orientation, role)
|
||||
|
||||
def data(self, index: QModelIndex, role=Qt.DisplayRole):
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
i, j = index.row(), index.column()
|
||||
|
||||
if role == Qt.DisplayRole:
|
||||
data_range = self.checksum_label.data_ranges[i]
|
||||
if j == 0:
|
||||
return self.message.convert_index(data_range[0], 0, self.proto_view, True)[0] + 1
|
||||
elif j == 1:
|
||||
return self.message.convert_index(data_range[1], 0, self.proto_view, True)[0]
|
||||
return None
|
||||
|
||||
def setData(self, index: QModelIndex, value, role: int = ...):
|
||||
try:
|
||||
int_val = int(value)
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
i, j = index.row(), index.column()
|
||||
|
||||
if i > len(self.checksum_label.data_ranges):
|
||||
return False
|
||||
|
||||
data_range = self.checksum_label.data_ranges[i]
|
||||
|
||||
if j == 0:
|
||||
converted_index = self.message.convert_index(int_val - 1, self.proto_view, 0, True)[0]
|
||||
if converted_index < data_range[1]:
|
||||
data_range[0] = converted_index
|
||||
elif j == 1:
|
||||
converted_index = self.message.convert_index(int_val, self.proto_view, 0, True)[0]
|
||||
if converted_index > data_range[0]:
|
||||
data_range[1] = converted_index
|
||||
|
||||
return True
|
||||
|
||||
def flags(self, index):
|
||||
if not index.isValid():
|
||||
return Qt.NoItemFlags
|
||||
|
||||
try:
|
||||
_ = self.checksum_label.data_ranges[index.row()]
|
||||
except IndexError:
|
||||
return Qt.NoItemFlags
|
||||
|
||||
return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
|
||||
|
||||
def __init__(self, checksum_label: ChecksumLabel, message: Message, proto_view: int, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_ChecksumOptions()
|
||||
self.ui.setupUi(self)
|
||||
self.checksum_label = checksum_label
|
||||
self.data_range_table_model = self.RangeTableModel(checksum_label, message, proto_view, parent=self)
|
||||
self.ui.tableViewDataRanges.setItemDelegateForColumn(0, SpinBoxDelegate(1, 999999, self))
|
||||
self.ui.tableViewDataRanges.setItemDelegateForColumn(1, SpinBoxDelegate(1, 999999, self))
|
||||
self.ui.tableViewDataRanges.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
self.ui.tableViewDataRanges.setModel(self.data_range_table_model)
|
||||
self.ui.tableViewDataRanges.setEditTriggers(QAbstractItemView.AllEditTriggers)
|
||||
self.display_crc_data_ranges_in_table()
|
||||
self.ui.comboBoxCRCFunction.addItems([crc_name for crc_name in GenericCRC.DEFAULT_POLYNOMIALS])
|
||||
self.ui.comboBoxCRCFunction.addItems([special_crc_name for special_crc_name in self.SPECIAL_CRCS])
|
||||
self.ui.lineEditCRCPolynomial.setValidator(QRegExpValidator(QRegExp("[0-9,a-f]*")))
|
||||
self.ui.comboBoxCategory.clear()
|
||||
for _, member in self.checksum_label.Category.__members__.items():
|
||||
self.ui.comboBoxCategory.addItem(member.value)
|
||||
self.set_ui_for_category()
|
||||
self.setFocus()
|
||||
self.create_connects()
|
||||
|
||||
@property
|
||||
def proto_view(self):
|
||||
return self.data_range_table_model.proto_view
|
||||
|
||||
@proto_view.setter
|
||||
def proto_view(self, value):
|
||||
if value != self.data_range_table_model.proto_view:
|
||||
self.data_range_table_model.proto_view = value
|
||||
self.data_range_table_model.update()
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.comboBoxCRCFunction.currentIndexChanged.connect(self.on_combobox_crc_function_current_index_changed)
|
||||
self.ui.btnAddRange.clicked.connect(self.on_btn_add_range_clicked)
|
||||
self.ui.btnRemoveRange.clicked.connect(self.on_btn_remove_range_clicked)
|
||||
self.ui.lineEditCRCPolynomial.editingFinished.connect(self.on_line_edit_crc_polynomial_editing_finished)
|
||||
self.ui.lineEditStartValue.editingFinished.connect(self.on_line_edit_start_value_editing_finished)
|
||||
self.ui.lineEditFinalXOR.editingFinished.connect(self.on_line_edit_final_xor_editing_finished)
|
||||
self.ui.comboBoxCategory.currentIndexChanged.connect(self.on_combobox_category_current_index_changed)
|
||||
self.ui.radioButtonWSPAuto.clicked.connect(self.on_radio_button_wsp_auto_clicked)
|
||||
self.ui.radioButtonWSPChecksum4.clicked.connect(self.on_radio_button_wsp_checksum4_clicked)
|
||||
self.ui.radioButtonWSPChecksum8.clicked.connect(self.on_radio_button_wsp_checksum8_clicked)
|
||||
self.ui.radioButtonWSPCRC8.clicked.connect(self.on_radio_button_wsp_crc8_clicked)
|
||||
self.ui.checkBoxRefIn.clicked.connect(self.on_check_box_ref_in_clicked)
|
||||
self.ui.checkBoxRefOut.clicked.connect(self.on_check_box_ref_out_clicked)
|
||||
|
||||
def set_checksum_ui_elements(self):
|
||||
if self.checksum_label.is_generic_crc:
|
||||
self.ui.lineEditCRCPolynomial.setText(self.checksum_label.checksum.polynomial_as_hex_str)
|
||||
self.ui.lineEditStartValue.setText(util.bit2hex(self.checksum_label.checksum.start_value))
|
||||
self.ui.lineEditFinalXOR.setText(util.bit2hex(self.checksum_label.checksum.final_xor))
|
||||
self.ui.checkBoxRefIn.setChecked(self.checksum_label.checksum.lsb_first)
|
||||
self.ui.checkBoxRefOut.setChecked(self.checksum_label.checksum.reverse_all)
|
||||
self.__set_crc_function_index()
|
||||
self.__ensure_same_length()
|
||||
self.__set_crc_info_label()
|
||||
elif self.checksum_label.category == self.checksum_label.Category.wsp:
|
||||
if self.checksum_label.checksum.mode == WSPChecksum.ChecksumMode.auto:
|
||||
self.ui.radioButtonWSPAuto.setChecked(True)
|
||||
elif self.checksum_label.checksum.mode == WSPChecksum.ChecksumMode.checksum4:
|
||||
self.ui.radioButtonWSPChecksum4.setChecked(True)
|
||||
elif self.checksum_label.checksum.mode == WSPChecksum.ChecksumMode.checksum8:
|
||||
self.ui.radioButtonWSPChecksum8.setChecked(True)
|
||||
elif self.checksum_label.checksum.mode == WSPChecksum.ChecksumMode.crc8:
|
||||
self.ui.radioButtonWSPCRC8.setChecked(True)
|
||||
|
||||
def set_ui_for_category(self):
|
||||
self.ui.comboBoxCategory.setCurrentText(self.checksum_label.category.value)
|
||||
if self.checksum_label.category == self.checksum_label.Category.generic:
|
||||
self.ui.stackedWidget.setCurrentWidget(self.ui.page_crc)
|
||||
elif self.checksum_label.category == self.checksum_label.Category.wsp:
|
||||
self.ui.stackedWidget.setCurrentWidget(self.ui.page_wsp)
|
||||
else:
|
||||
raise ValueError("Unknown category")
|
||||
|
||||
self.set_checksum_ui_elements()
|
||||
|
||||
def display_crc_data_ranges_in_table(self):
|
||||
self.data_range_table_model.update()
|
||||
|
||||
def __set_crc_function_index(self):
|
||||
# Get the combobox index
|
||||
crc_found = False
|
||||
for crc_name in GenericCRC.DEFAULT_POLYNOMIALS:
|
||||
test_crc = GenericCRC(crc_name)
|
||||
if test_crc == self.checksum_label.checksum:
|
||||
self.ui.comboBoxCRCFunction.setCurrentText(crc_name)
|
||||
crc_found = True
|
||||
break
|
||||
|
||||
if not crc_found:
|
||||
for crc_name, crc in self.SPECIAL_CRCS.items():
|
||||
if self.checksum_label.checksum == crc:
|
||||
self.ui.comboBoxCRCFunction.setCurrentText(crc_name)
|
||||
crc_found = True
|
||||
break
|
||||
|
||||
if not crc_found:
|
||||
self.__add_and_select_custom_item()
|
||||
elif "Custom" in [self.ui.comboBoxCRCFunction.itemText(i) for i in range(self.ui.comboBoxCRCFunction.count())]:
|
||||
self.ui.comboBoxCRCFunction.removeItem(self.ui.comboBoxCRCFunction.count() - 1)
|
||||
|
||||
|
||||
def __set_crc_info_label(self):
|
||||
crc = self.checksum_label.checksum # type: GenericCRC
|
||||
self.ui.label_crc_info.setText("<b>CRC Summary:</b><ul>"
|
||||
"<li>Polynomial = {}<>"
|
||||
"<li>Length of checksum = {} bit</li>"
|
||||
"<li>start value length = {} bit</li>"
|
||||
"<li>final XOR length = {} bit</li>"
|
||||
"</ul>".format(crc.polynomial_to_html, crc.poly_order-1,
|
||||
len(crc.start_value), len(crc.final_xor)))
|
||||
|
||||
def __ensure_same_length(self):
|
||||
for dependant_line_edit in [self.ui.lineEditStartValue, self.ui.lineEditFinalXOR]: # type: QLineEdit
|
||||
if len(self.ui.lineEditCRCPolynomial.text()) < len(dependant_line_edit.text()):
|
||||
dependant_line_edit.setText(dependant_line_edit.text()[:len(self.ui.lineEditCRCPolynomial.text())])
|
||||
dependant_line_edit.editingFinished.emit()
|
||||
elif len(self.ui.lineEditCRCPolynomial.text()) > len(dependant_line_edit.text()):
|
||||
# pad zeros at front
|
||||
dependant_line_edit.setText("0" * (len(self.ui.lineEditCRCPolynomial.text()) - len(dependant_line_edit.text()))
|
||||
+ dependant_line_edit.text())
|
||||
dependant_line_edit.editingFinished.emit()
|
||||
|
||||
def __add_and_select_custom_item(self):
|
||||
if "Custom" not in [self.ui.comboBoxCRCFunction.itemText(i) for i in range(self.ui.comboBoxCRCFunction.count())]:
|
||||
self.ui.comboBoxCRCFunction.addItem("Custom")
|
||||
self.ui.comboBoxCRCFunction.blockSignals(True)
|
||||
self.ui.comboBoxCRCFunction.setCurrentText("Custom")
|
||||
self.ui.comboBoxCRCFunction.blockSignals(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_add_range_clicked(self):
|
||||
self.checksum_label.data_ranges.append([0, self.checksum_label.start])
|
||||
self.data_range_table_model.update()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_remove_range_clicked(self):
|
||||
if len(self.checksum_label.data_ranges) > 1:
|
||||
self.checksum_label.data_ranges.pop(-1)
|
||||
self.data_range_table_model.update()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_crc_function_current_index_changed(self, index: int):
|
||||
poly_str = self.ui.comboBoxCRCFunction.itemText(index)
|
||||
if poly_str in GenericCRC.DEFAULT_POLYNOMIALS:
|
||||
self.checksum_label.checksum.polynomial = self.checksum_label.checksum.choose_polynomial(poly_str)
|
||||
self.checksum_label.checksum.start_value = array.array("B", [0] * (self.checksum_label.checksum.poly_order - 1))
|
||||
self.checksum_label.checksum.final_xor = array.array("B", [0] * (self.checksum_label.checksum.poly_order - 1))
|
||||
elif poly_str in self.SPECIAL_CRCS:
|
||||
self.checksum_label.checksum = copy.deepcopy(self.SPECIAL_CRCS[poly_str])
|
||||
else:
|
||||
logger.error("Unknown CRC")
|
||||
return
|
||||
|
||||
self.ui.lineEditCRCPolynomial.setText(self.checksum_label.checksum.polynomial_as_hex_str)
|
||||
self.ui.lineEditStartValue.setText(util.bit2hex(self.checksum_label.checksum.start_value))
|
||||
self.ui.lineEditFinalXOR.setText(util.bit2hex(self.checksum_label.checksum.final_xor))
|
||||
self.ui.lineEditCRCPolynomial.editingFinished.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_crc_polynomial_editing_finished(self):
|
||||
self.checksum_label.checksum.set_polynomial_from_hex(self.ui.lineEditCRCPolynomial.text())
|
||||
self.__ensure_same_length()
|
||||
self.__set_crc_info_label()
|
||||
self.__set_crc_function_index()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_check_box_ref_in_clicked(self):
|
||||
self.checksum_label.checksum.lsb_first = self.ui.checkBoxRefIn.isChecked()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_check_box_ref_out_clicked(self):
|
||||
self.checksum_label.checksum.reverse_all = self.ui.checkBoxRefOut.isChecked()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_start_value_editing_finished(self):
|
||||
crc = self.checksum_label.checksum
|
||||
start_value = util.hex2bit(self.ui.lineEditStartValue.text())
|
||||
# pad with zeros at front
|
||||
start_value = array.array("B", [0]*(crc.poly_order - 1 - len(start_value))) + start_value
|
||||
crc.start_value = start_value[0:crc.poly_order-1]
|
||||
self.ui.lineEditStartValue.setText(util.bit2hex(crc.start_value))
|
||||
self.__set_crc_info_label()
|
||||
self.__set_crc_function_index()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_final_xor_editing_finished(self):
|
||||
crc = self.checksum_label.checksum
|
||||
final_xor = util.hex2bit(self.ui.lineEditFinalXOR.text())
|
||||
final_xor = array.array("B", [0] * (crc.poly_order - 1 - len(final_xor))) + final_xor
|
||||
crc.final_xor = final_xor[0:crc.poly_order-1]
|
||||
self.ui.lineEditFinalXOR.setText(util.bit2hex(crc.final_xor))
|
||||
self.__set_crc_info_label()
|
||||
self.__set_crc_function_index()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_category_current_index_changed(self, index: int):
|
||||
self.checksum_label.category = self.checksum_label.Category(self.ui.comboBoxCategory.currentText())
|
||||
self.set_ui_for_category()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_radio_button_wsp_auto_clicked(self):
|
||||
self.checksum_label.checksum.mode = WSPChecksum.ChecksumMode.auto
|
||||
|
||||
@pyqtSlot()
|
||||
def on_radio_button_wsp_checksum4_clicked(self):
|
||||
self.checksum_label.checksum.mode = WSPChecksum.ChecksumMode.checksum4
|
||||
|
||||
@pyqtSlot()
|
||||
def on_radio_button_wsp_checksum8_clicked(self):
|
||||
self.checksum_label.checksum.mode = WSPChecksum.ChecksumMode.checksum8
|
||||
|
||||
@pyqtSlot()
|
||||
def on_radio_button_wsp_crc8_clicked(self):
|
||||
self.checksum_label.checksum.mode = WSPChecksum.ChecksumMode.crc8
|
||||
@@ -0,0 +1,553 @@
|
||||
from statistics import median
|
||||
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import QRegExp, pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtGui import QRegExpValidator, QIcon
|
||||
from PyQt5.QtWidgets import QWidget, QSpinBox, QLabel, QComboBox, QSlider
|
||||
|
||||
from urh import settings
|
||||
from urh.dev import config
|
||||
from urh.dev.BackendHandler import BackendHandler, Backends
|
||||
from urh.dev.VirtualDevice import VirtualDevice
|
||||
from urh.plugins.NetworkSDRInterface.NetworkSDRInterfacePlugin import NetworkSDRInterfacePlugin
|
||||
from urh.plugins.PluginManager import PluginManager
|
||||
from urh.ui.ui_send_recv_device_settings import Ui_FormDeviceSettings
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class DeviceSettingsWidget(QWidget):
|
||||
selected_device_changed = pyqtSignal()
|
||||
gain_edited = pyqtSignal()
|
||||
device_parameters_changed = pyqtSignal(dict)
|
||||
|
||||
def __init__(self, project_manager: ProjectManager, is_tx: bool, backend_handler: BackendHandler = None,
|
||||
continuous_send_mode=False, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_FormDeviceSettings()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.__device = None # type: VirtualDevice
|
||||
|
||||
self.is_tx = is_tx
|
||||
self.is_rx = not is_tx
|
||||
if backend_handler is None:
|
||||
self.backend_handler = BackendHandler()
|
||||
else:
|
||||
self.backend_handler = backend_handler
|
||||
|
||||
if self.is_rx:
|
||||
self.ui.spinBoxNRepeat.hide()
|
||||
self.ui.labelNRepeat.hide()
|
||||
else:
|
||||
self.ui.labelDCCorrection.hide()
|
||||
self.ui.checkBoxDCCorrection.hide()
|
||||
|
||||
self.bw_sr_are_locked = settings.read("lock_bandwidth_sample_rate", True, bool)
|
||||
self.ui.cbDevice.clear()
|
||||
items = self.get_devices_for_combobox(continuous_send_mode)
|
||||
self.ui.cbDevice.addItems(items)
|
||||
self.bootstrap(project_manager.device_conf, enforce_default=True)
|
||||
|
||||
self.ui.btnLockBWSR.setChecked(self.bw_sr_are_locked)
|
||||
self.on_btn_lock_bw_sr_clicked()
|
||||
|
||||
ip_range = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])"
|
||||
ip_regex = QRegExp("^" + ip_range
|
||||
+ "\\." + ip_range
|
||||
+ "\\." + ip_range
|
||||
+ "\\." + ip_range + "$")
|
||||
self.ui.lineEditIP.setValidator(QRegExpValidator(ip_regex))
|
||||
|
||||
self.create_connects()
|
||||
self.sync_gain_sliders()
|
||||
|
||||
def bootstrap(self, conf_dict: dict, enforce_default=False):
|
||||
def set_val(ui_widget, key: str, default):
|
||||
try:
|
||||
value = conf_dict[key]
|
||||
except KeyError:
|
||||
value = default if enforce_default else None
|
||||
|
||||
if value is not None:
|
||||
ui_widget.setValue(value)
|
||||
|
||||
self.set_bandwidth_status()
|
||||
|
||||
self.ui.cbDevice.setCurrentText(conf_dict.get("name", ""))
|
||||
dev_name = self.ui.cbDevice.currentText()
|
||||
self.set_device_ui_items_visibility(dev_name, overwrite_settings=True)
|
||||
|
||||
set_val(self.ui.spinBoxFreq, "frequency", config.DEFAULT_FREQUENCY)
|
||||
set_val(self.ui.spinBoxSampleRate, "sample_rate", config.DEFAULT_SAMPLE_RATE)
|
||||
set_val(self.ui.spinBoxBandwidth, "bandwidth", config.DEFAULT_BANDWIDTH)
|
||||
set_val(self.ui.spinBoxGain, self.rx_tx_prefix + "gain", config.DEFAULT_GAIN)
|
||||
set_val(self.ui.spinBoxIFGain, self.rx_tx_prefix + "if_gain", config.DEFAULT_IF_GAIN)
|
||||
set_val(self.ui.spinBoxBasebandGain, self.rx_tx_prefix + "baseband_gain", config.DEFAULT_BB_GAIN)
|
||||
set_val(self.ui.spinBoxFreqCorrection, "freq_correction", config.DEFAULT_FREQ_CORRECTION)
|
||||
set_val(self.ui.spinBoxNRepeat, "num_sending_repeats", settings.read('num_sending_repeats', 1, type=int))
|
||||
|
||||
self.ui.lineEditSubdevice.setText(conf_dict.get("subdevice", ""))
|
||||
|
||||
if self.rx_tx_prefix + "antenna_index" in conf_dict:
|
||||
self.ui.comboBoxAntenna.setCurrentIndex(conf_dict[self.rx_tx_prefix + "antenna_index"])
|
||||
|
||||
if self.rx_tx_prefix + "gain" not in conf_dict:
|
||||
self.set_default_rf_gain()
|
||||
|
||||
if self.rx_tx_prefix + "if_gain" not in conf_dict:
|
||||
self.set_default_if_gain()
|
||||
|
||||
if self.rx_tx_prefix + "baseband_gain" not in conf_dict:
|
||||
self.set_default_bb_gain()
|
||||
|
||||
if self.is_rx:
|
||||
checked = conf_dict.get("apply_dc_correction", True)
|
||||
if isinstance(checked, str):
|
||||
checked = True if checked == "True" else False
|
||||
self.ui.checkBoxDCCorrection.setChecked(checked)
|
||||
|
||||
checked = conf_dict.get("bias_tee_enabled", False)
|
||||
if isinstance(checked, str):
|
||||
checked = True if checked == "True" else False
|
||||
self.ui.checkBoxBiasTee.setChecked(checked)
|
||||
|
||||
self.emit_editing_finished_signals()
|
||||
|
||||
@property
|
||||
def device(self) -> VirtualDevice:
|
||||
return self.__device
|
||||
|
||||
@device.setter
|
||||
def device(self, value: VirtualDevice):
|
||||
self.__device = value
|
||||
|
||||
@property
|
||||
def rx_tx_prefix(self) -> str:
|
||||
return "rx_" if self.is_rx else "tx_"
|
||||
|
||||
@property
|
||||
def selected_device_conf(self) -> dict:
|
||||
device_name = self.ui.cbDevice.currentText()
|
||||
key = device_name if device_name in config.DEVICE_CONFIG.keys() else "Fallback"
|
||||
return config.DEVICE_CONFIG[key]
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.spinBoxFreq.editingFinished.connect(self.on_spinbox_frequency_editing_finished)
|
||||
self.ui.spinBoxSampleRate.editingFinished.connect(self.on_spinbox_sample_rate_editing_finished)
|
||||
|
||||
self.ui.spinBoxGain.editingFinished.connect(self.on_spinbox_gain_editing_finished)
|
||||
self.ui.spinBoxGain.valueChanged.connect(self.on_spinbox_gain_value_changed)
|
||||
self.ui.sliderGain.valueChanged.connect(self.on_slider_gain_value_changed)
|
||||
|
||||
self.ui.spinBoxIFGain.editingFinished.connect(self.on_spinbox_if_gain_editing_finished)
|
||||
self.ui.spinBoxIFGain.valueChanged.connect(self.on_spinbox_if_gain_value_changed)
|
||||
self.ui.sliderIFGain.valueChanged.connect(self.on_slider_if_gain_value_changed)
|
||||
|
||||
self.ui.spinBoxBasebandGain.editingFinished.connect(self.on_spinbox_baseband_gain_editing_finished)
|
||||
self.ui.spinBoxBasebandGain.valueChanged.connect(self.on_spinbox_baseband_gain_value_changed)
|
||||
self.ui.sliderBasebandGain.valueChanged.connect(self.on_slider_baseband_gain_value_changed)
|
||||
|
||||
self.ui.spinBoxBandwidth.editingFinished.connect(self.on_spinbox_bandwidth_editing_finished)
|
||||
self.ui.spinBoxPort.editingFinished.connect(self.on_spinbox_port_editing_finished)
|
||||
self.ui.lineEditIP.editingFinished.connect(self.on_line_edit_ip_editing_finished)
|
||||
self.ui.lineEditSubdevice.editingFinished.connect(self.on_line_edit_subdevice_editing_finished)
|
||||
|
||||
self.ui.comboBoxAntenna.currentIndexChanged.connect(self.on_combobox_antenna_current_index_changed)
|
||||
self.ui.comboBoxChannel.currentIndexChanged.connect(self.on_combobox_channel_current_index_changed)
|
||||
|
||||
self.ui.spinBoxFreqCorrection.editingFinished.connect(self.on_spinbox_freq_correction_editing_finished)
|
||||
self.ui.comboBoxDirectSampling.currentIndexChanged.connect(self.on_combobox_direct_sampling_index_changed)
|
||||
|
||||
self.ui.cbDevice.currentIndexChanged.connect(self.on_cb_device_current_index_changed)
|
||||
|
||||
self.ui.spinBoxNRepeat.editingFinished.connect(self.on_num_repeats_changed)
|
||||
self.ui.btnLockBWSR.clicked.connect(self.on_btn_lock_bw_sr_clicked)
|
||||
|
||||
self.ui.btnRefreshDeviceIdentifier.clicked.connect(self.on_btn_refresh_device_identifier_clicked)
|
||||
self.ui.comboBoxDeviceIdentifier.currentIndexChanged.connect(
|
||||
self.on_combo_box_device_identifier_current_index_changed)
|
||||
|
||||
self.ui.comboBoxDeviceIdentifier.editTextChanged.connect(self.on_combo_box_device_identifier_edit_text_changed)
|
||||
|
||||
self.ui.checkBoxBiasTee.clicked.connect(self.on_check_box_bias_tee_clicked)
|
||||
self.ui.checkBoxDCCorrection.clicked.connect(self.on_check_box_dc_correction_clicked)
|
||||
|
||||
def set_gain_defaults(self):
|
||||
self.set_default_rf_gain()
|
||||
self.set_default_if_gain()
|
||||
self.set_default_bb_gain()
|
||||
|
||||
def set_default_rf_gain(self):
|
||||
conf = self.selected_device_conf
|
||||
prefix = self.rx_tx_prefix
|
||||
if prefix + "rf_gain" in conf:
|
||||
key = prefix + "rf_gain"
|
||||
gain = conf[key][int(np.percentile(range(len(conf[key])), 25))]
|
||||
self.ui.spinBoxGain.setValue(gain)
|
||||
|
||||
def set_default_if_gain(self):
|
||||
conf = self.selected_device_conf
|
||||
prefix = self.rx_tx_prefix
|
||||
if prefix + "if_gain" in conf:
|
||||
key = prefix + "if_gain"
|
||||
if_gain = conf[key][int(median(range(len(conf[key]))))]
|
||||
self.ui.spinBoxIFGain.setValue(if_gain)
|
||||
|
||||
def set_default_bb_gain(self):
|
||||
conf = self.selected_device_conf
|
||||
prefix = self.rx_tx_prefix
|
||||
if prefix + "baseband_gain" in conf:
|
||||
key = prefix + "baseband_gain"
|
||||
baseband_gain = conf[key][int(np.percentile(list(range(len(conf[key]))), 25))]
|
||||
self.ui.spinBoxBasebandGain.setValue(baseband_gain)
|
||||
|
||||
def sync_gain_sliders(self):
|
||||
self.ui.spinBoxGain.valueChanged.emit(self.ui.spinBoxGain.value())
|
||||
self.ui.spinBoxIFGain.valueChanged.emit(self.ui.spinBoxIFGain.value())
|
||||
self.ui.spinBoxBasebandGain.valueChanged.emit(self.ui.spinBoxBasebandGain.value())
|
||||
|
||||
def set_device_ui_items_visibility(self, device_name: str, overwrite_settings=True):
|
||||
key = device_name if device_name in config.DEVICE_CONFIG.keys() else "Fallback"
|
||||
conf = config.DEVICE_CONFIG[key]
|
||||
key_ui_dev_param_map = {"center_freq": "Freq", "sample_rate": "SampleRate", "bandwidth": "Bandwidth"}
|
||||
|
||||
for key, ui_item in key_ui_dev_param_map.items():
|
||||
spinbox = getattr(self.ui, "spinBox" + ui_item) # type: QSpinBox
|
||||
label = getattr(self.ui, "label" + ui_item) # type: QLabel
|
||||
if key in conf:
|
||||
spinbox.setVisible(True)
|
||||
label.setVisible(True)
|
||||
|
||||
if isinstance(conf[key], list):
|
||||
spinbox.setMinimum(min(conf[key]))
|
||||
spinbox.setMaximum(max(conf[key]))
|
||||
spinbox.setSingleStep(conf[key][1] - conf[key][0])
|
||||
spinbox.auto_update_step_size = False
|
||||
if "default_" + key in conf:
|
||||
spinbox.setValue(conf["default_" + key])
|
||||
else:
|
||||
spinbox.setMinimum(conf[key].start)
|
||||
spinbox.setMaximum(conf[key].stop)
|
||||
spinbox.auto_update_step_size = True
|
||||
spinbox.adjust_step()
|
||||
else:
|
||||
spinbox.setVisible(False)
|
||||
label.setVisible(False)
|
||||
|
||||
self.ui.btnLockBWSR.setVisible("sample_rate" in conf and "bandwidth" in conf)
|
||||
|
||||
if self.device is not None:
|
||||
self.ui.labelSubdevice.setVisible(self.device.subdevice is not None)
|
||||
self.ui.lineEditSubdevice.setVisible(self.device.subdevice is not None)
|
||||
|
||||
if "freq_correction" in conf:
|
||||
self.ui.labelFreqCorrection.setVisible(True)
|
||||
self.ui.spinBoxFreqCorrection.setVisible(True)
|
||||
self.ui.spinBoxFreqCorrection.setMinimum(conf["freq_correction"].start)
|
||||
self.ui.spinBoxFreqCorrection.setMaximum(conf["freq_correction"].stop)
|
||||
self.ui.spinBoxFreqCorrection.setSingleStep(conf["freq_correction"].step)
|
||||
else:
|
||||
self.ui.labelFreqCorrection.setVisible(False)
|
||||
self.ui.spinBoxFreqCorrection.setVisible(False)
|
||||
|
||||
if "direct_sampling" in conf:
|
||||
self.ui.labelDirectSampling.setVisible(True)
|
||||
self.ui.comboBoxDirectSampling.setVisible(True)
|
||||
items = [self.ui.comboBoxDirectSampling.itemText(i) for i in range(self.ui.comboBoxDirectSampling.count())]
|
||||
if items != conf["direct_sampling"]:
|
||||
self.ui.comboBoxDirectSampling.clear()
|
||||
self.ui.comboBoxDirectSampling.addItems(conf["direct_sampling"])
|
||||
else:
|
||||
self.ui.labelDirectSampling.setVisible(False)
|
||||
self.ui.comboBoxDirectSampling.setVisible(False)
|
||||
|
||||
prefix = self.rx_tx_prefix
|
||||
key_ui_gain_map = {prefix + "rf_gain": "Gain", prefix + "if_gain": "IFGain",
|
||||
prefix + "baseband_gain": "BasebandGain"}
|
||||
for conf_key, ui_element in key_ui_gain_map.items():
|
||||
getattr(self.ui, "label" + ui_element).setVisible(conf_key in conf)
|
||||
|
||||
spinbox = getattr(self.ui, "spinBox" + ui_element) # type: QSpinBox
|
||||
slider = getattr(self.ui, "slider" + ui_element) # type: QSlider
|
||||
|
||||
if conf_key in conf:
|
||||
gain_values = conf[conf_key]
|
||||
assert len(gain_values) >= 2
|
||||
spinbox.setMinimum(gain_values[0])
|
||||
spinbox.setMaximum(gain_values[-1])
|
||||
if overwrite_settings:
|
||||
spinbox.setValue(gain_values[len(gain_values) // 2])
|
||||
spinbox.setSingleStep(gain_values[1] - gain_values[0])
|
||||
spinbox.setVisible(True)
|
||||
|
||||
slider.setMaximum(len(gain_values) - 1)
|
||||
else:
|
||||
spinbox.setVisible(False)
|
||||
slider.setVisible(False)
|
||||
getattr(self.ui, "slider" + ui_element).setVisible(conf_key in conf)
|
||||
|
||||
if overwrite_settings:
|
||||
key_ui_channel_ant_map = {prefix + "antenna": "Antenna", prefix + "channel": "Channel"}
|
||||
for conf_key, ui_element in key_ui_channel_ant_map.items():
|
||||
getattr(self.ui, "label" + ui_element).setVisible(conf_key in conf)
|
||||
combobox = getattr(self.ui, "comboBox" + ui_element) # type: QComboBox
|
||||
if conf_key in conf:
|
||||
combobox.clear()
|
||||
combobox.addItems(conf[conf_key])
|
||||
if conf_key + "_default_index" in conf:
|
||||
combobox.setCurrentIndex(conf[conf_key + "_default_index"])
|
||||
|
||||
combobox.setVisible(True)
|
||||
else:
|
||||
combobox.setVisible(False)
|
||||
|
||||
multi_dev_support = hasattr(self.device, "has_multi_device_support") and self.device.has_multi_device_support
|
||||
self.ui.labelDeviceIdentifier.setVisible(multi_dev_support)
|
||||
self.ui.btnRefreshDeviceIdentifier.setVisible(multi_dev_support)
|
||||
self.ui.comboBoxDeviceIdentifier.setVisible(multi_dev_support)
|
||||
self.ui.lineEditIP.setVisible("ip" in conf)
|
||||
self.ui.labelIP.setVisible("ip" in conf)
|
||||
self.ui.spinBoxPort.setVisible("port" in conf)
|
||||
self.ui.labelPort.setVisible("port" in conf)
|
||||
show_dc_correction = self.is_rx and self.device is not None and self.device.apply_dc_correction is not None
|
||||
self.ui.checkBoxDCCorrection.setVisible(show_dc_correction)
|
||||
self.ui.labelDCCorrection.setVisible(show_dc_correction)
|
||||
|
||||
show_bias_tee = "bias_tee_enabled" in conf and self.device is not None and self.device.bias_tee_enabled is not None
|
||||
self.ui.labelBiasTee.setVisible(show_bias_tee)
|
||||
self.ui.checkBoxBiasTee.setVisible(show_bias_tee)
|
||||
|
||||
def get_devices_for_combobox(self, continuous_send_mode):
|
||||
items = []
|
||||
for device_name in self.backend_handler.DEVICE_NAMES:
|
||||
dev = self.backend_handler.device_backends[device_name.lower()]
|
||||
if self.is_tx and dev.is_enabled and dev.supports_tx:
|
||||
if not continuous_send_mode:
|
||||
items.append(device_name)
|
||||
elif dev.selected_backend != Backends.grc:
|
||||
items.append(device_name)
|
||||
elif self.is_rx and dev.is_enabled and dev.supports_rx:
|
||||
items.append(device_name)
|
||||
|
||||
if PluginManager().is_plugin_enabled("NetworkSDRInterface"):
|
||||
items.append(NetworkSDRInterfacePlugin.NETWORK_SDR_NAME)
|
||||
|
||||
return items
|
||||
|
||||
def set_bandwidth_status(self):
|
||||
if hasattr(self, "device") and self.device is not None and self.device.backend != Backends.none:
|
||||
self.ui.spinBoxBandwidth.setEnabled(self.device.bandwidth_is_adjustable)
|
||||
self.ui.btnLockBWSR.setEnabled(self.device.bandwidth_is_adjustable)
|
||||
|
||||
if not self.device.bandwidth_is_adjustable:
|
||||
self.bw_sr_are_locked = False
|
||||
self.ui.spinBoxBandwidth.setToolTip(self.tr("Your driver of RTL-SDR does not support "
|
||||
"setting the bandwidth. "
|
||||
"If you need this feature, install a recent version."))
|
||||
else:
|
||||
self.ui.spinBoxBandwidth.setToolTip("")
|
||||
self.bw_sr_are_locked = self.ui.btnLockBWSR.isChecked()
|
||||
|
||||
def emit_editing_finished_signals(self):
|
||||
self.ui.spinBoxFreq.editingFinished.emit()
|
||||
self.ui.spinBoxBandwidth.editingFinished.emit()
|
||||
self.ui.spinBoxGain.editingFinished.emit()
|
||||
self.ui.spinBoxIFGain.editingFinished.emit()
|
||||
self.ui.spinBoxBasebandGain.editingFinished.emit()
|
||||
self.ui.spinBoxNRepeat.editingFinished.emit()
|
||||
self.ui.spinBoxSampleRate.editingFinished.emit()
|
||||
self.ui.spinBoxFreqCorrection.editingFinished.emit()
|
||||
self.ui.lineEditIP.editingFinished.emit()
|
||||
self.ui.lineEditSubdevice.editingFinished.emit()
|
||||
self.ui.spinBoxPort.editingFinished.emit()
|
||||
self.ui.comboBoxAntenna.currentIndexChanged.emit(self.ui.comboBoxAntenna.currentIndex())
|
||||
self.ui.comboBoxChannel.currentIndexChanged.emit(self.ui.comboBoxChannel.currentIndex())
|
||||
self.ui.checkBoxDCCorrection.clicked.emit(self.ui.checkBoxDCCorrection.isChecked())
|
||||
self.ui.checkBoxBiasTee.clicked.emit(self.ui.checkBoxBiasTee.isChecked())
|
||||
|
||||
def emit_device_parameters_changed(self):
|
||||
settings = {"name": str(self.device.name)}
|
||||
for attrib in ("frequency", "sample_rate", "bandwidth", "gain", "if_gain", "baseband_gain", "freq_correction",
|
||||
"antenna_index", "num_sending_repeats", "apply_dc_correction", "subdevice", "bias_tee_enabled"):
|
||||
try:
|
||||
value = getattr(self.device, attrib, None)
|
||||
if value is not None:
|
||||
if "gain" in attrib or attrib == "antenna_index":
|
||||
attrib = self.rx_tx_prefix + attrib
|
||||
settings[attrib] = value
|
||||
except (ValueError, AttributeError):
|
||||
continue
|
||||
|
||||
self.device_parameters_changed.emit(settings)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_lock_bw_sr_clicked(self):
|
||||
self.bw_sr_are_locked = self.ui.btnLockBWSR.isChecked()
|
||||
settings.write("lock_bandwidth_sample_rate", self.bw_sr_are_locked)
|
||||
if self.bw_sr_are_locked:
|
||||
self.ui.btnLockBWSR.setIcon(QIcon(":/icons/icons/lock.svg"))
|
||||
self.ui.spinBoxBandwidth.setValue(self.ui.spinBoxSampleRate.value())
|
||||
self.ui.spinBoxBandwidth.editingFinished.emit()
|
||||
else:
|
||||
self.ui.btnLockBWSR.setIcon(QIcon(":/icons/icons/unlock.svg"))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_sample_rate_editing_finished(self):
|
||||
self.device.sample_rate = self.ui.spinBoxSampleRate.value()
|
||||
if self.bw_sr_are_locked:
|
||||
self.ui.spinBoxBandwidth.setValue(self.ui.spinBoxSampleRate.value())
|
||||
self.device.bandwidth = self.ui.spinBoxBandwidth.value()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_frequency_editing_finished(self):
|
||||
self.device.frequency = self.ui.spinBoxFreq.value()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_bandwidth_editing_finished(self):
|
||||
self.device.bandwidth = self.ui.spinBoxBandwidth.value()
|
||||
if self.bw_sr_are_locked:
|
||||
self.ui.spinBoxSampleRate.setValue(self.ui.spinBoxBandwidth.value())
|
||||
self.device.sample_rate = self.ui.spinBoxSampleRate.value()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_ip_editing_finished(self):
|
||||
self.device.ip = self.ui.lineEditIP.text()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_subdevice_editing_finished(self):
|
||||
self.device.subdevice = self.ui.lineEditSubdevice.text()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_port_editing_finished(self):
|
||||
self.device.port = self.ui.spinBoxPort.value()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_antenna_current_index_changed(self, index: int):
|
||||
self.device.antenna_index = index
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_channel_current_index_changed(self, index: int):
|
||||
self.device.channel_index = index
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_freq_correction_editing_finished(self):
|
||||
self.device.freq_correction = self.ui.spinBoxFreqCorrection.value()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_direct_sampling_index_changed(self, index: int):
|
||||
self.device.direct_sampling_mode = index
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_gain_editing_finished(self):
|
||||
self.device.gain = self.ui.spinBoxGain.value()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_gain_value_changed(self, value: int):
|
||||
dev_conf = self.selected_device_conf
|
||||
try:
|
||||
self.ui.sliderGain.setValue(dev_conf[self.rx_tx_prefix + "rf_gain"].index(value))
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_slider_gain_value_changed(self, value: int):
|
||||
dev_conf = self.selected_device_conf
|
||||
self.ui.spinBoxGain.setValue(dev_conf[self.rx_tx_prefix + "rf_gain"][value])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_if_gain_editing_finished(self):
|
||||
self.device.if_gain = self.ui.spinBoxIFGain.value()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_slider_if_gain_value_changed(self, value: int):
|
||||
dev_conf = self.selected_device_conf
|
||||
self.ui.spinBoxIFGain.setValue(dev_conf[self.rx_tx_prefix + "if_gain"][value])
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_if_gain_value_changed(self, value: int):
|
||||
dev_conf = self.selected_device_conf
|
||||
try:
|
||||
self.ui.sliderIFGain.setValue(dev_conf[self.rx_tx_prefix + "if_gain"].index(value))
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
|
||||
@pyqtSlot()
|
||||
def on_num_repeats_changed(self):
|
||||
self.device.num_sending_repeats = self.ui.spinBoxNRepeat.value()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_baseband_gain_editing_finished(self):
|
||||
self.device.baseband_gain = self.ui.spinBoxBasebandGain.value()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_slider_baseband_gain_value_changed(self, value: int):
|
||||
dev_conf = self.selected_device_conf
|
||||
self.ui.spinBoxBasebandGain.setValue(dev_conf[self.rx_tx_prefix + "baseband_gain"][value])
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_baseband_gain_value_changed(self, value: int):
|
||||
dev_conf = self.selected_device_conf
|
||||
try:
|
||||
self.ui.sliderBasebandGain.setValue(dev_conf[self.rx_tx_prefix + "baseband_gain"].index(value))
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
|
||||
def update_for_new_device(self, overwrite_settings=True):
|
||||
if self.device is not None:
|
||||
self.device.free_data()
|
||||
|
||||
# Here init_device of dialogs gets called
|
||||
self.selected_device_changed.emit()
|
||||
|
||||
dev_name = self.ui.cbDevice.currentText()
|
||||
self.set_device_ui_items_visibility(dev_name, overwrite_settings=overwrite_settings)
|
||||
|
||||
if overwrite_settings:
|
||||
self.set_gain_defaults()
|
||||
|
||||
self.sync_gain_sliders()
|
||||
self.set_bandwidth_status()
|
||||
|
||||
self.ui.comboBoxDeviceIdentifier.clear()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_cb_device_current_index_changed(self):
|
||||
self.update_for_new_device(overwrite_settings=True)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_refresh_device_identifier_clicked(self):
|
||||
if self.device is None:
|
||||
return
|
||||
self.ui.comboBoxDeviceIdentifier.clear()
|
||||
self.ui.comboBoxDeviceIdentifier.addItems(self.device.get_device_list())
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_check_box_bias_tee_clicked(self, checked: bool):
|
||||
if self.device is not None:
|
||||
self.device.bias_tee_enabled = bool(checked)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_check_box_dc_correction_clicked(self, checked: bool):
|
||||
self.device.apply_dc_correction = bool(checked)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_combo_box_device_identifier_current_index_changed(self):
|
||||
if self.device is not None:
|
||||
self.device.device_serial = self.ui.comboBoxDeviceIdentifier.currentText()
|
||||
self.device.device_number = self.ui.comboBoxDeviceIdentifier.currentIndex()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_combo_box_device_identifier_edit_text_changed(self, new_text: str):
|
||||
self.device.device_serial = new_text
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from urh.controller.MainController import MainController
|
||||
|
||||
app = QApplication([])
|
||||
mc = MainController()
|
||||
widget = DeviceSettingsWidget(mc.project_manager, is_tx=False)
|
||||
|
||||
widget.show()
|
||||
app.exec_()
|
||||
@@ -0,0 +1,88 @@
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.dialogs.ModulatorDialog import ModulatorDialog
|
||||
from urh.signalprocessing.Modulator import Modulator
|
||||
from urh.ui.ui_modulation_settings_widget import Ui_ModulationSettings
|
||||
|
||||
|
||||
class ModulationSettingsWidget(QWidget):
|
||||
def __init__(self, modulators, selected_index=0, signal_tree_model=None, parent=None):
|
||||
"""
|
||||
|
||||
:type modulators: list of Modulator
|
||||
:param parent:
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_ModulationSettings()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.ui.labelModulationProfile.setVisible(settings.read("multiple_modulations", False, bool))
|
||||
self.ui.comboBoxModulationProfiles.setVisible(settings.read("multiple_modulations", False, bool))
|
||||
|
||||
self.signal_tree_model = signal_tree_model
|
||||
self.modulators = modulators # type: list[Modulator]
|
||||
for modulator in self.modulators:
|
||||
self.ui.comboBoxModulationProfiles.addItem(modulator.name)
|
||||
|
||||
self.ui.comboBoxModulationProfiles.setCurrentIndex(selected_index)
|
||||
|
||||
self.show_selected_modulation_infos()
|
||||
self.create_connects()
|
||||
|
||||
@property
|
||||
def selected_modulator(self) -> Modulator:
|
||||
return self.modulators[self.ui.comboBoxModulationProfiles.currentIndex()]
|
||||
|
||||
@selected_modulator.setter
|
||||
def selected_modulator(self, value: Modulator):
|
||||
if value in self.modulators:
|
||||
self.ui.comboBoxModulationProfiles.setCurrentIndex(self.modulators.index(value))
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.comboBoxModulationProfiles.currentIndexChanged.connect(self.on_cb_modulation_type_current_index_changed)
|
||||
self.ui.btnConfigurationDialog.clicked.connect(self.on_btn_configuration_dialog_clicked)
|
||||
|
||||
def show_selected_modulation_infos(self):
|
||||
modulator = self.selected_modulator
|
||||
self.ui.labelCarrierFrequencyValue.setText(modulator.carrier_frequency_str)
|
||||
self.ui.labelSamplesPerSymbolValue.setText(modulator.samples_per_symbol_str)
|
||||
self.ui.labelSampleRateValue.setText(modulator.sample_rate_str)
|
||||
self.ui.labelModulationTypeValue.setText(modulator.modulation_type_verbose)
|
||||
|
||||
self.ui.labelParameters.setText(modulator.parameter_type_str)
|
||||
self.ui.labelParameterValues.setText(modulator.parameters_string)
|
||||
self.ui.labelBitsPerSymbol.setText(str(modulator.bits_per_symbol))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_cb_modulation_type_current_index_changed(self):
|
||||
self.show_selected_modulation_infos()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_configuration_dialog_clicked(self):
|
||||
dialog = ModulatorDialog(self.modulators, tree_model=self.signal_tree_model, parent=self)
|
||||
dialog.ui.comboBoxCustomModulations.setCurrentIndex(self.ui.comboBoxModulationProfiles.currentIndex())
|
||||
dialog.finished.connect(self.refresh_modulators_from_dialog)
|
||||
dialog.show()
|
||||
dialog.initialize("10101011010010")
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_modulators_from_dialog(self):
|
||||
current_index = 0
|
||||
if type(self.sender()) == ModulatorDialog:
|
||||
current_index = self.sender().ui.comboBoxCustomModulations.currentIndex()
|
||||
|
||||
self.ui.comboBoxModulationProfiles.clear()
|
||||
for modulator in self.modulators:
|
||||
self.ui.comboBoxModulationProfiles.addItem(modulator.name)
|
||||
|
||||
self.ui.comboBoxModulationProfiles.setCurrentIndex(current_index)
|
||||
self.show_selected_modulation_infos()
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
app = QApplication([""])
|
||||
w = ModulationSettingsWidget([Modulator("test")])
|
||||
w.show()
|
||||
app.exec()
|
||||
@@ -0,0 +1,47 @@
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtWidgets import QFrame, QVBoxLayout
|
||||
|
||||
from urh import settings
|
||||
from urh.models.PluginListModel import PluginListModel
|
||||
from urh.ui.ui_plugins import Ui_FramePlugins
|
||||
|
||||
|
||||
class PluginFrame(QFrame):
|
||||
def __init__(self, plugins, highlighted_plugins=None, parent=None):
|
||||
"""
|
||||
:type plugins: list of Plugin
|
||||
:type highlighted_plugins: list of Plugin
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_FramePlugins()
|
||||
self.ui.setupUi(self)
|
||||
self.model = PluginListModel(plugins, highlighted_plugins=highlighted_plugins)
|
||||
self.ui.listViewPlugins.setModel(self.model)
|
||||
self.settings_layout = QVBoxLayout()
|
||||
self.ui.groupBoxSettings.setLayout(self.settings_layout)
|
||||
self.create_connects()
|
||||
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.listViewPlugins.selectionModel().selectionChanged.connect(self.on_list_selection_changed)
|
||||
for plugin in self.model.plugins:
|
||||
if hasattr(plugin, "show_proto_sniff_dialog_clicked"):
|
||||
plugin.show_proto_sniff_dialog_clicked.connect(self.parent().parent().show_proto_sniff_dialog)
|
||||
|
||||
def save_enabled_states(self):
|
||||
for plugin in self.model.plugins:
|
||||
settings.write(plugin.name, plugin.enabled)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_list_selection_changed(self):
|
||||
i = self.ui.listViewPlugins.currentIndex().row()
|
||||
self.ui.txtEditPluginDescription.setText(self.model.plugins[i].description)
|
||||
|
||||
if self.settings_layout.count() > 0:
|
||||
widget = self.settings_layout.takeAt(0).widget()
|
||||
self.settings_layout.removeWidget(widget)
|
||||
widget.setParent(None)
|
||||
|
||||
self.settings_layout.addWidget(self.model.plugins[i].settings_frame)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,229 @@
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtWidgets import QWidget, QCompleter, QDirModel
|
||||
|
||||
from urh import settings
|
||||
from urh.dev.BackendHandler import BackendHandler
|
||||
from urh.signalprocessing.ProtocolSniffer import ProtocolSniffer
|
||||
from urh.ui.ui_send_recv_sniff_settings import Ui_SniffSettings
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class SniffSettingsWidget(QWidget):
|
||||
sniff_setting_edited = pyqtSignal()
|
||||
sniff_file_edited = pyqtSignal()
|
||||
sniff_parameters_changed = pyqtSignal(dict)
|
||||
|
||||
def __init__(self, device_name: str, project_manager: ProjectManager, signal=None, backend_handler=None,
|
||||
network_raw_mode=False, signals=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_SniffSettings()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
signals = signals if signals is not None else []
|
||||
self.project_manager = project_manager
|
||||
|
||||
for encoding in self.project_manager.decodings:
|
||||
self.ui.comboBox_sniff_encoding.addItem(encoding.name)
|
||||
|
||||
self.bootstrap(project_manager.device_conf, signal, enforce_default=True)
|
||||
|
||||
self.sniffer = ProtocolSniffer(samples_per_symbol=self.ui.spinbox_sniff_SamplesPerSymbol.value(),
|
||||
center=self.ui.spinbox_sniff_Center.value(),
|
||||
center_spacing=self.ui.spinBoxCenterSpacing.value(),
|
||||
noise=self.ui.spinbox_sniff_Noise.value(),
|
||||
tolerance=self.ui.spinbox_sniff_ErrorTolerance.value(),
|
||||
modulation_type=self.ui.combox_sniff_Modulation.currentText(),
|
||||
bits_per_symbol=self.ui.spinBoxBitsPerSymbol.value(),
|
||||
device=device_name,
|
||||
backend_handler=BackendHandler() if backend_handler is None else backend_handler,
|
||||
network_raw_mode=network_raw_mode)
|
||||
|
||||
self.sniffer.adaptive_noise = self.ui.checkBoxAdaptiveNoise.isChecked()
|
||||
self.sniffer.automatic_center = self.ui.checkBoxAutoCenter.isChecked()
|
||||
|
||||
self.__set_center_offset_visibility()
|
||||
|
||||
self.create_connects()
|
||||
self.ui.comboBox_sniff_encoding.currentIndexChanged.emit(self.ui.comboBox_sniff_encoding.currentIndex())
|
||||
self.ui.comboBox_sniff_viewtype.setCurrentIndex(settings.read('default_view', 0, int))
|
||||
|
||||
# Auto Complete like a Boss
|
||||
completer = QCompleter()
|
||||
completer.setModel(QDirModel(completer))
|
||||
self.ui.lineEdit_sniff_OutputFile.setCompleter(completer)
|
||||
|
||||
self.signals = signals
|
||||
|
||||
if len(signals) == 0:
|
||||
self.ui.label_sniff_Signal.hide()
|
||||
self.ui.btn_sniff_use_signal.hide()
|
||||
self.ui.comboBox_sniff_signal.hide()
|
||||
else:
|
||||
for signal in signals:
|
||||
self.ui.comboBox_sniff_signal.addItem(signal.name)
|
||||
|
||||
def __set_center_offset_visibility(self):
|
||||
visible = self.ui.spinBoxBitsPerSymbol.value() > 1
|
||||
self.ui.labelCenterSpacing.setVisible(visible)
|
||||
self.ui.spinBoxCenterSpacing.setVisible(visible)
|
||||
|
||||
def bootstrap(self, conf_dict: dict, signal=None, enforce_default=False):
|
||||
def set_val(widget, key: str, default):
|
||||
try:
|
||||
value = conf_dict[key]
|
||||
except KeyError:
|
||||
value = default if enforce_default else None
|
||||
|
||||
if value is not None:
|
||||
if hasattr(widget, "setValue"):
|
||||
widget.setValue(value)
|
||||
elif hasattr(widget, "setCurrentIndex"):
|
||||
widget.setCurrentIndex(value)
|
||||
|
||||
set_val(self.ui.spinbox_sniff_SamplesPerSymbol, "samples_per_symbol", signal.samples_per_symbol if signal else 100)
|
||||
set_val(self.ui.spinbox_sniff_Center, "center", signal.center if signal else 0.02)
|
||||
set_val(self.ui.spinBoxCenterSpacing, "center_spacing", signal.center_spacing if signal else 0.1)
|
||||
set_val(self.ui.spinbox_sniff_ErrorTolerance, "tolerance", signal.tolerance if signal else 5)
|
||||
set_val(self.ui.spinbox_sniff_Noise, "noise", signal.noise_threshold_relative if signal else 0.001)
|
||||
self.ui.combox_sniff_Modulation.setCurrentText(conf_dict.get("modulation_type", signal.modulation_type if signal else "FSK"))
|
||||
set_val(self.ui.spinBoxBitsPerSymbol, "bits_per_symbol", signal.bits_per_symbol if signal else 1)
|
||||
self.ui.comboBox_sniff_encoding.setCurrentText(conf_dict.get("decoding_name", ""))
|
||||
self.ui.checkBoxAdaptiveNoise.setChecked(bool(conf_dict.get("adaptive_noise", False)))
|
||||
self.ui.checkBoxAutoCenter.setChecked(bool(conf_dict.get("automatic_center", False)))
|
||||
self.ui.spinbox_sniff_Center.setDisabled(self.ui.checkBoxAutoCenter.isChecked())
|
||||
|
||||
self.emit_editing_finished_signals()
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.spinbox_sniff_Noise.editingFinished.connect(self.on_noise_edited)
|
||||
self.ui.spinbox_sniff_Center.editingFinished.connect(self.on_center_edited)
|
||||
self.ui.spinBoxCenterSpacing.editingFinished.connect(self.on_center_spacing_edited)
|
||||
self.ui.spinbox_sniff_SamplesPerSymbol.editingFinished.connect(self.on_samples_per_symbol_edited)
|
||||
self.ui.spinbox_sniff_ErrorTolerance.editingFinished.connect(self.on_tolerance_edited)
|
||||
self.ui.combox_sniff_Modulation.currentTextChanged.connect(self.on_modulation_changed)
|
||||
self.ui.spinBoxBitsPerSymbol.editingFinished.connect(self.on_spin_box_bits_per_symbol_editing_finished)
|
||||
|
||||
self.ui.comboBox_sniff_viewtype.currentIndexChanged.connect(self.on_view_type_changed)
|
||||
self.ui.lineEdit_sniff_OutputFile.editingFinished.connect(self.on_line_edit_output_file_editing_finished)
|
||||
self.ui.comboBox_sniff_encoding.currentIndexChanged.connect(self.on_combobox_sniff_encoding_index_changed)
|
||||
self.ui.checkBox_sniff_Timestamp.clicked.connect(self.on_checkbox_sniff_timestamp_clicked)
|
||||
self.ui.btn_sniff_use_signal.clicked.connect(self.on_btn_sniff_use_signal_clicked)
|
||||
self.ui.checkBoxAdaptiveNoise.clicked.connect(self.on_check_box_adaptive_noise_clicked)
|
||||
self.ui.checkBoxAutoCenter.clicked.connect(self.on_check_box_auto_center_clicked)
|
||||
|
||||
def emit_editing_finished_signals(self):
|
||||
self.ui.spinbox_sniff_Noise.editingFinished.emit()
|
||||
self.ui.spinbox_sniff_Center.editingFinished.emit()
|
||||
self.ui.spinBoxCenterSpacing.editingFinished.emit()
|
||||
self.ui.spinbox_sniff_SamplesPerSymbol.editingFinished.emit()
|
||||
self.ui.spinBoxBitsPerSymbol.editingFinished.emit()
|
||||
self.ui.spinbox_sniff_ErrorTolerance.editingFinished.emit()
|
||||
self.ui.lineEdit_sniff_OutputFile.editingFinished.emit()
|
||||
self.ui.checkBoxAdaptiveNoise.clicked.emit()
|
||||
|
||||
def emit_sniff_parameters_changed(self):
|
||||
self.sniff_parameters_changed.emit(dict(samples_per_symbol=self.sniffer.signal.samples_per_symbol,
|
||||
center=self.sniffer.signal.center,
|
||||
center_spacing=self.sniffer.signal.center_spacing,
|
||||
noise=self.sniffer.signal.noise_threshold,
|
||||
tolerance=self.sniffer.signal.tolerance,
|
||||
modulation_type=self.sniffer.signal.modulation_type,
|
||||
bits_per_symbol=self.sniffer.signal.bits_per_symbol,
|
||||
decoding_name=self.sniffer.decoder.name,
|
||||
adaptive_noise=self.sniffer.adaptive_noise,
|
||||
automatic_center=self.sniffer.automatic_center))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_noise_edited(self):
|
||||
self.sniffer.signal.noise_threshold_relative = self.ui.spinbox_sniff_Noise.value()
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_center_edited(self):
|
||||
self.sniffer.signal.center = self.ui.spinbox_sniff_Center.value()
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_center_spacing_edited(self):
|
||||
self.sniffer.signal.center_spacing = self.ui.spinBoxCenterSpacing.value()
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_samples_per_symbol_edited(self):
|
||||
self.sniffer.signal.samples_per_symbol = self.ui.spinbox_sniff_SamplesPerSymbol.value()
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spin_box_bits_per_symbol_editing_finished(self):
|
||||
self.sniffer.signal.bits_per_symbol = self.ui.spinBoxBitsPerSymbol.value()
|
||||
self.__set_center_offset_visibility()
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_tolerance_edited(self):
|
||||
self.sniffer.signal.tolerance = self.ui.spinbox_sniff_ErrorTolerance.value()
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_modulation_changed(self, new_modulation: str):
|
||||
self.sniffer.signal.silent_set_modulation_type(new_modulation)
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_view_type_changed(self):
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_sniff_encoding_index_changed(self, index: int):
|
||||
if self.sniffer.decoder != self.project_manager.decodings[index]:
|
||||
self.sniffer.set_decoder_for_messages(self.project_manager.decodings[index])
|
||||
self.sniffer.decoder = self.project_manager.decodings[index]
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_output_file_editing_finished(self):
|
||||
self.ui.lineEdit_sniff_OutputFile.setStyleSheet("")
|
||||
text = self.ui.lineEdit_sniff_OutputFile.text()
|
||||
if text and not text.endswith(".txt"):
|
||||
text += ".txt"
|
||||
self.ui.lineEdit_sniff_OutputFile.setText(text)
|
||||
|
||||
if text and not os.path.isfile(text):
|
||||
try:
|
||||
open(text, "w").close()
|
||||
except Exception as e:
|
||||
self.ui.lineEdit_sniff_OutputFile.setStyleSheet("color:red;")
|
||||
return
|
||||
|
||||
self.sniffer.sniff_file = text
|
||||
self.sniff_file_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_checkbox_sniff_timestamp_clicked(self):
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_sniff_use_signal_clicked(self):
|
||||
try:
|
||||
signal = self.signals[self.ui.comboBox_sniff_signal.currentIndex()]
|
||||
except IndexError:
|
||||
return
|
||||
|
||||
self.ui.spinbox_sniff_SamplesPerSymbol.setValue(signal.samples_per_symbol)
|
||||
self.ui.spinbox_sniff_Center.setValue(signal.center)
|
||||
self.ui.spinbox_sniff_Noise.setValue(signal.noise_threshold_relative)
|
||||
self.ui.spinbox_sniff_ErrorTolerance.setValue(signal.tolerance)
|
||||
self.ui.combox_sniff_Modulation.setCurrentText(signal.modulation_type)
|
||||
|
||||
self.emit_editing_finished_signals()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_check_box_adaptive_noise_clicked(self):
|
||||
self.sniffer.adaptive_noise = self.ui.checkBoxAdaptiveNoise.isChecked()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_check_box_auto_center_clicked(self):
|
||||
self.sniffer.automatic_center = self.ui.checkBoxAutoCenter.isChecked()
|
||||
self.ui.spinbox_sniff_Center.setDisabled(self.ui.checkBoxAutoCenter.isChecked())
|
||||
2
Software/Universal Radio Hacker/src/urh/cythonext/.gitignore
vendored
Normal file
2
Software/Universal Radio Hacker/src/urh/cythonext/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.so
|
||||
*.pyd
|
||||
14
Software/Universal Radio Hacker/src/urh/cythonext/analyze.py
Normal file
14
Software/Universal Radio Hacker/src/urh/cythonext/analyze.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from subprocess import call, Popen
|
||||
|
||||
MODULES = ["path_creator", "signal_functions", "util", "auto_interpretation"]
|
||||
COMPILER_DIRECTIVES = {'language_level': 3,
|
||||
'cdivision': True,
|
||||
'wraparound': False,
|
||||
'boundscheck': False,
|
||||
'initializedcheck': False,
|
||||
}
|
||||
|
||||
for module in MODULES:
|
||||
call(["cython", "-a", "-X", ",".join("{}={}".format(key, val) for key, val in COMPILER_DIRECTIVES.items()),
|
||||
"--cplus", "-3", module + ".pyx"])
|
||||
Popen(["firefox", module + ".html"])
|
||||
@@ -0,0 +1,240 @@
|
||||
# noinspection PyUnresolvedReferences
|
||||
cimport numpy as np
|
||||
import numpy as np
|
||||
from cpython cimport array
|
||||
import array
|
||||
import cython
|
||||
|
||||
from cython.parallel import prange
|
||||
from libc.stdlib cimport malloc, free
|
||||
from libcpp.algorithm cimport sort
|
||||
from libc.stdint cimport uint64_t
|
||||
|
||||
cpdef tuple k_means(float[:] data, unsigned int k=2):
|
||||
cdef float[:] centers = np.empty(k, dtype=np.float32)
|
||||
cdef list clusters = []
|
||||
cdef set unique = set(data)
|
||||
cdef unsigned long i
|
||||
|
||||
if len(unique) < k:
|
||||
print("Warning: less different values than k")
|
||||
k = len(unique)
|
||||
|
||||
for i in range(k):
|
||||
centers[i] = unique.pop()
|
||||
clusters.append([])
|
||||
|
||||
cdef float[:] old_centers = np.array(centers, dtype=np.float32)
|
||||
cdef float distance, min_distance, error = 1.0
|
||||
cdef unsigned int j, index = 0, N = len(data)
|
||||
|
||||
while error != 0:
|
||||
for i in range(k):
|
||||
clusters[i].clear()
|
||||
|
||||
for i in range(N):
|
||||
min_distance = 999999999
|
||||
for j in range(k):
|
||||
distance = (centers[j] - data[i]) * (centers[j] - data[i])
|
||||
if distance < min_distance:
|
||||
min_distance = distance
|
||||
index = j
|
||||
clusters[index].append(data[i])
|
||||
|
||||
old_centers = np.array(centers)
|
||||
for i in range(k):
|
||||
centers[i] = np.mean(clusters[i])
|
||||
|
||||
error = 0.0
|
||||
for i in range(k):
|
||||
error += old_centers[i] * old_centers[i] - centers[i] * centers[i]
|
||||
|
||||
return centers, clusters
|
||||
|
||||
|
||||
def segment_messages_from_magnitudes(cython.floating[:] magnitudes, float noise_threshold):
|
||||
"""
|
||||
Get the list of start, end indices of messages
|
||||
|
||||
:param magnitudes: Magnitudes of samples
|
||||
:param q: Factor which controls how many samples of previous above noise plateau must be under noise to be counted as noise
|
||||
:return:
|
||||
"""
|
||||
cdef list result = []
|
||||
|
||||
if len(magnitudes) == 0:
|
||||
return []
|
||||
|
||||
cdef unsigned long i, N = len(magnitudes), start = 0
|
||||
cdef unsigned long summed_message_samples = 0
|
||||
|
||||
# tolerance / robustness against outliers
|
||||
cdef unsigned int outlier_tolerance = 10
|
||||
cdef unsigned int conseq_above = 0, conseq_below = 0
|
||||
|
||||
# Three states: 1 = above noise, 0 = in noise, but not yet above k threshold (k * above_total), -1 = in noise
|
||||
cdef int state
|
||||
state = 1 if magnitudes[0] > noise_threshold else -1
|
||||
|
||||
cdef bint is_above_noise
|
||||
|
||||
for i in range(N):
|
||||
is_above_noise = magnitudes[i] > noise_threshold
|
||||
if state == 1:
|
||||
if is_above_noise:
|
||||
conseq_below = 0
|
||||
else:
|
||||
conseq_below += 1
|
||||
elif state == -1:
|
||||
if is_above_noise:
|
||||
conseq_above += 1
|
||||
else:
|
||||
conseq_above = 0
|
||||
|
||||
# Perform state change if necessary
|
||||
if state == 1 and conseq_below >= outlier_tolerance:
|
||||
# 1 -> -1
|
||||
state = -1
|
||||
result.append((start, i - conseq_below))
|
||||
summed_message_samples += (i-conseq_below) - start
|
||||
conseq_below = conseq_above = 0
|
||||
elif state == -1 and conseq_above >= outlier_tolerance:
|
||||
# -1 -> 1
|
||||
state = 1
|
||||
start = i - conseq_above
|
||||
conseq_below = conseq_above = 0
|
||||
|
||||
# append last message
|
||||
if state == 1 and start < N - conseq_below:
|
||||
result.append((start, N - conseq_below))
|
||||
|
||||
return result
|
||||
|
||||
cpdef uint64_t[:] get_threshold_divisor_histogram(uint64_t[:] plateau_lengths, float threshold=0.2):
|
||||
"""
|
||||
Get a histogram (i.e. count) how many times a value is a threshold divisor for other values in given data
|
||||
|
||||
Threshold divisible is defined as having a decimal place less than .2 (threshold)
|
||||
|
||||
:param plateau_lengths:
|
||||
:return:
|
||||
"""
|
||||
cdef uint64_t i, j, x, y, minimum, maximum, num_lengths = len(plateau_lengths)
|
||||
|
||||
cdef np.ndarray[np.uint64_t, ndim=1] histogram = np.zeros(int(np.max(plateau_lengths)) + 1, dtype=np.uint64)
|
||||
|
||||
for i in range(0, num_lengths):
|
||||
for j in range(i+1, num_lengths):
|
||||
x = plateau_lengths[i]
|
||||
y = plateau_lengths[j]
|
||||
if x == 0 or y == 0:
|
||||
continue
|
||||
|
||||
if x < y:
|
||||
minimum = x
|
||||
maximum = y
|
||||
else:
|
||||
minimum = y
|
||||
maximum = x
|
||||
|
||||
if maximum / <double>minimum - (maximum / minimum) < threshold:
|
||||
histogram[minimum] += 1
|
||||
|
||||
return histogram
|
||||
|
||||
cpdef np.ndarray[np.uint64_t, ndim=1] merge_plateaus(np.ndarray[np.uint64_t, ndim=1] plateaus,
|
||||
uint64_t tolerance,
|
||||
uint64_t max_count):
|
||||
cdef uint64_t j, n, L = len(plateaus), current = 0, i = 1, tmp_sum
|
||||
if L == 0:
|
||||
return np.zeros(0, dtype=np.uint64)
|
||||
|
||||
cdef np.ndarray[np.uint64_t, ndim=1] result = np.empty(L, dtype=np.uint64)
|
||||
if plateaus[0] <= tolerance:
|
||||
result[0] = 0
|
||||
else:
|
||||
result[0] = plateaus[0]
|
||||
|
||||
while i < L and current < max_count:
|
||||
if plateaus[i] <= tolerance:
|
||||
# Look ahead to see whether we need to merge a larger window e.g. for 67, 1, 10, 1, 21
|
||||
n = 2
|
||||
while i + n < L and plateaus[i + n] <= tolerance:
|
||||
n += 2
|
||||
|
||||
tmp_sum = 0
|
||||
for j in range(i - 1, min(L, i + n)):
|
||||
tmp_sum += plateaus[j]
|
||||
|
||||
result[current] = tmp_sum
|
||||
i += n
|
||||
else:
|
||||
current += 1
|
||||
result[current] = plateaus[i]
|
||||
i += 1
|
||||
|
||||
return result[:current+1]
|
||||
|
||||
|
||||
cpdef np.ndarray[np.uint64_t, ndim=1] get_plateau_lengths(float[:] rect_data, float center, int percentage=25):
|
||||
if len(rect_data) == 0 or center is None:
|
||||
return np.array([], dtype=np.uint64)
|
||||
|
||||
cdef int state, new_state
|
||||
state = -1 if rect_data[0] <= center else 1
|
||||
cdef unsigned long long plateau_length = 0
|
||||
cdef unsigned long long current_sum = 0
|
||||
cdef unsigned long long i = 0
|
||||
cdef unsigned long long len_data = len(rect_data)
|
||||
cdef float sample
|
||||
|
||||
cdef array.array result = array.array('Q', [])
|
||||
|
||||
for i in range(0, len_data):
|
||||
if current_sum >= percentage * len_data / 100:
|
||||
break
|
||||
|
||||
sample = rect_data[i]
|
||||
new_state = -1 if sample <= center else 1
|
||||
|
||||
if state == new_state:
|
||||
plateau_length += 1
|
||||
else:
|
||||
result.append(plateau_length)
|
||||
current_sum += plateau_length
|
||||
state = new_state
|
||||
plateau_length = 1
|
||||
|
||||
return np.array(result, dtype=np.uint64)
|
||||
|
||||
|
||||
cdef float median(double[:] data, unsigned long start, unsigned long data_len, unsigned int k=3) nogil:
|
||||
cdef unsigned long i, j
|
||||
|
||||
if start + k > data_len:
|
||||
k = data_len - start
|
||||
|
||||
cdef float* buffer = <float *>malloc(k * sizeof(float))
|
||||
for i in range(0, k):
|
||||
buffer[i] = data[start+i]
|
||||
|
||||
sort(&buffer[0], (&buffer[0]) + k)
|
||||
try:
|
||||
return buffer[k//2]
|
||||
finally:
|
||||
free(buffer)
|
||||
|
||||
cpdef np.ndarray[np.float32_t, ndim=1] median_filter(double[:] data, unsigned int k=3):
|
||||
cdef long long start, end, i, n = len(data)
|
||||
|
||||
cdef np.ndarray[np.float32_t, ndim=1] result = np.zeros(n, dtype=np.float32)
|
||||
|
||||
for i in prange(0, n, nogil=True, schedule='static'):
|
||||
if i < k // 2:
|
||||
start = 0
|
||||
else:
|
||||
start = i - k // 2
|
||||
|
||||
result[i] = median(data, start=i, data_len=n, k=k)
|
||||
|
||||
return result
|
||||
369
Software/Universal Radio Hacker/src/urh/cythonext/awre_util.pyx
Normal file
369
Software/Universal Radio Hacker/src/urh/cythonext/awre_util.pyx
Normal file
@@ -0,0 +1,369 @@
|
||||
# noinspection PyUnresolvedReferences
|
||||
cimport numpy as np
|
||||
import numpy as np
|
||||
|
||||
|
||||
from libc.math cimport floor, ceil, pow
|
||||
from libc.stdlib cimport malloc, free
|
||||
|
||||
from libcpp cimport bool
|
||||
from libc.stdint cimport uint8_t, uint32_t, int32_t, int64_t
|
||||
|
||||
from urh.cythonext.util import crc
|
||||
from urh.cythonext.util cimport bit_array_to_number
|
||||
|
||||
cpdef set find_longest_common_sub_sequence_indices(np.uint8_t[::1] seq1, np.uint8_t[::1] seq2):
|
||||
cdef unsigned int i, j, longest = 0, counter = 0, len_bits1 = len(seq1), len_bits2 = len(seq2)
|
||||
cdef unsigned short max_results = 10, current_result = 0
|
||||
|
||||
cdef unsigned int[:, ::1] m = np.zeros((len_bits1+1, len_bits2+1), dtype=np.uint32, order="C")
|
||||
cdef unsigned int[:, ::1] result_indices = np.zeros((max_results, 2), dtype=np.uint32, order="C")
|
||||
|
||||
for i in range(0, len_bits1):
|
||||
for j in range(0, len_bits2):
|
||||
if seq1[i] == seq2[j]:
|
||||
counter = m[i, j] + 1
|
||||
m[i+1, j+1] = counter
|
||||
|
||||
if counter > longest:
|
||||
longest = counter
|
||||
|
||||
current_result = 0
|
||||
result_indices[current_result, 0] = i - counter + 1
|
||||
result_indices[current_result, 1] = i + 1
|
||||
elif counter == longest:
|
||||
if current_result < max_results - 1:
|
||||
current_result += 1
|
||||
result_indices[current_result, 0] = i - counter + 1
|
||||
result_indices[current_result, 1] = i + 1
|
||||
|
||||
cdef set result = set()
|
||||
for i in range(current_result+1):
|
||||
result.add((result_indices[i, 0], result_indices[i, 1]))
|
||||
|
||||
return result
|
||||
|
||||
cpdef uint32_t find_first_difference(uint8_t[::1] bits1, uint8_t[::1] bits2, uint32_t len_bits1, uint32_t len_bits2) nogil:
|
||||
cdef uint32_t i, smaller_len = min(len_bits1, len_bits2)
|
||||
|
||||
for i in range(0, smaller_len):
|
||||
if bits1[i] != bits2[i]:
|
||||
return i
|
||||
|
||||
return smaller_len
|
||||
|
||||
cpdef np.ndarray[np.uint32_t, ndim=2, mode="c"] get_difference_matrix(list bitvectors):
|
||||
cdef uint32_t i, j, N = len(bitvectors)
|
||||
cdef np.ndarray[np.uint32_t, ndim=2, mode="c"] result = np.zeros((N, N), dtype=np.uint32, order="C")
|
||||
|
||||
cdef uint8_t[::1] bitvector_i
|
||||
cdef uint32_t len_bitvector_i
|
||||
|
||||
for i in range(N):
|
||||
bitvector_i = bitvectors[i]
|
||||
len_bitvector_i = len(bitvector_i)
|
||||
for j in range(i + 1, N):
|
||||
result[i, j] = find_first_difference(bitvector_i, bitvectors[j], len_bitvector_i, len(bitvectors[j]))
|
||||
|
||||
return result
|
||||
|
||||
cpdef list get_hexvectors(list bitvectors):
|
||||
cdef list result = []
|
||||
cdef uint8_t[::1] bitvector
|
||||
cdef size_t i, j, M, N = len(bitvectors)
|
||||
|
||||
cdef np.ndarray[np.uint8_t, mode="c"] hexvector
|
||||
cdef size_t len_bitvector
|
||||
|
||||
for i in range(0, N):
|
||||
bitvector = bitvectors[i]
|
||||
len_bitvector = len(bitvector)
|
||||
|
||||
M = <size_t>ceil(len_bitvector / 4)
|
||||
hexvector = np.zeros(M, dtype=np.uint8, order="C")
|
||||
|
||||
for j in range(0, M):
|
||||
hexvector[j] = bit_array_to_number(bitvector, min(len_bitvector, 4*j+4), 4*j)
|
||||
|
||||
result.append(hexvector)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
cdef int lower_multiple_of_n(int number, int n) nogil:
|
||||
return n * <int>floor(number / n)
|
||||
|
||||
cdef int64_t find(uint8_t[:] data, int64_t len_data, uint8_t element, int64_t start=0) nogil:
|
||||
cdef int64_t i
|
||||
for i in range(start, len_data):
|
||||
if data[i] == element:
|
||||
return i
|
||||
return -1
|
||||
|
||||
cpdef tuple get_raw_preamble_position(uint8_t[:] bitvector):
|
||||
cdef int64_t N = len(bitvector)
|
||||
if N == 0:
|
||||
return 0, 0
|
||||
|
||||
cdef int64_t i, j, n, m, start = -1
|
||||
cdef double k = 0
|
||||
|
||||
cdef int64_t lower = 0, upper = 0
|
||||
cdef uint8_t a, b
|
||||
|
||||
cdef uint8_t* preamble_pattern = NULL
|
||||
cdef int64_t len_preamble_pattern, preamble_end
|
||||
|
||||
cdef bool preamble_end_reached
|
||||
|
||||
while k < 2 and start < N:
|
||||
start += 1
|
||||
|
||||
a = bitvector[start]
|
||||
b = 1 if a == 0 else 0
|
||||
|
||||
# now we search for the pattern a^n b^m
|
||||
n = find(bitvector, N, b, start) - start
|
||||
|
||||
if n <= 0:
|
||||
return 0, 0, 0
|
||||
|
||||
m = find(bitvector, N, a, start+n) - n - start
|
||||
|
||||
if m <= 0:
|
||||
return 0, 0, 0
|
||||
|
||||
#preamble_pattern = a * n + b * m
|
||||
len_preamble_pattern = n + m
|
||||
preamble_pattern = <uint8_t*> malloc(len_preamble_pattern * sizeof(uint8_t))
|
||||
|
||||
for j in range(0, n):
|
||||
preamble_pattern[j] = a
|
||||
for j in range(n, len_preamble_pattern):
|
||||
preamble_pattern[j] = b
|
||||
|
||||
preamble_end = start
|
||||
preamble_end_reached = False
|
||||
for i in range(start, N, len_preamble_pattern):
|
||||
if preamble_end_reached:
|
||||
break
|
||||
for j in range(0, len_preamble_pattern):
|
||||
if bitvector[i+j] != preamble_pattern[j]:
|
||||
preamble_end_reached = True
|
||||
preamble_end = i
|
||||
break
|
||||
|
||||
free(preamble_pattern)
|
||||
|
||||
upper = start + lower_multiple_of_n(preamble_end + 1 - start, len_preamble_pattern)
|
||||
lower = upper - len_preamble_pattern
|
||||
|
||||
k = (upper - start) / len_preamble_pattern
|
||||
|
||||
if k > 2:
|
||||
return start, lower, upper
|
||||
else:
|
||||
# no preamble found
|
||||
return 0, 0, 0
|
||||
|
||||
|
||||
cpdef dict find_possible_sync_words(np.ndarray[np.uint32_t, ndim=2, mode="c"] difference_matrix,
|
||||
np.ndarray[np.uint32_t, ndim=2, mode="c"] raw_preamble_positions,
|
||||
list bitvectors, int n_gram_length):
|
||||
cdef dict possible_sync_words = dict()
|
||||
|
||||
cdef uint32_t i, j, num_rows = difference_matrix.shape[0], num_cols = difference_matrix.shape[1]
|
||||
cdef uint32_t sync_len, sync_end, start, index, k, n
|
||||
|
||||
cdef bytes sync_word
|
||||
|
||||
cdef np.ndarray[np.uint8_t, mode="c"] bitvector
|
||||
|
||||
cdef uint8_t ij_ctr = 0
|
||||
cdef uint32_t* ij_arr = <uint32_t*>malloc(2 * sizeof(uint32_t))
|
||||
cdef uint8_t* temp = NULL
|
||||
|
||||
for i in range(0, num_rows):
|
||||
for j in range(i + 1, num_cols):
|
||||
# position of first difference between message i and j
|
||||
sync_end = difference_matrix[i, j]
|
||||
|
||||
if sync_end == 0:
|
||||
continue
|
||||
|
||||
ij_arr[0] = i
|
||||
ij_arr[1] = j
|
||||
|
||||
for k in range(0, 2):
|
||||
for ij_ctr in range(0, 2):
|
||||
index = ij_arr[ij_ctr]
|
||||
start = raw_preamble_positions[index, 0] + raw_preamble_positions[index, k + 1]
|
||||
|
||||
# We take the next lower multiple of n for the sync len
|
||||
# In doubt, it is better to under estimate the sync len to prevent it from
|
||||
# taking needed values from other fields e.g. leading zeros for a length field
|
||||
sync_len = max(0, lower_multiple_of_n(sync_end - start, n_gram_length))
|
||||
|
||||
if sync_len >= 2:
|
||||
bitvector = bitvectors[index]
|
||||
if sync_len == 2:
|
||||
# Sync word must not be empty or just two bits long and "10" or "01" because
|
||||
# that would be indistinguishable from the preamble
|
||||
if bitvector[start] == 0 and bitvector[start+1] == 1:
|
||||
continue
|
||||
if bitvector[start] == 1 and bitvector[start+1] == 0:
|
||||
continue
|
||||
|
||||
temp = <uint8_t*>malloc(sync_len * sizeof(uint8_t))
|
||||
for n in range(0, sync_len):
|
||||
temp[n] = bitvector[start+n]
|
||||
sync_word = <bytes> temp[:sync_len]
|
||||
free(temp)
|
||||
|
||||
possible_sync_words.setdefault(sync_word, 0)
|
||||
if (start + sync_len) % n_gram_length == 0:
|
||||
# if sync end aligns nicely at n gram length give it a larger score
|
||||
possible_sync_words[sync_word] += 1
|
||||
else:
|
||||
possible_sync_words[sync_word] += 0.5
|
||||
|
||||
free(ij_arr)
|
||||
return possible_sync_words
|
||||
|
||||
cpdef np.ndarray[np.float64_t] create_difference_histogram(list vectors, list active_indices):
|
||||
"""
|
||||
Return a histogram of common ranges. E.g. [1, 1, 0.75, 0.8] means 75% of values at third column are equal
|
||||
|
||||
:param vectors: Vectors over which differences the histogram will be created
|
||||
:param active_indices: Active indices of vectors. Vectors with index not in this list will be ignored
|
||||
:return:
|
||||
"""
|
||||
cdef unsigned long i,j,k,index_i,index_j, L = len(active_indices)
|
||||
cdef unsigned long longest = 0, len_vector, len_vector_i
|
||||
for i in active_indices:
|
||||
len_vector = len(vectors[i])
|
||||
if len_vector > longest:
|
||||
longest = len_vector
|
||||
|
||||
cdef np.ndarray[np.float64_t] histogram = np.zeros(longest, dtype=np.float64)
|
||||
cdef double n = (len(active_indices) * (len(active_indices) - 1)) // 2
|
||||
|
||||
cdef np.ndarray[np.uint8_t] bitvector_i, bitvector_j
|
||||
|
||||
for i in range(0, L - 1):
|
||||
index_i = active_indices[i]
|
||||
bitvector_i = vectors[index_i]
|
||||
len_vector_i = len(bitvector_i)
|
||||
for j in range(i+1, L):
|
||||
index_j = active_indices[j]
|
||||
bitvector_j = vectors[index_j]
|
||||
for k in range(0, <size_t>min(len_vector_i, <size_t>len(bitvector_j))):
|
||||
if bitvector_i[k] == bitvector_j[k]:
|
||||
histogram[k] += 1 / n
|
||||
return histogram
|
||||
|
||||
cpdef list find_occurrences(np.uint8_t[::1] a, np.uint8_t[::1] b,
|
||||
unsigned long[:] ignore_indices=None, bool return_after_first=False):
|
||||
"""
|
||||
Find the indices of occurrences of b in a.
|
||||
|
||||
:param a: Larger array
|
||||
:param b: Subarray to search for
|
||||
:return: List of start indices of b in a
|
||||
"""
|
||||
cdef unsigned long i, j
|
||||
cdef unsigned long len_a = len(a), len_b = len(b)
|
||||
|
||||
cdef bool ignore_indices_present = ignore_indices is not None
|
||||
|
||||
if len_b > len_a:
|
||||
return []
|
||||
|
||||
cdef list result = []
|
||||
cdef bool found
|
||||
for i in range(0, (len_a-len_b) + 1):
|
||||
found = True
|
||||
for j in range(0, len_b):
|
||||
if ignore_indices_present:
|
||||
if i+j in ignore_indices:
|
||||
found = False
|
||||
break
|
||||
|
||||
if a[i+j] != b[j]:
|
||||
found = False
|
||||
break
|
||||
if found:
|
||||
if return_after_first:
|
||||
return [i]
|
||||
else:
|
||||
result.append(i)
|
||||
|
||||
return result
|
||||
|
||||
cpdef np.ndarray[np.int32_t, ndim=2, mode="c"] create_seq_number_difference_matrix(list bitvectors, int n_gram_length):
|
||||
"""
|
||||
Create the difference matrix e.g.
|
||||
10 20 0
|
||||
1 2 3
|
||||
4 5 6
|
||||
|
||||
means first eight bits of messages 1 and 2 (row 1) differ by 10 if they are considered as decimal number
|
||||
|
||||
:type bitvectors: list of np.ndarray
|
||||
:type n_gram_length: int
|
||||
:rtype: np.ndarray
|
||||
"""
|
||||
cdef size_t max_len = len(max(bitvectors, key=len))
|
||||
cdef size_t i, j, k, index, N = len(bitvectors), M = <size_t>ceil(max_len / n_gram_length)
|
||||
cdef uint8_t[::1] bv1, bv2
|
||||
cdef size_t len_bv1, len_bv2
|
||||
cdef int32_t diff
|
||||
cdef int32_t n_gram_power_two = <int32_t>pow(2, n_gram_length)
|
||||
|
||||
cdef np.ndarray[np.int32_t, ndim=2, mode="c"] result = np.full((N - 1, M), -1, dtype=np.int32)
|
||||
for i in range(1, N):
|
||||
bv1 = bitvectors[i - 1]
|
||||
bv2 = bitvectors[i]
|
||||
len_bv1 = len(bv1)
|
||||
len_bv2 = len(bv2)
|
||||
k = min(len_bv1, len_bv2)
|
||||
for j in range(0, k, n_gram_length):
|
||||
index = j / n_gram_length
|
||||
if index < M:
|
||||
diff = bit_array_to_number(bv2, min(len_bv2, j + n_gram_length), j) -\
|
||||
bit_array_to_number(bv1, min(len_bv1, j+n_gram_length), j)
|
||||
# add + n_gram_power_two because in C modulo can be negative
|
||||
result[i - 1, index] = (diff + n_gram_power_two) % n_gram_power_two
|
||||
|
||||
return result
|
||||
|
||||
cpdef set check_crc_for_messages(list message_indices, list bitvectors,
|
||||
unsigned long data_start, unsigned long data_stop,
|
||||
unsigned long crc_start, unsigned long crc_stop,
|
||||
unsigned char[:] crc_polynomial, unsigned char[:] crc_start_value,
|
||||
unsigned char[:] crc_final_xor,
|
||||
bool crc_lsb_first, bool crc_reverse_polynomial,
|
||||
bool crc_reverse_all, bool crc_little_endian):
|
||||
"""
|
||||
Check a configurable subset of bitvectors for a matching CRC and return the indices of the
|
||||
vectors who match the CRC with the given parameters
|
||||
:return:
|
||||
"""
|
||||
cdef set result = set()
|
||||
cdef unsigned long j, index, end = len(message_indices)
|
||||
cdef np.ndarray[np.uint8_t] bits
|
||||
cdef unsigned char[:] crc_input
|
||||
cdef unsigned long long check
|
||||
|
||||
for j in range(0, end):
|
||||
index = message_indices[j]
|
||||
bits = bitvectors[index]
|
||||
crc_input = bits[data_start:data_stop]
|
||||
#check = int("".join(map(str, bits[crc_start:crc_stop])), 2)
|
||||
check = bit_array_to_number(bits[crc_start:crc_stop], crc_stop - crc_start)
|
||||
if crc(crc_input, crc_polynomial, crc_start_value, crc_final_xor,
|
||||
crc_lsb_first, crc_reverse_polynomial,
|
||||
crc_reverse_all, crc_little_endian) == check:
|
||||
result.add(index)
|
||||
|
||||
return result
|
||||
18
Software/Universal Radio Hacker/src/urh/cythonext/build.py
Normal file
18
Software/Universal Radio Hacker/src/urh/cythonext/build.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from subprocess import call
|
||||
|
||||
build_dir = os.path.join(tempfile.gettempdir(), "build")
|
||||
|
||||
|
||||
def main():
|
||||
cur_dir = os.path.realpath(__file__)
|
||||
os.chdir(os.path.realpath(os.path.join(cur_dir, "..", "..", "..", "..")))
|
||||
# call([sys.executable, "setup.py", "clean", "--all"])
|
||||
rc = call([sys.executable, "setup.py", "build_ext", "--inplace", "-j{}".format(os.cpu_count())])
|
||||
return rc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,125 @@
|
||||
# noinspection PyUnresolvedReferences
|
||||
cimport numpy as np
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import QByteArray, QDataStream
|
||||
from PyQt5.QtGui import QPainterPath
|
||||
|
||||
# As we do not use any numpy C API functions we do no import_array here,
|
||||
# because it can lead to OS X error: https://github.com/jopohl/urh/issues/273
|
||||
# np.import_array()
|
||||
|
||||
from cython.parallel import prange
|
||||
from urh.cythonext.util cimport iq
|
||||
|
||||
from urh import settings
|
||||
import cython
|
||||
import math
|
||||
import struct
|
||||
|
||||
cpdef create_path(iq[:] samples, long long start, long long end, list subpath_ranges=None):
|
||||
cdef iq[:] values
|
||||
cdef long long[::1] sample_rng
|
||||
cdef np.int64_t[::1] x
|
||||
cdef iq sample, minimum, maximum, tmp
|
||||
cdef float scale_factor
|
||||
cdef long long i,j,index, chunk_end, num_samples, pixels_on_path, samples_per_pixel
|
||||
num_samples = end - start
|
||||
|
||||
cdef dict type_lookup = {"char[:]": np.int8, "unsigned char[:]": np.uint8,
|
||||
"short[:]": np.int16, "unsigned short[:]": np.uint16,
|
||||
"float[:]": np.float32, "double[:]": np.float64}
|
||||
|
||||
subpath_ranges = [(start, end)] if subpath_ranges is None else subpath_ranges
|
||||
pixels_on_path = settings.PIXELS_PER_PATH
|
||||
|
||||
samples_per_pixel = <long long>(num_samples / pixels_on_path)
|
||||
|
||||
cdef int num_threads = 0
|
||||
if samples_per_pixel < 20000:
|
||||
num_threads = 1
|
||||
|
||||
if samples_per_pixel > 1:
|
||||
sample_rng = np.arange(start, end, samples_per_pixel, dtype=np.int64)
|
||||
values = np.zeros(2 * len(sample_rng), dtype=type_lookup[cython.typeof(samples)], order="C")
|
||||
scale_factor = num_samples / (2.0 * len(sample_rng)) # 2.0 is important to make it a float division!
|
||||
for i in prange(start, end, samples_per_pixel, nogil=True, schedule='static', num_threads=num_threads):
|
||||
chunk_end = i + samples_per_pixel
|
||||
if chunk_end >= end:
|
||||
chunk_end = end
|
||||
|
||||
tmp = samples[i]
|
||||
minimum = tmp
|
||||
maximum = tmp
|
||||
|
||||
for j in range(i + 1, chunk_end):
|
||||
sample = samples[j]
|
||||
if sample < minimum:
|
||||
minimum = sample
|
||||
elif sample > maximum:
|
||||
maximum = sample
|
||||
|
||||
index = <long long>(2*(i-start)/samples_per_pixel)
|
||||
values[index] = minimum
|
||||
values[index + 1] = maximum
|
||||
|
||||
x = np.repeat(sample_rng, 2)
|
||||
else:
|
||||
x = np.arange(start, end, dtype=np.int64)
|
||||
values = samples[start:end]
|
||||
scale_factor = 1.0
|
||||
|
||||
cdef list result = []
|
||||
if scale_factor == 0:
|
||||
scale_factor = 1 # prevent division by zero
|
||||
|
||||
for subpath_range in subpath_ranges:
|
||||
sub_start = ((((subpath_range[0]-start)/scale_factor) * scale_factor) - 2*scale_factor) / scale_factor
|
||||
sub_start =int(max(0, math.floor(sub_start)))
|
||||
sub_end = ((((subpath_range[1]-start)/scale_factor) * scale_factor) + 2*scale_factor) / scale_factor
|
||||
sub_end = int(max(0, math.ceil(sub_end)))
|
||||
result.append(array_to_QPath(x[sub_start:sub_end], values[sub_start:sub_end]))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
cpdef create_live_path(iq[:] samples, unsigned int start, unsigned int end):
|
||||
return array_to_QPath(np.arange(start, end).astype(np.int64), samples)
|
||||
|
||||
cpdef array_to_QPath(np.int64_t[:] x, y):
|
||||
"""
|
||||
Convert an array of x,y coordinates to QPainterPath as efficiently as possible.
|
||||
|
||||
Speed this up using >> operator
|
||||
Format is:
|
||||
numVerts(i4) 0(i4)
|
||||
x(f8) y(f8) 0(i4) <-- 0 means this vertex does not connect
|
||||
x(f8) y(f8) 1(i4) <-- 1 means this vertex connects to the previous vertex
|
||||
...
|
||||
0(i4)
|
||||
|
||||
All values are big endian--pack using struct.pack('>d') or struct.pack('>i')
|
||||
"""
|
||||
cdef long long n = x.shape[0]
|
||||
arr = np.zeros(n + 2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')])
|
||||
|
||||
byte_view = arr.view(dtype=np.uint8)
|
||||
byte_view[:12] = 0
|
||||
byte_view.data[12:20] = struct.pack('>ii', n, 0)
|
||||
|
||||
arr[1:n+1]['x'] = x
|
||||
arr[1:n+1]['y'] = np.negative(y) # negate y since coordinate system is inverted
|
||||
arr[1:n+1]['c'] = 1
|
||||
|
||||
cdef long long last_index = 20 * (n + 1)
|
||||
byte_view.data[last_index:last_index + 4] = struct.pack('>i', 0)
|
||||
|
||||
try:
|
||||
buf = QByteArray.fromRawData(byte_view.data[12:last_index + 4])
|
||||
except TypeError:
|
||||
buf = QByteArray(byte_view.data[12:last_index + 4])
|
||||
|
||||
path = QPainterPath()
|
||||
ds = QDataStream(buf)
|
||||
ds >> path
|
||||
|
||||
return path
|
||||
@@ -0,0 +1,542 @@
|
||||
# noinspection PyUnresolvedReferences
|
||||
cimport numpy as np
|
||||
import cython
|
||||
import numpy as np
|
||||
from libcpp cimport bool
|
||||
|
||||
from libc.stdint cimport uint8_t, uint16_t, uint32_t, int64_t
|
||||
from libc.stdio cimport printf
|
||||
from libc.stdlib cimport malloc, free
|
||||
from urh.cythonext.util cimport IQ, iq, bit_array_to_number
|
||||
|
||||
from cython.parallel import prange
|
||||
from libc.math cimport atan2, sqrt, M_PI, abs
|
||||
|
||||
cdef extern from "math.h" nogil:
|
||||
float cosf(float x)
|
||||
float acosf(float x)
|
||||
float sinf(float x)
|
||||
|
||||
cdef extern from "complex.h" namespace "std" nogil:
|
||||
float arg(float complex x)
|
||||
float complex conj(float complex x)
|
||||
|
||||
# As we do not use any numpy C API functions we do no import_array here,
|
||||
# because it can lead to OS X error: https://github.com/jopohl/urh/issues/273
|
||||
# np.import_array()
|
||||
|
||||
cdef int64_t PAUSE_STATE = -1
|
||||
|
||||
cdef float complex imag_unit = 1j
|
||||
cdef float NOISE_FSK_PSK = -4.0
|
||||
cdef float NOISE_ASK = 0.0
|
||||
|
||||
cdef float get_noise_for_mod_type(str mod_type):
|
||||
if mod_type == "ASK":
|
||||
return NOISE_ASK
|
||||
elif mod_type == "FSK":
|
||||
return NOISE_FSK_PSK
|
||||
elif mod_type == "PSK" or mod_type == "OQPSK":
|
||||
return NOISE_FSK_PSK
|
||||
elif mod_type == "QAM":
|
||||
return NOISE_ASK * NOISE_FSK_PSK
|
||||
else:
|
||||
return 0
|
||||
|
||||
cdef get_numpy_dtype(iq cython_type):
|
||||
if str(cython.typeof(cython_type)) == "char":
|
||||
return np.int8
|
||||
elif str(cython.typeof(cython_type)) == "short":
|
||||
return np.int16
|
||||
elif str(cython.typeof(cython_type)) == "float":
|
||||
return np.float32
|
||||
else:
|
||||
raise ValueError("dtype {} not supported for modulation".format(cython.typeof(cython_type)))
|
||||
|
||||
cpdef modulate_c(uint8_t[:] bits, uint32_t samples_per_symbol, str modulation_type,
|
||||
float[:] parameters, uint16_t bits_per_symbol,
|
||||
float carrier_amplitude, float carrier_frequency, float carrier_phase, float sample_rate,
|
||||
uint32_t pause, uint32_t start, dtype=np.float32,
|
||||
float gauss_bt=0.5, float filter_width=1.0):
|
||||
|
||||
if dtype == np.int8:
|
||||
return __modulate(
|
||||
bits, samples_per_symbol, modulation_type, parameters, bits_per_symbol, carrier_amplitude,
|
||||
carrier_frequency, carrier_phase, sample_rate, pause, start, <char>0, gauss_bt, filter_width
|
||||
)
|
||||
elif dtype == np.int16:
|
||||
return __modulate(
|
||||
bits, samples_per_symbol, modulation_type, parameters, bits_per_symbol, carrier_amplitude,
|
||||
carrier_frequency, carrier_phase, sample_rate, pause, start, <short>0, gauss_bt, filter_width
|
||||
)
|
||||
elif dtype == np.float32:
|
||||
return __modulate(
|
||||
bits, samples_per_symbol, modulation_type, parameters, bits_per_symbol, carrier_amplitude,
|
||||
carrier_frequency, carrier_phase, sample_rate, pause, start, <float>0.0, gauss_bt, filter_width
|
||||
)
|
||||
else:
|
||||
raise ValueError("Unsupported dtype for modulation {}".format(dtype))
|
||||
|
||||
|
||||
cpdef __modulate(uint8_t[:] bits, uint32_t samples_per_symbol, str modulation_type,
|
||||
float[:] parameters, uint16_t bits_per_symbol,
|
||||
float carrier_amplitude, float carrier_frequency, float carrier_phase, float sample_rate,
|
||||
uint32_t pause, uint32_t start, iq iq_type,
|
||||
float gauss_bt=0.5, float filter_width=1.0):
|
||||
cdef int64_t i = 0, j = 0, index = 0, prev_index=0, s_i = 0, num_bits = len(bits)
|
||||
cdef uint32_t total_symbols = int(num_bits // bits_per_symbol)
|
||||
cdef int64_t total_samples = total_symbols * samples_per_symbol + pause
|
||||
|
||||
cdef float a = carrier_amplitude, f = carrier_frequency, phi = carrier_phase
|
||||
|
||||
cdef float f_previous = 0, phase_correction = 0
|
||||
cdef float t = 0, current_arg = 0
|
||||
|
||||
result = np.zeros((total_samples, 2), dtype=get_numpy_dtype(iq_type))
|
||||
if num_bits == 0:
|
||||
return result
|
||||
|
||||
cdef iq[:, ::1] result_view = result
|
||||
|
||||
cdef bool is_fsk = modulation_type.lower() == "fsk"
|
||||
cdef bool is_ask = modulation_type.lower() == "ask"
|
||||
cdef bool is_psk = modulation_type.lower() == "psk"
|
||||
cdef bool is_oqpsk = modulation_type.lower() == "oqpsk"
|
||||
cdef bool is_gfsk = modulation_type.lower() == "gfsk"
|
||||
|
||||
assert is_fsk or is_ask or is_psk or is_gfsk or is_oqpsk
|
||||
|
||||
cdef uint8_t[:] oqpsk_bits
|
||||
if is_oqpsk:
|
||||
assert bits_per_symbol == 2
|
||||
bits = get_oqpsk_bits(bits)
|
||||
|
||||
cdef np.ndarray[np.float32_t, ndim=2] gauss_filtered_freqs_phases
|
||||
if is_gfsk:
|
||||
gauss_filtered_freqs_phases = get_gauss_filtered_freqs_phases(bits, parameters, total_symbols,
|
||||
samples_per_symbol, sample_rate, carrier_phase,
|
||||
start, gauss_bt, filter_width)
|
||||
|
||||
|
||||
cdef float* phase_corrections = NULL
|
||||
if is_fsk and total_symbols > 0:
|
||||
phase_corrections = <float*>malloc(total_symbols * sizeof(float))
|
||||
phase_corrections[0] = 0.0
|
||||
for s_i in range(1, total_symbols):
|
||||
# Add phase correction to FSK modulation in order to prevent spiky jumps
|
||||
index = bit_array_to_number(bits, end=(s_i+1)*bits_per_symbol, start=s_i*bits_per_symbol)
|
||||
prev_index = bit_array_to_number(bits, end=s_i*bits_per_symbol, start=(s_i-1)*bits_per_symbol)
|
||||
|
||||
f = parameters[index]
|
||||
f_previous = parameters[prev_index]
|
||||
|
||||
if f != f_previous:
|
||||
t = (s_i*samples_per_symbol+start-1) / sample_rate
|
||||
phase_corrections[s_i] = (phase_corrections[s_i-1] + 2 * M_PI * (f_previous-f) * t) % (2 * M_PI)
|
||||
else:
|
||||
phase_corrections[s_i] = phase_corrections[s_i-1]
|
||||
|
||||
for s_i in prange(0, total_symbols, schedule="static", nogil=True):
|
||||
index = bit_array_to_number(bits, end=(s_i+1)*bits_per_symbol, start=s_i*bits_per_symbol)
|
||||
|
||||
a = carrier_amplitude
|
||||
f = carrier_frequency
|
||||
phi = carrier_phase
|
||||
phase_correction = 0
|
||||
|
||||
if is_ask:
|
||||
a = parameters[index]
|
||||
if a == 0:
|
||||
continue
|
||||
elif is_fsk:
|
||||
f = parameters[index]
|
||||
phase_correction = phase_corrections[s_i]
|
||||
|
||||
elif is_psk or is_oqpsk:
|
||||
phi = parameters[index]
|
||||
|
||||
for i in range(s_i * samples_per_symbol, (s_i+1)*samples_per_symbol):
|
||||
t = (i+start) / sample_rate
|
||||
if is_gfsk:
|
||||
f = gauss_filtered_freqs_phases[i, 0]
|
||||
phi = gauss_filtered_freqs_phases[i, 1]
|
||||
current_arg = 2 * M_PI * f * t + phi + phase_correction
|
||||
|
||||
result_view[i, 0] = <iq>(a * cosf(current_arg))
|
||||
result_view[i, 1] = <iq>(a * sinf(current_arg))
|
||||
|
||||
if is_oqpsk:
|
||||
for i in range(0, samples_per_symbol):
|
||||
result_view[i, 1] = 0
|
||||
for i in range(total_samples-pause-samples_per_symbol, total_samples-pause):
|
||||
result_view[i, 0] = 0
|
||||
|
||||
if phase_corrections != NULL:
|
||||
free(phase_corrections)
|
||||
|
||||
return result
|
||||
|
||||
cpdef uint8_t[:] get_oqpsk_bits(uint8_t[:] original_bits):
|
||||
# TODO: This method does not work correctly. Fix it when we have a test signal
|
||||
cdef int64_t i, num_bits = len(original_bits)
|
||||
if num_bits == 0:
|
||||
return np.zeros(0, dtype=np.uint8)
|
||||
|
||||
result = np.zeros(num_bits+2, dtype=np.uint8)
|
||||
result[0] = original_bits[0]
|
||||
result[num_bits+2-1] = original_bits[num_bits-1]
|
||||
|
||||
for i in range(2, num_bits-2, 2):
|
||||
result[i] = original_bits[i]
|
||||
result[i+1] = original_bits[i-1]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
cdef np.ndarray[np.float32_t, ndim=2] get_gauss_filtered_freqs_phases(uint8_t[:] bits, float[:] parameters,
|
||||
uint32_t num_symbols, uint32_t samples_per_symbol,
|
||||
float sample_rate, float phi, uint32_t start,
|
||||
float gauss_bt, float filter_width):
|
||||
cdef int64_t i, s_i, index, num_values = num_symbols * samples_per_symbol
|
||||
cdef np.ndarray[np.float32_t, ndim=1] frequencies = np.empty(num_values, dtype=np.float32)
|
||||
cdef uint16_t bits_per_symbol = int(len(bits) // num_symbols)
|
||||
|
||||
for s_i in range(0, num_symbols):
|
||||
index = bit_array_to_number(bits, end=(s_i+1)*bits_per_symbol, start=s_i*bits_per_symbol)
|
||||
|
||||
for i in range(s_i * samples_per_symbol, (s_i+1)*samples_per_symbol):
|
||||
frequencies[i] = parameters[index]
|
||||
|
||||
cdef np.ndarray[np.float32_t, ndim=1] t = np.arange(start, start + num_values, dtype=np.float32) / sample_rate
|
||||
cdef np.ndarray[np.float32_t, ndim=1] gfir = gauss_fir(sample_rate, samples_per_symbol,
|
||||
bt=gauss_bt, filter_width=filter_width)
|
||||
|
||||
if len(frequencies) >= len(gfir):
|
||||
frequencies = np.convolve(frequencies, gfir, mode="same")
|
||||
else:
|
||||
# Prevent dimension crash later, because gaussian finite impulse response is longer then param_vector
|
||||
frequencies = np.convolve(gfir, frequencies, mode="same")[:len(frequencies)]
|
||||
|
||||
cdef np.ndarray[np.float32_t, ndim=1] phases = np.zeros(len(frequencies), dtype=np.float32)
|
||||
phases[0] = phi
|
||||
for i in range(0, len(phases) - 1):
|
||||
# Correct the phase to prevent spiky jumps
|
||||
phases[i + 1] = 2 * M_PI * t[i] * (frequencies[i] - frequencies[i + 1]) + phases[i]
|
||||
|
||||
return np.column_stack((frequencies, phases))
|
||||
|
||||
cdef np.ndarray[np.float32_t, ndim=1] gauss_fir(float sample_rate, uint32_t samples_per_symbol,
|
||||
float bt=.5, float filter_width=1.0):
|
||||
"""
|
||||
|
||||
:param filter_width: Filter width
|
||||
:param bt: normalized 3-dB bandwidth-symbol time product
|
||||
:return:
|
||||
"""
|
||||
# http://onlinelibrary.wiley.com/doi/10.1002/9780470041956.app2/pdf
|
||||
cdef np.ndarray[np.float32_t] k = np.arange(-int(filter_width * samples_per_symbol),
|
||||
int(filter_width * samples_per_symbol) + 1,
|
||||
dtype=np.float32)
|
||||
cdef float ts = samples_per_symbol / sample_rate # symbol time
|
||||
cdef np.ndarray[np.float32_t] h = np.sqrt((2 * np.pi) / (np.log(2))) * bt / ts * np.exp(
|
||||
-(((np.sqrt(2) * np.pi) / np.sqrt(np.log(2)) * bt * k / samples_per_symbol) ** 2))
|
||||
return h / h.sum()
|
||||
|
||||
cdef float clamp(float x) nogil:
|
||||
if x < -1.0:
|
||||
x = -1.0
|
||||
elif x > 1.0:
|
||||
x = 1.0
|
||||
return x
|
||||
|
||||
cdef float[::1] costa_demod(IQ samples, float noise_sqrd, int loop_order, float bandwidth=0.1, float damping=sqrt(2.0) / 2.0):
|
||||
cdef float alpha = (4 * damping * bandwidth) / (1.0 + 2.0 * damping * bandwidth + bandwidth * bandwidth)
|
||||
cdef float beta = (4 * bandwidth * bandwidth) / (1.0 + 2.0 * damping * bandwidth + bandwidth * bandwidth)
|
||||
|
||||
cdef long long i = 0, num_samples = len(samples)
|
||||
cdef float real = 0, imag = 0
|
||||
|
||||
cdef float scale, shift, real_float, imag_float, ref_real, ref_imag
|
||||
|
||||
cdef float f1, f2, costa_freq = 0, costa_error = 0, costa_phase = 1.5
|
||||
|
||||
cdef float complex current_sample, nco_out, nco_times_sample
|
||||
|
||||
cdef float[::1] result = np.empty(num_samples, dtype=np.float32)
|
||||
|
||||
if str(cython.typeof(samples)) == "char[:, ::1]":
|
||||
scale = 127.5
|
||||
shift = 0.5
|
||||
elif str(cython.typeof(samples)) == "unsigned char[:, ::1]":
|
||||
scale = 127.5
|
||||
shift = -127.5
|
||||
elif str(cython.typeof(samples)) == "short[:, ::1]":
|
||||
scale = 32767.5
|
||||
shift = 0.5
|
||||
elif str(cython.typeof(samples)) == "unsigned short[:, ::1]":
|
||||
scale = 65535.0
|
||||
shift = -32767.5
|
||||
elif str(cython.typeof(samples)) == "float[:, ::1]":
|
||||
scale = 1.0
|
||||
shift = 0.0
|
||||
else:
|
||||
raise ValueError("Unsupported dtype")
|
||||
|
||||
if loop_order > 4:
|
||||
# TODO: Adapt this when PSK demodulation with order > 4 shall be supported
|
||||
loop_order = 4
|
||||
|
||||
for i in range(1, num_samples):
|
||||
real = samples[i, 0]
|
||||
imag = samples[i, 1]
|
||||
|
||||
if real * real + imag * imag <= noise_sqrd:
|
||||
result[i] = NOISE_FSK_PSK
|
||||
continue
|
||||
|
||||
real_float = (real + shift) / scale
|
||||
imag_float = (imag + shift) / scale
|
||||
|
||||
current_sample = real_float + imag_unit * imag_float
|
||||
nco_out = cosf(-costa_phase) + imag_unit * sinf(-costa_phase)
|
||||
nco_times_sample = nco_out * current_sample
|
||||
|
||||
if loop_order == 2:
|
||||
costa_error = nco_times_sample.imag * nco_times_sample.real
|
||||
elif loop_order == 4:
|
||||
f1 = 1.0 if nco_times_sample.real > 0.0 else -1.0
|
||||
f2 = 1.0 if nco_times_sample.imag > 0.0 else -1.0
|
||||
costa_error = f1 * nco_times_sample.imag - f2 * nco_times_sample.real
|
||||
|
||||
costa_error = clamp(costa_error)
|
||||
|
||||
# advance the loop
|
||||
costa_freq += beta * costa_error
|
||||
costa_phase += costa_freq + alpha * costa_error
|
||||
|
||||
# wrap the phase
|
||||
while costa_phase > (2 * M_PI):
|
||||
costa_phase -= 2 * M_PI
|
||||
while costa_phase < (-2 * M_PI):
|
||||
costa_phase += 2 * M_PI
|
||||
|
||||
costa_freq = clamp(costa_freq)
|
||||
|
||||
if loop_order == 2:
|
||||
result[i] = nco_times_sample.real
|
||||
elif loop_order == 4:
|
||||
result[i] = 2 * nco_times_sample.real + nco_times_sample.imag
|
||||
|
||||
return result
|
||||
|
||||
|
||||
cpdef np.ndarray[np.float32_t, ndim=1] afp_demod(IQ samples, float noise_mag,
|
||||
str mod_type, int mod_order, float costas_loop_bandwidth=0.1):
|
||||
if len(samples) <= 2:
|
||||
return np.zeros(len(samples), dtype=np.float32)
|
||||
|
||||
cdef long long i = 0, ns = len(samples)
|
||||
cdef float NOISE = get_noise_for_mod_type(mod_type)
|
||||
cdef float noise_sqrd = noise_mag * noise_mag, real = 0, imag = 0, magnitude = 0, max_magnitude
|
||||
cdef float complex tmp
|
||||
|
||||
if str(cython.typeof(samples)) == "char[:, ::1]":
|
||||
max_magnitude = sqrt(127*127 + 128*128)
|
||||
elif str(cython.typeof(samples)) == "unsigned char[:, ::1]":
|
||||
max_magnitude = sqrt(255*255)
|
||||
elif str(cython.typeof(samples)) == "short[:, ::1]":
|
||||
max_magnitude = sqrt(32768*32768 + 32767*32767)
|
||||
elif str(cython.typeof(samples)) == "unsigned short[:, ::1]":
|
||||
max_magnitude = sqrt(65535*65535)
|
||||
elif str(cython.typeof(samples)) == "float[:, ::1]":
|
||||
max_magnitude = sqrt(2)
|
||||
else:
|
||||
raise ValueError("Unsupported dtype")
|
||||
|
||||
|
||||
if mod_type == "PSK":
|
||||
return np.asarray(costa_demod(samples, noise_sqrd, mod_order, bandwidth=costas_loop_bandwidth))
|
||||
|
||||
cdef float[::1] result = np.zeros(ns, dtype=np.float32, order="C")
|
||||
result[0] = NOISE
|
||||
|
||||
for i in prange(1, ns, nogil=True, schedule="static"):
|
||||
real = samples[i, 0]
|
||||
imag = samples[i, 1]
|
||||
magnitude = real * real + imag * imag
|
||||
if magnitude <= noise_sqrd: # |c| <= mag_treshold
|
||||
result[i] = NOISE
|
||||
continue
|
||||
|
||||
if mod_type == "ASK":
|
||||
result[i] = sqrt(magnitude) / max_magnitude
|
||||
elif mod_type == "FSK":
|
||||
#tmp = samples[i - 1].conjugate() * c
|
||||
tmp = (samples[i-1, 0] - imag_unit * samples[i-1, 1]) * (real + imag_unit * imag)
|
||||
result[i] = atan2(tmp.imag, tmp.real) # Freq
|
||||
|
||||
return np.asarray(result)
|
||||
|
||||
cpdef np.ndarray[np.float32_t, ndim=1] get_center_thresholds(float center, float spacing, int modulation_order):
|
||||
cdef np.ndarray[np.float32_t, ndim=1] result = np.empty(modulation_order-1, dtype=np.float32)
|
||||
cdef int i, n = modulation_order // 2
|
||||
|
||||
for i in range(0, n):
|
||||
result[i] = center - (n-(i+1)) * spacing
|
||||
|
||||
for i in range(n, modulation_order-1):
|
||||
result[i] = center + (i+1-n) * spacing
|
||||
|
||||
return result
|
||||
|
||||
cpdef int64_t[:, ::1] grab_pulse_lens(float[::1] samples, float center, uint16_t tolerance,
|
||||
str modulation_type, uint32_t samples_per_symbol,
|
||||
uint8_t bits_per_symbol=1, float center_spacing=0.1):
|
||||
"""
|
||||
Get the pulse lengths after quadrature demodulation
|
||||
|
||||
arr[i][0] gives type of symbol e.g. (arr[i][0] = 1) and (arr[i][0] = 0) for binary modulation
|
||||
Pause is (arr[i][0] = -1)
|
||||
arr[i][1] gives length of pulse
|
||||
"""
|
||||
cdef bool is_ask = modulation_type == "ASK"
|
||||
cdef int64_t i, j, pulse_length = 0, num_samples = len(samples)
|
||||
cdef int64_t cur_index = 0, consecutive_ones = 0, consecutive_zeros = 0, consecutive_pause = 0
|
||||
cdef float s = 0, s_prev = 0
|
||||
cdef int cur_state = 0, new_state = 0, tmp_state = 0
|
||||
cdef float NOISE = get_noise_for_mod_type(modulation_type)
|
||||
|
||||
cdef int modulation_order = 2**bits_per_symbol
|
||||
cdef int k
|
||||
|
||||
|
||||
cdef np.ndarray[np.float32_t, ndim=1] thresholds = get_center_thresholds(center, center_spacing, modulation_order)
|
||||
|
||||
cdef int64_t[:, ::1] result = np.zeros((num_samples, 2), dtype=np.int64, order="C")
|
||||
if num_samples == 0:
|
||||
return result
|
||||
|
||||
cdef int64_t[:] state_count = np.zeros(modulation_order, dtype=np.int64)
|
||||
|
||||
s_prev = samples[0]
|
||||
if s_prev == NOISE:
|
||||
cur_state = PAUSE_STATE
|
||||
else:
|
||||
cur_state = modulation_order - 1
|
||||
for k in range(modulation_order - 1):
|
||||
if s <= thresholds[k]:
|
||||
cur_state = k
|
||||
break
|
||||
|
||||
for i in range(num_samples):
|
||||
pulse_length += 1
|
||||
s = samples[i]
|
||||
|
||||
if s == NOISE:
|
||||
tmp_state = PAUSE_STATE
|
||||
else:
|
||||
tmp_state = modulation_order - 1
|
||||
for k in range(modulation_order - 1):
|
||||
if s <= thresholds[k]:
|
||||
tmp_state = k
|
||||
break
|
||||
|
||||
if tmp_state == PAUSE_STATE:
|
||||
consecutive_pause += 1
|
||||
else:
|
||||
consecutive_pause = 0
|
||||
|
||||
for j in range(0, modulation_order):
|
||||
if j == tmp_state:
|
||||
state_count[j] += 1
|
||||
else:
|
||||
state_count[j] = 0
|
||||
|
||||
if cur_state == tmp_state:
|
||||
continue
|
||||
|
||||
new_state = -42
|
||||
|
||||
if consecutive_pause > tolerance:
|
||||
new_state = PAUSE_STATE
|
||||
else:
|
||||
for j in range(0, modulation_order):
|
||||
if state_count[j] > tolerance:
|
||||
new_state = j
|
||||
break
|
||||
|
||||
if new_state == -42:
|
||||
continue
|
||||
|
||||
if is_ask and cur_state == PAUSE_STATE and (pulse_length - tolerance) < samples_per_symbol:
|
||||
# Aggregate short pauses for ASK
|
||||
cur_state = 0
|
||||
|
||||
if cur_index > 0 and result[cur_index - 1, 0] == cur_state:
|
||||
result[cur_index - 1, 1] += pulse_length - tolerance
|
||||
else:
|
||||
result[cur_index, 0] = cur_state
|
||||
result[cur_index, 1] = pulse_length - tolerance
|
||||
cur_index += 1
|
||||
|
||||
pulse_length = tolerance
|
||||
cur_state = new_state
|
||||
|
||||
# Append last one
|
||||
cdef int64_t len_result = len(result)
|
||||
if cur_index < len_result:
|
||||
if cur_index > 0 and result[cur_index - 1, 0] == cur_state:
|
||||
result[cur_index - 1, 1] += pulse_length - tolerance
|
||||
else:
|
||||
result[cur_index, 0] = cur_state
|
||||
result[cur_index, 1] = pulse_length - tolerance
|
||||
cur_index += 1
|
||||
|
||||
return result[:cur_index]
|
||||
|
||||
cpdef int find_nearest_center(float sample, float[::1] centers, int num_centers) nogil:
|
||||
cdef int i = 0
|
||||
cdef float center = 0
|
||||
cdef int result = 0
|
||||
cdef float min_diff = 99999
|
||||
cdef float cur_diff = 0
|
||||
|
||||
for i in range(0, num_centers):
|
||||
center = centers[i]
|
||||
cur_diff = (sample - center) * (sample - center)
|
||||
if cur_diff < min_diff:
|
||||
min_diff = cur_diff
|
||||
result = i
|
||||
|
||||
return result
|
||||
|
||||
cpdef np.ndarray[np.complex64_t, ndim=1] fir_filter(float complex[::1] input_samples, float complex[::1] filter_taps):
|
||||
cdef int i = 0, j = 0
|
||||
cdef int N = len(input_samples)
|
||||
cdef int M = len(filter_taps)
|
||||
cdef np.ndarray[np.complex64_t, ndim=1] output = np.zeros(N+M-1, dtype=np.complex64)
|
||||
|
||||
|
||||
for i in range(N):
|
||||
for j in range(M):
|
||||
output[i+j] += input_samples[i] * filter_taps[j]
|
||||
|
||||
|
||||
return output[:N]
|
||||
|
||||
cpdef np.ndarray[np.complex64_t, ndim=1] iir_filter(np.ndarray[np.float64_t, ndim=1] a,
|
||||
np.ndarray[np.float64_t, ndim=1] b,
|
||||
np.ndarray[np.complex64_t, ndim=1] signal):
|
||||
cdef np.ndarray[np.complex64_t, ndim=1] result = np.zeros(len(signal), dtype=np.complex64)
|
||||
|
||||
cdef long n = 0, j = 0, k = 0
|
||||
cdef long M = len(a)
|
||||
cdef long N = len(b)
|
||||
for n in range(max(M, N+1) , len(signal)):
|
||||
for j in range(M):
|
||||
result[n] += a[j] * signal[n-j]
|
||||
|
||||
for k in range(N):
|
||||
result[n] += b[k] * result[n-1-k]
|
||||
|
||||
return result
|
||||
12
Software/Universal Radio Hacker/src/urh/cythonext/util.pxd
Normal file
12
Software/Universal Radio Hacker/src/urh/cythonext/util.pxd
Normal file
@@ -0,0 +1,12 @@
|
||||
ctypedef fused iq:
|
||||
char
|
||||
unsigned char
|
||||
short
|
||||
unsigned short
|
||||
float
|
||||
|
||||
ctypedef iq[:, ::1] IQ
|
||||
|
||||
from libc.stdint cimport uint64_t, uint8_t, int64_t
|
||||
|
||||
cpdef uint64_t bit_array_to_number(uint8_t[:] bits, int64_t end, int64_t start=*) nogil
|
||||
340
Software/Universal Radio Hacker/src/urh/cythonext/util.pyx
Normal file
340
Software/Universal Radio Hacker/src/urh/cythonext/util.pyx
Normal file
@@ -0,0 +1,340 @@
|
||||
# noinspection PyUnresolvedReferences
|
||||
cimport numpy as np
|
||||
import numpy as np
|
||||
|
||||
# As we do not use any numpy C API functions we do no import_array here,
|
||||
# because it can lead to OS X error: https://github.com/jopohl/urh/issues/273
|
||||
# np.import_array()
|
||||
|
||||
from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, int64_t
|
||||
from libc.stdlib cimport malloc, calloc, free
|
||||
from cython.parallel import prange
|
||||
from libc.math cimport log10,pow,sqrt
|
||||
from libcpp cimport bool
|
||||
|
||||
from cpython cimport array
|
||||
import array
|
||||
|
||||
from urh.cythonext.util cimport iq
|
||||
|
||||
cpdef tuple minmax(iq[:] arr):
|
||||
cdef long long i, ns = len(arr)
|
||||
if ns == 0:
|
||||
return 0, 0
|
||||
|
||||
cdef iq maximum = arr[0]
|
||||
cdef iq minimum = arr[0]
|
||||
cdef iq e
|
||||
|
||||
for i in range(1, ns):
|
||||
e = arr[i]
|
||||
if e > maximum:
|
||||
maximum = e
|
||||
if e < minimum:
|
||||
minimum = e
|
||||
|
||||
return minimum, maximum
|
||||
|
||||
cpdef np.ndarray[np.float32_t, ndim=2] arr2decibel(np.ndarray[np.complex64_t, ndim=2] arr):
|
||||
cdef long long x = arr.shape[0]
|
||||
cdef long long y = arr.shape[1]
|
||||
cdef long long i, j = 0
|
||||
cdef np.ndarray[np.float32_t, ndim=2] result = np.empty((x,y), dtype=np.float32)
|
||||
cdef np.float32_t factor = 10.0
|
||||
|
||||
for i in prange(x, nogil=True, schedule='static'):
|
||||
for j in range(y):
|
||||
result[i, j] = factor * log10(arr[i, j].real * arr[i, j].real + arr[i, j].imag * arr[i, j].imag)
|
||||
return result
|
||||
|
||||
cpdef uint64_t bit_array_to_number(uint8_t[:] bits, int64_t end, int64_t start=0) nogil:
|
||||
if end < 1:
|
||||
return 0
|
||||
|
||||
cdef long long i, acc = 1
|
||||
cdef unsigned long long result = 0
|
||||
|
||||
for i in range(start, end):
|
||||
result += bits[end-1-i+start] * acc
|
||||
acc *= 2
|
||||
|
||||
return result
|
||||
|
||||
cpdef uint64_t arr_to_number(uint8_t[:] inpt, bool reverse = False, unsigned int start = 0):
|
||||
cdef uint64_t result = 0
|
||||
cdef unsigned int i, len_inpt = len(inpt)
|
||||
for i in range(start, len_inpt):
|
||||
if not reverse:
|
||||
if inpt[len_inpt - 1 - i + start]:
|
||||
result |= (1 << (i-start))
|
||||
else:
|
||||
if inpt[i]:
|
||||
result |= (1 << (i-start))
|
||||
return result
|
||||
|
||||
cpdef uint64_t crc(uint8_t[:] inpt, uint8_t[:] polynomial, uint8_t[:] start_value, uint8_t[:] final_xor, bool lsb_first, bool reverse_polynomial, bool reverse_all, bool little_endian):
|
||||
cdef unsigned int len_inpt = len(inpt)
|
||||
cdef unsigned int i, idx, poly_order = len(polynomial)
|
||||
cdef uint64_t crc_mask = <uint64_t> pow(2, poly_order - 1) - 1
|
||||
cdef uint64_t poly_mask = (crc_mask + 1) >> 1
|
||||
cdef uint64_t poly_int = arr_to_number(polynomial, reverse_polynomial, 1) & crc_mask
|
||||
cdef unsigned short j, x
|
||||
|
||||
# start value
|
||||
cdef uint64_t temp, crc = arr_to_number(start_value, False, 0) & crc_mask
|
||||
|
||||
for i in range(0, len_inpt+7, 8):
|
||||
for j in range(0, 8):
|
||||
if lsb_first:
|
||||
idx = i + (7 - j)
|
||||
else:
|
||||
idx = i + j
|
||||
|
||||
# generic crc algorithm
|
||||
if idx >= len_inpt:
|
||||
break
|
||||
|
||||
if (crc & poly_mask > 0) != inpt[idx]:
|
||||
crc = (crc << 1) & crc_mask
|
||||
crc ^= poly_int
|
||||
else:
|
||||
crc = (crc << 1) & crc_mask
|
||||
|
||||
# final XOR
|
||||
crc ^= arr_to_number(final_xor, False, 0) & crc_mask
|
||||
|
||||
# reverse all bits
|
||||
if reverse_all:
|
||||
temp = 0
|
||||
for i in range(0, poly_order - 1):
|
||||
if crc & (1 << i):
|
||||
temp |= (1 << (poly_order - 2 - i))
|
||||
crc = temp & crc_mask
|
||||
|
||||
# little endian encoding, different for 16, 32, 64 bit
|
||||
if poly_order - 1 == 16 and little_endian:
|
||||
crc = ((crc << 8) & 0xFF00) | (crc >> 8)
|
||||
elif poly_order - 1 == 32 and little_endian:
|
||||
crc = ((crc << 24) & <uint64_t>0xFF000000) | ((crc << 8) & 0x00FF0000) | ((crc >> 8) & 0x0000FF00) | (crc >> 24)
|
||||
elif poly_order - 1 == 64 and little_endian:
|
||||
crc = ((crc << 56) & <uint64_t>0xFF00000000000000) | (crc >> 56) \
|
||||
| ((crc >> 40) & <uint64_t>0x000000000000FF00) | ((crc << 40) & <uint64_t>0x00FF000000000000) \
|
||||
| ((crc << 24) & <uint64_t>0x0000FF0000000000) | ((crc >> 24) & <uint64_t>0x0000000000FF0000) \
|
||||
| ((crc << 8) & <uint64_t>0x000000FF00000000) | ((crc >> 8) & <uint64_t>0x00000000FF000000)
|
||||
|
||||
return crc & crc_mask
|
||||
|
||||
|
||||
cpdef np.ndarray[np.double_t, ndim=1] get_magnitudes(IQ arr):
|
||||
cdef uint64_t i, n = len(arr)
|
||||
|
||||
cdef np.ndarray[np.double_t, ndim=1] result = np.zeros(n, dtype = np.double)
|
||||
|
||||
for i in range(0, n):
|
||||
result[i] = sqrt(arr[i][0] * arr[i][0] + arr[i][1] * arr[i][1])
|
||||
|
||||
return result
|
||||
|
||||
cpdef np.ndarray[np.uint64_t, ndim=1] calculate_cache(uint8_t[:] polynomial, bool reverse_polynomial=False, uint8_t bits=8):
|
||||
cdef uint8_t j, poly_order = len(polynomial)
|
||||
cdef uint64_t crc_mask = <uint64_t> pow(2, poly_order - 1) - 1
|
||||
cdef uint64_t poly_mask = (crc_mask + 1) >> 1
|
||||
cdef uint64_t poly_int = arr_to_number(polynomial, reverse_polynomial, 1) & crc_mask
|
||||
cdef uint64_t crcv, i
|
||||
cdef np.ndarray[np.uint64_t, ndim=1] cache = np.zeros(<uint64_t> pow(2, bits), dtype = np.uint64)
|
||||
# Caching
|
||||
for i in range(0, <uint32_t> len(cache)):
|
||||
crcv = i << (poly_order - 1 - bits)
|
||||
for _ in range(0, bits):
|
||||
if (crcv & poly_mask) > 0:
|
||||
crcv = (crcv << 1) & crc_mask
|
||||
crcv ^= poly_int
|
||||
else:
|
||||
crcv = (crcv << 1) & crc_mask
|
||||
cache[i] = crcv
|
||||
return cache
|
||||
|
||||
cpdef uint64_t cached_crc(uint64_t[:] cache, uint8_t bits, uint8_t[:] inpt, uint8_t[:] polynomial, uint8_t[:] start_value, uint8_t[:] final_xor, bool lsb_first, bool reverse_polynomial, bool reverse_all, bool little_endian):
|
||||
cdef unsigned int len_inpt = len(inpt)
|
||||
cdef unsigned int i, poly_order = len(polynomial)
|
||||
cdef uint64_t crc_mask = <uint64_t> pow(2, poly_order - 1) - 1
|
||||
cdef uint64_t poly_mask = (crc_mask + 1) >> 1
|
||||
cdef uint64_t poly_int = arr_to_number(polynomial, reverse_polynomial, 1) & crc_mask
|
||||
cdef uint64_t temp, crcv, data, pos
|
||||
cdef uint8_t j
|
||||
|
||||
# For inputs smaller than 8 bits, call normal function
|
||||
if len_inpt < bits:
|
||||
return crc(inpt, polynomial, start_value, final_xor, lsb_first, reverse_polynomial, reverse_all, little_endian)
|
||||
|
||||
# CRC
|
||||
crcv = arr_to_number(start_value, False, 0) & crc_mask
|
||||
for i in range(0, len_inpt - bits + 1, bits):
|
||||
data = 0
|
||||
if lsb_first:
|
||||
for j in range(0, bits):
|
||||
if inpt[i + j]:
|
||||
data |= (1 << j)
|
||||
else:
|
||||
for j in range(0, bits):
|
||||
if inpt[i + bits - 1 - j]:
|
||||
data |= (1 << j)
|
||||
pos = (crcv >> (poly_order - bits - 1)) ^ data
|
||||
crcv = ((crcv << bits) ^ cache[pos]) & crc_mask
|
||||
|
||||
# Are we done?
|
||||
if len_inpt % bits > 0:
|
||||
# compute rest of crc inpt[-(len_inpt%8):] with normal function
|
||||
# Set start_value to current crc value
|
||||
for i in range(0, len(start_value)):
|
||||
start_value[len(start_value) - 1 - i] = True if (crcv & (1 << i)) > 0 else False
|
||||
crcv = crc(inpt[len_inpt-(len_inpt%bits):len_inpt], polynomial, start_value, final_xor, lsb_first, reverse_polynomial, reverse_all, little_endian)
|
||||
else:
|
||||
# final XOR
|
||||
crcv ^= arr_to_number(final_xor, False, 0) & crc_mask
|
||||
|
||||
# reverse all bits
|
||||
if reverse_all:
|
||||
temp = 0
|
||||
for i in range(0, poly_order - 1):
|
||||
if crcv & (1 << i):
|
||||
temp |= (1 << (poly_order - 2 - i))
|
||||
crcv = temp & crc_mask
|
||||
|
||||
# little endian encoding, different for 16, 32, 64 bit
|
||||
if poly_order - 1 == 16 and little_endian:
|
||||
crcv = ((crcv << 8) & 0xFF00) | (crcv >> 8)
|
||||
elif poly_order - 1 == 32 and little_endian:
|
||||
crcv = ((crcv << 24) & <uint64_t>0xFF000000) | ((crcv << 8) & 0x00FF0000) | ((crcv >> 8) & 0x0000FF00) | (crcv >> 24)
|
||||
elif poly_order - 1 == 64 and little_endian:
|
||||
crcv = ((crcv << 56) & <uint64_t>0xFF00000000000000) | (crcv >> 56) \
|
||||
| ((crcv >> 40) & <uint64_t>0x000000000000FF00) | ((crcv << 40) & <uint64_t>0x00FF000000000000) \
|
||||
| ((crcv << 24) & <uint64_t>0x0000FF0000000000) | ((crcv >> 24) & <uint64_t>0x0000000000FF0000) \
|
||||
| ((crcv << 8) & <uint64_t>0x000000FF00000000) | ((crcv >> 8) & <uint64_t>0x00000000FF000000)
|
||||
return crcv & crc_mask
|
||||
|
||||
cpdef tuple get_crc_datarange(uint8_t[:] inpt, uint8_t[:] polynomial, uint64_t vrfy_crc_start, uint8_t[:] start_value, uint8_t[:] final_xor, bool lsb_first, bool reverse_polynomial, bool reverse_all, bool little_endian):
|
||||
cdef uint32_t len_inpt = len(inpt), poly_order = len(polynomial)
|
||||
cdef uint8_t j = 0, len_crc = poly_order - 1
|
||||
|
||||
if vrfy_crc_start-1+len_crc >= len_inpt or vrfy_crc_start < 2:
|
||||
return 0, 0
|
||||
|
||||
cdef uint64_t* steps = <uint64_t*>calloc(len_inpt+2, sizeof(uint64_t))
|
||||
cdef uint64_t temp
|
||||
cdef uint64_t crc_mask = <uint64_t> pow(2, poly_order - 1) - 1
|
||||
cdef uint64_t poly_mask = (crc_mask + 1) >> 1
|
||||
cdef uint64_t poly_int = arr_to_number(polynomial, reverse_polynomial, 1) & crc_mask
|
||||
cdef uint64_t final_xor_int = arr_to_number(final_xor, False, 0) & crc_mask
|
||||
cdef uint64_t vrfy_crc_int = arr_to_number(inpt[vrfy_crc_start:vrfy_crc_start+len_crc], False, 0) & crc_mask
|
||||
cdef uint64_t crcvalue = arr_to_number(start_value, False, 0) & crc_mask
|
||||
cdef bool found
|
||||
cdef uint32_t i, idx, offset, data_end = vrfy_crc_start
|
||||
cdef uint8_t* step = <uint8_t*>calloc(len_inpt, sizeof(uint8_t))
|
||||
step[0] = 1
|
||||
|
||||
# crcvalue is initialized with start_value
|
||||
for i in range(0, data_end+7, 8):
|
||||
for j in range(0, 8):
|
||||
if lsb_first:
|
||||
idx = i + (7 - j)
|
||||
else:
|
||||
idx = i + j
|
||||
|
||||
# generic crc algorithm
|
||||
if idx >= data_end:
|
||||
break
|
||||
|
||||
if (crcvalue & poly_mask > 0) != step[idx]:
|
||||
crcvalue = (crcvalue << 1) & crc_mask
|
||||
crcvalue ^= poly_int
|
||||
else:
|
||||
crcvalue = (crcvalue << 1) & crc_mask
|
||||
# Save steps XORed with final_xor
|
||||
steps[idx] = crcvalue ^ final_xor_int
|
||||
|
||||
free(step)
|
||||
|
||||
# Reverse and little endian
|
||||
if reverse_all or little_endian:
|
||||
for i in range(0, data_end):
|
||||
# reverse all bits
|
||||
if reverse_all:
|
||||
temp = 0
|
||||
for j in range(0, poly_order - 1):
|
||||
if steps[i] & (1 << j):
|
||||
temp |= (1 << (poly_order -2 - j))
|
||||
steps[j] = temp & crc_mask
|
||||
|
||||
# little endian encoding, different for 16, 32, 64 bit
|
||||
if poly_order - 1 == 16 and little_endian:
|
||||
steps[i] = ((steps[i] << 8) & <uint64_t> 0xFF00) | (steps[i] >> 8)
|
||||
elif poly_order - 1 == 32 and little_endian:
|
||||
steps[i] = ((steps[i] << 24) & <uint64_t> 0xFF000000) | ((steps[i] << 8) & <uint64_t> 0x00FF0000) | ((steps[i] >> 8) & <uint64_t> 0x0000FF00) | (steps[i] >> 24)
|
||||
elif poly_order - 1 == 64 and little_endian:
|
||||
steps[i] = ((steps[i] << 56) & <uint64_t> 0xFF00000000000000) | (steps[i] >> 56) \
|
||||
| ((steps[i] >> 40) & <uint64_t> 0x000000000000FF00) | ((steps[i] << 40) & <uint64_t> 0x00FF000000000000) \
|
||||
| ((steps[i] << 24) & <uint64_t> 0x0000FF0000000000) | ((steps[i] >> 24) & <uint64_t> 0x0000000000FF0000) \
|
||||
| ((steps[i] << 8) & <uint64_t> 0x000000FF00000000) | ((steps[i] >> 8) & <uint64_t> 0x00000000FF000000)
|
||||
|
||||
# Test data range from 0...start_crc until start_crc-1...start_crc
|
||||
# Compute start value
|
||||
crcvalue = crc(inpt[:data_end], polynomial, start_value, final_xor, lsb_first, reverse_polynomial, reverse_all, little_endian)
|
||||
try:
|
||||
if vrfy_crc_int == crcvalue:
|
||||
return 0, data_end
|
||||
found = False
|
||||
|
||||
i = 0
|
||||
while i < data_end - 1:
|
||||
offset = 0
|
||||
while inpt[i + offset] == False and i+offset < data_end - 1: # skip leading 0s in data (doesn't change crc...)
|
||||
offset += 1
|
||||
# XOR delta=crc(10000...) to last crc value to create next crc value
|
||||
crcvalue ^= steps[data_end-i-offset-1]
|
||||
if found:
|
||||
return i, data_end # Return start_data, end_data
|
||||
if vrfy_crc_int == crcvalue:
|
||||
found = True
|
||||
i += 1 + offset
|
||||
|
||||
# No beginning found
|
||||
return 0, 0
|
||||
finally:
|
||||
free(steps)
|
||||
|
||||
cdef db(unsigned int t, unsigned int p, unsigned int k, unsigned int n,
|
||||
uint8_t* a, uint8_t* sequence, uint64_t* current_index):
|
||||
cdef unsigned int i,j
|
||||
|
||||
if t > n:
|
||||
if n % p == 0:
|
||||
for i in range(1, p+1):
|
||||
sequence[current_index[0]] = a[i]
|
||||
current_index[0] += 1
|
||||
else:
|
||||
a[t] = a[t - p]
|
||||
db(t + 1, p, k, n, a, sequence, current_index)
|
||||
for j in range(a[t - p] + 1, k):
|
||||
a[t] = j
|
||||
db(t+1, t, k, n, a, sequence, current_index)
|
||||
|
||||
cpdef array.array de_bruijn(unsigned int n):
|
||||
cdef unsigned int k = 2 # Alphabet size is 2 because our alphabet is [0, 1]
|
||||
cdef uint64_t len_sequence = k ** n
|
||||
|
||||
cdef uint8_t* a = <uint8_t*>calloc(k*n, sizeof(uint8_t))
|
||||
|
||||
cdef array.array array_template = array.array('B', [])
|
||||
cdef array.array sequence
|
||||
sequence = array.clone(array_template, len_sequence, zero=False)
|
||||
|
||||
cdef uint64_t* current_index = <uint64_t*>calloc(1, sizeof(uint64_t))
|
||||
|
||||
db(1, 1, k, n, a, sequence.data.as_uchars, current_index)
|
||||
|
||||
try:
|
||||
return sequence
|
||||
finally:
|
||||
free(a)
|
||||
free(current_index)
|
||||
313
Software/Universal Radio Hacker/src/urh/dev/BackendHandler.py
Normal file
313
Software/Universal Radio Hacker/src/urh/dev/BackendHandler.py
Normal file
@@ -0,0 +1,313 @@
|
||||
import os
|
||||
import sys
|
||||
from enum import Enum
|
||||
from subprocess import call, DEVNULL
|
||||
|
||||
from urh import settings
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class Backends(Enum):
|
||||
none = "no available backend"
|
||||
native = "native backend"
|
||||
grc = "GNU Radio backend"
|
||||
network = "Network Backend" # provided by network sdr plugin
|
||||
|
||||
|
||||
class BackendContainer(object):
|
||||
def __init__(self, name, avail_backends: set, supports_rx: bool, supports_tx: bool):
|
||||
self.name = name
|
||||
self.avail_backends = avail_backends
|
||||
self.selected_backend = Backends[settings.read(name + "_selected_backend", "none")]
|
||||
if self.selected_backend not in self.avail_backends:
|
||||
self.selected_backend = Backends.none
|
||||
|
||||
if self.selected_backend == Backends.none:
|
||||
if Backends.native in self.avail_backends:
|
||||
self.selected_backend = Backends.native
|
||||
elif Backends.grc in self.avail_backends:
|
||||
self.selected_backend = Backends.grc
|
||||
|
||||
self.is_enabled = settings.read(name + "_is_enabled", True, bool)
|
||||
self.__supports_rx = supports_rx
|
||||
self.__supports_tx = supports_tx
|
||||
if len(self.avail_backends) == 0:
|
||||
self.is_enabled = False
|
||||
|
||||
def __repr__(self):
|
||||
return "avail backends: " + str(self.avail_backends) + "| selected backend:" + str(self.selected_backend)
|
||||
|
||||
@property
|
||||
def supports_rx(self) -> bool:
|
||||
return self.__supports_rx
|
||||
|
||||
@property
|
||||
def supports_tx(self) -> bool:
|
||||
return self.__supports_tx
|
||||
|
||||
@property
|
||||
def has_gnuradio_backend(self):
|
||||
return Backends.grc in self.avail_backends
|
||||
|
||||
@property
|
||||
def has_native_backend(self):
|
||||
return Backends.native in self.avail_backends
|
||||
|
||||
def set_enabled(self, enabled: bool):
|
||||
self.is_enabled = enabled
|
||||
self.write_settings()
|
||||
|
||||
def set_selected_backend(self, sel_backend: Backends):
|
||||
self.selected_backend = sel_backend
|
||||
self.write_settings()
|
||||
|
||||
def write_settings(self):
|
||||
settings.write(self.name + "_is_enabled", self.is_enabled)
|
||||
|
||||
if self.selected_backend == Backends.grc and len(self.avail_backends) == 1:
|
||||
# if GNU Radio is the only backend available we do not save it to ini,
|
||||
# in order to auto enable native backend if a native extension is built afterwards
|
||||
# see: https://github.com/jopohl/urh/issues/270
|
||||
pass
|
||||
else:
|
||||
settings.write(self.name + "_selected_backend", self.selected_backend.name)
|
||||
|
||||
|
||||
class BackendHandler(object):
|
||||
"""
|
||||
This class controls the devices backend.
|
||||
1) List available backends for devices
|
||||
2) List available devices (at least one backend)
|
||||
3) Manage the selection of devices backend
|
||||
|
||||
"""
|
||||
DEVICE_NAMES = ("AirSpy R2", "AirSpy Mini", "BladeRF", "FUNcube", "HackRF",
|
||||
"LimeSDR", "PlutoSDR", "RTL-SDR", "RTL-TCP", "SDRPlay", "SoundCard", "USRP")
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.__gr_python_interpreter = settings.read('gr_python_interpreter', '')
|
||||
if not self.__gr_python_interpreter:
|
||||
self.__gr_python_interpreter = settings.read("python2_exe", '') # legacy
|
||||
|
||||
self.set_gnuradio_installed_status()
|
||||
|
||||
if not hasattr(sys, 'frozen'):
|
||||
self.path = os.path.dirname(os.path.realpath(__file__))
|
||||
else:
|
||||
self.path = os.path.dirname(sys.executable)
|
||||
|
||||
self.device_backends = {}
|
||||
""":type: dict[str, BackendContainer] """
|
||||
|
||||
self.get_backends()
|
||||
|
||||
@property
|
||||
def gr_python_interpreter(self):
|
||||
return self.__gr_python_interpreter
|
||||
|
||||
@gr_python_interpreter.setter
|
||||
def gr_python_interpreter(self, value):
|
||||
if value != self.__gr_python_interpreter:
|
||||
self.__gr_python_interpreter = value
|
||||
self.set_gnuradio_installed_status(force=True)
|
||||
settings.write("gr_python_interpreter", value)
|
||||
|
||||
@property
|
||||
def num_native_backends(self):
|
||||
return len([dev for dev, backend_container in self.device_backends.items()
|
||||
if Backends.native in backend_container.avail_backends and dev.lower() != "rtl-tcp"])
|
||||
|
||||
@property
|
||||
def __plutosdr_native_enabled(self) -> bool:
|
||||
try:
|
||||
from urh.dev.native.lib import plutosdr
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def __bladerf_native_enabled(self) -> bool:
|
||||
try:
|
||||
from urh.dev.native.lib import bladerf
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def __hackrf_native_enabled(self) -> bool:
|
||||
try:
|
||||
from urh.dev.native.lib import hackrf
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def __usrp_native_enabled(self) -> bool:
|
||||
old_stdout = devnull = None
|
||||
try:
|
||||
try:
|
||||
# Redirect stderr to /dev/null to hide USRP messages
|
||||
devnull = open(os.devnull, 'w')
|
||||
old_stdout = os.dup(sys.stdout.fileno())
|
||||
os.dup2(devnull.fileno(), sys.stdout.fileno())
|
||||
except:
|
||||
pass
|
||||
|
||||
from urh.dev.native.lib import usrp
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
finally:
|
||||
if old_stdout is not None:
|
||||
os.dup2(old_stdout, sys.stdout.fileno())
|
||||
if devnull is not None:
|
||||
devnull.close()
|
||||
|
||||
@property
|
||||
def __soundcard_enabled(self) -> bool:
|
||||
try:
|
||||
import pyaudio
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def __airspy_native_enabled(self) -> bool:
|
||||
try:
|
||||
from urh.dev.native.lib import airspy
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def __lime_native_enabled(self) -> bool:
|
||||
try:
|
||||
from urh.dev.native.lib import limesdr
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def __rtlsdr_native_enabled(self) -> bool:
|
||||
try:
|
||||
from urh.dev.native.lib import rtlsdr
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def __sdrplay_native_enabled(self) -> bool:
|
||||
try:
|
||||
from urh.dev.native.lib import sdrplay
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def __check_gr_python_interpreter(self, interpreter):
|
||||
# Use shell=True to prevent console window popping up on windows
|
||||
return call('"{0}" -c "import gnuradio"'.format(interpreter), shell=True, stderr=DEVNULL) == 0
|
||||
|
||||
def set_gnuradio_installed_status(self, force=False):
|
||||
current_setting = settings.read('gnuradio_is_installed', -1, int)
|
||||
if not force and current_setting != -1:
|
||||
self.gnuradio_is_installed = bool(current_setting)
|
||||
return
|
||||
|
||||
if os.path.isfile(self.gr_python_interpreter) and os.access(self.gr_python_interpreter, os.X_OK):
|
||||
try:
|
||||
self.gnuradio_is_installed = self.__check_gr_python_interpreter(self.gr_python_interpreter)
|
||||
except OSError:
|
||||
self.gnuradio_is_installed = False
|
||||
else:
|
||||
self.gnuradio_is_installed = False
|
||||
|
||||
settings.write("gnuradio_is_installed", int(self.gnuradio_is_installed))
|
||||
|
||||
def __device_has_gr_scripts(self, devname: str):
|
||||
if not hasattr(sys, "frozen"):
|
||||
script_path = os.path.join(self.path, "gr", "scripts")
|
||||
else:
|
||||
script_path = self.path
|
||||
devname = devname.lower().split(" ")[0]
|
||||
has_send_file = False
|
||||
has_recv_file = False
|
||||
for f in os.listdir(script_path):
|
||||
if f == "{0}_send.py".format(devname):
|
||||
has_send_file = True
|
||||
elif f == "{0}_recv.py".format(devname):
|
||||
has_recv_file = True
|
||||
|
||||
return has_recv_file, has_send_file
|
||||
|
||||
def __avail_backends_for_device(self, devname: str):
|
||||
backends = set()
|
||||
supports_rx, supports_tx = self.__device_has_gr_scripts(devname)
|
||||
if self.gnuradio_is_installed and (supports_rx or supports_tx):
|
||||
backends.add(Backends.grc)
|
||||
|
||||
if devname.lower() == "plutosdr" and self.__plutosdr_native_enabled:
|
||||
supports_rx, supports_tx = True, True
|
||||
backends.add(Backends.native)
|
||||
|
||||
if devname.lower() == "bladerf" and self.__bladerf_native_enabled:
|
||||
supports_rx, supports_tx = True, True
|
||||
backends.add(Backends.native)
|
||||
|
||||
if devname.lower() == "hackrf" and self.__hackrf_native_enabled:
|
||||
supports_rx, supports_tx = True, True
|
||||
backends.add(Backends.native)
|
||||
|
||||
if devname.lower() == "usrp" and self.__usrp_native_enabled:
|
||||
supports_rx, supports_tx = True, True
|
||||
backends.add(Backends.native)
|
||||
|
||||
if devname.lower() == "limesdr" and self.__lime_native_enabled:
|
||||
supports_rx, supports_tx = True, True
|
||||
backends.add(Backends.native)
|
||||
|
||||
if devname.lower().startswith("airspy") and self.__airspy_native_enabled:
|
||||
supports_rx, supports_tx = True, False
|
||||
backends.add(Backends.native)
|
||||
|
||||
if devname.lower().replace("-", "") == "rtlsdr" and self.__rtlsdr_native_enabled:
|
||||
supports_rx, supports_tx = True, False
|
||||
backends.add(Backends.native)
|
||||
|
||||
if devname.lower().replace("-", "") == "rtltcp":
|
||||
supports_rx, supports_tx = True, False
|
||||
backends.add(Backends.native)
|
||||
|
||||
if devname.lower() == "sdrplay" and self.__sdrplay_native_enabled:
|
||||
supports_rx, supports_tx = True, False
|
||||
backends.add(Backends.native)
|
||||
|
||||
if devname.lower() == "soundcard" and self.__soundcard_enabled:
|
||||
supports_rx, supports_tx = True, True
|
||||
backends.add(Backends.native)
|
||||
|
||||
return backends, supports_rx, supports_tx
|
||||
|
||||
def get_backends(self):
|
||||
self.device_backends.clear()
|
||||
for device_name in self.DEVICE_NAMES:
|
||||
ab, rx_suprt, tx_suprt = self.__avail_backends_for_device(device_name)
|
||||
container = BackendContainer(device_name.lower(), ab, rx_suprt, tx_suprt)
|
||||
self.device_backends[device_name.lower()] = container
|
||||
|
||||
def get_key_from_device_display_text(self, displayed_device_name):
|
||||
displayed_device_name = displayed_device_name.lower()
|
||||
for key in self.DEVICE_NAMES:
|
||||
key = key.lower()
|
||||
if displayed_device_name.startswith(key):
|
||||
return key
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def perform_soundcard_health_check():
|
||||
result = "SoundCard -- "
|
||||
try:
|
||||
import pyaudio
|
||||
return result + "OK"
|
||||
except Exception as e:
|
||||
return result + str(e)
|
||||
75
Software/Universal Radio Hacker/src/urh/dev/EndlessSender.py
Normal file
75
Software/Universal Radio Hacker/src/urh/dev/EndlessSender.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import numpy as np
|
||||
|
||||
from urh import settings
|
||||
from urh.dev.VirtualDevice import Mode, VirtualDevice
|
||||
from urh.util.RingBuffer import RingBuffer
|
||||
|
||||
|
||||
class EndlessSender(object):
|
||||
"""
|
||||
Enter endless send mode for a device and send data if data gets pushed to ringbuffer.
|
||||
"""
|
||||
|
||||
def __init__(self, backend_handler, name: str):
|
||||
self.__device = VirtualDevice(backend_handler=backend_handler, name=name, mode=Mode.send)
|
||||
self.ringbuffer = RingBuffer(int(settings.CONTINUOUS_BUFFER_SIZE_MB * 10 ** 6) // 8, self.__device.data_type)
|
||||
self.__device.continuous_send_ring_buffer = self.ringbuffer
|
||||
self.__device.is_send_continuous = True
|
||||
|
||||
@property
|
||||
def device(self) -> VirtualDevice:
|
||||
return self.__device
|
||||
|
||||
@device.setter
|
||||
def device(self, value: VirtualDevice):
|
||||
self.__device = value
|
||||
self.__device.is_send_continuous = True
|
||||
self.ringbuffer = RingBuffer(int(settings.CONTINUOUS_BUFFER_SIZE_MB * 10 ** 6) // 8, self.__device.data_type)
|
||||
self.__device.continuous_send_ring_buffer = self.ringbuffer
|
||||
|
||||
@property
|
||||
def device_name(self) -> str:
|
||||
return self.device.name
|
||||
|
||||
@device_name.setter
|
||||
def device_name(self, value: str):
|
||||
if value != self.device_name:
|
||||
self.device = VirtualDevice(backend_handler=self.device.backend_handler, name=value, mode=Mode.send)
|
||||
|
||||
def start(self):
|
||||
self.device.num_sending_repeats = 0
|
||||
self.device.start()
|
||||
|
||||
def stop(self):
|
||||
self.device.stop("EndlessSender stopped.")
|
||||
|
||||
def push_data(self, data: np.ndarray):
|
||||
self.ringbuffer.push(data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from urh.dev.BackendHandler import BackendHandler
|
||||
from urh.signalprocessing.Message import Message
|
||||
from urh.signalprocessing.MessageType import MessageType
|
||||
from urh.signalprocessing.Modulator import Modulator
|
||||
from urh.util.Logger import logger
|
||||
import time
|
||||
|
||||
endless_sender = EndlessSender(BackendHandler(), "HackRF")
|
||||
msg = Message([1, 0] * 16 + [1, 1, 0, 0] * 8 + [0, 0, 1, 1] * 8 + [1, 0, 1, 1, 1, 0, 0, 1, 1, 1] * 4, 0,
|
||||
MessageType("empty_message_type"))
|
||||
modulator = Modulator("test_modulator")
|
||||
modulator.samples_per_symbol = 1000
|
||||
modulator.carrier_freq_hz = 55e3
|
||||
|
||||
logger.debug("Starting endless sender")
|
||||
endless_sender.start()
|
||||
time.sleep(1)
|
||||
logger.debug("Pushing data")
|
||||
endless_sender.push_data(modulator.modulate(msg.encoded_bits))
|
||||
logger.debug("Pushed data")
|
||||
time.sleep(5)
|
||||
logger.debug("Stopping endless sender")
|
||||
endless_sender.stop()
|
||||
time.sleep(1)
|
||||
logger.debug("bye")
|
||||
67
Software/Universal Radio Hacker/src/urh/dev/PCAP.py
Normal file
67
Software/Universal Radio Hacker/src/urh/dev/PCAP.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import os
|
||||
import struct
|
||||
import time
|
||||
|
||||
from urh.util.Logger import logger
|
||||
|
||||
from urh.signalprocessing.Message import Message
|
||||
|
||||
|
||||
class PCAP(object):
|
||||
def __init__(self):
|
||||
self.timestamp_sec = None
|
||||
self.timestamp_nsec = None
|
||||
|
||||
def reset_timestamp(self):
|
||||
self.timestamp_sec = None
|
||||
self.timestamp_nsec = None
|
||||
|
||||
def build_global_header(self) -> bytes:
|
||||
MAGIC_NUMBER = 0xa1b23c4d # Nanosecond resolution
|
||||
VERSION_MAJOR, VERSION_MINOR = 2, 4
|
||||
THISZONE = 0
|
||||
SIGFIGS = 0
|
||||
SNAPLEN = 65535
|
||||
NETWORK = 147
|
||||
|
||||
self.reset_timestamp()
|
||||
|
||||
return struct.pack(">IHHiIII", MAGIC_NUMBER, VERSION_MAJOR, VERSION_MINOR, THISZONE, SIGFIGS, SNAPLEN, NETWORK)
|
||||
|
||||
def build_packet(self, ts_sec: int, ts_nsec: int, data: bytes) -> bytes:
|
||||
if self.timestamp_nsec is None or self.timestamp_sec is None:
|
||||
self.timestamp_sec, self.timestamp_nsec = self.get_seconds_nseconds(time.time())
|
||||
|
||||
self.timestamp_sec += int(ts_sec)
|
||||
self.timestamp_nsec += int(ts_nsec)
|
||||
if self.timestamp_nsec >= 1e9:
|
||||
self.timestamp_sec += int(self.timestamp_nsec / 1e9)
|
||||
self.timestamp_nsec = int(self.timestamp_nsec % 1e9)
|
||||
|
||||
l = len(data)
|
||||
return struct.pack(">IIII", self.timestamp_sec, self.timestamp_nsec, l, l) + data
|
||||
|
||||
def write_packets(self, packets, filename: str, sample_rate: int):
|
||||
"""
|
||||
|
||||
:type packets: list of Message
|
||||
:param filename:
|
||||
:return:
|
||||
"""
|
||||
if os.path.isfile(filename):
|
||||
logger.warning("{0} already exists. Overwriting it".format(filename))
|
||||
|
||||
with open(filename, "wb") as f:
|
||||
f.write(self.build_global_header())
|
||||
|
||||
with open(filename, "ab") as f:
|
||||
rel_time_offset_ns = 0
|
||||
for pkt in packets:
|
||||
f.write(self.build_packet(0, rel_time_offset_ns, pkt.decoded_bits_buffer))
|
||||
rel_time_offset_ns = pkt.get_duration(sample_rate) * 10 ** 9
|
||||
|
||||
@staticmethod
|
||||
def get_seconds_nseconds(timestamp):
|
||||
seconds = int(timestamp)
|
||||
nseconds = int((timestamp - seconds) * 10 ** 9)
|
||||
return seconds, nseconds
|
||||
743
Software/Universal Radio Hacker/src/urh/dev/VirtualDevice.py
Normal file
743
Software/Universal Radio Hacker/src/urh/dev/VirtualDevice.py
Normal file
@@ -0,0 +1,743 @@
|
||||
import time
|
||||
from enum import Enum
|
||||
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import pyqtSignal, QObject
|
||||
|
||||
from urh.dev import config
|
||||
from urh.dev.BackendHandler import Backends, BackendHandler
|
||||
from urh.dev.native.Device import Device
|
||||
from urh.plugins.NetworkSDRInterface.NetworkSDRInterfacePlugin import NetworkSDRInterfacePlugin
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class Mode(Enum):
|
||||
receive = 1
|
||||
send = 2
|
||||
spectrum = 3
|
||||
|
||||
|
||||
class VirtualDevice(QObject):
|
||||
"""
|
||||
Wrapper class for providing sending methods for grc and native devices
|
||||
|
||||
"""
|
||||
started = pyqtSignal()
|
||||
stopped = pyqtSignal()
|
||||
sender_needs_restart = pyqtSignal()
|
||||
|
||||
fatal_error_occurred = pyqtSignal(str)
|
||||
ready_for_action = pyqtSignal()
|
||||
|
||||
continuous_send_msg = "Continuous send mode is not supported for GNU Radio backend. " \
|
||||
"You can change the configured device backend in options."
|
||||
|
||||
def __init__(self, backend_handler, name: str, mode: Mode, freq=None, sample_rate=None, bandwidth=None,
|
||||
gain=None, if_gain=None, baseband_gain=None, samples_to_send=None,
|
||||
device_ip=None, sending_repeats=1, parent=None, resume_on_full_receive_buffer=False, raw_mode=True,
|
||||
portnumber=1234):
|
||||
super().__init__(parent)
|
||||
self.name = name
|
||||
self.mode = mode
|
||||
self.backend_handler = backend_handler
|
||||
|
||||
freq = config.DEFAULT_FREQUENCY if freq is None else freq
|
||||
sample_rate = config.DEFAULT_SAMPLE_RATE if sample_rate is None else sample_rate
|
||||
bandwidth = config.DEFAULT_BANDWIDTH if bandwidth is None else bandwidth
|
||||
gain = config.DEFAULT_GAIN if gain is None else gain
|
||||
if_gain = config.DEFAULT_IF_GAIN if if_gain is None else if_gain
|
||||
baseband_gain = config.DEFAULT_BB_GAIN if baseband_gain is None else baseband_gain
|
||||
|
||||
resume_on_full_receive_buffer = self.mode == Mode.spectrum or resume_on_full_receive_buffer
|
||||
|
||||
if self.name == NetworkSDRInterfacePlugin.NETWORK_SDR_NAME:
|
||||
self.backend = Backends.network
|
||||
else:
|
||||
try:
|
||||
self.backend = self.backend_handler.device_backends[name.lower()].selected_backend
|
||||
except KeyError:
|
||||
logger.warning("Invalid device name: {0}".format(name))
|
||||
self.backend = Backends.none
|
||||
self.__dev = None
|
||||
return
|
||||
|
||||
if self.backend == Backends.grc:
|
||||
if mode == Mode.receive:
|
||||
from urh.dev.gr.ReceiverThread import ReceiverThread
|
||||
self.__dev = ReceiverThread(freq, sample_rate, bandwidth, gain, if_gain, baseband_gain,
|
||||
parent=parent, resume_on_full_receive_buffer=resume_on_full_receive_buffer)
|
||||
elif mode == Mode.send:
|
||||
from urh.dev.gr.SenderThread import SenderThread
|
||||
self.__dev = SenderThread(freq, sample_rate, bandwidth, gain, if_gain, baseband_gain,
|
||||
parent=parent)
|
||||
self.__dev.data = samples_to_send
|
||||
self.__dev.samples_per_transmission = len(samples_to_send) if samples_to_send is not None else 2 ** 15
|
||||
elif mode == Mode.spectrum:
|
||||
from urh.dev.gr.SpectrumThread import SpectrumThread
|
||||
self.__dev = SpectrumThread(freq, sample_rate, bandwidth, gain, if_gain, baseband_gain,
|
||||
parent=parent)
|
||||
else:
|
||||
raise ValueError("Unknown mode")
|
||||
self.__dev.device = name
|
||||
self.__dev.started.connect(self.emit_started_signal)
|
||||
self.__dev.stopped.connect(self.emit_stopped_signal)
|
||||
self.__dev.sender_needs_restart.connect(self.emit_sender_needs_restart)
|
||||
elif self.backend == Backends.native:
|
||||
name = self.name.lower()
|
||||
if name in map(str.lower, BackendHandler.DEVICE_NAMES):
|
||||
if name == "hackrf":
|
||||
from urh.dev.native.HackRF import HackRF
|
||||
self.__dev = HackRF(center_freq=freq, sample_rate=sample_rate, bandwidth=bandwidth,
|
||||
gain=gain, if_gain=if_gain, baseband_gain=baseband_gain,
|
||||
resume_on_full_receive_buffer=resume_on_full_receive_buffer)
|
||||
elif name.replace("-", "") == "rtlsdr":
|
||||
from urh.dev.native.RTLSDR import RTLSDR
|
||||
self.__dev = RTLSDR(freq=freq, gain=gain, srate=sample_rate, device_number=0,
|
||||
resume_on_full_receive_buffer=resume_on_full_receive_buffer)
|
||||
elif name.replace("-", "") == "rtltcp":
|
||||
from urh.dev.native.RTLSDRTCP import RTLSDRTCP
|
||||
self.__dev = RTLSDRTCP(freq=freq, gain=gain, srate=sample_rate, bandwidth=bandwidth,
|
||||
device_number=0,
|
||||
resume_on_full_receive_buffer=resume_on_full_receive_buffer)
|
||||
elif name == "limesdr":
|
||||
from urh.dev.native.LimeSDR import LimeSDR
|
||||
self.__dev = LimeSDR(center_freq=freq, sample_rate=sample_rate, bandwidth=bandwidth, gain=gain,
|
||||
resume_on_full_receive_buffer=resume_on_full_receive_buffer)
|
||||
elif name == "bladerf":
|
||||
from urh.dev.native.BladeRF import BladeRF
|
||||
self.__dev = BladeRF(center_freq=freq, sample_rate=sample_rate, bandwidth=bandwidth, gain=gain,
|
||||
resume_on_full_receive_buffer=resume_on_full_receive_buffer)
|
||||
elif name == "plutosdr":
|
||||
from urh.dev.native.PlutoSDR import PlutoSDR
|
||||
self.__dev = PlutoSDR(center_freq=freq, sample_rate=sample_rate, bandwidth=bandwidth, gain=gain,
|
||||
resume_on_full_receive_buffer=resume_on_full_receive_buffer)
|
||||
elif name.startswith("airspy"):
|
||||
from urh.dev.native.AirSpy import AirSpy
|
||||
self.__dev = AirSpy(center_freq=freq, sample_rate=sample_rate, bandwidth=bandwidth,
|
||||
gain=gain, if_gain=if_gain, baseband_gain=baseband_gain,
|
||||
resume_on_full_receive_buffer=resume_on_full_receive_buffer)
|
||||
elif name.startswith("usrp"):
|
||||
from urh.dev.native.USRP import USRP
|
||||
self.__dev = USRP(center_freq=freq, sample_rate=sample_rate, bandwidth=bandwidth, gain=gain,
|
||||
resume_on_full_receive_buffer=resume_on_full_receive_buffer)
|
||||
elif name.startswith("sdrplay"):
|
||||
from urh.dev.native.SDRPlay import SDRPlay
|
||||
self.__dev = SDRPlay(center_freq=freq, sample_rate=sample_rate, bandwidth=bandwidth,
|
||||
gain=gain, if_gain=if_gain,
|
||||
resume_on_full_receive_buffer=resume_on_full_receive_buffer)
|
||||
elif name == "soundcard":
|
||||
from urh.dev.native.SoundCard import SoundCard
|
||||
self.__dev = SoundCard(sample_rate=sample_rate,
|
||||
resume_on_full_receive_buffer=resume_on_full_receive_buffer)
|
||||
else:
|
||||
raise NotImplementedError("Native Backend for {0} not yet implemented".format(name))
|
||||
|
||||
elif name == "test":
|
||||
# For Unittests Only
|
||||
self.__dev = Device(freq, sample_rate, bandwidth, gain, if_gain, baseband_gain,
|
||||
resume_on_full_receive_buffer)
|
||||
else:
|
||||
raise ValueError("Unknown device name {0}".format(name))
|
||||
self.__dev.portnumber = portnumber
|
||||
self.__dev.device_ip = device_ip
|
||||
if mode == Mode.send:
|
||||
self.__dev.init_send_parameters(samples_to_send, sending_repeats)
|
||||
elif self.backend == Backends.network:
|
||||
self.__dev = NetworkSDRInterfacePlugin(raw_mode=raw_mode,
|
||||
resume_on_full_receive_buffer=resume_on_full_receive_buffer,
|
||||
spectrum=self.mode == Mode.spectrum, sending=self.mode == Mode.send)
|
||||
self.__dev.send_connection_established.connect(self.emit_ready_for_action)
|
||||
self.__dev.receive_server_started.connect(self.emit_ready_for_action)
|
||||
self.__dev.error_occurred.connect(self.emit_fatal_error_occurred)
|
||||
self.__dev.samples_to_send = samples_to_send
|
||||
elif self.backend == Backends.none:
|
||||
self.__dev = None
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
if mode == Mode.spectrum:
|
||||
self.__dev.is_in_spectrum_mode = True
|
||||
|
||||
@property
|
||||
def backend_is_native(self) -> bool:
|
||||
return self.backend == Backends.native
|
||||
|
||||
@property
|
||||
def data_type(self):
|
||||
if self.backend == Backends.native:
|
||||
return self.__dev.DATA_TYPE
|
||||
else:
|
||||
return np.float32
|
||||
|
||||
@property
|
||||
def has_multi_device_support(self):
|
||||
return hasattr(self.__dev, "has_multi_device_support") and self.__dev.has_multi_device_support
|
||||
|
||||
@property
|
||||
def device_serial(self):
|
||||
if hasattr(self.__dev, "device_serial"):
|
||||
return self.__dev.device_serial
|
||||
else:
|
||||
return None
|
||||
|
||||
@device_serial.setter
|
||||
def device_serial(self, value):
|
||||
if hasattr(self.__dev, "device_serial"):
|
||||
self.__dev.device_serial = value
|
||||
|
||||
@property
|
||||
def device_number(self):
|
||||
if hasattr(self.__dev, "device_number"):
|
||||
return self.__dev.device_number
|
||||
else:
|
||||
return None
|
||||
|
||||
@device_number.setter
|
||||
def device_number(self, value):
|
||||
if hasattr(self.__dev, "device_number"):
|
||||
self.__dev.device_number = value
|
||||
|
||||
@property
|
||||
def bandwidth(self):
|
||||
return self.__dev.bandwidth
|
||||
|
||||
@bandwidth.setter
|
||||
def bandwidth(self, value):
|
||||
self.__dev.bandwidth = value
|
||||
|
||||
@property
|
||||
def apply_dc_correction(self):
|
||||
if self.backend == Backends.native:
|
||||
return self.__dev.apply_dc_correction
|
||||
else:
|
||||
return None
|
||||
|
||||
@apply_dc_correction.setter
|
||||
def apply_dc_correction(self, value: bool):
|
||||
if self.backend == Backends.native:
|
||||
self.__dev.apply_dc_correction = bool(value)
|
||||
|
||||
@property
|
||||
def bias_tee_enabled(self):
|
||||
if self.backend_is_native:
|
||||
return self.__dev.bias_tee_enabled
|
||||
else:
|
||||
return None
|
||||
|
||||
@bias_tee_enabled.setter
|
||||
def bias_tee_enabled(self, value: bool):
|
||||
if self.backend_is_native:
|
||||
self.__dev.bias_tee_enabled = value
|
||||
|
||||
@property
|
||||
def bandwidth_is_adjustable(self):
|
||||
if self.backend == Backends.grc:
|
||||
return True
|
||||
elif self.backend == Backends.native:
|
||||
return self.__dev.bandwidth_is_adjustable
|
||||
elif self.backend == Backends.network:
|
||||
return True
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@property
|
||||
def frequency(self):
|
||||
if self.backend in (Backends.grc, Backends.native):
|
||||
return self.__dev.frequency
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@frequency.setter
|
||||
def frequency(self, value):
|
||||
if self.backend in (Backends.grc, Backends.native):
|
||||
self.__dev.frequency = value
|
||||
elif self.backend == Backends.network:
|
||||
pass
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@property
|
||||
def num_samples_to_send(self) -> int:
|
||||
if self.backend in (Backends.native, Backends.network):
|
||||
return self.__dev.num_samples_to_send
|
||||
else:
|
||||
raise ValueError(self.continuous_send_msg)
|
||||
|
||||
@num_samples_to_send.setter
|
||||
def num_samples_to_send(self, value: int):
|
||||
if self.backend in (Backends.native, Backends.network):
|
||||
self.__dev.num_samples_to_send = value
|
||||
else:
|
||||
raise ValueError(self.continuous_send_msg)
|
||||
|
||||
@property
|
||||
def is_send_continuous(self) -> bool:
|
||||
if self.backend in (Backends.native, Backends.network):
|
||||
return self.__dev.sending_is_continuous
|
||||
else:
|
||||
raise ValueError(self.continuous_send_msg)
|
||||
|
||||
@is_send_continuous.setter
|
||||
def is_send_continuous(self, value: bool):
|
||||
if self.backend in (Backends.native, Backends.network):
|
||||
self.__dev.sending_is_continuous = value
|
||||
else:
|
||||
raise ValueError(self.continuous_send_msg)
|
||||
|
||||
@property
|
||||
def is_raw_mode(self) -> bool:
|
||||
if self.backend == Backends.network:
|
||||
return self.__dev.raw_mode
|
||||
else:
|
||||
return True
|
||||
|
||||
@property
|
||||
def continuous_send_ring_buffer(self):
|
||||
if self.backend in (Backends.native, Backends.network):
|
||||
return self.__dev.continuous_send_ring_buffer
|
||||
else:
|
||||
raise ValueError(self.continuous_send_msg)
|
||||
|
||||
@continuous_send_ring_buffer.setter
|
||||
def continuous_send_ring_buffer(self, value):
|
||||
if self.backend in (Backends.native, Backends.network):
|
||||
self.__dev.continuous_send_ring_buffer = value
|
||||
else:
|
||||
raise ValueError(self.continuous_send_msg)
|
||||
|
||||
@property
|
||||
def is_in_spectrum_mode(self):
|
||||
if self.backend in (Backends.grc, Backends.native, Backends.network):
|
||||
return self.__dev.is_in_spectrum_mode
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@is_in_spectrum_mode.setter
|
||||
def is_in_spectrum_mode(self, value: bool):
|
||||
if self.backend in (Backends.grc, Backends.native, Backends.network):
|
||||
self.__dev.is_in_spectrum_mode = value
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@property
|
||||
def gain(self):
|
||||
return self.__dev.gain
|
||||
|
||||
@gain.setter
|
||||
def gain(self, value):
|
||||
try:
|
||||
self.__dev.gain = value
|
||||
except AttributeError as e:
|
||||
logger.warning(str(e))
|
||||
|
||||
@property
|
||||
def if_gain(self):
|
||||
try:
|
||||
return self.__dev.if_gain
|
||||
except AttributeError as e:
|
||||
logger.warning(str(e))
|
||||
|
||||
@if_gain.setter
|
||||
def if_gain(self, value):
|
||||
try:
|
||||
self.__dev.if_gain = value
|
||||
except AttributeError as e:
|
||||
logger.warning(str(e))
|
||||
|
||||
@property
|
||||
def baseband_gain(self):
|
||||
return self.__dev.baseband_gain
|
||||
|
||||
@baseband_gain.setter
|
||||
def baseband_gain(self, value):
|
||||
self.__dev.baseband_gain = value
|
||||
|
||||
@property
|
||||
def sample_rate(self):
|
||||
return self.__dev.sample_rate
|
||||
|
||||
@sample_rate.setter
|
||||
def sample_rate(self, value):
|
||||
self.__dev.sample_rate = value
|
||||
|
||||
@property
|
||||
def channel_index(self) -> int:
|
||||
return self.__dev.channel_index
|
||||
|
||||
@channel_index.setter
|
||||
def channel_index(self, value: int):
|
||||
self.__dev.channel_index = value
|
||||
|
||||
@property
|
||||
def antenna_index(self) -> int:
|
||||
return self.__dev.antenna_index
|
||||
|
||||
@antenna_index.setter
|
||||
def antenna_index(self, value: int):
|
||||
self.__dev.antenna_index = value
|
||||
|
||||
@property
|
||||
def freq_correction(self):
|
||||
return self.__dev.freq_correction
|
||||
|
||||
@freq_correction.setter
|
||||
def freq_correction(self, value):
|
||||
self.__dev.freq_correction = value
|
||||
|
||||
@property
|
||||
def direct_sampling_mode(self) -> int:
|
||||
return self.__dev.direct_sampling_mode
|
||||
|
||||
@direct_sampling_mode.setter
|
||||
def direct_sampling_mode(self, value):
|
||||
self.__dev.direct_sampling_mode = value
|
||||
|
||||
@property
|
||||
def samples_to_send(self):
|
||||
if self.backend == Backends.grc:
|
||||
return self.__dev.data
|
||||
elif self.backend in (Backends.native, Backends.network):
|
||||
return self.__dev.samples_to_send
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@samples_to_send.setter
|
||||
def samples_to_send(self, value):
|
||||
if self.backend == Backends.grc:
|
||||
self.__dev.data = value
|
||||
elif self.backend == Backends.native:
|
||||
self.__dev.init_send_parameters(value, self.num_sending_repeats)
|
||||
elif self.backend == Backends.network:
|
||||
self.__dev.samples_to_send = value
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@property
|
||||
def subdevice(self):
|
||||
if hasattr(self.__dev, "subdevice"):
|
||||
return self.__dev.subdevice
|
||||
else:
|
||||
return None
|
||||
|
||||
@subdevice.setter
|
||||
def subdevice(self, value: str):
|
||||
if hasattr(self.__dev, "subdevice"):
|
||||
self.__dev.subdevice = value
|
||||
|
||||
@property
|
||||
def ip(self):
|
||||
if self.backend == Backends.grc:
|
||||
return self.__dev.device_ip
|
||||
elif self.backend == Backends.native:
|
||||
return self.__dev.device_ip
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@ip.setter
|
||||
def ip(self, value):
|
||||
if self.backend == Backends.grc:
|
||||
self.__dev.device_ip = value
|
||||
elif self.backend == Backends.native:
|
||||
self.__dev.device_ip = value
|
||||
elif self.backend in (Backends.none, Backends.network):
|
||||
pass
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@property
|
||||
def port(self):
|
||||
if self.backend in (Backends.grc, Backends.native, Backends.network):
|
||||
return self.__dev.port
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@port.setter
|
||||
def port(self, value):
|
||||
if self.backend in (Backends.grc, Backends.native, Backends.network):
|
||||
self.__dev.port = value
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
if self.backend == Backends.grc:
|
||||
return self.__dev.data
|
||||
elif self.backend == Backends.native:
|
||||
if self.mode == Mode.send:
|
||||
return self.__dev.samples_to_send
|
||||
else:
|
||||
return self.__dev.receive_buffer
|
||||
elif self.backend == Backends.network:
|
||||
if self.mode == Mode.send:
|
||||
raise NotImplementedError("Todo")
|
||||
else:
|
||||
if self.__dev.raw_mode:
|
||||
return self.__dev.receive_buffer
|
||||
else:
|
||||
return self.__dev.received_bits
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@data.setter
|
||||
def data(self, value):
|
||||
if self.backend == Backends.grc:
|
||||
self.__dev.data = value
|
||||
elif self.backend == Backends.native:
|
||||
if self.mode == Mode.send:
|
||||
self.__dev.samples_to_send = value
|
||||
else:
|
||||
self.__dev.receive_buffer = value
|
||||
else:
|
||||
logger.warning("{}:{} has no data".format(self.__class__.__name__, self.backend.name))
|
||||
|
||||
def free_data(self):
|
||||
if self.backend == Backends.grc:
|
||||
self.__dev.data = None
|
||||
elif self.backend == Backends.native:
|
||||
self.__dev.samples_to_send = None
|
||||
self.__dev.receive_buffer = None
|
||||
elif self.backend == Backends.network:
|
||||
self.__dev.free_data()
|
||||
elif self.backend == Backends.none:
|
||||
pass
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@property
|
||||
def resume_on_full_receive_buffer(self) -> bool:
|
||||
return self.__dev.resume_on_full_receive_buffer
|
||||
|
||||
@resume_on_full_receive_buffer.setter
|
||||
def resume_on_full_receive_buffer(self, value: bool):
|
||||
if value != self.__dev.resume_on_full_receive_buffer:
|
||||
self.__dev.resume_on_full_receive_buffer = value
|
||||
if self.backend == Backends.native:
|
||||
self.__dev.receive_buffer = None
|
||||
elif self.backend == Backends.grc:
|
||||
self.__dev.data = None
|
||||
|
||||
@property
|
||||
def num_sending_repeats(self):
|
||||
return self.__dev.sending_repeats
|
||||
|
||||
@num_sending_repeats.setter
|
||||
def num_sending_repeats(self, value):
|
||||
self.__dev.sending_repeats = value
|
||||
|
||||
@property
|
||||
def current_index(self):
|
||||
if self.backend == Backends.grc:
|
||||
return self.__dev.current_index
|
||||
elif self.backend == Backends.native:
|
||||
if self.mode == Mode.send:
|
||||
return self.__dev.current_sent_sample
|
||||
else:
|
||||
return self.__dev.current_recv_index
|
||||
elif self.backend == Backends.network:
|
||||
if self.mode == Mode.send:
|
||||
return self.__dev.current_sent_sample
|
||||
else:
|
||||
return self.__dev.current_receive_index
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@current_index.setter
|
||||
def current_index(self, value):
|
||||
if self.backend == Backends.grc:
|
||||
self.__dev.current_index = value
|
||||
elif self.backend == Backends.native:
|
||||
if self.mode == Mode.send:
|
||||
self.__dev.current_sent_sample = value
|
||||
else:
|
||||
self.__dev.current_recv_index = value
|
||||
elif self.backend == Backends.network:
|
||||
if self.mode == Mode.send:
|
||||
self.__dev.current_sent_sample = value
|
||||
else:
|
||||
self.__dev.current_receive_index = value
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@property
|
||||
def current_iteration(self):
|
||||
if self.backend == Backends.grc:
|
||||
return self.__dev.current_iteration
|
||||
elif self.backend in (Backends.native, Backends.network):
|
||||
return self.__dev.current_sending_repeat
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@current_iteration.setter
|
||||
def current_iteration(self, value):
|
||||
if self.backend == Backends.grc:
|
||||
self.__dev.current_iteration = value
|
||||
elif self.backend in (Backends.native, Backends.network):
|
||||
self.__dev.current_sending_repeat = value
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@property
|
||||
def sending_finished(self):
|
||||
if self.backend == Backends.grc:
|
||||
return self.__dev.current_iteration is None
|
||||
elif self.backend in (Backends.native, Backends.network):
|
||||
return self.__dev.sending_finished
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
@property
|
||||
def spectrum(self):
|
||||
if self.mode == Mode.spectrum:
|
||||
if self.backend == Backends.grc:
|
||||
return self.__dev.x, self.__dev.y
|
||||
elif self.backend == Backends.native or self.backend == Backends.network:
|
||||
w = np.abs(np.fft.fft(self.__dev.receive_buffer.as_complex64()))
|
||||
freqs = np.fft.fftfreq(len(w), 1 / self.sample_rate)
|
||||
idx = np.argsort(freqs)
|
||||
return freqs[idx].astype(np.float32), w[idx].astype(np.float32)
|
||||
else:
|
||||
raise ValueError("Spectrum x only available in spectrum mode")
|
||||
|
||||
def start(self):
|
||||
if self.backend == Backends.grc:
|
||||
self.__dev.setTerminationEnabled(True)
|
||||
self.__dev.terminate()
|
||||
time.sleep(0.1)
|
||||
self.__dev.start() # Already connected to started signal in constructor
|
||||
elif self.backend == Backends.native:
|
||||
if self.mode == Mode.send:
|
||||
self.__dev.start_tx_mode(resume=True)
|
||||
else:
|
||||
self.__dev.start_rx_mode()
|
||||
|
||||
self.emit_started_signal()
|
||||
elif self.backend == Backends.network:
|
||||
if self.mode == Mode.receive or self.mode == Mode.spectrum:
|
||||
self.__dev.start_tcp_server_for_receiving()
|
||||
else:
|
||||
self.__dev.start_raw_sending_thread()
|
||||
|
||||
self.emit_started_signal()
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
def stop(self, msg: str):
|
||||
if self.backend == Backends.grc:
|
||||
self.__dev.stop(msg) # Already connected to stopped in constructor
|
||||
elif self.backend == Backends.native:
|
||||
if self.mode == Mode.send:
|
||||
self.__dev.stop_tx_mode(msg)
|
||||
else:
|
||||
self.__dev.stop_rx_mode(msg)
|
||||
self.emit_stopped_signal()
|
||||
elif self.backend == Backends.network:
|
||||
self.__dev.stop_tcp_server()
|
||||
self.__dev.stop_sending_thread()
|
||||
self.emit_stopped_signal()
|
||||
elif self.backend == Backends.none:
|
||||
pass
|
||||
else:
|
||||
logger.error("Stop device: Unsupported backend " + str(self.backend))
|
||||
|
||||
def stop_on_error(self, msg: str):
|
||||
if self.backend == Backends.grc:
|
||||
self.__dev.stop(msg) # Already connected to stopped in constructor
|
||||
elif self.backend == Backends.native:
|
||||
self.read_messages() # Clear errors
|
||||
self.__dev.stop_rx_mode("Stop on error")
|
||||
self.__dev.stop_tx_mode("Stop on error")
|
||||
self.emit_stopped_signal()
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
def cleanup(self):
|
||||
if self.backend == Backends.grc:
|
||||
if self.mode == Mode.send:
|
||||
self.__dev.socket.close()
|
||||
time.sleep(0.1)
|
||||
self.__dev.quit()
|
||||
self.data = None
|
||||
|
||||
elif self.backend == Backends.native:
|
||||
self.data = None
|
||||
|
||||
elif self.backend == Backends.none:
|
||||
pass
|
||||
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
def emit_stopped_signal(self):
|
||||
self.stopped.emit()
|
||||
|
||||
def emit_started_signal(self):
|
||||
self.started.emit()
|
||||
|
||||
def emit_sender_needs_restart(self):
|
||||
self.sender_needs_restart.emit()
|
||||
|
||||
def read_messages(self) -> str:
|
||||
"""
|
||||
returns a string of new device messages separated by newlines
|
||||
|
||||
:return:
|
||||
"""
|
||||
if self.backend == Backends.grc:
|
||||
errors = self.__dev.read_errors()
|
||||
|
||||
if "FATAL: " in errors:
|
||||
self.fatal_error_occurred.emit(errors[errors.index("FATAL: "):])
|
||||
|
||||
return errors
|
||||
elif self.backend == Backends.native:
|
||||
messages = "\n".join(self.__dev.device_messages)
|
||||
self.__dev.device_messages.clear()
|
||||
|
||||
if messages and not messages.endswith("\n"):
|
||||
messages += "\n"
|
||||
|
||||
if "successfully started" in messages:
|
||||
self.ready_for_action.emit()
|
||||
elif "failed to start" in messages:
|
||||
self.fatal_error_occurred.emit(messages[messages.index("failed to start"):])
|
||||
|
||||
return messages
|
||||
elif self.backend == Backends.network:
|
||||
return ""
|
||||
else:
|
||||
raise ValueError("Unsupported Backend")
|
||||
|
||||
def set_server_port(self, port: int):
|
||||
if self.backend == Backends.network:
|
||||
self.__dev.server_port = port
|
||||
else:
|
||||
raise ValueError("Setting port only supported for NetworkSDR Plugin")
|
||||
|
||||
def set_client_port(self, port: int):
|
||||
if self.backend == Backends.network:
|
||||
self.__dev.client_port = port
|
||||
else:
|
||||
raise ValueError("Setting port only supported for NetworkSDR Plugin")
|
||||
|
||||
def get_device_list(self):
|
||||
if hasattr(self.__dev, "get_device_list"):
|
||||
return self.__dev.get_device_list()
|
||||
else:
|
||||
return []
|
||||
|
||||
def increase_gr_port(self):
|
||||
if self.backend == Backends.grc:
|
||||
self.__dev.gr_port += 1
|
||||
logger.info("Retry with port " + str(self.__dev.gr_port))
|
||||
else:
|
||||
raise ValueError("Only for GR backend")
|
||||
|
||||
def emit_ready_for_action(self):
|
||||
"""
|
||||
Notify observers that device is successfully initialized
|
||||
:return:
|
||||
"""
|
||||
self.ready_for_action.emit()
|
||||
|
||||
def emit_fatal_error_occurred(self, msg: str):
|
||||
self.fatal_error_occurred.emit(msg)
|
||||
142
Software/Universal Radio Hacker/src/urh/dev/config.py
Normal file
142
Software/Universal Radio Hacker/src/urh/dev/config.py
Normal file
@@ -0,0 +1,142 @@
|
||||
import copy
|
||||
from collections import OrderedDict, namedtuple
|
||||
|
||||
from urh.plugins.NetworkSDRInterface.NetworkSDRInterfacePlugin import NetworkSDRInterfacePlugin
|
||||
|
||||
DEFAULT_FREQUENCY = 433.92e6
|
||||
DEFAULT_SAMPLE_RATE = 1e6
|
||||
DEFAULT_BANDWIDTH = 1e6
|
||||
DEFAULT_GAIN = 20
|
||||
DEFAULT_IF_GAIN = 20
|
||||
DEFAULT_BB_GAIN = 16
|
||||
DEFAULT_FREQ_CORRECTION = 1
|
||||
DEFAULT_DIRECT_SAMPLING_MODE = 0
|
||||
|
||||
DEVICE_CONFIG = OrderedDict()
|
||||
|
||||
dev_range = namedtuple("dev_range", ["start", "stop", "step"])
|
||||
|
||||
K = 10 ** 3
|
||||
M = 10 ** 6
|
||||
G = 10 ** 9
|
||||
|
||||
DEVICE_CONFIG["PlutoSDR"] = {
|
||||
"center_freq": dev_range(start=70 * M, stop=6 * G, step=1),
|
||||
"sample_rate": dev_range(start=2.1 * M, stop=61.44 * M, step=1),
|
||||
"bandwidth": dev_range(start=0.2 * M, stop=56 * M, step=1),
|
||||
"tx_rf_gain": list(range(-89, 1)),
|
||||
"rx_rf_gain": list(range(-3, 72)),
|
||||
}
|
||||
|
||||
# http://www.nuand.com/bladeRF-brief.pdf
|
||||
DEVICE_CONFIG["BladeRF"] = {
|
||||
"center_freq": dev_range(start=47 * M, stop=6 * G, step=1),
|
||||
"sample_rate": dev_range(start=520.834 * K, stop=61.44 * M, step=1), # https://github.com/jopohl/urh/issues/930
|
||||
"bandwidth": dev_range(start=1.5 * M, stop=28 * M, step=1),
|
||||
"rx_channel": ["RX1", "RX2"],
|
||||
"tx_channel": ["TX1", "TX2"],
|
||||
"tx_rf_gain": list(range(0, 61)),
|
||||
"rx_rf_gain": list(range(0, 61)),
|
||||
"bias_tee_enabled": [False, True]
|
||||
}
|
||||
|
||||
# https://github.com/mossmann/hackrf/wiki/HackRF-One#features
|
||||
DEVICE_CONFIG["HackRF"] = {
|
||||
"center_freq": dev_range(start=10, stop=6 * G, step=1),
|
||||
"sample_rate": dev_range(start=2 * M, stop=20 * M, step=1),
|
||||
"bandwidth": dev_range(start=2 * M, stop=20 * M, step=1),
|
||||
"tx_rf_gain": [0, 14],
|
||||
"rx_rf_gain": [0, 14],
|
||||
"rx_if_gain": [0, 8, 16, 24, 32, 40],
|
||||
"tx_if_gain": list(range(0, 48)),
|
||||
"rx_baseband_gain": list(range(0, 63, 2)), # only available in RX
|
||||
"bias_tee_enabled": [False, True]
|
||||
}
|
||||
|
||||
# https://kb.ettus.com/About_USRP_Bandwidths_and_Sampling_Rates
|
||||
DEVICE_CONFIG["USRP"] = {
|
||||
"center_freq": dev_range(start=0, stop=6 * G, step=1),
|
||||
"sample_rate": dev_range(start=1, stop=200 * M, step=1),
|
||||
"bandwidth": dev_range(start=1, stop=120 * M, step=1),
|
||||
"subdevice": "", # http://files.ettus.com/manual/page_configuration.html#config_subdev
|
||||
"rx_rf_gain": list(range(0, 101)),
|
||||
"tx_rf_gain": list(range(0, 101)),
|
||||
"rx_antenna": ["Antenna 1", "Antenna 2", "Antenna 3"],
|
||||
"tx_antenna": ["Antenna 1", "Antenna 2", "Antenna 3"]
|
||||
}
|
||||
|
||||
# https://myriadrf.org/projects/limesdr/
|
||||
DEVICE_CONFIG["LimeSDR"] = {
|
||||
"center_freq": dev_range(start=100 * K, stop=int(3.8 * G), step=1),
|
||||
"sample_rate": dev_range(start=2 * M, stop=30 * M, step=1),
|
||||
"bandwidth": dev_range(start=2 * M, stop=130 * M, step=1),
|
||||
"rx_rf_gain": list(range(0, 101)), # Normalized Gain 0-100%
|
||||
"tx_rf_gain": list(range(0, 101)), # Normalized Gain 0-100%
|
||||
"rx_channel": ["RX1", "RX2"],
|
||||
"tx_channel": ["TX1", "TX2"],
|
||||
"rx_antenna": ["None", "High (RX_H)", "Low (RX_L)", "Wide (RX_W)"],
|
||||
"rx_antenna_default_index": 2,
|
||||
"tx_antenna": ["None", "Band 1 (TX_1)", "Band 2 (TX_2)"],
|
||||
"tx_antenna_default_index": 1
|
||||
}
|
||||
|
||||
# http://osmocom.org/projects/sdr/wiki/rtl-sdr
|
||||
DEVICE_CONFIG["RTL-SDR"] = {
|
||||
# 0.1 MHz lower limit because: https://github.com/jopohl/urh/issues/211
|
||||
"center_freq": dev_range(start=0.1 * M, stop=2200 * M, step=1),
|
||||
"sample_rate": dev_range(start=1, stop=int(3.2 * M), step=1),
|
||||
"bandwidth": dev_range(start=1, stop=int(3.2 * M), step=1),
|
||||
"rx_rf_gain": list(range(-100, 500)),
|
||||
"direct_sampling": ["disabled", "I-ADC input enabled", "Q-ADC input enabled"],
|
||||
"freq_correction": dev_range(start=-1 * 10 ** 3, stop=1 * 10 ** 3, step=1)
|
||||
}
|
||||
|
||||
DEVICE_CONFIG["RTL-TCP"] = copy.deepcopy(DEVICE_CONFIG["RTL-SDR"])
|
||||
DEVICE_CONFIG["RTL-TCP"]["ip"] = ""
|
||||
DEVICE_CONFIG["RTL-TCP"]["port"] = ""
|
||||
|
||||
DEVICE_CONFIG[NetworkSDRInterfacePlugin.NETWORK_SDR_NAME] = {}
|
||||
|
||||
# http://www.rtl-sdr.com/review-airspy-vs-sdrplay-rsp-vs-hackrf/
|
||||
# https://airspy.com/products/
|
||||
DEVICE_CONFIG["AirSpy R2"] = {
|
||||
"center_freq": dev_range(start=24, stop=1800 * M, step=1),
|
||||
"sample_rate": [10 * M, 10 * M], # This device always uses 10M, no matter what is configured.
|
||||
"bandwidth": [10 * M, 10 * M],
|
||||
"rx_rf_gain": list(range(0, 16)),
|
||||
"rx_if_gain": list(range(0, 16)),
|
||||
"rx_baseband_gain": list(range(0, 16)),
|
||||
}
|
||||
|
||||
DEVICE_CONFIG["AirSpy Mini"] = {
|
||||
"center_freq": dev_range(start=24, stop=1800 * M, step=1),
|
||||
"sample_rate": [6 * M, 6 * M],
|
||||
# Documentation says: "10, 6 and 3 MSPS IQ output" but it always uses 6M, no matter what is configured.
|
||||
"bandwidth": [6 * M, 6 * M],
|
||||
"rx_rf_gain": list(range(0, 16)),
|
||||
"rx_if_gain": list(range(0, 16)),
|
||||
"rx_baseband_gain": list(range(0, 16)),
|
||||
}
|
||||
|
||||
DEVICE_CONFIG["SDRPlay"] = {
|
||||
"center_freq": dev_range(start=1 * K, stop=2 * G, step=1),
|
||||
"sample_rate": dev_range(start=2 * M, stop=10 * M, step=1),
|
||||
"bandwidth": [0, 200e3, 300e3, 600e3, 1536e3, 5000e3, 6000e3, 7000e3, 8000e3],
|
||||
"rx_rf_gain": list(range(20, 60)),
|
||||
"rx_if_gain": [0, 450, 1620, 2048],
|
||||
"rx_antenna": ["Antenna A", "Antenna B", "Hi-Z"],
|
||||
"rx_antenna_default_index": 0,
|
||||
}
|
||||
|
||||
DEVICE_CONFIG["SoundCard"] = {
|
||||
"sample_rate": [16e3, 22.05e3, 24e3, 32e3, 44.1e3, 48e3, 96e3, 192e3],
|
||||
"default_sample_rate": 48e3,
|
||||
}
|
||||
|
||||
DEVICE_CONFIG["Fallback"] = {
|
||||
"center_freq": dev_range(start=1 * M, stop=6 * G, step=1),
|
||||
"sample_rate": dev_range(start=2 * M, stop=20 * M, step=1),
|
||||
"bandwidth": dev_range(start=2 * M, stop=20 * M, step=1),
|
||||
"rx_rf_gain": list(range(0, 51)),
|
||||
"tx_rf_gain": list(range(0, 51)),
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from queue import Queue, Empty
|
||||
from subprocess import Popen, PIPE
|
||||
from threading import Thread
|
||||
|
||||
from PyQt5.QtCore import QThread, pyqtSignal
|
||||
|
||||
from urh import settings
|
||||
from urh.util.Logger import logger
|
||||
|
||||
ON_POSIX = 'posix' in sys.builtin_module_names
|
||||
|
||||
|
||||
class AbstractBaseThread(QThread):
|
||||
started = pyqtSignal()
|
||||
stopped = pyqtSignal()
|
||||
sender_needs_restart = pyqtSignal()
|
||||
|
||||
def __init__(self, frequency, sample_rate, bandwidth, gain, if_gain, baseband_gain, receiving: bool,
|
||||
ip='127.0.0.1', parent=None):
|
||||
super().__init__(parent)
|
||||
self.ip = ip
|
||||
self.gr_port = 1337
|
||||
self._sample_rate = sample_rate
|
||||
self._frequency = frequency
|
||||
self._gain = gain
|
||||
self._if_gain = if_gain
|
||||
self._baseband_gain = baseband_gain
|
||||
self._bandwidth = bandwidth
|
||||
self._freq_correction = 1
|
||||
self._direct_sampling_mode = 0
|
||||
self._antenna_index = 0
|
||||
self._channel_index = 0
|
||||
self._receiving = receiving # False for Sender-Thread
|
||||
self.device = "USRP"
|
||||
self.current_index = 0
|
||||
|
||||
self.is_in_spectrum_mode = False
|
||||
|
||||
self.socket = None
|
||||
|
||||
self.gr_python_interpreter = settings.read("gr_python_interpreter", "")
|
||||
|
||||
self.queue = Queue()
|
||||
self.data = None # Placeholder for SenderThread
|
||||
self.current_iteration = 0 # Counts number of Sendings in SenderThread
|
||||
|
||||
self.gr_process = None
|
||||
|
||||
@property
|
||||
def sample_rate(self):
|
||||
return self._sample_rate
|
||||
|
||||
@sample_rate.setter
|
||||
def sample_rate(self, value):
|
||||
self._sample_rate = value
|
||||
if self.gr_process:
|
||||
try:
|
||||
self.gr_process.stdin.write(b'SR:' + bytes(str(value), "utf8") + b'\n')
|
||||
self.gr_process.stdin.flush()
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
|
||||
@property
|
||||
def frequency(self):
|
||||
return self._frequency
|
||||
|
||||
@frequency.setter
|
||||
def frequency(self, value):
|
||||
self._frequency = value
|
||||
if self.gr_process:
|
||||
try:
|
||||
self.gr_process.stdin.write(b'F:' + bytes(str(value), "utf8") + b'\n')
|
||||
self.gr_process.stdin.flush()
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
|
||||
@property
|
||||
def gain(self):
|
||||
return self._gain
|
||||
|
||||
@gain.setter
|
||||
def gain(self, value):
|
||||
self._gain = value
|
||||
if self.gr_process:
|
||||
try:
|
||||
self.gr_process.stdin.write(b'G:' + bytes(str(value), "utf8") + b'\n')
|
||||
self.gr_process.stdin.flush()
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
|
||||
@property
|
||||
def if_gain(self):
|
||||
return self._if_gain
|
||||
|
||||
@if_gain.setter
|
||||
def if_gain(self, value):
|
||||
self._if_gain = value
|
||||
if self.gr_process:
|
||||
try:
|
||||
self.gr_process.stdin.write(b'IFG:' + bytes(str(value), "utf8") + b'\n')
|
||||
self.gr_process.stdin.flush()
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
|
||||
@property
|
||||
def baseband_gain(self):
|
||||
return self._baseband_gain
|
||||
|
||||
@baseband_gain.setter
|
||||
def baseband_gain(self, value):
|
||||
self._baseband_gain = value
|
||||
if self.gr_process:
|
||||
try:
|
||||
self.gr_process.stdin.write(b'BBG:' + bytes(str(value), "utf8") + b'\n')
|
||||
self.gr_process.stdin.flush()
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
|
||||
@property
|
||||
def bandwidth(self):
|
||||
return self._bandwidth
|
||||
|
||||
@bandwidth.setter
|
||||
def bandwidth(self, value):
|
||||
self._bandwidth = value
|
||||
if self.gr_process:
|
||||
try:
|
||||
self.gr_process.stdin.write(b'BW:' + bytes(str(value), "utf8") + b'\n')
|
||||
self.gr_process.stdin.flush()
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
|
||||
@property
|
||||
def freq_correction(self):
|
||||
return self._freq_correction
|
||||
|
||||
@freq_correction.setter
|
||||
def freq_correction(self, value):
|
||||
self._freq_correction = value
|
||||
if self.gr_process:
|
||||
try:
|
||||
self.gr_process.stdin.write(b'FC:' + bytes(str(value), "utf8") + b'\n')
|
||||
self.gr_process.stdin.flush()
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
|
||||
@property
|
||||
def channel_index(self):
|
||||
return self._channel_index
|
||||
|
||||
@channel_index.setter
|
||||
def channel_index(self, value):
|
||||
self._channel_index = value
|
||||
|
||||
@property
|
||||
def antenna_index(self):
|
||||
return self._antenna_index
|
||||
|
||||
@antenna_index.setter
|
||||
def antenna_index(self, value):
|
||||
self._antenna_index = value
|
||||
|
||||
@property
|
||||
def direct_sampling_mode(self):
|
||||
return self._direct_sampling_mode
|
||||
|
||||
@direct_sampling_mode.setter
|
||||
def direct_sampling_mode(self, value):
|
||||
self._direct_sampling_mode = value
|
||||
if self.gr_process:
|
||||
try:
|
||||
self.gr_process.stdin.write(b'DSM:' + bytes(str(value), "utf8") + b'\n')
|
||||
self.gr_process.stdin.flush()
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
|
||||
def initialize_process(self):
|
||||
self.started.emit()
|
||||
|
||||
if not hasattr(sys, 'frozen'):
|
||||
rp = os.path.realpath(os.path.join(os.path.dirname(__file__), "scripts"))
|
||||
else:
|
||||
rp = os.path.realpath(os.path.dirname(sys.executable))
|
||||
|
||||
suffix = "_recv.py" if self._receiving else "_send.py"
|
||||
filename = self.device.lower().split(" ")[0] + suffix
|
||||
|
||||
if not self.gr_python_interpreter:
|
||||
self.stop(
|
||||
"FATAL: Could not find a GR compatible Python interpreter. "
|
||||
"Make sure you have a running GNU Radio installation.")
|
||||
return
|
||||
|
||||
options = [self.gr_python_interpreter, os.path.join(rp, filename),
|
||||
"--sample-rate", str(int(self.sample_rate)), "--frequency", str(int(self.frequency)),
|
||||
"--gain", str(self.gain), "--if-gain", str(self.if_gain), "--bb-gain", str(self.baseband_gain),
|
||||
"--bandwidth", str(int(self.bandwidth)), "--freq-correction", str(self.freq_correction),
|
||||
"--direct-sampling", str(self.direct_sampling_mode), "--channel-index", str(self.channel_index),
|
||||
"--port", str(self.gr_port)]
|
||||
|
||||
logger.info("Starting GNU Radio")
|
||||
logger.debug(" ".join(options))
|
||||
self.gr_process = Popen(options, stdout=PIPE, stderr=PIPE, stdin=PIPE, bufsize=1)
|
||||
logger.info("Started GNU Radio")
|
||||
t = Thread(target=self.enqueue_output, args=(self.gr_process.stderr, self.queue))
|
||||
t.daemon = True # thread dies with the program
|
||||
t.start()
|
||||
|
||||
def init_recv_socket(self):
|
||||
logger.info("Initializing receive socket")
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
logger.info("Initialized receive socket")
|
||||
|
||||
while not self.isInterruptionRequested():
|
||||
try:
|
||||
time.sleep(0.1)
|
||||
logger.info("Trying to get a connection to GNU Radio...")
|
||||
self.socket.connect((self.ip, self.gr_port))
|
||||
logger.info("Got connection")
|
||||
break
|
||||
except (ConnectionRefusedError, ConnectionResetError):
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.error("Unexpected error", str(e))
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
def read_errors(self, initial_errors=None):
|
||||
result = [] if initial_errors is None else initial_errors
|
||||
while True:
|
||||
try:
|
||||
result.append(self.queue.get_nowait())
|
||||
except Empty:
|
||||
break
|
||||
|
||||
result = b"".join(result)
|
||||
try:
|
||||
return result.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return "Could not decode device message"
|
||||
|
||||
def enqueue_output(self, out, queue):
|
||||
for line in iter(out.readline, b''):
|
||||
queue.put(line)
|
||||
out.close()
|
||||
|
||||
def stop(self, msg: str):
|
||||
if msg and not msg.startswith("FIN"):
|
||||
self.requestInterruption()
|
||||
time.sleep(0.1)
|
||||
|
||||
try:
|
||||
logger.info("Kill grc process")
|
||||
self.gr_process.kill()
|
||||
logger.info("Term grc process")
|
||||
self.gr_process.terminate()
|
||||
self.gr_process = None
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
logger.info(msg)
|
||||
self.stopped.emit()
|
||||
@@ -0,0 +1,67 @@
|
||||
import numpy as np
|
||||
|
||||
from urh import settings
|
||||
from urh.dev.gr.AbstractBaseThread import AbstractBaseThread
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class ReceiverThread(AbstractBaseThread):
|
||||
def __init__(self, frequency, sample_rate, bandwidth, gain, if_gain, baseband_gain, ip='127.0.0.1',
|
||||
parent=None, resume_on_full_receive_buffer=False):
|
||||
super().__init__(frequency, sample_rate, bandwidth, gain, if_gain, baseband_gain, True, ip, parent)
|
||||
|
||||
self.resume_on_full_receive_buffer = resume_on_full_receive_buffer # for Live Sniffing
|
||||
self.data = None
|
||||
|
||||
def init_recv_buffer(self):
|
||||
n_samples = settings.get_receive_buffer_size(self.resume_on_full_receive_buffer, self.is_in_spectrum_mode)
|
||||
self.data = np.zeros(n_samples, dtype=np.complex64)
|
||||
|
||||
def run(self):
|
||||
if self.data is None:
|
||||
self.init_recv_buffer()
|
||||
|
||||
self.initialize_process()
|
||||
logger.info("Initialize receive socket")
|
||||
self.init_recv_socket()
|
||||
|
||||
recv = self.socket.recv
|
||||
rcvd = b""
|
||||
|
||||
try:
|
||||
while not self.isInterruptionRequested():
|
||||
try:
|
||||
rcvd += recv(32768) # Receive Buffer = 32768 Byte+
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
if len(rcvd) < 8:
|
||||
self.stop("Stopped receiving: No data received anymore")
|
||||
return
|
||||
|
||||
if len(rcvd) % 8 != 0:
|
||||
continue
|
||||
|
||||
try:
|
||||
tmp = np.fromstring(rcvd, dtype=np.complex64)
|
||||
|
||||
num_samples = len(tmp)
|
||||
if self.data is None:
|
||||
# seems to be sometimes None in rare cases
|
||||
self.init_recv_buffer()
|
||||
|
||||
if self.current_index + num_samples >= len(self.data):
|
||||
if self.resume_on_full_receive_buffer:
|
||||
self.current_index = 0
|
||||
if num_samples >= len(self.data):
|
||||
self.stop("Receiving buffer too small.")
|
||||
else:
|
||||
self.stop("Receiving Buffer is full.")
|
||||
return
|
||||
self.data[self.current_index:self.current_index + num_samples] = tmp
|
||||
self.current_index += num_samples
|
||||
rcvd = b""
|
||||
except ValueError:
|
||||
self.stop("Could not receive data. Is your Hardware ok?")
|
||||
except RuntimeError:
|
||||
logger.error("Receiver Thread crashed.")
|
||||
@@ -0,0 +1,70 @@
|
||||
import socket
|
||||
import time
|
||||
|
||||
import numpy
|
||||
import numpy as np
|
||||
|
||||
from urh.dev.gr.AbstractBaseThread import AbstractBaseThread
|
||||
from urh.util import util
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class SenderThread(AbstractBaseThread):
|
||||
MAX_SAMPLES_PER_TRANSMISSION = 4096
|
||||
|
||||
def __init__(self, frequency, sample_rate, bandwidth, gain, if_gain, baseband_gain, ip='127.0.0.1', parent=None):
|
||||
super().__init__(frequency, sample_rate, bandwidth, gain, if_gain, baseband_gain, False, ip, parent)
|
||||
|
||||
self.data = numpy.empty(1, dtype=numpy.complex64)
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
port = util.get_free_port()
|
||||
self.gr_port = port
|
||||
|
||||
self.sending_repeats = 1 # How often shall we send the data?
|
||||
|
||||
self.__samples_per_transmission = self.MAX_SAMPLES_PER_TRANSMISSION
|
||||
|
||||
@property
|
||||
def repeat_endless(self):
|
||||
return self.sending_repeats == 0 or self.sending_repeats == -1
|
||||
|
||||
@property
|
||||
def samples_per_transmission(self):
|
||||
return self.__samples_per_transmission
|
||||
|
||||
@samples_per_transmission.setter
|
||||
def samples_per_transmission(self, val: int):
|
||||
if val >= self.MAX_SAMPLES_PER_TRANSMISSION:
|
||||
self.__samples_per_transmission = self.MAX_SAMPLES_PER_TRANSMISSION
|
||||
elif val <= 1:
|
||||
self.__samples_per_transmission = 1
|
||||
else:
|
||||
self.__samples_per_transmission = 2 ** (int(np.log2(val)) - 1)
|
||||
|
||||
def run(self):
|
||||
self.initialize_process()
|
||||
len_data = len(self.data)
|
||||
self.current_iteration = self.current_iteration if self.current_iteration is not None else 0
|
||||
time.sleep(1)
|
||||
|
||||
try:
|
||||
while self.current_index < len_data and not self.isInterruptionRequested():
|
||||
time.sleep(self.samples_per_transmission / self.sample_rate)
|
||||
self.socket.sendto(
|
||||
self.data[self.current_index:self.current_index + self.samples_per_transmission].tostring(),
|
||||
(self.ip, self.gr_port))
|
||||
self.current_index += self.samples_per_transmission
|
||||
|
||||
if self.current_index >= len_data:
|
||||
self.current_iteration += 1
|
||||
else:
|
||||
continue
|
||||
|
||||
if self.repeat_endless or self.current_iteration < self.sending_repeats:
|
||||
self.current_index = 0
|
||||
|
||||
self.current_index = len_data - 1
|
||||
self.current_iteration = None
|
||||
self.stop("FIN - All data was sent successfully")
|
||||
except RuntimeError:
|
||||
logger.error("Sender thread crashed.")
|
||||
@@ -0,0 +1,69 @@
|
||||
import numpy as np
|
||||
|
||||
from urh import settings
|
||||
from urh.dev.gr.AbstractBaseThread import AbstractBaseThread
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class SpectrumThread(AbstractBaseThread):
|
||||
def __init__(self, frequency, sample_rate, bandwidth, gain, if_gain, baseband_gain, ip='127.0.0.1', parent=None):
|
||||
super().__init__(frequency, sample_rate, bandwidth, gain, if_gain, baseband_gain, True, ip, parent)
|
||||
self.buf_size = settings.SPECTRUM_BUFFER_SIZE
|
||||
self.data = np.zeros(self.buf_size, dtype=np.complex64)
|
||||
self.x = None
|
||||
self.y = None
|
||||
|
||||
def run(self):
|
||||
logger.debug("Spectrum Thread: Init Process")
|
||||
self.initialize_process()
|
||||
logger.debug("Spectrum Thread: Process initialized")
|
||||
self.init_recv_socket()
|
||||
logger.debug("Spectrum Thread: Socket initialized")
|
||||
|
||||
recv = self.socket.recv
|
||||
rcvd = b""
|
||||
|
||||
try:
|
||||
logger.debug("Spectrum Thread: Enter main loop")
|
||||
while not self.isInterruptionRequested():
|
||||
try:
|
||||
rcvd += recv(32768) # Receive Buffer = 32768 Byte
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
if len(rcvd) < 8:
|
||||
self.stop("Stopped receiving, because no data transmitted anymore")
|
||||
return
|
||||
|
||||
if len(rcvd) % 8 != 0:
|
||||
continue
|
||||
|
||||
try:
|
||||
tmp = np.fromstring(rcvd, dtype=np.complex64)
|
||||
|
||||
len_tmp = len(tmp)
|
||||
|
||||
if self.data is None:
|
||||
self.data = np.zeros(self.buf_size, dtype=np.complex64) # type: np.ndarray
|
||||
|
||||
if self.current_index + len_tmp >= len(self.data):
|
||||
self.data[self.current_index:] = tmp[:len(self.data) - self.current_index]
|
||||
tmp = tmp[len(self.data) - self.current_index:]
|
||||
w = np.abs(np.fft.fft(self.data))
|
||||
freqs = np.fft.fftfreq(len(w), 1 / self.sample_rate)
|
||||
idx = np.argsort(freqs)
|
||||
self.x = freqs[idx].astype(np.float32)
|
||||
self.y = w[idx].astype(np.float32)
|
||||
|
||||
self.data = np.zeros(len(self.data), dtype=np.complex64)
|
||||
self.data[0:len(tmp)] = tmp
|
||||
self.current_index = len(tmp)
|
||||
continue
|
||||
|
||||
self.data[self.current_index:self.current_index + len_tmp] = tmp
|
||||
self.current_index += len_tmp
|
||||
rcvd = b""
|
||||
except ValueError:
|
||||
self.stop("Could not receive data. Is your Hardware ok?")
|
||||
except RuntimeError as e:
|
||||
logger.error("Spectrum thread crashed", str(e.args))
|
||||
@@ -0,0 +1,9 @@
|
||||
import sys
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
|
||||
def init_path():
|
||||
# Append script path at end to prevent conflicts in case of frozen interpreter
|
||||
sys.path.append(sys.path.pop(0))
|
||||
@@ -0,0 +1,90 @@
|
||||
import sys
|
||||
if sys.version_info[0] >= 3:
|
||||
from queue import Queue, Empty
|
||||
else:
|
||||
from Queue import Queue, Empty
|
||||
|
||||
from threading import Thread
|
||||
import time
|
||||
|
||||
|
||||
class InputHandlerThread(Thread):
|
||||
def __init__(self, device):
|
||||
Thread.__init__(self)
|
||||
self.queue = Queue()
|
||||
self.device = device
|
||||
self.daemon = True
|
||||
|
||||
t = Thread(target=self.enqueue_input, args=(sys.stdin, self.queue,))
|
||||
t.daemon = True # thread dies with the program
|
||||
t.start()
|
||||
|
||||
def enqueue_input(self, inp, queue):
|
||||
for line in iter(inp.readline, b''):
|
||||
queue.put(line)
|
||||
inp.close()
|
||||
|
||||
def read_input(self):
|
||||
result = []
|
||||
while True:
|
||||
try:
|
||||
result.append(self.queue.get_nowait())
|
||||
except Empty:
|
||||
break
|
||||
|
||||
result = b"".join(result)
|
||||
return result.decode("utf-8")
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
input_commands = self.read_input().split("\n")
|
||||
|
||||
for i in input_commands:
|
||||
if len(i) > 0:
|
||||
if i.startswith("SR:"):
|
||||
try:
|
||||
v = float(i.replace("SR:", "").split(" ")[-1])
|
||||
except ValueError:
|
||||
v = 1
|
||||
self.device.set_samp_rate(v)
|
||||
elif i.startswith("G:"):
|
||||
try:
|
||||
v = int(i.replace("G:", "").split(" ")[-1])
|
||||
except ValueError:
|
||||
v = 1
|
||||
self.device.set_gain(v)
|
||||
elif i.startswith("IFG:"):
|
||||
try:
|
||||
v = int(i.replace("IFG:", "").split(" ")[-1])
|
||||
except ValueError:
|
||||
v = 1
|
||||
self.device.set_if_gain(v)
|
||||
elif i.startswith("BBG:"):
|
||||
try:
|
||||
v = int(i.replace("BBG:", "").split(" ")[-1])
|
||||
except ValueError:
|
||||
v = 1
|
||||
self.device.set_baseband_gain(v)
|
||||
elif i.startswith("BW:"):
|
||||
try:
|
||||
v = float(i.replace("BW:", "").split(" ")[-1])
|
||||
except ValueError:
|
||||
v = 1
|
||||
self.device.set_bw(v)
|
||||
elif i.startswith("F:"):
|
||||
try:
|
||||
v = float(i.replace("F:", "").split(" ")[-1])
|
||||
except ValueError:
|
||||
v = 1
|
||||
self.device.set_freq(v)
|
||||
elif i.startswith("FC:"):
|
||||
try:
|
||||
v = float(i.replace("FC:", "").split(" ")[-1])
|
||||
except ValueError:
|
||||
v = 1
|
||||
self.device.set_freq_correction(v)
|
||||
elif i.startswith("DSM:"):
|
||||
print("GNU Radio does not support setting direct sampling mode live.")
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import re
|
||||
import sys
|
||||
|
||||
#TARGET = sys.argv[1] # e.g. airspy_recv.py
|
||||
TARGET = "funcube_recv.py"
|
||||
|
||||
variables = []
|
||||
used_variables = []
|
||||
|
||||
start_vars = False
|
||||
start_blocks = False
|
||||
with open("top_block.py", "r") as f:
|
||||
for line in f:
|
||||
if not start_vars and line.strip().startswith("# Variables"):
|
||||
start_vars = True
|
||||
elif start_vars and line.strip().startswith("self."):
|
||||
variables.append(re.search("= (.*) =", line).group(1))
|
||||
elif line.strip().startswith("# Blocks"):
|
||||
start_vars, start_blocks = False, True
|
||||
elif start_blocks and line.strip().startswith("self."):
|
||||
try:
|
||||
used_variables.append(re.search(r"\(([a-z\_0-9]*)[\)\,]", line).group(1))
|
||||
except AttributeError:
|
||||
pass
|
||||
elif line.strip().startswith("# Connections"):
|
||||
break
|
||||
|
||||
used_variables.append("port")
|
||||
|
||||
used_variables = list(filter(None, used_variables))
|
||||
|
||||
start_vars = False
|
||||
imports_written = False
|
||||
with open("top_block.py", "r") as r:
|
||||
with open(TARGET, "w") as f:
|
||||
for line in r:
|
||||
if line.strip().startswith("#"):
|
||||
if not imports_written:
|
||||
f.write("from optparse import OptionParser\n")
|
||||
f.write("from InputHandlerThread import InputHandlerThread\n")
|
||||
f.write("import Initializer\n")
|
||||
f.write("\nInitializer.init_path()\n")
|
||||
imports_written = True
|
||||
|
||||
if line.strip().startswith("def __init__"):
|
||||
f.write(line.replace("self", "self, " + ", ".join(used_variables)))
|
||||
continue
|
||||
|
||||
if not start_vars and line.strip().startswith("# Variables"):
|
||||
start_vars = True
|
||||
elif start_vars and line.strip().startswith("self."):
|
||||
var_name = re.search("= (.*) =", line).group(1)
|
||||
if var_name in used_variables:
|
||||
f.write(line[:line.rindex("=")]+"\n")
|
||||
continue
|
||||
|
||||
elif line.strip().startswith("# Blocks"):
|
||||
start_vars = False
|
||||
|
||||
if line.strip().startswith("def main("):
|
||||
f.write("if __name__ == '__main__':\n")
|
||||
f.write(" parser = OptionParser(usage='%prog: [options]')\n")
|
||||
f.write(" parser.add_option('-s', '--sample-rate', dest='sample_rate', default=100000)\n")
|
||||
f.write(" parser.add_option('-f', '--frequency', dest='frequency', default=433000)\n")
|
||||
f.write(" parser.add_option('-g', '--gain', dest='rf_gain', default=30)\n")
|
||||
f.write(" parser.add_option('-i', '--if-gain', dest='if_gain', default=30)\n")
|
||||
f.write(" parser.add_option('-b', '--bb-gain', dest='bb_gain', default=30)\n")
|
||||
f.write(" parser.add_option('-w', '--bandwidth', dest='bandwidth', default=250000)\n")
|
||||
f.write(" parser.add_option('-c', '--freq-correction', dest='freq_correction', default=0)\n")
|
||||
f.write(" parser.add_option('-d', '--direct-sampling', dest='direct_sampling', default=0)\n")
|
||||
f.write(" parser.add_option('-n', '--channel-index', dest='channel_index', default=0)\n")
|
||||
f.write(" parser.add_option('-a', '--antenna-index', dest='antenna_index', default=0)\n")
|
||||
f.write(" parser.add_option('-p', '--port', dest='port', default=1234)\n\n")
|
||||
f.write(" (options, args) = parser.parse_args()\n")
|
||||
args = ", ".join(["int(options.{})".format(var) for var in used_variables])
|
||||
f.write(" tb = top_block({})\n".format(args))
|
||||
f.write(" iht = InputHandlerThread(tb)\n")
|
||||
f.write(" iht.start()\n")
|
||||
f.write(" tb.start()\n")
|
||||
f.write(" tb.wait()\n")
|
||||
sys.exit(0)
|
||||
|
||||
if not line.strip().startswith("#"):
|
||||
f.write(line)
|
||||
@@ -0,0 +1,547 @@
|
||||
options:
|
||||
parameters:
|
||||
author: ''
|
||||
category: '[GRC Hier Blocks]'
|
||||
cmake_opt: ''
|
||||
comment: ''
|
||||
copyright: ''
|
||||
description: ''
|
||||
gen_cmake: 'On'
|
||||
gen_linking: dynamic
|
||||
generate_options: no_gui
|
||||
hier_block_src_path: '.:'
|
||||
id: top_block
|
||||
max_nouts: '0'
|
||||
output_language: python
|
||||
placement: (0,0)
|
||||
qt_qss_theme: ''
|
||||
realtime_scheduling: ''
|
||||
run: 'True'
|
||||
run_command: '{python} -u {filename}'
|
||||
run_options: run
|
||||
sizing_mode: fixed
|
||||
thread_safe_setters: ''
|
||||
title: ''
|
||||
window_size: ''
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [8, 8]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
blocks:
|
||||
- name: antenna_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [707, 5]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: bandwidth
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '250000'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [219, 7]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: bb_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [420, 5]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: channel_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [559, 91]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: direct_sampling_mode
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [707, 88]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: freq_correction
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [563, 5]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: frequency
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 433e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [319, 93]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: if_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [342, 6]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: port
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '1234'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [988, 44]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: rf_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [223, 96]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: sample_rate
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 3e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [423, 90]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: blocks_tcp_server_sink_0
|
||||
id: blocks_tcp_server_sink
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
comment: ''
|
||||
ipaddr: 127.0.0.1
|
||||
noblock: 'False'
|
||||
port: port
|
||||
type: complex
|
||||
vlen: '1'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [591, 313]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: osmosdr_source_0
|
||||
id: osmosdr_source
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
ant0: ''
|
||||
ant1: ''
|
||||
ant10: ''
|
||||
ant11: ''
|
||||
ant12: ''
|
||||
ant13: ''
|
||||
ant14: ''
|
||||
ant15: ''
|
||||
ant16: ''
|
||||
ant17: ''
|
||||
ant18: ''
|
||||
ant19: ''
|
||||
ant2: ''
|
||||
ant20: ''
|
||||
ant21: ''
|
||||
ant22: ''
|
||||
ant23: ''
|
||||
ant24: ''
|
||||
ant25: ''
|
||||
ant26: ''
|
||||
ant27: ''
|
||||
ant28: ''
|
||||
ant29: ''
|
||||
ant3: ''
|
||||
ant30: ''
|
||||
ant31: ''
|
||||
ant4: ''
|
||||
ant5: ''
|
||||
ant6: ''
|
||||
ant7: ''
|
||||
ant8: ''
|
||||
ant9: ''
|
||||
args: airspy
|
||||
bb_gain0: bb_gain
|
||||
bb_gain1: '20'
|
||||
bb_gain10: '20'
|
||||
bb_gain11: '20'
|
||||
bb_gain12: '20'
|
||||
bb_gain13: '20'
|
||||
bb_gain14: '20'
|
||||
bb_gain15: '20'
|
||||
bb_gain16: '20'
|
||||
bb_gain17: '20'
|
||||
bb_gain18: '20'
|
||||
bb_gain19: '20'
|
||||
bb_gain2: '20'
|
||||
bb_gain20: '20'
|
||||
bb_gain21: '20'
|
||||
bb_gain22: '20'
|
||||
bb_gain23: '20'
|
||||
bb_gain24: '20'
|
||||
bb_gain25: '20'
|
||||
bb_gain26: '20'
|
||||
bb_gain27: '20'
|
||||
bb_gain28: '20'
|
||||
bb_gain29: '20'
|
||||
bb_gain3: '20'
|
||||
bb_gain30: '20'
|
||||
bb_gain31: '20'
|
||||
bb_gain4: '20'
|
||||
bb_gain5: '20'
|
||||
bb_gain6: '20'
|
||||
bb_gain7: '20'
|
||||
bb_gain8: '20'
|
||||
bb_gain9: '20'
|
||||
bw0: bandwidth
|
||||
bw1: '0'
|
||||
bw10: '0'
|
||||
bw11: '0'
|
||||
bw12: '0'
|
||||
bw13: '0'
|
||||
bw14: '0'
|
||||
bw15: '0'
|
||||
bw16: '0'
|
||||
bw17: '0'
|
||||
bw18: '0'
|
||||
bw19: '0'
|
||||
bw2: '0'
|
||||
bw20: '0'
|
||||
bw21: '0'
|
||||
bw22: '0'
|
||||
bw23: '0'
|
||||
bw24: '0'
|
||||
bw25: '0'
|
||||
bw26: '0'
|
||||
bw27: '0'
|
||||
bw28: '0'
|
||||
bw29: '0'
|
||||
bw3: '0'
|
||||
bw30: '0'
|
||||
bw31: '0'
|
||||
bw4: '0'
|
||||
bw5: '0'
|
||||
bw6: '0'
|
||||
bw7: '0'
|
||||
bw8: '0'
|
||||
bw9: '0'
|
||||
clock_source0: ''
|
||||
clock_source1: ''
|
||||
clock_source2: ''
|
||||
clock_source3: ''
|
||||
clock_source4: ''
|
||||
clock_source5: ''
|
||||
clock_source6: ''
|
||||
clock_source7: ''
|
||||
comment: ''
|
||||
corr0: freq_correction
|
||||
corr1: '0'
|
||||
corr10: '0'
|
||||
corr11: '0'
|
||||
corr12: '0'
|
||||
corr13: '0'
|
||||
corr14: '0'
|
||||
corr15: '0'
|
||||
corr16: '0'
|
||||
corr17: '0'
|
||||
corr18: '0'
|
||||
corr19: '0'
|
||||
corr2: '0'
|
||||
corr20: '0'
|
||||
corr21: '0'
|
||||
corr22: '0'
|
||||
corr23: '0'
|
||||
corr24: '0'
|
||||
corr25: '0'
|
||||
corr26: '0'
|
||||
corr27: '0'
|
||||
corr28: '0'
|
||||
corr29: '0'
|
||||
corr3: '0'
|
||||
corr30: '0'
|
||||
corr31: '0'
|
||||
corr4: '0'
|
||||
corr5: '0'
|
||||
corr6: '0'
|
||||
corr7: '0'
|
||||
corr8: '0'
|
||||
corr9: '0'
|
||||
dc_offset_mode0: '0'
|
||||
dc_offset_mode1: '0'
|
||||
dc_offset_mode10: '0'
|
||||
dc_offset_mode11: '0'
|
||||
dc_offset_mode12: '0'
|
||||
dc_offset_mode13: '0'
|
||||
dc_offset_mode14: '0'
|
||||
dc_offset_mode15: '0'
|
||||
dc_offset_mode16: '0'
|
||||
dc_offset_mode17: '0'
|
||||
dc_offset_mode18: '0'
|
||||
dc_offset_mode19: '0'
|
||||
dc_offset_mode2: '0'
|
||||
dc_offset_mode20: '0'
|
||||
dc_offset_mode21: '0'
|
||||
dc_offset_mode22: '0'
|
||||
dc_offset_mode23: '0'
|
||||
dc_offset_mode24: '0'
|
||||
dc_offset_mode25: '0'
|
||||
dc_offset_mode26: '0'
|
||||
dc_offset_mode27: '0'
|
||||
dc_offset_mode28: '0'
|
||||
dc_offset_mode29: '0'
|
||||
dc_offset_mode3: '0'
|
||||
dc_offset_mode30: '0'
|
||||
dc_offset_mode31: '0'
|
||||
dc_offset_mode4: '0'
|
||||
dc_offset_mode5: '0'
|
||||
dc_offset_mode6: '0'
|
||||
dc_offset_mode7: '0'
|
||||
dc_offset_mode8: '0'
|
||||
dc_offset_mode9: '0'
|
||||
freq0: frequency
|
||||
freq1: 100e6
|
||||
freq10: 100e6
|
||||
freq11: 100e6
|
||||
freq12: 100e6
|
||||
freq13: 100e6
|
||||
freq14: 100e6
|
||||
freq15: 100e6
|
||||
freq16: 100e6
|
||||
freq17: 100e6
|
||||
freq18: 100e6
|
||||
freq19: 100e6
|
||||
freq2: 100e6
|
||||
freq20: 100e6
|
||||
freq21: 100e6
|
||||
freq22: 100e6
|
||||
freq23: 100e6
|
||||
freq24: 100e6
|
||||
freq25: 100e6
|
||||
freq26: 100e6
|
||||
freq27: 100e6
|
||||
freq28: 100e6
|
||||
freq29: 100e6
|
||||
freq3: 100e6
|
||||
freq30: 100e6
|
||||
freq31: 100e6
|
||||
freq4: 100e6
|
||||
freq5: 100e6
|
||||
freq6: 100e6
|
||||
freq7: 100e6
|
||||
freq8: 100e6
|
||||
freq9: 100e6
|
||||
gain0: rf_gain
|
||||
gain1: '10'
|
||||
gain10: '10'
|
||||
gain11: '10'
|
||||
gain12: '10'
|
||||
gain13: '10'
|
||||
gain14: '10'
|
||||
gain15: '10'
|
||||
gain16: '10'
|
||||
gain17: '10'
|
||||
gain18: '10'
|
||||
gain19: '10'
|
||||
gain2: '10'
|
||||
gain20: '10'
|
||||
gain21: '10'
|
||||
gain22: '10'
|
||||
gain23: '10'
|
||||
gain24: '10'
|
||||
gain25: '10'
|
||||
gain26: '10'
|
||||
gain27: '10'
|
||||
gain28: '10'
|
||||
gain29: '10'
|
||||
gain3: '10'
|
||||
gain30: '10'
|
||||
gain31: '10'
|
||||
gain4: '10'
|
||||
gain5: '10'
|
||||
gain6: '10'
|
||||
gain7: '10'
|
||||
gain8: '10'
|
||||
gain9: '10'
|
||||
gain_mode0: 'False'
|
||||
gain_mode1: 'False'
|
||||
gain_mode10: 'False'
|
||||
gain_mode11: 'False'
|
||||
gain_mode12: 'False'
|
||||
gain_mode13: 'False'
|
||||
gain_mode14: 'False'
|
||||
gain_mode15: 'False'
|
||||
gain_mode16: 'False'
|
||||
gain_mode17: 'False'
|
||||
gain_mode18: 'False'
|
||||
gain_mode19: 'False'
|
||||
gain_mode2: 'False'
|
||||
gain_mode20: 'False'
|
||||
gain_mode21: 'False'
|
||||
gain_mode22: 'False'
|
||||
gain_mode23: 'False'
|
||||
gain_mode24: 'False'
|
||||
gain_mode25: 'False'
|
||||
gain_mode26: 'False'
|
||||
gain_mode27: 'False'
|
||||
gain_mode28: 'False'
|
||||
gain_mode29: 'False'
|
||||
gain_mode3: 'False'
|
||||
gain_mode30: 'False'
|
||||
gain_mode31: 'False'
|
||||
gain_mode4: 'False'
|
||||
gain_mode5: 'False'
|
||||
gain_mode6: 'False'
|
||||
gain_mode7: 'False'
|
||||
gain_mode8: 'False'
|
||||
gain_mode9: 'False'
|
||||
if_gain0: if_gain
|
||||
if_gain1: '20'
|
||||
if_gain10: '20'
|
||||
if_gain11: '20'
|
||||
if_gain12: '20'
|
||||
if_gain13: '20'
|
||||
if_gain14: '20'
|
||||
if_gain15: '20'
|
||||
if_gain16: '20'
|
||||
if_gain17: '20'
|
||||
if_gain18: '20'
|
||||
if_gain19: '20'
|
||||
if_gain2: '20'
|
||||
if_gain20: '20'
|
||||
if_gain21: '20'
|
||||
if_gain22: '20'
|
||||
if_gain23: '20'
|
||||
if_gain24: '20'
|
||||
if_gain25: '20'
|
||||
if_gain26: '20'
|
||||
if_gain27: '20'
|
||||
if_gain28: '20'
|
||||
if_gain29: '20'
|
||||
if_gain3: '20'
|
||||
if_gain30: '20'
|
||||
if_gain31: '20'
|
||||
if_gain4: '20'
|
||||
if_gain5: '20'
|
||||
if_gain6: '20'
|
||||
if_gain7: '20'
|
||||
if_gain8: '20'
|
||||
if_gain9: '20'
|
||||
iq_balance_mode0: '0'
|
||||
iq_balance_mode1: '0'
|
||||
iq_balance_mode10: '0'
|
||||
iq_balance_mode11: '0'
|
||||
iq_balance_mode12: '0'
|
||||
iq_balance_mode13: '0'
|
||||
iq_balance_mode14: '0'
|
||||
iq_balance_mode15: '0'
|
||||
iq_balance_mode16: '0'
|
||||
iq_balance_mode17: '0'
|
||||
iq_balance_mode18: '0'
|
||||
iq_balance_mode19: '0'
|
||||
iq_balance_mode2: '0'
|
||||
iq_balance_mode20: '0'
|
||||
iq_balance_mode21: '0'
|
||||
iq_balance_mode22: '0'
|
||||
iq_balance_mode23: '0'
|
||||
iq_balance_mode24: '0'
|
||||
iq_balance_mode25: '0'
|
||||
iq_balance_mode26: '0'
|
||||
iq_balance_mode27: '0'
|
||||
iq_balance_mode28: '0'
|
||||
iq_balance_mode29: '0'
|
||||
iq_balance_mode3: '0'
|
||||
iq_balance_mode30: '0'
|
||||
iq_balance_mode31: '0'
|
||||
iq_balance_mode4: '0'
|
||||
iq_balance_mode5: '0'
|
||||
iq_balance_mode6: '0'
|
||||
iq_balance_mode7: '0'
|
||||
iq_balance_mode8: '0'
|
||||
iq_balance_mode9: '0'
|
||||
maxoutbuf: '0'
|
||||
minoutbuf: '0'
|
||||
nchan: '1'
|
||||
num_mboards: '1'
|
||||
sample_rate: sample_rate
|
||||
sync: sync
|
||||
time_source0: ''
|
||||
time_source1: ''
|
||||
time_source2: ''
|
||||
time_source3: ''
|
||||
time_source4: ''
|
||||
time_source5: ''
|
||||
time_source6: ''
|
||||
time_source7: ''
|
||||
type: fc32
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [219, 216]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
connections:
|
||||
- [osmosdr_source_0, '0', blocks_tcp_server_sink_0, '0']
|
||||
|
||||
metadata:
|
||||
file_format: 1
|
||||
@@ -0,0 +1,139 @@
|
||||
from optparse import OptionParser
|
||||
from InputHandlerThread import InputHandlerThread
|
||||
import Initializer
|
||||
|
||||
Initializer.init_path()
|
||||
|
||||
|
||||
import signal
|
||||
import sys
|
||||
|
||||
import osmosdr
|
||||
from gnuradio import blocks
|
||||
from gnuradio import gr
|
||||
|
||||
|
||||
class top_block(gr.top_block):
|
||||
|
||||
def __init__(self, sample_rate, frequency, freq_correction, rf_gain, if_gain, bb_gain, bandwidth, port):
|
||||
gr.top_block.__init__(self, "Top Block")
|
||||
|
||||
self.sample_rate = sample_rate
|
||||
self.rf_gain = rf_gain
|
||||
self.port = port
|
||||
self.if_gain = if_gain
|
||||
self.frequency = frequency
|
||||
self.freq_correction = freq_correction
|
||||
self.bb_gain = bb_gain
|
||||
self.bandwidth = bandwidth
|
||||
|
||||
self.osmosdr_source_0 = osmosdr.source(
|
||||
args="numchan=" + str(1) + " " + 'airspy'
|
||||
)
|
||||
self.osmosdr_source_0.set_time_unknown_pps(osmosdr.time_spec_t())
|
||||
self.osmosdr_source_0.set_sample_rate(sample_rate)
|
||||
self.osmosdr_source_0.set_center_freq(frequency, 0)
|
||||
self.osmosdr_source_0.set_freq_corr(freq_correction, 0)
|
||||
self.osmosdr_source_0.set_gain(rf_gain, 0)
|
||||
self.osmosdr_source_0.set_if_gain(if_gain, 0)
|
||||
self.osmosdr_source_0.set_bb_gain(bb_gain, 0)
|
||||
self.osmosdr_source_0.set_antenna('', 0)
|
||||
self.osmosdr_source_0.set_bandwidth(bandwidth, 0)
|
||||
self.blocks_tcp_server_sink_0 = blocks.tcp_server_sink(gr.sizeof_gr_complex * 1, '127.0.0.1', port, False)
|
||||
|
||||
self.connect((self.osmosdr_source_0, 0), (self.blocks_tcp_server_sink_0, 0))
|
||||
|
||||
def get_sample_rate(self):
|
||||
return self.sample_rate
|
||||
|
||||
def set_sample_rate(self, sample_rate):
|
||||
self.sample_rate = sample_rate
|
||||
self.osmosdr_source_0.set_sample_rate(self.sample_rate)
|
||||
|
||||
def get_rf_gain(self):
|
||||
return self.rf_gain
|
||||
|
||||
def set_rf_gain(self, rf_gain):
|
||||
self.rf_gain = rf_gain
|
||||
self.osmosdr_source_0.set_gain(self.rf_gain, 0)
|
||||
|
||||
def get_port(self):
|
||||
return self.port
|
||||
|
||||
def set_port(self, port):
|
||||
self.port = port
|
||||
|
||||
def get_if_gain(self):
|
||||
return self.if_gain
|
||||
|
||||
def set_if_gain(self, if_gain):
|
||||
self.if_gain = if_gain
|
||||
self.osmosdr_source_0.set_if_gain(self.if_gain, 0)
|
||||
|
||||
def get_frequency(self):
|
||||
return self.frequency
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
self.frequency = frequency
|
||||
self.osmosdr_source_0.set_center_freq(self.frequency, 0)
|
||||
|
||||
def get_freq_correction(self):
|
||||
return self.freq_correction
|
||||
|
||||
def set_freq_correction(self, freq_correction):
|
||||
self.freq_correction = freq_correction
|
||||
self.osmosdr_source_0.set_freq_corr(self.freq_correction, 0)
|
||||
|
||||
def get_direct_sampling_mode(self):
|
||||
return self.direct_sampling_mode
|
||||
|
||||
def set_direct_sampling_mode(self, direct_sampling_mode):
|
||||
self.direct_sampling_mode = direct_sampling_mode
|
||||
|
||||
def get_channel_index(self):
|
||||
return self.channel_index
|
||||
|
||||
def set_channel_index(self, channel_index):
|
||||
self.channel_index = channel_index
|
||||
|
||||
def get_bb_gain(self):
|
||||
return self.bb_gain
|
||||
|
||||
def set_bb_gain(self, bb_gain):
|
||||
self.bb_gain = bb_gain
|
||||
self.osmosdr_source_0.set_bb_gain(self.bb_gain, 0)
|
||||
|
||||
def get_bandwidth(self):
|
||||
return self.bandwidth
|
||||
|
||||
def set_bandwidth(self, bandwidth):
|
||||
self.bandwidth = bandwidth
|
||||
self.osmosdr_source_0.set_bandwidth(self.bandwidth, 0)
|
||||
|
||||
def get_antenna_index(self):
|
||||
return self.antenna_index
|
||||
|
||||
def set_antenna_index(self, antenna_index):
|
||||
self.antenna_index = antenna_index
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = OptionParser(usage='%prog: [options]')
|
||||
parser.add_option('-s', '--sample-rate', dest='sample_rate', default=100000)
|
||||
parser.add_option('-f', '--frequency', dest='frequency', default=433000)
|
||||
parser.add_option('-g', '--gain', dest='rf_gain', default=30)
|
||||
parser.add_option('-i', '--if-gain', dest='if_gain', default=30)
|
||||
parser.add_option('-b', '--bb-gain', dest='bb_gain', default=30)
|
||||
parser.add_option('-w', '--bandwidth', dest='bandwidth', default=250000)
|
||||
parser.add_option('-c', '--freq-correction', dest='freq_correction', default=0)
|
||||
parser.add_option('-d', '--direct-sampling', dest='direct_sampling', default=0)
|
||||
parser.add_option('-n', '--channel-index', dest='channel_index', default=0)
|
||||
parser.add_option('-a', '--antenna-index', dest='antenna_index', default=0)
|
||||
parser.add_option('-p', '--port', dest='port', default=1234)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
tb = top_block(int(options.sample_rate), int(options.frequency), int(options.freq_correction), int(options.rf_gain), int(options.if_gain), int(options.bb_gain), int(options.bandwidth), int(options.port))
|
||||
iht = InputHandlerThread(tb)
|
||||
iht.start()
|
||||
tb.start()
|
||||
tb.wait()
|
||||
@@ -0,0 +1,547 @@
|
||||
options:
|
||||
parameters:
|
||||
author: ''
|
||||
category: Custom
|
||||
cmake_opt: ''
|
||||
comment: ''
|
||||
copyright: ''
|
||||
description: ''
|
||||
gen_cmake: 'On'
|
||||
gen_linking: dynamic
|
||||
generate_options: no_gui
|
||||
hier_block_src_path: '.:'
|
||||
id: top_block
|
||||
max_nouts: '0'
|
||||
output_language: python
|
||||
placement: (0,0)
|
||||
qt_qss_theme: ''
|
||||
realtime_scheduling: ''
|
||||
run: 'True'
|
||||
run_command: '{python} -u {filename}'
|
||||
run_options: run
|
||||
sizing_mode: fixed
|
||||
thread_safe_setters: ''
|
||||
title: ''
|
||||
window_size: ''
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [8, 8]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
blocks:
|
||||
- name: antenna_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [707, 5]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: bandwidth
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '250000'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [219, 7]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: bb_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [420, 5]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: channel_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [559, 91]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: direct_sampling_mode
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [707, 88]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: freq_correction
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [563, 5]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: frequency
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 433e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [319, 93]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: if_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [342, 6]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: port
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '1234'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [988, 44]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: rf_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [223, 96]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: sample_rate
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 3e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [423, 90]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: blocks_tcp_server_sink_0
|
||||
id: blocks_tcp_server_sink
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
comment: ''
|
||||
ipaddr: 127.0.0.1
|
||||
noblock: 'False'
|
||||
port: port
|
||||
type: complex
|
||||
vlen: '1'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [591, 313]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: osmosdr_source_0
|
||||
id: osmosdr_source
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
ant0: ''
|
||||
ant1: ''
|
||||
ant10: ''
|
||||
ant11: ''
|
||||
ant12: ''
|
||||
ant13: ''
|
||||
ant14: ''
|
||||
ant15: ''
|
||||
ant16: ''
|
||||
ant17: ''
|
||||
ant18: ''
|
||||
ant19: ''
|
||||
ant2: ''
|
||||
ant20: ''
|
||||
ant21: ''
|
||||
ant22: ''
|
||||
ant23: ''
|
||||
ant24: ''
|
||||
ant25: ''
|
||||
ant26: ''
|
||||
ant27: ''
|
||||
ant28: ''
|
||||
ant29: ''
|
||||
ant3: ''
|
||||
ant30: ''
|
||||
ant31: ''
|
||||
ant4: ''
|
||||
ant5: ''
|
||||
ant6: ''
|
||||
ant7: ''
|
||||
ant8: ''
|
||||
ant9: ''
|
||||
args: bladerf
|
||||
bb_gain0: bb_gain
|
||||
bb_gain1: '20'
|
||||
bb_gain10: '20'
|
||||
bb_gain11: '20'
|
||||
bb_gain12: '20'
|
||||
bb_gain13: '20'
|
||||
bb_gain14: '20'
|
||||
bb_gain15: '20'
|
||||
bb_gain16: '20'
|
||||
bb_gain17: '20'
|
||||
bb_gain18: '20'
|
||||
bb_gain19: '20'
|
||||
bb_gain2: '20'
|
||||
bb_gain20: '20'
|
||||
bb_gain21: '20'
|
||||
bb_gain22: '20'
|
||||
bb_gain23: '20'
|
||||
bb_gain24: '20'
|
||||
bb_gain25: '20'
|
||||
bb_gain26: '20'
|
||||
bb_gain27: '20'
|
||||
bb_gain28: '20'
|
||||
bb_gain29: '20'
|
||||
bb_gain3: '20'
|
||||
bb_gain30: '20'
|
||||
bb_gain31: '20'
|
||||
bb_gain4: '20'
|
||||
bb_gain5: '20'
|
||||
bb_gain6: '20'
|
||||
bb_gain7: '20'
|
||||
bb_gain8: '20'
|
||||
bb_gain9: '20'
|
||||
bw0: bandwidth
|
||||
bw1: '0'
|
||||
bw10: '0'
|
||||
bw11: '0'
|
||||
bw12: '0'
|
||||
bw13: '0'
|
||||
bw14: '0'
|
||||
bw15: '0'
|
||||
bw16: '0'
|
||||
bw17: '0'
|
||||
bw18: '0'
|
||||
bw19: '0'
|
||||
bw2: '0'
|
||||
bw20: '0'
|
||||
bw21: '0'
|
||||
bw22: '0'
|
||||
bw23: '0'
|
||||
bw24: '0'
|
||||
bw25: '0'
|
||||
bw26: '0'
|
||||
bw27: '0'
|
||||
bw28: '0'
|
||||
bw29: '0'
|
||||
bw3: '0'
|
||||
bw30: '0'
|
||||
bw31: '0'
|
||||
bw4: '0'
|
||||
bw5: '0'
|
||||
bw6: '0'
|
||||
bw7: '0'
|
||||
bw8: '0'
|
||||
bw9: '0'
|
||||
clock_source0: ''
|
||||
clock_source1: ''
|
||||
clock_source2: ''
|
||||
clock_source3: ''
|
||||
clock_source4: ''
|
||||
clock_source5: ''
|
||||
clock_source6: ''
|
||||
clock_source7: ''
|
||||
comment: ''
|
||||
corr0: freq_correction
|
||||
corr1: '0'
|
||||
corr10: '0'
|
||||
corr11: '0'
|
||||
corr12: '0'
|
||||
corr13: '0'
|
||||
corr14: '0'
|
||||
corr15: '0'
|
||||
corr16: '0'
|
||||
corr17: '0'
|
||||
corr18: '0'
|
||||
corr19: '0'
|
||||
corr2: '0'
|
||||
corr20: '0'
|
||||
corr21: '0'
|
||||
corr22: '0'
|
||||
corr23: '0'
|
||||
corr24: '0'
|
||||
corr25: '0'
|
||||
corr26: '0'
|
||||
corr27: '0'
|
||||
corr28: '0'
|
||||
corr29: '0'
|
||||
corr3: '0'
|
||||
corr30: '0'
|
||||
corr31: '0'
|
||||
corr4: '0'
|
||||
corr5: '0'
|
||||
corr6: '0'
|
||||
corr7: '0'
|
||||
corr8: '0'
|
||||
corr9: '0'
|
||||
dc_offset_mode0: '0'
|
||||
dc_offset_mode1: '0'
|
||||
dc_offset_mode10: '0'
|
||||
dc_offset_mode11: '0'
|
||||
dc_offset_mode12: '0'
|
||||
dc_offset_mode13: '0'
|
||||
dc_offset_mode14: '0'
|
||||
dc_offset_mode15: '0'
|
||||
dc_offset_mode16: '0'
|
||||
dc_offset_mode17: '0'
|
||||
dc_offset_mode18: '0'
|
||||
dc_offset_mode19: '0'
|
||||
dc_offset_mode2: '0'
|
||||
dc_offset_mode20: '0'
|
||||
dc_offset_mode21: '0'
|
||||
dc_offset_mode22: '0'
|
||||
dc_offset_mode23: '0'
|
||||
dc_offset_mode24: '0'
|
||||
dc_offset_mode25: '0'
|
||||
dc_offset_mode26: '0'
|
||||
dc_offset_mode27: '0'
|
||||
dc_offset_mode28: '0'
|
||||
dc_offset_mode29: '0'
|
||||
dc_offset_mode3: '0'
|
||||
dc_offset_mode30: '0'
|
||||
dc_offset_mode31: '0'
|
||||
dc_offset_mode4: '0'
|
||||
dc_offset_mode5: '0'
|
||||
dc_offset_mode6: '0'
|
||||
dc_offset_mode7: '0'
|
||||
dc_offset_mode8: '0'
|
||||
dc_offset_mode9: '0'
|
||||
freq0: frequency
|
||||
freq1: 100e6
|
||||
freq10: 100e6
|
||||
freq11: 100e6
|
||||
freq12: 100e6
|
||||
freq13: 100e6
|
||||
freq14: 100e6
|
||||
freq15: 100e6
|
||||
freq16: 100e6
|
||||
freq17: 100e6
|
||||
freq18: 100e6
|
||||
freq19: 100e6
|
||||
freq2: 100e6
|
||||
freq20: 100e6
|
||||
freq21: 100e6
|
||||
freq22: 100e6
|
||||
freq23: 100e6
|
||||
freq24: 100e6
|
||||
freq25: 100e6
|
||||
freq26: 100e6
|
||||
freq27: 100e6
|
||||
freq28: 100e6
|
||||
freq29: 100e6
|
||||
freq3: 100e6
|
||||
freq30: 100e6
|
||||
freq31: 100e6
|
||||
freq4: 100e6
|
||||
freq5: 100e6
|
||||
freq6: 100e6
|
||||
freq7: 100e6
|
||||
freq8: 100e6
|
||||
freq9: 100e6
|
||||
gain0: rf_gain
|
||||
gain1: '10'
|
||||
gain10: '10'
|
||||
gain11: '10'
|
||||
gain12: '10'
|
||||
gain13: '10'
|
||||
gain14: '10'
|
||||
gain15: '10'
|
||||
gain16: '10'
|
||||
gain17: '10'
|
||||
gain18: '10'
|
||||
gain19: '10'
|
||||
gain2: '10'
|
||||
gain20: '10'
|
||||
gain21: '10'
|
||||
gain22: '10'
|
||||
gain23: '10'
|
||||
gain24: '10'
|
||||
gain25: '10'
|
||||
gain26: '10'
|
||||
gain27: '10'
|
||||
gain28: '10'
|
||||
gain29: '10'
|
||||
gain3: '10'
|
||||
gain30: '10'
|
||||
gain31: '10'
|
||||
gain4: '10'
|
||||
gain5: '10'
|
||||
gain6: '10'
|
||||
gain7: '10'
|
||||
gain8: '10'
|
||||
gain9: '10'
|
||||
gain_mode0: 'False'
|
||||
gain_mode1: 'False'
|
||||
gain_mode10: 'False'
|
||||
gain_mode11: 'False'
|
||||
gain_mode12: 'False'
|
||||
gain_mode13: 'False'
|
||||
gain_mode14: 'False'
|
||||
gain_mode15: 'False'
|
||||
gain_mode16: 'False'
|
||||
gain_mode17: 'False'
|
||||
gain_mode18: 'False'
|
||||
gain_mode19: 'False'
|
||||
gain_mode2: 'False'
|
||||
gain_mode20: 'False'
|
||||
gain_mode21: 'False'
|
||||
gain_mode22: 'False'
|
||||
gain_mode23: 'False'
|
||||
gain_mode24: 'False'
|
||||
gain_mode25: 'False'
|
||||
gain_mode26: 'False'
|
||||
gain_mode27: 'False'
|
||||
gain_mode28: 'False'
|
||||
gain_mode29: 'False'
|
||||
gain_mode3: 'False'
|
||||
gain_mode30: 'False'
|
||||
gain_mode31: 'False'
|
||||
gain_mode4: 'False'
|
||||
gain_mode5: 'False'
|
||||
gain_mode6: 'False'
|
||||
gain_mode7: 'False'
|
||||
gain_mode8: 'False'
|
||||
gain_mode9: 'False'
|
||||
if_gain0: if_gain
|
||||
if_gain1: '20'
|
||||
if_gain10: '20'
|
||||
if_gain11: '20'
|
||||
if_gain12: '20'
|
||||
if_gain13: '20'
|
||||
if_gain14: '20'
|
||||
if_gain15: '20'
|
||||
if_gain16: '20'
|
||||
if_gain17: '20'
|
||||
if_gain18: '20'
|
||||
if_gain19: '20'
|
||||
if_gain2: '20'
|
||||
if_gain20: '20'
|
||||
if_gain21: '20'
|
||||
if_gain22: '20'
|
||||
if_gain23: '20'
|
||||
if_gain24: '20'
|
||||
if_gain25: '20'
|
||||
if_gain26: '20'
|
||||
if_gain27: '20'
|
||||
if_gain28: '20'
|
||||
if_gain29: '20'
|
||||
if_gain3: '20'
|
||||
if_gain30: '20'
|
||||
if_gain31: '20'
|
||||
if_gain4: '20'
|
||||
if_gain5: '20'
|
||||
if_gain6: '20'
|
||||
if_gain7: '20'
|
||||
if_gain8: '20'
|
||||
if_gain9: '20'
|
||||
iq_balance_mode0: '0'
|
||||
iq_balance_mode1: '0'
|
||||
iq_balance_mode10: '0'
|
||||
iq_balance_mode11: '0'
|
||||
iq_balance_mode12: '0'
|
||||
iq_balance_mode13: '0'
|
||||
iq_balance_mode14: '0'
|
||||
iq_balance_mode15: '0'
|
||||
iq_balance_mode16: '0'
|
||||
iq_balance_mode17: '0'
|
||||
iq_balance_mode18: '0'
|
||||
iq_balance_mode19: '0'
|
||||
iq_balance_mode2: '0'
|
||||
iq_balance_mode20: '0'
|
||||
iq_balance_mode21: '0'
|
||||
iq_balance_mode22: '0'
|
||||
iq_balance_mode23: '0'
|
||||
iq_balance_mode24: '0'
|
||||
iq_balance_mode25: '0'
|
||||
iq_balance_mode26: '0'
|
||||
iq_balance_mode27: '0'
|
||||
iq_balance_mode28: '0'
|
||||
iq_balance_mode29: '0'
|
||||
iq_balance_mode3: '0'
|
||||
iq_balance_mode30: '0'
|
||||
iq_balance_mode31: '0'
|
||||
iq_balance_mode4: '0'
|
||||
iq_balance_mode5: '0'
|
||||
iq_balance_mode6: '0'
|
||||
iq_balance_mode7: '0'
|
||||
iq_balance_mode8: '0'
|
||||
iq_balance_mode9: '0'
|
||||
maxoutbuf: '0'
|
||||
minoutbuf: '0'
|
||||
nchan: '1'
|
||||
num_mboards: '1'
|
||||
sample_rate: sample_rate
|
||||
sync: sync
|
||||
time_source0: ''
|
||||
time_source1: ''
|
||||
time_source2: ''
|
||||
time_source3: ''
|
||||
time_source4: ''
|
||||
time_source5: ''
|
||||
time_source6: ''
|
||||
time_source7: ''
|
||||
type: fc32
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [219, 216]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
connections:
|
||||
- [osmosdr_source_0, '0', blocks_tcp_server_sink_0, '0']
|
||||
|
||||
metadata:
|
||||
file_format: 1
|
||||
@@ -0,0 +1,137 @@
|
||||
from optparse import OptionParser
|
||||
|
||||
import Initializer
|
||||
from InputHandlerThread import InputHandlerThread
|
||||
|
||||
Initializer.init_path()
|
||||
|
||||
from gnuradio import blocks
|
||||
from gnuradio import gr
|
||||
import osmosdr
|
||||
|
||||
|
||||
class top_block(gr.top_block):
|
||||
|
||||
def __init__(self, sample_rate, frequency, freq_correction, rf_gain, if_gain, bb_gain, bandwidth, port):
|
||||
gr.top_block.__init__(self, "Top Block")
|
||||
|
||||
self.sample_rate = sample_rate
|
||||
self.rf_gain = rf_gain
|
||||
self.port = port
|
||||
self.if_gain = if_gain
|
||||
self.frequency = frequency
|
||||
self.freq_correction = freq_correction
|
||||
self.bb_gain = bb_gain
|
||||
self.bandwidth = bandwidth
|
||||
|
||||
self.osmosdr_source_0 = osmosdr.source(
|
||||
args="numchan=" + str(1) + " " + 'bladerf'
|
||||
)
|
||||
self.osmosdr_source_0.set_time_unknown_pps(osmosdr.time_spec_t())
|
||||
self.osmosdr_source_0.set_sample_rate(sample_rate)
|
||||
self.osmosdr_source_0.set_center_freq(frequency, 0)
|
||||
self.osmosdr_source_0.set_freq_corr(freq_correction, 0)
|
||||
self.osmosdr_source_0.set_gain(rf_gain, 0)
|
||||
self.osmosdr_source_0.set_if_gain(if_gain, 0)
|
||||
self.osmosdr_source_0.set_bb_gain(bb_gain, 0)
|
||||
self.osmosdr_source_0.set_antenna('', 0)
|
||||
self.osmosdr_source_0.set_bandwidth(bandwidth, 0)
|
||||
self.blocks_tcp_server_sink_0 = blocks.tcp_server_sink(gr.sizeof_gr_complex * 1, '127.0.0.1', port, False)
|
||||
|
||||
self.connect((self.osmosdr_source_0, 0), (self.blocks_tcp_server_sink_0, 0))
|
||||
|
||||
def get_sample_rate(self):
|
||||
return self.sample_rate
|
||||
|
||||
def set_sample_rate(self, sample_rate):
|
||||
self.sample_rate = sample_rate
|
||||
self.osmosdr_source_0.set_sample_rate(self.sample_rate)
|
||||
|
||||
def get_rf_gain(self):
|
||||
return self.rf_gain
|
||||
|
||||
def set_rf_gain(self, rf_gain):
|
||||
self.rf_gain = rf_gain
|
||||
self.osmosdr_source_0.set_gain(self.rf_gain, 0)
|
||||
|
||||
def get_port(self):
|
||||
return self.port
|
||||
|
||||
def set_port(self, port):
|
||||
self.port = port
|
||||
|
||||
def get_if_gain(self):
|
||||
return self.if_gain
|
||||
|
||||
def set_if_gain(self, if_gain):
|
||||
self.if_gain = if_gain
|
||||
self.osmosdr_source_0.set_if_gain(self.if_gain, 0)
|
||||
|
||||
def get_frequency(self):
|
||||
return self.frequency
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
self.frequency = frequency
|
||||
self.osmosdr_source_0.set_center_freq(self.frequency, 0)
|
||||
|
||||
def get_freq_correction(self):
|
||||
return self.freq_correction
|
||||
|
||||
def set_freq_correction(self, freq_correction):
|
||||
self.freq_correction = freq_correction
|
||||
self.osmosdr_source_0.set_freq_corr(self.freq_correction, 0)
|
||||
|
||||
def get_direct_sampling_mode(self):
|
||||
return self.direct_sampling_mode
|
||||
|
||||
def set_direct_sampling_mode(self, direct_sampling_mode):
|
||||
self.direct_sampling_mode = direct_sampling_mode
|
||||
|
||||
def get_channel_index(self):
|
||||
return self.channel_index
|
||||
|
||||
def set_channel_index(self, channel_index):
|
||||
self.channel_index = channel_index
|
||||
|
||||
def get_bb_gain(self):
|
||||
return self.bb_gain
|
||||
|
||||
def set_bb_gain(self, bb_gain):
|
||||
self.bb_gain = bb_gain
|
||||
self.osmosdr_source_0.set_bb_gain(self.bb_gain, 0)
|
||||
|
||||
def get_bandwidth(self):
|
||||
return self.bandwidth
|
||||
|
||||
def set_bandwidth(self, bandwidth):
|
||||
self.bandwidth = bandwidth
|
||||
self.osmosdr_source_0.set_bandwidth(self.bandwidth, 0)
|
||||
|
||||
def get_antenna_index(self):
|
||||
return self.antenna_index
|
||||
|
||||
def set_antenna_index(self, antenna_index):
|
||||
self.antenna_index = antenna_index
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = OptionParser(usage='%prog: [options]')
|
||||
parser.add_option('-s', '--sample-rate', dest='sample_rate', default=100000)
|
||||
parser.add_option('-f', '--frequency', dest='frequency', default=433000)
|
||||
parser.add_option('-g', '--gain', dest='rf_gain', default=30)
|
||||
parser.add_option('-i', '--if-gain', dest='if_gain', default=30)
|
||||
parser.add_option('-b', '--bb-gain', dest='bb_gain', default=30)
|
||||
parser.add_option('-w', '--bandwidth', dest='bandwidth', default=250000)
|
||||
parser.add_option('-c', '--freq-correction', dest='freq_correction', default=0)
|
||||
parser.add_option('-d', '--direct-sampling', dest='direct_sampling', default=0)
|
||||
parser.add_option('-n', '--channel-index', dest='channel_index', default=0)
|
||||
parser.add_option('-a', '--antenna-index', dest='antenna_index', default=0)
|
||||
parser.add_option('-p', '--port', dest='port', default=1234)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
tb = top_block(int(options.sample_rate), int(options.frequency), int(options.freq_correction), int(options.rf_gain),
|
||||
int(options.if_gain), int(options.bb_gain), int(options.bandwidth), int(options.port))
|
||||
iht = InputHandlerThread(tb)
|
||||
iht.start()
|
||||
tb.start()
|
||||
tb.wait()
|
||||
@@ -0,0 +1,454 @@
|
||||
options:
|
||||
parameters:
|
||||
author: ''
|
||||
category: Custom
|
||||
cmake_opt: ''
|
||||
comment: ''
|
||||
copyright: ''
|
||||
description: ''
|
||||
gen_cmake: 'On'
|
||||
gen_linking: dynamic
|
||||
generate_options: no_gui
|
||||
hier_block_src_path: '.:'
|
||||
id: top_block
|
||||
max_nouts: '0'
|
||||
output_language: python
|
||||
placement: (0,0)
|
||||
qt_qss_theme: ''
|
||||
realtime_scheduling: ''
|
||||
run: 'True'
|
||||
run_command: '{python} -u {filename}'
|
||||
run_options: run
|
||||
sizing_mode: fixed
|
||||
thread_safe_setters: ''
|
||||
title: ''
|
||||
window_size: ''
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [8, 8]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
blocks:
|
||||
- name: antenna_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [705, 7]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: bandwidth
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '250000'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [217, 9]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: bb_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [418, 7]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: channel_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [557, 93]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: direct_sampling_mode
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [705, 90]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: freq_correction
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [561, 7]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: frequency
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 433e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [317, 95]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: if_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [340, 8]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: port
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '1234'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [986, 46]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: rf_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [221, 98]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: sample_rate
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 3e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [421, 92]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: blocks_udp_source_0
|
||||
id: blocks_udp_source
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
comment: ''
|
||||
eof: 'False'
|
||||
ipaddr: 127.0.0.1
|
||||
maxoutbuf: '0'
|
||||
minoutbuf: '0'
|
||||
port: port
|
||||
psize: '65536'
|
||||
type: complex
|
||||
vlen: '1'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [349, 269]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: osmosdr_sink_0
|
||||
id: osmosdr_sink
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
ant0: ''
|
||||
ant1: ''
|
||||
ant10: ''
|
||||
ant11: ''
|
||||
ant12: ''
|
||||
ant13: ''
|
||||
ant14: ''
|
||||
ant15: ''
|
||||
ant16: ''
|
||||
ant17: ''
|
||||
ant18: ''
|
||||
ant19: ''
|
||||
ant2: ''
|
||||
ant20: ''
|
||||
ant21: ''
|
||||
ant22: ''
|
||||
ant23: ''
|
||||
ant24: ''
|
||||
ant25: ''
|
||||
ant26: ''
|
||||
ant27: ''
|
||||
ant28: ''
|
||||
ant29: ''
|
||||
ant3: ''
|
||||
ant30: ''
|
||||
ant31: ''
|
||||
ant4: ''
|
||||
ant5: ''
|
||||
ant6: ''
|
||||
ant7: ''
|
||||
ant8: ''
|
||||
ant9: ''
|
||||
args: bladerf
|
||||
bb_gain0: bb_gain
|
||||
bb_gain1: '20'
|
||||
bb_gain10: '20'
|
||||
bb_gain11: '20'
|
||||
bb_gain12: '20'
|
||||
bb_gain13: '20'
|
||||
bb_gain14: '20'
|
||||
bb_gain15: '20'
|
||||
bb_gain16: '20'
|
||||
bb_gain17: '20'
|
||||
bb_gain18: '20'
|
||||
bb_gain19: '20'
|
||||
bb_gain2: '20'
|
||||
bb_gain20: '20'
|
||||
bb_gain21: '20'
|
||||
bb_gain22: '20'
|
||||
bb_gain23: '20'
|
||||
bb_gain24: '20'
|
||||
bb_gain25: '20'
|
||||
bb_gain26: '20'
|
||||
bb_gain27: '20'
|
||||
bb_gain28: '20'
|
||||
bb_gain29: '20'
|
||||
bb_gain3: '20'
|
||||
bb_gain30: '20'
|
||||
bb_gain31: '20'
|
||||
bb_gain4: '20'
|
||||
bb_gain5: '20'
|
||||
bb_gain6: '20'
|
||||
bb_gain7: '20'
|
||||
bb_gain8: '20'
|
||||
bb_gain9: '20'
|
||||
bw0: bandwidth
|
||||
bw1: '0'
|
||||
bw10: '0'
|
||||
bw11: '0'
|
||||
bw12: '0'
|
||||
bw13: '0'
|
||||
bw14: '0'
|
||||
bw15: '0'
|
||||
bw16: '0'
|
||||
bw17: '0'
|
||||
bw18: '0'
|
||||
bw19: '0'
|
||||
bw2: '0'
|
||||
bw20: '0'
|
||||
bw21: '0'
|
||||
bw22: '0'
|
||||
bw23: '0'
|
||||
bw24: '0'
|
||||
bw25: '0'
|
||||
bw26: '0'
|
||||
bw27: '0'
|
||||
bw28: '0'
|
||||
bw29: '0'
|
||||
bw3: '0'
|
||||
bw30: '0'
|
||||
bw31: '0'
|
||||
bw4: '0'
|
||||
bw5: '0'
|
||||
bw6: '0'
|
||||
bw7: '0'
|
||||
bw8: '0'
|
||||
bw9: '0'
|
||||
clock_source0: ''
|
||||
clock_source1: ''
|
||||
clock_source2: ''
|
||||
clock_source3: ''
|
||||
clock_source4: ''
|
||||
clock_source5: ''
|
||||
clock_source6: ''
|
||||
clock_source7: ''
|
||||
comment: ''
|
||||
corr0: freq_correction
|
||||
corr1: '0'
|
||||
corr10: '0'
|
||||
corr11: '0'
|
||||
corr12: '0'
|
||||
corr13: '0'
|
||||
corr14: '0'
|
||||
corr15: '0'
|
||||
corr16: '0'
|
||||
corr17: '0'
|
||||
corr18: '0'
|
||||
corr19: '0'
|
||||
corr2: '0'
|
||||
corr20: '0'
|
||||
corr21: '0'
|
||||
corr22: '0'
|
||||
corr23: '0'
|
||||
corr24: '0'
|
||||
corr25: '0'
|
||||
corr26: '0'
|
||||
corr27: '0'
|
||||
corr28: '0'
|
||||
corr29: '0'
|
||||
corr3: '0'
|
||||
corr30: '0'
|
||||
corr31: '0'
|
||||
corr4: '0'
|
||||
corr5: '0'
|
||||
corr6: '0'
|
||||
corr7: '0'
|
||||
corr8: '0'
|
||||
corr9: '0'
|
||||
freq0: frequency
|
||||
freq1: 100e6
|
||||
freq10: 100e6
|
||||
freq11: 100e6
|
||||
freq12: 100e6
|
||||
freq13: 100e6
|
||||
freq14: 100e6
|
||||
freq15: 100e6
|
||||
freq16: 100e6
|
||||
freq17: 100e6
|
||||
freq18: 100e6
|
||||
freq19: 100e6
|
||||
freq2: 100e6
|
||||
freq20: 100e6
|
||||
freq21: 100e6
|
||||
freq22: 100e6
|
||||
freq23: 100e6
|
||||
freq24: 100e6
|
||||
freq25: 100e6
|
||||
freq26: 100e6
|
||||
freq27: 100e6
|
||||
freq28: 100e6
|
||||
freq29: 100e6
|
||||
freq3: 100e6
|
||||
freq30: 100e6
|
||||
freq31: 100e6
|
||||
freq4: 100e6
|
||||
freq5: 100e6
|
||||
freq6: 100e6
|
||||
freq7: 100e6
|
||||
freq8: 100e6
|
||||
freq9: 100e6
|
||||
gain0: rf_gain
|
||||
gain1: '10'
|
||||
gain10: '10'
|
||||
gain11: '10'
|
||||
gain12: '10'
|
||||
gain13: '10'
|
||||
gain14: '10'
|
||||
gain15: '10'
|
||||
gain16: '10'
|
||||
gain17: '10'
|
||||
gain18: '10'
|
||||
gain19: '10'
|
||||
gain2: '10'
|
||||
gain20: '10'
|
||||
gain21: '10'
|
||||
gain22: '10'
|
||||
gain23: '10'
|
||||
gain24: '10'
|
||||
gain25: '10'
|
||||
gain26: '10'
|
||||
gain27: '10'
|
||||
gain28: '10'
|
||||
gain29: '10'
|
||||
gain3: '10'
|
||||
gain30: '10'
|
||||
gain31: '10'
|
||||
gain4: '10'
|
||||
gain5: '10'
|
||||
gain6: '10'
|
||||
gain7: '10'
|
||||
gain8: '10'
|
||||
gain9: '10'
|
||||
if_gain0: if_gain
|
||||
if_gain1: '20'
|
||||
if_gain10: '20'
|
||||
if_gain11: '20'
|
||||
if_gain12: '20'
|
||||
if_gain13: '20'
|
||||
if_gain14: '20'
|
||||
if_gain15: '20'
|
||||
if_gain16: '20'
|
||||
if_gain17: '20'
|
||||
if_gain18: '20'
|
||||
if_gain19: '20'
|
||||
if_gain2: '20'
|
||||
if_gain20: '20'
|
||||
if_gain21: '20'
|
||||
if_gain22: '20'
|
||||
if_gain23: '20'
|
||||
if_gain24: '20'
|
||||
if_gain25: '20'
|
||||
if_gain26: '20'
|
||||
if_gain27: '20'
|
||||
if_gain28: '20'
|
||||
if_gain29: '20'
|
||||
if_gain3: '20'
|
||||
if_gain30: '20'
|
||||
if_gain31: '20'
|
||||
if_gain4: '20'
|
||||
if_gain5: '20'
|
||||
if_gain6: '20'
|
||||
if_gain7: '20'
|
||||
if_gain8: '20'
|
||||
if_gain9: '20'
|
||||
maxoutbuf: '0'
|
||||
minoutbuf: '0'
|
||||
nchan: '1'
|
||||
num_mboards: '1'
|
||||
sample_rate: sample_rate
|
||||
sync: sync
|
||||
time_source0: ''
|
||||
time_source1: ''
|
||||
time_source2: ''
|
||||
time_source3: ''
|
||||
time_source4: ''
|
||||
time_source5: ''
|
||||
time_source6: ''
|
||||
time_source7: ''
|
||||
type: fc32
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [746, 195]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
connections:
|
||||
- [blocks_udp_source_0, '0', osmosdr_sink_0, '0']
|
||||
|
||||
metadata:
|
||||
file_format: 1
|
||||
@@ -0,0 +1,137 @@
|
||||
from optparse import OptionParser
|
||||
|
||||
import Initializer
|
||||
from InputHandlerThread import InputHandlerThread
|
||||
|
||||
Initializer.init_path()
|
||||
|
||||
from gnuradio import blocks
|
||||
from gnuradio import gr
|
||||
import osmosdr
|
||||
|
||||
|
||||
class top_block(gr.top_block):
|
||||
|
||||
def __init__(self, sample_rate, frequency, freq_correction, rf_gain, if_gain, bb_gain, bandwidth, port):
|
||||
gr.top_block.__init__(self, "Top Block")
|
||||
|
||||
self.sample_rate = sample_rate
|
||||
self.rf_gain = rf_gain
|
||||
self.port = port
|
||||
self.if_gain = if_gain
|
||||
self.frequency = frequency
|
||||
self.freq_correction = freq_correction
|
||||
self.bb_gain = bb_gain
|
||||
self.bandwidth = bandwidth
|
||||
|
||||
self.osmosdr_sink_0 = osmosdr.sink(
|
||||
args="numchan=" + str(1) + " " + 'bladerf'
|
||||
)
|
||||
self.osmosdr_sink_0.set_time_unknown_pps(osmosdr.time_spec_t())
|
||||
self.osmosdr_sink_0.set_sample_rate(sample_rate)
|
||||
self.osmosdr_sink_0.set_center_freq(frequency, 0)
|
||||
self.osmosdr_sink_0.set_freq_corr(freq_correction, 0)
|
||||
self.osmosdr_sink_0.set_gain(rf_gain, 0)
|
||||
self.osmosdr_sink_0.set_if_gain(if_gain, 0)
|
||||
self.osmosdr_sink_0.set_bb_gain(bb_gain, 0)
|
||||
self.osmosdr_sink_0.set_antenna('', 0)
|
||||
self.osmosdr_sink_0.set_bandwidth(bandwidth, 0)
|
||||
self.blocks_udp_source_0 = blocks.udp_source(gr.sizeof_gr_complex * 1, '127.0.0.1', port, 65536, False)
|
||||
|
||||
self.connect((self.blocks_udp_source_0, 0), (self.osmosdr_sink_0, 0))
|
||||
|
||||
def get_sample_rate(self):
|
||||
return self.sample_rate
|
||||
|
||||
def set_sample_rate(self, sample_rate):
|
||||
self.sample_rate = sample_rate
|
||||
self.osmosdr_sink_0.set_sample_rate(self.sample_rate)
|
||||
|
||||
def get_rf_gain(self):
|
||||
return self.rf_gain
|
||||
|
||||
def set_rf_gain(self, rf_gain):
|
||||
self.rf_gain = rf_gain
|
||||
self.osmosdr_sink_0.set_gain(self.rf_gain, 0)
|
||||
|
||||
def get_port(self):
|
||||
return self.port
|
||||
|
||||
def set_port(self, port):
|
||||
self.port = port
|
||||
|
||||
def get_if_gain(self):
|
||||
return self.if_gain
|
||||
|
||||
def set_if_gain(self, if_gain):
|
||||
self.if_gain = if_gain
|
||||
self.osmosdr_sink_0.set_if_gain(self.if_gain, 0)
|
||||
|
||||
def get_frequency(self):
|
||||
return self.frequency
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
self.frequency = frequency
|
||||
self.osmosdr_sink_0.set_center_freq(self.frequency, 0)
|
||||
|
||||
def get_freq_correction(self):
|
||||
return self.freq_correction
|
||||
|
||||
def set_freq_correction(self, freq_correction):
|
||||
self.freq_correction = freq_correction
|
||||
self.osmosdr_sink_0.set_freq_corr(self.freq_correction, 0)
|
||||
|
||||
def get_direct_sampling_mode(self):
|
||||
return self.direct_sampling_mode
|
||||
|
||||
def set_direct_sampling_mode(self, direct_sampling_mode):
|
||||
self.direct_sampling_mode = direct_sampling_mode
|
||||
|
||||
def get_channel_index(self):
|
||||
return self.channel_index
|
||||
|
||||
def set_channel_index(self, channel_index):
|
||||
self.channel_index = channel_index
|
||||
|
||||
def get_bb_gain(self):
|
||||
return self.bb_gain
|
||||
|
||||
def set_bb_gain(self, bb_gain):
|
||||
self.bb_gain = bb_gain
|
||||
self.osmosdr_sink_0.set_bb_gain(self.bb_gain, 0)
|
||||
|
||||
def get_bandwidth(self):
|
||||
return self.bandwidth
|
||||
|
||||
def set_bandwidth(self, bandwidth):
|
||||
self.bandwidth = bandwidth
|
||||
self.osmosdr_sink_0.set_bandwidth(self.bandwidth, 0)
|
||||
|
||||
def get_antenna_index(self):
|
||||
return self.antenna_index
|
||||
|
||||
def set_antenna_index(self, antenna_index):
|
||||
self.antenna_index = antenna_index
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = OptionParser(usage='%prog: [options]')
|
||||
parser.add_option('-s', '--sample-rate', dest='sample_rate', default=100000)
|
||||
parser.add_option('-f', '--frequency', dest='frequency', default=433000)
|
||||
parser.add_option('-g', '--gain', dest='rf_gain', default=30)
|
||||
parser.add_option('-i', '--if-gain', dest='if_gain', default=30)
|
||||
parser.add_option('-b', '--bb-gain', dest='bb_gain', default=30)
|
||||
parser.add_option('-w', '--bandwidth', dest='bandwidth', default=250000)
|
||||
parser.add_option('-c', '--freq-correction', dest='freq_correction', default=0)
|
||||
parser.add_option('-d', '--direct-sampling', dest='direct_sampling', default=0)
|
||||
parser.add_option('-n', '--channel-index', dest='channel_index', default=0)
|
||||
parser.add_option('-a', '--antenna-index', dest='antenna_index', default=0)
|
||||
parser.add_option('-p', '--port', dest='port', default=1234)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
tb = top_block(int(options.sample_rate), int(options.frequency), int(options.freq_correction), int(options.rf_gain),
|
||||
int(options.if_gain), int(options.bb_gain), int(options.bandwidth), int(options.port))
|
||||
iht = InputHandlerThread(tb)
|
||||
iht.start()
|
||||
tb.start()
|
||||
tb.wait()
|
||||
@@ -0,0 +1,547 @@
|
||||
options:
|
||||
parameters:
|
||||
author: ''
|
||||
category: Custom
|
||||
cmake_opt: ''
|
||||
comment: ''
|
||||
copyright: ''
|
||||
description: ''
|
||||
gen_cmake: 'On'
|
||||
gen_linking: dynamic
|
||||
generate_options: no_gui
|
||||
hier_block_src_path: '.:'
|
||||
id: top_block
|
||||
max_nouts: '0'
|
||||
output_language: python
|
||||
placement: (0,0)
|
||||
qt_qss_theme: ''
|
||||
realtime_scheduling: ''
|
||||
run: 'True'
|
||||
run_command: '{python} -u {filename}'
|
||||
run_options: run
|
||||
sizing_mode: fixed
|
||||
thread_safe_setters: ''
|
||||
title: ''
|
||||
window_size: ''
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [8, 8]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
blocks:
|
||||
- name: antenna_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [707, 5]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: bandwidth
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '250000'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [219, 7]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: bb_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [420, 5]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: channel_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [559, 91]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: direct_sampling_mode
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [707, 88]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: freq_correction
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [563, 5]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: frequency
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 433e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [319, 93]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: if_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [342, 6]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: port
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '1234'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [988, 44]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: rf_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [223, 96]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: sample_rate
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 3e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [423, 90]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: blocks_tcp_server_sink_0
|
||||
id: blocks_tcp_server_sink
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
comment: ''
|
||||
ipaddr: 127.0.0.1
|
||||
noblock: 'False'
|
||||
port: port
|
||||
type: complex
|
||||
vlen: '1'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [591, 313]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: osmosdr_source_0
|
||||
id: osmosdr_source
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
ant0: ''
|
||||
ant1: ''
|
||||
ant10: ''
|
||||
ant11: ''
|
||||
ant12: ''
|
||||
ant13: ''
|
||||
ant14: ''
|
||||
ant15: ''
|
||||
ant16: ''
|
||||
ant17: ''
|
||||
ant18: ''
|
||||
ant19: ''
|
||||
ant2: ''
|
||||
ant20: ''
|
||||
ant21: ''
|
||||
ant22: ''
|
||||
ant23: ''
|
||||
ant24: ''
|
||||
ant25: ''
|
||||
ant26: ''
|
||||
ant27: ''
|
||||
ant28: ''
|
||||
ant29: ''
|
||||
ant3: ''
|
||||
ant30: ''
|
||||
ant31: ''
|
||||
ant4: ''
|
||||
ant5: ''
|
||||
ant6: ''
|
||||
ant7: ''
|
||||
ant8: ''
|
||||
ant9: ''
|
||||
args: fcd
|
||||
bb_gain0: bb_gain
|
||||
bb_gain1: '20'
|
||||
bb_gain10: '20'
|
||||
bb_gain11: '20'
|
||||
bb_gain12: '20'
|
||||
bb_gain13: '20'
|
||||
bb_gain14: '20'
|
||||
bb_gain15: '20'
|
||||
bb_gain16: '20'
|
||||
bb_gain17: '20'
|
||||
bb_gain18: '20'
|
||||
bb_gain19: '20'
|
||||
bb_gain2: '20'
|
||||
bb_gain20: '20'
|
||||
bb_gain21: '20'
|
||||
bb_gain22: '20'
|
||||
bb_gain23: '20'
|
||||
bb_gain24: '20'
|
||||
bb_gain25: '20'
|
||||
bb_gain26: '20'
|
||||
bb_gain27: '20'
|
||||
bb_gain28: '20'
|
||||
bb_gain29: '20'
|
||||
bb_gain3: '20'
|
||||
bb_gain30: '20'
|
||||
bb_gain31: '20'
|
||||
bb_gain4: '20'
|
||||
bb_gain5: '20'
|
||||
bb_gain6: '20'
|
||||
bb_gain7: '20'
|
||||
bb_gain8: '20'
|
||||
bb_gain9: '20'
|
||||
bw0: bandwidth
|
||||
bw1: '0'
|
||||
bw10: '0'
|
||||
bw11: '0'
|
||||
bw12: '0'
|
||||
bw13: '0'
|
||||
bw14: '0'
|
||||
bw15: '0'
|
||||
bw16: '0'
|
||||
bw17: '0'
|
||||
bw18: '0'
|
||||
bw19: '0'
|
||||
bw2: '0'
|
||||
bw20: '0'
|
||||
bw21: '0'
|
||||
bw22: '0'
|
||||
bw23: '0'
|
||||
bw24: '0'
|
||||
bw25: '0'
|
||||
bw26: '0'
|
||||
bw27: '0'
|
||||
bw28: '0'
|
||||
bw29: '0'
|
||||
bw3: '0'
|
||||
bw30: '0'
|
||||
bw31: '0'
|
||||
bw4: '0'
|
||||
bw5: '0'
|
||||
bw6: '0'
|
||||
bw7: '0'
|
||||
bw8: '0'
|
||||
bw9: '0'
|
||||
clock_source0: ''
|
||||
clock_source1: ''
|
||||
clock_source2: ''
|
||||
clock_source3: ''
|
||||
clock_source4: ''
|
||||
clock_source5: ''
|
||||
clock_source6: ''
|
||||
clock_source7: ''
|
||||
comment: ''
|
||||
corr0: freq_correction
|
||||
corr1: '0'
|
||||
corr10: '0'
|
||||
corr11: '0'
|
||||
corr12: '0'
|
||||
corr13: '0'
|
||||
corr14: '0'
|
||||
corr15: '0'
|
||||
corr16: '0'
|
||||
corr17: '0'
|
||||
corr18: '0'
|
||||
corr19: '0'
|
||||
corr2: '0'
|
||||
corr20: '0'
|
||||
corr21: '0'
|
||||
corr22: '0'
|
||||
corr23: '0'
|
||||
corr24: '0'
|
||||
corr25: '0'
|
||||
corr26: '0'
|
||||
corr27: '0'
|
||||
corr28: '0'
|
||||
corr29: '0'
|
||||
corr3: '0'
|
||||
corr30: '0'
|
||||
corr31: '0'
|
||||
corr4: '0'
|
||||
corr5: '0'
|
||||
corr6: '0'
|
||||
corr7: '0'
|
||||
corr8: '0'
|
||||
corr9: '0'
|
||||
dc_offset_mode0: '0'
|
||||
dc_offset_mode1: '0'
|
||||
dc_offset_mode10: '0'
|
||||
dc_offset_mode11: '0'
|
||||
dc_offset_mode12: '0'
|
||||
dc_offset_mode13: '0'
|
||||
dc_offset_mode14: '0'
|
||||
dc_offset_mode15: '0'
|
||||
dc_offset_mode16: '0'
|
||||
dc_offset_mode17: '0'
|
||||
dc_offset_mode18: '0'
|
||||
dc_offset_mode19: '0'
|
||||
dc_offset_mode2: '0'
|
||||
dc_offset_mode20: '0'
|
||||
dc_offset_mode21: '0'
|
||||
dc_offset_mode22: '0'
|
||||
dc_offset_mode23: '0'
|
||||
dc_offset_mode24: '0'
|
||||
dc_offset_mode25: '0'
|
||||
dc_offset_mode26: '0'
|
||||
dc_offset_mode27: '0'
|
||||
dc_offset_mode28: '0'
|
||||
dc_offset_mode29: '0'
|
||||
dc_offset_mode3: '0'
|
||||
dc_offset_mode30: '0'
|
||||
dc_offset_mode31: '0'
|
||||
dc_offset_mode4: '0'
|
||||
dc_offset_mode5: '0'
|
||||
dc_offset_mode6: '0'
|
||||
dc_offset_mode7: '0'
|
||||
dc_offset_mode8: '0'
|
||||
dc_offset_mode9: '0'
|
||||
freq0: frequency
|
||||
freq1: 100e6
|
||||
freq10: 100e6
|
||||
freq11: 100e6
|
||||
freq12: 100e6
|
||||
freq13: 100e6
|
||||
freq14: 100e6
|
||||
freq15: 100e6
|
||||
freq16: 100e6
|
||||
freq17: 100e6
|
||||
freq18: 100e6
|
||||
freq19: 100e6
|
||||
freq2: 100e6
|
||||
freq20: 100e6
|
||||
freq21: 100e6
|
||||
freq22: 100e6
|
||||
freq23: 100e6
|
||||
freq24: 100e6
|
||||
freq25: 100e6
|
||||
freq26: 100e6
|
||||
freq27: 100e6
|
||||
freq28: 100e6
|
||||
freq29: 100e6
|
||||
freq3: 100e6
|
||||
freq30: 100e6
|
||||
freq31: 100e6
|
||||
freq4: 100e6
|
||||
freq5: 100e6
|
||||
freq6: 100e6
|
||||
freq7: 100e6
|
||||
freq8: 100e6
|
||||
freq9: 100e6
|
||||
gain0: rf_gain
|
||||
gain1: '10'
|
||||
gain10: '10'
|
||||
gain11: '10'
|
||||
gain12: '10'
|
||||
gain13: '10'
|
||||
gain14: '10'
|
||||
gain15: '10'
|
||||
gain16: '10'
|
||||
gain17: '10'
|
||||
gain18: '10'
|
||||
gain19: '10'
|
||||
gain2: '10'
|
||||
gain20: '10'
|
||||
gain21: '10'
|
||||
gain22: '10'
|
||||
gain23: '10'
|
||||
gain24: '10'
|
||||
gain25: '10'
|
||||
gain26: '10'
|
||||
gain27: '10'
|
||||
gain28: '10'
|
||||
gain29: '10'
|
||||
gain3: '10'
|
||||
gain30: '10'
|
||||
gain31: '10'
|
||||
gain4: '10'
|
||||
gain5: '10'
|
||||
gain6: '10'
|
||||
gain7: '10'
|
||||
gain8: '10'
|
||||
gain9: '10'
|
||||
gain_mode0: 'False'
|
||||
gain_mode1: 'False'
|
||||
gain_mode10: 'False'
|
||||
gain_mode11: 'False'
|
||||
gain_mode12: 'False'
|
||||
gain_mode13: 'False'
|
||||
gain_mode14: 'False'
|
||||
gain_mode15: 'False'
|
||||
gain_mode16: 'False'
|
||||
gain_mode17: 'False'
|
||||
gain_mode18: 'False'
|
||||
gain_mode19: 'False'
|
||||
gain_mode2: 'False'
|
||||
gain_mode20: 'False'
|
||||
gain_mode21: 'False'
|
||||
gain_mode22: 'False'
|
||||
gain_mode23: 'False'
|
||||
gain_mode24: 'False'
|
||||
gain_mode25: 'False'
|
||||
gain_mode26: 'False'
|
||||
gain_mode27: 'False'
|
||||
gain_mode28: 'False'
|
||||
gain_mode29: 'False'
|
||||
gain_mode3: 'False'
|
||||
gain_mode30: 'False'
|
||||
gain_mode31: 'False'
|
||||
gain_mode4: 'False'
|
||||
gain_mode5: 'False'
|
||||
gain_mode6: 'False'
|
||||
gain_mode7: 'False'
|
||||
gain_mode8: 'False'
|
||||
gain_mode9: 'False'
|
||||
if_gain0: if_gain
|
||||
if_gain1: '20'
|
||||
if_gain10: '20'
|
||||
if_gain11: '20'
|
||||
if_gain12: '20'
|
||||
if_gain13: '20'
|
||||
if_gain14: '20'
|
||||
if_gain15: '20'
|
||||
if_gain16: '20'
|
||||
if_gain17: '20'
|
||||
if_gain18: '20'
|
||||
if_gain19: '20'
|
||||
if_gain2: '20'
|
||||
if_gain20: '20'
|
||||
if_gain21: '20'
|
||||
if_gain22: '20'
|
||||
if_gain23: '20'
|
||||
if_gain24: '20'
|
||||
if_gain25: '20'
|
||||
if_gain26: '20'
|
||||
if_gain27: '20'
|
||||
if_gain28: '20'
|
||||
if_gain29: '20'
|
||||
if_gain3: '20'
|
||||
if_gain30: '20'
|
||||
if_gain31: '20'
|
||||
if_gain4: '20'
|
||||
if_gain5: '20'
|
||||
if_gain6: '20'
|
||||
if_gain7: '20'
|
||||
if_gain8: '20'
|
||||
if_gain9: '20'
|
||||
iq_balance_mode0: '0'
|
||||
iq_balance_mode1: '0'
|
||||
iq_balance_mode10: '0'
|
||||
iq_balance_mode11: '0'
|
||||
iq_balance_mode12: '0'
|
||||
iq_balance_mode13: '0'
|
||||
iq_balance_mode14: '0'
|
||||
iq_balance_mode15: '0'
|
||||
iq_balance_mode16: '0'
|
||||
iq_balance_mode17: '0'
|
||||
iq_balance_mode18: '0'
|
||||
iq_balance_mode19: '0'
|
||||
iq_balance_mode2: '0'
|
||||
iq_balance_mode20: '0'
|
||||
iq_balance_mode21: '0'
|
||||
iq_balance_mode22: '0'
|
||||
iq_balance_mode23: '0'
|
||||
iq_balance_mode24: '0'
|
||||
iq_balance_mode25: '0'
|
||||
iq_balance_mode26: '0'
|
||||
iq_balance_mode27: '0'
|
||||
iq_balance_mode28: '0'
|
||||
iq_balance_mode29: '0'
|
||||
iq_balance_mode3: '0'
|
||||
iq_balance_mode30: '0'
|
||||
iq_balance_mode31: '0'
|
||||
iq_balance_mode4: '0'
|
||||
iq_balance_mode5: '0'
|
||||
iq_balance_mode6: '0'
|
||||
iq_balance_mode7: '0'
|
||||
iq_balance_mode8: '0'
|
||||
iq_balance_mode9: '0'
|
||||
maxoutbuf: '0'
|
||||
minoutbuf: '0'
|
||||
nchan: '1'
|
||||
num_mboards: '1'
|
||||
sample_rate: sample_rate
|
||||
sync: sync
|
||||
time_source0: ''
|
||||
time_source1: ''
|
||||
time_source2: ''
|
||||
time_source3: ''
|
||||
time_source4: ''
|
||||
time_source5: ''
|
||||
time_source6: ''
|
||||
time_source7: ''
|
||||
type: fc32
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [219, 216]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
connections:
|
||||
- [osmosdr_source_0, '0', blocks_tcp_server_sink_0, '0']
|
||||
|
||||
metadata:
|
||||
file_format: 1
|
||||
@@ -0,0 +1,137 @@
|
||||
from optparse import OptionParser
|
||||
|
||||
import Initializer
|
||||
from InputHandlerThread import InputHandlerThread
|
||||
|
||||
Initializer.init_path()
|
||||
|
||||
from gnuradio import blocks
|
||||
from gnuradio import gr
|
||||
import osmosdr
|
||||
|
||||
|
||||
class top_block(gr.top_block):
|
||||
|
||||
def __init__(self, sample_rate, frequency, freq_correction, rf_gain, if_gain, bb_gain, bandwidth, port):
|
||||
gr.top_block.__init__(self, "Top Block")
|
||||
|
||||
self.sample_rate = sample_rate
|
||||
self.rf_gain = rf_gain
|
||||
self.port = port
|
||||
self.if_gain = if_gain
|
||||
self.frequency = frequency
|
||||
self.freq_correction = freq_correction
|
||||
self.bb_gain = bb_gain
|
||||
self.bandwidth = bandwidth
|
||||
|
||||
self.osmosdr_source_0 = osmosdr.source(
|
||||
args="numchan=" + str(1) + " " + 'fcd'
|
||||
)
|
||||
self.osmosdr_source_0.set_time_unknown_pps(osmosdr.time_spec_t())
|
||||
self.osmosdr_source_0.set_sample_rate(sample_rate)
|
||||
self.osmosdr_source_0.set_center_freq(frequency, 0)
|
||||
self.osmosdr_source_0.set_freq_corr(freq_correction, 0)
|
||||
self.osmosdr_source_0.set_gain(rf_gain, 0)
|
||||
self.osmosdr_source_0.set_if_gain(if_gain, 0)
|
||||
self.osmosdr_source_0.set_bb_gain(bb_gain, 0)
|
||||
self.osmosdr_source_0.set_antenna('', 0)
|
||||
self.osmosdr_source_0.set_bandwidth(bandwidth, 0)
|
||||
self.blocks_tcp_server_sink_0 = blocks.tcp_server_sink(gr.sizeof_gr_complex * 1, '127.0.0.1', port, False)
|
||||
|
||||
self.connect((self.osmosdr_source_0, 0), (self.blocks_tcp_server_sink_0, 0))
|
||||
|
||||
def get_sample_rate(self):
|
||||
return self.sample_rate
|
||||
|
||||
def set_sample_rate(self, sample_rate):
|
||||
self.sample_rate = sample_rate
|
||||
self.osmosdr_source_0.set_sample_rate(self.sample_rate)
|
||||
|
||||
def get_rf_gain(self):
|
||||
return self.rf_gain
|
||||
|
||||
def set_rf_gain(self, rf_gain):
|
||||
self.rf_gain = rf_gain
|
||||
self.osmosdr_source_0.set_gain(self.rf_gain, 0)
|
||||
|
||||
def get_port(self):
|
||||
return self.port
|
||||
|
||||
def set_port(self, port):
|
||||
self.port = port
|
||||
|
||||
def get_if_gain(self):
|
||||
return self.if_gain
|
||||
|
||||
def set_if_gain(self, if_gain):
|
||||
self.if_gain = if_gain
|
||||
self.osmosdr_source_0.set_if_gain(self.if_gain, 0)
|
||||
|
||||
def get_frequency(self):
|
||||
return self.frequency
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
self.frequency = frequency
|
||||
self.osmosdr_source_0.set_center_freq(self.frequency, 0)
|
||||
|
||||
def get_freq_correction(self):
|
||||
return self.freq_correction
|
||||
|
||||
def set_freq_correction(self, freq_correction):
|
||||
self.freq_correction = freq_correction
|
||||
self.osmosdr_source_0.set_freq_corr(self.freq_correction, 0)
|
||||
|
||||
def get_direct_sampling_mode(self):
|
||||
return self.direct_sampling_mode
|
||||
|
||||
def set_direct_sampling_mode(self, direct_sampling_mode):
|
||||
self.direct_sampling_mode = direct_sampling_mode
|
||||
|
||||
def get_channel_index(self):
|
||||
return self.channel_index
|
||||
|
||||
def set_channel_index(self, channel_index):
|
||||
self.channel_index = channel_index
|
||||
|
||||
def get_bb_gain(self):
|
||||
return self.bb_gain
|
||||
|
||||
def set_bb_gain(self, bb_gain):
|
||||
self.bb_gain = bb_gain
|
||||
self.osmosdr_source_0.set_bb_gain(self.bb_gain, 0)
|
||||
|
||||
def get_bandwidth(self):
|
||||
return self.bandwidth
|
||||
|
||||
def set_bandwidth(self, bandwidth):
|
||||
self.bandwidth = bandwidth
|
||||
self.osmosdr_source_0.set_bandwidth(self.bandwidth, 0)
|
||||
|
||||
def get_antenna_index(self):
|
||||
return self.antenna_index
|
||||
|
||||
def set_antenna_index(self, antenna_index):
|
||||
self.antenna_index = antenna_index
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = OptionParser(usage='%prog: [options]')
|
||||
parser.add_option('-s', '--sample-rate', dest='sample_rate', default=100000)
|
||||
parser.add_option('-f', '--frequency', dest='frequency', default=433000)
|
||||
parser.add_option('-g', '--gain', dest='rf_gain', default=30)
|
||||
parser.add_option('-i', '--if-gain', dest='if_gain', default=30)
|
||||
parser.add_option('-b', '--bb-gain', dest='bb_gain', default=30)
|
||||
parser.add_option('-w', '--bandwidth', dest='bandwidth', default=250000)
|
||||
parser.add_option('-c', '--freq-correction', dest='freq_correction', default=0)
|
||||
parser.add_option('-d', '--direct-sampling', dest='direct_sampling', default=0)
|
||||
parser.add_option('-n', '--channel-index', dest='channel_index', default=0)
|
||||
parser.add_option('-a', '--antenna-index', dest='antenna_index', default=0)
|
||||
parser.add_option('-p', '--port', dest='port', default=1234)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
tb = top_block(int(options.sample_rate), int(options.frequency), int(options.freq_correction), int(options.rf_gain),
|
||||
int(options.if_gain), int(options.bb_gain), int(options.bandwidth), int(options.port))
|
||||
iht = InputHandlerThread(tb)
|
||||
iht.start()
|
||||
tb.start()
|
||||
tb.wait()
|
||||
@@ -0,0 +1,547 @@
|
||||
options:
|
||||
parameters:
|
||||
author: ''
|
||||
category: Custom
|
||||
cmake_opt: ''
|
||||
comment: ''
|
||||
copyright: ''
|
||||
description: ''
|
||||
gen_cmake: 'On'
|
||||
gen_linking: dynamic
|
||||
generate_options: no_gui
|
||||
hier_block_src_path: '.:'
|
||||
id: top_block
|
||||
max_nouts: '0'
|
||||
output_language: python
|
||||
placement: (0,0)
|
||||
qt_qss_theme: ''
|
||||
realtime_scheduling: ''
|
||||
run: 'True'
|
||||
run_command: '{python} -u {filename}'
|
||||
run_options: run
|
||||
sizing_mode: fixed
|
||||
thread_safe_setters: ''
|
||||
title: ''
|
||||
window_size: ''
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [8, 8]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
blocks:
|
||||
- name: antenna_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [707, 5]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: bandwidth
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '250000'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [219, 7]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: bb_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [420, 5]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: channel_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [559, 91]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: direct_sampling_mode
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [707, 88]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: freq_correction
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [563, 5]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: frequency
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 433e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [319, 93]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: if_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [342, 6]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: port
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '1234'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [988, 44]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: rf_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [223, 96]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: sample_rate
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 3e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [423, 90]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: blocks_tcp_server_sink_0
|
||||
id: blocks_tcp_server_sink
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
comment: ''
|
||||
ipaddr: 127.0.0.1
|
||||
noblock: 'False'
|
||||
port: port
|
||||
type: complex
|
||||
vlen: '1'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [601, 309]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: osmosdr_source_0
|
||||
id: osmosdr_source
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
ant0: ''
|
||||
ant1: ''
|
||||
ant10: ''
|
||||
ant11: ''
|
||||
ant12: ''
|
||||
ant13: ''
|
||||
ant14: ''
|
||||
ant15: ''
|
||||
ant16: ''
|
||||
ant17: ''
|
||||
ant18: ''
|
||||
ant19: ''
|
||||
ant2: ''
|
||||
ant20: ''
|
||||
ant21: ''
|
||||
ant22: ''
|
||||
ant23: ''
|
||||
ant24: ''
|
||||
ant25: ''
|
||||
ant26: ''
|
||||
ant27: ''
|
||||
ant28: ''
|
||||
ant29: ''
|
||||
ant3: ''
|
||||
ant30: ''
|
||||
ant31: ''
|
||||
ant4: ''
|
||||
ant5: ''
|
||||
ant6: ''
|
||||
ant7: ''
|
||||
ant8: ''
|
||||
ant9: ''
|
||||
args: hackrf
|
||||
bb_gain0: bb_gain
|
||||
bb_gain1: '20'
|
||||
bb_gain10: '20'
|
||||
bb_gain11: '20'
|
||||
bb_gain12: '20'
|
||||
bb_gain13: '20'
|
||||
bb_gain14: '20'
|
||||
bb_gain15: '20'
|
||||
bb_gain16: '20'
|
||||
bb_gain17: '20'
|
||||
bb_gain18: '20'
|
||||
bb_gain19: '20'
|
||||
bb_gain2: '20'
|
||||
bb_gain20: '20'
|
||||
bb_gain21: '20'
|
||||
bb_gain22: '20'
|
||||
bb_gain23: '20'
|
||||
bb_gain24: '20'
|
||||
bb_gain25: '20'
|
||||
bb_gain26: '20'
|
||||
bb_gain27: '20'
|
||||
bb_gain28: '20'
|
||||
bb_gain29: '20'
|
||||
bb_gain3: '20'
|
||||
bb_gain30: '20'
|
||||
bb_gain31: '20'
|
||||
bb_gain4: '20'
|
||||
bb_gain5: '20'
|
||||
bb_gain6: '20'
|
||||
bb_gain7: '20'
|
||||
bb_gain8: '20'
|
||||
bb_gain9: '20'
|
||||
bw0: bandwidth
|
||||
bw1: '0'
|
||||
bw10: '0'
|
||||
bw11: '0'
|
||||
bw12: '0'
|
||||
bw13: '0'
|
||||
bw14: '0'
|
||||
bw15: '0'
|
||||
bw16: '0'
|
||||
bw17: '0'
|
||||
bw18: '0'
|
||||
bw19: '0'
|
||||
bw2: '0'
|
||||
bw20: '0'
|
||||
bw21: '0'
|
||||
bw22: '0'
|
||||
bw23: '0'
|
||||
bw24: '0'
|
||||
bw25: '0'
|
||||
bw26: '0'
|
||||
bw27: '0'
|
||||
bw28: '0'
|
||||
bw29: '0'
|
||||
bw3: '0'
|
||||
bw30: '0'
|
||||
bw31: '0'
|
||||
bw4: '0'
|
||||
bw5: '0'
|
||||
bw6: '0'
|
||||
bw7: '0'
|
||||
bw8: '0'
|
||||
bw9: '0'
|
||||
clock_source0: ''
|
||||
clock_source1: ''
|
||||
clock_source2: ''
|
||||
clock_source3: ''
|
||||
clock_source4: ''
|
||||
clock_source5: ''
|
||||
clock_source6: ''
|
||||
clock_source7: ''
|
||||
comment: ''
|
||||
corr0: freq_correction
|
||||
corr1: '0'
|
||||
corr10: '0'
|
||||
corr11: '0'
|
||||
corr12: '0'
|
||||
corr13: '0'
|
||||
corr14: '0'
|
||||
corr15: '0'
|
||||
corr16: '0'
|
||||
corr17: '0'
|
||||
corr18: '0'
|
||||
corr19: '0'
|
||||
corr2: '0'
|
||||
corr20: '0'
|
||||
corr21: '0'
|
||||
corr22: '0'
|
||||
corr23: '0'
|
||||
corr24: '0'
|
||||
corr25: '0'
|
||||
corr26: '0'
|
||||
corr27: '0'
|
||||
corr28: '0'
|
||||
corr29: '0'
|
||||
corr3: '0'
|
||||
corr30: '0'
|
||||
corr31: '0'
|
||||
corr4: '0'
|
||||
corr5: '0'
|
||||
corr6: '0'
|
||||
corr7: '0'
|
||||
corr8: '0'
|
||||
corr9: '0'
|
||||
dc_offset_mode0: '0'
|
||||
dc_offset_mode1: '0'
|
||||
dc_offset_mode10: '0'
|
||||
dc_offset_mode11: '0'
|
||||
dc_offset_mode12: '0'
|
||||
dc_offset_mode13: '0'
|
||||
dc_offset_mode14: '0'
|
||||
dc_offset_mode15: '0'
|
||||
dc_offset_mode16: '0'
|
||||
dc_offset_mode17: '0'
|
||||
dc_offset_mode18: '0'
|
||||
dc_offset_mode19: '0'
|
||||
dc_offset_mode2: '0'
|
||||
dc_offset_mode20: '0'
|
||||
dc_offset_mode21: '0'
|
||||
dc_offset_mode22: '0'
|
||||
dc_offset_mode23: '0'
|
||||
dc_offset_mode24: '0'
|
||||
dc_offset_mode25: '0'
|
||||
dc_offset_mode26: '0'
|
||||
dc_offset_mode27: '0'
|
||||
dc_offset_mode28: '0'
|
||||
dc_offset_mode29: '0'
|
||||
dc_offset_mode3: '0'
|
||||
dc_offset_mode30: '0'
|
||||
dc_offset_mode31: '0'
|
||||
dc_offset_mode4: '0'
|
||||
dc_offset_mode5: '0'
|
||||
dc_offset_mode6: '0'
|
||||
dc_offset_mode7: '0'
|
||||
dc_offset_mode8: '0'
|
||||
dc_offset_mode9: '0'
|
||||
freq0: frequency
|
||||
freq1: 100e6
|
||||
freq10: 100e6
|
||||
freq11: 100e6
|
||||
freq12: 100e6
|
||||
freq13: 100e6
|
||||
freq14: 100e6
|
||||
freq15: 100e6
|
||||
freq16: 100e6
|
||||
freq17: 100e6
|
||||
freq18: 100e6
|
||||
freq19: 100e6
|
||||
freq2: 100e6
|
||||
freq20: 100e6
|
||||
freq21: 100e6
|
||||
freq22: 100e6
|
||||
freq23: 100e6
|
||||
freq24: 100e6
|
||||
freq25: 100e6
|
||||
freq26: 100e6
|
||||
freq27: 100e6
|
||||
freq28: 100e6
|
||||
freq29: 100e6
|
||||
freq3: 100e6
|
||||
freq30: 100e6
|
||||
freq31: 100e6
|
||||
freq4: 100e6
|
||||
freq5: 100e6
|
||||
freq6: 100e6
|
||||
freq7: 100e6
|
||||
freq8: 100e6
|
||||
freq9: 100e6
|
||||
gain0: rf_gain
|
||||
gain1: '10'
|
||||
gain10: '10'
|
||||
gain11: '10'
|
||||
gain12: '10'
|
||||
gain13: '10'
|
||||
gain14: '10'
|
||||
gain15: '10'
|
||||
gain16: '10'
|
||||
gain17: '10'
|
||||
gain18: '10'
|
||||
gain19: '10'
|
||||
gain2: '10'
|
||||
gain20: '10'
|
||||
gain21: '10'
|
||||
gain22: '10'
|
||||
gain23: '10'
|
||||
gain24: '10'
|
||||
gain25: '10'
|
||||
gain26: '10'
|
||||
gain27: '10'
|
||||
gain28: '10'
|
||||
gain29: '10'
|
||||
gain3: '10'
|
||||
gain30: '10'
|
||||
gain31: '10'
|
||||
gain4: '10'
|
||||
gain5: '10'
|
||||
gain6: '10'
|
||||
gain7: '10'
|
||||
gain8: '10'
|
||||
gain9: '10'
|
||||
gain_mode0: 'False'
|
||||
gain_mode1: 'False'
|
||||
gain_mode10: 'False'
|
||||
gain_mode11: 'False'
|
||||
gain_mode12: 'False'
|
||||
gain_mode13: 'False'
|
||||
gain_mode14: 'False'
|
||||
gain_mode15: 'False'
|
||||
gain_mode16: 'False'
|
||||
gain_mode17: 'False'
|
||||
gain_mode18: 'False'
|
||||
gain_mode19: 'False'
|
||||
gain_mode2: 'False'
|
||||
gain_mode20: 'False'
|
||||
gain_mode21: 'False'
|
||||
gain_mode22: 'False'
|
||||
gain_mode23: 'False'
|
||||
gain_mode24: 'False'
|
||||
gain_mode25: 'False'
|
||||
gain_mode26: 'False'
|
||||
gain_mode27: 'False'
|
||||
gain_mode28: 'False'
|
||||
gain_mode29: 'False'
|
||||
gain_mode3: 'False'
|
||||
gain_mode30: 'False'
|
||||
gain_mode31: 'False'
|
||||
gain_mode4: 'False'
|
||||
gain_mode5: 'False'
|
||||
gain_mode6: 'False'
|
||||
gain_mode7: 'False'
|
||||
gain_mode8: 'False'
|
||||
gain_mode9: 'False'
|
||||
if_gain0: if_gain
|
||||
if_gain1: '20'
|
||||
if_gain10: '20'
|
||||
if_gain11: '20'
|
||||
if_gain12: '20'
|
||||
if_gain13: '20'
|
||||
if_gain14: '20'
|
||||
if_gain15: '20'
|
||||
if_gain16: '20'
|
||||
if_gain17: '20'
|
||||
if_gain18: '20'
|
||||
if_gain19: '20'
|
||||
if_gain2: '20'
|
||||
if_gain20: '20'
|
||||
if_gain21: '20'
|
||||
if_gain22: '20'
|
||||
if_gain23: '20'
|
||||
if_gain24: '20'
|
||||
if_gain25: '20'
|
||||
if_gain26: '20'
|
||||
if_gain27: '20'
|
||||
if_gain28: '20'
|
||||
if_gain29: '20'
|
||||
if_gain3: '20'
|
||||
if_gain30: '20'
|
||||
if_gain31: '20'
|
||||
if_gain4: '20'
|
||||
if_gain5: '20'
|
||||
if_gain6: '20'
|
||||
if_gain7: '20'
|
||||
if_gain8: '20'
|
||||
if_gain9: '20'
|
||||
iq_balance_mode0: '0'
|
||||
iq_balance_mode1: '0'
|
||||
iq_balance_mode10: '0'
|
||||
iq_balance_mode11: '0'
|
||||
iq_balance_mode12: '0'
|
||||
iq_balance_mode13: '0'
|
||||
iq_balance_mode14: '0'
|
||||
iq_balance_mode15: '0'
|
||||
iq_balance_mode16: '0'
|
||||
iq_balance_mode17: '0'
|
||||
iq_balance_mode18: '0'
|
||||
iq_balance_mode19: '0'
|
||||
iq_balance_mode2: '0'
|
||||
iq_balance_mode20: '0'
|
||||
iq_balance_mode21: '0'
|
||||
iq_balance_mode22: '0'
|
||||
iq_balance_mode23: '0'
|
||||
iq_balance_mode24: '0'
|
||||
iq_balance_mode25: '0'
|
||||
iq_balance_mode26: '0'
|
||||
iq_balance_mode27: '0'
|
||||
iq_balance_mode28: '0'
|
||||
iq_balance_mode29: '0'
|
||||
iq_balance_mode3: '0'
|
||||
iq_balance_mode30: '0'
|
||||
iq_balance_mode31: '0'
|
||||
iq_balance_mode4: '0'
|
||||
iq_balance_mode5: '0'
|
||||
iq_balance_mode6: '0'
|
||||
iq_balance_mode7: '0'
|
||||
iq_balance_mode8: '0'
|
||||
iq_balance_mode9: '0'
|
||||
maxoutbuf: '0'
|
||||
minoutbuf: '0'
|
||||
nchan: '1'
|
||||
num_mboards: '1'
|
||||
sample_rate: sample_rate
|
||||
sync: sync
|
||||
time_source0: ''
|
||||
time_source1: ''
|
||||
time_source2: ''
|
||||
time_source3: ''
|
||||
time_source4: ''
|
||||
time_source5: ''
|
||||
time_source6: ''
|
||||
time_source7: ''
|
||||
type: fc32
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [219, 215]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
connections:
|
||||
- [osmosdr_source_0, '0', blocks_tcp_server_sink_0, '0']
|
||||
|
||||
metadata:
|
||||
file_format: 1
|
||||
@@ -0,0 +1,137 @@
|
||||
from optparse import OptionParser
|
||||
|
||||
import Initializer
|
||||
from InputHandlerThread import InputHandlerThread
|
||||
|
||||
Initializer.init_path()
|
||||
|
||||
from gnuradio import blocks
|
||||
from gnuradio import gr
|
||||
import osmosdr
|
||||
|
||||
|
||||
class top_block(gr.top_block):
|
||||
|
||||
def __init__(self, sample_rate, frequency, freq_correction, rf_gain, if_gain, bb_gain, bandwidth, port):
|
||||
gr.top_block.__init__(self, "Top Block")
|
||||
|
||||
self.sample_rate = sample_rate
|
||||
self.rf_gain = rf_gain
|
||||
self.port = port
|
||||
self.if_gain = if_gain
|
||||
self.frequency = frequency
|
||||
self.freq_correction = freq_correction
|
||||
self.bb_gain = bb_gain
|
||||
self.bandwidth = bandwidth
|
||||
|
||||
self.osmosdr_source_0 = osmosdr.source(
|
||||
args="numchan=" + str(1) + " " + 'hackrf'
|
||||
)
|
||||
self.osmosdr_source_0.set_time_unknown_pps(osmosdr.time_spec_t())
|
||||
self.osmosdr_source_0.set_sample_rate(sample_rate)
|
||||
self.osmosdr_source_0.set_center_freq(frequency, 0)
|
||||
self.osmosdr_source_0.set_freq_corr(freq_correction, 0)
|
||||
self.osmosdr_source_0.set_gain(rf_gain, 0)
|
||||
self.osmosdr_source_0.set_if_gain(if_gain, 0)
|
||||
self.osmosdr_source_0.set_bb_gain(bb_gain, 0)
|
||||
self.osmosdr_source_0.set_antenna('', 0)
|
||||
self.osmosdr_source_0.set_bandwidth(bandwidth, 0)
|
||||
self.blocks_tcp_server_sink_0 = blocks.tcp_server_sink(gr.sizeof_gr_complex * 1, '127.0.0.1', port, False)
|
||||
|
||||
self.connect((self.osmosdr_source_0, 0), (self.blocks_tcp_server_sink_0, 0))
|
||||
|
||||
def get_sample_rate(self):
|
||||
return self.sample_rate
|
||||
|
||||
def set_sample_rate(self, sample_rate):
|
||||
self.sample_rate = sample_rate
|
||||
self.osmosdr_source_0.set_sample_rate(self.sample_rate)
|
||||
|
||||
def get_rf_gain(self):
|
||||
return self.rf_gain
|
||||
|
||||
def set_rf_gain(self, rf_gain):
|
||||
self.rf_gain = rf_gain
|
||||
self.osmosdr_source_0.set_gain(self.rf_gain, 0)
|
||||
|
||||
def get_port(self):
|
||||
return self.port
|
||||
|
||||
def set_port(self, port):
|
||||
self.port = port
|
||||
|
||||
def get_if_gain(self):
|
||||
return self.if_gain
|
||||
|
||||
def set_if_gain(self, if_gain):
|
||||
self.if_gain = if_gain
|
||||
self.osmosdr_source_0.set_if_gain(self.if_gain, 0)
|
||||
|
||||
def get_frequency(self):
|
||||
return self.frequency
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
self.frequency = frequency
|
||||
self.osmosdr_source_0.set_center_freq(self.frequency, 0)
|
||||
|
||||
def get_freq_correction(self):
|
||||
return self.freq_correction
|
||||
|
||||
def set_freq_correction(self, freq_correction):
|
||||
self.freq_correction = freq_correction
|
||||
self.osmosdr_source_0.set_freq_corr(self.freq_correction, 0)
|
||||
|
||||
def get_direct_sampling_mode(self):
|
||||
return self.direct_sampling_mode
|
||||
|
||||
def set_direct_sampling_mode(self, direct_sampling_mode):
|
||||
self.direct_sampling_mode = direct_sampling_mode
|
||||
|
||||
def get_channel_index(self):
|
||||
return self.channel_index
|
||||
|
||||
def set_channel_index(self, channel_index):
|
||||
self.channel_index = channel_index
|
||||
|
||||
def get_bb_gain(self):
|
||||
return self.bb_gain
|
||||
|
||||
def set_bb_gain(self, bb_gain):
|
||||
self.bb_gain = bb_gain
|
||||
self.osmosdr_source_0.set_bb_gain(self.bb_gain, 0)
|
||||
|
||||
def get_bandwidth(self):
|
||||
return self.bandwidth
|
||||
|
||||
def set_bandwidth(self, bandwidth):
|
||||
self.bandwidth = bandwidth
|
||||
self.osmosdr_source_0.set_bandwidth(self.bandwidth, 0)
|
||||
|
||||
def get_antenna_index(self):
|
||||
return self.antenna_index
|
||||
|
||||
def set_antenna_index(self, antenna_index):
|
||||
self.antenna_index = antenna_index
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = OptionParser(usage='%prog: [options]')
|
||||
parser.add_option('-s', '--sample-rate', dest='sample_rate', default=100000)
|
||||
parser.add_option('-f', '--frequency', dest='frequency', default=433000)
|
||||
parser.add_option('-g', '--gain', dest='rf_gain', default=30)
|
||||
parser.add_option('-i', '--if-gain', dest='if_gain', default=30)
|
||||
parser.add_option('-b', '--bb-gain', dest='bb_gain', default=30)
|
||||
parser.add_option('-w', '--bandwidth', dest='bandwidth', default=250000)
|
||||
parser.add_option('-c', '--freq-correction', dest='freq_correction', default=0)
|
||||
parser.add_option('-d', '--direct-sampling', dest='direct_sampling', default=0)
|
||||
parser.add_option('-n', '--channel-index', dest='channel_index', default=0)
|
||||
parser.add_option('-a', '--antenna-index', dest='antenna_index', default=0)
|
||||
parser.add_option('-p', '--port', dest='port', default=1234)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
tb = top_block(int(options.sample_rate), int(options.frequency), int(options.freq_correction), int(options.rf_gain),
|
||||
int(options.if_gain), int(options.bb_gain), int(options.bandwidth), int(options.port))
|
||||
iht = InputHandlerThread(tb)
|
||||
iht.start()
|
||||
tb.start()
|
||||
tb.wait()
|
||||
@@ -0,0 +1,454 @@
|
||||
options:
|
||||
parameters:
|
||||
author: ''
|
||||
category: Custom
|
||||
cmake_opt: ''
|
||||
comment: ''
|
||||
copyright: ''
|
||||
description: ''
|
||||
gen_cmake: 'On'
|
||||
gen_linking: dynamic
|
||||
generate_options: no_gui
|
||||
hier_block_src_path: '.:'
|
||||
id: top_block
|
||||
max_nouts: '0'
|
||||
output_language: python
|
||||
placement: (0,0)
|
||||
qt_qss_theme: ''
|
||||
realtime_scheduling: ''
|
||||
run: 'True'
|
||||
run_command: '{python} -u {filename}'
|
||||
run_options: run
|
||||
sizing_mode: fixed
|
||||
thread_safe_setters: ''
|
||||
title: ''
|
||||
window_size: ''
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [8, 8]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
blocks:
|
||||
- name: antenna_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [705, 7]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: bandwidth
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '250000'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [217, 9]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: bb_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [418, 7]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: channel_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [557, 93]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: direct_sampling_mode
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [705, 90]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: freq_correction
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [561, 7]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: frequency
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 433e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [317, 95]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: if_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [340, 8]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: port
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '1234'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [986, 46]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: rf_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [221, 98]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: sample_rate
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 3e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [421, 92]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: blocks_udp_source_0
|
||||
id: blocks_udp_source
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
comment: ''
|
||||
eof: 'False'
|
||||
ipaddr: 127.0.0.1
|
||||
maxoutbuf: '0'
|
||||
minoutbuf: '0'
|
||||
port: port
|
||||
psize: '65536'
|
||||
type: complex
|
||||
vlen: '1'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [349, 269]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: osmosdr_sink_0
|
||||
id: osmosdr_sink
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
ant0: ''
|
||||
ant1: ''
|
||||
ant10: ''
|
||||
ant11: ''
|
||||
ant12: ''
|
||||
ant13: ''
|
||||
ant14: ''
|
||||
ant15: ''
|
||||
ant16: ''
|
||||
ant17: ''
|
||||
ant18: ''
|
||||
ant19: ''
|
||||
ant2: ''
|
||||
ant20: ''
|
||||
ant21: ''
|
||||
ant22: ''
|
||||
ant23: ''
|
||||
ant24: ''
|
||||
ant25: ''
|
||||
ant26: ''
|
||||
ant27: ''
|
||||
ant28: ''
|
||||
ant29: ''
|
||||
ant3: ''
|
||||
ant30: ''
|
||||
ant31: ''
|
||||
ant4: ''
|
||||
ant5: ''
|
||||
ant6: ''
|
||||
ant7: ''
|
||||
ant8: ''
|
||||
ant9: ''
|
||||
args: hackrf
|
||||
bb_gain0: bb_gain
|
||||
bb_gain1: '20'
|
||||
bb_gain10: '20'
|
||||
bb_gain11: '20'
|
||||
bb_gain12: '20'
|
||||
bb_gain13: '20'
|
||||
bb_gain14: '20'
|
||||
bb_gain15: '20'
|
||||
bb_gain16: '20'
|
||||
bb_gain17: '20'
|
||||
bb_gain18: '20'
|
||||
bb_gain19: '20'
|
||||
bb_gain2: '20'
|
||||
bb_gain20: '20'
|
||||
bb_gain21: '20'
|
||||
bb_gain22: '20'
|
||||
bb_gain23: '20'
|
||||
bb_gain24: '20'
|
||||
bb_gain25: '20'
|
||||
bb_gain26: '20'
|
||||
bb_gain27: '20'
|
||||
bb_gain28: '20'
|
||||
bb_gain29: '20'
|
||||
bb_gain3: '20'
|
||||
bb_gain30: '20'
|
||||
bb_gain31: '20'
|
||||
bb_gain4: '20'
|
||||
bb_gain5: '20'
|
||||
bb_gain6: '20'
|
||||
bb_gain7: '20'
|
||||
bb_gain8: '20'
|
||||
bb_gain9: '20'
|
||||
bw0: bandwidth
|
||||
bw1: '0'
|
||||
bw10: '0'
|
||||
bw11: '0'
|
||||
bw12: '0'
|
||||
bw13: '0'
|
||||
bw14: '0'
|
||||
bw15: '0'
|
||||
bw16: '0'
|
||||
bw17: '0'
|
||||
bw18: '0'
|
||||
bw19: '0'
|
||||
bw2: '0'
|
||||
bw20: '0'
|
||||
bw21: '0'
|
||||
bw22: '0'
|
||||
bw23: '0'
|
||||
bw24: '0'
|
||||
bw25: '0'
|
||||
bw26: '0'
|
||||
bw27: '0'
|
||||
bw28: '0'
|
||||
bw29: '0'
|
||||
bw3: '0'
|
||||
bw30: '0'
|
||||
bw31: '0'
|
||||
bw4: '0'
|
||||
bw5: '0'
|
||||
bw6: '0'
|
||||
bw7: '0'
|
||||
bw8: '0'
|
||||
bw9: '0'
|
||||
clock_source0: ''
|
||||
clock_source1: ''
|
||||
clock_source2: ''
|
||||
clock_source3: ''
|
||||
clock_source4: ''
|
||||
clock_source5: ''
|
||||
clock_source6: ''
|
||||
clock_source7: ''
|
||||
comment: ''
|
||||
corr0: freq_correction
|
||||
corr1: '0'
|
||||
corr10: '0'
|
||||
corr11: '0'
|
||||
corr12: '0'
|
||||
corr13: '0'
|
||||
corr14: '0'
|
||||
corr15: '0'
|
||||
corr16: '0'
|
||||
corr17: '0'
|
||||
corr18: '0'
|
||||
corr19: '0'
|
||||
corr2: '0'
|
||||
corr20: '0'
|
||||
corr21: '0'
|
||||
corr22: '0'
|
||||
corr23: '0'
|
||||
corr24: '0'
|
||||
corr25: '0'
|
||||
corr26: '0'
|
||||
corr27: '0'
|
||||
corr28: '0'
|
||||
corr29: '0'
|
||||
corr3: '0'
|
||||
corr30: '0'
|
||||
corr31: '0'
|
||||
corr4: '0'
|
||||
corr5: '0'
|
||||
corr6: '0'
|
||||
corr7: '0'
|
||||
corr8: '0'
|
||||
corr9: '0'
|
||||
freq0: frequency
|
||||
freq1: 100e6
|
||||
freq10: 100e6
|
||||
freq11: 100e6
|
||||
freq12: 100e6
|
||||
freq13: 100e6
|
||||
freq14: 100e6
|
||||
freq15: 100e6
|
||||
freq16: 100e6
|
||||
freq17: 100e6
|
||||
freq18: 100e6
|
||||
freq19: 100e6
|
||||
freq2: 100e6
|
||||
freq20: 100e6
|
||||
freq21: 100e6
|
||||
freq22: 100e6
|
||||
freq23: 100e6
|
||||
freq24: 100e6
|
||||
freq25: 100e6
|
||||
freq26: 100e6
|
||||
freq27: 100e6
|
||||
freq28: 100e6
|
||||
freq29: 100e6
|
||||
freq3: 100e6
|
||||
freq30: 100e6
|
||||
freq31: 100e6
|
||||
freq4: 100e6
|
||||
freq5: 100e6
|
||||
freq6: 100e6
|
||||
freq7: 100e6
|
||||
freq8: 100e6
|
||||
freq9: 100e6
|
||||
gain0: rf_gain
|
||||
gain1: '10'
|
||||
gain10: '10'
|
||||
gain11: '10'
|
||||
gain12: '10'
|
||||
gain13: '10'
|
||||
gain14: '10'
|
||||
gain15: '10'
|
||||
gain16: '10'
|
||||
gain17: '10'
|
||||
gain18: '10'
|
||||
gain19: '10'
|
||||
gain2: '10'
|
||||
gain20: '10'
|
||||
gain21: '10'
|
||||
gain22: '10'
|
||||
gain23: '10'
|
||||
gain24: '10'
|
||||
gain25: '10'
|
||||
gain26: '10'
|
||||
gain27: '10'
|
||||
gain28: '10'
|
||||
gain29: '10'
|
||||
gain3: '10'
|
||||
gain30: '10'
|
||||
gain31: '10'
|
||||
gain4: '10'
|
||||
gain5: '10'
|
||||
gain6: '10'
|
||||
gain7: '10'
|
||||
gain8: '10'
|
||||
gain9: '10'
|
||||
if_gain0: if_gain
|
||||
if_gain1: '20'
|
||||
if_gain10: '20'
|
||||
if_gain11: '20'
|
||||
if_gain12: '20'
|
||||
if_gain13: '20'
|
||||
if_gain14: '20'
|
||||
if_gain15: '20'
|
||||
if_gain16: '20'
|
||||
if_gain17: '20'
|
||||
if_gain18: '20'
|
||||
if_gain19: '20'
|
||||
if_gain2: '20'
|
||||
if_gain20: '20'
|
||||
if_gain21: '20'
|
||||
if_gain22: '20'
|
||||
if_gain23: '20'
|
||||
if_gain24: '20'
|
||||
if_gain25: '20'
|
||||
if_gain26: '20'
|
||||
if_gain27: '20'
|
||||
if_gain28: '20'
|
||||
if_gain29: '20'
|
||||
if_gain3: '20'
|
||||
if_gain30: '20'
|
||||
if_gain31: '20'
|
||||
if_gain4: '20'
|
||||
if_gain5: '20'
|
||||
if_gain6: '20'
|
||||
if_gain7: '20'
|
||||
if_gain8: '20'
|
||||
if_gain9: '20'
|
||||
maxoutbuf: '0'
|
||||
minoutbuf: '0'
|
||||
nchan: '1'
|
||||
num_mboards: '1'
|
||||
sample_rate: sample_rate
|
||||
sync: sync
|
||||
time_source0: ''
|
||||
time_source1: ''
|
||||
time_source2: ''
|
||||
time_source3: ''
|
||||
time_source4: ''
|
||||
time_source5: ''
|
||||
time_source6: ''
|
||||
time_source7: ''
|
||||
type: fc32
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [746, 195]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
connections:
|
||||
- [blocks_udp_source_0, '0', osmosdr_sink_0, '0']
|
||||
|
||||
metadata:
|
||||
file_format: 1
|
||||
@@ -0,0 +1,137 @@
|
||||
from optparse import OptionParser
|
||||
|
||||
import Initializer
|
||||
from InputHandlerThread import InputHandlerThread
|
||||
|
||||
Initializer.init_path()
|
||||
|
||||
from gnuradio import blocks
|
||||
from gnuradio import gr
|
||||
import osmosdr
|
||||
|
||||
|
||||
class top_block(gr.top_block):
|
||||
|
||||
def __init__(self, sample_rate, frequency, freq_correction, rf_gain, if_gain, bb_gain, bandwidth, port):
|
||||
gr.top_block.__init__(self, "Top Block")
|
||||
|
||||
self.sample_rate = sample_rate
|
||||
self.rf_gain = rf_gain
|
||||
self.port = port
|
||||
self.if_gain = if_gain
|
||||
self.frequency = frequency
|
||||
self.freq_correction = freq_correction
|
||||
self.bb_gain = bb_gain
|
||||
self.bandwidth = bandwidth
|
||||
|
||||
self.osmosdr_sink_0 = osmosdr.sink(
|
||||
args="numchan=" + str(1) + " " + 'hackrf'
|
||||
)
|
||||
self.osmosdr_sink_0.set_time_unknown_pps(osmosdr.time_spec_t())
|
||||
self.osmosdr_sink_0.set_sample_rate(sample_rate)
|
||||
self.osmosdr_sink_0.set_center_freq(frequency, 0)
|
||||
self.osmosdr_sink_0.set_freq_corr(freq_correction, 0)
|
||||
self.osmosdr_sink_0.set_gain(rf_gain, 0)
|
||||
self.osmosdr_sink_0.set_if_gain(if_gain, 0)
|
||||
self.osmosdr_sink_0.set_bb_gain(bb_gain, 0)
|
||||
self.osmosdr_sink_0.set_antenna('', 0)
|
||||
self.osmosdr_sink_0.set_bandwidth(bandwidth, 0)
|
||||
self.blocks_udp_source_0 = blocks.udp_source(gr.sizeof_gr_complex * 1, '127.0.0.1', port, 65536, False)
|
||||
|
||||
self.connect((self.blocks_udp_source_0, 0), (self.osmosdr_sink_0, 0))
|
||||
|
||||
def get_sample_rate(self):
|
||||
return self.sample_rate
|
||||
|
||||
def set_sample_rate(self, sample_rate):
|
||||
self.sample_rate = sample_rate
|
||||
self.osmosdr_sink_0.set_sample_rate(self.sample_rate)
|
||||
|
||||
def get_rf_gain(self):
|
||||
return self.rf_gain
|
||||
|
||||
def set_rf_gain(self, rf_gain):
|
||||
self.rf_gain = rf_gain
|
||||
self.osmosdr_sink_0.set_gain(self.rf_gain, 0)
|
||||
|
||||
def get_port(self):
|
||||
return self.port
|
||||
|
||||
def set_port(self, port):
|
||||
self.port = port
|
||||
|
||||
def get_if_gain(self):
|
||||
return self.if_gain
|
||||
|
||||
def set_if_gain(self, if_gain):
|
||||
self.if_gain = if_gain
|
||||
self.osmosdr_sink_0.set_if_gain(self.if_gain, 0)
|
||||
|
||||
def get_frequency(self):
|
||||
return self.frequency
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
self.frequency = frequency
|
||||
self.osmosdr_sink_0.set_center_freq(self.frequency, 0)
|
||||
|
||||
def get_freq_correction(self):
|
||||
return self.freq_correction
|
||||
|
||||
def set_freq_correction(self, freq_correction):
|
||||
self.freq_correction = freq_correction
|
||||
self.osmosdr_sink_0.set_freq_corr(self.freq_correction, 0)
|
||||
|
||||
def get_direct_sampling_mode(self):
|
||||
return self.direct_sampling_mode
|
||||
|
||||
def set_direct_sampling_mode(self, direct_sampling_mode):
|
||||
self.direct_sampling_mode = direct_sampling_mode
|
||||
|
||||
def get_channel_index(self):
|
||||
return self.channel_index
|
||||
|
||||
def set_channel_index(self, channel_index):
|
||||
self.channel_index = channel_index
|
||||
|
||||
def get_bb_gain(self):
|
||||
return self.bb_gain
|
||||
|
||||
def set_bb_gain(self, bb_gain):
|
||||
self.bb_gain = bb_gain
|
||||
self.osmosdr_sink_0.set_bb_gain(self.bb_gain, 0)
|
||||
|
||||
def get_bandwidth(self):
|
||||
return self.bandwidth
|
||||
|
||||
def set_bandwidth(self, bandwidth):
|
||||
self.bandwidth = bandwidth
|
||||
self.osmosdr_sink_0.set_bandwidth(self.bandwidth, 0)
|
||||
|
||||
def get_antenna_index(self):
|
||||
return self.antenna_index
|
||||
|
||||
def set_antenna_index(self, antenna_index):
|
||||
self.antenna_index = antenna_index
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = OptionParser(usage='%prog: [options]')
|
||||
parser.add_option('-s', '--sample-rate', dest='sample_rate', default=100000)
|
||||
parser.add_option('-f', '--frequency', dest='frequency', default=433000)
|
||||
parser.add_option('-g', '--gain', dest='rf_gain', default=30)
|
||||
parser.add_option('-i', '--if-gain', dest='if_gain', default=30)
|
||||
parser.add_option('-b', '--bb-gain', dest='bb_gain', default=30)
|
||||
parser.add_option('-w', '--bandwidth', dest='bandwidth', default=250000)
|
||||
parser.add_option('-c', '--freq-correction', dest='freq_correction', default=0)
|
||||
parser.add_option('-d', '--direct-sampling', dest='direct_sampling', default=0)
|
||||
parser.add_option('-n', '--channel-index', dest='channel_index', default=0)
|
||||
parser.add_option('-a', '--antenna-index', dest='antenna_index', default=0)
|
||||
parser.add_option('-p', '--port', dest='port', default=1234)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
tb = top_block(int(options.sample_rate), int(options.frequency), int(options.freq_correction), int(options.rf_gain),
|
||||
int(options.if_gain), int(options.bb_gain), int(options.bandwidth), int(options.port))
|
||||
iht = InputHandlerThread(tb)
|
||||
iht.start()
|
||||
tb.start()
|
||||
tb.wait()
|
||||
@@ -0,0 +1,547 @@
|
||||
options:
|
||||
parameters:
|
||||
author: ''
|
||||
category: '[GRC Hier Blocks]'
|
||||
cmake_opt: ''
|
||||
comment: ''
|
||||
copyright: ''
|
||||
description: ''
|
||||
gen_cmake: 'On'
|
||||
gen_linking: dynamic
|
||||
generate_options: no_gui
|
||||
hier_block_src_path: '.:'
|
||||
id: top_block
|
||||
max_nouts: '0'
|
||||
output_language: python
|
||||
placement: (0,0)
|
||||
qt_qss_theme: ''
|
||||
realtime_scheduling: ''
|
||||
run: 'True'
|
||||
run_command: '{python} -u {filename}'
|
||||
run_options: run
|
||||
sizing_mode: fixed
|
||||
thread_safe_setters: ''
|
||||
title: ''
|
||||
window_size: ''
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [8, 8]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
blocks:
|
||||
- name: antenna_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [707, 5]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: bandwidth
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '250000'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [219, 7]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: bb_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [420, 5]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: channel_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [559, 91]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: direct_sampling_mode
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [707, 88]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: freq_correction
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [563, 5]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: frequency
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 433e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [319, 93]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: if_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [342, 6]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: port
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '1234'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [988, 44]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: rf_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [223, 96]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: sample_rate
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 3e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [423, 90]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: blocks_tcp_server_sink_0
|
||||
id: blocks_tcp_server_sink
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
comment: ''
|
||||
ipaddr: 127.0.0.1
|
||||
noblock: 'False'
|
||||
port: port
|
||||
type: complex
|
||||
vlen: '1'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [591, 313]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: osmosdr_source_0
|
||||
id: osmosdr_source
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
ant0: ''
|
||||
ant1: ''
|
||||
ant10: ''
|
||||
ant11: ''
|
||||
ant12: ''
|
||||
ant13: ''
|
||||
ant14: ''
|
||||
ant15: ''
|
||||
ant16: ''
|
||||
ant17: ''
|
||||
ant18: ''
|
||||
ant19: ''
|
||||
ant2: ''
|
||||
ant20: ''
|
||||
ant21: ''
|
||||
ant22: ''
|
||||
ant23: ''
|
||||
ant24: ''
|
||||
ant25: ''
|
||||
ant26: ''
|
||||
ant27: ''
|
||||
ant28: ''
|
||||
ant29: ''
|
||||
ant3: ''
|
||||
ant30: ''
|
||||
ant31: ''
|
||||
ant4: ''
|
||||
ant5: ''
|
||||
ant6: ''
|
||||
ant7: ''
|
||||
ant8: ''
|
||||
ant9: ''
|
||||
args: rtl
|
||||
bb_gain0: bb_gain
|
||||
bb_gain1: '20'
|
||||
bb_gain10: '20'
|
||||
bb_gain11: '20'
|
||||
bb_gain12: '20'
|
||||
bb_gain13: '20'
|
||||
bb_gain14: '20'
|
||||
bb_gain15: '20'
|
||||
bb_gain16: '20'
|
||||
bb_gain17: '20'
|
||||
bb_gain18: '20'
|
||||
bb_gain19: '20'
|
||||
bb_gain2: '20'
|
||||
bb_gain20: '20'
|
||||
bb_gain21: '20'
|
||||
bb_gain22: '20'
|
||||
bb_gain23: '20'
|
||||
bb_gain24: '20'
|
||||
bb_gain25: '20'
|
||||
bb_gain26: '20'
|
||||
bb_gain27: '20'
|
||||
bb_gain28: '20'
|
||||
bb_gain29: '20'
|
||||
bb_gain3: '20'
|
||||
bb_gain30: '20'
|
||||
bb_gain31: '20'
|
||||
bb_gain4: '20'
|
||||
bb_gain5: '20'
|
||||
bb_gain6: '20'
|
||||
bb_gain7: '20'
|
||||
bb_gain8: '20'
|
||||
bb_gain9: '20'
|
||||
bw0: bandwidth
|
||||
bw1: '0'
|
||||
bw10: '0'
|
||||
bw11: '0'
|
||||
bw12: '0'
|
||||
bw13: '0'
|
||||
bw14: '0'
|
||||
bw15: '0'
|
||||
bw16: '0'
|
||||
bw17: '0'
|
||||
bw18: '0'
|
||||
bw19: '0'
|
||||
bw2: '0'
|
||||
bw20: '0'
|
||||
bw21: '0'
|
||||
bw22: '0'
|
||||
bw23: '0'
|
||||
bw24: '0'
|
||||
bw25: '0'
|
||||
bw26: '0'
|
||||
bw27: '0'
|
||||
bw28: '0'
|
||||
bw29: '0'
|
||||
bw3: '0'
|
||||
bw30: '0'
|
||||
bw31: '0'
|
||||
bw4: '0'
|
||||
bw5: '0'
|
||||
bw6: '0'
|
||||
bw7: '0'
|
||||
bw8: '0'
|
||||
bw9: '0'
|
||||
clock_source0: ''
|
||||
clock_source1: ''
|
||||
clock_source2: ''
|
||||
clock_source3: ''
|
||||
clock_source4: ''
|
||||
clock_source5: ''
|
||||
clock_source6: ''
|
||||
clock_source7: ''
|
||||
comment: ''
|
||||
corr0: freq_correction
|
||||
corr1: '0'
|
||||
corr10: '0'
|
||||
corr11: '0'
|
||||
corr12: '0'
|
||||
corr13: '0'
|
||||
corr14: '0'
|
||||
corr15: '0'
|
||||
corr16: '0'
|
||||
corr17: '0'
|
||||
corr18: '0'
|
||||
corr19: '0'
|
||||
corr2: '0'
|
||||
corr20: '0'
|
||||
corr21: '0'
|
||||
corr22: '0'
|
||||
corr23: '0'
|
||||
corr24: '0'
|
||||
corr25: '0'
|
||||
corr26: '0'
|
||||
corr27: '0'
|
||||
corr28: '0'
|
||||
corr29: '0'
|
||||
corr3: '0'
|
||||
corr30: '0'
|
||||
corr31: '0'
|
||||
corr4: '0'
|
||||
corr5: '0'
|
||||
corr6: '0'
|
||||
corr7: '0'
|
||||
corr8: '0'
|
||||
corr9: '0'
|
||||
dc_offset_mode0: '0'
|
||||
dc_offset_mode1: '0'
|
||||
dc_offset_mode10: '0'
|
||||
dc_offset_mode11: '0'
|
||||
dc_offset_mode12: '0'
|
||||
dc_offset_mode13: '0'
|
||||
dc_offset_mode14: '0'
|
||||
dc_offset_mode15: '0'
|
||||
dc_offset_mode16: '0'
|
||||
dc_offset_mode17: '0'
|
||||
dc_offset_mode18: '0'
|
||||
dc_offset_mode19: '0'
|
||||
dc_offset_mode2: '0'
|
||||
dc_offset_mode20: '0'
|
||||
dc_offset_mode21: '0'
|
||||
dc_offset_mode22: '0'
|
||||
dc_offset_mode23: '0'
|
||||
dc_offset_mode24: '0'
|
||||
dc_offset_mode25: '0'
|
||||
dc_offset_mode26: '0'
|
||||
dc_offset_mode27: '0'
|
||||
dc_offset_mode28: '0'
|
||||
dc_offset_mode29: '0'
|
||||
dc_offset_mode3: '0'
|
||||
dc_offset_mode30: '0'
|
||||
dc_offset_mode31: '0'
|
||||
dc_offset_mode4: '0'
|
||||
dc_offset_mode5: '0'
|
||||
dc_offset_mode6: '0'
|
||||
dc_offset_mode7: '0'
|
||||
dc_offset_mode8: '0'
|
||||
dc_offset_mode9: '0'
|
||||
freq0: frequency
|
||||
freq1: 100e6
|
||||
freq10: 100e6
|
||||
freq11: 100e6
|
||||
freq12: 100e6
|
||||
freq13: 100e6
|
||||
freq14: 100e6
|
||||
freq15: 100e6
|
||||
freq16: 100e6
|
||||
freq17: 100e6
|
||||
freq18: 100e6
|
||||
freq19: 100e6
|
||||
freq2: 100e6
|
||||
freq20: 100e6
|
||||
freq21: 100e6
|
||||
freq22: 100e6
|
||||
freq23: 100e6
|
||||
freq24: 100e6
|
||||
freq25: 100e6
|
||||
freq26: 100e6
|
||||
freq27: 100e6
|
||||
freq28: 100e6
|
||||
freq29: 100e6
|
||||
freq3: 100e6
|
||||
freq30: 100e6
|
||||
freq31: 100e6
|
||||
freq4: 100e6
|
||||
freq5: 100e6
|
||||
freq6: 100e6
|
||||
freq7: 100e6
|
||||
freq8: 100e6
|
||||
freq9: 100e6
|
||||
gain0: rf_gain
|
||||
gain1: '10'
|
||||
gain10: '10'
|
||||
gain11: '10'
|
||||
gain12: '10'
|
||||
gain13: '10'
|
||||
gain14: '10'
|
||||
gain15: '10'
|
||||
gain16: '10'
|
||||
gain17: '10'
|
||||
gain18: '10'
|
||||
gain19: '10'
|
||||
gain2: '10'
|
||||
gain20: '10'
|
||||
gain21: '10'
|
||||
gain22: '10'
|
||||
gain23: '10'
|
||||
gain24: '10'
|
||||
gain25: '10'
|
||||
gain26: '10'
|
||||
gain27: '10'
|
||||
gain28: '10'
|
||||
gain29: '10'
|
||||
gain3: '10'
|
||||
gain30: '10'
|
||||
gain31: '10'
|
||||
gain4: '10'
|
||||
gain5: '10'
|
||||
gain6: '10'
|
||||
gain7: '10'
|
||||
gain8: '10'
|
||||
gain9: '10'
|
||||
gain_mode0: 'False'
|
||||
gain_mode1: 'False'
|
||||
gain_mode10: 'False'
|
||||
gain_mode11: 'False'
|
||||
gain_mode12: 'False'
|
||||
gain_mode13: 'False'
|
||||
gain_mode14: 'False'
|
||||
gain_mode15: 'False'
|
||||
gain_mode16: 'False'
|
||||
gain_mode17: 'False'
|
||||
gain_mode18: 'False'
|
||||
gain_mode19: 'False'
|
||||
gain_mode2: 'False'
|
||||
gain_mode20: 'False'
|
||||
gain_mode21: 'False'
|
||||
gain_mode22: 'False'
|
||||
gain_mode23: 'False'
|
||||
gain_mode24: 'False'
|
||||
gain_mode25: 'False'
|
||||
gain_mode26: 'False'
|
||||
gain_mode27: 'False'
|
||||
gain_mode28: 'False'
|
||||
gain_mode29: 'False'
|
||||
gain_mode3: 'False'
|
||||
gain_mode30: 'False'
|
||||
gain_mode31: 'False'
|
||||
gain_mode4: 'False'
|
||||
gain_mode5: 'False'
|
||||
gain_mode6: 'False'
|
||||
gain_mode7: 'False'
|
||||
gain_mode8: 'False'
|
||||
gain_mode9: 'False'
|
||||
if_gain0: if_gain
|
||||
if_gain1: '20'
|
||||
if_gain10: '20'
|
||||
if_gain11: '20'
|
||||
if_gain12: '20'
|
||||
if_gain13: '20'
|
||||
if_gain14: '20'
|
||||
if_gain15: '20'
|
||||
if_gain16: '20'
|
||||
if_gain17: '20'
|
||||
if_gain18: '20'
|
||||
if_gain19: '20'
|
||||
if_gain2: '20'
|
||||
if_gain20: '20'
|
||||
if_gain21: '20'
|
||||
if_gain22: '20'
|
||||
if_gain23: '20'
|
||||
if_gain24: '20'
|
||||
if_gain25: '20'
|
||||
if_gain26: '20'
|
||||
if_gain27: '20'
|
||||
if_gain28: '20'
|
||||
if_gain29: '20'
|
||||
if_gain3: '20'
|
||||
if_gain30: '20'
|
||||
if_gain31: '20'
|
||||
if_gain4: '20'
|
||||
if_gain5: '20'
|
||||
if_gain6: '20'
|
||||
if_gain7: '20'
|
||||
if_gain8: '20'
|
||||
if_gain9: '20'
|
||||
iq_balance_mode0: '0'
|
||||
iq_balance_mode1: '0'
|
||||
iq_balance_mode10: '0'
|
||||
iq_balance_mode11: '0'
|
||||
iq_balance_mode12: '0'
|
||||
iq_balance_mode13: '0'
|
||||
iq_balance_mode14: '0'
|
||||
iq_balance_mode15: '0'
|
||||
iq_balance_mode16: '0'
|
||||
iq_balance_mode17: '0'
|
||||
iq_balance_mode18: '0'
|
||||
iq_balance_mode19: '0'
|
||||
iq_balance_mode2: '0'
|
||||
iq_balance_mode20: '0'
|
||||
iq_balance_mode21: '0'
|
||||
iq_balance_mode22: '0'
|
||||
iq_balance_mode23: '0'
|
||||
iq_balance_mode24: '0'
|
||||
iq_balance_mode25: '0'
|
||||
iq_balance_mode26: '0'
|
||||
iq_balance_mode27: '0'
|
||||
iq_balance_mode28: '0'
|
||||
iq_balance_mode29: '0'
|
||||
iq_balance_mode3: '0'
|
||||
iq_balance_mode30: '0'
|
||||
iq_balance_mode31: '0'
|
||||
iq_balance_mode4: '0'
|
||||
iq_balance_mode5: '0'
|
||||
iq_balance_mode6: '0'
|
||||
iq_balance_mode7: '0'
|
||||
iq_balance_mode8: '0'
|
||||
iq_balance_mode9: '0'
|
||||
maxoutbuf: '0'
|
||||
minoutbuf: '0'
|
||||
nchan: '1'
|
||||
num_mboards: '1'
|
||||
sample_rate: sample_rate
|
||||
sync: sync
|
||||
time_source0: ''
|
||||
time_source1: ''
|
||||
time_source2: ''
|
||||
time_source3: ''
|
||||
time_source4: ''
|
||||
time_source5: ''
|
||||
time_source6: ''
|
||||
time_source7: ''
|
||||
type: fc32
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [219, 216]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
connections:
|
||||
- [osmosdr_source_0, '0', blocks_tcp_server_sink_0, '0']
|
||||
|
||||
metadata:
|
||||
file_format: 1
|
||||
@@ -0,0 +1,137 @@
|
||||
from optparse import OptionParser
|
||||
|
||||
import Initializer
|
||||
from InputHandlerThread import InputHandlerThread
|
||||
|
||||
Initializer.init_path()
|
||||
|
||||
from gnuradio import blocks
|
||||
from gnuradio import gr
|
||||
import osmosdr
|
||||
|
||||
|
||||
class top_block(gr.top_block):
|
||||
|
||||
def __init__(self, sample_rate, frequency, freq_correction, rf_gain, if_gain, bb_gain, bandwidth, port):
|
||||
gr.top_block.__init__(self, "Top Block")
|
||||
|
||||
self.sample_rate = sample_rate
|
||||
self.rf_gain = rf_gain
|
||||
self.port = port
|
||||
self.if_gain = if_gain
|
||||
self.frequency = frequency
|
||||
self.freq_correction = freq_correction
|
||||
self.bb_gain = bb_gain
|
||||
self.bandwidth = bandwidth
|
||||
|
||||
self.osmosdr_source_0 = osmosdr.source(
|
||||
args="numchan=" + str(1) + " " + 'rtl'
|
||||
)
|
||||
self.osmosdr_source_0.set_time_unknown_pps(osmosdr.time_spec_t())
|
||||
self.osmosdr_source_0.set_sample_rate(sample_rate)
|
||||
self.osmosdr_source_0.set_center_freq(frequency, 0)
|
||||
self.osmosdr_source_0.set_freq_corr(freq_correction, 0)
|
||||
self.osmosdr_source_0.set_gain(rf_gain, 0)
|
||||
self.osmosdr_source_0.set_if_gain(if_gain, 0)
|
||||
self.osmosdr_source_0.set_bb_gain(bb_gain, 0)
|
||||
self.osmosdr_source_0.set_antenna('', 0)
|
||||
self.osmosdr_source_0.set_bandwidth(bandwidth, 0)
|
||||
self.blocks_tcp_server_sink_0 = blocks.tcp_server_sink(gr.sizeof_gr_complex * 1, '127.0.0.1', port, False)
|
||||
|
||||
self.connect((self.osmosdr_source_0, 0), (self.blocks_tcp_server_sink_0, 0))
|
||||
|
||||
def get_sample_rate(self):
|
||||
return self.sample_rate
|
||||
|
||||
def set_sample_rate(self, sample_rate):
|
||||
self.sample_rate = sample_rate
|
||||
self.osmosdr_source_0.set_sample_rate(self.sample_rate)
|
||||
|
||||
def get_rf_gain(self):
|
||||
return self.rf_gain
|
||||
|
||||
def set_rf_gain(self, rf_gain):
|
||||
self.rf_gain = rf_gain
|
||||
self.osmosdr_source_0.set_gain(self.rf_gain, 0)
|
||||
|
||||
def get_port(self):
|
||||
return self.port
|
||||
|
||||
def set_port(self, port):
|
||||
self.port = port
|
||||
|
||||
def get_if_gain(self):
|
||||
return self.if_gain
|
||||
|
||||
def set_if_gain(self, if_gain):
|
||||
self.if_gain = if_gain
|
||||
self.osmosdr_source_0.set_if_gain(self.if_gain, 0)
|
||||
|
||||
def get_frequency(self):
|
||||
return self.frequency
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
self.frequency = frequency
|
||||
self.osmosdr_source_0.set_center_freq(self.frequency, 0)
|
||||
|
||||
def get_freq_correction(self):
|
||||
return self.freq_correction
|
||||
|
||||
def set_freq_correction(self, freq_correction):
|
||||
self.freq_correction = freq_correction
|
||||
self.osmosdr_source_0.set_freq_corr(self.freq_correction, 0)
|
||||
|
||||
def get_direct_sampling_mode(self):
|
||||
return self.direct_sampling_mode
|
||||
|
||||
def set_direct_sampling_mode(self, direct_sampling_mode):
|
||||
self.direct_sampling_mode = direct_sampling_mode
|
||||
|
||||
def get_channel_index(self):
|
||||
return self.channel_index
|
||||
|
||||
def set_channel_index(self, channel_index):
|
||||
self.channel_index = channel_index
|
||||
|
||||
def get_bb_gain(self):
|
||||
return self.bb_gain
|
||||
|
||||
def set_bb_gain(self, bb_gain):
|
||||
self.bb_gain = bb_gain
|
||||
self.osmosdr_source_0.set_bb_gain(self.bb_gain, 0)
|
||||
|
||||
def get_bandwidth(self):
|
||||
return self.bandwidth
|
||||
|
||||
def set_bandwidth(self, bandwidth):
|
||||
self.bandwidth = bandwidth
|
||||
self.osmosdr_source_0.set_bandwidth(self.bandwidth, 0)
|
||||
|
||||
def get_antenna_index(self):
|
||||
return self.antenna_index
|
||||
|
||||
def set_antenna_index(self, antenna_index):
|
||||
self.antenna_index = antenna_index
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = OptionParser(usage='%prog: [options]')
|
||||
parser.add_option('-s', '--sample-rate', dest='sample_rate', default=100000)
|
||||
parser.add_option('-f', '--frequency', dest='frequency', default=433000)
|
||||
parser.add_option('-g', '--gain', dest='rf_gain', default=30)
|
||||
parser.add_option('-i', '--if-gain', dest='if_gain', default=30)
|
||||
parser.add_option('-b', '--bb-gain', dest='bb_gain', default=30)
|
||||
parser.add_option('-w', '--bandwidth', dest='bandwidth', default=250000)
|
||||
parser.add_option('-c', '--freq-correction', dest='freq_correction', default=0)
|
||||
parser.add_option('-d', '--direct-sampling', dest='direct_sampling', default=0)
|
||||
parser.add_option('-n', '--channel-index', dest='channel_index', default=0)
|
||||
parser.add_option('-a', '--antenna-index', dest='antenna_index', default=0)
|
||||
parser.add_option('-p', '--port', dest='port', default=1234)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
tb = top_block(int(options.sample_rate), int(options.frequency), int(options.freq_correction), int(options.rf_gain),
|
||||
int(options.if_gain), int(options.bb_gain), int(options.bandwidth), int(options.port))
|
||||
iht = InputHandlerThread(tb)
|
||||
iht.start()
|
||||
tb.start()
|
||||
tb.wait()
|
||||
@@ -0,0 +1,547 @@
|
||||
options:
|
||||
parameters:
|
||||
author: ''
|
||||
category: '[GRC Hier Blocks]'
|
||||
cmake_opt: ''
|
||||
comment: ''
|
||||
copyright: ''
|
||||
description: ''
|
||||
gen_cmake: 'On'
|
||||
gen_linking: dynamic
|
||||
generate_options: no_gui
|
||||
hier_block_src_path: '.:'
|
||||
id: top_block
|
||||
max_nouts: '0'
|
||||
output_language: python
|
||||
placement: (0,0)
|
||||
qt_qss_theme: ''
|
||||
realtime_scheduling: ''
|
||||
run: 'True'
|
||||
run_command: '{python} -u {filename}'
|
||||
run_options: run
|
||||
sizing_mode: fixed
|
||||
thread_safe_setters: ''
|
||||
title: ''
|
||||
window_size: ''
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [8, 8]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
blocks:
|
||||
- name: antenna_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [707, 5]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: bandwidth
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '250000'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [219, 7]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: bb_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [420, 5]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: channel_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [559, 91]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: direct_sampling_mode
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [707, 88]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: freq_correction
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [563, 5]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: frequency
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 433e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [319, 93]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: if_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [342, 6]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: port
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '1234'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [988, 44]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: rf_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [223, 96]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: sample_rate
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 3e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [423, 90]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: blocks_tcp_server_sink_0
|
||||
id: blocks_tcp_server_sink
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
comment: ''
|
||||
ipaddr: 127.0.0.1
|
||||
noblock: 'False'
|
||||
port: port
|
||||
type: complex
|
||||
vlen: '1'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [591, 313]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: osmosdr_source_0
|
||||
id: osmosdr_source
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
ant0: ''
|
||||
ant1: ''
|
||||
ant10: ''
|
||||
ant11: ''
|
||||
ant12: ''
|
||||
ant13: ''
|
||||
ant14: ''
|
||||
ant15: ''
|
||||
ant16: ''
|
||||
ant17: ''
|
||||
ant18: ''
|
||||
ant19: ''
|
||||
ant2: ''
|
||||
ant20: ''
|
||||
ant21: ''
|
||||
ant22: ''
|
||||
ant23: ''
|
||||
ant24: ''
|
||||
ant25: ''
|
||||
ant26: ''
|
||||
ant27: ''
|
||||
ant28: ''
|
||||
ant29: ''
|
||||
ant3: ''
|
||||
ant30: ''
|
||||
ant31: ''
|
||||
ant4: ''
|
||||
ant5: ''
|
||||
ant6: ''
|
||||
ant7: ''
|
||||
ant8: ''
|
||||
ant9: ''
|
||||
args: sdrplay
|
||||
bb_gain0: bb_gain
|
||||
bb_gain1: '20'
|
||||
bb_gain10: '20'
|
||||
bb_gain11: '20'
|
||||
bb_gain12: '20'
|
||||
bb_gain13: '20'
|
||||
bb_gain14: '20'
|
||||
bb_gain15: '20'
|
||||
bb_gain16: '20'
|
||||
bb_gain17: '20'
|
||||
bb_gain18: '20'
|
||||
bb_gain19: '20'
|
||||
bb_gain2: '20'
|
||||
bb_gain20: '20'
|
||||
bb_gain21: '20'
|
||||
bb_gain22: '20'
|
||||
bb_gain23: '20'
|
||||
bb_gain24: '20'
|
||||
bb_gain25: '20'
|
||||
bb_gain26: '20'
|
||||
bb_gain27: '20'
|
||||
bb_gain28: '20'
|
||||
bb_gain29: '20'
|
||||
bb_gain3: '20'
|
||||
bb_gain30: '20'
|
||||
bb_gain31: '20'
|
||||
bb_gain4: '20'
|
||||
bb_gain5: '20'
|
||||
bb_gain6: '20'
|
||||
bb_gain7: '20'
|
||||
bb_gain8: '20'
|
||||
bb_gain9: '20'
|
||||
bw0: bandwidth
|
||||
bw1: '0'
|
||||
bw10: '0'
|
||||
bw11: '0'
|
||||
bw12: '0'
|
||||
bw13: '0'
|
||||
bw14: '0'
|
||||
bw15: '0'
|
||||
bw16: '0'
|
||||
bw17: '0'
|
||||
bw18: '0'
|
||||
bw19: '0'
|
||||
bw2: '0'
|
||||
bw20: '0'
|
||||
bw21: '0'
|
||||
bw22: '0'
|
||||
bw23: '0'
|
||||
bw24: '0'
|
||||
bw25: '0'
|
||||
bw26: '0'
|
||||
bw27: '0'
|
||||
bw28: '0'
|
||||
bw29: '0'
|
||||
bw3: '0'
|
||||
bw30: '0'
|
||||
bw31: '0'
|
||||
bw4: '0'
|
||||
bw5: '0'
|
||||
bw6: '0'
|
||||
bw7: '0'
|
||||
bw8: '0'
|
||||
bw9: '0'
|
||||
clock_source0: ''
|
||||
clock_source1: ''
|
||||
clock_source2: ''
|
||||
clock_source3: ''
|
||||
clock_source4: ''
|
||||
clock_source5: ''
|
||||
clock_source6: ''
|
||||
clock_source7: ''
|
||||
comment: ''
|
||||
corr0: freq_correction
|
||||
corr1: '0'
|
||||
corr10: '0'
|
||||
corr11: '0'
|
||||
corr12: '0'
|
||||
corr13: '0'
|
||||
corr14: '0'
|
||||
corr15: '0'
|
||||
corr16: '0'
|
||||
corr17: '0'
|
||||
corr18: '0'
|
||||
corr19: '0'
|
||||
corr2: '0'
|
||||
corr20: '0'
|
||||
corr21: '0'
|
||||
corr22: '0'
|
||||
corr23: '0'
|
||||
corr24: '0'
|
||||
corr25: '0'
|
||||
corr26: '0'
|
||||
corr27: '0'
|
||||
corr28: '0'
|
||||
corr29: '0'
|
||||
corr3: '0'
|
||||
corr30: '0'
|
||||
corr31: '0'
|
||||
corr4: '0'
|
||||
corr5: '0'
|
||||
corr6: '0'
|
||||
corr7: '0'
|
||||
corr8: '0'
|
||||
corr9: '0'
|
||||
dc_offset_mode0: '0'
|
||||
dc_offset_mode1: '0'
|
||||
dc_offset_mode10: '0'
|
||||
dc_offset_mode11: '0'
|
||||
dc_offset_mode12: '0'
|
||||
dc_offset_mode13: '0'
|
||||
dc_offset_mode14: '0'
|
||||
dc_offset_mode15: '0'
|
||||
dc_offset_mode16: '0'
|
||||
dc_offset_mode17: '0'
|
||||
dc_offset_mode18: '0'
|
||||
dc_offset_mode19: '0'
|
||||
dc_offset_mode2: '0'
|
||||
dc_offset_mode20: '0'
|
||||
dc_offset_mode21: '0'
|
||||
dc_offset_mode22: '0'
|
||||
dc_offset_mode23: '0'
|
||||
dc_offset_mode24: '0'
|
||||
dc_offset_mode25: '0'
|
||||
dc_offset_mode26: '0'
|
||||
dc_offset_mode27: '0'
|
||||
dc_offset_mode28: '0'
|
||||
dc_offset_mode29: '0'
|
||||
dc_offset_mode3: '0'
|
||||
dc_offset_mode30: '0'
|
||||
dc_offset_mode31: '0'
|
||||
dc_offset_mode4: '0'
|
||||
dc_offset_mode5: '0'
|
||||
dc_offset_mode6: '0'
|
||||
dc_offset_mode7: '0'
|
||||
dc_offset_mode8: '0'
|
||||
dc_offset_mode9: '0'
|
||||
freq0: frequency
|
||||
freq1: 100e6
|
||||
freq10: 100e6
|
||||
freq11: 100e6
|
||||
freq12: 100e6
|
||||
freq13: 100e6
|
||||
freq14: 100e6
|
||||
freq15: 100e6
|
||||
freq16: 100e6
|
||||
freq17: 100e6
|
||||
freq18: 100e6
|
||||
freq19: 100e6
|
||||
freq2: 100e6
|
||||
freq20: 100e6
|
||||
freq21: 100e6
|
||||
freq22: 100e6
|
||||
freq23: 100e6
|
||||
freq24: 100e6
|
||||
freq25: 100e6
|
||||
freq26: 100e6
|
||||
freq27: 100e6
|
||||
freq28: 100e6
|
||||
freq29: 100e6
|
||||
freq3: 100e6
|
||||
freq30: 100e6
|
||||
freq31: 100e6
|
||||
freq4: 100e6
|
||||
freq5: 100e6
|
||||
freq6: 100e6
|
||||
freq7: 100e6
|
||||
freq8: 100e6
|
||||
freq9: 100e6
|
||||
gain0: rf_gain
|
||||
gain1: '10'
|
||||
gain10: '10'
|
||||
gain11: '10'
|
||||
gain12: '10'
|
||||
gain13: '10'
|
||||
gain14: '10'
|
||||
gain15: '10'
|
||||
gain16: '10'
|
||||
gain17: '10'
|
||||
gain18: '10'
|
||||
gain19: '10'
|
||||
gain2: '10'
|
||||
gain20: '10'
|
||||
gain21: '10'
|
||||
gain22: '10'
|
||||
gain23: '10'
|
||||
gain24: '10'
|
||||
gain25: '10'
|
||||
gain26: '10'
|
||||
gain27: '10'
|
||||
gain28: '10'
|
||||
gain29: '10'
|
||||
gain3: '10'
|
||||
gain30: '10'
|
||||
gain31: '10'
|
||||
gain4: '10'
|
||||
gain5: '10'
|
||||
gain6: '10'
|
||||
gain7: '10'
|
||||
gain8: '10'
|
||||
gain9: '10'
|
||||
gain_mode0: 'False'
|
||||
gain_mode1: 'False'
|
||||
gain_mode10: 'False'
|
||||
gain_mode11: 'False'
|
||||
gain_mode12: 'False'
|
||||
gain_mode13: 'False'
|
||||
gain_mode14: 'False'
|
||||
gain_mode15: 'False'
|
||||
gain_mode16: 'False'
|
||||
gain_mode17: 'False'
|
||||
gain_mode18: 'False'
|
||||
gain_mode19: 'False'
|
||||
gain_mode2: 'False'
|
||||
gain_mode20: 'False'
|
||||
gain_mode21: 'False'
|
||||
gain_mode22: 'False'
|
||||
gain_mode23: 'False'
|
||||
gain_mode24: 'False'
|
||||
gain_mode25: 'False'
|
||||
gain_mode26: 'False'
|
||||
gain_mode27: 'False'
|
||||
gain_mode28: 'False'
|
||||
gain_mode29: 'False'
|
||||
gain_mode3: 'False'
|
||||
gain_mode30: 'False'
|
||||
gain_mode31: 'False'
|
||||
gain_mode4: 'False'
|
||||
gain_mode5: 'False'
|
||||
gain_mode6: 'False'
|
||||
gain_mode7: 'False'
|
||||
gain_mode8: 'False'
|
||||
gain_mode9: 'False'
|
||||
if_gain0: if_gain
|
||||
if_gain1: '20'
|
||||
if_gain10: '20'
|
||||
if_gain11: '20'
|
||||
if_gain12: '20'
|
||||
if_gain13: '20'
|
||||
if_gain14: '20'
|
||||
if_gain15: '20'
|
||||
if_gain16: '20'
|
||||
if_gain17: '20'
|
||||
if_gain18: '20'
|
||||
if_gain19: '20'
|
||||
if_gain2: '20'
|
||||
if_gain20: '20'
|
||||
if_gain21: '20'
|
||||
if_gain22: '20'
|
||||
if_gain23: '20'
|
||||
if_gain24: '20'
|
||||
if_gain25: '20'
|
||||
if_gain26: '20'
|
||||
if_gain27: '20'
|
||||
if_gain28: '20'
|
||||
if_gain29: '20'
|
||||
if_gain3: '20'
|
||||
if_gain30: '20'
|
||||
if_gain31: '20'
|
||||
if_gain4: '20'
|
||||
if_gain5: '20'
|
||||
if_gain6: '20'
|
||||
if_gain7: '20'
|
||||
if_gain8: '20'
|
||||
if_gain9: '20'
|
||||
iq_balance_mode0: '0'
|
||||
iq_balance_mode1: '0'
|
||||
iq_balance_mode10: '0'
|
||||
iq_balance_mode11: '0'
|
||||
iq_balance_mode12: '0'
|
||||
iq_balance_mode13: '0'
|
||||
iq_balance_mode14: '0'
|
||||
iq_balance_mode15: '0'
|
||||
iq_balance_mode16: '0'
|
||||
iq_balance_mode17: '0'
|
||||
iq_balance_mode18: '0'
|
||||
iq_balance_mode19: '0'
|
||||
iq_balance_mode2: '0'
|
||||
iq_balance_mode20: '0'
|
||||
iq_balance_mode21: '0'
|
||||
iq_balance_mode22: '0'
|
||||
iq_balance_mode23: '0'
|
||||
iq_balance_mode24: '0'
|
||||
iq_balance_mode25: '0'
|
||||
iq_balance_mode26: '0'
|
||||
iq_balance_mode27: '0'
|
||||
iq_balance_mode28: '0'
|
||||
iq_balance_mode29: '0'
|
||||
iq_balance_mode3: '0'
|
||||
iq_balance_mode30: '0'
|
||||
iq_balance_mode31: '0'
|
||||
iq_balance_mode4: '0'
|
||||
iq_balance_mode5: '0'
|
||||
iq_balance_mode6: '0'
|
||||
iq_balance_mode7: '0'
|
||||
iq_balance_mode8: '0'
|
||||
iq_balance_mode9: '0'
|
||||
maxoutbuf: '0'
|
||||
minoutbuf: '0'
|
||||
nchan: '1'
|
||||
num_mboards: '1'
|
||||
sample_rate: sample_rate
|
||||
sync: sync
|
||||
time_source0: ''
|
||||
time_source1: ''
|
||||
time_source2: ''
|
||||
time_source3: ''
|
||||
time_source4: ''
|
||||
time_source5: ''
|
||||
time_source6: ''
|
||||
time_source7: ''
|
||||
type: fc32
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [219, 216]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
connections:
|
||||
- [osmosdr_source_0, '0', blocks_tcp_server_sink_0, '0']
|
||||
|
||||
metadata:
|
||||
file_format: 1
|
||||
@@ -0,0 +1,137 @@
|
||||
from optparse import OptionParser
|
||||
|
||||
import Initializer
|
||||
from InputHandlerThread import InputHandlerThread
|
||||
|
||||
Initializer.init_path()
|
||||
|
||||
from gnuradio import blocks
|
||||
from gnuradio import gr
|
||||
import osmosdr
|
||||
|
||||
|
||||
class top_block(gr.top_block):
|
||||
|
||||
def __init__(self, sample_rate, frequency, freq_correction, rf_gain, if_gain, bb_gain, bandwidth, port):
|
||||
gr.top_block.__init__(self, "Top Block")
|
||||
|
||||
self.sample_rate = sample_rate
|
||||
self.rf_gain = rf_gain
|
||||
self.port = port
|
||||
self.if_gain = if_gain
|
||||
self.frequency = frequency
|
||||
self.freq_correction = freq_correction
|
||||
self.bb_gain = bb_gain
|
||||
self.bandwidth = bandwidth
|
||||
|
||||
self.osmosdr_source_0 = osmosdr.source(
|
||||
args="numchan=" + str(1) + " " + 'sdrplay'
|
||||
)
|
||||
self.osmosdr_source_0.set_time_unknown_pps(osmosdr.time_spec_t())
|
||||
self.osmosdr_source_0.set_sample_rate(sample_rate)
|
||||
self.osmosdr_source_0.set_center_freq(frequency, 0)
|
||||
self.osmosdr_source_0.set_freq_corr(freq_correction, 0)
|
||||
self.osmosdr_source_0.set_gain(rf_gain, 0)
|
||||
self.osmosdr_source_0.set_if_gain(if_gain, 0)
|
||||
self.osmosdr_source_0.set_bb_gain(bb_gain, 0)
|
||||
self.osmosdr_source_0.set_antenna('', 0)
|
||||
self.osmosdr_source_0.set_bandwidth(bandwidth, 0)
|
||||
self.blocks_tcp_server_sink_0 = blocks.tcp_server_sink(gr.sizeof_gr_complex * 1, '127.0.0.1', port, False)
|
||||
|
||||
self.connect((self.osmosdr_source_0, 0), (self.blocks_tcp_server_sink_0, 0))
|
||||
|
||||
def get_sample_rate(self):
|
||||
return self.sample_rate
|
||||
|
||||
def set_sample_rate(self, sample_rate):
|
||||
self.sample_rate = sample_rate
|
||||
self.osmosdr_source_0.set_sample_rate(self.sample_rate)
|
||||
|
||||
def get_rf_gain(self):
|
||||
return self.rf_gain
|
||||
|
||||
def set_rf_gain(self, rf_gain):
|
||||
self.rf_gain = rf_gain
|
||||
self.osmosdr_source_0.set_gain(self.rf_gain, 0)
|
||||
|
||||
def get_port(self):
|
||||
return self.port
|
||||
|
||||
def set_port(self, port):
|
||||
self.port = port
|
||||
|
||||
def get_if_gain(self):
|
||||
return self.if_gain
|
||||
|
||||
def set_if_gain(self, if_gain):
|
||||
self.if_gain = if_gain
|
||||
self.osmosdr_source_0.set_if_gain(self.if_gain, 0)
|
||||
|
||||
def get_frequency(self):
|
||||
return self.frequency
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
self.frequency = frequency
|
||||
self.osmosdr_source_0.set_center_freq(self.frequency, 0)
|
||||
|
||||
def get_freq_correction(self):
|
||||
return self.freq_correction
|
||||
|
||||
def set_freq_correction(self, freq_correction):
|
||||
self.freq_correction = freq_correction
|
||||
self.osmosdr_source_0.set_freq_corr(self.freq_correction, 0)
|
||||
|
||||
def get_direct_sampling_mode(self):
|
||||
return self.direct_sampling_mode
|
||||
|
||||
def set_direct_sampling_mode(self, direct_sampling_mode):
|
||||
self.direct_sampling_mode = direct_sampling_mode
|
||||
|
||||
def get_channel_index(self):
|
||||
return self.channel_index
|
||||
|
||||
def set_channel_index(self, channel_index):
|
||||
self.channel_index = channel_index
|
||||
|
||||
def get_bb_gain(self):
|
||||
return self.bb_gain
|
||||
|
||||
def set_bb_gain(self, bb_gain):
|
||||
self.bb_gain = bb_gain
|
||||
self.osmosdr_source_0.set_bb_gain(self.bb_gain, 0)
|
||||
|
||||
def get_bandwidth(self):
|
||||
return self.bandwidth
|
||||
|
||||
def set_bandwidth(self, bandwidth):
|
||||
self.bandwidth = bandwidth
|
||||
self.osmosdr_source_0.set_bandwidth(self.bandwidth, 0)
|
||||
|
||||
def get_antenna_index(self):
|
||||
return self.antenna_index
|
||||
|
||||
def set_antenna_index(self, antenna_index):
|
||||
self.antenna_index = antenna_index
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = OptionParser(usage='%prog: [options]')
|
||||
parser.add_option('-s', '--sample-rate', dest='sample_rate', default=100000)
|
||||
parser.add_option('-f', '--frequency', dest='frequency', default=433000)
|
||||
parser.add_option('-g', '--gain', dest='rf_gain', default=30)
|
||||
parser.add_option('-i', '--if-gain', dest='if_gain', default=30)
|
||||
parser.add_option('-b', '--bb-gain', dest='bb_gain', default=30)
|
||||
parser.add_option('-w', '--bandwidth', dest='bandwidth', default=250000)
|
||||
parser.add_option('-c', '--freq-correction', dest='freq_correction', default=0)
|
||||
parser.add_option('-d', '--direct-sampling', dest='direct_sampling', default=0)
|
||||
parser.add_option('-n', '--channel-index', dest='channel_index', default=0)
|
||||
parser.add_option('-a', '--antenna-index', dest='antenna_index', default=0)
|
||||
parser.add_option('-p', '--port', dest='port', default=1234)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
tb = top_block(int(options.sample_rate), int(options.frequency), int(options.freq_correction), int(options.rf_gain),
|
||||
int(options.if_gain), int(options.bb_gain), int(options.bandwidth), int(options.port))
|
||||
iht = InputHandlerThread(tb)
|
||||
iht.start()
|
||||
tb.start()
|
||||
tb.wait()
|
||||
@@ -0,0 +1,547 @@
|
||||
options:
|
||||
parameters:
|
||||
author: ''
|
||||
category: Custom
|
||||
cmake_opt: ''
|
||||
comment: ''
|
||||
copyright: ''
|
||||
description: ''
|
||||
gen_cmake: 'On'
|
||||
gen_linking: dynamic
|
||||
generate_options: no_gui
|
||||
hier_block_src_path: '.:'
|
||||
id: top_block
|
||||
max_nouts: '0'
|
||||
output_language: python
|
||||
placement: (0,0)
|
||||
qt_qss_theme: ''
|
||||
realtime_scheduling: ''
|
||||
run: 'True'
|
||||
run_command: '{python} -u {filename}'
|
||||
run_options: run
|
||||
sizing_mode: fixed
|
||||
thread_safe_setters: ''
|
||||
title: ''
|
||||
window_size: ''
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [8, 8]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
blocks:
|
||||
- name: antenna_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [707, 5]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: bandwidth
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '250000'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [219, 7]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: bb_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [420, 5]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: channel_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [559, 91]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: direct_sampling_mode
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [707, 88]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: freq_correction
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [563, 5]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: frequency
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 433e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [319, 93]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: if_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [342, 6]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: port
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '1234'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [988, 44]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: rf_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [223, 96]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: sample_rate
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 3e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [423, 90]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: blocks_tcp_server_sink_0
|
||||
id: blocks_tcp_server_sink
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
comment: ''
|
||||
ipaddr: 127.0.0.1
|
||||
noblock: 'False'
|
||||
port: port
|
||||
type: complex
|
||||
vlen: '1'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [591, 313]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: osmosdr_source_0
|
||||
id: osmosdr_source
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
ant0: ''
|
||||
ant1: ''
|
||||
ant10: ''
|
||||
ant11: ''
|
||||
ant12: ''
|
||||
ant13: ''
|
||||
ant14: ''
|
||||
ant15: ''
|
||||
ant16: ''
|
||||
ant17: ''
|
||||
ant18: ''
|
||||
ant19: ''
|
||||
ant2: ''
|
||||
ant20: ''
|
||||
ant21: ''
|
||||
ant22: ''
|
||||
ant23: ''
|
||||
ant24: ''
|
||||
ant25: ''
|
||||
ant26: ''
|
||||
ant27: ''
|
||||
ant28: ''
|
||||
ant29: ''
|
||||
ant3: ''
|
||||
ant30: ''
|
||||
ant31: ''
|
||||
ant4: ''
|
||||
ant5: ''
|
||||
ant6: ''
|
||||
ant7: ''
|
||||
ant8: ''
|
||||
ant9: ''
|
||||
args: uhd
|
||||
bb_gain0: bb_gain
|
||||
bb_gain1: '20'
|
||||
bb_gain10: '20'
|
||||
bb_gain11: '20'
|
||||
bb_gain12: '20'
|
||||
bb_gain13: '20'
|
||||
bb_gain14: '20'
|
||||
bb_gain15: '20'
|
||||
bb_gain16: '20'
|
||||
bb_gain17: '20'
|
||||
bb_gain18: '20'
|
||||
bb_gain19: '20'
|
||||
bb_gain2: '20'
|
||||
bb_gain20: '20'
|
||||
bb_gain21: '20'
|
||||
bb_gain22: '20'
|
||||
bb_gain23: '20'
|
||||
bb_gain24: '20'
|
||||
bb_gain25: '20'
|
||||
bb_gain26: '20'
|
||||
bb_gain27: '20'
|
||||
bb_gain28: '20'
|
||||
bb_gain29: '20'
|
||||
bb_gain3: '20'
|
||||
bb_gain30: '20'
|
||||
bb_gain31: '20'
|
||||
bb_gain4: '20'
|
||||
bb_gain5: '20'
|
||||
bb_gain6: '20'
|
||||
bb_gain7: '20'
|
||||
bb_gain8: '20'
|
||||
bb_gain9: '20'
|
||||
bw0: bandwidth
|
||||
bw1: '0'
|
||||
bw10: '0'
|
||||
bw11: '0'
|
||||
bw12: '0'
|
||||
bw13: '0'
|
||||
bw14: '0'
|
||||
bw15: '0'
|
||||
bw16: '0'
|
||||
bw17: '0'
|
||||
bw18: '0'
|
||||
bw19: '0'
|
||||
bw2: '0'
|
||||
bw20: '0'
|
||||
bw21: '0'
|
||||
bw22: '0'
|
||||
bw23: '0'
|
||||
bw24: '0'
|
||||
bw25: '0'
|
||||
bw26: '0'
|
||||
bw27: '0'
|
||||
bw28: '0'
|
||||
bw29: '0'
|
||||
bw3: '0'
|
||||
bw30: '0'
|
||||
bw31: '0'
|
||||
bw4: '0'
|
||||
bw5: '0'
|
||||
bw6: '0'
|
||||
bw7: '0'
|
||||
bw8: '0'
|
||||
bw9: '0'
|
||||
clock_source0: ''
|
||||
clock_source1: ''
|
||||
clock_source2: ''
|
||||
clock_source3: ''
|
||||
clock_source4: ''
|
||||
clock_source5: ''
|
||||
clock_source6: ''
|
||||
clock_source7: ''
|
||||
comment: ''
|
||||
corr0: freq_correction
|
||||
corr1: '0'
|
||||
corr10: '0'
|
||||
corr11: '0'
|
||||
corr12: '0'
|
||||
corr13: '0'
|
||||
corr14: '0'
|
||||
corr15: '0'
|
||||
corr16: '0'
|
||||
corr17: '0'
|
||||
corr18: '0'
|
||||
corr19: '0'
|
||||
corr2: '0'
|
||||
corr20: '0'
|
||||
corr21: '0'
|
||||
corr22: '0'
|
||||
corr23: '0'
|
||||
corr24: '0'
|
||||
corr25: '0'
|
||||
corr26: '0'
|
||||
corr27: '0'
|
||||
corr28: '0'
|
||||
corr29: '0'
|
||||
corr3: '0'
|
||||
corr30: '0'
|
||||
corr31: '0'
|
||||
corr4: '0'
|
||||
corr5: '0'
|
||||
corr6: '0'
|
||||
corr7: '0'
|
||||
corr8: '0'
|
||||
corr9: '0'
|
||||
dc_offset_mode0: '0'
|
||||
dc_offset_mode1: '0'
|
||||
dc_offset_mode10: '0'
|
||||
dc_offset_mode11: '0'
|
||||
dc_offset_mode12: '0'
|
||||
dc_offset_mode13: '0'
|
||||
dc_offset_mode14: '0'
|
||||
dc_offset_mode15: '0'
|
||||
dc_offset_mode16: '0'
|
||||
dc_offset_mode17: '0'
|
||||
dc_offset_mode18: '0'
|
||||
dc_offset_mode19: '0'
|
||||
dc_offset_mode2: '0'
|
||||
dc_offset_mode20: '0'
|
||||
dc_offset_mode21: '0'
|
||||
dc_offset_mode22: '0'
|
||||
dc_offset_mode23: '0'
|
||||
dc_offset_mode24: '0'
|
||||
dc_offset_mode25: '0'
|
||||
dc_offset_mode26: '0'
|
||||
dc_offset_mode27: '0'
|
||||
dc_offset_mode28: '0'
|
||||
dc_offset_mode29: '0'
|
||||
dc_offset_mode3: '0'
|
||||
dc_offset_mode30: '0'
|
||||
dc_offset_mode31: '0'
|
||||
dc_offset_mode4: '0'
|
||||
dc_offset_mode5: '0'
|
||||
dc_offset_mode6: '0'
|
||||
dc_offset_mode7: '0'
|
||||
dc_offset_mode8: '0'
|
||||
dc_offset_mode9: '0'
|
||||
freq0: frequency
|
||||
freq1: 100e6
|
||||
freq10: 100e6
|
||||
freq11: 100e6
|
||||
freq12: 100e6
|
||||
freq13: 100e6
|
||||
freq14: 100e6
|
||||
freq15: 100e6
|
||||
freq16: 100e6
|
||||
freq17: 100e6
|
||||
freq18: 100e6
|
||||
freq19: 100e6
|
||||
freq2: 100e6
|
||||
freq20: 100e6
|
||||
freq21: 100e6
|
||||
freq22: 100e6
|
||||
freq23: 100e6
|
||||
freq24: 100e6
|
||||
freq25: 100e6
|
||||
freq26: 100e6
|
||||
freq27: 100e6
|
||||
freq28: 100e6
|
||||
freq29: 100e6
|
||||
freq3: 100e6
|
||||
freq30: 100e6
|
||||
freq31: 100e6
|
||||
freq4: 100e6
|
||||
freq5: 100e6
|
||||
freq6: 100e6
|
||||
freq7: 100e6
|
||||
freq8: 100e6
|
||||
freq9: 100e6
|
||||
gain0: rf_gain
|
||||
gain1: '10'
|
||||
gain10: '10'
|
||||
gain11: '10'
|
||||
gain12: '10'
|
||||
gain13: '10'
|
||||
gain14: '10'
|
||||
gain15: '10'
|
||||
gain16: '10'
|
||||
gain17: '10'
|
||||
gain18: '10'
|
||||
gain19: '10'
|
||||
gain2: '10'
|
||||
gain20: '10'
|
||||
gain21: '10'
|
||||
gain22: '10'
|
||||
gain23: '10'
|
||||
gain24: '10'
|
||||
gain25: '10'
|
||||
gain26: '10'
|
||||
gain27: '10'
|
||||
gain28: '10'
|
||||
gain29: '10'
|
||||
gain3: '10'
|
||||
gain30: '10'
|
||||
gain31: '10'
|
||||
gain4: '10'
|
||||
gain5: '10'
|
||||
gain6: '10'
|
||||
gain7: '10'
|
||||
gain8: '10'
|
||||
gain9: '10'
|
||||
gain_mode0: 'False'
|
||||
gain_mode1: 'False'
|
||||
gain_mode10: 'False'
|
||||
gain_mode11: 'False'
|
||||
gain_mode12: 'False'
|
||||
gain_mode13: 'False'
|
||||
gain_mode14: 'False'
|
||||
gain_mode15: 'False'
|
||||
gain_mode16: 'False'
|
||||
gain_mode17: 'False'
|
||||
gain_mode18: 'False'
|
||||
gain_mode19: 'False'
|
||||
gain_mode2: 'False'
|
||||
gain_mode20: 'False'
|
||||
gain_mode21: 'False'
|
||||
gain_mode22: 'False'
|
||||
gain_mode23: 'False'
|
||||
gain_mode24: 'False'
|
||||
gain_mode25: 'False'
|
||||
gain_mode26: 'False'
|
||||
gain_mode27: 'False'
|
||||
gain_mode28: 'False'
|
||||
gain_mode29: 'False'
|
||||
gain_mode3: 'False'
|
||||
gain_mode30: 'False'
|
||||
gain_mode31: 'False'
|
||||
gain_mode4: 'False'
|
||||
gain_mode5: 'False'
|
||||
gain_mode6: 'False'
|
||||
gain_mode7: 'False'
|
||||
gain_mode8: 'False'
|
||||
gain_mode9: 'False'
|
||||
if_gain0: if_gain
|
||||
if_gain1: '20'
|
||||
if_gain10: '20'
|
||||
if_gain11: '20'
|
||||
if_gain12: '20'
|
||||
if_gain13: '20'
|
||||
if_gain14: '20'
|
||||
if_gain15: '20'
|
||||
if_gain16: '20'
|
||||
if_gain17: '20'
|
||||
if_gain18: '20'
|
||||
if_gain19: '20'
|
||||
if_gain2: '20'
|
||||
if_gain20: '20'
|
||||
if_gain21: '20'
|
||||
if_gain22: '20'
|
||||
if_gain23: '20'
|
||||
if_gain24: '20'
|
||||
if_gain25: '20'
|
||||
if_gain26: '20'
|
||||
if_gain27: '20'
|
||||
if_gain28: '20'
|
||||
if_gain29: '20'
|
||||
if_gain3: '20'
|
||||
if_gain30: '20'
|
||||
if_gain31: '20'
|
||||
if_gain4: '20'
|
||||
if_gain5: '20'
|
||||
if_gain6: '20'
|
||||
if_gain7: '20'
|
||||
if_gain8: '20'
|
||||
if_gain9: '20'
|
||||
iq_balance_mode0: '0'
|
||||
iq_balance_mode1: '0'
|
||||
iq_balance_mode10: '0'
|
||||
iq_balance_mode11: '0'
|
||||
iq_balance_mode12: '0'
|
||||
iq_balance_mode13: '0'
|
||||
iq_balance_mode14: '0'
|
||||
iq_balance_mode15: '0'
|
||||
iq_balance_mode16: '0'
|
||||
iq_balance_mode17: '0'
|
||||
iq_balance_mode18: '0'
|
||||
iq_balance_mode19: '0'
|
||||
iq_balance_mode2: '0'
|
||||
iq_balance_mode20: '0'
|
||||
iq_balance_mode21: '0'
|
||||
iq_balance_mode22: '0'
|
||||
iq_balance_mode23: '0'
|
||||
iq_balance_mode24: '0'
|
||||
iq_balance_mode25: '0'
|
||||
iq_balance_mode26: '0'
|
||||
iq_balance_mode27: '0'
|
||||
iq_balance_mode28: '0'
|
||||
iq_balance_mode29: '0'
|
||||
iq_balance_mode3: '0'
|
||||
iq_balance_mode30: '0'
|
||||
iq_balance_mode31: '0'
|
||||
iq_balance_mode4: '0'
|
||||
iq_balance_mode5: '0'
|
||||
iq_balance_mode6: '0'
|
||||
iq_balance_mode7: '0'
|
||||
iq_balance_mode8: '0'
|
||||
iq_balance_mode9: '0'
|
||||
maxoutbuf: '0'
|
||||
minoutbuf: '0'
|
||||
nchan: '1'
|
||||
num_mboards: '1'
|
||||
sample_rate: sample_rate
|
||||
sync: sync
|
||||
time_source0: ''
|
||||
time_source1: ''
|
||||
time_source2: ''
|
||||
time_source3: ''
|
||||
time_source4: ''
|
||||
time_source5: ''
|
||||
time_source6: ''
|
||||
time_source7: ''
|
||||
type: fc32
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [219, 216]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
connections:
|
||||
- [osmosdr_source_0, '0', blocks_tcp_server_sink_0, '0']
|
||||
|
||||
metadata:
|
||||
file_format: 1
|
||||
@@ -0,0 +1,140 @@
|
||||
from optparse import OptionParser
|
||||
|
||||
import Initializer
|
||||
from InputHandlerThread import InputHandlerThread
|
||||
|
||||
Initializer.init_path()
|
||||
|
||||
|
||||
from gnuradio import blocks
|
||||
from gnuradio import gr
|
||||
import osmosdr
|
||||
|
||||
|
||||
class top_block(gr.top_block):
|
||||
|
||||
def __init__(self, sample_rate, frequency, freq_correction, rf_gain, if_gain, bb_gain, bandwidth, port):
|
||||
gr.top_block.__init__(self, "Top Block")
|
||||
|
||||
self.sample_rate = sample_rate
|
||||
self.rf_gain = rf_gain
|
||||
self.port = port
|
||||
self.if_gain = if_gain
|
||||
self.frequency = frequency
|
||||
self.freq_correction = freq_correction
|
||||
self.bb_gain = bb_gain
|
||||
self.bandwidth = bandwidth
|
||||
|
||||
self.osmosdr_source_0 = osmosdr.source(
|
||||
args="numchan=" + str(1) + " " + 'uhd'
|
||||
)
|
||||
self.osmosdr_source_0.set_time_unknown_pps(osmosdr.time_spec_t())
|
||||
self.osmosdr_source_0.set_sample_rate(sample_rate)
|
||||
self.osmosdr_source_0.set_center_freq(frequency, 0)
|
||||
self.osmosdr_source_0.set_freq_corr(freq_correction, 0)
|
||||
self.osmosdr_source_0.set_gain(rf_gain, 0)
|
||||
self.osmosdr_source_0.set_if_gain(if_gain, 0)
|
||||
self.osmosdr_source_0.set_bb_gain(bb_gain, 0)
|
||||
self.osmosdr_source_0.set_antenna('', 0)
|
||||
self.osmosdr_source_0.set_bandwidth(bandwidth, 0)
|
||||
self.blocks_tcp_server_sink_0 = blocks.tcp_server_sink(gr.sizeof_gr_complex*1, '127.0.0.1', port, False)
|
||||
|
||||
|
||||
|
||||
self.connect((self.osmosdr_source_0, 0), (self.blocks_tcp_server_sink_0, 0))
|
||||
|
||||
def get_sample_rate(self):
|
||||
return self.sample_rate
|
||||
|
||||
def set_sample_rate(self, sample_rate):
|
||||
self.sample_rate = sample_rate
|
||||
self.osmosdr_source_0.set_sample_rate(self.sample_rate)
|
||||
|
||||
def get_rf_gain(self):
|
||||
return self.rf_gain
|
||||
|
||||
def set_rf_gain(self, rf_gain):
|
||||
self.rf_gain = rf_gain
|
||||
self.osmosdr_source_0.set_gain(self.rf_gain, 0)
|
||||
|
||||
def get_port(self):
|
||||
return self.port
|
||||
|
||||
def set_port(self, port):
|
||||
self.port = port
|
||||
|
||||
def get_if_gain(self):
|
||||
return self.if_gain
|
||||
|
||||
def set_if_gain(self, if_gain):
|
||||
self.if_gain = if_gain
|
||||
self.osmosdr_source_0.set_if_gain(self.if_gain, 0)
|
||||
|
||||
def get_frequency(self):
|
||||
return self.frequency
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
self.frequency = frequency
|
||||
self.osmosdr_source_0.set_center_freq(self.frequency, 0)
|
||||
|
||||
def get_freq_correction(self):
|
||||
return self.freq_correction
|
||||
|
||||
def set_freq_correction(self, freq_correction):
|
||||
self.freq_correction = freq_correction
|
||||
self.osmosdr_source_0.set_freq_corr(self.freq_correction, 0)
|
||||
|
||||
def get_direct_sampling_mode(self):
|
||||
return self.direct_sampling_mode
|
||||
|
||||
def set_direct_sampling_mode(self, direct_sampling_mode):
|
||||
self.direct_sampling_mode = direct_sampling_mode
|
||||
|
||||
def get_channel_index(self):
|
||||
return self.channel_index
|
||||
|
||||
def set_channel_index(self, channel_index):
|
||||
self.channel_index = channel_index
|
||||
|
||||
def get_bb_gain(self):
|
||||
return self.bb_gain
|
||||
|
||||
def set_bb_gain(self, bb_gain):
|
||||
self.bb_gain = bb_gain
|
||||
self.osmosdr_source_0.set_bb_gain(self.bb_gain, 0)
|
||||
|
||||
def get_bandwidth(self):
|
||||
return self.bandwidth
|
||||
|
||||
def set_bandwidth(self, bandwidth):
|
||||
self.bandwidth = bandwidth
|
||||
self.osmosdr_source_0.set_bandwidth(self.bandwidth, 0)
|
||||
|
||||
def get_antenna_index(self):
|
||||
return self.antenna_index
|
||||
|
||||
def set_antenna_index(self, antenna_index):
|
||||
self.antenna_index = antenna_index
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = OptionParser(usage='%prog: [options]')
|
||||
parser.add_option('-s', '--sample-rate', dest='sample_rate', default=100000)
|
||||
parser.add_option('-f', '--frequency', dest='frequency', default=433000)
|
||||
parser.add_option('-g', '--gain', dest='rf_gain', default=30)
|
||||
parser.add_option('-i', '--if-gain', dest='if_gain', default=30)
|
||||
parser.add_option('-b', '--bb-gain', dest='bb_gain', default=30)
|
||||
parser.add_option('-w', '--bandwidth', dest='bandwidth', default=250000)
|
||||
parser.add_option('-c', '--freq-correction', dest='freq_correction', default=0)
|
||||
parser.add_option('-d', '--direct-sampling', dest='direct_sampling', default=0)
|
||||
parser.add_option('-n', '--channel-index', dest='channel_index', default=0)
|
||||
parser.add_option('-a', '--antenna-index', dest='antenna_index', default=0)
|
||||
parser.add_option('-p', '--port', dest='port', default=1234)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
tb = top_block(int(options.sample_rate), int(options.frequency), int(options.freq_correction), int(options.rf_gain), int(options.if_gain), int(options.bb_gain), int(options.bandwidth), int(options.port))
|
||||
iht = InputHandlerThread(tb)
|
||||
iht.start()
|
||||
tb.start()
|
||||
tb.wait()
|
||||
@@ -0,0 +1,454 @@
|
||||
options:
|
||||
parameters:
|
||||
author: ''
|
||||
category: Custom
|
||||
cmake_opt: ''
|
||||
comment: ''
|
||||
copyright: ''
|
||||
description: ''
|
||||
gen_cmake: 'On'
|
||||
gen_linking: dynamic
|
||||
generate_options: no_gui
|
||||
hier_block_src_path: '.:'
|
||||
id: top_block
|
||||
max_nouts: '0'
|
||||
output_language: python
|
||||
placement: (0,0)
|
||||
qt_qss_theme: ''
|
||||
realtime_scheduling: ''
|
||||
run: 'True'
|
||||
run_command: '{python} -u {filename}'
|
||||
run_options: run
|
||||
sizing_mode: fixed
|
||||
thread_safe_setters: ''
|
||||
title: ''
|
||||
window_size: ''
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [8, 8]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
blocks:
|
||||
- name: antenna_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [705, 7]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: bandwidth
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '250000'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [217, 9]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: bb_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [418, 7]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: channel_index
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [557, 93]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: direct_sampling_mode
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [705, 90]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: freq_correction
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '0'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [561, 7]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: frequency
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 433e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [317, 95]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: if_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [340, 8]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: port
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '1234'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [986, 46]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: rf_gain
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: '10'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [221, 98]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: sample_rate
|
||||
id: variable
|
||||
parameters:
|
||||
comment: ''
|
||||
value: 3e6
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [421, 92]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
- name: blocks_udp_source_0
|
||||
id: blocks_udp_source
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
comment: ''
|
||||
eof: 'False'
|
||||
ipaddr: 127.0.0.1
|
||||
maxoutbuf: '0'
|
||||
minoutbuf: '0'
|
||||
port: port
|
||||
psize: '65536'
|
||||
type: complex
|
||||
vlen: '1'
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [349, 269]
|
||||
rotation: 0
|
||||
state: true
|
||||
- name: osmosdr_sink_0
|
||||
id: osmosdr_sink
|
||||
parameters:
|
||||
affinity: ''
|
||||
alias: ''
|
||||
ant0: ''
|
||||
ant1: ''
|
||||
ant10: ''
|
||||
ant11: ''
|
||||
ant12: ''
|
||||
ant13: ''
|
||||
ant14: ''
|
||||
ant15: ''
|
||||
ant16: ''
|
||||
ant17: ''
|
||||
ant18: ''
|
||||
ant19: ''
|
||||
ant2: ''
|
||||
ant20: ''
|
||||
ant21: ''
|
||||
ant22: ''
|
||||
ant23: ''
|
||||
ant24: ''
|
||||
ant25: ''
|
||||
ant26: ''
|
||||
ant27: ''
|
||||
ant28: ''
|
||||
ant29: ''
|
||||
ant3: ''
|
||||
ant30: ''
|
||||
ant31: ''
|
||||
ant4: ''
|
||||
ant5: ''
|
||||
ant6: ''
|
||||
ant7: ''
|
||||
ant8: ''
|
||||
ant9: ''
|
||||
args: uhd
|
||||
bb_gain0: bb_gain
|
||||
bb_gain1: '20'
|
||||
bb_gain10: '20'
|
||||
bb_gain11: '20'
|
||||
bb_gain12: '20'
|
||||
bb_gain13: '20'
|
||||
bb_gain14: '20'
|
||||
bb_gain15: '20'
|
||||
bb_gain16: '20'
|
||||
bb_gain17: '20'
|
||||
bb_gain18: '20'
|
||||
bb_gain19: '20'
|
||||
bb_gain2: '20'
|
||||
bb_gain20: '20'
|
||||
bb_gain21: '20'
|
||||
bb_gain22: '20'
|
||||
bb_gain23: '20'
|
||||
bb_gain24: '20'
|
||||
bb_gain25: '20'
|
||||
bb_gain26: '20'
|
||||
bb_gain27: '20'
|
||||
bb_gain28: '20'
|
||||
bb_gain29: '20'
|
||||
bb_gain3: '20'
|
||||
bb_gain30: '20'
|
||||
bb_gain31: '20'
|
||||
bb_gain4: '20'
|
||||
bb_gain5: '20'
|
||||
bb_gain6: '20'
|
||||
bb_gain7: '20'
|
||||
bb_gain8: '20'
|
||||
bb_gain9: '20'
|
||||
bw0: bandwidth
|
||||
bw1: '0'
|
||||
bw10: '0'
|
||||
bw11: '0'
|
||||
bw12: '0'
|
||||
bw13: '0'
|
||||
bw14: '0'
|
||||
bw15: '0'
|
||||
bw16: '0'
|
||||
bw17: '0'
|
||||
bw18: '0'
|
||||
bw19: '0'
|
||||
bw2: '0'
|
||||
bw20: '0'
|
||||
bw21: '0'
|
||||
bw22: '0'
|
||||
bw23: '0'
|
||||
bw24: '0'
|
||||
bw25: '0'
|
||||
bw26: '0'
|
||||
bw27: '0'
|
||||
bw28: '0'
|
||||
bw29: '0'
|
||||
bw3: '0'
|
||||
bw30: '0'
|
||||
bw31: '0'
|
||||
bw4: '0'
|
||||
bw5: '0'
|
||||
bw6: '0'
|
||||
bw7: '0'
|
||||
bw8: '0'
|
||||
bw9: '0'
|
||||
clock_source0: ''
|
||||
clock_source1: ''
|
||||
clock_source2: ''
|
||||
clock_source3: ''
|
||||
clock_source4: ''
|
||||
clock_source5: ''
|
||||
clock_source6: ''
|
||||
clock_source7: ''
|
||||
comment: ''
|
||||
corr0: freq_correction
|
||||
corr1: '0'
|
||||
corr10: '0'
|
||||
corr11: '0'
|
||||
corr12: '0'
|
||||
corr13: '0'
|
||||
corr14: '0'
|
||||
corr15: '0'
|
||||
corr16: '0'
|
||||
corr17: '0'
|
||||
corr18: '0'
|
||||
corr19: '0'
|
||||
corr2: '0'
|
||||
corr20: '0'
|
||||
corr21: '0'
|
||||
corr22: '0'
|
||||
corr23: '0'
|
||||
corr24: '0'
|
||||
corr25: '0'
|
||||
corr26: '0'
|
||||
corr27: '0'
|
||||
corr28: '0'
|
||||
corr29: '0'
|
||||
corr3: '0'
|
||||
corr30: '0'
|
||||
corr31: '0'
|
||||
corr4: '0'
|
||||
corr5: '0'
|
||||
corr6: '0'
|
||||
corr7: '0'
|
||||
corr8: '0'
|
||||
corr9: '0'
|
||||
freq0: frequency
|
||||
freq1: 100e6
|
||||
freq10: 100e6
|
||||
freq11: 100e6
|
||||
freq12: 100e6
|
||||
freq13: 100e6
|
||||
freq14: 100e6
|
||||
freq15: 100e6
|
||||
freq16: 100e6
|
||||
freq17: 100e6
|
||||
freq18: 100e6
|
||||
freq19: 100e6
|
||||
freq2: 100e6
|
||||
freq20: 100e6
|
||||
freq21: 100e6
|
||||
freq22: 100e6
|
||||
freq23: 100e6
|
||||
freq24: 100e6
|
||||
freq25: 100e6
|
||||
freq26: 100e6
|
||||
freq27: 100e6
|
||||
freq28: 100e6
|
||||
freq29: 100e6
|
||||
freq3: 100e6
|
||||
freq30: 100e6
|
||||
freq31: 100e6
|
||||
freq4: 100e6
|
||||
freq5: 100e6
|
||||
freq6: 100e6
|
||||
freq7: 100e6
|
||||
freq8: 100e6
|
||||
freq9: 100e6
|
||||
gain0: rf_gain
|
||||
gain1: '10'
|
||||
gain10: '10'
|
||||
gain11: '10'
|
||||
gain12: '10'
|
||||
gain13: '10'
|
||||
gain14: '10'
|
||||
gain15: '10'
|
||||
gain16: '10'
|
||||
gain17: '10'
|
||||
gain18: '10'
|
||||
gain19: '10'
|
||||
gain2: '10'
|
||||
gain20: '10'
|
||||
gain21: '10'
|
||||
gain22: '10'
|
||||
gain23: '10'
|
||||
gain24: '10'
|
||||
gain25: '10'
|
||||
gain26: '10'
|
||||
gain27: '10'
|
||||
gain28: '10'
|
||||
gain29: '10'
|
||||
gain3: '10'
|
||||
gain30: '10'
|
||||
gain31: '10'
|
||||
gain4: '10'
|
||||
gain5: '10'
|
||||
gain6: '10'
|
||||
gain7: '10'
|
||||
gain8: '10'
|
||||
gain9: '10'
|
||||
if_gain0: if_gain
|
||||
if_gain1: '20'
|
||||
if_gain10: '20'
|
||||
if_gain11: '20'
|
||||
if_gain12: '20'
|
||||
if_gain13: '20'
|
||||
if_gain14: '20'
|
||||
if_gain15: '20'
|
||||
if_gain16: '20'
|
||||
if_gain17: '20'
|
||||
if_gain18: '20'
|
||||
if_gain19: '20'
|
||||
if_gain2: '20'
|
||||
if_gain20: '20'
|
||||
if_gain21: '20'
|
||||
if_gain22: '20'
|
||||
if_gain23: '20'
|
||||
if_gain24: '20'
|
||||
if_gain25: '20'
|
||||
if_gain26: '20'
|
||||
if_gain27: '20'
|
||||
if_gain28: '20'
|
||||
if_gain29: '20'
|
||||
if_gain3: '20'
|
||||
if_gain30: '20'
|
||||
if_gain31: '20'
|
||||
if_gain4: '20'
|
||||
if_gain5: '20'
|
||||
if_gain6: '20'
|
||||
if_gain7: '20'
|
||||
if_gain8: '20'
|
||||
if_gain9: '20'
|
||||
maxoutbuf: '0'
|
||||
minoutbuf: '0'
|
||||
nchan: '1'
|
||||
num_mboards: '1'
|
||||
sample_rate: sample_rate
|
||||
sync: sync
|
||||
time_source0: ''
|
||||
time_source1: ''
|
||||
time_source2: ''
|
||||
time_source3: ''
|
||||
time_source4: ''
|
||||
time_source5: ''
|
||||
time_source6: ''
|
||||
time_source7: ''
|
||||
type: fc32
|
||||
states:
|
||||
bus_sink: false
|
||||
bus_source: false
|
||||
bus_structure: null
|
||||
coordinate: [746, 195]
|
||||
rotation: 0
|
||||
state: enabled
|
||||
|
||||
connections:
|
||||
- [blocks_udp_source_0, '0', osmosdr_sink_0, '0']
|
||||
|
||||
metadata:
|
||||
file_format: 1
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user