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

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