This commit is contained in:
RocketGod
2022-09-22 13:46:47 -07:00
parent f65104c2ab
commit e7667c1d93
565 changed files with 165005 additions and 0 deletions

View 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

View File

@ -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()

View 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

View 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

View 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

View 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()

View File

@ -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)

View 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

View File

@ -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")

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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)

View 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()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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 &lt;<a href='mailto:joahnnes.pohl90@gmail.com'>johannes.pohl90@gmail.com</a>&gt;</li>" \
"<li>Andreas Noack &lt;<a href='mailto:andreas.noack@hochschule-stralsund.de'>andreas.noack@hochschule-stralsund.de</a>&gt;</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")

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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_()

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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())

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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_()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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))

View 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)

View File

@ -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()

View File

@ -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

View File

@ -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))

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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_()

View File

@ -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()

View File

@ -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

View File

@ -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())

View File

@ -0,0 +1,2 @@
*.so
*.pyd

View 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"])

View File

@ -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

View 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

View 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())

View File

@ -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

View File

@ -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

View 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

View 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)

View 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)

View 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")

View 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

View 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)

View 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)),
}

View File

@ -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()

View File

@ -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.")

View File

@ -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.")

View File

@ -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))

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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