Add urh
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,710 @@
|
||||
import locale
|
||||
|
||||
import numpy
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QFontMetrics
|
||||
from PyQt5.QtWidgets import QInputDialog, QWidget, QUndoStack, QApplication
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.CompareFrameController import CompareFrameController
|
||||
from urh.controller.dialogs.ContinuousSendDialog import ContinuousSendDialog
|
||||
from urh.controller.dialogs.FuzzingDialog import FuzzingDialog
|
||||
from urh.controller.dialogs.ModulatorDialog import ModulatorDialog
|
||||
from urh.controller.dialogs.SendDialog import SendDialog
|
||||
from urh.models.GeneratorListModel import GeneratorListModel
|
||||
from urh.models.GeneratorTableModel import GeneratorTableModel
|
||||
from urh.models.GeneratorTreeModel import GeneratorTreeModel
|
||||
from urh.plugins.NetworkSDRInterface.NetworkSDRInterfacePlugin import NetworkSDRInterfacePlugin
|
||||
from urh.plugins.PluginManager import PluginManager
|
||||
from urh.plugins.RfCat.RfCatPlugin import RfCatPlugin
|
||||
from urh.signalprocessing.IQArray import IQArray
|
||||
from urh.signalprocessing.Message import Message
|
||||
from urh.signalprocessing.MessageType import MessageType
|
||||
from urh.signalprocessing.Modulator import Modulator
|
||||
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
|
||||
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
|
||||
from urh.ui.actions.Fuzz import Fuzz
|
||||
from urh.ui.ui_generator import Ui_GeneratorTab
|
||||
from urh.util import FileOperator, util
|
||||
from urh.util.Errors import Errors
|
||||
from urh.util.Formatter import Formatter
|
||||
from urh.util.Logger import logger
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class GeneratorTabController(QWidget):
|
||||
def __init__(self, compare_frame_controller: CompareFrameController, project_manager: ProjectManager, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_GeneratorTab()
|
||||
self.ui.setupUi(self)
|
||||
util.set_splitter_stylesheet(self.ui.splitter)
|
||||
|
||||
self.project_manager = project_manager
|
||||
|
||||
self.ui.treeProtocols.setHeaderHidden(True)
|
||||
self.tree_model = GeneratorTreeModel(compare_frame_controller)
|
||||
self.tree_model.set_root_item(compare_frame_controller.proto_tree_model.rootItem)
|
||||
self.tree_model.controller = self
|
||||
self.ui.treeProtocols.setModel(self.tree_model)
|
||||
|
||||
self.table_model = GeneratorTableModel(compare_frame_controller.proto_tree_model.rootItem,
|
||||
compare_frame_controller.decodings)
|
||||
self.table_model.controller = self
|
||||
self.ui.tableMessages.setModel(self.table_model)
|
||||
|
||||
self.label_list_model = GeneratorListModel(None)
|
||||
self.ui.listViewProtoLabels.setModel(self.label_list_model)
|
||||
|
||||
self.network_sdr_button_orig_tooltip = self.ui.btnNetworkSDRSend.toolTip()
|
||||
self.set_network_sdr_send_button_visibility()
|
||||
self.set_rfcat_button_visibility()
|
||||
self.network_sdr_plugin = NetworkSDRInterfacePlugin()
|
||||
self.rfcat_plugin = RfCatPlugin()
|
||||
self.init_rfcat_plugin()
|
||||
|
||||
self.modulation_msg_indices = []
|
||||
|
||||
self.refresh_modulators()
|
||||
self.on_selected_modulation_changed()
|
||||
self.set_fuzzing_ui_status()
|
||||
self.ui.prBarGeneration.hide()
|
||||
self.create_connects(compare_frame_controller)
|
||||
|
||||
self.set_modulation_profile_status()
|
||||
|
||||
def __get_modulator_of_message(self, message: Message) -> Modulator:
|
||||
if message.modulator_index > len(self.modulators) - 1:
|
||||
message.modulator_index = 0
|
||||
return self.modulators[message.modulator_index]
|
||||
|
||||
@property
|
||||
def selected_message_index(self) -> int:
|
||||
min_row, _, _, _ = self.ui.tableMessages.selection_range()
|
||||
return min_row #
|
||||
|
||||
@property
|
||||
def selected_message(self) -> Message:
|
||||
selected_msg_index = self.selected_message_index
|
||||
if selected_msg_index == -1 or selected_msg_index >= len(self.table_model.protocol.messages):
|
||||
return None
|
||||
|
||||
return self.table_model.protocol.messages[selected_msg_index]
|
||||
|
||||
@property
|
||||
def active_groups(self):
|
||||
return self.tree_model.groups
|
||||
|
||||
@property
|
||||
def modulators(self):
|
||||
return self.project_manager.modulators
|
||||
|
||||
@property
|
||||
def total_modulated_samples(self) -> int:
|
||||
return sum(int(len(msg.encoded_bits) * self.__get_modulator_of_message(msg).samples_per_symbol + msg.pause)
|
||||
for msg in self.table_model.protocol.messages)
|
||||
|
||||
@modulators.setter
|
||||
def modulators(self, value):
|
||||
assert type(value) == list
|
||||
self.project_manager.modulators = value
|
||||
|
||||
def create_connects(self, compare_frame_controller):
|
||||
compare_frame_controller.proto_tree_model.modelReset.connect(self.refresh_tree)
|
||||
compare_frame_controller.participant_changed.connect(self.table_model.refresh_vertical_header)
|
||||
self.ui.btnEditModulation.clicked.connect(self.show_modulation_dialog)
|
||||
self.ui.cBoxModulations.currentIndexChanged.connect(self.on_selected_modulation_changed)
|
||||
self.ui.tableMessages.selectionModel().selectionChanged.connect(self.on_table_selection_changed)
|
||||
self.ui.tableMessages.encodings_updated.connect(self.on_table_selection_changed)
|
||||
self.table_model.undo_stack.indexChanged.connect(self.on_undo_stack_index_changed)
|
||||
self.table_model.protocol.qt_signals.line_duplicated.connect(self.refresh_pause_list)
|
||||
self.table_model.protocol.qt_signals.fuzzing_started.connect(self.on_fuzzing_started)
|
||||
self.table_model.protocol.qt_signals.current_fuzzing_message_changed.connect(
|
||||
self.on_current_fuzzing_message_changed)
|
||||
self.table_model.protocol.qt_signals.fuzzing_finished.connect(self.on_fuzzing_finished)
|
||||
self.table_model.first_protocol_added.connect(self.on_first_protocol_added)
|
||||
self.label_list_model.protolabel_fuzzing_status_changed.connect(self.set_fuzzing_ui_status)
|
||||
self.ui.cbViewType.currentIndexChanged.connect(self.on_view_type_changed)
|
||||
self.ui.btnSend.clicked.connect(self.on_btn_send_clicked)
|
||||
self.ui.btnSave.clicked.connect(self.on_btn_save_clicked)
|
||||
self.ui.btnOpen.clicked.connect(self.on_btn_open_clicked)
|
||||
|
||||
self.project_manager.project_updated.connect(self.on_project_updated)
|
||||
|
||||
self.table_model.vertical_header_color_status_changed.connect(
|
||||
self.ui.tableMessages.on_vertical_header_color_status_changed)
|
||||
|
||||
self.label_list_model.protolabel_removed.connect(self.handle_proto_label_removed)
|
||||
|
||||
self.ui.lWPauses.item_edit_clicked.connect(self.edit_pause_item)
|
||||
self.ui.lWPauses.edit_all_items_clicked.connect(self.edit_all_pause_items)
|
||||
self.ui.lWPauses.itemSelectionChanged.connect(self.on_lWpauses_selection_changed)
|
||||
self.ui.lWPauses.lost_focus.connect(self.on_lWPauses_lost_focus)
|
||||
self.ui.lWPauses.doubleClicked.connect(self.on_lWPauses_double_clicked)
|
||||
self.ui.btnGenerate.clicked.connect(self.generate_file)
|
||||
self.label_list_model.protolabel_fuzzing_status_changed.connect(self.handle_plabel_fuzzing_state_changed)
|
||||
self.ui.btnFuzz.clicked.connect(self.on_btn_fuzzing_clicked)
|
||||
self.ui.tableMessages.create_label_triggered.connect(self.create_fuzzing_label)
|
||||
self.ui.tableMessages.edit_label_triggered.connect(self.show_fuzzing_dialog)
|
||||
self.ui.listViewProtoLabels.selection_changed.connect(self.handle_label_selection_changed)
|
||||
self.ui.listViewProtoLabels.edit_on_item_triggered.connect(self.show_fuzzing_dialog)
|
||||
|
||||
self.ui.btnNetworkSDRSend.clicked.connect(self.on_btn_network_sdr_clicked)
|
||||
self.ui.btnRfCatSend.clicked.connect(self.on_btn_rfcat_clicked)
|
||||
|
||||
self.network_sdr_plugin.sending_status_changed.connect(self.on_network_sdr_sending_status_changed)
|
||||
self.network_sdr_plugin.sending_stop_requested.connect(self.on_network_sdr_sending_stop_requested)
|
||||
self.network_sdr_plugin.current_send_message_changed.connect(self.on_send_message_changed)
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_tree(self):
|
||||
self.tree_model.beginResetModel()
|
||||
self.tree_model.endResetModel()
|
||||
self.ui.treeProtocols.expandAll()
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_table(self):
|
||||
self.table_model.update()
|
||||
self.ui.tableMessages.resize_columns()
|
||||
is_data_there = self.table_model.display_data is not None and len(self.table_model.display_data) > 0
|
||||
self.ui.btnSend.setEnabled(is_data_there)
|
||||
self.ui.btnGenerate.setEnabled(is_data_there)
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_label_list(self):
|
||||
self.label_list_model.message = self.selected_message
|
||||
self.label_list_model.update()
|
||||
|
||||
@property
|
||||
def generator_undo_stack(self) -> QUndoStack:
|
||||
return self.table_model.undo_stack
|
||||
|
||||
@pyqtSlot()
|
||||
def on_selected_modulation_changed(self):
|
||||
cur_ind = self.ui.cBoxModulations.currentIndex()
|
||||
min_row, max_row, _, _ = self.ui.tableMessages.selection_range()
|
||||
if min_row > -1:
|
||||
# set modulation for selected messages
|
||||
for row in range(min_row, max_row + 1):
|
||||
try:
|
||||
self.table_model.protocol.messages[row].modulator_index = cur_ind
|
||||
except IndexError:
|
||||
continue
|
||||
|
||||
self.show_modulation_info()
|
||||
|
||||
def refresh_modulators(self):
|
||||
current_index = 0
|
||||
if type(self.sender()) == ModulatorDialog:
|
||||
current_index = self.sender().ui.comboBoxCustomModulations.currentIndex()
|
||||
self.ui.cBoxModulations.clear()
|
||||
for modulator in self.modulators:
|
||||
self.ui.cBoxModulations.addItem(modulator.name)
|
||||
|
||||
self.ui.cBoxModulations.setCurrentIndex(current_index)
|
||||
|
||||
def bootstrap_modulator(self, protocol: ProtocolAnalyzer):
|
||||
"""
|
||||
Set initial parameters for default modulator if it was not edited by user previously
|
||||
:return:
|
||||
"""
|
||||
if len(self.modulators) != 1 or len(self.table_model.protocol.messages) == 0:
|
||||
return
|
||||
|
||||
modulator = self.modulators[0]
|
||||
modulator.samples_per_symbol = protocol.messages[0].samples_per_symbol
|
||||
modulator.bits_per_symbol = protocol.messages[0].bits_per_symbol
|
||||
|
||||
if protocol.signal:
|
||||
modulator.sample_rate = protocol.signal.sample_rate
|
||||
modulator.modulation_type = protocol.signal.modulation_type
|
||||
auto_freq = modulator.estimate_carrier_frequency(protocol.signal, protocol)
|
||||
if auto_freq is not None and auto_freq != 0:
|
||||
modulator.carrier_freq_hz = auto_freq
|
||||
|
||||
modulator.parameters = modulator.get_default_parameters()
|
||||
self.show_modulation_info()
|
||||
|
||||
def show_modulation_info(self):
|
||||
cur_ind = self.ui.cBoxModulations.currentIndex()
|
||||
mod = self.modulators[cur_ind]
|
||||
self.ui.lCarrierFreqValue.setText(mod.carrier_frequency_str)
|
||||
self.ui.lCarrierPhaseValue.setText(mod.carrier_phase_str)
|
||||
self.ui.lBitLenValue.setText(mod.samples_per_symbol_str)
|
||||
self.ui.lSampleRateValue.setText(mod.sample_rate_str)
|
||||
mod_type = mod.modulation_type
|
||||
self.ui.lModTypeValue.setText(mod_type)
|
||||
|
||||
self.ui.lParamCaption.setText(mod.parameter_type_str)
|
||||
self.ui.labelParameterValues.setText(mod.parameters_string)
|
||||
self.ui.labelBitsPerSymbol.setText(str(mod.bits_per_symbol))
|
||||
|
||||
def prepare_modulation_dialog(self) -> (ModulatorDialog, Message):
|
||||
preselected_index = self.ui.cBoxModulations.currentIndex()
|
||||
|
||||
min_row, max_row, start, end = self.ui.tableMessages.selection_range()
|
||||
if min_row > -1:
|
||||
try:
|
||||
selected_message = self.table_model.protocol.messages[min_row]
|
||||
preselected_index = selected_message.modulator_index
|
||||
except IndexError:
|
||||
selected_message = Message([1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0], 0, [], MessageType("empty"))
|
||||
else:
|
||||
selected_message = Message([1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0], 0, [], MessageType("empty"))
|
||||
if len(self.table_model.protocol.messages) > 0:
|
||||
selected_message.samples_per_symbol = self.table_model.protocol.messages[0].samples_per_symbol
|
||||
|
||||
for m in self.modulators:
|
||||
m.default_sample_rate = self.project_manager.device_conf["sample_rate"]
|
||||
|
||||
modulator_dialog = ModulatorDialog(self.modulators, tree_model=self.tree_model, parent=self.parent())
|
||||
modulator_dialog.ui.comboBoxCustomModulations.setCurrentIndex(preselected_index)
|
||||
|
||||
modulator_dialog.finished.connect(self.refresh_modulators)
|
||||
modulator_dialog.finished.connect(self.refresh_pause_list)
|
||||
|
||||
return modulator_dialog, selected_message
|
||||
|
||||
def set_modulation_profile_status(self):
|
||||
visible = settings.read("multiple_modulations", False, bool)
|
||||
self.ui.cBoxModulations.setVisible(visible)
|
||||
|
||||
def init_rfcat_plugin(self):
|
||||
self.set_rfcat_button_visibility()
|
||||
self.rfcat_plugin = RfCatPlugin()
|
||||
self.rfcat_plugin.current_send_message_changed.connect(self.on_send_message_changed)
|
||||
self.ui.btnRfCatSend.setEnabled(self.rfcat_plugin.rfcat_is_found)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_undo_stack_index_changed(self):
|
||||
self.refresh_table()
|
||||
self.refresh_pause_list()
|
||||
self.refresh_label_list()
|
||||
self.refresh_estimated_time()
|
||||
self.set_fuzzing_ui_status()
|
||||
|
||||
@pyqtSlot()
|
||||
def show_modulation_dialog(self):
|
||||
modulator_dialog, message = self.prepare_modulation_dialog()
|
||||
modulator_dialog.showMaximized()
|
||||
|
||||
modulator_dialog.initialize(message.encoded_bits_str[0:16])
|
||||
self.project_manager.modulation_was_edited = True
|
||||
|
||||
@pyqtSlot()
|
||||
def on_table_selection_changed(self):
|
||||
min_row, max_row, start, end = self.ui.tableMessages.selection_range()
|
||||
|
||||
if min_row == -1:
|
||||
self.ui.lEncodingValue.setText("-") #
|
||||
self.ui.lEncodingValue.setToolTip("")
|
||||
self.label_list_model.message = None
|
||||
return
|
||||
|
||||
container = self.table_model.protocol
|
||||
message = container.messages[min_row]
|
||||
self.label_list_model.message = message
|
||||
decoder_name = message.decoder.name
|
||||
metrics = QFontMetrics(self.ui.lEncodingValue.font())
|
||||
elidedName = metrics.elidedText(decoder_name, Qt.ElideRight, self.ui.lEncodingValue.width())
|
||||
self.ui.lEncodingValue.setText(elidedName)
|
||||
self.ui.lEncodingValue.setToolTip(decoder_name)
|
||||
self.ui.cBoxModulations.blockSignals(True)
|
||||
self.ui.cBoxModulations.setCurrentIndex(message.modulator_index)
|
||||
self.show_modulation_info()
|
||||
self.ui.cBoxModulations.blockSignals(False)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def edit_pause_item(self, index: int):
|
||||
message = self.table_model.protocol.messages[index]
|
||||
cur_len = message.pause
|
||||
new_len, ok = QInputDialog.getInt(self, self.tr("Enter new Pause Length"),
|
||||
self.tr("Pause Length:"), cur_len, 0)
|
||||
if ok:
|
||||
message.pause = new_len
|
||||
self.refresh_pause_list()
|
||||
|
||||
@pyqtSlot()
|
||||
def edit_all_pause_items(self):
|
||||
message = self.table_model.protocol.messages[0]
|
||||
cur_len = message.pause
|
||||
new_len, ok = QInputDialog.getInt(self, self.tr("Enter new Pause Length"),
|
||||
self.tr("Pause Length:"), cur_len, 0)
|
||||
if ok:
|
||||
for message in self.table_model.protocol.messages:
|
||||
message.pause = new_len
|
||||
|
||||
self.refresh_pause_list()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_lWPauses_double_clicked(self):
|
||||
sel_indexes = [index.row() for index in self.ui.lWPauses.selectedIndexes()]
|
||||
if len(sel_indexes) > 0:
|
||||
self.edit_pause_item(sel_indexes[0])
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_pause_list(self):
|
||||
self.ui.lWPauses.clear()
|
||||
|
||||
fmt_str = "Pause ({1:d}-{2:d}) <{0:d} samples ({3})>"
|
||||
for i, pause in enumerate(self.table_model.protocol.pauses):
|
||||
sr = self.__get_modulator_of_message(self.table_model.protocol.messages[i]).sample_rate
|
||||
item = fmt_str.format(pause, i + 1, i + 2, Formatter.science_time(pause / sr))
|
||||
self.ui.lWPauses.addItem(item)
|
||||
|
||||
self.refresh_estimated_time()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_lWpauses_selection_changed(self):
|
||||
rows = [index.row() for index in self.ui.lWPauses.selectedIndexes()]
|
||||
if len(rows) == 0:
|
||||
return
|
||||
self.ui.tableMessages.show_pause_active = True
|
||||
self.ui.tableMessages.pause_row = rows[0]
|
||||
self.ui.tableMessages.viewport().update()
|
||||
self.ui.tableMessages.scrollTo(self.table_model.index(rows[0], 0))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_lWPauses_lost_focus(self):
|
||||
self.ui.tableMessages.show_pause_active = False
|
||||
self.ui.tableMessages.viewport().update()
|
||||
|
||||
@pyqtSlot()
|
||||
def generate_file(self):
|
||||
try:
|
||||
total_samples = self.total_modulated_samples
|
||||
buffer = self.prepare_modulation_buffer(total_samples, show_error=False)
|
||||
if buffer is None:
|
||||
Errors.generic_error(self.tr("File too big"), self.tr("This file would get too big to save."))
|
||||
self.unsetCursor()
|
||||
return
|
||||
modulated_samples = self.modulate_data(buffer)
|
||||
try:
|
||||
sample_rate = self.modulators[0].sample_rate
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
sample_rate = 1e6
|
||||
FileOperator.ask_signal_file_name_and_save("generated", modulated_samples, sample_rate=sample_rate, parent=self)
|
||||
except Exception as e:
|
||||
Errors.exception(e)
|
||||
self.unsetCursor()
|
||||
|
||||
def prepare_modulation_buffer(self, total_samples: int, show_error=True) -> IQArray:
|
||||
dtype = Modulator.get_dtype()
|
||||
n = 2 if dtype == np.int8 else 4 if dtype == np.int16 else 8
|
||||
|
||||
memory_size_for_buffer = total_samples * n
|
||||
logger.debug("Allocating {0:.2f}MB for modulated samples".format(memory_size_for_buffer / (1024 ** 2)))
|
||||
try:
|
||||
# allocate it three times as we need the same amount for the sending process
|
||||
IQArray(None, dtype=dtype, n=3*total_samples)
|
||||
except MemoryError:
|
||||
# will go into continuous mode in this case
|
||||
if show_error:
|
||||
Errors.not_enough_ram_for_sending_precache(3*memory_size_for_buffer)
|
||||
return None
|
||||
|
||||
return IQArray(None, dtype=dtype, n=total_samples)
|
||||
|
||||
def modulate_data(self, buffer: IQArray) -> IQArray:
|
||||
"""
|
||||
|
||||
:param buffer: Buffer in which the modulated data shall be written, initialized with zeros
|
||||
:return:
|
||||
"""
|
||||
self.ui.prBarGeneration.show()
|
||||
self.ui.prBarGeneration.setValue(0)
|
||||
self.ui.prBarGeneration.setMaximum(self.table_model.row_count)
|
||||
self.modulation_msg_indices.clear()
|
||||
|
||||
pos = 0
|
||||
for i in range(0, self.table_model.row_count):
|
||||
message = self.table_model.protocol.messages[i]
|
||||
modulator = self.__get_modulator_of_message(message)
|
||||
# We do not need to modulate the pause extra, as result is already initialized with zeros
|
||||
modulated = modulator.modulate(start=0, data=message.encoded_bits, pause=0)
|
||||
buffer[pos:pos + len(modulated)] = modulated
|
||||
pos += len(modulated) + message.pause
|
||||
self.modulation_msg_indices.append(pos)
|
||||
self.ui.prBarGeneration.setValue(i + 1)
|
||||
QApplication.instance().processEvents()
|
||||
|
||||
self.ui.prBarGeneration.hide()
|
||||
return buffer
|
||||
|
||||
@pyqtSlot(int)
|
||||
def show_fuzzing_dialog(self, label_index: int):
|
||||
view = self.ui.cbViewType.currentIndex()
|
||||
|
||||
if self.label_list_model.message is not None:
|
||||
msg_index = self.table_model.protocol.messages.index(self.label_list_model.message)
|
||||
fdc = FuzzingDialog(protocol=self.table_model.protocol, label_index=label_index,
|
||||
msg_index=msg_index, proto_view=view, parent=self)
|
||||
fdc.show()
|
||||
fdc.finished.connect(self.on_fuzzing_dialog_finished)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_fuzzing_dialog_finished(self):
|
||||
self.refresh_label_list()
|
||||
self.refresh_table()
|
||||
self.set_fuzzing_ui_status()
|
||||
self.ui.tabWidget.setCurrentIndex(2)
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_plabel_fuzzing_state_changed(self):
|
||||
self.refresh_table()
|
||||
self.label_list_model.update()
|
||||
|
||||
@pyqtSlot(ProtocolLabel)
|
||||
def handle_proto_label_removed(self, plabel: ProtocolLabel):
|
||||
self.refresh_label_list()
|
||||
self.refresh_table()
|
||||
self.set_fuzzing_ui_status()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_fuzzing_clicked(self):
|
||||
fuz_mode = "Successive"
|
||||
if self.ui.rbConcurrent.isChecked():
|
||||
fuz_mode = "Concurrent"
|
||||
elif self.ui.rBExhaustive.isChecked():
|
||||
fuz_mode = "Exhaustive"
|
||||
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
fuzz_action = Fuzz(self.table_model.protocol, fuz_mode)
|
||||
self.table_model.undo_stack.push(fuzz_action)
|
||||
for row in fuzz_action.added_message_indices:
|
||||
self.table_model.update_checksums_for_row(row)
|
||||
self.unsetCursor()
|
||||
self.ui.tableMessages.setFocus()
|
||||
|
||||
@pyqtSlot()
|
||||
def set_fuzzing_ui_status(self):
|
||||
btn_was_enabled = self.ui.btnFuzz.isEnabled()
|
||||
fuzz_active = any(lbl.active_fuzzing for msg in self.table_model.protocol.messages for lbl in msg.message_type)
|
||||
self.ui.btnFuzz.setEnabled(fuzz_active)
|
||||
if self.ui.btnFuzz.isEnabled() and not btn_was_enabled:
|
||||
font = self.ui.btnFuzz.font()
|
||||
font.setBold(True)
|
||||
self.ui.btnFuzz.setFont(font)
|
||||
else:
|
||||
font = self.ui.btnFuzz.font()
|
||||
font.setBold(False)
|
||||
self.ui.btnFuzz.setFont(font)
|
||||
self.ui.btnFuzz.setStyleSheet("")
|
||||
|
||||
has_same_message = self.table_model.protocol.multiple_fuzz_labels_per_message
|
||||
self.ui.rBSuccessive.setEnabled(has_same_message)
|
||||
self.ui.rBExhaustive.setEnabled(has_same_message)
|
||||
self.ui.rbConcurrent.setEnabled(has_same_message)
|
||||
|
||||
def refresh_existing_encodings(self, encodings_from_file):
|
||||
"""
|
||||
Refresh existing encodings for messages, when encoding was changed by user in dialog
|
||||
|
||||
:return:
|
||||
"""
|
||||
update = False
|
||||
|
||||
for msg in self.table_model.protocol.messages:
|
||||
i = next((i for i, d in enumerate(encodings_from_file) if d.name == msg.decoder.name), 0)
|
||||
if msg.decoder != encodings_from_file[i]:
|
||||
update = True
|
||||
msg.decoder = encodings_from_file[i]
|
||||
msg.clear_decoded_bits()
|
||||
msg.clear_encoded_bits()
|
||||
|
||||
if update:
|
||||
self.refresh_table()
|
||||
self.refresh_estimated_time()
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_estimated_time(self):
|
||||
c = self.table_model.protocol
|
||||
if c.num_messages == 0:
|
||||
self.ui.lEstimatedTime.setText("Estimated Time: ")
|
||||
return
|
||||
|
||||
avg_msg_len = numpy.mean([len(msg.encoded_bits) for msg in c.messages])
|
||||
avg_samples_per_symbol = numpy.mean([m.samples_per_symbol for m in self.modulators])
|
||||
avg_sample_rate = numpy.mean([m.sample_rate for m in self.modulators])
|
||||
pause_samples = sum(c.pauses)
|
||||
nsamples = c.num_messages * avg_msg_len * avg_samples_per_symbol + pause_samples
|
||||
|
||||
self.ui.lEstimatedTime.setText(
|
||||
locale.format_string("Estimated Time: %.04f seconds", nsamples / avg_sample_rate))
|
||||
|
||||
@pyqtSlot(int, int, int)
|
||||
def create_fuzzing_label(self, msg_index: int, start: int, end: int):
|
||||
con = self.table_model.protocol
|
||||
start, end = con.convert_range(start, end - 1, self.ui.cbViewType.currentIndex(), 0, False, msg_index)
|
||||
lbl = con.create_fuzzing_label(start, end, msg_index)
|
||||
self.show_fuzzing_dialog(con.protocol_labels.index(lbl))
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_label_selection_changed(self):
|
||||
rows = [index.row() for index in self.ui.listViewProtoLabels.selectedIndexes()]
|
||||
if len(rows) == 0:
|
||||
return
|
||||
|
||||
maxrow = numpy.max(rows)
|
||||
|
||||
try:
|
||||
label = self.table_model.protocol.protocol_labels[maxrow]
|
||||
except IndexError:
|
||||
return
|
||||
if label.show and self.selected_message:
|
||||
start, end = self.selected_message.get_label_range(lbl=label, view=self.table_model.proto_view,
|
||||
decode=False)
|
||||
indx = self.table_model.index(0, int((start + end) / 2))
|
||||
self.ui.tableMessages.scrollTo(indx)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_view_type_changed(self):
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
self.table_model.proto_view = self.ui.cbViewType.currentIndex()
|
||||
self.ui.tableMessages.resize_columns()
|
||||
self.unsetCursor()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_send_clicked(self):
|
||||
try:
|
||||
total_samples = self.total_modulated_samples
|
||||
buffer = self.prepare_modulation_buffer(total_samples)
|
||||
if buffer is not None:
|
||||
modulated_data = self.modulate_data(buffer)
|
||||
else:
|
||||
# Enter continuous mode
|
||||
modulated_data = None
|
||||
|
||||
try:
|
||||
if modulated_data is not None:
|
||||
try:
|
||||
dialog = SendDialog(self.project_manager, modulated_data=modulated_data,
|
||||
modulation_msg_indices=self.modulation_msg_indices, parent=self)
|
||||
except MemoryError:
|
||||
# Not enough memory for device buffer so we need to create a continuous send dialog
|
||||
del modulated_data
|
||||
Errors.not_enough_ram_for_sending_precache(None)
|
||||
dialog = ContinuousSendDialog(self.project_manager,
|
||||
self.table_model.protocol.messages,
|
||||
self.modulators, total_samples, parent=self)
|
||||
else:
|
||||
dialog = ContinuousSendDialog(self.project_manager, self.table_model.protocol.messages,
|
||||
self.modulators, total_samples, parent=self)
|
||||
except OSError as e:
|
||||
logger.exception(e)
|
||||
return
|
||||
if dialog.has_empty_device_list:
|
||||
Errors.no_device()
|
||||
dialog.close()
|
||||
return
|
||||
|
||||
dialog.device_parameters_changed.connect(self.project_manager.set_device_parameters)
|
||||
dialog.show()
|
||||
dialog.graphics_view.show_full_scene(reinitialize=True)
|
||||
except Exception as e:
|
||||
Errors.exception(e)
|
||||
self.unsetCursor()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_save_clicked(self):
|
||||
filename = FileOperator.ask_save_file_name("profile.fuzz.xml", caption="Save fuzzing profile")
|
||||
if filename:
|
||||
self.table_model.protocol.to_xml_file(filename,
|
||||
decoders=self.project_manager.decodings,
|
||||
participants=self.project_manager.participants,
|
||||
modulators=self.modulators)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_open_clicked(self):
|
||||
dialog = FileOperator.get_open_dialog(directory_mode=False, parent=self, name_filter="fuzz")
|
||||
if dialog.exec_():
|
||||
for filename in dialog.selectedFiles():
|
||||
self.load_from_file(filename)
|
||||
|
||||
def load_from_file(self, filename: str):
|
||||
try:
|
||||
self.modulators = ProjectManager.read_modulators_from_file(filename)
|
||||
self.table_model.protocol.from_xml_file(filename)
|
||||
self.refresh_pause_list()
|
||||
self.refresh_estimated_time()
|
||||
self.refresh_modulators()
|
||||
self.show_modulation_info()
|
||||
self.refresh_table()
|
||||
self.set_fuzzing_ui_status()
|
||||
except:
|
||||
logger.error("You done something wrong to the xml fuzzing profile.")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_project_updated(self):
|
||||
self.table_model.refresh_vertical_header()
|
||||
|
||||
def set_network_sdr_send_button_visibility(self):
|
||||
is_plugin_enabled = PluginManager().is_plugin_enabled("NetworkSDRInterface")
|
||||
self.ui.btnNetworkSDRSend.setVisible(is_plugin_enabled)
|
||||
|
||||
def set_rfcat_button_visibility(self):
|
||||
is_plugin_enabled = PluginManager().is_plugin_enabled("RfCat")
|
||||
self.ui.btnRfCatSend.setVisible(is_plugin_enabled)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_network_sdr_clicked(self):
|
||||
if not self.network_sdr_plugin.is_sending:
|
||||
messages = self.table_model.protocol.messages
|
||||
sample_rates = [self.__get_modulator_of_message(msg).sample_rate for msg in messages]
|
||||
self.network_sdr_plugin.start_message_sending_thread(messages, sample_rates)
|
||||
else:
|
||||
self.network_sdr_plugin.stop_sending_thread()
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_network_sdr_sending_status_changed(self, is_sending: bool):
|
||||
self.ui.btnNetworkSDRSend.setChecked(is_sending)
|
||||
self.ui.btnNetworkSDRSend.setEnabled(True)
|
||||
self.ui.btnNetworkSDRSend.setToolTip(
|
||||
"Sending in progress" if is_sending else self.network_sdr_button_orig_tooltip)
|
||||
if not is_sending:
|
||||
self.ui.tableMessages.clearSelection()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_network_sdr_sending_stop_requested(self):
|
||||
self.ui.btnNetworkSDRSend.setToolTip("Stopping sending")
|
||||
self.ui.btnNetworkSDRSend.setEnabled(False)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_send_message_changed(self, message_index: int):
|
||||
self.ui.tableMessages.selectRow(message_index)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_rfcat_clicked(self):
|
||||
if not self.rfcat_plugin.is_sending:
|
||||
messages = self.table_model.protocol.messages
|
||||
sample_rates = [self.__get_modulator_of_message(msg).sample_rate for msg in messages]
|
||||
self.rfcat_plugin.start_message_sending_thread(messages, sample_rates, self.modulators,
|
||||
self.project_manager)
|
||||
else:
|
||||
self.rfcat_plugin.stop_sending_thread()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_fuzzing_started(self, num_values: int):
|
||||
self.ui.stackedWidgetFuzzing.setCurrentWidget(self.ui.pageFuzzingProgressBar)
|
||||
self.ui.progressBarFuzzing.setMaximum(num_values)
|
||||
self.ui.progressBarFuzzing.setValue(0)
|
||||
QApplication.instance().processEvents()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_fuzzing_finished(self):
|
||||
self.ui.stackedWidgetFuzzing.setCurrentWidget(self.ui.pageFuzzingUI)
|
||||
# Calculate Checksums for Fuzzed Messages
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
|
||||
self.unsetCursor()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_current_fuzzing_message_changed(self, current_message: int):
|
||||
self.ui.progressBarFuzzing.setValue(current_message)
|
||||
QApplication.instance().processEvents()
|
||||
|
||||
@pyqtSlot(ProtocolAnalyzer)
|
||||
def on_first_protocol_added(self, protocol: ProtocolAnalyzer):
|
||||
if not self.project_manager.modulation_was_edited:
|
||||
self.bootstrap_modulator(protocol)
|
@ -0,0 +1,967 @@
|
||||
import copy
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QDir, Qt, pyqtSlot, QTimer
|
||||
from PyQt5.QtGui import QIcon, QCloseEvent, QKeySequence
|
||||
from PyQt5.QtWidgets import QMainWindow, QUndoGroup, QActionGroup, QHeaderView, QAction, QMessageBox, QApplication, qApp
|
||||
|
||||
from urh import settings, version
|
||||
from urh.controller.CompareFrameController import CompareFrameController
|
||||
from urh.controller.GeneratorTabController import GeneratorTabController
|
||||
from urh.controller.SignalTabController import SignalTabController
|
||||
from urh.controller.SimulatorTabController import SimulatorTabController
|
||||
from urh.controller.dialogs.CSVImportDialog import CSVImportDialog
|
||||
from urh.controller.dialogs.DecoderDialog import DecoderDialog
|
||||
from urh.controller.dialogs.OptionsDialog import OptionsDialog
|
||||
from urh.controller.dialogs.ProjectDialog import ProjectDialog
|
||||
from urh.controller.dialogs.ProtocolSniffDialog import ProtocolSniffDialog
|
||||
from urh.controller.dialogs.ReceiveDialog import ReceiveDialog
|
||||
from urh.controller.dialogs.SpectrumDialogController import SpectrumDialogController
|
||||
from urh.controller.widgets.SignalFrame import SignalFrame
|
||||
from urh.models.FileFilterProxyModel import FileFilterProxyModel
|
||||
from urh.models.FileIconProvider import FileIconProvider
|
||||
from urh.models.FileSystemModel import FileSystemModel
|
||||
from urh.models.ParticipantLegendListModel import ParticipantLegendListModel
|
||||
from urh.plugins.PluginManager import PluginManager
|
||||
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
|
||||
from urh.signalprocessing.Signal import Signal
|
||||
from urh.ui.ui_main import Ui_MainWindow
|
||||
from urh.util import FileOperator, util
|
||||
from urh.util.Errors import Errors
|
||||
from urh.util.Logger import logger
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class MainController(QMainWindow):
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
self.ui = Ui_MainWindow()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
util.set_splitter_stylesheet(self.ui.splitter)
|
||||
|
||||
OptionsDialog.write_default_options()
|
||||
|
||||
self.project_save_timer = QTimer()
|
||||
self.project_manager = ProjectManager(self)
|
||||
self.plugin_manager = PluginManager()
|
||||
self.signal_tab_controller = SignalTabController(self.project_manager,
|
||||
parent=self.ui.tab_interpretation)
|
||||
self.ui.tab_interpretation.layout().addWidget(self.signal_tab_controller)
|
||||
self.compare_frame_controller = CompareFrameController(parent=self.ui.tab_protocol,
|
||||
plugin_manager=self.plugin_manager,
|
||||
project_manager=self.project_manager)
|
||||
self.compare_frame_controller.ui.splitter.setSizes([1, 1000000])
|
||||
|
||||
self.ui.tab_protocol.layout().addWidget(self.compare_frame_controller)
|
||||
|
||||
self.generator_tab_controller = GeneratorTabController(self.compare_frame_controller,
|
||||
self.project_manager,
|
||||
parent=self.ui.tab_generator)
|
||||
|
||||
self.simulator_tab_controller = SimulatorTabController(parent=self.ui.tab_simulator,
|
||||
compare_frame_controller=self.compare_frame_controller,
|
||||
generator_tab_controller=self.generator_tab_controller,
|
||||
project_manager=self.project_manager)
|
||||
|
||||
self.ui.tab_simulator.layout().addWidget(self.simulator_tab_controller)
|
||||
|
||||
self.undo_group = QUndoGroup()
|
||||
self.undo_group.addStack(self.signal_tab_controller.signal_undo_stack)
|
||||
self.undo_group.addStack(self.compare_frame_controller.protocol_undo_stack)
|
||||
self.undo_group.addStack(self.generator_tab_controller.generator_undo_stack)
|
||||
self.undo_group.setActiveStack(self.signal_tab_controller.signal_undo_stack)
|
||||
|
||||
self.cancel_action = QAction(self.tr("Cancel"), self)
|
||||
self.cancel_action.setShortcut(QKeySequence.Cancel if hasattr(QKeySequence, "Cancel") else "Esc")
|
||||
self.cancel_action.triggered.connect(self.on_cancel_triggered)
|
||||
self.cancel_action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
|
||||
self.cancel_action.setIcon(QIcon.fromTheme("dialog-cancel"))
|
||||
self.addAction(self.cancel_action)
|
||||
|
||||
self.ui.actionAuto_detect_new_signals.setChecked(settings.read("auto_detect_new_signals", True, bool))
|
||||
|
||||
self.participant_legend_model = ParticipantLegendListModel(self.project_manager.participants)
|
||||
self.ui.listViewParticipants.setModel(self.participant_legend_model)
|
||||
|
||||
gtc = self.generator_tab_controller
|
||||
gtc.ui.splitter.setSizes([int(gtc.width() / 0.7), int(gtc.width() / 0.3)])
|
||||
|
||||
self.ui.tab_generator.layout().addWidget(self.generator_tab_controller)
|
||||
|
||||
self.signal_protocol_dict = {} # type: dict[SignalFrame, ProtocolAnalyzer]
|
||||
|
||||
self.ui.lnEdtTreeFilter.setClearButtonEnabled(True)
|
||||
|
||||
group = QActionGroup(self)
|
||||
self.ui.actionFSK.setActionGroup(group)
|
||||
self.ui.actionOOK.setActionGroup(group)
|
||||
self.ui.actionNone.setActionGroup(group)
|
||||
self.ui.actionPSK.setActionGroup(group)
|
||||
|
||||
noise_threshold_setting = settings.read("default_noise_threshold", "automatic")
|
||||
noise_threshold_group = QActionGroup(self)
|
||||
self.ui.actionAutomaticNoiseThreshold.setActionGroup(noise_threshold_group)
|
||||
self.ui.actionAutomaticNoiseThreshold.setChecked(noise_threshold_setting == "automatic")
|
||||
self.ui.action1NoiseThreshold.setActionGroup(noise_threshold_group)
|
||||
self.ui.action1NoiseThreshold.setChecked(noise_threshold_setting == "1")
|
||||
self.ui.action5NoiseThreshold.setActionGroup(noise_threshold_group)
|
||||
self.ui.action5NoiseThreshold.setChecked(noise_threshold_setting == "5")
|
||||
self.ui.action10NoiseThreshold.setActionGroup(noise_threshold_group)
|
||||
self.ui.action10NoiseThreshold.setChecked(noise_threshold_setting == "10")
|
||||
self.ui.action100NoiseThreshold.setActionGroup(noise_threshold_group)
|
||||
self.ui.action100NoiseThreshold.setChecked(noise_threshold_setting == "100")
|
||||
|
||||
self.recentFileActionList = []
|
||||
self.create_connects()
|
||||
self.init_recent_file_action_list(settings.read("recentFiles", [], list))
|
||||
|
||||
self.filemodel = FileSystemModel(self)
|
||||
path = QDir.homePath()
|
||||
|
||||
self.filemodel.setIconProvider(FileIconProvider())
|
||||
self.filemodel.setRootPath(path)
|
||||
self.file_proxy_model = FileFilterProxyModel(self)
|
||||
self.file_proxy_model.setSourceModel(self.filemodel)
|
||||
self.ui.fileTree.setModel(self.file_proxy_model)
|
||||
|
||||
self.ui.fileTree.setRootIndex(self.file_proxy_model.mapFromSource(self.filemodel.index(path)))
|
||||
self.ui.fileTree.setToolTip(path)
|
||||
self.ui.fileTree.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)
|
||||
self.ui.fileTree.header().setSectionResizeMode(1, QHeaderView.Stretch)
|
||||
self.ui.fileTree.setFocus()
|
||||
|
||||
self.generator_tab_controller.table_model.cfc = self.compare_frame_controller
|
||||
|
||||
self.ui.actionConvert_Folder_to_Project.setEnabled(False)
|
||||
|
||||
undo_action = self.undo_group.createUndoAction(self)
|
||||
undo_action.setIcon(QIcon.fromTheme("edit-undo"))
|
||||
undo_action.setShortcut(QKeySequence.Undo)
|
||||
self.ui.menuEdit.insertAction(self.ui.actionDecoding, undo_action)
|
||||
|
||||
redo_action = self.undo_group.createRedoAction(self)
|
||||
redo_action.setIcon(QIcon.fromTheme("edit-redo"))
|
||||
redo_action.setShortcut(QKeySequence.Redo)
|
||||
self.ui.menuEdit.insertAction(self.ui.actionDecoding, redo_action)
|
||||
self.ui.menuEdit.insertSeparator(self.ui.actionDecoding)
|
||||
|
||||
self.ui.actionAbout_Qt.setIcon(QIcon(":/qt-project.org/qmessagebox/images/qtlogo-64.png"))
|
||||
|
||||
self.__set_non_project_warning_visibility()
|
||||
|
||||
self.ui.splitter.setSizes([0, 1])
|
||||
self.refresh_main_menu()
|
||||
|
||||
self.apply_default_view(settings.read('default_view', type=int))
|
||||
self.project_save_timer.start(ProjectManager.AUTOSAVE_INTERVAL_MINUTES * 60 * 1000)
|
||||
|
||||
self.ui.actionProject_settings.setVisible(False)
|
||||
self.ui.actionSave_project.setVisible(False)
|
||||
self.ui.actionClose_project.setVisible(False)
|
||||
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
def __set_non_project_warning_visibility(self):
|
||||
show = settings.read("show_non_project_warning", True, bool) and not self.project_manager.project_loaded
|
||||
self.ui.labelNonProjectMode.setVisible(show)
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.actionFullscreen_mode.setShortcut(QKeySequence.FullScreen)
|
||||
self.ui.actionOpen.setShortcut(QKeySequence(QKeySequence.Open))
|
||||
self.ui.actionOpen_directory.setShortcut(QKeySequence("Ctrl+Shift+O"))
|
||||
|
||||
self.ui.menuEdit.aboutToShow.connect(self.on_edit_menu_about_to_show)
|
||||
|
||||
self.ui.actionNew_Project.triggered.connect(self.on_new_project_action_triggered)
|
||||
self.ui.actionNew_Project.setShortcut(QKeySequence.New)
|
||||
self.ui.actionProject_settings.triggered.connect(self.on_project_settings_action_triggered)
|
||||
self.ui.actionSave_project.triggered.connect(self.save_project)
|
||||
self.ui.actionClose_project.triggered.connect(self.close_project)
|
||||
|
||||
self.ui.actionAbout_AutomaticHacker.triggered.connect(self.on_show_about_clicked)
|
||||
self.ui.actionRecord.triggered.connect(self.on_show_record_dialog_action_triggered)
|
||||
|
||||
self.ui.actionFullscreen_mode.triggered.connect(self.on_fullscreen_action_triggered)
|
||||
self.ui.actionSaveAllSignals.triggered.connect(self.signal_tab_controller.save_all)
|
||||
self.ui.actionCloseAllFiles.triggered.connect(self.on_close_all_files_action_triggered)
|
||||
self.ui.actionOpen.triggered.connect(self.on_open_file_action_triggered)
|
||||
self.ui.actionOpen_directory.triggered.connect(self.on_open_directory_action_triggered)
|
||||
self.ui.actionDecoding.triggered.connect(self.on_show_decoding_dialog_triggered)
|
||||
self.ui.actionSpectrum_Analyzer.triggered.connect(self.on_show_spectrum_dialog_action_triggered)
|
||||
self.ui.actionOptions.triggered.connect(self.show_options_dialog_action_triggered)
|
||||
self.ui.actionSniff_protocol.triggered.connect(self.show_proto_sniff_dialog)
|
||||
self.ui.actionAbout_Qt.triggered.connect(QApplication.instance().aboutQt)
|
||||
self.ui.actionSamples_from_csv.triggered.connect(self.on_import_samples_from_csv_action_triggered)
|
||||
self.ui.actionAuto_detect_new_signals.triggered.connect(self.on_auto_detect_new_signals_action_triggered)
|
||||
|
||||
self.ui.actionAutomaticNoiseThreshold.triggered.connect(self.on_action_automatic_noise_threshold_triggered)
|
||||
self.ui.action1NoiseThreshold.triggered.connect(self.on_action_1_noise_threshold_triggered)
|
||||
self.ui.action5NoiseThreshold.triggered.connect(self.on_action_5_noise_threshold_triggered)
|
||||
self.ui.action10NoiseThreshold.triggered.connect(self.on_action_10_noise_threshold_triggered)
|
||||
self.ui.action100NoiseThreshold.triggered.connect(self.on_action_100_noise_threshold_triggered)
|
||||
|
||||
self.ui.btnFileTreeGoUp.clicked.connect(self.on_btn_file_tree_go_up_clicked)
|
||||
self.ui.fileTree.directory_open_wanted.connect(self.project_manager.set_project_folder)
|
||||
|
||||
self.signal_tab_controller.frame_closed.connect(self.close_signal_frame)
|
||||
self.signal_tab_controller.signal_created.connect(self.on_signal_created)
|
||||
self.signal_tab_controller.ui.scrollArea.files_dropped.connect(self.on_files_dropped)
|
||||
self.signal_tab_controller.files_dropped.connect(self.on_files_dropped)
|
||||
self.signal_tab_controller.frame_was_dropped.connect(self.set_frame_numbers)
|
||||
|
||||
self.simulator_tab_controller.open_in_analysis_requested.connect(self.on_simulator_open_in_analysis_requested)
|
||||
self.simulator_tab_controller.rx_file_saved.connect(self.adjust_for_current_file)
|
||||
|
||||
self.compare_frame_controller.show_interpretation_clicked.connect(
|
||||
self.show_protocol_selection_in_interpretation)
|
||||
self.compare_frame_controller.files_dropped.connect(self.on_files_dropped)
|
||||
self.compare_frame_controller.show_decoding_clicked.connect(self.on_show_decoding_dialog_triggered)
|
||||
self.compare_frame_controller.ui.treeViewProtocols.files_dropped_on_group.connect(
|
||||
self.on_files_dropped_on_group)
|
||||
self.compare_frame_controller.participant_changed.connect(self.signal_tab_controller.on_participant_changed)
|
||||
self.compare_frame_controller.ui.treeViewProtocols.close_wanted.connect(self.on_cfc_close_wanted)
|
||||
self.compare_frame_controller.show_config_field_types_triggered.connect(
|
||||
self.on_show_field_types_config_action_triggered)
|
||||
|
||||
self.compare_frame_controller.load_protocol_clicked.connect(self.on_compare_frame_controller_load_protocol_clicked)
|
||||
self.compare_frame_controller.ui.listViewParticipants.doubleClicked.connect(self.on_project_settings_action_triggered)
|
||||
|
||||
self.ui.lnEdtTreeFilter.textChanged.connect(self.on_file_tree_filter_text_changed)
|
||||
|
||||
self.ui.tabWidget.currentChanged.connect(self.on_selected_tab_changed)
|
||||
self.project_save_timer.timeout.connect(self.save_project)
|
||||
|
||||
self.ui.actionConvert_Folder_to_Project.triggered.connect(self.project_manager.convert_folder_to_project)
|
||||
self.project_manager.project_loaded_status_changed.connect(self.on_project_loaded_status_changed)
|
||||
self.project_manager.project_updated.connect(self.on_project_updated)
|
||||
|
||||
self.ui.textEditProjectDescription.textChanged.connect(self.on_text_edit_project_description_text_changed)
|
||||
self.ui.tabWidget_Project.tabBarDoubleClicked.connect(self.on_project_tab_bar_double_clicked)
|
||||
|
||||
self.ui.listViewParticipants.doubleClicked.connect(self.on_project_settings_action_triggered)
|
||||
|
||||
self.ui.actionShowFileTree.triggered.connect(self.on_action_show_filetree_triggered)
|
||||
self.ui.actionShowFileTree.setShortcut(QKeySequence("F10"))
|
||||
|
||||
self.ui.labelNonProjectMode.linkActivated.connect(self.on_label_non_project_mode_link_activated)
|
||||
|
||||
self.ui.menuFile.addSeparator()
|
||||
for i in range(settings.MAX_RECENT_FILE_NR):
|
||||
recent_file_action = QAction(self)
|
||||
recent_file_action.setVisible(False)
|
||||
recent_file_action.triggered.connect(self.on_open_recent_action_triggered)
|
||||
self.recentFileActionList.append(recent_file_action)
|
||||
self.ui.menuFile.addAction(self.recentFileActionList[i])
|
||||
|
||||
def add_plain_bits_from_txt(self, filename: str):
|
||||
with open(filename) as f:
|
||||
protocol = ProtocolAnalyzer.get_protocol_from_string(f.readlines())
|
||||
|
||||
protocol.filename = filename
|
||||
protocol.name = util.get_name_from_filename(filename)
|
||||
|
||||
self.compare_frame_controller.add_protocol(protocol)
|
||||
self.compare_frame_controller.refresh()
|
||||
self.__add_empty_frame_for_filename(protocol, filename)
|
||||
|
||||
def __add_empty_frame_for_filename(self, protocol: ProtocolAnalyzer, filename: str):
|
||||
sf = self.signal_tab_controller.add_empty_frame(filename, protocol)
|
||||
self.signal_protocol_dict[sf] = protocol
|
||||
self.set_frame_numbers()
|
||||
self.file_proxy_model.open_files.add(filename)
|
||||
|
||||
def add_protocol_file(self, filename):
|
||||
proto = self.compare_frame_controller.add_protocol_from_file(filename)
|
||||
if proto:
|
||||
self.__add_empty_frame_for_filename(proto, filename)
|
||||
self.ui.tabWidget.setCurrentWidget(self.ui.tab_protocol)
|
||||
|
||||
def add_fuzz_profile(self, filename):
|
||||
self.ui.tabWidget.setCurrentIndex(2)
|
||||
self.generator_tab_controller.load_from_file(filename)
|
||||
|
||||
def add_simulator_profile(self, filename):
|
||||
self.ui.tabWidget.setCurrentIndex(3)
|
||||
self.simulator_tab_controller.load_simulator_file(filename)
|
||||
|
||||
def add_signalfile(self, filename: str, group_id=0, enforce_sample_rate=None):
|
||||
if not os.path.exists(filename):
|
||||
QMessageBox.critical(self, self.tr("File not Found"),
|
||||
self.tr("The file {0} could not be found. Was it moved or renamed?").format(
|
||||
filename))
|
||||
return
|
||||
|
||||
sig_name = os.path.splitext(os.path.basename(filename))[0]
|
||||
|
||||
# Use default sample rate for signal
|
||||
# Sample rate will be overridden in case of a project later
|
||||
if enforce_sample_rate is not None:
|
||||
sample_rate = enforce_sample_rate
|
||||
else:
|
||||
sample_rate = self.project_manager.device_conf["sample_rate"]
|
||||
|
||||
signal = Signal(filename, sig_name, sample_rate=sample_rate)
|
||||
|
||||
self.file_proxy_model.open_files.add(filename)
|
||||
self.add_signal(signal, group_id)
|
||||
|
||||
def add_signal(self, signal, group_id=0, index=-1):
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
pa = ProtocolAnalyzer(signal)
|
||||
sig_frame = self.signal_tab_controller.add_signal_frame(pa, index=index)
|
||||
pa = self.compare_frame_controller.add_protocol(pa, group_id)
|
||||
|
||||
signal.blockSignals(True)
|
||||
has_entry = self.project_manager.read_project_file_for_signal(signal)
|
||||
|
||||
if self.ui.actionAuto_detect_new_signals.isChecked() and not has_entry and not signal.changed:
|
||||
sig_frame.ui.stackedWidget.setCurrentWidget(sig_frame.ui.pageLoading)
|
||||
qApp.processEvents()
|
||||
if not signal.already_demodulated:
|
||||
signal.auto_detect(detect_modulation=True, detect_noise=False)
|
||||
sig_frame.ui.stackedWidget.setCurrentWidget(sig_frame.ui.pageSignal)
|
||||
|
||||
signal.blockSignals(False)
|
||||
|
||||
self.signal_protocol_dict[sig_frame] = pa
|
||||
|
||||
sig_frame.refresh_signal(draw_full_signal=True)
|
||||
sig_frame.refresh_signal_information(block=True)
|
||||
|
||||
qApp.processEvents()
|
||||
sig_frame.show_protocol(refresh=True)
|
||||
|
||||
if self.project_manager.read_participants_for_signal(signal, pa.messages):
|
||||
sig_frame.ui.gvSignal.redraw_view()
|
||||
|
||||
sig_frame.ui.gvSignal.auto_fit_view()
|
||||
self.set_frame_numbers()
|
||||
|
||||
self.compare_frame_controller.filter_search_results()
|
||||
self.refresh_main_menu()
|
||||
self.unsetCursor()
|
||||
|
||||
def close_protocol(self, protocol):
|
||||
self.compare_frame_controller.remove_protocol(protocol)
|
||||
# Needs to be removed in generator also, otherwise program crashes,
|
||||
# if item from tree in generator is selected and corresponding signal is closed
|
||||
self.generator_tab_controller.tree_model.remove_protocol(protocol)
|
||||
protocol.eliminate()
|
||||
|
||||
def close_signal_frame(self, signal_frame: SignalFrame):
|
||||
try:
|
||||
self.project_manager.write_signal_information_to_project_file(signal_frame.signal)
|
||||
try:
|
||||
proto = self.signal_protocol_dict[signal_frame]
|
||||
except KeyError:
|
||||
proto = None
|
||||
|
||||
if proto is not None:
|
||||
self.close_protocol(proto)
|
||||
del self.signal_protocol_dict[signal_frame]
|
||||
|
||||
if self.signal_tab_controller.ui.scrlAreaSignals.minimumHeight() > signal_frame.height():
|
||||
self.signal_tab_controller.ui.scrlAreaSignals.setMinimumHeight(
|
||||
self.signal_tab_controller.ui.scrlAreaSignals.minimumHeight() - signal_frame.height())
|
||||
|
||||
if signal_frame.signal is not None:
|
||||
# Non-Empty Frame (when a signal and not a protocol is opened)
|
||||
self.file_proxy_model.open_files.discard(signal_frame.signal.filename)
|
||||
|
||||
signal_frame.eliminate()
|
||||
|
||||
self.compare_frame_controller.ui.treeViewProtocols.expandAll()
|
||||
self.set_frame_numbers()
|
||||
self.refresh_main_menu()
|
||||
except Exception as e:
|
||||
Errors.exception(e)
|
||||
self.unsetCursor()
|
||||
|
||||
def add_files(self, filepaths, group_id=0, enforce_sample_rate=None):
|
||||
num_files = len(filepaths)
|
||||
if num_files == 0:
|
||||
return
|
||||
|
||||
for i, filename in enumerate(filepaths):
|
||||
if not os.path.exists(filename):
|
||||
continue
|
||||
|
||||
if os.path.isdir(filename):
|
||||
for f in self.signal_tab_controller.signal_frames:
|
||||
self.close_signal_frame(f)
|
||||
|
||||
FileOperator.RECENT_PATH = filename
|
||||
self.project_manager.set_project_folder(filename)
|
||||
return
|
||||
|
||||
FileOperator.RECENT_PATH = os.path.split(filename)[0]
|
||||
|
||||
if filename.endswith(".complex"):
|
||||
self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate)
|
||||
elif filename.endswith(".coco"):
|
||||
self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate)
|
||||
elif filename.endswith(".proto") or filename.endswith(".proto.xml") or filename.endswith(".bin"):
|
||||
self.add_protocol_file(filename)
|
||||
elif filename.endswith(".wav"):
|
||||
try:
|
||||
import wave
|
||||
w = wave.open(filename)
|
||||
w.close()
|
||||
except wave.Error as e:
|
||||
Errors.generic_error("Unsupported WAV type", "Only uncompressed WAVs (PCM) are supported.", str(e))
|
||||
continue
|
||||
self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate)
|
||||
elif filename.endswith(".fuzz") or filename.endswith(".fuzz.xml"):
|
||||
self.add_fuzz_profile(filename)
|
||||
elif filename.endswith(".sim") or filename.endswith(".sim.xml"):
|
||||
self.add_simulator_profile(filename)
|
||||
elif filename.endswith(".txt"):
|
||||
self.add_plain_bits_from_txt(filename)
|
||||
elif filename.endswith(".csv"):
|
||||
self.__import_csv(filename, group_id)
|
||||
continue
|
||||
elif os.path.basename(filename) == settings.PROJECT_FILE:
|
||||
self.project_manager.set_project_folder(os.path.split(filename)[0])
|
||||
else:
|
||||
self.add_signalfile(filename, group_id, enforce_sample_rate=enforce_sample_rate)
|
||||
|
||||
if self.project_manager.project_file is None:
|
||||
self.adjust_for_current_file(filename)
|
||||
|
||||
self.refresh_main_menu()
|
||||
|
||||
def set_frame_numbers(self):
|
||||
self.signal_tab_controller.set_frame_numbers()
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
self.save_project()
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
def close_all_files(self):
|
||||
self.signal_tab_controller.close_all()
|
||||
self.compare_frame_controller.reset()
|
||||
self.generator_tab_controller.table_model.protocol.clear()
|
||||
self.generator_tab_controller.refresh_tree()
|
||||
self.generator_tab_controller.refresh_table()
|
||||
self.generator_tab_controller.refresh_label_list()
|
||||
|
||||
self.signal_tab_controller.signal_undo_stack.clear()
|
||||
self.compare_frame_controller.protocol_undo_stack.clear()
|
||||
self.generator_tab_controller.generator_undo_stack.clear()
|
||||
|
||||
self.simulator_tab_controller.close_all()
|
||||
|
||||
def show_options_dialog_specific_tab(self, tab_index: int):
|
||||
op = OptionsDialog(self.plugin_manager.installed_plugins, parent=self)
|
||||
op.values_changed.connect(self.on_options_changed)
|
||||
op.ui.tabWidget.setCurrentIndex(tab_index)
|
||||
op.show()
|
||||
|
||||
def refresh_main_menu(self):
|
||||
enable = len(self.signal_protocol_dict) > 0
|
||||
self.ui.actionSaveAllSignals.setEnabled(enable)
|
||||
self.ui.actionCloseAllFiles.setEnabled(enable)
|
||||
|
||||
def apply_default_view(self, view_index: int):
|
||||
self.compare_frame_controller.ui.cbProtoView.setCurrentIndex(view_index)
|
||||
self.generator_tab_controller.ui.cbViewType.setCurrentIndex(view_index)
|
||||
self.simulator_tab_controller.ui.cbViewType.setCurrentIndex(view_index)
|
||||
for sig_frame in self.signal_tab_controller.signal_frames:
|
||||
sig_frame.ui.cbProtoView.setCurrentIndex(view_index)
|
||||
|
||||
def show_project_settings(self):
|
||||
pdc = ProjectDialog(new_project=False, project_manager=self.project_manager, parent=self)
|
||||
pdc.finished.connect(self.on_project_dialog_finished)
|
||||
pdc.show()
|
||||
|
||||
def collapse_project_tab_bar(self):
|
||||
self.ui.tabParticipants.hide()
|
||||
self.ui.tabDescription.hide()
|
||||
self.ui.tabWidget_Project.setMaximumHeight(self.ui.tabWidget_Project.tabBar().height())
|
||||
|
||||
def expand_project_tab_bar(self):
|
||||
self.ui.tabDescription.show()
|
||||
self.ui.tabParticipants.show()
|
||||
self.ui.tabWidget_Project.setMaximumHeight(9000)
|
||||
|
||||
def save_project(self):
|
||||
self.project_manager.save_project(simulator_config=self.simulator_tab_controller.simulator_config)
|
||||
|
||||
def close_project(self):
|
||||
self.save_project()
|
||||
self.close_all_files()
|
||||
self.compare_frame_controller.proto_analyzer.message_types.clear()
|
||||
self.compare_frame_controller.active_message_type.clear()
|
||||
self.compare_frame_controller.updateUI()
|
||||
self.project_manager.participants.clear()
|
||||
self.participant_legend_model.update()
|
||||
|
||||
self.filemodel.setRootPath(QDir.homePath())
|
||||
self.ui.fileTree.setRootIndex(self.file_proxy_model.mapFromSource(self.filemodel.index(QDir.homePath())))
|
||||
self.hide_file_tree()
|
||||
|
||||
self.project_manager.project_path = ""
|
||||
self.project_manager.project_file = None
|
||||
|
||||
@pyqtSlot()
|
||||
def on_project_tab_bar_double_clicked(self):
|
||||
if self.ui.tabParticipants.isVisible():
|
||||
self.collapse_project_tab_bar()
|
||||
else:
|
||||
self.expand_project_tab_bar()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_project_updated(self):
|
||||
self.participant_legend_model.update()
|
||||
self.compare_frame_controller.refresh()
|
||||
self.ui.textEditProjectDescription.setText(self.project_manager.description)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_fullscreen_action_triggered(self):
|
||||
if self.ui.actionFullscreen_mode.isChecked():
|
||||
self.showFullScreen()
|
||||
else:
|
||||
self.showMaximized()
|
||||
|
||||
def adjust_for_current_file(self, file_path):
|
||||
if file_path is None:
|
||||
return
|
||||
|
||||
if file_path in FileOperator.archives.keys():
|
||||
file_path = copy.copy(FileOperator.archives[file_path])
|
||||
|
||||
recent_file_paths = settings.read("recentFiles", [], list)
|
||||
recent_file_paths = [] if recent_file_paths is None else recent_file_paths # check None for OSX
|
||||
recent_file_paths = [p for p in recent_file_paths if p != file_path and p is not None and os.path.exists(p)]
|
||||
recent_file_paths.insert(0, file_path)
|
||||
recent_file_paths = recent_file_paths[:settings.MAX_RECENT_FILE_NR]
|
||||
|
||||
self.init_recent_file_action_list(recent_file_paths)
|
||||
|
||||
settings.write("recentFiles", recent_file_paths)
|
||||
|
||||
def init_recent_file_action_list(self, recent_file_paths: list):
|
||||
for i in range(len(self.recentFileActionList)):
|
||||
self.recentFileActionList[i].setVisible(False)
|
||||
|
||||
if recent_file_paths is None:
|
||||
return
|
||||
|
||||
for i, file_path in enumerate(recent_file_paths):
|
||||
if os.path.isfile(file_path):
|
||||
display_text = os.path.basename(file_path)
|
||||
self.recentFileActionList[i].setIcon(QIcon())
|
||||
elif os.path.isdir(file_path):
|
||||
head, tail = os.path.split(file_path)
|
||||
display_text = tail
|
||||
head, tail = os.path.split(head)
|
||||
if tail:
|
||||
display_text = tail + "/" + display_text
|
||||
|
||||
self.recentFileActionList[i].setIcon(QIcon.fromTheme("folder"))
|
||||
else:
|
||||
continue
|
||||
|
||||
self.recentFileActionList[i].setText(display_text)
|
||||
self.recentFileActionList[i].setData(file_path)
|
||||
self.recentFileActionList[i].setVisible(True)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_show_field_types_config_action_triggered(self):
|
||||
self.show_options_dialog_specific_tab(tab_index=2)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_open_recent_action_triggered(self):
|
||||
action = self.sender()
|
||||
try:
|
||||
if os.path.isdir(action.data()):
|
||||
self.project_manager.set_project_folder(action.data())
|
||||
elif os.path.isfile(action.data()):
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
self.add_files(FileOperator.uncompress_archives([action.data()], QDir.tempPath()))
|
||||
self.unsetCursor()
|
||||
except Exception as e:
|
||||
Errors.exception(e)
|
||||
self.unsetCursor()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_show_about_clicked(self):
|
||||
descr = "<b><h2>Universal Radio Hacker</h2></b>Version: {0}<br />" \
|
||||
"GitHub: <a href='https://github.com/jopohl/urh'>https://github.com/jopohl/urh</a><br /><br />" \
|
||||
"Creators:<i><ul><li>" \
|
||||
"Johannes Pohl <<a href='mailto:joahnnes.pohl90@gmail.com'>johannes.pohl90@gmail.com</a>></li>" \
|
||||
"<li>Andreas Noack <<a href='mailto:andreas.noack@hochschule-stralsund.de'>andreas.noack@hochschule-stralsund.de</a>></li>" \
|
||||
"</ul></i>".format(version.VERSION)
|
||||
|
||||
QMessageBox.about(self, self.tr("About"), self.tr(descr))
|
||||
|
||||
@pyqtSlot(int, int, int, int)
|
||||
def show_protocol_selection_in_interpretation(self, start_message, start, end_message, end):
|
||||
try:
|
||||
cfc = self.compare_frame_controller
|
||||
msg_total = 0
|
||||
last_sig_frame = None
|
||||
for protocol in cfc.protocol_list:
|
||||
if not protocol.show:
|
||||
continue
|
||||
n = protocol.num_messages
|
||||
view_type = cfc.ui.cbProtoView.currentIndex()
|
||||
messages = [i - msg_total for i in range(msg_total, msg_total + n) if start_message <= i <= end_message]
|
||||
if len(messages) > 0:
|
||||
try:
|
||||
signal_frame = next((sf for sf, pf in self.signal_protocol_dict.items() if pf == protocol))
|
||||
except StopIteration:
|
||||
QMessageBox.critical(self, self.tr("Error"),
|
||||
self.tr("Could not find corresponding signal frame."))
|
||||
return
|
||||
signal_frame.set_roi_from_protocol_analysis(min(messages), start, max(messages), end + 1, view_type)
|
||||
last_sig_frame = signal_frame
|
||||
msg_total += n
|
||||
focus_frame = last_sig_frame
|
||||
if last_sig_frame is not None:
|
||||
self.signal_tab_controller.ui.scrollArea.ensureWidgetVisible(last_sig_frame, 0, 0)
|
||||
|
||||
QApplication.instance().processEvents()
|
||||
self.ui.tabWidget.setCurrentIndex(0)
|
||||
if focus_frame is not None:
|
||||
focus_frame.ui.txtEdProto.setFocus()
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_file_tree_filter_text_changed(self, text: str):
|
||||
if len(text) > 0:
|
||||
self.filemodel.setNameFilters(["*" + text + "*"])
|
||||
else:
|
||||
self.filemodel.setNameFilters(["*"])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_show_decoding_dialog_triggered(self):
|
||||
signals = [sf.signal for sf in self.signal_tab_controller.signal_frames]
|
||||
decoding_controller = DecoderDialog(
|
||||
self.compare_frame_controller.decodings, signals,
|
||||
self.project_manager, parent=self)
|
||||
decoding_controller.finished.connect(self.update_decodings)
|
||||
decoding_controller.show()
|
||||
decoding_controller.decoder_update()
|
||||
|
||||
@pyqtSlot()
|
||||
def update_decodings(self):
|
||||
self.project_manager.load_decodings()
|
||||
self.compare_frame_controller.fill_decoding_combobox()
|
||||
self.compare_frame_controller.refresh_existing_encodings()
|
||||
|
||||
self.generator_tab_controller.refresh_existing_encodings(self.compare_frame_controller.decodings)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_selected_tab_changed(self, index: int):
|
||||
if index == 0:
|
||||
self.undo_group.setActiveStack(self.signal_tab_controller.signal_undo_stack)
|
||||
elif index == 1:
|
||||
self.undo_group.setActiveStack(self.compare_frame_controller.protocol_undo_stack)
|
||||
self.compare_frame_controller.ui.tblViewProtocol.resize_columns()
|
||||
self.compare_frame_controller.ui.tblViewProtocol.resize_vertical_header()
|
||||
h = max(self.compare_frame_controller.ui.btnSaveProto.height(),
|
||||
self.generator_tab_controller.ui.btnSave.height())
|
||||
self.compare_frame_controller.ui.btnSaveProto.setMinimumHeight(h)
|
||||
|
||||
th = self.compare_frame_controller.ui.tabWidget.tabBar().height()
|
||||
for i in range(self.compare_frame_controller.ui.tabWidget.count()):
|
||||
self.compare_frame_controller.ui.tabWidget.widget(i).layout().setContentsMargins(0, 7 + h - th, 0, 0)
|
||||
|
||||
elif index == 2:
|
||||
self.undo_group.setActiveStack(self.generator_tab_controller.generator_undo_stack)
|
||||
h = max(self.compare_frame_controller.ui.btnSaveProto.height(),
|
||||
self.generator_tab_controller.ui.btnSave.height())
|
||||
self.generator_tab_controller.ui.btnSave.setMinimumHeight(h)
|
||||
th = self.generator_tab_controller.ui.tabWidget.tabBar().height()
|
||||
for i in range(self.generator_tab_controller.ui.tabWidget.count()):
|
||||
self.generator_tab_controller.ui.tabWidget.widget(i).layout().setContentsMargins(0, 7 + h - th, 0, 0)
|
||||
# Modulators may got changed from Simulator Dialog
|
||||
self.generator_tab_controller.refresh_modulators()
|
||||
# Signals may got reordered in analysis
|
||||
self.generator_tab_controller.tree_model.update()
|
||||
self.generator_tab_controller.ui.treeProtocols.expandAll()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_show_record_dialog_action_triggered(self):
|
||||
pm = self.project_manager
|
||||
try:
|
||||
r = ReceiveDialog(pm, parent=self)
|
||||
except OSError as e:
|
||||
logger.error(repr(e))
|
||||
return
|
||||
|
||||
if r.has_empty_device_list:
|
||||
Errors.no_device()
|
||||
r.close()
|
||||
return
|
||||
|
||||
r.device_parameters_changed.connect(pm.set_device_parameters)
|
||||
r.files_recorded.connect(self.on_signals_recorded)
|
||||
r.show()
|
||||
|
||||
def create_protocol_sniff_dialog(self, testing_mode=False):
|
||||
pm = self.project_manager
|
||||
signal = next((proto.signal for proto in self.compare_frame_controller.protocol_list), None)
|
||||
signals = [f.signal for f in self.signal_tab_controller.signal_frames if f.signal]
|
||||
|
||||
psd = ProtocolSniffDialog(project_manager=pm, signal=signal, signals=signals,
|
||||
testing_mode=testing_mode, parent=self)
|
||||
|
||||
if psd.has_empty_device_list:
|
||||
Errors.no_device()
|
||||
psd.close()
|
||||
return None
|
||||
else:
|
||||
psd.device_parameters_changed.connect(pm.set_device_parameters)
|
||||
psd.protocol_accepted.connect(self.compare_frame_controller.add_sniffed_protocol_messages)
|
||||
return psd
|
||||
|
||||
@pyqtSlot()
|
||||
def show_proto_sniff_dialog(self):
|
||||
psd = self.create_protocol_sniff_dialog()
|
||||
if psd:
|
||||
psd.show()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_show_spectrum_dialog_action_triggered(self):
|
||||
pm = self.project_manager
|
||||
r = SpectrumDialogController(pm, parent=self)
|
||||
if r.has_empty_device_list:
|
||||
Errors.no_device()
|
||||
r.close()
|
||||
return
|
||||
|
||||
r.device_parameters_changed.connect(pm.set_device_parameters)
|
||||
r.show()
|
||||
|
||||
@pyqtSlot(list, float)
|
||||
def on_signals_recorded(self, file_names: list, sample_rate: float):
|
||||
QApplication.instance().setOverrideCursor(Qt.WaitCursor)
|
||||
for filename in file_names:
|
||||
self.add_signalfile(filename, enforce_sample_rate=sample_rate)
|
||||
QApplication.instance().restoreOverrideCursor()
|
||||
|
||||
@pyqtSlot()
|
||||
def show_options_dialog_action_triggered(self):
|
||||
self.show_options_dialog_specific_tab(tab_index=4)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_new_project_action_triggered(self):
|
||||
pdc = ProjectDialog(parent=self)
|
||||
pdc.finished.connect(self.on_project_dialog_finished)
|
||||
pdc.show()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_project_settings_action_triggered(self):
|
||||
self.show_project_settings()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_edit_menu_about_to_show(self):
|
||||
self.ui.actionShowFileTree.setChecked(self.ui.splitter.sizes()[0] > 0)
|
||||
|
||||
def hide_file_tree(self):
|
||||
self.ui.splitter.setSizes([0, 1])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_action_show_filetree_triggered(self):
|
||||
if self.ui.splitter.sizes()[0] > 0:
|
||||
self.hide_file_tree()
|
||||
else:
|
||||
self.ui.splitter.setSizes([1, 1])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_project_dialog_finished(self):
|
||||
if self.sender().committed:
|
||||
if self.sender().new_project:
|
||||
self.close_project()
|
||||
self.project_manager.from_dialog(self.sender())
|
||||
else:
|
||||
self.project_manager.project_updated.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_open_file_action_triggered(self):
|
||||
self.show_open_dialog(directory=False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_open_directory_action_triggered(self):
|
||||
self.show_open_dialog(directory=True)
|
||||
|
||||
def show_open_dialog(self, directory=False):
|
||||
dialog = FileOperator.get_open_dialog(directory_mode=directory, parent=self, name_filter="full")
|
||||
if dialog.exec_():
|
||||
try:
|
||||
file_names = dialog.selectedFiles()
|
||||
folders = [folder for folder in file_names if os.path.isdir(folder)]
|
||||
|
||||
if len(folders) > 0:
|
||||
folder = folders[0]
|
||||
for f in self.signal_tab_controller.signal_frames:
|
||||
self.close_signal_frame(f)
|
||||
|
||||
self.project_manager.set_project_folder(folder)
|
||||
else:
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
file_names = FileOperator.uncompress_archives(file_names, QDir.tempPath())
|
||||
self.add_files(file_names)
|
||||
self.unsetCursor()
|
||||
except Exception as e:
|
||||
Errors.exception(e)
|
||||
self.unsetCursor()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_close_all_files_action_triggered(self):
|
||||
self.close_all_files()
|
||||
|
||||
@pyqtSlot(list)
|
||||
def on_files_dropped(self, files):
|
||||
"""
|
||||
:type files: list of QtCore.QUrl
|
||||
"""
|
||||
self.__add_urls_to_group(files, group_id=0)
|
||||
|
||||
@pyqtSlot(list, int)
|
||||
def on_files_dropped_on_group(self, files, group_id: int):
|
||||
"""
|
||||
:param group_id:
|
||||
:type files: list of QtCore.QUrl
|
||||
"""
|
||||
self.__add_urls_to_group(files, group_id=group_id)
|
||||
|
||||
def __add_urls_to_group(self, file_urls, group_id=0):
|
||||
local_files = [file_url.toLocalFile() for file_url in file_urls if file_url.isLocalFile()]
|
||||
if len(local_files) > 0:
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
self.add_files(FileOperator.uncompress_archives(local_files, QDir.tempPath()), group_id=group_id)
|
||||
self.unsetCursor()
|
||||
|
||||
@pyqtSlot(list)
|
||||
def on_cfc_close_wanted(self, protocols: list):
|
||||
frame_protos = {sframe: protocol for sframe, protocol in self.signal_protocol_dict.items() if
|
||||
protocol in protocols}
|
||||
|
||||
for frame in frame_protos:
|
||||
self.close_signal_frame(frame)
|
||||
|
||||
for proto in (proto for proto in protocols if proto not in frame_protos.values()):
|
||||
# close protocols without associated signal frame
|
||||
self.close_protocol(proto)
|
||||
|
||||
@pyqtSlot(dict)
|
||||
def on_options_changed(self, changed_options: dict):
|
||||
refresh_protocol_needed = "show_pause_as_time" in changed_options
|
||||
|
||||
if refresh_protocol_needed:
|
||||
for sf in self.signal_tab_controller.signal_frames:
|
||||
sf.refresh_protocol()
|
||||
|
||||
self.project_manager.reload_field_types()
|
||||
|
||||
self.compare_frame_controller.refresh_field_types_for_labels()
|
||||
self.compare_frame_controller.set_shown_protocols()
|
||||
self.generator_tab_controller.set_network_sdr_send_button_visibility()
|
||||
self.generator_tab_controller.init_rfcat_plugin()
|
||||
self.generator_tab_controller.set_modulation_profile_status()
|
||||
self.simulator_tab_controller.refresh_field_types_for_labels()
|
||||
|
||||
if "num_sending_repeats" in changed_options:
|
||||
self.project_manager.device_conf["num_sending_repeats"] = changed_options["num_sending_repeats"]
|
||||
|
||||
if "default_view" in changed_options:
|
||||
self.apply_default_view(int(changed_options["default_view"]))
|
||||
|
||||
if "spectrogram_colormap" in changed_options:
|
||||
self.signal_tab_controller.redraw_spectrograms()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_text_edit_project_description_text_changed(self):
|
||||
self.project_manager.description = self.ui.textEditProjectDescription.toPlainText()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_file_tree_go_up_clicked(self):
|
||||
cur_dir = self.filemodel.rootDirectory()
|
||||
if cur_dir.cdUp():
|
||||
path = cur_dir.path()
|
||||
self.filemodel.setRootPath(path)
|
||||
self.ui.fileTree.setRootIndex(self.file_proxy_model.mapFromSource(self.filemodel.index(path)))
|
||||
|
||||
@pyqtSlot(int, Signal)
|
||||
def on_signal_created(self, index: int, signal: Signal):
|
||||
self.add_signal(signal, index=index)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_cancel_triggered(self):
|
||||
for signal_frame in self.signal_tab_controller.signal_frames:
|
||||
signal_frame.cancel_filtering()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_import_samples_from_csv_action_triggered(self):
|
||||
self.__import_csv(file_name="")
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_auto_detect_new_signals_action_triggered(self, checked: bool):
|
||||
settings.write("auto_detect_new_signals", bool(checked))
|
||||
|
||||
def __import_csv(self, file_name, group_id=0):
|
||||
def on_data_imported(complex_file, sample_rate):
|
||||
sample_rate = None if sample_rate == 0 else sample_rate
|
||||
self.add_files([complex_file], group_id=group_id, enforce_sample_rate=sample_rate)
|
||||
|
||||
dialog = CSVImportDialog(file_name, parent=self)
|
||||
dialog.data_imported.connect(on_data_imported)
|
||||
dialog.exec_()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_label_non_project_mode_link_activated(self, link: str):
|
||||
if link == "dont_show_non_project_again":
|
||||
self.ui.labelNonProjectMode.hide()
|
||||
settings.write("show_non_project_warning", False)
|
||||
elif link == "open_new_project_dialog":
|
||||
self.on_new_project_action_triggered()
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_project_loaded_status_changed(self, project_loaded: bool):
|
||||
self.ui.actionProject_settings.setVisible(project_loaded)
|
||||
self.ui.actionSave_project.setVisible(project_loaded)
|
||||
self.ui.actionClose_project.setVisible(project_loaded)
|
||||
self.ui.actionConvert_Folder_to_Project.setDisabled(project_loaded)
|
||||
self.__set_non_project_warning_visibility()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_compare_frame_controller_load_protocol_clicked(self):
|
||||
dialog = FileOperator.get_open_dialog(directory_mode=False, parent=self, name_filter="proto")
|
||||
if dialog.exec_():
|
||||
for filename in dialog.selectedFiles():
|
||||
self.add_protocol_file(filename)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_simulator_open_in_analysis_requested(self, text: str):
|
||||
protocol = ProtocolAnalyzer.get_protocol_from_string(text.split("\n"))
|
||||
protocol.name = "Transcript"
|
||||
|
||||
self.ui.tabWidget.setCurrentIndex(1)
|
||||
self.compare_frame_controller.add_protocol(protocol)
|
||||
self.compare_frame_controller.refresh()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_action_automatic_noise_threshold_triggered(self):
|
||||
settings.write("default_noise_threshold", "automatic")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_action_1_noise_threshold_triggered(self):
|
||||
settings.write("default_noise_threshold", "1")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_action_5_noise_threshold_triggered(self):
|
||||
settings.write("default_noise_threshold", "5")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_action_10_noise_threshold_triggered(self):
|
||||
settings.write("default_noise_threshold", "10")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_action_100_noise_threshold_triggered(self):
|
||||
settings.write("default_noise_threshold", "100")
|
@ -0,0 +1,249 @@
|
||||
from PyQt5.QtCore import QPoint, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtWidgets import QWidget, QSizePolicy, QUndoStack, QCheckBox, QMessageBox
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.widgets.SignalFrame import SignalFrame
|
||||
from urh.signalprocessing.Signal import Signal
|
||||
from urh.ui.ui_tab_interpretation import Ui_Interpretation
|
||||
from urh.util import util
|
||||
|
||||
|
||||
class SignalTabController(QWidget):
|
||||
frame_closed = pyqtSignal(SignalFrame)
|
||||
not_show_again_changed = pyqtSignal()
|
||||
signal_created = pyqtSignal(int, Signal)
|
||||
files_dropped = pyqtSignal(list)
|
||||
frame_was_dropped = pyqtSignal(int, int)
|
||||
|
||||
@property
|
||||
def num_frames(self):
|
||||
return len(self.signal_frames)
|
||||
|
||||
@property
|
||||
def signal_frames(self):
|
||||
"""
|
||||
|
||||
:rtype: list of SignalFrame
|
||||
"""
|
||||
splitter = self.ui.splitter
|
||||
return [splitter.widget(i) for i in range(splitter.count())
|
||||
if isinstance(splitter.widget(i), SignalFrame)]
|
||||
|
||||
@property
|
||||
def signal_undo_stack(self):
|
||||
return self.undo_stack
|
||||
|
||||
def __init__(self, project_manager, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_Interpretation()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
util.set_splitter_stylesheet(self.ui.splitter)
|
||||
|
||||
self.ui.placeholderLabel.setVisible(False)
|
||||
self.getting_started_status = None
|
||||
self.__set_getting_started_status(True)
|
||||
|
||||
self.undo_stack = QUndoStack()
|
||||
self.project_manager = project_manager
|
||||
|
||||
self.drag_pos = None
|
||||
|
||||
def on_files_dropped(self, files):
|
||||
self.files_dropped.emit(files)
|
||||
|
||||
def close_frame(self, frame:SignalFrame):
|
||||
self.frame_closed.emit(frame)
|
||||
|
||||
def add_signal_frame(self, proto_analyzer, index=-1):
|
||||
self.__set_getting_started_status(False)
|
||||
sig_frame = SignalFrame(proto_analyzer, self.undo_stack, self.project_manager, parent=self)
|
||||
sframes = self.signal_frames
|
||||
|
||||
if len(proto_analyzer.signal.filename) == 0:
|
||||
# new signal from "create signal from selection"
|
||||
sig_frame.ui.btnSaveSignal.show()
|
||||
|
||||
self.__create_connects_for_signal_frame(signal_frame=sig_frame)
|
||||
sig_frame.signal_created.connect(self.emit_signal_created)
|
||||
sig_frame.not_show_again_changed.connect(self.not_show_again_changed.emit)
|
||||
sig_frame.ui.lineEditSignalName.setToolTip(self.tr("Sourcefile: ") + proto_analyzer.signal.filename)
|
||||
sig_frame.apply_to_all_clicked.connect(self.on_apply_to_all_clicked)
|
||||
|
||||
prev_signal_frame = sframes[-1] if len(sframes) > 0 else None
|
||||
if prev_signal_frame is not None and hasattr(prev_signal_frame, "ui"):
|
||||
sig_frame.ui.cbProtoView.setCurrentIndex(prev_signal_frame.ui.cbProtoView.currentIndex())
|
||||
|
||||
sig_frame.blockSignals(True)
|
||||
|
||||
index = self.num_frames if index == -1 else index
|
||||
self.ui.splitter.insertWidget(index, sig_frame)
|
||||
sig_frame.blockSignals(False)
|
||||
|
||||
default_view = settings.read('default_view', 0, int)
|
||||
sig_frame.ui.cbProtoView.setCurrentIndex(default_view)
|
||||
|
||||
return sig_frame
|
||||
|
||||
def add_empty_frame(self, filename: str, proto):
|
||||
self.__set_getting_started_status(False)
|
||||
sig_frame = SignalFrame(proto_analyzer=proto, undo_stack=self.undo_stack, project_manager=self.project_manager,
|
||||
parent=self)
|
||||
|
||||
sig_frame.ui.lineEditSignalName.setText(filename)
|
||||
self.__create_connects_for_signal_frame(signal_frame=sig_frame)
|
||||
|
||||
self.ui.splitter.insertWidget(self.num_frames, sig_frame)
|
||||
|
||||
return sig_frame
|
||||
|
||||
def __set_getting_started_status(self, getting_started: bool):
|
||||
if getting_started == self.getting_started_status:
|
||||
return
|
||||
|
||||
self.getting_started_status = getting_started
|
||||
self.ui.labelGettingStarted.setVisible(getting_started)
|
||||
|
||||
if not getting_started:
|
||||
w = QWidget()
|
||||
w.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
|
||||
self.ui.splitter.addWidget(w)
|
||||
|
||||
def __create_connects_for_signal_frame(self, signal_frame: SignalFrame):
|
||||
signal_frame.hold_shift = settings.read('hold_shift_to_drag', True, type=bool)
|
||||
signal_frame.drag_started.connect(self.frame_dragged)
|
||||
signal_frame.frame_dropped.connect(self.frame_dropped)
|
||||
signal_frame.files_dropped.connect(self.on_files_dropped)
|
||||
signal_frame.closed.connect(self.close_frame)
|
||||
|
||||
def set_frame_numbers(self):
|
||||
for i, f in enumerate(self.signal_frames):
|
||||
f.ui.lSignalNr.setText("{0:d}:".format(i + 1))
|
||||
|
||||
@pyqtSlot()
|
||||
def save_all(self):
|
||||
if self.num_frames == 0:
|
||||
return
|
||||
|
||||
try:
|
||||
not_show = settings.read('not_show_save_dialog', False, type=bool)
|
||||
except TypeError:
|
||||
not_show = False
|
||||
|
||||
if not not_show:
|
||||
cb = QCheckBox("Don't ask me again.")
|
||||
msg_box = QMessageBox(QMessageBox.Question, self.tr("Confirm saving all signals"),
|
||||
self.tr("All changed signal files will be overwritten. OK?"))
|
||||
msg_box.addButton(QMessageBox.Yes)
|
||||
msg_box.addButton(QMessageBox.No)
|
||||
msg_box.setCheckBox(cb)
|
||||
|
||||
reply = msg_box.exec()
|
||||
not_show_again = cb.isChecked()
|
||||
settings.write("not_show_save_dialog", not_show_again)
|
||||
self.not_show_again_changed.emit()
|
||||
|
||||
if reply != QMessageBox.Yes:
|
||||
return
|
||||
|
||||
for f in self.signal_frames:
|
||||
if f.signal is None or f.signal.filename == "":
|
||||
continue
|
||||
f.signal.save()
|
||||
|
||||
@pyqtSlot()
|
||||
def close_all(self):
|
||||
for f in self.signal_frames:
|
||||
f.my_close()
|
||||
|
||||
@pyqtSlot(Signal)
|
||||
def on_apply_to_all_clicked(self, signal: Signal):
|
||||
for frame in self.signal_frames:
|
||||
if frame.signal is not None:
|
||||
frame.signal.noise_min_plot = signal.noise_min_plot
|
||||
frame.signal.noise_max_plot = signal.noise_max_plot
|
||||
|
||||
frame.signal.block_protocol_update = True
|
||||
proto_needs_update = False
|
||||
|
||||
if frame.signal.modulation_type != signal.modulation_type:
|
||||
frame.signal.modulation_type = signal.modulation_type
|
||||
proto_needs_update = True
|
||||
|
||||
if frame.signal.center != signal.center:
|
||||
frame.signal.center = signal.center
|
||||
proto_needs_update = True
|
||||
|
||||
if frame.signal.tolerance != signal.tolerance:
|
||||
frame.signal.tolerance = signal.tolerance
|
||||
proto_needs_update = True
|
||||
|
||||
if frame.signal.noise_threshold != signal.noise_threshold:
|
||||
frame.signal.noise_threshold_relative = signal.noise_threshold_relative
|
||||
proto_needs_update = True
|
||||
|
||||
if frame.signal.samples_per_symbol != signal.samples_per_symbol:
|
||||
frame.signal.samples_per_symbol = signal.samples_per_symbol
|
||||
proto_needs_update = True
|
||||
|
||||
if frame.signal.pause_threshold != signal.pause_threshold:
|
||||
frame.signal.pause_threshold = signal.pause_threshold
|
||||
proto_needs_update = True
|
||||
|
||||
if frame.signal.message_length_divisor != signal.message_length_divisor:
|
||||
frame.signal.message_length_divisor = signal.message_length_divisor
|
||||
proto_needs_update = True
|
||||
|
||||
frame.signal.block_protocol_update = False
|
||||
|
||||
if proto_needs_update:
|
||||
frame.signal.protocol_needs_update.emit()
|
||||
|
||||
@pyqtSlot(QPoint)
|
||||
def frame_dragged(self, pos: QPoint):
|
||||
self.drag_pos = pos
|
||||
|
||||
@pyqtSlot(QPoint)
|
||||
def frame_dropped(self, pos: QPoint):
|
||||
start = self.drag_pos
|
||||
if start is None:
|
||||
return
|
||||
|
||||
end = pos
|
||||
start_index = -1
|
||||
end_index = -1
|
||||
if self.num_frames > 1:
|
||||
for i, w in enumerate(self.signal_frames):
|
||||
if w.geometry().contains(start):
|
||||
start_index = i
|
||||
|
||||
if w.geometry().contains(end):
|
||||
end_index = i
|
||||
|
||||
self.swap_frames(start_index, end_index)
|
||||
self.frame_was_dropped.emit(start_index, end_index)
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
def swap_frames(self, from_index: int, to_index: int):
|
||||
if from_index != to_index:
|
||||
start_sig_widget = self.ui.splitter.widget(from_index)
|
||||
self.ui.splitter.insertWidget(to_index, start_sig_widget)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_participant_changed(self):
|
||||
for sframe in self.signal_frames:
|
||||
sframe.on_participant_changed()
|
||||
|
||||
def redraw_spectrograms(self):
|
||||
for frame in self.signal_frames:
|
||||
if frame.ui.gvSpectrogram.width_spectrogram > 0:
|
||||
frame.draw_spectrogram(force_redraw=True)
|
||||
|
||||
@pyqtSlot(Signal)
|
||||
def emit_signal_created(self, signal):
|
||||
try:
|
||||
index = self.signal_frames.index(self.sender()) + 1
|
||||
except ValueError:
|
||||
index = -1
|
||||
|
||||
self.signal_created.emit(index, signal)
|
@ -0,0 +1,584 @@
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import numpy
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QDir, QStringListModel, pyqtSignal
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QWidget, QFileDialog, QCompleter, QMessageBox, QFrame, \
|
||||
QHBoxLayout, QToolButton, QDialog
|
||||
|
||||
from urh.controller.CompareFrameController import CompareFrameController
|
||||
from urh.controller.GeneratorTabController import GeneratorTabController
|
||||
from urh.controller.dialogs.ModulatorDialog import ModulatorDialog
|
||||
from urh.controller.dialogs.ProtocolLabelDialog import ProtocolLabelDialog
|
||||
from urh.controller.dialogs.SimulatorDialog import SimulatorDialog
|
||||
from urh.controller.widgets.ChecksumWidget import ChecksumWidget
|
||||
from urh.models.ParticipantTableModel import ParticipantTableModel
|
||||
from urh.models.SimulatorMessageFieldModel import SimulatorMessageFieldModel
|
||||
from urh.models.SimulatorMessageTableModel import SimulatorMessageTableModel
|
||||
from urh.models.SimulatorParticipantListModel import SimulatorParticipantListModel
|
||||
from urh.signalprocessing.Participant import Participant
|
||||
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
|
||||
from urh.simulator.SimulatorConfiguration import SimulatorConfiguration
|
||||
from urh.simulator.SimulatorCounterAction import SimulatorCounterAction
|
||||
from urh.simulator.SimulatorExpressionParser import SimulatorExpressionParser
|
||||
from urh.simulator.SimulatorGotoAction import SimulatorGotoAction
|
||||
from urh.simulator.SimulatorItem import SimulatorItem
|
||||
from urh.simulator.SimulatorMessage import SimulatorMessage
|
||||
from urh.simulator.SimulatorProtocolLabel import SimulatorProtocolLabel
|
||||
from urh.simulator.SimulatorRule import SimulatorRuleCondition, ConditionType
|
||||
from urh.simulator.SimulatorSleepAction import SimulatorSleepAction
|
||||
from urh.simulator.SimulatorTriggerCommandAction import SimulatorTriggerCommandAction
|
||||
from urh.ui.RuleExpressionValidator import RuleExpressionValidator
|
||||
from urh.ui.SimulatorScene import SimulatorScene
|
||||
from urh.ui.delegates.ComboBoxDelegate import ComboBoxDelegate
|
||||
from urh.ui.delegates.ProtocolValueDelegate import ProtocolValueDelegate
|
||||
from urh.ui.ui_simulator import Ui_SimulatorTab
|
||||
from urh.util import util, FileOperator
|
||||
from urh.util.Errors import Errors
|
||||
from urh.util.Logger import logger
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class SimulatorTabController(QWidget):
|
||||
open_in_analysis_requested = pyqtSignal(str)
|
||||
rx_file_saved = pyqtSignal(str)
|
||||
|
||||
def __init__(self, compare_frame_controller: CompareFrameController,
|
||||
generator_tab_controller: GeneratorTabController,
|
||||
project_manager: ProjectManager, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self.project_manager = project_manager
|
||||
self.compare_frame_controller = compare_frame_controller
|
||||
self.generator_tab_controller = generator_tab_controller
|
||||
self.proto_analyzer = compare_frame_controller.proto_analyzer
|
||||
|
||||
self.simulator_config = SimulatorConfiguration(self.project_manager)
|
||||
self.sim_expression_parser = SimulatorExpressionParser(self.simulator_config)
|
||||
SimulatorItem.simulator_config = self.simulator_config
|
||||
SimulatorItem.expression_parser = self.sim_expression_parser
|
||||
|
||||
self.ui = Ui_SimulatorTab()
|
||||
self.ui.setupUi(self)
|
||||
util.set_splitter_stylesheet(self.ui.splitter)
|
||||
util.set_splitter_stylesheet(self.ui.splitterLeftRight)
|
||||
|
||||
self.ui.splitter.setSizes([int(self.width() / 0.7), int(self.width() / 0.3)])
|
||||
|
||||
self.ui.treeProtocols.setHeaderHidden(True)
|
||||
self.tree_model = self.generator_tab_controller.tree_model
|
||||
self.ui.treeProtocols.setModel(self.tree_model)
|
||||
|
||||
self.participant_table_model = ParticipantTableModel(project_manager.participants)
|
||||
self.ui.tableViewParticipants.setModel(self.participant_table_model)
|
||||
self.participant_table_model.update()
|
||||
|
||||
self.simulator_message_field_model = SimulatorMessageFieldModel(self)
|
||||
self.ui.tblViewFieldValues.setModel(self.simulator_message_field_model)
|
||||
self.ui.tblViewFieldValues.setItemDelegateForColumn(1, ComboBoxDelegate(ProtocolLabel.DISPLAY_FORMATS,
|
||||
parent=self.ui.tblViewFieldValues))
|
||||
self.ui.tblViewFieldValues.setItemDelegateForColumn(2, ComboBoxDelegate(SimulatorProtocolLabel.VALUE_TYPES,
|
||||
parent=self.ui.tblViewFieldValues))
|
||||
self.ui.tblViewFieldValues.setItemDelegateForColumn(3, ProtocolValueDelegate(controller=self,
|
||||
parent=self.ui.tblViewFieldValues))
|
||||
self.project_manager.reload_field_types()
|
||||
self.update_field_name_column()
|
||||
|
||||
self.simulator_message_table_model = SimulatorMessageTableModel(self.project_manager, self)
|
||||
self.ui.tblViewMessage.setModel(self.simulator_message_table_model)
|
||||
|
||||
self.ui.ruleCondLineEdit.setValidator(RuleExpressionValidator(self.sim_expression_parser, is_formula=False))
|
||||
self.completer_model = QStringListModel([])
|
||||
self.ui.ruleCondLineEdit.setCompleter(QCompleter(self.completer_model, self.ui.ruleCondLineEdit))
|
||||
self.ui.ruleCondLineEdit.setToolTip(self.sim_expression_parser.rule_condition_help)
|
||||
|
||||
self.simulator_scene = SimulatorScene(mode=0, simulator_config=self.simulator_config)
|
||||
self.simulator_scene.tree_root_item = compare_frame_controller.proto_tree_model.rootItem
|
||||
self.ui.gvSimulator.setScene(self.simulator_scene)
|
||||
self.ui.gvSimulator.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
self.ui.gvSimulator.proto_analyzer = compare_frame_controller.proto_analyzer
|
||||
|
||||
self.__active_item = None
|
||||
|
||||
self.ui.listViewSimulate.setModel(SimulatorParticipantListModel(self.simulator_config))
|
||||
self.ui.spinBoxNRepeat.setValue(self.project_manager.simulator_num_repeat)
|
||||
self.ui.spinBoxTimeout.setValue(self.project_manager.simulator_timeout_ms)
|
||||
self.ui.spinBoxRetries.setValue(self.project_manager.simulator_retries)
|
||||
self.ui.comboBoxError.setCurrentIndex(self.project_manager.simulator_error_handling_index)
|
||||
|
||||
# place save/load button at corner of tab widget
|
||||
frame = QFrame(parent=self)
|
||||
frame.setLayout(QHBoxLayout())
|
||||
frame.setFrameStyle(frame.NoFrame)
|
||||
self.ui.btnSave = QToolButton(self.ui.tab)
|
||||
self.ui.btnSave.setIcon(QIcon.fromTheme("document-save"))
|
||||
frame.layout().addWidget(self.ui.btnSave)
|
||||
|
||||
self.ui.btnLoad = QToolButton(self.ui.tab)
|
||||
self.ui.btnLoad.setIcon(QIcon.fromTheme("document-open"))
|
||||
frame.layout().addWidget(self.ui.btnLoad)
|
||||
frame.layout().setContentsMargins(0, 0, 0, 0)
|
||||
self.ui.tabWidget.setCornerWidget(frame)
|
||||
|
||||
self.ui.splitterLeftRight.setSizes([int(0.2 * self.width()), int(0.8 * self.width())])
|
||||
|
||||
self.create_connects()
|
||||
|
||||
def refresh_field_types_for_labels(self):
|
||||
for msg in self.simulator_config.get_all_messages():
|
||||
for lbl in (lbl for lbl in msg.message_type if lbl.field_type is not None):
|
||||
msg.message_type.change_field_type_of_label(lbl, self.field_types_by_caption.get(lbl.field_type.caption,
|
||||
None))
|
||||
|
||||
self.update_field_name_column()
|
||||
self.update_ui()
|
||||
|
||||
@property
|
||||
def field_types(self):
|
||||
return self.project_manager.field_types
|
||||
|
||||
@property
|
||||
def field_types_by_caption(self):
|
||||
return self.project_manager.field_types_by_caption
|
||||
|
||||
def update_field_name_column(self):
|
||||
field_types = [ft.caption for ft in self.field_types]
|
||||
self.ui.tblViewFieldValues.setItemDelegateForColumn(0, ComboBoxDelegate(field_types, is_editable=True,
|
||||
return_index=False,
|
||||
parent=self.ui.tblViewFieldValues))
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.btnChooseCommand.clicked.connect(self.on_btn_choose_command_clicked)
|
||||
self.ui.lineEditTriggerCommand.textChanged.connect(self.on_line_edit_trigger_command_text_changed)
|
||||
self.ui.checkBoxPassTranscriptSTDIN.clicked.connect(self.on_check_box_pass_transcript_STDIN_clicked)
|
||||
self.ui.doubleSpinBoxSleep.editingFinished.connect(self.on_spinbox_sleep_editing_finished)
|
||||
self.ui.ruleCondLineEdit.textChanged.connect(self.on_rule_cond_line_edit_text_changed)
|
||||
self.ui.btnStartSim.clicked.connect(self.on_btn_simulate_clicked)
|
||||
self.ui.goto_combobox.currentIndexChanged.connect(self.on_goto_combobox_index_changed)
|
||||
self.ui.spinBoxRepeat.valueChanged.connect(self.on_repeat_value_changed)
|
||||
self.ui.cbViewType.currentIndexChanged.connect(self.on_view_type_changed)
|
||||
self.ui.tblViewMessage.create_label_triggered.connect(self.create_simulator_label)
|
||||
self.ui.tblViewMessage.open_modulator_dialog_clicked.connect(self.open_modulator_dialog)
|
||||
self.ui.tblViewMessage.selectionModel().selectionChanged.connect(self.on_table_selection_changed)
|
||||
self.ui.tabWidget.currentChanged.connect(self.on_selected_tab_changed)
|
||||
self.ui.btnSave.clicked.connect(self.on_btn_save_clicked)
|
||||
self.ui.btnLoad.clicked.connect(self.on_btn_load_clicked)
|
||||
|
||||
self.ui.listViewSimulate.model().participant_simulate_changed.connect(self.on_participant_simulate_changed)
|
||||
|
||||
self.ui.btnAddParticipant.clicked.connect(self.ui.tableViewParticipants.on_add_action_triggered)
|
||||
self.ui.btnRemoveParticipant.clicked.connect(self.ui.tableViewParticipants.on_remove_action_triggered)
|
||||
self.ui.btnUp.clicked.connect(self.ui.tableViewParticipants.on_move_up_action_triggered)
|
||||
self.ui.btnDown.clicked.connect(self.ui.tableViewParticipants.on_move_down_action_triggered)
|
||||
self.participant_table_model.participant_edited.connect(self.on_participant_edited)
|
||||
|
||||
self.tree_model.modelReset.connect(self.refresh_tree)
|
||||
|
||||
self.simulator_scene.selectionChanged.connect(self.on_simulator_scene_selection_changed)
|
||||
self.simulator_scene.files_dropped.connect(self.on_files_dropped)
|
||||
|
||||
self.simulator_message_field_model.protocol_label_updated.connect(self.item_updated)
|
||||
self.ui.gvSimulator.message_updated.connect(self.item_updated)
|
||||
self.ui.gvSimulator.consolidate_messages_clicked.connect(self.consolidate_messages)
|
||||
|
||||
self.simulator_config.items_added.connect(self.refresh_message_table)
|
||||
self.simulator_config.items_updated.connect(self.refresh_message_table)
|
||||
self.simulator_config.items_moved.connect(self.refresh_message_table)
|
||||
self.simulator_config.items_deleted.connect(self.refresh_message_table)
|
||||
self.simulator_config.participants_changed.connect(self.on_participants_changed)
|
||||
self.simulator_config.item_dict_updated.connect(self.on_item_dict_updated)
|
||||
self.simulator_config.active_participants_updated.connect(self.on_active_participants_updated)
|
||||
|
||||
self.ui.gvSimulator.message_updated.connect(self.on_message_source_or_destination_updated)
|
||||
|
||||
self.ui.spinBoxNRepeat.valueChanged.connect(self.on_spinbox_num_repeat_value_changed)
|
||||
self.ui.spinBoxTimeout.valueChanged.connect(self.on_spinbox_timeout_value_changed)
|
||||
self.ui.comboBoxError.currentIndexChanged.connect(self.on_combobox_error_handling_index_changed)
|
||||
self.ui.spinBoxRetries.valueChanged.connect(self.on_spinbox_retries_value_changed)
|
||||
|
||||
self.ui.tblViewFieldValues.item_link_clicked.connect(self.on_table_item_link_clicked)
|
||||
self.ui.tblViewMessage.edit_label_triggered.connect(self.on_edit_label_triggered)
|
||||
|
||||
self.ui.spinBoxCounterStart.editingFinished.connect(self.on_spinbox_counter_start_editing_finished)
|
||||
self.ui.spinBoxCounterStep.editingFinished.connect(self.on_spinbox_counter_step_editing_finished)
|
||||
|
||||
def consolidate_messages(self):
|
||||
self.simulator_config.consolidate_messages()
|
||||
|
||||
def on_repeat_value_changed(self, value):
|
||||
self.active_item.repeat = value
|
||||
self.simulator_config.items_updated.emit([self.active_item])
|
||||
|
||||
def on_item_dict_updated(self):
|
||||
self.completer_model.setStringList(self.sim_expression_parser.get_identifiers())
|
||||
|
||||
def on_selected_tab_changed(self, index: int):
|
||||
if index == 0:
|
||||
if self.active_item is not None:
|
||||
self.ui.gvSimulator.jump_to_item(self.active_item)
|
||||
else:
|
||||
self.update_ui()
|
||||
else:
|
||||
self.ui.tblViewMessage.resize_columns()
|
||||
self.update_vertical_table_header()
|
||||
|
||||
def refresh_message_table(self):
|
||||
self.simulator_message_table_model.protocol.messages[:] = self.simulator_config.get_all_messages()
|
||||
self.simulator_message_table_model.update()
|
||||
|
||||
if isinstance(self.active_item, SimulatorMessage):
|
||||
self.simulator_message_field_model.update()
|
||||
|
||||
self.ui.tblViewMessage.resize_columns()
|
||||
self.update_ui()
|
||||
|
||||
def load_config_from_xml_tag(self, xml_tag, update_before=True):
|
||||
if xml_tag is None:
|
||||
return
|
||||
|
||||
if update_before:
|
||||
self.simulator_config.on_project_updated()
|
||||
|
||||
self.simulator_config.load_from_xml(xml_tag, self.proto_analyzer.message_types)
|
||||
self.project_manager.project_updated.emit()
|
||||
|
||||
def load_simulator_file(self, filename: str):
|
||||
try:
|
||||
tree = ET.parse(filename)
|
||||
self.load_config_from_xml_tag(tree.getroot(), update_before=False)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
def save_simulator_file(self, filename: str):
|
||||
tag = self.simulator_config.save_to_xml(standalone=True)
|
||||
util.write_xml_to_file(tag, filename)
|
||||
|
||||
def close_all(self):
|
||||
self.simulator_scene.clear_all()
|
||||
|
||||
@pyqtSlot(int, int, int)
|
||||
def create_simulator_label(self, msg_index: int, start: int, end: int):
|
||||
con = self.simulator_message_table_model.protocol
|
||||
start, end = con.convert_range(start, end - 1, self.ui.cbViewType.currentIndex(), 0, False, msg_index)
|
||||
lbl = self.simulator_config.add_label(start=start, end=end, parent_item=con.messages[msg_index])
|
||||
|
||||
try:
|
||||
index = self.simulator_message_field_model.message_type.index(lbl)
|
||||
self.ui.tblViewFieldValues.edit(self.simulator_message_field_model.createIndex(index, 0))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@pyqtSlot()
|
||||
def open_modulator_dialog(self):
|
||||
selected_message = self.simulator_message_table_model.protocol.messages[self.ui.tblViewMessage.selected_rows[0]]
|
||||
preselected_index = selected_message.modulator_index
|
||||
|
||||
modulator_dialog = ModulatorDialog(self.project_manager.modulators, tree_model=self.tree_model, parent=self)
|
||||
modulator_dialog.ui.comboBoxCustomModulations.setCurrentIndex(preselected_index)
|
||||
modulator_dialog.showMaximized()
|
||||
modulator_dialog.initialize(selected_message.encoded_bits_str[0:16])
|
||||
|
||||
modulator_dialog.finished.connect(self.refresh_modulators)
|
||||
modulator_dialog.finished.connect(self.generator_tab_controller.refresh_pause_list)
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_modulators(self):
|
||||
# update Generator tab ...
|
||||
cBoxModulations = self.generator_tab_controller.ui.cBoxModulations
|
||||
current_index = cBoxModulations.currentIndex()
|
||||
cBoxModulations.clear()
|
||||
|
||||
for modulator in self.project_manager.modulators:
|
||||
cBoxModulations.addItem(modulator.name)
|
||||
|
||||
cBoxModulations.setCurrentIndex(current_index)
|
||||
|
||||
# update Simulator tab ...
|
||||
index = self.sender().ui.comboBoxCustomModulations.currentIndex()
|
||||
|
||||
for row in self.ui.tblViewMessage.selected_rows:
|
||||
self.simulator_message_table_model.protocol.messages[row].modulator_index = index
|
||||
|
||||
def update_goto_combobox(self, active_item: SimulatorGotoAction):
|
||||
assert isinstance(active_item, SimulatorGotoAction)
|
||||
goto_combobox = self.ui.goto_combobox
|
||||
|
||||
goto_combobox.blockSignals(True)
|
||||
goto_combobox.clear()
|
||||
goto_combobox.addItem("Select item ...")
|
||||
goto_combobox.addItems(active_item.get_valid_goto_targets())
|
||||
goto_combobox.setCurrentIndex(-1)
|
||||
goto_combobox.blockSignals(False)
|
||||
|
||||
index = goto_combobox.findText(self.active_item.goto_target)
|
||||
|
||||
if index == -1:
|
||||
index = 0
|
||||
|
||||
goto_combobox.setCurrentIndex(index)
|
||||
|
||||
def update_ui(self):
|
||||
if self.active_item is not None:
|
||||
text = self.tr("Detail view for item #") + self.active_item.index()
|
||||
|
||||
if isinstance(self.active_item, SimulatorMessage):
|
||||
text += " (" + self.active_item.message_type.name + ")"
|
||||
self.ui.spinBoxRepeat.setValue(self.active_item.repeat)
|
||||
self.ui.lblEncodingDecoding.setText(self.active_item.decoder.name)
|
||||
|
||||
self.ui.lblMsgFieldsValues.setText(text)
|
||||
else:
|
||||
self.ui.lblMsgFieldsValues.setText(self.tr("Detail view for item"))
|
||||
|
||||
def update_vertical_table_header(self):
|
||||
self.simulator_message_table_model.refresh_vertical_header()
|
||||
self.ui.tblViewMessage.resize_vertical_header()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_rule_cond_line_edit_text_changed(self):
|
||||
self.active_item.condition = self.ui.ruleCondLineEdit.text()
|
||||
self.item_updated(self.active_item)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_view_type_changed(self):
|
||||
self.simulator_message_table_model.proto_view = self.ui.cbViewType.currentIndex()
|
||||
self.simulator_message_table_model.update()
|
||||
self.ui.tblViewMessage.resize_columns()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_goto_combobox_index_changed(self):
|
||||
if not isinstance(self.active_item, SimulatorGotoAction):
|
||||
return
|
||||
|
||||
self.active_item.goto_target = None if self.ui.goto_combobox.currentIndex() == 0 else self.ui.goto_combobox.currentText()
|
||||
self.item_updated(self.active_item)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_simulator_scene_selection_changed(self):
|
||||
selected_items = self.simulator_scene.selectedItems()
|
||||
self.active_item = selected_items[0].model_item if selected_items else None
|
||||
|
||||
self.update_ui()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_table_selection_changed(self):
|
||||
selection = self.ui.tblViewMessage.selectionModel().selection()
|
||||
|
||||
if selection.isEmpty():
|
||||
self.active_item = None
|
||||
self.ui.lNumSelectedColumns.setText("0")
|
||||
else:
|
||||
max_row = numpy.max([rng.bottom() for rng in selection])
|
||||
self.active_item = self.simulator_message_table_model.protocol.messages[max_row]
|
||||
_, _, start, end = self.ui.tblViewMessage.selection_range()
|
||||
self.ui.lNumSelectedColumns.setText(str(end - start))
|
||||
|
||||
self.update_ui()
|
||||
|
||||
@property
|
||||
def active_item(self):
|
||||
return self.__active_item
|
||||
|
||||
@active_item.setter
|
||||
def active_item(self, value):
|
||||
self.__active_item = value
|
||||
|
||||
if isinstance(self.active_item, SimulatorGotoAction):
|
||||
self.update_goto_combobox(self.active_item)
|
||||
|
||||
self.ui.detail_view_widget.setCurrentIndex(1)
|
||||
elif isinstance(self.active_item, SimulatorMessage):
|
||||
self.simulator_message_field_model.update()
|
||||
self.ui.spinBoxRepeat.setValue(self.active_item.repeat)
|
||||
self.ui.lblEncodingDecoding.setText(self.active_item.decoder.name)
|
||||
|
||||
self.ui.detail_view_widget.setCurrentIndex(2)
|
||||
elif (isinstance(self.active_item, SimulatorRuleCondition) and
|
||||
self.active_item.type != ConditionType.ELSE):
|
||||
self.ui.ruleCondLineEdit.setText(self.active_item.condition)
|
||||
self.ui.detail_view_widget.setCurrentIndex(3)
|
||||
elif isinstance(self.active_item, SimulatorTriggerCommandAction):
|
||||
self.ui.lineEditTriggerCommand.setText(self.active_item.command)
|
||||
self.ui.checkBoxPassTranscriptSTDIN.setChecked(self.active_item.pass_transcript)
|
||||
self.ui.detail_view_widget.setCurrentIndex(4)
|
||||
elif isinstance(self.active_item, SimulatorSleepAction):
|
||||
self.ui.doubleSpinBoxSleep.setValue(self.active_item.sleep_time)
|
||||
self.ui.detail_view_widget.setCurrentIndex(5)
|
||||
elif isinstance(self.active_item, SimulatorCounterAction):
|
||||
self.ui.spinBoxCounterStart.setValue(self.active_item.start)
|
||||
self.ui.spinBoxCounterStep.setValue(self.active_item.step)
|
||||
self.ui.detail_view_widget.setCurrentIndex(6)
|
||||
else:
|
||||
self.ui.detail_view_widget.setCurrentIndex(0)
|
||||
|
||||
self.update_ui()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_simulate_clicked(self):
|
||||
if not self.simulator_config.protocol_valid():
|
||||
QMessageBox.critical(self, self.tr("Invalid protocol configuration"),
|
||||
self.tr(
|
||||
"There are some problems with your protocol configuration. Please fix them first."))
|
||||
return
|
||||
|
||||
if not len(self.simulator_config.get_all_messages()):
|
||||
QMessageBox.critical(self, self.tr("No messages found"), self.tr("Please add at least one message."))
|
||||
return
|
||||
|
||||
num_simulated = len([p for p in self.project_manager.participants if p.simulate])
|
||||
if num_simulated == 0:
|
||||
if self.ui.listViewSimulate.model().rowCount() == 0:
|
||||
QMessageBox.critical(self, self.tr("No active participants"),
|
||||
self.tr("You have no active participants.<br>"
|
||||
"Please add a participant in the <i>Participants tab</i> and "
|
||||
"assign it to at least one message as <i>source</i> or <i>destination.</i>"))
|
||||
return
|
||||
else:
|
||||
QMessageBox.critical(self, self.tr("No participant for simulation selected"),
|
||||
self.tr("Please check at least one participant from the "
|
||||
"<i>Simulate these participants</i> list."))
|
||||
return
|
||||
|
||||
try:
|
||||
self.get_simulator_dialog().exec_()
|
||||
except Exception as e:
|
||||
Errors.exception(e)
|
||||
|
||||
def get_simulator_dialog(self) -> SimulatorDialog:
|
||||
protos = [p for proto_list in self.tree_model.protocols.values() for p in proto_list]
|
||||
signals = [p.signal for p in protos if p.signal is not None]
|
||||
|
||||
s = SimulatorDialog(self.simulator_config, self.project_manager.modulators,
|
||||
self.sim_expression_parser, self.project_manager, signals=signals,
|
||||
signal_tree_model=self.tree_model, parent=self)
|
||||
|
||||
s.rx_parameters_changed.connect(self.project_manager.on_simulator_rx_parameters_changed)
|
||||
s.sniff_parameters_changed.connect(self.project_manager.on_simulator_sniff_parameters_changed)
|
||||
s.tx_parameters_changed.connect(self.project_manager.on_simulator_tx_parameters_changed)
|
||||
s.open_in_analysis_requested.connect(self.open_in_analysis_requested.emit)
|
||||
s.rx_file_saved.connect(self.rx_file_saved.emit)
|
||||
|
||||
return s
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_choose_command_clicked(self):
|
||||
file_name, ok = QFileDialog.getOpenFileName(self, self.tr("Choose program"), QDir.homePath())
|
||||
|
||||
if file_name is not None and ok:
|
||||
self.ui.lineEditTriggerCommand.setText(file_name)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_trigger_command_text_changed(self):
|
||||
self.active_item.command = self.ui.lineEditTriggerCommand.text()
|
||||
self.item_updated(self.active_item)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_check_box_pass_transcript_STDIN_clicked(self):
|
||||
self.active_item.pass_transcript = self.ui.checkBoxPassTranscriptSTDIN.isChecked()
|
||||
self.item_updated(self.active_item)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_counter_start_editing_finished(self):
|
||||
self.active_item.start = self.ui.spinBoxCounterStart.value()
|
||||
self.item_updated(self.active_item)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_counter_step_editing_finished(self):
|
||||
self.active_item.step = self.ui.spinBoxCounterStep.value()
|
||||
self.item_updated(self.active_item)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_sleep_editing_finished(self):
|
||||
self.active_item.sleep_time = self.ui.doubleSpinBoxSleep.value()
|
||||
self.item_updated(self.active_item)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_participants_changed(self):
|
||||
self.update_vertical_table_header()
|
||||
self.participant_table_model.update()
|
||||
self.ui.listViewSimulate.model().update()
|
||||
|
||||
def item_updated(self, item: SimulatorItem):
|
||||
self.simulator_config.items_updated.emit([item])
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_tree(self):
|
||||
self.ui.treeProtocols.expandAll()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_save_clicked(self):
|
||||
filename = FileOperator.ask_save_file_name(initial_name="myprofile.sim.xml", caption="Save simulator profile")
|
||||
if filename:
|
||||
self.save_simulator_file(filename)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_load_clicked(self):
|
||||
dialog = FileOperator.get_open_dialog(False, parent=self, name_filter="simulator")
|
||||
if dialog.exec_():
|
||||
self.load_simulator_file(dialog.selectedFiles()[0])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_participant_edited(self):
|
||||
self.project_manager.project_updated.emit()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_num_repeat_value_changed(self, value):
|
||||
self.project_manager.simulator_num_repeat = value
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_timeout_value_changed(self, value):
|
||||
self.project_manager.simulator_timeout_ms = value
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_retries_value_changed(self, value):
|
||||
self.project_manager.simulator_retries = value
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_error_handling_index_changed(self, index: int):
|
||||
self.project_manager.simulator_error_handling_index = index
|
||||
|
||||
@pyqtSlot()
|
||||
def on_message_source_or_destination_updated(self):
|
||||
self.simulator_config.update_active_participants()
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
def on_table_item_link_clicked(self, row: int, column: int):
|
||||
try:
|
||||
lbl = self.simulator_message_field_model.message_type[row] # type: SimulatorProtocolLabel
|
||||
assert lbl.is_checksum_label
|
||||
assert isinstance(self.active_item, SimulatorMessage)
|
||||
except (IndexError, AssertionError):
|
||||
return
|
||||
|
||||
d = QDialog(parent=self)
|
||||
layout = QHBoxLayout()
|
||||
layout.addWidget(ChecksumWidget(lbl.label, self.active_item, self.ui.cbViewType.currentIndex()))
|
||||
d.setLayout(layout)
|
||||
d.show()
|
||||
|
||||
@pyqtSlot(Participant)
|
||||
def on_participant_simulate_changed(self, participant: Participant):
|
||||
self.simulator_scene.refresh_participant(participant)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_active_participants_updated(self):
|
||||
self.ui.listViewSimulate.model().update()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_edit_label_triggered(self, label_index: int):
|
||||
view_type = self.ui.cbViewType.currentIndex()
|
||||
protocol_label_dialog = ProtocolLabelDialog(message=self.ui.tblViewMessage.selected_message,
|
||||
viewtype=view_type, selected_index=label_index, parent=self)
|
||||
protocol_label_dialog.finished.connect(self.on_protocol_label_dialog_finished)
|
||||
protocol_label_dialog.showMaximized()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_protocol_label_dialog_finished(self):
|
||||
self.simulator_message_field_model.update()
|
||||
self.simulator_message_table_model.update()
|
||||
self.update_ui()
|
||||
|
||||
@pyqtSlot(list)
|
||||
def on_files_dropped(self, file_urls: list):
|
||||
for filename in (file_url.toLocalFile() for file_url in file_urls if file_url.isLocalFile()):
|
||||
self.load_simulator_file(filename)
|
@ -0,0 +1,38 @@
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt
|
||||
from PyQt5.QtWidgets import QDialog
|
||||
|
||||
from urh.ui.ui_advanced_modulation_settings import Ui_DialogAdvancedModSettings
|
||||
|
||||
|
||||
class AdvancedModulationOptionsDialog(QDialog):
|
||||
pause_threshold_edited = pyqtSignal(int)
|
||||
message_length_divisor_edited = pyqtSignal(int)
|
||||
|
||||
def __init__(self, pause_threshold: int, message_length_divisor: int, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogAdvancedModSettings()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.pause_threshold = pause_threshold
|
||||
self.message_length_divisor = message_length_divisor
|
||||
|
||||
self.ui.spinBoxPauseThreshold.setValue(pause_threshold)
|
||||
self.ui.spinBoxMessageLengthDivisor.setValue(message_length_divisor)
|
||||
|
||||
self.create_connects()
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.buttonBox.accepted.connect(self.on_accept_clicked)
|
||||
self.ui.buttonBox.rejected.connect(self.reject)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_accept_clicked(self):
|
||||
if self.pause_threshold != self.ui.spinBoxPauseThreshold.value():
|
||||
self.pause_threshold_edited.emit(self.ui.spinBoxPauseThreshold.value())
|
||||
|
||||
if self.message_length_divisor != self.ui.spinBoxMessageLengthDivisor.value():
|
||||
self.message_length_divisor_edited.emit(self.ui.spinBoxMessageLengthDivisor.value())
|
||||
|
||||
self.accept()
|
@ -0,0 +1,236 @@
|
||||
import csv
|
||||
|
||||
import os
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtWidgets import QDialog, QInputDialog, QApplication, QCompleter, QDirModel, QFileDialog
|
||||
|
||||
from urh.ui.ui_csv_wizard import Ui_DialogCSVImport
|
||||
from urh.util import FileOperator, util
|
||||
from urh.util.Errors import Errors
|
||||
|
||||
|
||||
class CSVImportDialog(QDialog):
|
||||
data_imported = pyqtSignal(str, float) # Complex Filename + Sample Rate
|
||||
|
||||
|
||||
PREVIEW_ROWS = 100
|
||||
COLUMNS = {"T": 0, "I": 1, "Q": 2}
|
||||
|
||||
def __init__(self, filename="", parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogCSVImport()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.ui.btnAutoDefault.hide()
|
||||
|
||||
completer = QCompleter()
|
||||
completer.setModel(QDirModel(completer))
|
||||
self.ui.lineEditFilename.setCompleter(completer)
|
||||
|
||||
self.filename = None # type: str
|
||||
self.ui.lineEditFilename.setText(filename)
|
||||
self.update_file()
|
||||
|
||||
self.ui.tableWidgetPreview.setColumnHidden(self.COLUMNS["T"], True)
|
||||
self.update_preview()
|
||||
|
||||
self.create_connects()
|
||||
|
||||
def create_connects(self):
|
||||
self.accepted.connect(self.on_accepted)
|
||||
self.ui.lineEditFilename.editingFinished.connect(self.on_line_edit_filename_editing_finished)
|
||||
self.ui.btnChooseFile.clicked.connect(self.on_btn_choose_file_clicked)
|
||||
self.ui.btnAddSeparator.clicked.connect(self.on_btn_add_separator_clicked)
|
||||
self.ui.comboBoxCSVSeparator.currentIndexChanged.connect(self.on_combobox_csv_separator_current_index_changed)
|
||||
self.ui.spinBoxIDataColumn.valueChanged.connect(self.on_spinbox_i_data_column_value_changed)
|
||||
self.ui.spinBoxQDataColumn.valueChanged.connect(self.on_spinbox_q_data_column_value_changed)
|
||||
self.ui.spinBoxTimestampColumn.valueChanged.connect(self.on_spinbox_timestamp_value_changed)
|
||||
|
||||
def update_file(self):
|
||||
filename = self.ui.lineEditFilename.text()
|
||||
self.filename = filename
|
||||
|
||||
enable = util.file_can_be_opened(filename)
|
||||
if enable:
|
||||
with open(self.filename, encoding="utf-8-sig") as f:
|
||||
lines = []
|
||||
for i, line in enumerate(f):
|
||||
if i >= self.PREVIEW_ROWS:
|
||||
break
|
||||
lines.append(line.strip())
|
||||
self.ui.plainTextEditFilePreview.setPlainText("\n".join(lines))
|
||||
else:
|
||||
self.ui.plainTextEditFilePreview.clear()
|
||||
|
||||
self.ui.plainTextEditFilePreview.setEnabled(enable)
|
||||
self.ui.comboBoxCSVSeparator.setEnabled(enable)
|
||||
self.ui.spinBoxIDataColumn.setEnabled(enable)
|
||||
self.ui.spinBoxQDataColumn.setEnabled(enable)
|
||||
self.ui.spinBoxTimestampColumn.setEnabled(enable)
|
||||
self.ui.tableWidgetPreview.setEnabled(enable)
|
||||
self.ui.labelFileNotFound.setVisible(not enable)
|
||||
|
||||
def update_preview(self):
|
||||
if not util.file_can_be_opened(self.filename):
|
||||
self.update_file()
|
||||
return
|
||||
|
||||
i_data_col = self.ui.spinBoxIDataColumn.value() - 1
|
||||
q_data_col = self.ui.spinBoxQDataColumn.value() - 1
|
||||
timestamp_col = self.ui.spinBoxTimestampColumn.value() - 1
|
||||
|
||||
self.ui.tableWidgetPreview.setRowCount(self.PREVIEW_ROWS)
|
||||
|
||||
with open(self.filename, encoding="utf-8-sig") as f:
|
||||
csv_reader = csv.reader(f, delimiter=self.ui.comboBoxCSVSeparator.currentText())
|
||||
row = -1
|
||||
|
||||
for line in csv_reader:
|
||||
row += 1
|
||||
result = self.parse_csv_line(line, i_data_col, q_data_col, timestamp_col)
|
||||
if result is not None:
|
||||
for key, value in result.items():
|
||||
self.ui.tableWidgetPreview.setItem(row, self.COLUMNS[key], util.create_table_item(value))
|
||||
else:
|
||||
for col in self.COLUMNS.values():
|
||||
self.ui.tableWidgetPreview.setItem(row, col, util.create_table_item("Invalid"))
|
||||
|
||||
if row >= self.PREVIEW_ROWS - 1:
|
||||
break
|
||||
|
||||
self.ui.tableWidgetPreview.setRowCount(row + 1)
|
||||
|
||||
@staticmethod
|
||||
def parse_csv_line(csv_line: str, i_data_col: int, q_data_col: int, timestamp_col: int):
|
||||
result = dict()
|
||||
|
||||
if i_data_col >= 0:
|
||||
try:
|
||||
result["I"] = float(csv_line[i_data_col])
|
||||
except:
|
||||
return None
|
||||
else:
|
||||
result["I"] = 0.0
|
||||
|
||||
if q_data_col >= 0:
|
||||
try:
|
||||
result["Q"] = float(csv_line[q_data_col])
|
||||
except:
|
||||
return None
|
||||
else:
|
||||
result["Q"] = 0.0
|
||||
|
||||
if timestamp_col >= 0:
|
||||
try:
|
||||
result["T"] = float(csv_line[timestamp_col])
|
||||
except:
|
||||
return None
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def parse_csv_file(filename: str, separator: str, i_data_col: int, q_data_col=-1, t_data_col=-1):
|
||||
iq_data = []
|
||||
timestamps = [] if t_data_col > -1 else None
|
||||
with open(filename, encoding="utf-8-sig") as f:
|
||||
csv_reader = csv.reader(f, delimiter=separator)
|
||||
for line in csv_reader:
|
||||
parsed = CSVImportDialog.parse_csv_line(line, i_data_col, q_data_col, t_data_col)
|
||||
if parsed is None:
|
||||
continue
|
||||
|
||||
iq_data.append(complex(parsed["I"], parsed["Q"]))
|
||||
if timestamps is not None:
|
||||
timestamps.append(parsed["T"])
|
||||
|
||||
iq_data = np.asarray(iq_data, dtype=np.complex64)
|
||||
sample_rate = CSVImportDialog.estimate_sample_rate(timestamps)
|
||||
return iq_data / abs(iq_data.max()), sample_rate
|
||||
|
||||
@staticmethod
|
||||
def estimate_sample_rate(timestamps):
|
||||
if timestamps is None or len(timestamps) < 2:
|
||||
return None
|
||||
|
||||
previous_timestamp = timestamps[0]
|
||||
durations = []
|
||||
|
||||
for timestamp in timestamps[1:CSVImportDialog.PREVIEW_ROWS]:
|
||||
durations.append(abs(timestamp-previous_timestamp))
|
||||
previous_timestamp = timestamp
|
||||
|
||||
return 1 / (sum(durations) / len(durations))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_filename_editing_finished(self):
|
||||
self.update_file()
|
||||
self.update_preview()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_choose_file_clicked(self):
|
||||
filename, _ = QFileDialog.getOpenFileName(self, self.tr("Choose file"), directory=FileOperator.RECENT_PATH,
|
||||
filter="CSV files (*.csv);;All files (*.*)")
|
||||
|
||||
if filename:
|
||||
self.ui.lineEditFilename.setText(filename)
|
||||
self.ui.lineEditFilename.editingFinished.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_add_separator_clicked(self):
|
||||
sep, ok = QInputDialog.getText(self, "Enter Separator", "Separator:", text=",")
|
||||
if ok and sep not in (self.ui.comboBoxCSVSeparator.itemText(i) for i in
|
||||
range(self.ui.comboBoxCSVSeparator.count())):
|
||||
if len(sep) == 1:
|
||||
self.ui.comboBoxCSVSeparator.addItem(sep)
|
||||
else:
|
||||
Errors.generic_error("Invalid Separator", "Separator must be exactly one character.")
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_csv_separator_current_index_changed(self, index: int):
|
||||
self.update_preview()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_i_data_column_value_changed(self, value: int):
|
||||
self.update_preview()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_q_data_column_value_changed(self, value: int):
|
||||
self.update_preview()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_timestamp_value_changed(self, value: int):
|
||||
self.ui.tableWidgetPreview.setColumnHidden(self.COLUMNS["T"], value == 0)
|
||||
self.update_preview()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_accepted(self):
|
||||
QApplication.setOverrideCursor(Qt.WaitCursor)
|
||||
|
||||
iq_data, sample_rate = self.parse_csv_file(self.filename, self.ui.comboBoxCSVSeparator.currentText(),
|
||||
self.ui.spinBoxIDataColumn.value()-1,
|
||||
self.ui.spinBoxQDataColumn.value()-1,
|
||||
self.ui.spinBoxTimestampColumn.value()-1)
|
||||
|
||||
target_filename = self.filename.rstrip(".csv")
|
||||
if os.path.exists(target_filename + ".complex"):
|
||||
i = 1
|
||||
while os.path.exists(target_filename + "_" + str(i) + ".complex"):
|
||||
i += 1
|
||||
else:
|
||||
i = None
|
||||
|
||||
target_filename = target_filename if not i else target_filename + "_" + str(i)
|
||||
target_filename += ".complex"
|
||||
|
||||
iq_data.tofile(target_filename)
|
||||
|
||||
self.data_imported.emit(target_filename, sample_rate if sample_rate is not None else 0)
|
||||
QApplication.restoreOverrideCursor()
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication(["urh"])
|
||||
csv_dia = CSVImportDialog()
|
||||
csv_dia.exec_()
|
@ -0,0 +1,111 @@
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtGui import QCloseEvent
|
||||
|
||||
from urh.controller.dialogs.SendDialog import SendDialog
|
||||
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
|
||||
from urh.dev.VirtualDevice import VirtualDevice, Mode
|
||||
from urh.signalprocessing.ContinuousModulator import ContinuousModulator
|
||||
from urh.ui.painting.ContinuousSceneManager import ContinuousSceneManager
|
||||
|
||||
|
||||
class ContinuousSendDialog(SendDialog):
|
||||
def __init__(self, project_manager, messages, modulators, total_samples: int, parent, testing_mode=False):
|
||||
super().__init__(project_manager, modulated_data=None, modulation_msg_indices=None,
|
||||
continuous_send_mode=True, parent=parent, testing_mode=testing_mode)
|
||||
self.messages = messages
|
||||
self.modulators = modulators
|
||||
|
||||
self.graphics_view = self.ui.graphicsViewContinuousSend
|
||||
self.ui.stackedWidget.setCurrentWidget(self.ui.page_continuous_send)
|
||||
self.ui.progressBarSample.hide()
|
||||
self.ui.lSamplesSentText.hide()
|
||||
|
||||
self.total_samples = total_samples
|
||||
self.ui.progressBarMessage.setMaximum(len(messages))
|
||||
|
||||
num_repeats = self.device_settings_widget.ui.spinBoxNRepeat.value()
|
||||
self.continuous_modulator = ContinuousModulator(messages, modulators, num_repeats=num_repeats)
|
||||
self.scene_manager = ContinuousSceneManager(ring_buffer=self.continuous_modulator.ring_buffer, parent=self)
|
||||
self.scene_manager.init_scene()
|
||||
self.graphics_view.setScene(self.scene_manager.scene)
|
||||
self.graphics_view.scene_manager = self.scene_manager
|
||||
|
||||
self.setWindowTitle("Send Signal (continuous mode)")
|
||||
self.ui.lSamplesSentText.setText("Progress:")
|
||||
|
||||
self.create_connects()
|
||||
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
|
||||
|
||||
def create_connects(self):
|
||||
SendRecvDialog.create_connects(self)
|
||||
|
||||
def _update_send_indicator(self, width: int):
|
||||
pass
|
||||
|
||||
def update_view(self):
|
||||
super().update_view()
|
||||
self.ui.progressBarMessage.setValue(self.continuous_modulator.current_message_index.value + 1)
|
||||
self.scene_manager.init_scene()
|
||||
self.scene_manager.show_full_scene()
|
||||
self.graphics_view.update()
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
self.continuous_modulator.stop()
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_started(self):
|
||||
super().on_device_started()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_stopped(self):
|
||||
super().on_device_stopped()
|
||||
self.continuous_modulator.stop(clear_buffer=False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_stop_clicked(self):
|
||||
super().on_stop_clicked()
|
||||
self.continuous_modulator.stop()
|
||||
self.continuous_modulator.current_message_index.value = 0
|
||||
self.scene_manager.clear_path()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_start_clicked(self):
|
||||
self.device_settings_widget.ui.spinBoxNRepeat.editingFinished.emit() # inform continuous modulator
|
||||
if not self.continuous_modulator.is_running:
|
||||
self.continuous_modulator.start()
|
||||
super().on_start_clicked()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_clear_clicked(self):
|
||||
self.continuous_modulator.stop()
|
||||
self.continuous_modulator.current_message_index.value = 0
|
||||
self.scene_manager.clear_path()
|
||||
self.reset()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_num_repeats_changed(self):
|
||||
super().on_num_repeats_changed()
|
||||
self.continuous_modulator.num_repeats = self.device_settings_widget.ui.spinBoxNRepeat.value()
|
||||
|
||||
def on_selected_device_changed(self):
|
||||
self.ui.txtEditErrors.clear()
|
||||
super().on_selected_device_changed()
|
||||
|
||||
def init_device(self):
|
||||
device_name = self.selected_device_name
|
||||
num_repeats = self.device_settings_widget.ui.spinBoxNRepeat.value()
|
||||
|
||||
self.device = VirtualDevice(self.backend_handler, device_name, Mode.send,
|
||||
device_ip="192.168.10.2", sending_repeats=num_repeats, parent=self)
|
||||
self.ui.btnStart.setEnabled(True)
|
||||
|
||||
try:
|
||||
self.device.is_send_continuous = True
|
||||
self.device.continuous_send_ring_buffer = self.continuous_modulator.ring_buffer
|
||||
self.device.num_samples_to_send = self.total_samples
|
||||
|
||||
self._create_device_connects()
|
||||
except ValueError as e:
|
||||
self.ui.txtEditErrors.setText("<font color='red'>" + str(e) + "<font>")
|
||||
self.ui.btnStart.setEnabled(False)
|
@ -0,0 +1,27 @@
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtWidgets import QDialog
|
||||
|
||||
from urh.ui.ui_costa import Ui_DialogCosta
|
||||
|
||||
|
||||
class CostaOptionsDialog(QDialog):
|
||||
def __init__(self, loop_bandwidth, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogCosta()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.costas_loop_bandwidth = loop_bandwidth
|
||||
self.ui.doubleSpinBoxLoopBandwidth.setValue(self.costas_loop_bandwidth)
|
||||
|
||||
self.create_connects()
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.buttonBox.accepted.connect(self.accept)
|
||||
self.ui.buttonBox.rejected.connect(self.reject)
|
||||
self.ui.doubleSpinBoxLoopBandwidth.valueChanged.connect(self.on_spinbox_loop_bandwidth_value_changed)
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_spinbox_loop_bandwidth_value_changed(self, value):
|
||||
self.costas_loop_bandwidth = value
|
@ -0,0 +1,872 @@
|
||||
import copy
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QDir, Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QCloseEvent, QDropEvent, QDragEnterEvent, QIcon
|
||||
from PyQt5.QtWidgets import QDialog, QTableWidgetItem, QFileDialog, QInputDialog, \
|
||||
QLineEdit, QMessageBox
|
||||
|
||||
from urh import settings
|
||||
from urh.signalprocessing.Encoding import Encoding
|
||||
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
|
||||
from urh.signalprocessing.Signal import Signal
|
||||
from urh.ui.painting.SignalSceneManager import SignalSceneManager
|
||||
from urh.ui.ui_decoding import Ui_Decoder
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class DecoderDialog(QDialog):
|
||||
def __init__(self, decodings, signals, project_manager: ProjectManager,
|
||||
parent=None):
|
||||
"""
|
||||
:type decodings: list of Encoding
|
||||
:type signals: list of Signal
|
||||
"""
|
||||
# Init
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_Decoder()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
# Variables
|
||||
self.old_inpt_txt = ""
|
||||
self.old_carrier_txt = ""
|
||||
self.old_decoderchain = []
|
||||
self.active_message = ""
|
||||
self.old_cutmark = ""
|
||||
self.old_morse = (1, 3)
|
||||
|
||||
self.project_manager = project_manager
|
||||
|
||||
# Initialize encoder
|
||||
self.decodings = decodings
|
||||
self.ui.combobox_decodings.clear()
|
||||
for decoding in self.decodings:
|
||||
self.ui.combobox_decodings.addItem(decoding.name)
|
||||
self.chainstr = []
|
||||
self.chainoptions = {}
|
||||
self.set_e()
|
||||
|
||||
self.last_selected_item = ""
|
||||
|
||||
# Signals
|
||||
self.signals = signals if signals is not None else []
|
||||
for signal in signals:
|
||||
if signal:
|
||||
self.ui.combobox_signals.addItem(signal.name)
|
||||
|
||||
# Function lists
|
||||
self.ui.basefunctions.addItem(settings.DECODING_EDGE)
|
||||
self.ui.basefunctions.addItem(settings.DECODING_MORSE)
|
||||
self.ui.basefunctions.addItem(settings.DECODING_SUBSTITUTION)
|
||||
self.ui.basefunctions.addItem(settings.DECODING_EXTERNAL)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_INVERT)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_DIFFERENTIAL)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_BITORDER)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_REDUNDANCY)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_CARRIER)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_DATAWHITENING)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_ENOCEAN)
|
||||
self.ui.additionalfunctions.addItem(settings.DECODING_CUT)
|
||||
|
||||
# Presets
|
||||
self.setWindowTitle("Decoding")
|
||||
self.setWindowIcon(QIcon(":/icons/icons/decoding.svg"))
|
||||
self.setAcceptDrops(True)
|
||||
self.inpt_text = "10010110"
|
||||
self.ui.inpt.setText(self.inpt_text)
|
||||
self.ui.optionWidget.setCurrentIndex(0)
|
||||
self.decoder_update()
|
||||
|
||||
self.ui.substitution.setColumnCount(2)
|
||||
self.ui.substitution.setRowCount(self.ui.substitution_rows.value())
|
||||
self.ui.substitution.setHorizontalHeaderLabels(['From', 'To'])
|
||||
self.ui.substitution.setColumnWidth(0, 190)
|
||||
self.ui.substitution.setColumnWidth(1, 190)
|
||||
|
||||
self.ui.btnAddtoYourDecoding.hide()
|
||||
self.ui.saveas.setVisible(False)
|
||||
|
||||
# Connects
|
||||
self.create_connects()
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.inpt.textChanged.connect(self.decoder_update)
|
||||
self.ui.multiple.valueChanged.connect(self.handle_multiple_changed)
|
||||
self.ui.carrier.textChanged.connect(self.handle_carrier_changed)
|
||||
self.ui.substitution_rows.valueChanged.connect(self.handle_substitution_rows_changed)
|
||||
self.ui.substitution.itemChanged.connect(self.handle_substitution_changed)
|
||||
|
||||
self.ui.btnChooseDecoder.clicked.connect(self.choose_decoder)
|
||||
self.ui.btnChooseEncoder.clicked.connect(self.choose_encoder)
|
||||
|
||||
self.ui.external_decoder.textEdited.connect(self.handle_external)
|
||||
self.ui.external_encoder.textEdited.connect(self.handle_external)
|
||||
self.ui.datawhitening_sync.textEdited.connect(self.handle_datawhitening)
|
||||
self.ui.datawhitening_polynomial.textEdited.connect(self.handle_datawhitening)
|
||||
self.ui.datawhitening_overwrite_crc.clicked.connect(self.handle_datawhitening)
|
||||
|
||||
self.ui.decoderchain.itemChanged.connect(self.decoderchainUpdate)
|
||||
self.ui.decoderchain.internalMove.connect(self.decoderchainUpdate)
|
||||
self.ui.decoderchain.deleteElement.connect(self.deleteElement)
|
||||
self.ui.decoderchain.currentRowChanged.connect(self.on_decoder_chain_current_row_changed)
|
||||
self.ui.basefunctions.currentRowChanged.connect(self.on_base_functions_current_row_changed)
|
||||
self.ui.additionalfunctions.currentRowChanged.connect(self.on_additional_functions_current_row_changed)
|
||||
self.ui.btnAddtoYourDecoding.clicked.connect(self.on_btn_add_to_your_decoding_clicked)
|
||||
|
||||
self.ui.combobox_decodings.currentIndexChanged.connect(self.set_e)
|
||||
self.ui.combobox_signals.currentIndexChanged.connect(self.set_signal)
|
||||
self.ui.saveas.clicked.connect(self.saveas)
|
||||
self.ui.delete_decoding.clicked.connect(self.delete_decoding)
|
||||
|
||||
self.ui.rB_delbefore.clicked.connect(self.handle_cut)
|
||||
self.ui.rB_delafter.clicked.connect(self.handle_cut)
|
||||
self.ui.rB_delbeforepos.clicked.connect(self.handle_cut)
|
||||
self.ui.rB_delafterpos.clicked.connect(self.handle_cut)
|
||||
self.ui.cutmark.textEdited.connect(self.handle_cut)
|
||||
self.ui.cutmark2.valueChanged.connect(self.handle_cut)
|
||||
|
||||
self.ui.morse_low.valueChanged.connect(self.handle_morse_changed)
|
||||
self.ui.morse_high.valueChanged.connect(self.handle_morse_changed)
|
||||
self.ui.morse_wait.valueChanged.connect(self.handle_morse_changed)
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
def choose_decoder(self):
|
||||
f, ok = QFileDialog.getOpenFileName(self, self.tr("Choose decoder program"), QDir.homePath())
|
||||
if f and ok:
|
||||
self.ui.external_decoder.setText(f)
|
||||
self.handle_external()
|
||||
|
||||
def choose_encoder(self):
|
||||
f, ok = QFileDialog.getOpenFileName(self, self.tr("Choose encoder program"), QDir.homePath())
|
||||
if f and ok:
|
||||
self.ui.external_encoder.setText(f)
|
||||
self.handle_external()
|
||||
|
||||
def save_to_file(self):
|
||||
if self.project_manager.project_file:
|
||||
self.project_manager.decodings = self.decodings
|
||||
else:
|
||||
prefix = os.path.realpath(os.path.join(settings.get_qt_settings_filename(), ".."))
|
||||
with open(os.path.join(prefix, settings.DECODINGS_FILE), "w") as f:
|
||||
for i in range(0, self.ui.combobox_decodings.count()):
|
||||
str = ""
|
||||
for j in self.decodings[i].get_chain():
|
||||
str += repr(j) + ", "
|
||||
str += "\n"
|
||||
f.write(str)
|
||||
|
||||
def saveas(self):
|
||||
# Ask for a name
|
||||
name, ok = QInputDialog.getText(self, self.tr("Save decoding"),
|
||||
self.tr("Please enter a name:"), QLineEdit.Normal, self.e.chain[0])
|
||||
|
||||
if ok and name != "":
|
||||
self.e.chain[0] = name
|
||||
self.decoderchainUpdate()
|
||||
|
||||
# If name is already there, overwrite existing
|
||||
for i in range(0, len(self.decodings)):
|
||||
if name == self.decodings[i].name:
|
||||
self.ui.combobox_decodings.setCurrentIndex(i)
|
||||
self.decodings[i] = Encoding(self.chainstr)
|
||||
self.set_e()
|
||||
self.ui.saveas.setVisible(False)
|
||||
self.save_to_file()
|
||||
return
|
||||
|
||||
self.decodings.append(Encoding(self.chainstr))
|
||||
self.ui.combobox_decodings.addItem(self.chainstr[0])
|
||||
self.ui.combobox_decodings.setCurrentIndex(self.ui.combobox_decodings.count() - 1)
|
||||
self.set_e()
|
||||
self.save_to_file()
|
||||
|
||||
def delete_decoding(self):
|
||||
num = self.ui.combobox_decodings.currentIndex()
|
||||
if num >= 0:
|
||||
reply = QMessageBox.question(self, self.tr("Delete Decoding?"),
|
||||
self.tr("Do you really want to delete " + "'{}'?".format(
|
||||
self.decodings[num].name)),
|
||||
QMessageBox.Yes | QMessageBox.No)
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
self.decodings.pop(num)
|
||||
self.ui.combobox_decodings.removeItem(num)
|
||||
self.save_to_file()
|
||||
|
||||
def set_e(self):
|
||||
if self.ui.combobox_decodings.count() < 1: # Empty list
|
||||
return
|
||||
|
||||
self.e = copy.deepcopy(self.decodings[self.ui.combobox_decodings.currentIndex()])
|
||||
""":type: encoding """
|
||||
chain = self.e.get_chain()
|
||||
self.ui.decoderchain.clear()
|
||||
self.chainoptions.clear()
|
||||
last_i = ""
|
||||
for i in chain:
|
||||
if i in [settings.DECODING_INVERT, settings.DECODING_ENOCEAN, settings.DECODING_DIFFERENTIAL,
|
||||
settings.DECODING_REDUNDANCY, settings.DECODING_CARRIER, settings.DECODING_BITORDER,
|
||||
settings.DECODING_EDGE, settings.DECODING_DATAWHITENING, settings.DECODING_SUBSTITUTION,
|
||||
settings.DECODING_EXTERNAL, settings.DECODING_CUT, settings.DECODING_MORSE,
|
||||
settings.DECODING_DISABLED_PREFIX]:
|
||||
self.ui.decoderchain.addItem(i)
|
||||
self.decoderchainUpdate()
|
||||
last_i = self.ui.decoderchain.item(self.ui.decoderchain.count() - 1).text()
|
||||
else:
|
||||
if any(x in last_i for x in [settings.DECODING_REDUNDANCY, settings.DECODING_CARRIER,
|
||||
settings.DECODING_SUBSTITUTION, settings.DECODING_EXTERNAL,
|
||||
settings.DECODING_DATAWHITENING, settings.DECODING_CUT,
|
||||
settings.DECODING_MORSE]):
|
||||
self.chainoptions[last_i] = i
|
||||
|
||||
self.decoderchainUpdate()
|
||||
self.decoder_update()
|
||||
self.ui.saveas.setVisible(False)
|
||||
|
||||
def decoderchainUpdate(self):
|
||||
# for i in range (0, self.ui.decoderchain.count()):
|
||||
# print(i, "->", self.ui.decoderchain.item(i).text())
|
||||
# print()
|
||||
self.ui.saveas.setVisible(True)
|
||||
self.eliminateDuplicates()
|
||||
self.chainstr = [self.e.name]
|
||||
for i in range(0, self.ui.decoderchain.count()):
|
||||
op = self.ui.decoderchain.item(i).text()
|
||||
|
||||
# Is this function disabled?
|
||||
if settings.DECODING_DISABLED_PREFIX in op:
|
||||
continue
|
||||
|
||||
self.chainstr.append(op)
|
||||
|
||||
# Add parameters to chainstr
|
||||
if settings.DECODING_REDUNDANCY in op:
|
||||
# Read Multiple Value
|
||||
if op in self.chainoptions:
|
||||
self.chainstr.append(self.chainoptions[op])
|
||||
else:
|
||||
self.chainoptions[op] = 2
|
||||
self.chainstr.append(2) # Default
|
||||
elif settings.DECODING_CARRIER in op:
|
||||
# Read Carrier Field and add string to chainstr
|
||||
if op in self.chainoptions:
|
||||
self.chainstr.append(self.chainoptions[op])
|
||||
else:
|
||||
self.chainoptions[op] = ""
|
||||
self.chainstr.append("") # Default
|
||||
elif settings.DECODING_SUBSTITUTION in op:
|
||||
# Add substitution string to chainstr: Format = src0:dst0;src1:dst1;...
|
||||
if op in self.chainoptions:
|
||||
self.chainstr.append(self.chainoptions[op])
|
||||
else:
|
||||
self.chainoptions[op] = ""
|
||||
self.chainstr.append("") # Default
|
||||
elif settings.DECODING_EXTERNAL in op:
|
||||
# Add program path's string to chainstr: Format = decoder;encoder
|
||||
if op in self.chainoptions:
|
||||
self.chainstr.append(self.chainoptions[op])
|
||||
else:
|
||||
self.chainoptions[op] = ""
|
||||
self.chainstr.append("") # Default
|
||||
elif settings.DECODING_DATAWHITENING in op:
|
||||
# Add Data Whitening Parameters
|
||||
if op in self.chainoptions:
|
||||
self.chainstr.append(self.chainoptions[op])
|
||||
else:
|
||||
self.chainoptions[op] = ""
|
||||
self.chainstr.append("0xe9cae9ca;0x21;0") # Default
|
||||
elif settings.DECODING_CUT in op:
|
||||
# Add cut parameters
|
||||
if op in self.chainoptions:
|
||||
self.chainstr.append(self.chainoptions[op])
|
||||
else:
|
||||
self.chainoptions[op] = ""
|
||||
self.chainstr.append("0;1010") # Default
|
||||
elif settings.DECODING_MORSE in op:
|
||||
# Add morse parameters
|
||||
if op in self.chainoptions:
|
||||
self.chainstr.append(self.chainoptions[op])
|
||||
else:
|
||||
self.chainoptions[op] = ""
|
||||
self.chainstr.append("1;3;1") # Default
|
||||
|
||||
self.e.set_chain(self.chainstr)
|
||||
self.decoder_update()
|
||||
|
||||
def deleteElement(self):
|
||||
if self.ui.decoderchain.count() == 0: # Clear all
|
||||
self.chainoptions.clear()
|
||||
else:
|
||||
self.chainoptions.pop(self.ui.decoderchain.active_element_text, None)
|
||||
self.decoderchainUpdate()
|
||||
|
||||
def eliminateDuplicates(self):
|
||||
decoderchain_count = self.ui.decoderchain.count()
|
||||
olddecoderchain_count = len(self.old_decoderchain)
|
||||
|
||||
# Special case for 1 element (add " ")
|
||||
if decoderchain_count == 1:
|
||||
tmp = self.ui.decoderchain.item(0).text()
|
||||
if tmp[-1] != " " and not tmp[-1].isnumeric():
|
||||
self.ui.decoderchain.takeItem(0)
|
||||
self.ui.decoderchain.insertItem(0, tmp + " ")
|
||||
|
||||
# Ignore internal move (same count()) and removed elements and lists < 2 // len(self.old_decoderchain)+1 == self.ui.decoderchain.count()
|
||||
if decoderchain_count > 1 and decoderchain_count > olddecoderchain_count:
|
||||
elem = 0
|
||||
while elem < olddecoderchain_count:
|
||||
if self.ui.decoderchain.item(elem).text() == self.old_decoderchain[elem]:
|
||||
elem += 1
|
||||
else:
|
||||
break
|
||||
|
||||
# Count number of current elements and append string "#<num>" to current text, if num > 1
|
||||
txt = self.ui.decoderchain.item(elem).text()
|
||||
num = 0
|
||||
for i in range(0, decoderchain_count):
|
||||
if txt in self.ui.decoderchain.item(i).text():
|
||||
num += 1
|
||||
if num > 1:
|
||||
tmp_txt = txt + " #" + str(num)
|
||||
else:
|
||||
tmp_txt = txt + " "
|
||||
|
||||
# Check duplicate names
|
||||
dup = False
|
||||
for i in range(0, decoderchain_count):
|
||||
if self.ui.decoderchain.item(i).text() == tmp_txt:
|
||||
dup = True
|
||||
break
|
||||
|
||||
if dup:
|
||||
for i in range(1, num):
|
||||
if i > 1:
|
||||
tmp_txt = txt + " #" + str(i)
|
||||
else:
|
||||
tmp_txt = txt + " "
|
||||
|
||||
dup = False
|
||||
for j in range(0, decoderchain_count):
|
||||
if self.ui.decoderchain.item(j).text() == tmp_txt:
|
||||
dup = True
|
||||
break
|
||||
if not dup:
|
||||
break
|
||||
|
||||
# Replace current element with new "text #<num>"
|
||||
if not dup:
|
||||
self.ui.decoderchain.takeItem(elem)
|
||||
self.ui.decoderchain.insertItem(elem, tmp_txt)
|
||||
|
||||
# Save current decoderchain to old_decoderchain
|
||||
self.old_decoderchain = []
|
||||
for i in range(0, decoderchain_count):
|
||||
self.old_decoderchain.append(self.ui.decoderchain.item(i).text())
|
||||
|
||||
def decoder_update(self):
|
||||
# Only allow {0, 1}
|
||||
signaltype = self.ui.combobox_signals.currentIndex()
|
||||
inpt_txt = self.ui.inpt.text()
|
||||
if signaltype == 0:
|
||||
if inpt_txt.count("0") + inpt_txt.count("1") < len(inpt_txt):
|
||||
self.ui.inpt.setText(self.old_inpt_txt)
|
||||
else:
|
||||
self.old_inpt_txt = inpt_txt
|
||||
|
||||
# Write decoded bits
|
||||
bit = self.e.str2bit(self.ui.inpt.text())
|
||||
decoded = self.e.bit2str(self.e.decode(bit))
|
||||
errors = "[Decoding Errors = " + str(self.e.analyze(bit)[0]) + "]"
|
||||
self.ui.decoding_errors_label.setText(errors)
|
||||
self.ui.output.setText(decoded)
|
||||
self.ui.output.setCursorPosition(0)
|
||||
|
||||
if len(decoded) > 0:
|
||||
if signaltype == 0:
|
||||
temp_signal = SignalSceneManager.create_rectangle(inpt_txt)[0]
|
||||
self.ui.graphicsView_signal.setScene(temp_signal)
|
||||
self.ui.graphicsView_signal.update()
|
||||
|
||||
temp_decoded = SignalSceneManager.create_rectangle(decoded)[0]
|
||||
self.ui.graphicsView_decoded.setScene(temp_decoded)
|
||||
self.ui.graphicsView_decoded.update()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_base_functions_current_row_changed(self, index: int):
|
||||
if self.ui.basefunctions.currentItem().text() is not None:
|
||||
self.ui.decoderchain.setCurrentRow(-1)
|
||||
self.set_information(0)
|
||||
else:
|
||||
self.ui.optionWidget.setCurrentIndex(0)
|
||||
self.ui.info.clear()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_additional_functions_current_row_changed(self, index: int):
|
||||
if self.ui.additionalfunctions.currentItem() is not None:
|
||||
self.ui.decoderchain.setCurrentRow(-1)
|
||||
self.set_information(1)
|
||||
else:
|
||||
self.ui.optionWidget.setCurrentIndex(0)
|
||||
self.ui.info.clear()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_decoder_chain_current_row_changed(self, index: int):
|
||||
if self.ui.decoderchain.currentItem() is not None:
|
||||
self.set_information(2)
|
||||
else:
|
||||
self.ui.optionWidget.setCurrentIndex(0)
|
||||
self.ui.info.clear()
|
||||
|
||||
def set_information(self, mode: int):
|
||||
# Presets
|
||||
decoderEdit = False
|
||||
self.ui.optionWidget.setCurrentIndex(0)
|
||||
txt = ""
|
||||
|
||||
# Determine selected element
|
||||
if mode == 0:
|
||||
element = self.ui.basefunctions.currentItem().text()
|
||||
txt += element + ":\n"
|
||||
self.last_selected_item = element
|
||||
self.ui.btnAddtoYourDecoding.show()
|
||||
elif mode == 1:
|
||||
element = self.ui.additionalfunctions.currentItem().text()
|
||||
txt += element + ":\n"
|
||||
self.last_selected_item = element
|
||||
self.ui.btnAddtoYourDecoding.show()
|
||||
elif mode == 2:
|
||||
decoderEdit = True
|
||||
txt = "## In Your Decoding ##\n\n"
|
||||
element = self.ui.decoderchain.currentItem().text()
|
||||
if element[-1] == " ":
|
||||
elementname = element[0:-1]
|
||||
else:
|
||||
elementname = element
|
||||
txt += elementname + ":\n"
|
||||
self.active_message = element
|
||||
self.ui.btnAddtoYourDecoding.hide()
|
||||
|
||||
# Remove "[Disabled] " for further tasks
|
||||
if settings.DECODING_DISABLED_PREFIX in element:
|
||||
element = element[len(settings.DECODING_DISABLED_PREFIX):]
|
||||
|
||||
# Write info text and show options
|
||||
if settings.DECODING_EDGE in element:
|
||||
txt += "Trigger on signal edge, i.e. the transition between low and high.\n" \
|
||||
"- Low to High (01) is 1\n" \
|
||||
"- High to Low (10) is 0"
|
||||
elif settings.DECODING_SUBSTITUTION in element:
|
||||
txt += "A set of manual defined signal sequences FROM (e.g. 110, 100) is replaced by another set of " \
|
||||
"sequences TO (e.g. 01, 10). Note that all FROM entries must have the same length, otherwise " \
|
||||
"the result is unpredictable! (For TX: all TO entries must have the same length)"
|
||||
self.ui.optionWidget.setCurrentIndex(3)
|
||||
# Values can only be changed when editing decoder, otherwise default value
|
||||
if not decoderEdit:
|
||||
self.ui.substitution_rows.setValue(4)
|
||||
self.ui.substitution.setRowCount(0)
|
||||
self.ui.substitution.setRowCount(4)
|
||||
else:
|
||||
if element in self.chainoptions:
|
||||
values = self.chainoptions[element]
|
||||
if values == "":
|
||||
self.ui.substitution_rows.setValue(4)
|
||||
self.ui.substitution.setRowCount(0)
|
||||
self.ui.substitution.setRowCount(4)
|
||||
else:
|
||||
arrs = self.e.get_subst_array(values)
|
||||
if len(arrs[0]) == len(arrs[1]):
|
||||
self.ui.substitution_rows.setValue(len(arrs[0]))
|
||||
self.ui.substitution.setRowCount(len(arrs[0]))
|
||||
for i in range(0, len(arrs[0])):
|
||||
self.ui.substitution.setItem(i, 0, QTableWidgetItem(self.e.bit2str(arrs[0][i])))
|
||||
self.ui.substitution.setItem(i, 1, QTableWidgetItem(self.e.bit2str(arrs[1][i])))
|
||||
else:
|
||||
self.ui.substitution_rows.setValue(4)
|
||||
self.ui.substitution.setRowCount(0)
|
||||
self.ui.substitution.setRowCount(4)
|
||||
self.ui.substitution.setEnabled(decoderEdit)
|
||||
self.ui.substitution_rows.setEnabled(decoderEdit)
|
||||
|
||||
elif settings.DECODING_EXTERNAL in element:
|
||||
txt += "The decoding (and encoding) process is delegated to external programs or scripts via parameter.\n" \
|
||||
"Example: Given the signal 10010110, your program is called as './decoder 10010110'. Your program " \
|
||||
"computes and prints a corresponding set of 0s and 1s which is fed back into the decoding process. "
|
||||
self.ui.optionWidget.setCurrentIndex(4)
|
||||
# Values can only be changed when editing decoder, otherwise default value
|
||||
if not decoderEdit:
|
||||
self.ui.external_decoder.setText("")
|
||||
self.ui.external_encoder.setText("")
|
||||
else:
|
||||
if element in self.chainoptions:
|
||||
value = self.chainoptions[element]
|
||||
if value == "":
|
||||
self.ui.external_decoder.setText("")
|
||||
self.ui.external_encoder.setText("")
|
||||
else:
|
||||
decstr, encstr = value.split(";")
|
||||
self.ui.external_decoder.setText(decstr)
|
||||
self.ui.external_encoder.setText(encstr)
|
||||
else:
|
||||
self.ui.external_decoder.setText("")
|
||||
self.ui.external_encoder.setText("")
|
||||
self.ui.external_decoder.setEnabled(decoderEdit)
|
||||
self.ui.external_encoder.setEnabled(decoderEdit)
|
||||
self.ui.btnChooseDecoder.setEnabled(decoderEdit)
|
||||
self.ui.btnChooseEncoder.setEnabled(decoderEdit)
|
||||
|
||||
elif settings.DECODING_INVERT in element:
|
||||
txt += "All bits are inverted, i.e. 0->1 and 1->0."
|
||||
elif settings.DECODING_ENOCEAN in element:
|
||||
txt += "Remove Wireless Short-Packet (WSP) encoding that is used by EnOcean standard."
|
||||
elif settings.DECODING_DIFFERENTIAL in element:
|
||||
txt += "Every transition between low and high (0->1 or 1->0) becomes 1, no transition (0->0 or 1->1) remains 0.\n" \
|
||||
"The first signal bit is regarded as start value and directly copied.\n" \
|
||||
"Example: 0011 becomes 0010 [0|(0->0)|(0->1)|(1->1)]."
|
||||
elif settings.DECODING_BITORDER in element:
|
||||
txt += "Every byte (8 bit) is reversed, i.e. the order of the bits 01234567 (e.g. least significant bit first) " \
|
||||
"is changed to 76543210 (e.g. most significant bit first)."
|
||||
elif settings.DECODING_REDUNDANCY in element:
|
||||
txt += "If the source signal always has multiple redundant bits for one bit (e.g. 1111=1, 0000=0), the " \
|
||||
"redundancy is removed here. You have to define the number of redundant bits."
|
||||
self.ui.optionWidget.setCurrentIndex(1)
|
||||
# Values can only be changed when editing decoder, otherwise default value
|
||||
if not decoderEdit:
|
||||
self.ui.multiple.setValue(2)
|
||||
else:
|
||||
if element in self.chainoptions:
|
||||
value = self.chainoptions[element]
|
||||
if value == "":
|
||||
self.ui.multiple.setValue(2)
|
||||
else:
|
||||
self.ui.multiple.setValue(int(value))
|
||||
else:
|
||||
self.ui.multiple.setValue(2)
|
||||
self.ui.multiple.setEnabled(decoderEdit)
|
||||
elif settings.DECODING_MORSE in element:
|
||||
txt += "If the signal is a morse code, e.g. 00111001001110011100, where information are " \
|
||||
"transported with long and short sequences of 1 (0 just for padding), then this " \
|
||||
"decoding evaluates those sequences (Example output: 1011)."
|
||||
self.ui.optionWidget.setCurrentIndex(7)
|
||||
# # Values can only be changed when editing decoder, otherwise default value
|
||||
if not decoderEdit:
|
||||
self.ui.morse_low.setValue(1)
|
||||
self.ui.morse_high.setValue(3)
|
||||
self.ui.morse_wait.setValue(1)
|
||||
else:
|
||||
if element in self.chainoptions:
|
||||
value = self.chainoptions[element]
|
||||
if value == "":
|
||||
self.ui.morse_low.setValue(1)
|
||||
self.ui.morse_high.setValue(3)
|
||||
self.ui.morse_wait.setValue(1)
|
||||
else:
|
||||
try:
|
||||
l, h, w = value.split(";")
|
||||
self.ui.morse_low.setValue(int(l))
|
||||
self.ui.morse_high.setValue(int(h))
|
||||
self.ui.morse_wait.setValue(int(w))
|
||||
except ValueError:
|
||||
self.ui.morse_low.setValue(1)
|
||||
self.ui.morse_high.setValue(3)
|
||||
self.ui.morse_wait.setValue(1)
|
||||
else:
|
||||
self.ui.morse_low.setValue(1)
|
||||
self.ui.morse_high.setValue(3)
|
||||
self.ui.morse_wait.setValue(1)
|
||||
self.ui.morse_low.setEnabled(decoderEdit)
|
||||
self.ui.morse_high.setEnabled(decoderEdit)
|
||||
self.ui.morse_wait.setEnabled(decoderEdit)
|
||||
elif settings.DECODING_CARRIER in element:
|
||||
txt += "A carrier is a fixed pattern like 1_1_1_1 where the actual data lies in between, e.g. 1a1a1b1. This " \
|
||||
"function extracts the actual bit information (here: aab) from the signal at '_'/'.' positions.\n" \
|
||||
"Examples:\n" \
|
||||
"- Carrier = '1_' means 1_1_1_...\n" \
|
||||
"- Carrier = '01_' means 01_01_01_01..."
|
||||
self.ui.optionWidget.setCurrentIndex(2)
|
||||
# Values can only be changed when editing decoder, otherwise default value
|
||||
if not decoderEdit:
|
||||
self.ui.carrier.setText("1_")
|
||||
else:
|
||||
if element in self.chainoptions:
|
||||
value = self.chainoptions[element]
|
||||
if value == "":
|
||||
self.ui.carrier.setText("1_")
|
||||
else:
|
||||
self.ui.carrier.setText(value)
|
||||
else:
|
||||
self.ui.carrier.setText("1_")
|
||||
self.ui.carrier.setEnabled(decoderEdit)
|
||||
elif settings.DECODING_DATAWHITENING in element:
|
||||
txt += "Texas Instruments CC110x chips allow a data whitening that is applied before sending the signals to HF. " \
|
||||
"After a preamble (1010...) there is a fixed 16/32 bit sync word. The following data (incl. 16 bit CRC) " \
|
||||
"is masked (XOR) with the output of a LFSR.\n" \
|
||||
"This unmasks the data."
|
||||
self.ui.optionWidget.setCurrentIndex(5)
|
||||
# Values can only be changed when editing decoder, otherwise default value
|
||||
if not decoderEdit:
|
||||
self.ui.datawhitening_sync.setText("0xe9cae9ca")
|
||||
self.ui.datawhitening_polynomial.setText("0x21")
|
||||
self.ui.datawhitening_overwrite_crc.setChecked(False)
|
||||
else:
|
||||
if element in self.chainoptions:
|
||||
value = self.chainoptions[element]
|
||||
if value == "":
|
||||
self.ui.datawhitening_sync.setText("0xe9cae9ca")
|
||||
self.ui.datawhitening_polynomial.setText("0x21")
|
||||
self.ui.datawhitening_overwrite_crc.setChecked(False)
|
||||
else:
|
||||
try:
|
||||
whitening_sync, whitening_polynomial, whitening_overwrite_crc = value.split(";")
|
||||
self.ui.datawhitening_sync.setText(whitening_sync)
|
||||
self.ui.datawhitening_polynomial.setText(whitening_polynomial)
|
||||
self.ui.datawhitening_overwrite_crc.setChecked(True if whitening_overwrite_crc == "1" else False)
|
||||
|
||||
except ValueError:
|
||||
self.ui.datawhitening_sync.setText("0xe9cae9ca")
|
||||
self.ui.datawhitening_polynomial.setText("0x21")
|
||||
self.ui.datawhitening_overwrite_crc.setChecked(False)
|
||||
|
||||
self.ui.datawhitening_sync.setEnabled(decoderEdit)
|
||||
self.ui.datawhitening_polynomial.setEnabled(decoderEdit)
|
||||
self.ui.datawhitening_overwrite_crc.setEnabled(decoderEdit)
|
||||
|
||||
elif settings.DECODING_CUT in element:
|
||||
txt += "This function enables you to cut data from your messages, in order to shorten or align them for a " \
|
||||
"better view. Note that this decoding does NOT support encoding, because cut data is gone!\n" \
|
||||
"Example:\n" \
|
||||
"- Cut before '1010' would delete everything before first '1010' bits.\n" \
|
||||
"- Cut before Position = 3 (in bit) would delete the first three bits.\n"
|
||||
self.ui.optionWidget.setCurrentIndex(6)
|
||||
# Values can only be changed when editing decoder, otherwise default value
|
||||
if not decoderEdit:
|
||||
self.ui.cutmark.setText("1010")
|
||||
self.old_cutmark = self.ui.cutmark.text()
|
||||
self.ui.cutmark2.setValue(1)
|
||||
self.ui.rB_delbefore.setChecked(False)
|
||||
self.ui.rB_delafter.setChecked(False)
|
||||
self.ui.rB_delbeforepos.setChecked(False)
|
||||
self.ui.rB_delafterpos.setChecked(False)
|
||||
self.ui.cutmark.setEnabled(False)
|
||||
self.ui.cutmark2.setEnabled(False)
|
||||
else:
|
||||
if element in self.chainoptions:
|
||||
value = self.chainoptions[element]
|
||||
if value == "":
|
||||
self.ui.cutmark.setText("1010")
|
||||
self.ui.cutmark.setEnabled(True)
|
||||
self.old_cutmark = self.ui.cutmark.text()
|
||||
self.ui.cutmark2.setValue(1)
|
||||
self.ui.cutmark2.setEnabled(False)
|
||||
self.ui.rB_delbefore.setChecked(True)
|
||||
self.ui.rB_delafter.setChecked(False)
|
||||
self.ui.rB_delbeforepos.setChecked(False)
|
||||
self.ui.rB_delafterpos.setChecked(False)
|
||||
else:
|
||||
try:
|
||||
cmode, cmark = value.split(";")
|
||||
cmode = int(cmode)
|
||||
if cmode == 0:
|
||||
self.ui.rB_delbefore.setChecked(True)
|
||||
self.ui.cutmark.setEnabled(True)
|
||||
self.ui.cutmark2.setEnabled(False)
|
||||
self.ui.cutmark.setText(cmark)
|
||||
elif cmode == 1:
|
||||
self.ui.rB_delafter.setChecked(True)
|
||||
self.ui.cutmark.setEnabled(True)
|
||||
self.ui.cutmark2.setEnabled(False)
|
||||
self.ui.cutmark.setText(cmark)
|
||||
elif cmode == 2:
|
||||
self.ui.rB_delbeforepos.setChecked(True)
|
||||
self.ui.cutmark.setEnabled(False)
|
||||
self.ui.cutmark2.setEnabled(True)
|
||||
self.ui.cutmark2.setValue(int(cmark))
|
||||
elif cmode == 3:
|
||||
self.ui.rB_delafterpos.setChecked(True)
|
||||
self.ui.cutmark.setEnabled(False)
|
||||
self.ui.cutmark2.setEnabled(True)
|
||||
self.ui.cutmark2.setValue(int(cmark))
|
||||
|
||||
except ValueError:
|
||||
self.ui.cutmark.setText("1010")
|
||||
self.old_cutmark = self.ui.cutmark.text()
|
||||
self.ui.cutmark2.setValue(1)
|
||||
self.ui.rB_delbefore.setChecked(True)
|
||||
self.ui.rB_delafter.setChecked(False)
|
||||
self.ui.rB_delbeforepos.setChecked(False)
|
||||
self.ui.rB_delafterpos.setChecked(False)
|
||||
self.ui.cutmark.setEnabled(True)
|
||||
self.ui.cutmark2.setEnabled(False)
|
||||
else:
|
||||
self.ui.cutmark.setText("1010")
|
||||
self.old_cutmark = self.ui.cutmark.text()
|
||||
self.ui.cutmark2.setValue(1)
|
||||
self.ui.rB_delbefore.setChecked(True)
|
||||
self.ui.rB_delafter.setChecked(False)
|
||||
self.ui.rB_delbeforepos.setChecked(False)
|
||||
self.ui.rB_delafterpos.setChecked(False)
|
||||
self.ui.cutmark.setEnabled(True)
|
||||
self.ui.cutmark2.setEnabled(False)
|
||||
self.ui.rB_delbefore.setEnabled(decoderEdit)
|
||||
self.ui.rB_delafter.setEnabled(decoderEdit)
|
||||
self.ui.rB_delbeforepos.setEnabled(decoderEdit)
|
||||
self.ui.rB_delafterpos.setEnabled(decoderEdit)
|
||||
|
||||
self.ui.info.setText(txt)
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_datawhitening(self):
|
||||
datawhiteningstr = self.ui.datawhitening_sync.text() + ";" + self.ui.datawhitening_polynomial.text() + ";" + \
|
||||
("1" if self.ui.datawhitening_overwrite_crc.isChecked() else "0")
|
||||
if settings.DECODING_DATAWHITENING in self.active_message:
|
||||
self.chainoptions[self.active_message] = datawhiteningstr
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_external(self):
|
||||
externalstr = self.ui.external_decoder.text() + ";" + self.ui.external_encoder.text()
|
||||
if settings.DECODING_EXTERNAL in self.active_message:
|
||||
self.chainoptions[self.active_message] = externalstr
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_substitution_changed(self):
|
||||
subststr = ""
|
||||
for i in range(0, self.ui.substitution_rows.value()):
|
||||
if self.ui.substitution.item(i, 0) and self.ui.substitution.item(i, 1):
|
||||
subststr += self.ui.substitution.item(i, 0).text() + ":" + self.ui.substitution.item(i, 1).text() + ";"
|
||||
if settings.DECODING_SUBSTITUTION in self.active_message:
|
||||
self.chainoptions[self.active_message] = subststr
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_substitution_rows_changed(self):
|
||||
# Substitution Row Spinbox
|
||||
self.ui.substitution.setRowCount(self.ui.substitution_rows.value())
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_multiple_changed(self):
|
||||
# Multiple Spinbox
|
||||
val = self.ui.multiple.value()
|
||||
if settings.DECODING_REDUNDANCY in self.active_message:
|
||||
self.chainoptions[self.active_message] = val
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_morse_changed(self):
|
||||
# Multiple Spinbox
|
||||
val_low = self.ui.morse_low.value()
|
||||
val_high = self.ui.morse_high.value()
|
||||
val_wait = self.ui.morse_wait.value()
|
||||
|
||||
if val_low >= val_high:
|
||||
self.ui.morse_low.setValue(self.old_morse[0])
|
||||
self.ui.morse_high.setValue(self.old_morse[1])
|
||||
(val_low, val_high) = self.old_morse
|
||||
else:
|
||||
self.old_morse = (val_low, val_high)
|
||||
|
||||
if settings.DECODING_MORSE in self.active_message:
|
||||
self.chainoptions[self.active_message] = "{};{};{}".format(val_low, val_high, val_wait)
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_carrier_changed(self):
|
||||
# Only allow {0, 1}
|
||||
carrier_txt = self.ui.carrier.text()
|
||||
if carrier_txt.count("0") + carrier_txt.count("1") + carrier_txt.count("_") + carrier_txt.count(
|
||||
".") + carrier_txt.count("*") < len(carrier_txt):
|
||||
self.ui.carrier.setText(self.old_carrier_txt)
|
||||
else:
|
||||
self.old_carrier_txt = carrier_txt
|
||||
# Carrier Textbox
|
||||
# self.e.carrier = self.e.str2bit(self.ui.carrier.text())
|
||||
if settings.DECODING_CARRIER in self.active_message:
|
||||
self.chainoptions[self.active_message] = carrier_txt
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_cut(self):
|
||||
cmode = 0
|
||||
cmark = ""
|
||||
if self.ui.rB_delbefore.isChecked() or self.ui.rB_delafter.isChecked():
|
||||
# Activate right cutmark field
|
||||
self.ui.cutmark.setEnabled(True)
|
||||
self.ui.cutmark2.setEnabled(False)
|
||||
# set cmode
|
||||
if self.ui.rB_delafter.isChecked():
|
||||
cmode = 1
|
||||
# check values in cutmark
|
||||
cmark = self.ui.cutmark.text()
|
||||
if cmark.count("0") + cmark.count("1") < len(cmark):
|
||||
self.ui.cutmark.setText(self.old_cutmark)
|
||||
else:
|
||||
self.old_cutmark = cmark
|
||||
else:
|
||||
# Activate right cutmark field
|
||||
self.ui.cutmark.setEnabled(False)
|
||||
self.ui.cutmark2.setEnabled(True)
|
||||
# set cmode
|
||||
if self.ui.rB_delbeforepos.isChecked():
|
||||
cmode = 2
|
||||
else:
|
||||
cmode = 3
|
||||
cmark = str(self.ui.cutmark2.value())
|
||||
|
||||
cut_text = str(cmode) + ";" + cmark
|
||||
|
||||
if settings.DECODING_CUT in self.active_message:
|
||||
self.chainoptions[self.active_message] = cut_text
|
||||
self.decoderchainUpdate()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_add_to_your_decoding_clicked(self):
|
||||
if self.last_selected_item != "":
|
||||
self.ui.decoderchain.addItem(self.last_selected_item)
|
||||
self.decoderchainUpdate()
|
||||
self.ui.decoderchain.setCurrentRow(self.ui.decoderchain.count()-1)
|
||||
|
||||
def dragEnterEvent(self, event: QDragEnterEvent):
|
||||
event.accept()
|
||||
|
||||
def dropEvent(self, event: QDropEvent):
|
||||
# if not self.ui.decoderchain.geometry().contains(self.mapToGlobal(event.pos())):
|
||||
if self.ui.decoderchain.currentItem() is not None:
|
||||
self.chainoptions.pop(self.ui.decoderchain.currentItem().text(), None)
|
||||
self.ui.decoderchain.takeItem(self.ui.decoderchain.currentRow())
|
||||
self.decoderchainUpdate()
|
||||
|
||||
def set_signal(self):
|
||||
indx = self.ui.combobox_signals.currentIndex()
|
||||
if indx != 0:
|
||||
self.ui.inpt.setReadOnly(True)
|
||||
else:
|
||||
self.ui.inpt.setReadOnly(False)
|
||||
self.ui.inpt.setText("10010110")
|
||||
self.decoder_update()
|
||||
return
|
||||
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
|
||||
signal = self.signals[indx - 1]
|
||||
pa = ProtocolAnalyzer(signal)
|
||||
pa.get_protocol_from_signal()
|
||||
self.ui.inpt.setText("".join(pa.plain_bits_str))
|
||||
self.ui.inpt.setCursorPosition(0)
|
||||
|
||||
if signal is not None and pa.messages:
|
||||
last_message = pa.messages[-1]
|
||||
lookup = {i: msg.bit_sample_pos for i, msg in enumerate(pa.messages)}
|
||||
|
||||
plot_data = signal.qad[lookup[0][0]:lookup[pa.num_messages - 1][len(last_message) - 1]]
|
||||
self.ui.graphicsView_signal.plot_data(plot_data)
|
||||
|
||||
self.ui.graphicsView_signal.centerOn(0, 0)
|
||||
self.unsetCursor()
|
||||
|
@ -0,0 +1,67 @@
|
||||
from PyQt5.QtCore import pyqtSlot, Qt
|
||||
from PyQt5.QtWidgets import QDialog, QLabel, QRadioButton
|
||||
|
||||
from urh import settings
|
||||
from urh.signalprocessing.Filter import Filter
|
||||
from urh.ui.ui_filter_bandwidth_dialog import Ui_DialogFilterBandwidth
|
||||
|
||||
|
||||
class FilterBandwidthDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogFilterBandwidth()
|
||||
self.ui.setupUi(self)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
bw_type = settings.read("bandpass_filter_bw_type", "Medium", str)
|
||||
custom_bw = settings.read("bandpass_filter_custom_bw", 0.1, float)
|
||||
|
||||
for item in dir(self.ui):
|
||||
item = getattr(self.ui, item)
|
||||
if isinstance(item, QLabel):
|
||||
name = item.objectName().replace("label", "")
|
||||
key = next((key for key in Filter.BANDWIDTHS.keys() if name.startswith(key.replace(" ", ""))), None)
|
||||
if key is not None and name.endswith("Bandwidth"):
|
||||
item.setText("{0:n}".format(Filter.BANDWIDTHS[key]))
|
||||
elif key is not None and name.endswith("KernelLength"):
|
||||
item.setText(str(Filter.get_filter_length_from_bandwidth(Filter.BANDWIDTHS[key])))
|
||||
elif isinstance(item, QRadioButton):
|
||||
item.setChecked(bw_type.replace(" ", "_") == item.objectName().replace("radioButton", ""))
|
||||
|
||||
self.ui.doubleSpinBoxCustomBandwidth.setValue(custom_bw)
|
||||
self.ui.spinBoxCustomKernelLength.setValue(Filter.get_filter_length_from_bandwidth(custom_bw))
|
||||
|
||||
self.create_connects()
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.doubleSpinBoxCustomBandwidth.valueChanged.connect(self.on_spin_box_custom_bandwidth_value_changed)
|
||||
self.ui.spinBoxCustomKernelLength.valueChanged.connect(self.on_spin_box_custom_kernel_length_value_changed)
|
||||
self.ui.buttonBox.accepted.connect(self.on_accepted)
|
||||
|
||||
@property
|
||||
def checked_radiobutton(self):
|
||||
for rb in dir(self.ui):
|
||||
radio_button = getattr(self.ui, rb)
|
||||
if isinstance(radio_button, QRadioButton) and radio_button.isChecked():
|
||||
return radio_button
|
||||
return None
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_spin_box_custom_bandwidth_value_changed(self, bw: float):
|
||||
self.ui.spinBoxCustomKernelLength.blockSignals(True)
|
||||
self.ui.spinBoxCustomKernelLength.setValue(Filter.get_filter_length_from_bandwidth(bw))
|
||||
self.ui.spinBoxCustomKernelLength.blockSignals(False)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spin_box_custom_kernel_length_value_changed(self, filter_len: int):
|
||||
self.ui.doubleSpinBoxCustomBandwidth.blockSignals(True)
|
||||
self.ui.doubleSpinBoxCustomBandwidth.setValue(Filter.get_bandwidth_from_filter_length(filter_len))
|
||||
self.ui.doubleSpinBoxCustomBandwidth.blockSignals(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_accepted(self):
|
||||
if self.checked_radiobutton is not None:
|
||||
bw_type = self.checked_radiobutton.objectName().replace("radioButton", "").replace("_", " ")
|
||||
settings.write("bandpass_filter_bw_type", bw_type)
|
||||
|
||||
settings.write("bandpass_filter_custom_bw", self.ui.doubleSpinBoxCustomBandwidth.value())
|
@ -0,0 +1,100 @@
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt
|
||||
from PyQt5.QtWidgets import QDialog
|
||||
|
||||
from urh.signalprocessing.Filter import Filter, FilterType
|
||||
from urh.ui.ui_filter_dialog import Ui_FilterDialog
|
||||
|
||||
|
||||
class FilterDialog(QDialog):
|
||||
filter_accepted = pyqtSignal(Filter)
|
||||
|
||||
def __init__(self, dsp_filter: Filter, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_FilterDialog()
|
||||
self.ui.setupUi(self)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.error_message = ""
|
||||
|
||||
self.set_dsp_filter_status(dsp_filter.filter_type)
|
||||
self.create_connects()
|
||||
|
||||
def set_dsp_filter_status(self, dsp_filter_type: FilterType):
|
||||
if dsp_filter_type == FilterType.moving_average:
|
||||
self.ui.radioButtonMovingAverage.setChecked(True)
|
||||
self.ui.lineEditCustomTaps.setEnabled(False)
|
||||
self.ui.spinBoxNumTaps.setEnabled(True)
|
||||
elif dsp_filter_type == FilterType.dc_correction:
|
||||
self.ui.radioButtonDCcorrection.setChecked(True)
|
||||
self.ui.lineEditCustomTaps.setEnabled(False)
|
||||
self.ui.spinBoxNumTaps.setEnabled(False)
|
||||
else:
|
||||
self.ui.radioButtonCustomTaps.setChecked(True)
|
||||
self.ui.spinBoxNumTaps.setEnabled(True)
|
||||
self.ui.lineEditCustomTaps.setEnabled(True)
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.radioButtonMovingAverage.clicked.connect(self.on_radio_button_moving_average_clicked)
|
||||
self.ui.radioButtonCustomTaps.clicked.connect(self.on_radio_button_custom_taps_clicked)
|
||||
self.ui.radioButtonDCcorrection.clicked.connect(self.on_radio_button_dc_correction_clicked)
|
||||
|
||||
self.ui.spinBoxNumTaps.valueChanged.connect(self.set_error_status)
|
||||
self.ui.lineEditCustomTaps.textEdited.connect(self.set_error_status)
|
||||
self.ui.buttonBox.accepted.connect(self.on_accept_clicked)
|
||||
self.ui.buttonBox.rejected.connect(self.reject)
|
||||
|
||||
def build_filter(self) -> Filter:
|
||||
if self.ui.radioButtonMovingAverage.isChecked():
|
||||
n = self.ui.spinBoxNumTaps.value()
|
||||
return Filter([1/n for _ in range(n)], filter_type=FilterType.moving_average)
|
||||
elif self.ui.radioButtonDCcorrection.isChecked():
|
||||
return Filter([], filter_type=FilterType.dc_correction)
|
||||
else:
|
||||
# custom filter
|
||||
try:
|
||||
taps = eval(self.ui.lineEditCustomTaps.text())
|
||||
try:
|
||||
taps = list(map(float, taps))
|
||||
self.error_message = ""
|
||||
return Filter(taps)
|
||||
except (ValueError, TypeError) as e:
|
||||
self.error_message = "Error casting taps:\n" + str(e)
|
||||
return None
|
||||
|
||||
except SyntaxError as e:
|
||||
self.error_message = "Error parsing taps:\n" + str(e)
|
||||
return None
|
||||
|
||||
def set_error_status(self):
|
||||
dsp_filter = self.build_filter()
|
||||
if dsp_filter is None:
|
||||
self.ui.lineEditCustomTaps.setStyleSheet("background: red")
|
||||
self.ui.lineEditCustomTaps.setToolTip(self.error_message)
|
||||
elif len(dsp_filter.taps) != self.ui.spinBoxNumTaps.value():
|
||||
self.ui.lineEditCustomTaps.setStyleSheet("background: yellow")
|
||||
self.ui.lineEditCustomTaps.setToolTip("The number of the filter taps does not match the configured number of taps. I will use your configured filter taps.")
|
||||
else:
|
||||
self.ui.lineEditCustomTaps.setStyleSheet("")
|
||||
self.ui.lineEditCustomTaps.setToolTip("")
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_radio_button_moving_average_clicked(self, checked: bool):
|
||||
if checked:
|
||||
self.set_dsp_filter_status(FilterType.moving_average)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_radio_button_custom_taps_clicked(self, checked: bool):
|
||||
if checked:
|
||||
self.set_dsp_filter_status(FilterType.custom)
|
||||
self.set_error_status()
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_radio_button_dc_correction_clicked(self, checked: bool):
|
||||
if checked:
|
||||
self.set_dsp_filter_status(FilterType.dc_correction)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_accept_clicked(self):
|
||||
dsp_filter = self.build_filter()
|
||||
self.filter_accepted.emit(dsp_filter)
|
||||
self.accept()
|
@ -0,0 +1,362 @@
|
||||
import math
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QCloseEvent
|
||||
from PyQt5.QtWidgets import QDialog, QInputDialog
|
||||
|
||||
from urh import settings
|
||||
from urh.models.FuzzingTableModel import FuzzingTableModel
|
||||
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
|
||||
from urh.signalprocessing.ProtocolAnalyzerContainer import ProtocolAnalyzerContainer
|
||||
from urh.ui.ui_fuzzing import Ui_FuzzingDialog
|
||||
|
||||
|
||||
class FuzzingDialog(QDialog):
|
||||
def __init__(self, protocol: ProtocolAnalyzerContainer, label_index: int, msg_index: int, proto_view: int,
|
||||
parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_FuzzingDialog()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.protocol = protocol
|
||||
msg_index = msg_index if msg_index != -1 else 0
|
||||
self.ui.spinBoxFuzzMessage.setValue(msg_index + 1)
|
||||
self.ui.spinBoxFuzzMessage.setMinimum(1)
|
||||
self.ui.spinBoxFuzzMessage.setMaximum(self.protocol.num_messages)
|
||||
|
||||
self.ui.comboBoxFuzzingLabel.addItems([l.name for l in self.message.message_type])
|
||||
self.ui.comboBoxFuzzingLabel.setCurrentIndex(label_index)
|
||||
|
||||
self.proto_view = proto_view
|
||||
self.fuzz_table_model = FuzzingTableModel(self.current_label, proto_view)
|
||||
self.fuzz_table_model.remove_duplicates = self.ui.chkBRemoveDuplicates.isChecked()
|
||||
self.ui.tblFuzzingValues.setModel(self.fuzz_table_model)
|
||||
self.fuzz_table_model.update()
|
||||
|
||||
self.ui.spinBoxFuzzingStart.setValue(self.current_label_start + 1)
|
||||
self.ui.spinBoxFuzzingEnd.setValue(self.current_label_end)
|
||||
self.ui.spinBoxFuzzingStart.setMaximum(len(self.message_data))
|
||||
self.ui.spinBoxFuzzingEnd.setMaximum(len(self.message_data))
|
||||
|
||||
self.update_message_data_string()
|
||||
self.ui.tblFuzzingValues.resize_me()
|
||||
|
||||
self.create_connects()
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
return self.protocol.messages[int(self.ui.spinBoxFuzzMessage.value() - 1)]
|
||||
|
||||
@property
|
||||
def current_label_index(self):
|
||||
return self.ui.comboBoxFuzzingLabel.currentIndex()
|
||||
|
||||
@property
|
||||
def current_label(self) -> ProtocolLabel:
|
||||
if len(self.message.message_type) == 0:
|
||||
return None
|
||||
|
||||
cur_label = self.message.message_type[self.current_label_index].get_copy()
|
||||
self.message.message_type[self.current_label_index] = cur_label
|
||||
cur_label.fuzz_values = [fv for fv in cur_label.fuzz_values if fv] # Remove empty strings
|
||||
|
||||
if len(cur_label.fuzz_values) == 0:
|
||||
cur_label.fuzz_values.append(self.message.plain_bits_str[cur_label.start:cur_label.end])
|
||||
return cur_label
|
||||
|
||||
@property
|
||||
def current_label_start(self):
|
||||
if self.current_label and self.message:
|
||||
return self.message.get_label_range(self.current_label, self.proto_view, False)[0]
|
||||
else:
|
||||
return -1
|
||||
|
||||
@property
|
||||
def current_label_end(self):
|
||||
if self.current_label and self.message:
|
||||
return self.message.get_label_range(self.current_label, self.proto_view, False)[1]
|
||||
else:
|
||||
return -1
|
||||
|
||||
@property
|
||||
def message_data(self):
|
||||
if self.proto_view == 0:
|
||||
return self.message.plain_bits_str
|
||||
elif self.proto_view == 1:
|
||||
return self.message.plain_hex_str
|
||||
elif self.proto_view == 2:
|
||||
return self.message.plain_ascii_str
|
||||
else:
|
||||
return None
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.spinBoxFuzzingStart.valueChanged.connect(self.on_fuzzing_start_changed)
|
||||
self.ui.spinBoxFuzzingEnd.valueChanged.connect(self.on_fuzzing_end_changed)
|
||||
self.ui.comboBoxFuzzingLabel.currentIndexChanged.connect(self.on_combo_box_fuzzing_label_current_index_changed)
|
||||
self.ui.btnRepeatValues.clicked.connect(self.on_btn_repeat_values_clicked)
|
||||
self.ui.btnAddRow.clicked.connect(self.on_btn_add_row_clicked)
|
||||
self.ui.btnDelRow.clicked.connect(self.on_btn_del_row_clicked)
|
||||
self.ui.tblFuzzingValues.deletion_wanted.connect(self.delete_lines)
|
||||
self.ui.chkBRemoveDuplicates.stateChanged.connect(self.on_remove_duplicates_state_changed)
|
||||
self.ui.sBAddRangeStart.valueChanged.connect(self.on_fuzzing_range_start_changed)
|
||||
self.ui.sBAddRangeEnd.valueChanged.connect(self.on_fuzzing_range_end_changed)
|
||||
self.ui.checkBoxLowerBound.stateChanged.connect(self.on_lower_bound_checked_changed)
|
||||
self.ui.checkBoxUpperBound.stateChanged.connect(self.on_upper_bound_checked_changed)
|
||||
self.ui.spinBoxLowerBound.valueChanged.connect(self.on_lower_bound_changed)
|
||||
self.ui.spinBoxUpperBound.valueChanged.connect(self.on_upper_bound_changed)
|
||||
self.ui.spinBoxRandomMinimum.valueChanged.connect(self.on_random_range_min_changed)
|
||||
self.ui.spinBoxRandomMaximum.valueChanged.connect(self.on_random_range_max_changed)
|
||||
self.ui.spinBoxFuzzMessage.valueChanged.connect(self.on_fuzz_msg_changed)
|
||||
self.ui.btnAddFuzzingValues.clicked.connect(self.on_btn_add_fuzzing_values_clicked)
|
||||
self.ui.comboBoxFuzzingLabel.editTextChanged.connect(self.set_current_label_name)
|
||||
|
||||
def update_message_data_string(self):
|
||||
fuz_start = self.current_label_start
|
||||
fuz_end = self.current_label_end
|
||||
num_proto_bits = 10
|
||||
num_fuz_bits = 16
|
||||
|
||||
proto_start = fuz_start - num_proto_bits
|
||||
preambel = "... "
|
||||
if proto_start <= 0:
|
||||
proto_start = 0
|
||||
preambel = ""
|
||||
|
||||
proto_end = fuz_end + num_proto_bits
|
||||
postambel = " ..."
|
||||
if proto_end >= len(self.message_data) - 1:
|
||||
proto_end = len(self.message_data) - 1
|
||||
postambel = ""
|
||||
|
||||
fuzamble = ""
|
||||
if fuz_end - fuz_start > num_fuz_bits:
|
||||
fuz_end = fuz_start + num_fuz_bits
|
||||
fuzamble = "..."
|
||||
|
||||
self.ui.lPreBits.setText(preambel + self.message_data[proto_start:self.current_label_start])
|
||||
self.ui.lFuzzedBits.setText(self.message_data[fuz_start:fuz_end] + fuzamble)
|
||||
self.ui.lPostBits.setText(self.message_data[self.current_label_end:proto_end] + postambel)
|
||||
self.set_add_spinboxes_maximum_on_label_change()
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_fuzzing_start_changed(self, value: int):
|
||||
self.ui.spinBoxFuzzingEnd.setMinimum(self.ui.spinBoxFuzzingStart.value())
|
||||
new_start = self.message.convert_index(value - 1, self.proto_view, 0, False)[0]
|
||||
self.current_label.start = new_start
|
||||
self.current_label.fuzz_values[:] = []
|
||||
self.update_message_data_string()
|
||||
self.fuzz_table_model.update()
|
||||
self.ui.tblFuzzingValues.resize_me()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_fuzzing_end_changed(self, value: int):
|
||||
self.ui.spinBoxFuzzingStart.setMaximum(self.ui.spinBoxFuzzingEnd.value())
|
||||
new_end = self.message.convert_index(value - 1, self.proto_view, 0, False)[1] + 1
|
||||
self.current_label.end = new_end
|
||||
self.current_label.fuzz_values[:] = []
|
||||
self.update_message_data_string()
|
||||
self.fuzz_table_model.update()
|
||||
self.ui.tblFuzzingValues.resize_me()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combo_box_fuzzing_label_current_index_changed(self, index: int):
|
||||
self.fuzz_table_model.fuzzing_label = self.current_label
|
||||
self.fuzz_table_model.update()
|
||||
self.update_message_data_string()
|
||||
self.ui.tblFuzzingValues.resize_me()
|
||||
|
||||
self.ui.spinBoxFuzzingStart.blockSignals(True)
|
||||
self.ui.spinBoxFuzzingStart.setValue(self.current_label_start + 1)
|
||||
self.ui.spinBoxFuzzingStart.blockSignals(False)
|
||||
|
||||
self.ui.spinBoxFuzzingEnd.blockSignals(True)
|
||||
self.ui.spinBoxFuzzingEnd.setValue(self.current_label_end)
|
||||
self.ui.spinBoxFuzzingEnd.blockSignals(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_add_row_clicked(self):
|
||||
self.current_label.add_fuzz_value()
|
||||
self.fuzz_table_model.update()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_del_row_clicked(self):
|
||||
min_row, max_row, _, _ = self.ui.tblFuzzingValues.selection_range()
|
||||
self.delete_lines(min_row, max_row)
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
def delete_lines(self, min_row, max_row):
|
||||
if min_row == -1:
|
||||
self.current_label.fuzz_values = self.current_label.fuzz_values[:-1]
|
||||
else:
|
||||
self.current_label.fuzz_values = self.current_label.fuzz_values[:min_row] + self.current_label.fuzz_values[
|
||||
max_row + 1:]
|
||||
|
||||
_ = self.current_label # if user deleted all, this will restore a fuzz value
|
||||
|
||||
self.fuzz_table_model.update()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_remove_duplicates_state_changed(self):
|
||||
self.fuzz_table_model.remove_duplicates = self.ui.chkBRemoveDuplicates.isChecked()
|
||||
self.fuzz_table_model.update()
|
||||
self.remove_duplicates()
|
||||
|
||||
@pyqtSlot()
|
||||
def set_add_spinboxes_maximum_on_label_change(self):
|
||||
nbits = self.current_label.end - self.current_label.start # Use Bit Start/End for maximum calc.
|
||||
if nbits >= 32:
|
||||
nbits = 31
|
||||
max_val = 2 ** nbits - 1
|
||||
self.ui.sBAddRangeStart.setMaximum(max_val - 1)
|
||||
self.ui.sBAddRangeEnd.setMaximum(max_val)
|
||||
self.ui.sBAddRangeEnd.setValue(max_val)
|
||||
self.ui.sBAddRangeStep.setMaximum(max_val)
|
||||
self.ui.spinBoxLowerBound.setMaximum(max_val - 1)
|
||||
self.ui.spinBoxUpperBound.setMaximum(max_val)
|
||||
self.ui.spinBoxUpperBound.setValue(max_val)
|
||||
self.ui.spinBoxBoundaryNumber.setMaximum(int(max_val / 2) + 1)
|
||||
self.ui.spinBoxRandomMinimum.setMaximum(max_val - 1)
|
||||
self.ui.spinBoxRandomMaximum.setMaximum(max_val)
|
||||
self.ui.spinBoxRandomMaximum.setValue(max_val)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_fuzzing_range_start_changed(self, value: int):
|
||||
self.ui.sBAddRangeEnd.setMinimum(value)
|
||||
self.ui.sBAddRangeStep.setMaximum(self.ui.sBAddRangeEnd.value() - value)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_fuzzing_range_end_changed(self, value: int):
|
||||
self.ui.sBAddRangeStart.setMaximum(value - 1)
|
||||
self.ui.sBAddRangeStep.setMaximum(value - self.ui.sBAddRangeStart.value())
|
||||
|
||||
@pyqtSlot()
|
||||
def on_lower_bound_checked_changed(self):
|
||||
if self.ui.checkBoxLowerBound.isChecked():
|
||||
self.ui.spinBoxLowerBound.setEnabled(True)
|
||||
self.ui.spinBoxBoundaryNumber.setEnabled(True)
|
||||
elif not self.ui.checkBoxUpperBound.isChecked():
|
||||
self.ui.spinBoxLowerBound.setEnabled(False)
|
||||
self.ui.spinBoxBoundaryNumber.setEnabled(False)
|
||||
else:
|
||||
self.ui.spinBoxLowerBound.setEnabled(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_upper_bound_checked_changed(self):
|
||||
if self.ui.checkBoxUpperBound.isChecked():
|
||||
self.ui.spinBoxUpperBound.setEnabled(True)
|
||||
self.ui.spinBoxBoundaryNumber.setEnabled(True)
|
||||
elif not self.ui.checkBoxLowerBound.isChecked():
|
||||
self.ui.spinBoxUpperBound.setEnabled(False)
|
||||
self.ui.spinBoxBoundaryNumber.setEnabled(False)
|
||||
else:
|
||||
self.ui.spinBoxUpperBound.setEnabled(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_lower_bound_changed(self):
|
||||
self.ui.spinBoxUpperBound.setMinimum(self.ui.spinBoxLowerBound.value())
|
||||
self.ui.spinBoxBoundaryNumber.setMaximum(math.ceil((self.ui.spinBoxUpperBound.value()
|
||||
- self.ui.spinBoxLowerBound.value()) / 2))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_upper_bound_changed(self):
|
||||
self.ui.spinBoxLowerBound.setMaximum(self.ui.spinBoxUpperBound.value() - 1)
|
||||
self.ui.spinBoxBoundaryNumber.setMaximum(math.ceil((self.ui.spinBoxUpperBound.value()
|
||||
- self.ui.spinBoxLowerBound.value()) / 2))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_random_range_min_changed(self):
|
||||
self.ui.spinBoxRandomMaximum.setMinimum(self.ui.spinBoxRandomMinimum.value())
|
||||
|
||||
@pyqtSlot()
|
||||
def on_random_range_max_changed(self):
|
||||
self.ui.spinBoxRandomMinimum.setMaximum(self.ui.spinBoxRandomMaximum.value() - 1)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_add_fuzzing_values_clicked(self):
|
||||
if self.ui.comboBoxStrategy.currentIndex() == 0:
|
||||
self.__add_fuzzing_range()
|
||||
elif self.ui.comboBoxStrategy.currentIndex() == 1:
|
||||
self.__add_fuzzing_boundaries()
|
||||
elif self.ui.comboBoxStrategy.currentIndex() == 2:
|
||||
self.__add_random_fuzzing_values()
|
||||
|
||||
def __add_fuzzing_range(self):
|
||||
start = self.ui.sBAddRangeStart.value()
|
||||
end = self.ui.sBAddRangeEnd.value()
|
||||
step = self.ui.sBAddRangeStep.value()
|
||||
self.fuzz_table_model.add_range(start, end + 1, step)
|
||||
|
||||
def __add_fuzzing_boundaries(self):
|
||||
lower_bound = -1
|
||||
if self.ui.spinBoxLowerBound.isEnabled():
|
||||
lower_bound = self.ui.spinBoxLowerBound.value()
|
||||
|
||||
upper_bound = -1
|
||||
if self.ui.spinBoxUpperBound.isEnabled():
|
||||
upper_bound = self.ui.spinBoxUpperBound.value()
|
||||
|
||||
num_vals = self.ui.spinBoxBoundaryNumber.value()
|
||||
self.fuzz_table_model.add_boundaries(lower_bound, upper_bound, num_vals)
|
||||
|
||||
def __add_random_fuzzing_values(self):
|
||||
n = self.ui.spinBoxNumberRandom.value()
|
||||
minimum = self.ui.spinBoxRandomMinimum.value()
|
||||
maximum = self.ui.spinBoxRandomMaximum.value()
|
||||
self.fuzz_table_model.add_random(n, minimum, maximum)
|
||||
|
||||
def remove_duplicates(self):
|
||||
if self.ui.chkBRemoveDuplicates.isChecked():
|
||||
for lbl in self.message.message_type:
|
||||
seq = lbl.fuzz_values[:]
|
||||
seen = set()
|
||||
add_seen = seen.add
|
||||
lbl.fuzz_values = [l for l in seq if not (l in seen or add_seen(l))]
|
||||
|
||||
@pyqtSlot()
|
||||
def set_current_label_name(self):
|
||||
self.current_label.name = self.ui.comboBoxFuzzingLabel.currentText()
|
||||
self.ui.comboBoxFuzzingLabel.setItemText(self.ui.comboBoxFuzzingLabel.currentIndex(), self.current_label.name)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_fuzz_msg_changed(self, index: int):
|
||||
self.ui.comboBoxFuzzingLabel.setDisabled(False)
|
||||
|
||||
sel_label_ind = self.ui.comboBoxFuzzingLabel.currentIndex()
|
||||
self.ui.comboBoxFuzzingLabel.blockSignals(True)
|
||||
self.ui.comboBoxFuzzingLabel.clear()
|
||||
|
||||
if len(self.message.message_type) == 0:
|
||||
self.ui.comboBoxFuzzingLabel.setDisabled(True)
|
||||
return
|
||||
|
||||
self.ui.comboBoxFuzzingLabel.addItems([lbl.name for lbl in self.message.message_type])
|
||||
self.ui.comboBoxFuzzingLabel.blockSignals(False)
|
||||
|
||||
if sel_label_ind < self.ui.comboBoxFuzzingLabel.count():
|
||||
self.ui.comboBoxFuzzingLabel.setCurrentIndex(sel_label_ind)
|
||||
else:
|
||||
self.ui.comboBoxFuzzingLabel.setCurrentIndex(0)
|
||||
|
||||
self.fuzz_table_model.fuzzing_label = self.current_label
|
||||
self.fuzz_table_model.update()
|
||||
self.update_message_data_string()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_repeat_values_clicked(self):
|
||||
num_repeats, ok = QInputDialog.getInt(self, self.tr("How many times shall values be repeated?"),
|
||||
self.tr("Number of repeats:"), 1, 1)
|
||||
if ok:
|
||||
self.ui.chkBRemoveDuplicates.setChecked(False)
|
||||
min_row, max_row, _, _ = self.ui.tblFuzzingValues.selection_range()
|
||||
if min_row == -1:
|
||||
start, end = 0, len(self.current_label.fuzz_values)
|
||||
else:
|
||||
start, end = min_row, max_row + 1
|
||||
self.fuzz_table_model.repeat_fuzzing_values(start, end, num_repeats)
|
@ -0,0 +1,111 @@
|
||||
import copy
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QCloseEvent
|
||||
from PyQt5.QtWidgets import QDialog
|
||||
|
||||
from urh import settings
|
||||
from urh.models.RulesetTableModel import RulesetTableModel
|
||||
from urh.signalprocessing import Ruleset
|
||||
from urh.signalprocessing.MessageType import MessageType
|
||||
from urh.signalprocessing.Ruleset import Rule, OPERATION_DESCRIPTION
|
||||
from urh.ui.delegates.ComboBoxDelegate import ComboBoxDelegate
|
||||
from urh.ui.ui_messagetype_options import Ui_DialogMessageType
|
||||
|
||||
|
||||
class MessageTypeDialog(QDialog):
|
||||
|
||||
def __init__(self, message_type: MessageType, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogMessageType()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
operator_descriptions = list(OPERATION_DESCRIPTION.values())
|
||||
operator_descriptions.sort()
|
||||
|
||||
self.setWindowTitle(self.tr("Rules for {}".format(message_type.name)))
|
||||
self.message_type = message_type
|
||||
self.original_ruleset = copy.deepcopy(message_type.ruleset)
|
||||
self.original_assigned_status = message_type.assigned_by_ruleset
|
||||
self.ruleset_table_model = RulesetTableModel(message_type.ruleset, operator_descriptions, parent=self)
|
||||
self.ui.tblViewRuleset.setModel(self.ruleset_table_model)
|
||||
|
||||
self.ui.btnRemoveRule.setEnabled(len(message_type.ruleset) > 0)
|
||||
self.set_ruleset_ui_status()
|
||||
|
||||
self.ui.rbAssignAutomatically.setChecked(self.message_type.assigned_by_ruleset)
|
||||
self.ui.rbAssignManually.setChecked(self.message_type.assign_manually)
|
||||
|
||||
self.ui.tblViewRuleset.setItemDelegateForColumn(2, ComboBoxDelegate(["Bit", "Hex", "ASCII"], parent=self))
|
||||
self.ui.tblViewRuleset.setItemDelegateForColumn(3, ComboBoxDelegate(operator_descriptions, parent=self))
|
||||
|
||||
for i in range(len(message_type.ruleset)):
|
||||
self.open_editors(i)
|
||||
|
||||
self.ui.cbRulesetMode.setCurrentIndex(self.message_type.ruleset.mode.value)
|
||||
|
||||
self.create_connects()
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.btnAddRule.clicked.connect(self.on_btn_add_rule_clicked)
|
||||
self.ui.btnRemoveRule.clicked.connect(self.on_btn_remove_rule_clicked)
|
||||
self.ui.rbAssignAutomatically.clicked.connect(self.on_rb_assign_automatically_clicked)
|
||||
self.ui.rbAssignManually.clicked.connect(self.on_rb_assign_manually_clicked)
|
||||
self.ui.cbRulesetMode.currentIndexChanged.connect(self.on_cb_rulesetmode_current_index_changed)
|
||||
|
||||
self.ui.buttonBox.accepted.connect(self.accept)
|
||||
self.ui.buttonBox.rejected.connect(self.on_rejected)
|
||||
|
||||
def set_ruleset_ui_status(self):
|
||||
self.ui.tblViewRuleset.setEnabled(self.message_type.assigned_by_ruleset)
|
||||
self.ui.btnRemoveRule.setEnabled(self.message_type.assigned_by_ruleset and len(self.message_type.ruleset) > 0)
|
||||
self.ui.btnAddRule.setEnabled(self.message_type.assigned_by_ruleset)
|
||||
self.ui.cbRulesetMode.setEnabled(self.message_type.assigned_by_ruleset)
|
||||
|
||||
def open_editors(self, row):
|
||||
self.ui.tblViewRuleset.openPersistentEditor(self.ruleset_table_model.index(row, 2))
|
||||
self.ui.tblViewRuleset.openPersistentEditor(self.ruleset_table_model.index(row, 3))
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
self.ui.tblViewRuleset.setItemDelegateForColumn(2, None)
|
||||
self.ui.tblViewRuleset.setItemDelegateForColumn(3, None)
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_rejected(self):
|
||||
self.message_type.ruleset = self.original_ruleset
|
||||
self.message_type.assigned_by_ruleset = self.original_assigned_status
|
||||
self.reject()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_add_rule_clicked(self):
|
||||
self.ui.btnRemoveRule.setEnabled(True)
|
||||
self.message_type.ruleset.append(Rule(start=0, end=0, operator="=", target_value="1", value_type=0))
|
||||
self.ruleset_table_model.update()
|
||||
|
||||
for i in range(len(self.message_type.ruleset)):
|
||||
self.open_editors(i)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_remove_rule_clicked(self):
|
||||
self.ruleset_table_model.ruleset.remove(self.message_type.ruleset[-1])
|
||||
self.ruleset_table_model.update()
|
||||
self.ui.btnRemoveRule.setEnabled(len(self.message_type.ruleset) > 0)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_rb_assign_automatically_clicked(self):
|
||||
self.message_type.assigned_by_ruleset = True
|
||||
self.set_ruleset_ui_status()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_rb_assign_manually_clicked(self):
|
||||
self.message_type.assigned_by_ruleset = False
|
||||
self.set_ruleset_ui_status()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_cb_rulesetmode_current_index_changed(self, index: int):
|
||||
self.message_type.ruleset.mode = Ruleset.Mode(index)
|
@ -0,0 +1,68 @@
|
||||
import math
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtWidgets import QDialog, QTableWidgetItem
|
||||
|
||||
from urh.ui.delegates.KillerSpinBoxDelegate import KillerSpinBoxDelegate
|
||||
from urh.ui.delegates.SpinBoxDelegate import SpinBoxDelegate
|
||||
from urh.ui.ui_modulation_parameters_dialog import Ui_DialogModulationParameters
|
||||
|
||||
|
||||
class ModulationParametersDialog(QDialog):
|
||||
def __init__(self, parameters: list, modulation_type: str, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogModulationParameters()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.parameters = parameters
|
||||
self.num_bits = int(math.log2(len(parameters)))
|
||||
|
||||
if "FSK" in modulation_type:
|
||||
self.ui.tblSymbolParameters.setItemDelegateForColumn(1, KillerSpinBoxDelegate(-1e12, 1e12, self))
|
||||
self.ui.tblSymbolParameters.horizontalHeaderItem(1).setText("Frequency in Hz")
|
||||
elif "ASK" in modulation_type:
|
||||
self.ui.tblSymbolParameters.horizontalHeaderItem(1).setText("Amplitude")
|
||||
self.ui.tblSymbolParameters.setItemDelegateForColumn(1, SpinBoxDelegate(0, 100, self, "%"))
|
||||
elif "PSK" in modulation_type:
|
||||
self.ui.tblSymbolParameters.setItemDelegateForColumn(1, SpinBoxDelegate(-360, 360, self, "°"))
|
||||
self.ui.tblSymbolParameters.horizontalHeaderItem(1).setText("Phase")
|
||||
|
||||
fmt = "{0:0" + str(self.num_bits) + "b}"
|
||||
self.ui.tblSymbolParameters.setRowCount(len(parameters))
|
||||
for i, parameter in enumerate(parameters):
|
||||
item = QTableWidgetItem(fmt.format(i))
|
||||
font = item.font()
|
||||
font.setBold(True)
|
||||
item.setFont(font)
|
||||
item.setFlags(Qt.ItemIsEnabled)
|
||||
self.ui.tblSymbolParameters.setItem(i, 0, item)
|
||||
|
||||
item = QTableWidgetItem()
|
||||
item.setData(Qt.DisplayRole, self.parameters[i])
|
||||
self.ui.tblSymbolParameters.setItem(i, 1, item)
|
||||
self.ui.tblSymbolParameters.openPersistentEditor(self.ui.tblSymbolParameters.item(i, 1))
|
||||
|
||||
self.create_connects()
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.buttonBox.accepted.connect(self.on_accepted)
|
||||
self.ui.buttonBox.rejected.connect(self.reject)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_accepted(self):
|
||||
for i in range(self.ui.tblSymbolParameters.rowCount()):
|
||||
self.parameters[i] = float(self.ui.tblSymbolParameters.item(i, 1).text())
|
||||
|
||||
self.accept()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
app = QApplication(["urh"])
|
||||
|
||||
dialog = ModulationParametersDialog([0, 100.0], "ASK")
|
||||
dialog.show()
|
||||
|
||||
app.exec_()
|
@ -0,0 +1,636 @@
|
||||
from array import array
|
||||
|
||||
import numpy
|
||||
from PyQt5.QtCore import Qt, pyqtSlot, QRegExp, QTimer
|
||||
from PyQt5.QtGui import QCloseEvent, QResizeEvent, QKeyEvent, QIcon, QRegExpValidator
|
||||
from PyQt5.QtWidgets import QDialog, QMessageBox, QLineEdit
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.dialogs.ModulationParametersDialog import ModulationParametersDialog
|
||||
from urh.signalprocessing.IQArray import IQArray
|
||||
from urh.signalprocessing.Modulator import Modulator
|
||||
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
|
||||
from urh.ui.ui_modulation import Ui_DialogModulation
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class ModulatorDialog(QDialog):
|
||||
def __init__(self, modulators, tree_model=None, parent=None):
|
||||
"""
|
||||
:type modulators: list of Modulator
|
||||
"""
|
||||
super().__init__(parent)
|
||||
|
||||
self.ui = Ui_DialogModulation()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.lock_samples_in_view = False
|
||||
|
||||
if tree_model is not None:
|
||||
self.ui.treeViewSignals.setModel(tree_model)
|
||||
self.ui.treeViewSignals.expandAll()
|
||||
self.ui.gVOriginalSignal.signal_tree_root = tree_model.rootItem
|
||||
|
||||
self.ui.comboBoxCustomModulations.clear()
|
||||
for modulator in modulators:
|
||||
self.ui.comboBoxCustomModulations.addItem(modulator.name)
|
||||
if len(modulators) == 1:
|
||||
self.ui.btnRemoveModulation.setDisabled(True)
|
||||
|
||||
self.modulators = modulators
|
||||
|
||||
self.set_ui_for_current_modulator()
|
||||
|
||||
self.ui.cbShowDataBitsOnly.setText(self.tr("Show Only Data Sequence\n"))
|
||||
self.ui.cbShowDataBitsOnly.setEnabled(False)
|
||||
self.protocol = None # type: ProtocolAnalyzer
|
||||
self.search_results = []
|
||||
self.ui.cbShowDataBitsOnly.setEnabled(False)
|
||||
self.ui.btnSearchNext.setEnabled(False)
|
||||
self.ui.btnSearchPrev.setEnabled(False)
|
||||
|
||||
self.ui.chkBoxLockSIV.setDisabled(True)
|
||||
|
||||
self.original_bits = ""
|
||||
|
||||
self.restore_bits_action = self.ui.linEdDataBits.addAction(QIcon.fromTheme("edit-undo"),
|
||||
QLineEdit.TrailingPosition)
|
||||
self.restore_bits_action.setEnabled(False)
|
||||
|
||||
self.configure_parameters_action = self.ui.lineEditParameters.addAction(QIcon.fromTheme("configure"),
|
||||
QLineEdit.TrailingPosition)
|
||||
|
||||
self.create_connects()
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
self.set_bits_per_symbol_enabled_status()
|
||||
self.set_modulation_profile_status()
|
||||
|
||||
# Ensure full screen is shown after resize
|
||||
QTimer.singleShot(100, self.show_full_scene)
|
||||
|
||||
def __cur_selected_mod_type(self):
|
||||
s = self.ui.comboBoxModulationType.currentText()
|
||||
return s[s.rindex("(") + 1:s.rindex(")")]
|
||||
|
||||
@staticmethod
|
||||
def __trim_number(number):
|
||||
if abs(number) >= 1e9: # giga
|
||||
return numpy.round(number / 1e9) * 1e9
|
||||
elif abs(number) >= 1e6: # mega
|
||||
return numpy.round(number / 1e6) * 1e6
|
||||
elif abs(number) >= 1e3: # Kilo
|
||||
return numpy.round(number / 1e3) * 1e3
|
||||
else:
|
||||
return number
|
||||
|
||||
@staticmethod
|
||||
def __ensure_multitude(num1, num2):
|
||||
try:
|
||||
if abs(num1) > abs(num2):
|
||||
num1 = abs(int(num1 / num2)) * num2
|
||||
else:
|
||||
num2 = abs(int(num2 / num1)) * num1
|
||||
return num1, num2
|
||||
except Exception:
|
||||
return num1, num2
|
||||
|
||||
def __set_gauss_ui_visibility(self, show: bool):
|
||||
self.ui.lGaussBT.setVisible(show)
|
||||
self.ui.lGaussWidth.setVisible(show)
|
||||
self.ui.spinBoxGaussBT.setVisible(show)
|
||||
self.ui.spinBoxGaussFilterWidth.setVisible(show)
|
||||
|
||||
self.ui.spinBoxGaussFilterWidth.setValue(self.current_modulator.gauss_filter_width)
|
||||
self.ui.spinBoxGaussBT.setValue(self.current_modulator.gauss_bt)
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
self.ui.lineEditParameters.editingFinished.emit()
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
|
||||
for gv in (self.ui.gVCarrier, self.ui.gVData, self.ui.gVModulated, self.ui.gVOriginalSignal):
|
||||
# Eliminate graphic views to prevent segfaults
|
||||
gv.eliminate()
|
||||
|
||||
super().closeEvent(event)
|
||||
|
||||
@property
|
||||
def current_modulator(self):
|
||||
return self.modulators[self.ui.comboBoxCustomModulations.currentIndex()]
|
||||
|
||||
def set_ui_for_current_modulator(self):
|
||||
index = self.ui.comboBoxModulationType.findText("*(" + self.current_modulator.modulation_type + ")",
|
||||
Qt.MatchWildcard)
|
||||
self.ui.comboBoxModulationType.setCurrentIndex(index)
|
||||
self.ui.doubleSpinBoxCarrierFreq.setValue(self.current_modulator.carrier_freq_hz)
|
||||
self.ui.doubleSpinBoxCarrierPhase.setValue(self.current_modulator.carrier_phase_deg)
|
||||
self.ui.spinBoxSamplesPerSymbol.setValue(self.current_modulator.samples_per_symbol)
|
||||
self.ui.spinBoxSampleRate.setValue(self.current_modulator.sample_rate)
|
||||
self.ui.spinBoxBitsPerSymbol.setValue(self.current_modulator.bits_per_symbol)
|
||||
|
||||
self.update_modulation_parameters()
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.doubleSpinBoxCarrierFreq.valueChanged.connect(self.on_carrier_freq_changed)
|
||||
self.ui.doubleSpinBoxCarrierPhase.valueChanged.connect(self.on_carrier_phase_changed)
|
||||
self.ui.spinBoxSamplesPerSymbol.valueChanged.connect(self.on_samples_per_symbol_changed)
|
||||
self.ui.spinBoxSampleRate.valueChanged.connect(self.on_sample_rate_changed)
|
||||
self.ui.linEdDataBits.textChanged.connect(self.on_data_bits_changed)
|
||||
self.ui.spinBoxBitsPerSymbol.valueChanged.connect(self.on_bits_per_symbol_changed)
|
||||
self.ui.comboBoxModulationType.currentIndexChanged.connect(self.on_modulation_type_changed)
|
||||
self.ui.gVOriginalSignal.zoomed.connect(self.on_orig_signal_zoomed)
|
||||
self.ui.cbShowDataBitsOnly.stateChanged.connect(self.on_show_data_bits_only_changed)
|
||||
self.ui.btnSearchNext.clicked.connect(self.on_btn_next_search_result_clicked)
|
||||
self.ui.btnSearchPrev.clicked.connect(self.on_btn_prev_search_result_clicked)
|
||||
self.ui.comboBoxCustomModulations.editTextChanged.connect(self.on_custom_modulation_name_edited)
|
||||
self.ui.comboBoxCustomModulations.currentIndexChanged.connect(self.on_custom_modulation_index_changed)
|
||||
self.ui.btnAddModulation.clicked.connect(self.add_modulator)
|
||||
self.ui.btnRemoveModulation.clicked.connect(self.on_remove_modulator_clicked)
|
||||
self.ui.gVModulated.zoomed.connect(self.on_carrier_data_modulated_zoomed)
|
||||
self.ui.gVCarrier.zoomed.connect(self.on_carrier_data_modulated_zoomed)
|
||||
self.ui.gVData.zoomed.connect(self.on_carrier_data_modulated_zoomed)
|
||||
self.ui.gVModulated.selection_width_changed.connect(self.on_modulated_selection_changed)
|
||||
self.ui.gVOriginalSignal.selection_width_changed.connect(self.on_original_selection_changed)
|
||||
self.ui.spinBoxGaussBT.valueChanged.connect(self.on_gauss_bt_changed)
|
||||
self.ui.spinBoxGaussFilterWidth.valueChanged.connect(self.on_gauss_filter_width_changed)
|
||||
|
||||
self.ui.chkBoxLockSIV.stateChanged.connect(self.on_lock_siv_changed)
|
||||
|
||||
self.ui.gVOriginalSignal.signal_loaded.connect(self.handle_signal_loaded)
|
||||
self.ui.btnAutoDetect.clicked.connect(self.on_btn_autodetect_clicked)
|
||||
|
||||
self.restore_bits_action.triggered.connect(self.on_restore_bits_action_triggered)
|
||||
self.configure_parameters_action.triggered.connect(self.on_configure_parameters_action_triggered)
|
||||
self.ui.lineEditParameters.editingFinished.connect(self.on_line_edit_parameters_editing_finished)
|
||||
|
||||
def draw_carrier(self):
|
||||
self.ui.gVCarrier.plot_data(self.current_modulator.carrier_data)
|
||||
|
||||
def draw_data_bits(self):
|
||||
self.ui.gVData.setScene(self.current_modulator.data_scene)
|
||||
self.ui.gVData.update()
|
||||
|
||||
def draw_modulated(self):
|
||||
self.ui.gVModulated.plot_data(self.current_modulator.modulate(pause=0).imag)
|
||||
if self.lock_samples_in_view:
|
||||
siv = self.ui.gVOriginalSignal.view_rect().width()
|
||||
self.adjust_samples_in_view(siv)
|
||||
else:
|
||||
self.mark_samples_in_view()
|
||||
|
||||
def draw_original_signal(self, start=0, end=-1):
|
||||
scene_manager = self.ui.gVOriginalSignal.scene_manager
|
||||
if scene_manager is None:
|
||||
return
|
||||
|
||||
if end == -1:
|
||||
end = scene_manager.signal.num_samples
|
||||
|
||||
y = self.ui.gVOriginalSignal.view_rect().y()
|
||||
h = self.ui.gVOriginalSignal.view_rect().height()
|
||||
self.ui.gVOriginalSignal.setSceneRect(start, y, end - start, h)
|
||||
self.ui.gVOriginalSignal.fitInView(self.ui.gVOriginalSignal.sceneRect())
|
||||
scene_manager.show_scene_section(start, end)
|
||||
self.ui.gVOriginalSignal.update()
|
||||
|
||||
if self.lock_samples_in_view:
|
||||
self.adjust_samples_in_view(self.ui.gVModulated.view_rect().width())
|
||||
else:
|
||||
self.mark_samples_in_view()
|
||||
|
||||
def update_views(self):
|
||||
self.ui.gVCarrier.update()
|
||||
self.ui.gVData.update()
|
||||
self.ui.gVModulated.update()
|
||||
self.ui.gVOriginalSignal.update()
|
||||
|
||||
def search_data_sequence(self):
|
||||
if not self.ui.cbShowDataBitsOnly.isEnabled() or not self.ui.cbShowDataBitsOnly.isChecked():
|
||||
return
|
||||
|
||||
search_seq = self.ui.linEdDataBits.text()
|
||||
if len(search_seq) == 0 or self.protocol is None:
|
||||
return
|
||||
|
||||
self.search_results[:] = []
|
||||
proto_bits = self.protocol.plain_bits_str
|
||||
len_seq = len(search_seq)
|
||||
|
||||
for i, message in enumerate(proto_bits):
|
||||
j = message.find(search_seq)
|
||||
while j != -1:
|
||||
self.search_results.append((i, j, j + len_seq))
|
||||
j = message.find(search_seq, j + 1)
|
||||
|
||||
self.ui.lTotalSearchresults.setText(str(len(self.search_results)))
|
||||
self.show_search_result(0)
|
||||
|
||||
def show_search_result(self, i: int):
|
||||
if len(self.search_results) == 0:
|
||||
self.ui.lCurrentSearchResult.setText("0")
|
||||
self.ui.gVOriginalSignal.scene_manager.clear_path()
|
||||
return
|
||||
|
||||
message, start_index, end_index = self.search_results[i]
|
||||
|
||||
start, nsamples = self.protocol.get_samplepos_of_bitseq(message, start_index, message, end_index, False)
|
||||
self.draw_original_signal(start=start, end=start + nsamples)
|
||||
|
||||
self.ui.lCurrentSearchResult.setText(str(i + 1))
|
||||
self.ui.btnSearchNext.setEnabled(i != len(self.search_results) - 1)
|
||||
self.ui.btnSearchPrev.setEnabled(i > 0)
|
||||
|
||||
def add_modulator(self):
|
||||
names = [m.name for m in self.modulators]
|
||||
name = "Modulation"
|
||||
number = 1
|
||||
while name in names:
|
||||
name = "Modulation " + str(number)
|
||||
number += 1
|
||||
self.modulators.append(Modulator(name))
|
||||
self.ui.comboBoxCustomModulations.addItem(name)
|
||||
self.ui.comboBoxCustomModulations.setCurrentIndex(len(self.modulators) - 1)
|
||||
self.ui.btnRemoveModulation.setEnabled(True)
|
||||
|
||||
def adjust_samples_in_view(self, target_siv: float):
|
||||
self.ui.gVOriginalSignal.scale(self.ui.gVOriginalSignal.view_rect().width() / target_siv, 1)
|
||||
mod_zoom_factor = self.ui.gVModulated.view_rect().width() / target_siv
|
||||
self.ui.gVModulated.scale(mod_zoom_factor, 1)
|
||||
self.ui.gVCarrier.scale(mod_zoom_factor, 1)
|
||||
self.ui.gVData.scale(mod_zoom_factor, 1)
|
||||
self.mark_samples_in_view()
|
||||
|
||||
def detect_fsk_frequencies(self):
|
||||
if not self.current_modulator.is_frequency_based:
|
||||
return
|
||||
|
||||
frequencies = []
|
||||
try:
|
||||
if not self.current_modulator.is_binary_modulation:
|
||||
raise NotImplementedError()
|
||||
|
||||
zero_freq = self.protocol.estimate_frequency_for_zero(self.current_modulator.sample_rate)
|
||||
one_freq = self.protocol.estimate_frequency_for_one(self.current_modulator.sample_rate)
|
||||
zero_freq = self.__trim_number(zero_freq)
|
||||
one_freq = self.__trim_number(one_freq)
|
||||
zero_freq, one_freq = self.__ensure_multitude(zero_freq, one_freq)
|
||||
|
||||
if zero_freq == one_freq:
|
||||
# If frequencies are equal, it is very likely the zero freq is negative
|
||||
zero_freq = -one_freq
|
||||
|
||||
frequencies = [zero_freq, one_freq]
|
||||
|
||||
except (AttributeError, NotImplementedError):
|
||||
frequencies = self.current_modulator.get_default_parameters()
|
||||
|
||||
self.current_modulator.parameters = array("f", frequencies)
|
||||
self.update_modulation_parameters()
|
||||
|
||||
def handle_signal_loaded(self, protocol):
|
||||
self.setCursor(Qt.WaitCursor)
|
||||
self.ui.cbShowDataBitsOnly.setEnabled(True)
|
||||
self.ui.chkBoxLockSIV.setEnabled(True)
|
||||
self.ui.btnAutoDetect.setEnabled(True)
|
||||
self.protocol = protocol
|
||||
|
||||
# Apply bit length of original signal to current modulator
|
||||
self.ui.spinBoxSamplesPerSymbol.setValue(self.ui.gVOriginalSignal.signal.samples_per_symbol)
|
||||
|
||||
# https://github.com/jopohl/urh/issues/130
|
||||
self.ui.gVModulated.show_full_scene(reinitialize=True)
|
||||
self.ui.gVCarrier.show_full_scene(reinitialize=True)
|
||||
self.ui.gVData.show_full_scene(reinitialize=True)
|
||||
|
||||
self.unsetCursor()
|
||||
|
||||
def mark_samples_in_view(self):
|
||||
self.ui.lSamplesInViewModulated.setText(str(int(self.ui.gVModulated.view_rect().width())))
|
||||
|
||||
if self.ui.gVOriginalSignal.scene_manager is not None:
|
||||
self.ui.lSamplesInViewOrigSignal.setText(str(int(self.ui.gVOriginalSignal.view_rect().width())))
|
||||
else:
|
||||
self.ui.lSamplesInViewOrigSignal.setText("-")
|
||||
return
|
||||
|
||||
if int(self.ui.gVOriginalSignal.view_rect().width()) != int(self.ui.gVModulated.view_rect().width()):
|
||||
font = self.ui.lSamplesInViewModulated.font()
|
||||
font.setBold(False)
|
||||
self.ui.lSamplesInViewModulated.setFont(font)
|
||||
self.ui.lSamplesInViewOrigSignal.setFont(font)
|
||||
|
||||
self.ui.lSamplesInViewOrigSignal.setStyleSheet("QLabel { color : red; }")
|
||||
self.ui.lSamplesInViewModulated.setStyleSheet("QLabel { color : red; }")
|
||||
else:
|
||||
font = self.ui.lSamplesInViewModulated.font()
|
||||
font.setBold(True)
|
||||
self.ui.lSamplesInViewModulated.setFont(font)
|
||||
self.ui.lSamplesInViewOrigSignal.setFont(font)
|
||||
|
||||
self.ui.lSamplesInViewOrigSignal.setStyleSheet("")
|
||||
self.ui.lSamplesInViewModulated.setStyleSheet("")
|
||||
|
||||
def set_default_modulation_parameters(self):
|
||||
self.current_modulator.parameters = self.current_modulator.get_default_parameters()
|
||||
self.update_modulation_parameters()
|
||||
|
||||
def set_modulation_profile_status(self):
|
||||
visible = settings.read("multiple_modulations", False, bool)
|
||||
self.ui.btnAddModulation.setVisible(visible)
|
||||
self.ui.btnRemoveModulation.setVisible(visible)
|
||||
self.ui.comboBoxCustomModulations.setVisible(visible)
|
||||
|
||||
def resizeEvent(self, event: QResizeEvent):
|
||||
self.update_views()
|
||||
|
||||
def keyPressEvent(self, event: QKeyEvent):
|
||||
if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return:
|
||||
return
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def initialize(self, bits: str):
|
||||
self.on_modulation_type_changed() # for drawing modulated signal initially
|
||||
self.original_bits = bits
|
||||
self.ui.linEdDataBits.setText(bits)
|
||||
self.draw_original_signal()
|
||||
self.ui.gVModulated.show_full_scene(reinitialize=True)
|
||||
self.ui.gVModulated.auto_fit_view()
|
||||
self.ui.gVData.show_full_scene(reinitialize=True)
|
||||
self.ui.gVData.auto_fit_view()
|
||||
self.ui.gVCarrier.show_full_scene(reinitialize=True)
|
||||
self.ui.gVCarrier.auto_fit_view()
|
||||
|
||||
self.mark_samples_in_view()
|
||||
|
||||
def update_modulation_parameters(self):
|
||||
n = len(self.current_modulator.parameters) - 1
|
||||
if self.current_modulator.is_amplitude_based:
|
||||
regex = r"(100|[0-9]{1,2})"
|
||||
elif self.current_modulator.is_frequency_based:
|
||||
regex = r"((-?[0-9]+)[.,]?[0-9]*[kKmMgG]?)"
|
||||
elif self.current_modulator.is_phase_based:
|
||||
regex = r"(-?(36[0]|3[0-5][0-9]|[12][0-9][0-9]|[1-9]?[0-9]))"
|
||||
else:
|
||||
raise ValueError("Unknown modulation type")
|
||||
|
||||
full_regex = r"^(" + regex + r"/){" + str(n) + "}" + regex + r"$"
|
||||
self.ui.lineEditParameters.setValidator(QRegExpValidator(QRegExp(full_regex)))
|
||||
self.ui.lineEditParameters.setText(self.current_modulator.parameters_string)
|
||||
|
||||
def set_bits_per_symbol_enabled_status(self):
|
||||
if self.current_modulator.modulation_type == "OQPSK":
|
||||
self.ui.spinBoxBitsPerSymbol.setEnabled(False)
|
||||
self.ui.spinBoxBitsPerSymbol.setValue(2)
|
||||
else:
|
||||
self.ui.spinBoxBitsPerSymbol.setEnabled(True)
|
||||
|
||||
def show_full_scene(self):
|
||||
for graphic_view in (self.ui.gVModulated, self.ui.gVData, self.ui.gVCarrier):
|
||||
graphic_view.show_full_scene(reinitialize=True)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_carrier_freq_changed(self):
|
||||
self.current_modulator.carrier_freq_hz = self.ui.doubleSpinBoxCarrierFreq.value()
|
||||
self.draw_carrier()
|
||||
self.draw_modulated()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_carrier_phase_changed(self):
|
||||
self.current_modulator.carrier_phase_deg = self.ui.doubleSpinBoxCarrierPhase.value()
|
||||
self.draw_carrier()
|
||||
self.draw_modulated()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_samples_per_symbol_changed(self):
|
||||
self.current_modulator.samples_per_symbol = self.ui.spinBoxSamplesPerSymbol.value()
|
||||
self.draw_carrier()
|
||||
self.draw_data_bits()
|
||||
self.draw_modulated()
|
||||
self.show_full_scene()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_data_bits_changed(self):
|
||||
text = self.ui.linEdDataBits.text()
|
||||
text = ''.join(c for c in text if c == "1" or c == "0")
|
||||
self.ui.linEdDataBits.blockSignals(True)
|
||||
self.ui.linEdDataBits.setText(text)
|
||||
self.ui.linEdDataBits.blockSignals(False)
|
||||
self.current_modulator.display_bits = text
|
||||
self.draw_carrier()
|
||||
self.draw_data_bits()
|
||||
self.draw_modulated()
|
||||
if len(text) > 0:
|
||||
if len(text) > 24:
|
||||
display_text = text[0:24] + "..."
|
||||
else:
|
||||
display_text = text
|
||||
self.ui.cbShowDataBitsOnly.setToolTip(text)
|
||||
self.ui.cbShowDataBitsOnly.setText(self.tr("Show Only Data Sequence\n") + "(" + display_text + ")")
|
||||
else:
|
||||
self.ui.cbShowDataBitsOnly.setToolTip("")
|
||||
self.ui.cbShowDataBitsOnly.setText(self.tr("Show Only Data Sequence\n"))
|
||||
|
||||
self.search_data_sequence()
|
||||
self.restore_bits_action.setEnabled(text != self.original_bits)
|
||||
self.show_full_scene()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_sample_rate_changed(self):
|
||||
if int(self.ui.spinBoxSampleRate.value()) > 0:
|
||||
self.current_modulator.sample_rate = int(self.ui.spinBoxSampleRate.value())
|
||||
self.draw_carrier()
|
||||
self.draw_modulated()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_gauss_bt_changed(self):
|
||||
self.current_modulator.gauss_bt = self.ui.spinBoxGaussBT.value()
|
||||
self.draw_modulated()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_gauss_filter_width_changed(self):
|
||||
self.current_modulator.gauss_filter_width = self.ui.spinBoxGaussFilterWidth.value()
|
||||
self.draw_modulated()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_bits_per_symbol_changed(self):
|
||||
if self.current_modulator.bits_per_symbol == self.ui.spinBoxBitsPerSymbol.value():
|
||||
return
|
||||
self.current_modulator.bits_per_symbol = self.ui.spinBoxBitsPerSymbol.value()
|
||||
self.set_default_modulation_parameters()
|
||||
self.draw_modulated()
|
||||
self.show_full_scene()
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def on_modulation_type_changed(self):
|
||||
write_default_parameters = self.current_modulator.modulation_type != self.__cur_selected_mod_type()
|
||||
self.current_modulator.modulation_type = self.__cur_selected_mod_type()
|
||||
|
||||
self.__set_gauss_ui_visibility(self.__cur_selected_mod_type() == "GFSK")
|
||||
|
||||
self.ui.labelParameters.setText(self.current_modulator.parameter_type_str)
|
||||
if write_default_parameters:
|
||||
self.set_default_modulation_parameters()
|
||||
else:
|
||||
self.update_modulation_parameters()
|
||||
|
||||
self.set_bits_per_symbol_enabled_status()
|
||||
self.draw_modulated()
|
||||
self.show_full_scene()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_orig_signal_zoomed(self):
|
||||
start = self.ui.gVOriginalSignal.view_rect().x()
|
||||
end = start + self.ui.gVOriginalSignal.view_rect().width()
|
||||
|
||||
self.ui.gVOriginalSignal.centerOn(start + (end - start) / 2, 0)
|
||||
if self.lock_samples_in_view:
|
||||
self.adjust_samples_in_view(self.ui.gVOriginalSignal.view_rect().width())
|
||||
|
||||
x = self.ui.gVOriginalSignal.view_rect().x() + self.ui.gVOriginalSignal.view_rect().width() / 2
|
||||
y = 0
|
||||
|
||||
self.ui.gVModulated.centerOn(x, y)
|
||||
self.ui.gVCarrier.centerOn(x, y)
|
||||
self.ui.gVData.centerOn(x, y)
|
||||
else:
|
||||
self.mark_samples_in_view()
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_carrier_data_modulated_zoomed(self, factor: float):
|
||||
|
||||
x = self.sender().view_rect().x() + self.sender().view_rect().width() / 2
|
||||
y = 0
|
||||
for gv in (self.ui.gVCarrier, self.ui.gVData, self.ui.gVModulated):
|
||||
if gv == self.sender():
|
||||
continue
|
||||
if factor == -1:
|
||||
gv.show_full_scene()
|
||||
else:
|
||||
gv.scale(factor, 1)
|
||||
gv.centerOn(x, y)
|
||||
|
||||
if self.lock_samples_in_view:
|
||||
self.adjust_samples_in_view(self.ui.gVModulated.view_rect().width())
|
||||
self.ui.gVOriginalSignal.centerOn(x, y)
|
||||
else:
|
||||
self.mark_samples_in_view()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_custom_modulation_name_edited(self):
|
||||
self.current_modulator.name = self.ui.comboBoxCustomModulations.currentText()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_custom_modulation_index_changed(self):
|
||||
self.set_ui_for_current_modulator()
|
||||
self.draw_carrier()
|
||||
self.draw_data_bits()
|
||||
self.draw_modulated()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_next_search_result_clicked(self):
|
||||
cur_search_result = int(self.ui.lCurrentSearchResult.text()) - 1
|
||||
self.show_search_result(cur_search_result + 1)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_prev_search_result_clicked(self):
|
||||
cur_search_result = int(self.ui.lCurrentSearchResult.text()) - 1
|
||||
self.show_search_result(cur_search_result - 1)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_show_data_bits_only_changed(self, redraw=True):
|
||||
show_data_bits_only = self.ui.cbShowDataBitsOnly.isChecked()
|
||||
if not self.ui.cbShowDataBitsOnly.isEnabled() or not show_data_bits_only:
|
||||
self.ui.btnSearchPrev.setEnabled(False)
|
||||
self.ui.btnSearchNext.setEnabled(False)
|
||||
self.ui.lCurrentSearchResult.setText("-")
|
||||
self.ui.lTotalSearchresults.setText("-")
|
||||
else:
|
||||
self.search_data_sequence()
|
||||
|
||||
if not redraw:
|
||||
return
|
||||
|
||||
if self.ui.cbShowDataBitsOnly.isEnabled() and not show_data_bits_only:
|
||||
self.draw_original_signal()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_remove_modulator_clicked(self):
|
||||
index = self.ui.comboBoxCustomModulations.currentIndex()
|
||||
self.ui.comboBoxCustomModulations.removeItem(index)
|
||||
self.modulators.remove(self.modulators[index])
|
||||
|
||||
if len(self.modulators) == 1:
|
||||
self.ui.btnRemoveModulation.setDisabled(True)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_lock_siv_changed(self):
|
||||
self.lock_samples_in_view = self.ui.chkBoxLockSIV.isChecked()
|
||||
if self.lock_samples_in_view:
|
||||
self.adjust_samples_in_view(self.ui.gVModulated.view_rect().width())
|
||||
|
||||
@pyqtSlot()
|
||||
def on_restore_bits_action_triggered(self):
|
||||
self.ui.linEdDataBits.setText(self.original_bits)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_autodetect_clicked(self):
|
||||
signal = self.ui.gVOriginalSignal.scene_manager.signal
|
||||
freq = self.current_modulator.estimate_carrier_frequency(signal, self.protocol)
|
||||
|
||||
if freq is None or freq == 0:
|
||||
QMessageBox.information(self, self.tr("No results"),
|
||||
self.tr("Unable to detect parameters from current signal"))
|
||||
return
|
||||
|
||||
self.ui.doubleSpinBoxCarrierFreq.setValue(freq)
|
||||
self.detect_fsk_frequencies()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_modulated_selection_changed(self, new_width: int):
|
||||
self.ui.lModulatedSelectedSamples.setText(str(abs(new_width)))
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_original_selection_changed(self, new_width: int):
|
||||
self.ui.lOriginalSignalSamplesSelected.setText(str(abs(new_width)))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_configure_parameters_action_triggered(self):
|
||||
self.ui.lineEditParameters.editingFinished.emit()
|
||||
dialog = ModulationParametersDialog(self.current_modulator.parameters, self.current_modulator.modulation_type,
|
||||
self)
|
||||
dialog.accepted.connect(self.update_modulation_parameters)
|
||||
dialog.show()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_parameters_editing_finished(self):
|
||||
if not self.ui.lineEditParameters.hasAcceptableInput():
|
||||
return
|
||||
|
||||
text = self.ui.lineEditParameters.text()
|
||||
parameters = []
|
||||
for param in text.split("/"):
|
||||
param = param.upper().replace(",", ".")
|
||||
factor = 1
|
||||
if param.endswith("G"):
|
||||
factor = 10 ** 9
|
||||
param = param[:-1]
|
||||
elif param.endswith("M"):
|
||||
factor = 10 ** 6
|
||||
param = param[:-1]
|
||||
elif param.endswith("K"):
|
||||
factor = 10 ** 3
|
||||
param = param[:-1]
|
||||
|
||||
try:
|
||||
parameters.append(factor * float(param))
|
||||
except ValueError:
|
||||
logger.warning("Could not convert {} to number".format(param))
|
||||
return
|
||||
|
||||
self.current_modulator.parameters[:] = array("f", parameters)
|
||||
self.draw_modulated()
|
||||
self.show_full_scene()
|
@ -0,0 +1,511 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal, QSize, QAbstractTableModel, QModelIndex
|
||||
from PyQt5.QtGui import QCloseEvent, QIcon, QPixmap
|
||||
from PyQt5.QtWidgets import QDialog, QHBoxLayout, QCompleter, QDirModel, QApplication, QHeaderView, QRadioButton, \
|
||||
QFileDialog, qApp
|
||||
|
||||
from urh import settings, colormaps
|
||||
from urh.controller.widgets.PluginFrame import PluginFrame
|
||||
from urh.dev.BackendHandler import BackendHandler, Backends
|
||||
from urh.dev.native import ExtensionHelper
|
||||
from urh.models.FieldTypeTableModel import FieldTypeTableModel
|
||||
from urh.signalprocessing.FieldType import FieldType
|
||||
from urh.signalprocessing.Modulator import Modulator
|
||||
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
|
||||
from urh.signalprocessing.Spectrogram import Spectrogram
|
||||
from urh.ui.delegates.ComboBoxDelegate import ComboBoxDelegate
|
||||
from urh.ui.ui_options import Ui_DialogOptions
|
||||
from urh.util import util
|
||||
|
||||
|
||||
class DeviceOptionsTableModel(QAbstractTableModel):
|
||||
header_labels = ["Software Defined Radio", "Info", "Native backend (recommended)", "GNU Radio backend"]
|
||||
|
||||
def __init__(self, backend_handler: BackendHandler, parent=None):
|
||||
self.backend_handler = backend_handler
|
||||
|
||||
super().__init__(parent)
|
||||
|
||||
def update(self):
|
||||
self.beginResetModel()
|
||||
self.endResetModel()
|
||||
|
||||
def columnCount(self, parent: QModelIndex = None, *args, **kwargs):
|
||||
return len(self.header_labels)
|
||||
|
||||
def rowCount(self, parent: QModelIndex = None, *args, **kwargs):
|
||||
return len(self.backend_handler.DEVICE_NAMES)
|
||||
|
||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
||||
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
|
||||
return self.header_labels[section]
|
||||
return super().headerData(section, orientation, role)
|
||||
|
||||
def get_device_at(self, index: int):
|
||||
dev_key = self.backend_handler.get_key_from_device_display_text(self.backend_handler.DEVICE_NAMES[index])
|
||||
return self.backend_handler.device_backends[dev_key]
|
||||
|
||||
def data(self, index: QModelIndex, role=Qt.DisplayRole):
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
i = index.row()
|
||||
j = index.column()
|
||||
device = self.get_device_at(i)
|
||||
if role == Qt.DisplayRole:
|
||||
if j == 0:
|
||||
return self.backend_handler.DEVICE_NAMES[i]
|
||||
elif j == 1:
|
||||
if device.is_enabled:
|
||||
if device.supports_rx and device.supports_tx:
|
||||
device_info = "supports RX and TX"
|
||||
elif device.supports_rx and not device.supports_tx:
|
||||
device_info = "supports RX only"
|
||||
elif not device.supports_rx and device.supports_tx:
|
||||
device_info = "supports TX only"
|
||||
else:
|
||||
device_info = ""
|
||||
else:
|
||||
device_info = "disabled"
|
||||
|
||||
return device_info
|
||||
elif j == 2:
|
||||
return "" if device.has_native_backend else "not available"
|
||||
elif j == 3:
|
||||
return "" if device.has_gnuradio_backend else "not available"
|
||||
elif role == Qt.CheckStateRole:
|
||||
if j == 0 and (device.has_native_backend or device.has_gnuradio_backend):
|
||||
return Qt.Checked if device.is_enabled else Qt.Unchecked
|
||||
elif j == 2 and device.has_native_backend:
|
||||
return Qt.Checked if device.selected_backend == Backends.native else Qt.Unchecked
|
||||
elif j == 3 and device.has_gnuradio_backend:
|
||||
return Qt.Checked if device.selected_backend == Backends.grc else Qt.Unchecked
|
||||
|
||||
def setData(self, index: QModelIndex, value, role=None):
|
||||
if not index.isValid():
|
||||
return False
|
||||
|
||||
i, j = index.row(), index.column()
|
||||
device = self.get_device_at(i)
|
||||
if role == Qt.CheckStateRole:
|
||||
enabled = bool(value)
|
||||
if j == 0:
|
||||
device.is_enabled = enabled
|
||||
if j == 2:
|
||||
if enabled and device.has_native_backend:
|
||||
device.selected_backend = Backends.native
|
||||
elif not enabled and device.has_gnuradio_backend:
|
||||
device.selected_backend = Backends.grc
|
||||
elif j == 3:
|
||||
if enabled and device.has_gnuradio_backend:
|
||||
device.selected_backend = Backends.grc
|
||||
elif not enabled and device.has_native_backend:
|
||||
device.selected_backend = Backends.native
|
||||
|
||||
self.update()
|
||||
device.write_settings()
|
||||
return True
|
||||
|
||||
def flags(self, index: QModelIndex):
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
j = index.column()
|
||||
device = self.get_device_at(index.row())
|
||||
if j == 0 and not device.has_native_backend and not device.has_gnuradio_backend:
|
||||
return Qt.NoItemFlags
|
||||
|
||||
if j in [1, 2, 3] and not device.is_enabled:
|
||||
return Qt.NoItemFlags
|
||||
|
||||
if j == 2 and not device.has_native_backend:
|
||||
return Qt.NoItemFlags
|
||||
|
||||
if j == 3 and not device.has_gnuradio_backend:
|
||||
return Qt.NoItemFlags
|
||||
|
||||
flags = Qt.ItemIsEnabled
|
||||
|
||||
if j in [0, 2, 3]:
|
||||
flags |= Qt.ItemIsUserCheckable
|
||||
|
||||
return flags
|
||||
|
||||
|
||||
class OptionsDialog(QDialog):
|
||||
values_changed = pyqtSignal(dict)
|
||||
|
||||
def __init__(self, installed_plugins, highlighted_plugins=None, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.backend_handler = BackendHandler()
|
||||
|
||||
self.ui = Ui_DialogOptions()
|
||||
self.ui.setupUi(self)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.device_options_model = DeviceOptionsTableModel(self.backend_handler, self)
|
||||
self.device_options_model.update()
|
||||
self.ui.tblDevices.setModel(self.device_options_model)
|
||||
self.ui.tblDevices.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
|
||||
self.ui.tblDevices.setItemDelegateForColumn(1, ComboBoxDelegate(["native", "GNU Radio"]))
|
||||
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
layout = QHBoxLayout(self.ui.tab_plugins)
|
||||
self.plugin_controller = PluginFrame(installed_plugins, highlighted_plugins, parent=self)
|
||||
layout.addWidget(self.plugin_controller)
|
||||
self.ui.tab_plugins.setLayout(layout)
|
||||
|
||||
self.ui.btnViewBuildLog.hide()
|
||||
self.build_log = ""
|
||||
|
||||
# We use bundled native device backends on windows, so no need to reconfigure them
|
||||
self.ui.groupBoxNativeOptions.setVisible(sys.platform != "win32")
|
||||
self.ui.labelIconTheme.setVisible(sys.platform == "linux")
|
||||
self.ui.comboBoxIconTheme.setVisible(sys.platform == "linux")
|
||||
|
||||
self.ui.comboBoxTheme.setCurrentIndex(settings.read("theme_index", 0, int))
|
||||
self.ui.comboBoxIconTheme.setCurrentIndex(settings.read("icon_theme_index", 0, int))
|
||||
self.ui.checkBoxShowConfirmCloseDialog.setChecked(not settings.read('not_show_close_dialog', False, bool))
|
||||
self.ui.checkBoxHoldShiftToDrag.setChecked(settings.read('hold_shift_to_drag', True, bool))
|
||||
self.ui.checkBoxDefaultFuzzingPause.setChecked(settings.read('use_default_fuzzing_pause', True, bool))
|
||||
|
||||
self.ui.checkBoxAlignLabels.setChecked(settings.read('align_labels', True, bool))
|
||||
|
||||
self.ui.doubleSpinBoxRAMThreshold.setValue(100 * settings.read('ram_threshold', 0.6, float))
|
||||
|
||||
if self.backend_handler.gr_python_interpreter:
|
||||
self.ui.lineEditGRPythonInterpreter.setText(self.backend_handler.gr_python_interpreter)
|
||||
|
||||
self.ui.doubleSpinBoxFuzzingPause.setValue(settings.read("default_fuzzing_pause", 10 ** 6, int))
|
||||
self.ui.doubleSpinBoxFuzzingPause.setEnabled(settings.read('use_default_fuzzing_pause', True, bool))
|
||||
|
||||
self.ui.checkBoxMultipleModulations.setChecked(settings.read("multiple_modulations", False, bool))
|
||||
|
||||
self.ui.radioButtonLowModulationAccuracy.setChecked(Modulator.get_dtype() == np.int8)
|
||||
self.ui.radioButtonMediumModulationAccuracy.setChecked(Modulator.get_dtype() == np.int16)
|
||||
self.ui.radioButtonHighModulationAccuracy.setChecked(Modulator.get_dtype() == np.float32)
|
||||
|
||||
completer = QCompleter()
|
||||
completer.setModel(QDirModel(completer))
|
||||
self.ui.lineEditGRPythonInterpreter.setCompleter(completer)
|
||||
|
||||
self.ui.spinBoxFontSize.setValue(qApp.font().pointSize())
|
||||
|
||||
self.refresh_device_tab()
|
||||
|
||||
self.create_connects()
|
||||
self.old_show_pause_as_time = False
|
||||
|
||||
self.field_type_table_model = FieldTypeTableModel([], parent=self)
|
||||
self.ui.tblLabeltypes.setModel(self.field_type_table_model)
|
||||
self.ui.tblLabeltypes.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
|
||||
self.ui.tblLabeltypes.setItemDelegateForColumn(1, ComboBoxDelegate([f.name for f in FieldType.Function],
|
||||
return_index=False, parent=self))
|
||||
self.ui.tblLabeltypes.setItemDelegateForColumn(2, ComboBoxDelegate(ProtocolLabel.DISPLAY_FORMATS, parent=self))
|
||||
|
||||
self.read_options()
|
||||
|
||||
self.old_default_view = self.ui.comboBoxDefaultView.currentIndex()
|
||||
self.old_num_sending_repeats = self.ui.spinBoxNumSendingRepeats.value()
|
||||
self.ui.labelRebuildNativeStatus.setText("")
|
||||
|
||||
self.show_available_colormaps()
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.doubleSpinBoxFuzzingPause.valueChanged.connect(self.on_spinbox_fuzzing_pause_value_changed)
|
||||
self.ui.lineEditGRPythonInterpreter.editingFinished.connect(self.on_gr_python_interpreter_path_edited)
|
||||
self.ui.btnChooseGRPythonInterpreter.clicked.connect(self.on_btn_choose_gr_python_interpreter_clicked)
|
||||
self.ui.comboBoxTheme.currentIndexChanged.connect(self.on_combo_box_theme_index_changed)
|
||||
self.ui.checkBoxShowConfirmCloseDialog.clicked.connect(self.on_checkbox_confirm_close_dialog_clicked)
|
||||
self.ui.checkBoxHoldShiftToDrag.clicked.connect(self.on_checkbox_hold_shift_to_drag_clicked)
|
||||
self.ui.checkBoxAlignLabels.clicked.connect(self.on_checkbox_align_labels_clicked)
|
||||
self.ui.checkBoxDefaultFuzzingPause.clicked.connect(self.on_checkbox_default_fuzzing_pause_clicked)
|
||||
self.ui.btnAddLabelType.clicked.connect(self.on_btn_add_label_type_clicked)
|
||||
self.ui.btnRemoveLabeltype.clicked.connect(self.on_btn_remove_label_type_clicked)
|
||||
self.ui.radioButtonLowModulationAccuracy.clicked.connect(self.on_radio_button_low_modulation_accuracy_clicked)
|
||||
self.ui.radioButtonMediumModulationAccuracy.clicked.connect(self.on_radio_button_medium_modulation_accuracy_clicked)
|
||||
self.ui.radioButtonHighModulationAccuracy.clicked.connect(self.on_radio_button_high_modulation_accuracy_clicked)
|
||||
|
||||
self.ui.doubleSpinBoxRAMThreshold.valueChanged.connect(self.on_double_spinbox_ram_threshold_value_changed)
|
||||
self.ui.btnRebuildNative.clicked.connect(self.on_btn_rebuild_native_clicked)
|
||||
self.ui.comboBoxIconTheme.currentIndexChanged.connect(self.on_combobox_icon_theme_index_changed)
|
||||
self.ui.checkBoxMultipleModulations.clicked.connect(self.on_checkbox_multiple_modulations_clicked)
|
||||
self.ui.btnViewBuildLog.clicked.connect(self.on_btn_view_build_log_clicked)
|
||||
self.ui.labelDeviceMissingInfo.linkActivated.connect(self.on_label_device_missing_info_link_activated)
|
||||
self.ui.spinBoxFontSize.editingFinished.connect(self.on_spin_box_font_size_editing_finished)
|
||||
|
||||
def show_gnuradio_infos(self):
|
||||
self.ui.lineEditGRPythonInterpreter.setText(self.backend_handler.gr_python_interpreter)
|
||||
|
||||
if self.backend_handler.gnuradio_is_installed:
|
||||
self.ui.lineEditGRPythonInterpreter.setStyleSheet("background-color: lightgreen")
|
||||
self.ui.lineEditGRPythonInterpreter.setToolTip("GNU Radio interface is working.")
|
||||
else:
|
||||
self.ui.lineEditGRPythonInterpreter.setStyleSheet("background-color: orange")
|
||||
self.ui.lineEditGRPythonInterpreter.setToolTip("GNU Radio is not installed or incompatible with "
|
||||
"the configured python interpreter.")
|
||||
|
||||
def read_options(self):
|
||||
self.ui.comboBoxDefaultView.setCurrentIndex(settings.read('default_view', 0, type=int))
|
||||
self.ui.spinBoxNumSendingRepeats.setValue(settings.read('num_sending_repeats', 0, type=int))
|
||||
self.ui.checkBoxPauseTime.setChecked(settings.read('show_pause_as_time', False, type=bool))
|
||||
|
||||
self.old_show_pause_as_time = bool(self.ui.checkBoxPauseTime.isChecked())
|
||||
|
||||
self.field_type_table_model.field_types = FieldType.load_from_xml()
|
||||
self.field_type_table_model.update()
|
||||
|
||||
def refresh_device_tab(self):
|
||||
self.backend_handler.get_backends()
|
||||
self.show_gnuradio_infos()
|
||||
self.device_options_model.update()
|
||||
|
||||
def show_available_colormaps(self):
|
||||
height = 50
|
||||
|
||||
selected = colormaps.read_selected_colormap_name_from_settings()
|
||||
for colormap_name in sorted(colormaps.maps.keys()):
|
||||
image = Spectrogram.create_colormap_image(colormap_name, height=height)
|
||||
rb = QRadioButton(colormap_name)
|
||||
rb.setObjectName(colormap_name)
|
||||
rb.setChecked(colormap_name == selected)
|
||||
rb.setIcon(QIcon(QPixmap.fromImage(image)))
|
||||
rb.setIconSize(QSize(256, height))
|
||||
self.ui.scrollAreaWidgetSpectrogramColormapContents.layout().addWidget(rb)
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
changed_values = {}
|
||||
if bool(self.ui.checkBoxPauseTime.isChecked()) != self.old_show_pause_as_time:
|
||||
changed_values['show_pause_as_time'] = bool(self.ui.checkBoxPauseTime.isChecked())
|
||||
if self.old_default_view != self.ui.comboBoxDefaultView.currentIndex():
|
||||
changed_values['default_view'] = self.ui.comboBoxDefaultView.currentIndex()
|
||||
if self.old_num_sending_repeats != self.ui.spinBoxNumSendingRepeats.value():
|
||||
changed_values["num_sending_repeats"] = self.ui.spinBoxNumSendingRepeats.value()
|
||||
|
||||
settings.write('default_view', self.ui.comboBoxDefaultView.currentIndex())
|
||||
settings.write('num_sending_repeats', self.ui.spinBoxNumSendingRepeats.value())
|
||||
settings.write('show_pause_as_time', self.ui.checkBoxPauseTime.isChecked())
|
||||
|
||||
FieldType.save_to_xml(self.field_type_table_model.field_types)
|
||||
self.plugin_controller.save_enabled_states()
|
||||
for plugin in self.plugin_controller.model.plugins:
|
||||
plugin.destroy_settings_frame()
|
||||
|
||||
for i in range(self.ui.scrollAreaWidgetSpectrogramColormapContents.layout().count()):
|
||||
widget = self.ui.scrollAreaWidgetSpectrogramColormapContents.layout().itemAt(i).widget()
|
||||
if isinstance(widget, QRadioButton) and widget.isChecked():
|
||||
selected_colormap_name = widget.objectName()
|
||||
if selected_colormap_name != colormaps.read_selected_colormap_name_from_settings():
|
||||
colormaps.choose_colormap(selected_colormap_name)
|
||||
colormaps.write_selected_colormap_to_settings(selected_colormap_name)
|
||||
changed_values["spectrogram_colormap"] = selected_colormap_name
|
||||
break
|
||||
|
||||
self.values_changed.emit(changed_values)
|
||||
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
def set_gnuradio_status(self):
|
||||
self.backend_handler.gr_python_interpreter = self.ui.lineEditGRPythonInterpreter.text()
|
||||
self.refresh_device_tab()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_add_label_type_clicked(self):
|
||||
suffix = 1
|
||||
field_type_names = {ft.caption for ft in self.field_type_table_model.field_types}
|
||||
while "New Fieldtype #" + str(suffix) in field_type_names:
|
||||
suffix += 1
|
||||
|
||||
caption = "New Fieldtype #" + str(suffix)
|
||||
self.field_type_table_model.field_types.append(FieldType(caption, FieldType.Function.CUSTOM))
|
||||
self.field_type_table_model.update()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_remove_label_type_clicked(self):
|
||||
if self.field_type_table_model.field_types:
|
||||
selected_indices = {i.row() for i in self.ui.tblLabeltypes.selectedIndexes()}
|
||||
|
||||
if selected_indices:
|
||||
for i in reversed(sorted(selected_indices)):
|
||||
self.field_type_table_model.field_types.pop(i)
|
||||
else:
|
||||
self.field_type_table_model.field_types.pop()
|
||||
|
||||
self.field_type_table_model.update()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_double_spinbox_ram_threshold_value_changed(self):
|
||||
val = self.ui.doubleSpinBoxRAMThreshold.value()
|
||||
settings.write("ram_threshold", val / 100)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_checkbox_confirm_close_dialog_clicked(self, checked: bool):
|
||||
settings.write("not_show_close_dialog", not checked)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combo_box_theme_index_changed(self, index: int):
|
||||
settings.write('theme_index', index)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_icon_theme_index_changed(self, index: int):
|
||||
settings.write('icon_theme_index', index)
|
||||
util.set_icon_theme()
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_checkbox_hold_shift_to_drag_clicked(self, checked: bool):
|
||||
settings.write("hold_shift_to_drag", checked)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_checkbox_default_fuzzing_pause_clicked(self, checked: bool):
|
||||
settings.write('use_default_fuzzing_pause', checked)
|
||||
self.ui.doubleSpinBoxFuzzingPause.setEnabled(checked)
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_spinbox_fuzzing_pause_value_changed(self, value: float):
|
||||
settings.write("default_fuzzing_pause", int(value))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_gr_python_interpreter_path_edited(self):
|
||||
self.set_gnuradio_status()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_choose_gr_python_interpreter_clicked(self):
|
||||
if sys.platform == "win32":
|
||||
dialog_filter = "Executable (*.exe);;All files (*.*)"
|
||||
else:
|
||||
dialog_filter = ""
|
||||
filename, _ = QFileDialog.getOpenFileName(self, self.tr("Choose python interpreter"), filter=dialog_filter)
|
||||
if filename:
|
||||
self.ui.lineEditGRPythonInterpreter.setText(filename)
|
||||
self.set_gnuradio_status()
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_checkbox_align_labels_clicked(self, checked: bool):
|
||||
settings.write("align_labels", checked)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_rebuild_native_clicked(self):
|
||||
library_dirs = None if not self.ui.lineEditLibDirs.text() \
|
||||
else list(map(str.strip, self.ui.lineEditLibDirs.text().split(",")))
|
||||
include_dirs = None if not self.ui.lineEditIncludeDirs.text() \
|
||||
else list(map(str.strip, self.ui.lineEditIncludeDirs.text().split(",")))
|
||||
|
||||
extensions, _ = ExtensionHelper.get_device_extensions_and_extras(library_dirs=library_dirs, include_dirs=include_dirs)
|
||||
|
||||
self.ui.labelRebuildNativeStatus.setText(self.tr("Rebuilding device extensions..."))
|
||||
QApplication.instance().processEvents()
|
||||
build_cmd = [sys.executable, os.path.realpath(ExtensionHelper.__file__),
|
||||
"build_ext", "--inplace", "-t", tempfile.gettempdir()]
|
||||
if library_dirs:
|
||||
build_cmd.extend(["-L", ":".join(library_dirs)])
|
||||
if include_dirs:
|
||||
build_cmd.extend(["-I", ":".join(include_dirs)])
|
||||
|
||||
subprocess.call([sys.executable, os.path.realpath(ExtensionHelper.__file__), "clean", "--all"])
|
||||
p = subprocess.Popen(build_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
|
||||
num_dots = 1
|
||||
while p.poll() is None:
|
||||
self.ui.labelRebuildNativeStatus.setText(self.tr("Rebuilding device extensions" + ". " * num_dots))
|
||||
QApplication.instance().processEvents()
|
||||
time.sleep(0.1)
|
||||
num_dots %= 10
|
||||
num_dots += 1
|
||||
|
||||
rc = p.returncode
|
||||
if rc == 0:
|
||||
self.ui.labelRebuildNativeStatus.setText(self.tr("<font color=green>"
|
||||
"Rebuilt {0} device extensions. "
|
||||
"</font>"
|
||||
"Please restart URH.".format(len(extensions))))
|
||||
else:
|
||||
self.ui.labelRebuildNativeStatus.setText(self.tr("<font color='red'>"
|
||||
"Failed to rebuild {0} device extensions. "
|
||||
"</font>"
|
||||
"Run URH as root (<b>sudo urh</b>) "
|
||||
"and try again.".format(len(extensions))))
|
||||
|
||||
self.build_log = p.stdout.read().decode()
|
||||
self.ui.btnViewBuildLog.show()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_checkbox_multiple_modulations_clicked(self):
|
||||
settings.write("multiple_modulations", self.ui.checkBoxMultipleModulations.isChecked())
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_view_build_log_clicked(self):
|
||||
if not self.build_log:
|
||||
return
|
||||
|
||||
dialog = util.create_textbox_dialog(self.build_log, "Build log", parent=self)
|
||||
dialog.show()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_label_device_missing_info_link_activated(self, link: str):
|
||||
if link == "health_check":
|
||||
info = ExtensionHelper.perform_health_check()
|
||||
info += "\n" + BackendHandler.perform_soundcard_health_check()
|
||||
|
||||
if util.get_shared_library_path():
|
||||
if sys.platform == "win32":
|
||||
info += "\n\n[INFO] Used DLLs from " + util.get_shared_library_path()
|
||||
else:
|
||||
info += "\n\n[INFO] Used shared libraries from " + util.get_shared_library_path()
|
||||
|
||||
d = util.create_textbox_dialog(info, "Health check for native extensions", self)
|
||||
d.show()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spin_box_font_size_editing_finished(self):
|
||||
settings.write("font_size", self.ui.spinBoxFontSize.value())
|
||||
font = qApp.font()
|
||||
font.setPointSize(self.ui.spinBoxFontSize.value())
|
||||
qApp.setFont(font)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_radio_button_high_modulation_accuracy_clicked(self, checked):
|
||||
if checked:
|
||||
settings.write("modulation_dtype", "float32")
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_radio_button_medium_modulation_accuracy_clicked(self, checked):
|
||||
if checked:
|
||||
settings.write("modulation_dtype", "int16")
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_radio_button_low_modulation_accuracy_clicked(self, checked):
|
||||
if checked:
|
||||
settings.write("modulation_dtype", "int8")
|
||||
|
||||
@staticmethod
|
||||
def write_default_options():
|
||||
keys = settings.all_keys()
|
||||
|
||||
if 'default_view' not in keys:
|
||||
settings.write('default_view', 0)
|
||||
|
||||
if 'num_sending_repeats' not in keys:
|
||||
settings.write('num_sending_repeats', 0)
|
||||
|
||||
if 'show_pause_as_time' not in keys:
|
||||
settings.write('show_pause_as_time', False)
|
||||
|
||||
settings.sync() # Ensure conf dir is created to have field types in place
|
||||
|
||||
if not os.path.isfile(settings.FIELD_TYPE_SETTINGS):
|
||||
FieldType.save_to_xml(FieldType.default_field_types())
|
||||
|
||||
bh = BackendHandler()
|
||||
for be in bh.device_backends.values():
|
||||
be.write_settings()
|
@ -0,0 +1,187 @@
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QRegExp, Qt
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtGui import QRegExpValidator, QCloseEvent
|
||||
from PyQt5.QtWidgets import QDialog, QCompleter, QDirModel
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.dialogs.SpectrumDialogController import SpectrumDialogController
|
||||
from urh.dev import config
|
||||
from urh.models.ParticipantTableModel import ParticipantTableModel
|
||||
from urh.signalprocessing.Participant import Participant
|
||||
from urh.ui.ui_project import Ui_ProjectDialog
|
||||
from urh.util import FileOperator
|
||||
from urh.util.Errors import Errors
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class ProjectDialog(QDialog):
|
||||
def __init__(self, new_project=True, project_manager: ProjectManager = None, parent=None):
|
||||
super().__init__(parent)
|
||||
if not new_project:
|
||||
assert project_manager is not None
|
||||
|
||||
self.ui = Ui_ProjectDialog()
|
||||
self.ui.setupUi(self)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
if new_project:
|
||||
self.participant_table_model = ParticipantTableModel([])
|
||||
else:
|
||||
self.participant_table_model = ParticipantTableModel(project_manager.participants)
|
||||
|
||||
self.ui.spinBoxSampleRate.setValue(project_manager.device_conf["sample_rate"])
|
||||
self.ui.spinBoxFreq.setValue(project_manager.device_conf["frequency"])
|
||||
self.ui.spinBoxBandwidth.setValue(project_manager.device_conf["bandwidth"])
|
||||
self.ui.spinBoxGain.setValue(project_manager.device_conf.get("gain", config.DEFAULT_GAIN))
|
||||
self.ui.txtEdDescription.setPlainText(project_manager.description)
|
||||
self.ui.lineEdit_Path.setText(project_manager.project_path)
|
||||
self.ui.lineEditBroadcastAddress.setText(project_manager.broadcast_address_hex)
|
||||
|
||||
self.ui.btnSelectPath.hide()
|
||||
self.ui.lineEdit_Path.setDisabled(True)
|
||||
self.setWindowTitle("Edit project settings")
|
||||
self.ui.lNewProject.setText("Edit project")
|
||||
|
||||
self.ui.tblParticipants.setModel(self.participant_table_model)
|
||||
self.participant_table_model.update()
|
||||
|
||||
self.ui.lineEditBroadcastAddress.setValidator(QRegExpValidator(QRegExp("([a-fA-F ]|[0-9]){,}")))
|
||||
|
||||
self.sample_rate = self.ui.spinBoxSampleRate.value()
|
||||
self.freq = self.ui.spinBoxFreq.value()
|
||||
self.bandwidth = self.ui.spinBoxBandwidth.value()
|
||||
self.gain = self.ui.spinBoxGain.value()
|
||||
self.description = self.ui.txtEdDescription.toPlainText()
|
||||
self.broadcast_address_hex = self.ui.lineEditBroadcastAddress.text()
|
||||
|
||||
self.path = self.ui.lineEdit_Path.text()
|
||||
self.new_project = new_project
|
||||
self.committed = False
|
||||
self.setModal(True)
|
||||
|
||||
completer = QCompleter()
|
||||
completer.setModel(QDirModel(completer))
|
||||
self.ui.lineEdit_Path.setCompleter(completer)
|
||||
|
||||
self.create_connects()
|
||||
# add two participants
|
||||
if self.participant_table_model.rowCount() == 0 and new_project:
|
||||
self.ui.btnAddParticipant.click()
|
||||
self.ui.btnAddParticipant.click()
|
||||
|
||||
if new_project:
|
||||
self.ui.lineEdit_Path.setText(os.path.realpath(os.path.join(os.curdir, "new")))
|
||||
|
||||
self.on_line_edit_path_text_edited()
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
@property
|
||||
def participants(self):
|
||||
"""
|
||||
|
||||
:rtype: list of Participant
|
||||
"""
|
||||
return self.participant_table_model.participants
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.spinBoxFreq.valueChanged.connect(self.on_spin_box_frequency_value_changed)
|
||||
self.ui.spinBoxSampleRate.valueChanged.connect(self.on_spin_box_sample_rate_value_changed)
|
||||
self.ui.spinBoxBandwidth.valueChanged.connect(self.on_spin_box_bandwidth_value_changed)
|
||||
self.ui.spinBoxGain.valueChanged.connect(self.on_spin_box_gain_value_changed)
|
||||
self.ui.txtEdDescription.textChanged.connect(self.on_txt_edit_description_text_changed)
|
||||
self.ui.lineEditBroadcastAddress.textEdited.connect(self.on_line_edit_broadcast_address_text_edited)
|
||||
|
||||
self.ui.btnAddParticipant.clicked.connect(self.ui.tblParticipants.on_add_action_triggered)
|
||||
self.ui.btnRemoveParticipant.clicked.connect(self.ui.tblParticipants.on_remove_action_triggered)
|
||||
self.ui.btnUp.clicked.connect(self.ui.tblParticipants.on_move_up_action_triggered)
|
||||
self.ui.btnDown.clicked.connect(self.ui.tblParticipants.on_move_down_action_triggered)
|
||||
|
||||
self.ui.lineEdit_Path.textEdited.connect(self.on_line_edit_path_text_edited)
|
||||
self.ui.buttonBox.accepted.connect(self.on_button_box_accepted)
|
||||
self.ui.buttonBox.rejected.connect(self.reject)
|
||||
self.ui.btnSelectPath.clicked.connect(self.on_btn_select_path_clicked)
|
||||
self.ui.lOpenSpectrumAnalyzer.linkActivated.connect(self.on_spectrum_analyzer_link_activated)
|
||||
|
||||
def set_path(self, path):
|
||||
self.path = path
|
||||
self.ui.lineEdit_Path.setText(self.path)
|
||||
name = os.path.basename(os.path.normpath(self.path))
|
||||
self.ui.lblName.setText(name)
|
||||
|
||||
self.ui.lblNewPath.setVisible(not os.path.isdir(self.path))
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_spin_box_sample_rate_value_changed(self, value: float):
|
||||
self.sample_rate = value
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_spin_box_frequency_value_changed(self, value: float):
|
||||
self.freq = value
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_spin_box_bandwidth_value_changed(self, value: float):
|
||||
self.bandwidth = value
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spin_box_gain_value_changed(self, value: int):
|
||||
self.gain = value
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_path_text_edited(self):
|
||||
self.set_path(self.ui.lineEdit_Path.text())
|
||||
|
||||
@pyqtSlot()
|
||||
def on_txt_edit_description_text_changed(self):
|
||||
self.description = self.ui.txtEdDescription.toPlainText()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_button_box_accepted(self):
|
||||
self.path = os.path.realpath(self.path)
|
||||
if not os.path.exists(self.path):
|
||||
try:
|
||||
os.makedirs(self.path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Path should be created now, if not raise Error
|
||||
if not os.path.exists(self.path):
|
||||
Errors.invalid_path(self.path)
|
||||
return
|
||||
|
||||
self.committed = True
|
||||
self.accept()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_line_edit_broadcast_address_text_edited(self, value: str):
|
||||
self.broadcast_address_hex = value
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_select_path_clicked(self):
|
||||
directory = FileOperator.get_directory()
|
||||
if directory:
|
||||
self.set_path(directory)
|
||||
|
||||
@pyqtSlot(dict)
|
||||
def set_recording_params_from_spectrum_analyzer_link(self, args: dict):
|
||||
self.ui.spinBoxFreq.setValue(args["frequency"])
|
||||
self.ui.spinBoxSampleRate.setValue(args["sample_rate"])
|
||||
self.ui.spinBoxBandwidth.setValue(args["bandwidth"])
|
||||
self.ui.spinBoxGain.setValue(args.get("gain", config.DEFAULT_GAIN))
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_spectrum_analyzer_link_activated(self, link: str):
|
||||
if link == "open_spectrum_analyzer":
|
||||
r = SpectrumDialogController(ProjectManager(None), parent=self)
|
||||
if r.has_empty_device_list:
|
||||
Errors.no_device()
|
||||
r.close()
|
||||
return
|
||||
|
||||
r.device_parameters_changed.connect(self.set_recording_params_from_spectrum_analyzer_link)
|
||||
r.show()
|
@ -0,0 +1,128 @@
|
||||
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtGui import QKeyEvent, QCloseEvent
|
||||
from PyQt5.QtWidgets import QDialog, QHeaderView, QAbstractItemView
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.widgets.ChecksumWidget import ChecksumWidget
|
||||
from urh.models.PLabelTableModel import PLabelTableModel
|
||||
from urh.signalprocessing.ChecksumLabel import ChecksumLabel
|
||||
from urh.signalprocessing.FieldType import FieldType
|
||||
from urh.signalprocessing.Message import Message
|
||||
from urh.signalprocessing.MessageType import MessageType
|
||||
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
|
||||
from urh.simulator.SimulatorProtocolLabel import SimulatorProtocolLabel
|
||||
from urh.ui.delegates.CheckBoxDelegate import CheckBoxDelegate
|
||||
from urh.ui.delegates.ComboBoxDelegate import ComboBoxDelegate
|
||||
from urh.ui.delegates.SpinBoxDelegate import SpinBoxDelegate
|
||||
from urh.ui.ui_properties_dialog import Ui_DialogLabels
|
||||
from urh.util import util
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class ProtocolLabelDialog(QDialog):
|
||||
apply_decoding_changed = pyqtSignal(ProtocolLabel, MessageType)
|
||||
|
||||
SPECIAL_CONFIG_TYPES = [FieldType.Function.CHECKSUM]
|
||||
|
||||
def __init__(self, message: Message, viewtype: int, selected_index=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogLabels()
|
||||
self.ui.setupUi(self)
|
||||
util.set_splitter_stylesheet(self.ui.splitter)
|
||||
|
||||
field_types = FieldType.load_from_xml()
|
||||
self.model = PLabelTableModel(message, field_types)
|
||||
|
||||
self.ui.tblViewProtoLabels.setItemDelegateForColumn(0, ComboBoxDelegate([ft.caption for ft in field_types],
|
||||
is_editable=True,
|
||||
return_index=False, parent=self))
|
||||
self.ui.tblViewProtoLabels.setItemDelegateForColumn(1, SpinBoxDelegate(1, len(message), self))
|
||||
self.ui.tblViewProtoLabels.setItemDelegateForColumn(2, SpinBoxDelegate(1, len(message), self))
|
||||
self.ui.tblViewProtoLabels.setItemDelegateForColumn(3,
|
||||
ComboBoxDelegate([""] * len(settings.LABEL_COLORS),
|
||||
colors=settings.LABEL_COLORS,
|
||||
parent=self))
|
||||
self.ui.tblViewProtoLabels.setItemDelegateForColumn(4, CheckBoxDelegate(self))
|
||||
self.ui.tblViewProtoLabels.setModel(self.model)
|
||||
self.ui.tblViewProtoLabels.setEditTriggers(QAbstractItemView.AllEditTriggers)
|
||||
|
||||
self.ui.tblViewProtoLabels.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
|
||||
self.ui.tblViewProtoLabels.resizeColumnsToContents()
|
||||
self.setWindowFlags(Qt.Window)
|
||||
self.setWindowTitle(self.tr("Edit Protocol Labels From Message Type %s") % message.message_type.name)
|
||||
|
||||
self.configure_special_config_tabs()
|
||||
self.ui.splitter.setSizes([int(self.height() / 2), int(self.height() / 2)])
|
||||
|
||||
self.create_connects()
|
||||
|
||||
if selected_index is not None:
|
||||
self.ui.tblViewProtoLabels.setCurrentIndex(self.model.index(selected_index, 0))
|
||||
|
||||
self.ui.cbProtoView.setCurrentIndex(viewtype)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
for i in range(self.model.rowCount()):
|
||||
self.open_editors(i)
|
||||
|
||||
def configure_special_config_tabs(self):
|
||||
self.ui.tabWidgetAdvancedSettings.clear()
|
||||
for lbl in self.model.message_type: # type: ProtocolLabel
|
||||
if isinstance(lbl, SimulatorProtocolLabel):
|
||||
lbl = lbl.label
|
||||
|
||||
if lbl.field_type is not None and lbl.field_type.function in self.SPECIAL_CONFIG_TYPES:
|
||||
if isinstance(lbl, ChecksumLabel):
|
||||
w = ChecksumWidget(lbl, self.model.message, self.model.proto_view)
|
||||
self.ui.tabWidgetAdvancedSettings.addTab(w, lbl.name)
|
||||
else:
|
||||
logger.error("No Special Config Dialog for field type " + lbl.field_type.caption)
|
||||
|
||||
if self.ui.tabWidgetAdvancedSettings.count() > 0:
|
||||
self.ui.tabWidgetAdvancedSettings.setCurrentIndex(0)
|
||||
self.ui.tabWidgetAdvancedSettings.setFocus()
|
||||
|
||||
self.ui.groupBoxAdvancedSettings.setVisible(self.ui.tabWidgetAdvancedSettings.count() > 0)
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.btnConfirm.clicked.connect(self.confirm)
|
||||
self.ui.cbProtoView.currentIndexChanged.connect(self.set_view_index)
|
||||
self.model.apply_decoding_changed.connect(self.on_apply_decoding_changed)
|
||||
self.model.special_status_label_changed.connect(self.on_label_special_status_changed)
|
||||
|
||||
def open_editors(self, row):
|
||||
self.ui.tblViewProtoLabels.openPersistentEditor(self.model.index(row, 4))
|
||||
|
||||
def keyPressEvent(self, event: QKeyEvent):
|
||||
if event.key() == Qt.Key_Enter:
|
||||
event.ignore()
|
||||
else:
|
||||
event.accept()
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot()
|
||||
def confirm(self):
|
||||
self.close()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def set_view_index(self, ind):
|
||||
self.model.proto_view = ind
|
||||
self.model.update()
|
||||
|
||||
for i in range(self.ui.tabWidgetAdvancedSettings.count()):
|
||||
self.ui.tabWidgetAdvancedSettings.widget(i).proto_view = ind
|
||||
|
||||
@pyqtSlot(ProtocolLabel)
|
||||
def on_apply_decoding_changed(self, lbl: ProtocolLabel):
|
||||
self.apply_decoding_changed.emit(lbl, self.model.message_type)
|
||||
|
||||
@pyqtSlot(ProtocolLabel)
|
||||
def on_label_special_status_changed(self, lbl: ProtocolLabel):
|
||||
self.configure_special_config_tabs()
|
@ -0,0 +1,138 @@
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtGui import QIcon, QCloseEvent
|
||||
|
||||
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
|
||||
from urh.controller.widgets.SniffSettingsWidget import SniffSettingsWidget
|
||||
from urh.ui.painting.LiveSceneManager import LiveSceneManager
|
||||
from urh.ui.painting.SniffSceneManager import SniffSceneManager
|
||||
from urh.util import util
|
||||
|
||||
|
||||
class ProtocolSniffDialog(SendRecvDialog):
|
||||
protocol_accepted = pyqtSignal(list)
|
||||
|
||||
def __init__(self, project_manager, signal=None, signals=None, parent=None, testing_mode=False):
|
||||
super().__init__(project_manager, is_tx=False, parent=parent, testing_mode=testing_mode)
|
||||
|
||||
self.graphics_view = self.ui.graphicsView_sniff_Preview
|
||||
self.ui.stackedWidget.setCurrentWidget(self.ui.page_sniff)
|
||||
self.hide_send_ui_items()
|
||||
self.hide_receive_ui_items()
|
||||
self.ui.sliderYscale.hide()
|
||||
self.ui.label_y_scale.hide()
|
||||
|
||||
signals = [] if signals is None else signals
|
||||
|
||||
self.sniff_settings_widget = SniffSettingsWidget(project_manager=project_manager,
|
||||
device_name=self.selected_device_name,
|
||||
signal=signal, signals=signals,
|
||||
backend_handler=self.backend_handler)
|
||||
self.ui.scrollAreaWidgetContents_2.layout().insertWidget(1, self.sniff_settings_widget)
|
||||
self.sniff_settings_widget.ui.btn_sniff_use_signal.setAutoDefault(False)
|
||||
|
||||
self.sniffer = self.sniff_settings_widget.sniffer
|
||||
self.setWindowTitle(self.tr("Sniff Protocol"))
|
||||
self.setWindowIcon(QIcon.fromTheme(":/icons/icons/sniffer.svg"))
|
||||
|
||||
self.ui.txtEd_sniff_Preview.setFont(util.get_monospace_font())
|
||||
|
||||
# set really in on_device_started
|
||||
self.scene_manager = None # type: LiveSceneManager
|
||||
self.create_connects()
|
||||
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
|
||||
|
||||
@property
|
||||
def view_type(self) -> int:
|
||||
return self.sniff_settings_widget.ui.comboBox_sniff_viewtype.currentIndex()
|
||||
|
||||
@property
|
||||
def show_timestamp(self) -> bool:
|
||||
return self.sniff_settings_widget.ui.checkBox_sniff_Timestamp.isChecked()
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
self.sniff_settings_widget.emit_sniff_parameters_changed()
|
||||
super().closeEvent(event)
|
||||
|
||||
def create_connects(self):
|
||||
super().create_connects()
|
||||
self.ui.btnAccept.clicked.connect(self.on_btn_accept_clicked)
|
||||
self.sniff_settings_widget.sniff_parameters_changed.connect(self.device_parameters_changed.emit)
|
||||
|
||||
self.sniff_settings_widget.sniff_setting_edited.connect(self.on_sniff_setting_edited)
|
||||
self.sniff_settings_widget.sniff_file_edited.connect(self.on_sniff_file_edited)
|
||||
self.sniffer.message_sniffed.connect(self.on_message_sniffed)
|
||||
self.sniffer.qt_signals.sniff_device_errors_changed.connect(self.on_device_errors_changed)
|
||||
|
||||
def init_device(self):
|
||||
self.sniffer.device_name = self.selected_device_name
|
||||
self.device = self.sniffer.rcv_device
|
||||
|
||||
self._create_device_connects()
|
||||
self.scene_manager = SniffSceneManager(np.array([], dtype=self.device.data_type), parent=self)
|
||||
|
||||
def emit_editing_finished_signals(self):
|
||||
super().emit_editing_finished_signals()
|
||||
self.sniff_settings_widget.emit_editing_finished_signals()
|
||||
|
||||
def update_view(self):
|
||||
if super().update_view():
|
||||
self.scene_manager.end = self.device.current_index
|
||||
self.scene_manager.init_scene()
|
||||
self.scene_manager.show_full_scene()
|
||||
self.graphics_view.update()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_started(self):
|
||||
self.scene_manager.data_array = self.device.data.real if hasattr(self.device.data, "real") else None
|
||||
|
||||
super().on_device_started()
|
||||
|
||||
self.ui.btnStart.setEnabled(False)
|
||||
self.set_device_ui_items_enabled(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_sniff_setting_edited(self):
|
||||
self.ui.txtEd_sniff_Preview.setPlainText(self.sniffer.decoded_to_string(self.view_type,
|
||||
include_timestamps=self.show_timestamp))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_start_clicked(self):
|
||||
super().on_start_clicked()
|
||||
self.sniffer.sniff()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_stop_clicked(self):
|
||||
self.sniffer.stop()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_clear_clicked(self):
|
||||
self.ui.txtEd_sniff_Preview.clear()
|
||||
self.scene_manager.clear_path()
|
||||
self.device.current_index = 0
|
||||
self.sniffer.clear()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_message_sniffed(self, index: int):
|
||||
try:
|
||||
msg = self.sniffer.messages[index]
|
||||
except IndexError:
|
||||
return
|
||||
new_data = self.sniffer.message_to_string(msg, self.view_type, include_timestamps=self.show_timestamp)
|
||||
if new_data.strip():
|
||||
self.ui.txtEd_sniff_Preview.appendPlainText(new_data)
|
||||
self.ui.txtEd_sniff_Preview.verticalScrollBar().setValue(
|
||||
self.ui.txtEd_sniff_Preview.verticalScrollBar().maximum())
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_accept_clicked(self):
|
||||
self.protocol_accepted.emit(self.sniffer.messages)
|
||||
self.close()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_device_errors_changed(self, txt: str):
|
||||
self.ui.txtEditErrors.append(txt)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_sniff_file_edited(self):
|
||||
self.ui.btnAccept.setDisabled(bool(self.sniffer.sniff_file))
|
@ -0,0 +1,110 @@
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
|
||||
from urh.dev.VirtualDevice import Mode, VirtualDevice
|
||||
from urh.ui.painting.LiveSceneManager import LiveSceneManager
|
||||
from urh.util import FileOperator
|
||||
from urh.util.Formatter import Formatter
|
||||
from datetime import datetime
|
||||
|
||||
class ReceiveDialog(SendRecvDialog):
|
||||
files_recorded = pyqtSignal(list, float)
|
||||
|
||||
def __init__(self, project_manager, parent=None, testing_mode=False):
|
||||
try:
|
||||
super().__init__(project_manager, is_tx=False, parent=parent, testing_mode=testing_mode)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
self.graphics_view = self.ui.graphicsViewReceive
|
||||
self.ui.stackedWidget.setCurrentWidget(self.ui.page_receive)
|
||||
self.hide_send_ui_items()
|
||||
self.already_saved = True
|
||||
self.recorded_files = []
|
||||
|
||||
self.setWindowTitle("Record Signal")
|
||||
self.setWindowIcon(QIcon.fromTheme("media-record"))
|
||||
|
||||
# set really in on_device_started
|
||||
self.scene_manager = None # type: LiveSceneManager
|
||||
self.create_connects()
|
||||
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
|
||||
|
||||
def create_connects(self):
|
||||
super().create_connects()
|
||||
self.ui.btnSave.clicked.connect(self.on_save_clicked)
|
||||
|
||||
def save_before_close(self):
|
||||
if not self.already_saved and self.device.current_index > 0:
|
||||
reply = QMessageBox.question(self, self.tr("Save data?"),
|
||||
self.tr("Do you want to save the data you have captured so far?"),
|
||||
QMessageBox.Yes | QMessageBox.No | QMessageBox.Abort)
|
||||
if reply == QMessageBox.Yes:
|
||||
self.on_save_clicked()
|
||||
elif reply == QMessageBox.Abort:
|
||||
return False
|
||||
|
||||
try:
|
||||
sample_rate = self.device.sample_rate
|
||||
except:
|
||||
sample_rate = 1e6
|
||||
|
||||
self.files_recorded.emit(self.recorded_files, sample_rate)
|
||||
return True
|
||||
|
||||
def update_view(self):
|
||||
if super().update_view():
|
||||
self.scene_manager.end = self.device.current_index
|
||||
self.scene_manager.init_scene()
|
||||
self.scene_manager.show_full_scene()
|
||||
self.graphics_view.update()
|
||||
|
||||
def init_device(self):
|
||||
self.device = VirtualDevice(self.backend_handler, self.selected_device_name, Mode.receive,
|
||||
device_ip="192.168.10.2", parent=self)
|
||||
self._create_device_connects()
|
||||
self.scene_manager = LiveSceneManager(np.array([], dtype=self.device.data_type), parent=self)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_start_clicked(self):
|
||||
super().on_start_clicked()
|
||||
self.device.start()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_started(self):
|
||||
self.scene_manager.plot_data = self.device.data.real if self.device.data is not None else None
|
||||
|
||||
super().on_device_started()
|
||||
|
||||
self.already_saved = False
|
||||
self.ui.btnStart.setEnabled(False)
|
||||
self.set_device_ui_items_enabled(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_clear_clicked(self):
|
||||
self.scene_manager.clear_path()
|
||||
self.reset()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_save_clicked(self):
|
||||
data = self.device.data[:self.device.current_index]
|
||||
|
||||
dev = self.device
|
||||
big_val = Formatter.big_value_with_suffix
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
initial_name = "{0}-{1}-{2}Hz-{3}Sps".format(dev.name, timestamp,
|
||||
big_val(dev.frequency), big_val(dev.sample_rate))
|
||||
|
||||
if dev.bandwidth_is_adjustable:
|
||||
initial_name += "-{}Hz".format(big_val(dev.bandwidth))
|
||||
|
||||
initial_name = initial_name.replace(Formatter.local_decimal_seperator(), "_").replace("_000", "")
|
||||
|
||||
filename = FileOperator.ask_signal_file_name_and_save(initial_name, data,
|
||||
sample_rate=dev.sample_rate, parent=self)
|
||||
self.already_saved = True
|
||||
if filename is not None and filename not in self.recorded_files:
|
||||
self.recorded_files.append(filename)
|
@ -0,0 +1,153 @@
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QBrush, QColor, QIcon, QPen
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
|
||||
from urh.dev.VirtualDevice import VirtualDevice, Mode
|
||||
from urh.signalprocessing.IQArray import IQArray
|
||||
from urh.signalprocessing.Signal import Signal
|
||||
from urh.ui.painting.SignalSceneManager import SignalSceneManager
|
||||
from urh.util import FileOperator
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class SendDialog(SendRecvDialog):
|
||||
def __init__(self, project_manager, modulated_data, modulation_msg_indices=None, continuous_send_mode=False,
|
||||
parent=None, testing_mode=False):
|
||||
super().__init__(project_manager, is_tx=True, continuous_send_mode=continuous_send_mode,
|
||||
parent=parent, testing_mode=testing_mode)
|
||||
|
||||
self.graphics_view = self.ui.graphicsViewSend
|
||||
self.ui.stackedWidget.setCurrentWidget(self.ui.page_send)
|
||||
self.hide_receive_ui_items()
|
||||
|
||||
self.ui.btnStart.setIcon(QIcon.fromTheme("media-playback-start"))
|
||||
self.setWindowTitle("Send Signal")
|
||||
self.setWindowIcon(QIcon.fromTheme("media-playback-start"))
|
||||
self.ui.btnStart.setToolTip("Send data")
|
||||
self.ui.btnStop.setToolTip("Stop sending")
|
||||
self.device_is_sending = False
|
||||
self.modulation_msg_indices = modulation_msg_indices
|
||||
|
||||
if self.modulation_msg_indices is not None:
|
||||
self.ui.progressBarMessage.setMaximum(len(self.modulation_msg_indices))
|
||||
else:
|
||||
self.ui.progressBarMessage.hide()
|
||||
self.ui.labelCurrentMessage.hide()
|
||||
|
||||
if modulated_data is not None:
|
||||
assert isinstance(modulated_data, IQArray)
|
||||
# modulated_data is none in continuous send mode
|
||||
self.ui.progressBarSample.setMaximum(len(modulated_data))
|
||||
samp_rate = self.device_settings_widget.ui.spinBoxSampleRate.value()
|
||||
signal = Signal("", "Modulated Preview", sample_rate=samp_rate)
|
||||
signal.iq_array = modulated_data
|
||||
self.scene_manager = SignalSceneManager(signal, parent=self)
|
||||
self.send_indicator = self.scene_manager.scene.addRect(0, -2, 0, 4,
|
||||
QPen(QColor(Qt.transparent), 0),
|
||||
QBrush(settings.SEND_INDICATOR_COLOR))
|
||||
self.send_indicator.stackBefore(self.scene_manager.scene.selection_area)
|
||||
self.scene_manager.init_scene()
|
||||
self.graphics_view.set_signal(signal)
|
||||
self.graphics_view.sample_rate = samp_rate
|
||||
|
||||
self.create_connects()
|
||||
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
|
||||
|
||||
def create_connects(self):
|
||||
super().create_connects()
|
||||
|
||||
self.graphics_view.save_as_clicked.connect(self.on_graphics_view_save_as_clicked)
|
||||
self.scene_manager.signal.data_edited.connect(self.on_signal_data_edited)
|
||||
|
||||
def _update_send_indicator(self, width: int):
|
||||
y, h = self.ui.graphicsViewSend.view_rect().y(), self.ui.graphicsViewSend.view_rect().height()
|
||||
self.send_indicator.setRect(0, y - h, width, 2 * h + abs(y))
|
||||
|
||||
def set_current_message_progress_bar_value(self, current_sample: int):
|
||||
if self.modulation_msg_indices is not None:
|
||||
msg_index = next((i for i, sample in enumerate(self.modulation_msg_indices) if sample >= current_sample),
|
||||
len(self.modulation_msg_indices))
|
||||
self.ui.progressBarMessage.setValue(msg_index + 1)
|
||||
|
||||
def update_view(self):
|
||||
if super().update_view():
|
||||
self._update_send_indicator(self.device.current_index)
|
||||
self.ui.progressBarSample.setValue(self.device.current_index)
|
||||
self.set_current_message_progress_bar_value(self.device.current_index)
|
||||
|
||||
if not self.device.sending_finished:
|
||||
self.ui.lblCurrentRepeatValue.setText(str(self.device.current_iteration + 1))
|
||||
else:
|
||||
self.ui.btnStop.click()
|
||||
self.ui.lblCurrentRepeatValue.setText("Sending finished")
|
||||
|
||||
def init_device(self):
|
||||
device_name = self.selected_device_name
|
||||
num_repeats = self.device_settings_widget.ui.spinBoxNRepeat.value()
|
||||
sts = self.scene_manager.signal.iq_array
|
||||
|
||||
self.device = VirtualDevice(self.backend_handler, device_name, Mode.send, samples_to_send=sts,
|
||||
device_ip="192.168.10.2", sending_repeats=num_repeats, parent=self)
|
||||
self._create_device_connects()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_graphics_view_save_as_clicked(self):
|
||||
filename = FileOperator.ask_save_file_name("signal.complex")
|
||||
if filename:
|
||||
try:
|
||||
try:
|
||||
self.scene_manager.signal.sample_rate = self.device.sample_rate
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
self.scene_manager.signal.save_as(filename)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, self.tr("Error saving signal"), e.args[0])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_signal_data_edited(self):
|
||||
signal = self.scene_manager.signal
|
||||
self.ui.progressBarSample.setMaximum(signal.num_samples)
|
||||
self.device.samples_to_send = signal.iq_array.data
|
||||
self.scene_manager.init_scene()
|
||||
self.ui.graphicsViewSend.redraw_view()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_start_clicked(self):
|
||||
super().on_start_clicked()
|
||||
if self.ui.progressBarSample.value() >= self.ui.progressBarSample.maximum() - 1:
|
||||
self.on_clear_clicked()
|
||||
|
||||
if self.device_is_sending:
|
||||
self.device.stop("Sending paused by user")
|
||||
else:
|
||||
self.device.start()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_stop_clicked(self):
|
||||
super().on_stop_clicked()
|
||||
self.on_clear_clicked()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_stopped(self):
|
||||
super().on_device_stopped()
|
||||
self.ui.btnStart.setIcon(QIcon.fromTheme("media-playback-start"))
|
||||
self.ui.btnStart.setText("Start")
|
||||
self.ui.btnStart.setToolTip("Start sending")
|
||||
self.device_is_sending = False
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_started(self):
|
||||
super().on_device_started()
|
||||
self.device_is_sending = True
|
||||
self.ui.btnStart.setEnabled(True)
|
||||
self.ui.btnStart.setIcon(QIcon.fromTheme("media-playback-pause"))
|
||||
self.ui.btnStart.setText("Pause")
|
||||
self.set_device_ui_items_enabled(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_clear_clicked(self):
|
||||
self._update_send_indicator(0)
|
||||
self.reset()
|
@ -0,0 +1,287 @@
|
||||
import locale
|
||||
import time
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QTimer, pyqtSignal, Qt
|
||||
from PyQt5.QtGui import QCloseEvent, QTransform
|
||||
from PyQt5.QtWidgets import QDialog, QGraphicsView
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.widgets.DeviceSettingsWidget import DeviceSettingsWidget
|
||||
from urh.dev.BackendHandler import BackendHandler, Backends
|
||||
from urh.dev.VirtualDevice import VirtualDevice
|
||||
from urh.plugins.NetworkSDRInterface.NetworkSDRInterfacePlugin import NetworkSDRInterfacePlugin
|
||||
from urh.ui.ui_send_recv import Ui_SendRecvDialog
|
||||
from urh.util import util
|
||||
from urh.util.Errors import Errors
|
||||
from urh.util.Formatter import Formatter
|
||||
from urh.util.Logger import logger
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class SendRecvDialog(QDialog):
|
||||
device_parameters_changed = pyqtSignal(dict)
|
||||
|
||||
def __init__(self, project_manager: ProjectManager, is_tx: bool, continuous_send_mode=False, parent=None, testing_mode=False):
|
||||
super().__init__(parent)
|
||||
self.is_tx = is_tx
|
||||
self.update_interval = 25
|
||||
|
||||
# This flag is needed. Will cause memory leak otherwise.
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
|
||||
self.setWindowFlags(Qt.Window)
|
||||
self.testing_mode = testing_mode
|
||||
|
||||
self.ui = Ui_SendRecvDialog()
|
||||
self.ui.setupUi(self)
|
||||
util.set_splitter_stylesheet(self.ui.splitter)
|
||||
|
||||
self.ui.txtEditErrors.setFont(util.get_monospace_font())
|
||||
|
||||
self.graphics_view = None # type: QGraphicsView
|
||||
|
||||
self.backend_handler = BackendHandler()
|
||||
|
||||
self.ui.btnStop.setEnabled(False)
|
||||
self.ui.btnSave.setEnabled(False)
|
||||
|
||||
self.start = 0
|
||||
|
||||
self.device_settings_widget = DeviceSettingsWidget(project_manager, is_tx,
|
||||
backend_handler=self.backend_handler,
|
||||
continuous_send_mode=continuous_send_mode)
|
||||
self.ui.scrollAreaWidgetContents_2.layout().insertWidget(0, self.device_settings_widget)
|
||||
|
||||
if testing_mode:
|
||||
self.device_settings_widget.ui.cbDevice.setCurrentText(NetworkSDRInterfacePlugin.NETWORK_SDR_NAME)
|
||||
|
||||
self.timer = QTimer(self)
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
self.ui.splitter.setSizes([int(0.4 * self.width()), int(0.6 * self.width())])
|
||||
|
||||
self.current_y_slider_value = 1
|
||||
|
||||
@property
|
||||
def is_rx(self) -> bool:
|
||||
return not self.is_tx
|
||||
|
||||
@property
|
||||
def has_empty_device_list(self):
|
||||
return self.device_settings_widget.ui.cbDevice.count() == 0
|
||||
|
||||
@property
|
||||
def device(self) -> VirtualDevice:
|
||||
return self.device_settings_widget.device
|
||||
|
||||
@device.setter
|
||||
def device(self, value):
|
||||
self.device_settings_widget.device = value
|
||||
|
||||
@property
|
||||
def selected_device_name(self) -> str:
|
||||
return self.device_settings_widget.ui.cbDevice.currentText()
|
||||
|
||||
def _eliminate_graphic_view(self):
|
||||
if self.graphics_view is not None:
|
||||
self.graphics_view.eliminate()
|
||||
|
||||
self.graphics_view = None
|
||||
|
||||
def hide_send_ui_items(self):
|
||||
for item in ("lblCurrentRepeatValue", "progressBarMessage",
|
||||
"lblRepeatText", "lSamplesSentText", "progressBarSample", "labelCurrentMessage"):
|
||||
getattr(self.ui, item).hide()
|
||||
|
||||
def hide_receive_ui_items(self):
|
||||
for item in ("lSamplesCaptured", "lSamplesCapturedText", "lSignalSize", "lSignalSizeText",
|
||||
"lTime", "lTimeText", "btnSave", "labelReceiveBufferFull", "lReceiveBufferFullText"):
|
||||
getattr(self.ui, item).hide()
|
||||
|
||||
def set_device_ui_items_enabled(self, enabled: bool):
|
||||
self.device_settings_widget.setEnabled(enabled)
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.btnStart.clicked.connect(self.on_start_clicked)
|
||||
self.ui.btnStop.clicked.connect(self.on_stop_clicked)
|
||||
self.ui.btnClear.clicked.connect(self.on_clear_clicked)
|
||||
|
||||
self.timer.timeout.connect(self.update_view)
|
||||
self.ui.sliderYscale.valueChanged.connect(self.on_slider_y_scale_value_changed)
|
||||
|
||||
self.device_settings_widget.selected_device_changed.connect(self.on_selected_device_changed)
|
||||
self.device_settings_widget.device_parameters_changed.connect(self.device_parameters_changed.emit)
|
||||
|
||||
def _create_device_connects(self):
|
||||
self.device.stopped.connect(self.on_device_stopped)
|
||||
self.device.started.connect(self.on_device_started)
|
||||
self.device.sender_needs_restart.connect(self._restart_device_thread)
|
||||
|
||||
def reset(self):
|
||||
self.device.current_index = 0
|
||||
self.device.current_iteration = 0
|
||||
self.ui.lSamplesCaptured.setText("0")
|
||||
self.ui.lSignalSize.setText("0")
|
||||
self.ui.lTime.setText("0")
|
||||
self.ui.lblCurrentRepeatValue.setText("-")
|
||||
self.ui.progressBarSample.setValue(0)
|
||||
self.ui.progressBarMessage.setValue(0)
|
||||
self.ui.btnSave.setEnabled(False)
|
||||
|
||||
def init_device(self):
|
||||
pass
|
||||
|
||||
def save_before_close(self):
|
||||
return True
|
||||
|
||||
def emit_editing_finished_signals(self):
|
||||
self.device_settings_widget.emit_editing_finished_signals()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_selected_device_changed(self):
|
||||
if hasattr(self.scene_manager, "plot_data"):
|
||||
self.scene_manager.plot_data = None
|
||||
|
||||
self.init_device()
|
||||
|
||||
self.graphics_view.scene_manager = self.scene_manager
|
||||
self.graphics_view.setScene(self.scene_manager.scene)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_start_clicked(self):
|
||||
self.emit_editing_finished_signals()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_stop_clicked(self):
|
||||
self.device.stop("Stopped receiving: Stop button clicked")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_stopped(self):
|
||||
if self.graphics_view is not None:
|
||||
self.graphics_view.capturing_data = False
|
||||
self.set_device_ui_items_enabled(True)
|
||||
self.ui.btnStart.setEnabled(True)
|
||||
self.ui.btnStop.setEnabled(False)
|
||||
self.ui.btnSave.setEnabled(self.device.current_index > 0)
|
||||
self.device_settings_widget.ui.comboBoxDeviceIdentifier.setEnabled(True)
|
||||
self.device_settings_widget.ui.btnRefreshDeviceIdentifier.setEnabled(True)
|
||||
self.device_settings_widget.set_bandwidth_status()
|
||||
|
||||
self.timer.stop()
|
||||
self.update_view()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_started(self):
|
||||
self.ui.txtEditErrors.clear()
|
||||
if self.graphics_view is not None:
|
||||
self.graphics_view.capturing_data = True
|
||||
self.ui.btnSave.setEnabled(False)
|
||||
self.ui.btnStart.setEnabled(False)
|
||||
self.ui.btnStop.setEnabled(True)
|
||||
self.device_settings_widget.ui.comboBoxDeviceIdentifier.setEnabled(False)
|
||||
self.device_settings_widget.ui.btnRefreshDeviceIdentifier.setEnabled(False)
|
||||
|
||||
self.timer.start(self.update_interval)
|
||||
|
||||
def __parse_error_messages(self, messages):
|
||||
messages = messages.lower()
|
||||
|
||||
if "no devices found for" in messages:
|
||||
self.device.stop_on_error("Could not establish connection to USRP")
|
||||
Errors.usrp_found()
|
||||
self.on_clear_clicked()
|
||||
|
||||
elif any(e in messages for e in ("hackrf_error_not_found", "hackrf_error_libusb")):
|
||||
self.device.stop_on_error("Could not establish connection to HackRF")
|
||||
Errors.hackrf_not_found()
|
||||
self.on_clear_clicked()
|
||||
|
||||
elif "no module named gnuradio" in messages:
|
||||
self.device.stop_on_error("Did not find gnuradio.")
|
||||
Errors.gnuradio_not_installed()
|
||||
self.on_clear_clicked()
|
||||
|
||||
elif "rtlsdr-open: error code: -1" in messages:
|
||||
self.device.stop_on_error("Could not open a RTL-SDR device.")
|
||||
self.on_clear_clicked()
|
||||
|
||||
elif "rtlsdr-open: error code: -12" in messages:
|
||||
self.device.stop_on_error("Could not open a RTL-SDR device")
|
||||
Errors.rtlsdr_sdr_driver()
|
||||
self.on_clear_clicked()
|
||||
|
||||
elif "Address already in use" in messages:
|
||||
self._restart_device_thread()
|
||||
|
||||
def update_view(self):
|
||||
txt = self.ui.txtEditErrors.toPlainText()
|
||||
new_messages = self.device.read_messages()
|
||||
|
||||
self.__parse_error_messages(new_messages)
|
||||
|
||||
if len(new_messages) > 1:
|
||||
self.ui.txtEditErrors.setPlainText(txt + new_messages)
|
||||
|
||||
self.ui.lSamplesCaptured.setText(Formatter.big_value_with_suffix(self.device.current_index, decimals=1))
|
||||
self.ui.lSignalSize.setText(locale.format_string("%.2f", (8 * self.device.current_index) / (1024 ** 2)))
|
||||
self.ui.lTime.setText(locale.format_string("%.2f", self.device.current_index / self.device.sample_rate))
|
||||
|
||||
if self.is_rx and self.device.data is not None and len(self.device.data) > 0:
|
||||
self.ui.labelReceiveBufferFull.setText("{0}%".format(int(100 * self.device.current_index /
|
||||
len(self.device.data))))
|
||||
|
||||
if self.device.current_index == 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _restart_device_thread(self):
|
||||
self.device.stop("Restarting with new port")
|
||||
|
||||
if self.device.backend == Backends.grc:
|
||||
self.device.increase_gr_port()
|
||||
|
||||
self.device.start()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_clear_clicked(self):
|
||||
pass
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
if self.device.backend is not Backends.none:
|
||||
self.emit_editing_finished_signals()
|
||||
|
||||
self.timer.stop()
|
||||
|
||||
self.device.stop("Dialog closed. Killing recording process.")
|
||||
logger.debug("Device stopped successfully.")
|
||||
|
||||
if not self.testing_mode:
|
||||
if not self.save_before_close():
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
time.sleep(0.1)
|
||||
if self.device.backend not in (Backends.none, Backends.network):
|
||||
# Backend none is selected, when no device is available
|
||||
logger.debug("Cleaning up device")
|
||||
self.device.cleanup()
|
||||
logger.debug("Successfully cleaned up device")
|
||||
self.device_settings_widget.emit_device_parameters_changed()
|
||||
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
|
||||
if self.device is not None:
|
||||
self.device.free_data()
|
||||
|
||||
self.scene_manager.eliminate()
|
||||
|
||||
self._eliminate_graphic_view()
|
||||
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_slider_y_scale_value_changed(self, new_value: int):
|
||||
self.graphics_view.scale(1, new_value / self.current_y_slider_value)
|
||||
self.graphics_view.centerOn(0, 0)
|
||||
self.current_y_slider_value = new_value
|
@ -0,0 +1,54 @@
|
||||
import locale
|
||||
import os
|
||||
import time
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QCloseEvent
|
||||
from PyQt5.QtWidgets import QDialog
|
||||
|
||||
from urh import settings
|
||||
from urh.ui.ui_signal_details import Ui_SignalDetails
|
||||
from urh.util.Formatter import Formatter
|
||||
|
||||
|
||||
class SignalDetailsDialog(QDialog):
|
||||
def __init__(self, signal, parent=None):
|
||||
super().__init__(parent)
|
||||
self.signal = signal
|
||||
self.ui = Ui_SignalDetails()
|
||||
self.ui.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
file = self.signal.filename
|
||||
|
||||
self.ui.lblName.setText(self.signal.name)
|
||||
|
||||
if os.path.isfile(file):
|
||||
self.ui.lblFile.setText(file)
|
||||
self.ui.lblFileSize.setText(locale.format_string("%.2fMB", os.path.getsize(file) / (1024 ** 2)))
|
||||
self.ui.lFileCreated.setText(time.ctime(os.path.getctime(file)))
|
||||
else:
|
||||
self.ui.lblFile.setText(self.tr("signal file not found"))
|
||||
self.ui.lblFileSize.setText("-")
|
||||
self.ui.lFileCreated.setText("-")
|
||||
|
||||
self.ui.lblSamplesTotal.setText("{0:n}".format(self.signal.num_samples).replace(",", " "))
|
||||
self.ui.dsb_sample_rate.setValue(self.signal.sample_rate)
|
||||
self.set_duration()
|
||||
|
||||
self.ui.dsb_sample_rate.valueChanged.connect(self.on_dsb_sample_rate_value_changed)
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_dsb_sample_rate_value_changed(self, value: float):
|
||||
self.signal.sample_rate = value
|
||||
self.set_duration()
|
||||
|
||||
def set_duration(self):
|
||||
dur = self.signal.num_samples / self.signal.sample_rate
|
||||
self.ui.lDuration.setText(Formatter.science_time(dur))
|
@ -0,0 +1,432 @@
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import QTimer, pyqtSlot, pyqtSignal, Qt
|
||||
from PyQt5.QtGui import QIcon, QCloseEvent
|
||||
from PyQt5.QtWidgets import QDialog, QFileDialog, QMessageBox, QGraphicsTextItem
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.dialogs.ProtocolSniffDialog import ProtocolSniffDialog
|
||||
from urh.controller.widgets.DeviceSettingsWidget import DeviceSettingsWidget
|
||||
from urh.controller.widgets.ModulationSettingsWidget import ModulationSettingsWidget
|
||||
from urh.controller.widgets.SniffSettingsWidget import SniffSettingsWidget
|
||||
from urh.dev.BackendHandler import BackendHandler
|
||||
from urh.dev.EndlessSender import EndlessSender
|
||||
from urh.signalprocessing.IQArray import IQArray
|
||||
from urh.simulator.Simulator import Simulator
|
||||
from urh.simulator.SimulatorConfiguration import SimulatorConfiguration
|
||||
from urh.ui.SimulatorScene import SimulatorScene
|
||||
from urh.ui.painting.LiveSceneManager import LiveSceneManager
|
||||
from urh.ui.painting.SniffSceneManager import SniffSceneManager
|
||||
from urh.ui.ui_simulator_dialog import Ui_DialogSimulator
|
||||
from urh.util import util, FileOperator
|
||||
from urh.util.Errors import Errors
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class SimulatorDialog(QDialog):
|
||||
rx_parameters_changed = pyqtSignal(dict)
|
||||
tx_parameters_changed = pyqtSignal(dict)
|
||||
sniff_parameters_changed = pyqtSignal(dict)
|
||||
open_in_analysis_requested = pyqtSignal(str)
|
||||
rx_file_saved = pyqtSignal(str)
|
||||
|
||||
def __init__(self, simulator_config, modulators,
|
||||
expression_parser, project_manager: ProjectManager, signals: list = None,
|
||||
signal_tree_model=None,
|
||||
parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_DialogSimulator()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.simulator_config = simulator_config # type: SimulatorConfiguration
|
||||
self.rx_needed = self.simulator_config.rx_needed
|
||||
self.tx_needed = self.simulator_config.tx_needed
|
||||
|
||||
self.current_transcript_index = 0
|
||||
|
||||
self.simulator_scene = SimulatorScene(mode=1,
|
||||
simulator_config=self.simulator_config)
|
||||
self.ui.gvSimulator.setScene(self.simulator_scene)
|
||||
self.project_manager = project_manager
|
||||
|
||||
self.update_interval = 25
|
||||
|
||||
self.timer = QTimer(self)
|
||||
|
||||
self.backend_handler = BackendHandler()
|
||||
if self.rx_needed:
|
||||
self.device_settings_rx_widget = DeviceSettingsWidget(project_manager,
|
||||
is_tx=False,
|
||||
backend_handler=self.backend_handler)
|
||||
|
||||
self.sniff_settings_widget = SniffSettingsWidget(self.device_settings_rx_widget.ui.cbDevice.currentText(),
|
||||
project_manager,
|
||||
signal=None,
|
||||
backend_handler=self.backend_handler,
|
||||
network_raw_mode=True, signals=signals)
|
||||
|
||||
self.device_settings_rx_widget.device = self.sniff_settings_widget.sniffer.rcv_device
|
||||
|
||||
self.sniff_settings_widget.ui.lineEdit_sniff_OutputFile.hide()
|
||||
self.sniff_settings_widget.ui.label_sniff_OutputFile.hide()
|
||||
self.sniff_settings_widget.ui.label_sniff_viewtype.hide()
|
||||
self.sniff_settings_widget.ui.checkBox_sniff_Timestamp.hide()
|
||||
self.sniff_settings_widget.ui.comboBox_sniff_viewtype.hide()
|
||||
|
||||
self.ui.scrollAreaWidgetContentsRX.layout().insertWidget(0, self.device_settings_rx_widget)
|
||||
self.ui.scrollAreaWidgetContentsRX.layout().insertWidget(1, self.sniff_settings_widget)
|
||||
|
||||
sniffer = self.sniff_settings_widget.sniffer
|
||||
|
||||
self.scene_manager = SniffSceneManager(np.array([], dtype=sniffer.rcv_device.data_type), parent=self)
|
||||
self.ui.graphicsViewPreview.setScene(self.scene_manager.scene)
|
||||
else:
|
||||
self.device_settings_rx_widget = self.sniff_settings_widget = self.scene_manager = None
|
||||
self.ui.tabWidgetSimulatorSettings.setTabEnabled(1, False)
|
||||
self.ui.graphicsViewPreview.hide()
|
||||
self.ui.btnSaveRX.hide()
|
||||
self.ui.checkBoxCaptureFullRX.hide()
|
||||
|
||||
sniffer = None
|
||||
|
||||
if self.tx_needed:
|
||||
self.device_settings_tx_widget = DeviceSettingsWidget(project_manager, is_tx=True,
|
||||
backend_handler=self.backend_handler,
|
||||
continuous_send_mode=True)
|
||||
self.device_settings_tx_widget.ui.spinBoxNRepeat.hide()
|
||||
self.device_settings_tx_widget.ui.labelNRepeat.hide()
|
||||
|
||||
self.modulation_settings_widget = ModulationSettingsWidget(modulators, signal_tree_model=signal_tree_model,
|
||||
parent=None)
|
||||
|
||||
self.ui.scrollAreaWidgetContentsTX.layout().insertWidget(0, self.device_settings_tx_widget)
|
||||
self.ui.scrollAreaWidgetContentsTX.layout().insertWidget(1, self.modulation_settings_widget)
|
||||
send_device = self.device_settings_tx_widget.ui.cbDevice.currentText()
|
||||
sender = EndlessSender(self.backend_handler, send_device)
|
||||
else:
|
||||
self.device_settings_tx_widget = self.modulation_settings_widget = None
|
||||
self.ui.tabWidgetSimulatorSettings.setTabEnabled(2, False)
|
||||
|
||||
sender = None
|
||||
|
||||
self.simulator = Simulator(self.simulator_config, modulators, expression_parser, project_manager,
|
||||
sniffer=sniffer, sender=sender)
|
||||
|
||||
if self.device_settings_tx_widget:
|
||||
self.device_settings_tx_widget.device = self.simulator.sender.device
|
||||
|
||||
self.update_buttons()
|
||||
self.create_connects()
|
||||
|
||||
if self.device_settings_rx_widget:
|
||||
self.device_settings_rx_widget.bootstrap(project_manager.simulator_rx_conf)
|
||||
|
||||
if self.device_settings_tx_widget:
|
||||
self.device_settings_tx_widget.bootstrap(project_manager.simulator_tx_conf)
|
||||
|
||||
self.ui.textEditTranscript.setFont(util.get_monospace_font())
|
||||
|
||||
if settings.read('default_view', 0, int) == 1:
|
||||
self.ui.radioButtonTranscriptHex.setChecked(True)
|
||||
|
||||
def create_connects(self):
|
||||
if self.rx_needed:
|
||||
self.device_settings_rx_widget.selected_device_changed.connect(self.on_selected_rx_device_changed)
|
||||
self.device_settings_rx_widget.device_parameters_changed.connect(self.rx_parameters_changed.emit)
|
||||
|
||||
self.sniff_settings_widget.sniff_parameters_changed.connect(self.sniff_parameters_changed.emit)
|
||||
|
||||
self.ui.btnSaveRX.clicked.connect(self.on_btn_save_rx_clicked)
|
||||
self.ui.checkBoxCaptureFullRX.clicked.connect(self.on_checkbox_capture_full_rx_clicked)
|
||||
|
||||
self.ui.btnTestSniffSettings.clicked.connect(self.on_btn_test_sniff_settings_clicked)
|
||||
self.ui.btnOpenInAnalysis.clicked.connect(self.on_btn_open_in_analysis_clicked)
|
||||
|
||||
if self.tx_needed:
|
||||
self.device_settings_tx_widget.selected_device_changed.connect(self.on_selected_tx_device_changed)
|
||||
self.device_settings_tx_widget.device_parameters_changed.connect(self.tx_parameters_changed.emit)
|
||||
|
||||
self.ui.radioButtonTranscriptBit.clicked.connect(self.on_radio_button_transcript_bit_clicked)
|
||||
self.ui.radioButtonTranscriptHex.clicked.connect(self.on_radio_button_transcript_hex_clicked)
|
||||
|
||||
self.simulator_scene.selectionChanged.connect(self.update_buttons)
|
||||
self.simulator_config.items_updated.connect(self.update_buttons)
|
||||
|
||||
self.ui.btnLogAll.clicked.connect(self.on_btn_log_all_clicked)
|
||||
self.ui.btnLogNone.clicked.connect(self.on_btn_log_none_clicked)
|
||||
self.ui.btnToggleLog.clicked.connect(self.on_btn_toggle_clicked)
|
||||
|
||||
self.ui.btnStartStop.clicked.connect(self.on_btn_start_stop_clicked)
|
||||
self.ui.btnSaveLog.clicked.connect(self.on_btn_save_log_clicked)
|
||||
self.ui.btnSaveTranscript.clicked.connect(self.on_btn_save_transcript_clicked)
|
||||
self.timer.timeout.connect(self.on_timer_timeout)
|
||||
self.simulator.simulation_started.connect(self.on_simulation_started)
|
||||
self.simulator.simulation_stopped.connect(self.on_simulation_stopped)
|
||||
|
||||
def update_buttons(self):
|
||||
selectable_items = self.simulator_scene.selectable_items()
|
||||
all_items_selected = all(item.model_item.logging_active for item in selectable_items)
|
||||
any_item_selected = any(item.model_item.logging_active for item in selectable_items)
|
||||
self.ui.btnToggleLog.setEnabled(len(self.simulator_scene.selectedItems()))
|
||||
self.ui.btnLogAll.setEnabled(not all_items_selected)
|
||||
self.ui.btnLogNone.setEnabled(any_item_selected)
|
||||
|
||||
def __get_full_transcript(self) -> list:
|
||||
return self.simulator.transcript.get_for_all_participants(all_rounds=True,
|
||||
use_bit=self.ui.radioButtonTranscriptBit.isChecked())
|
||||
|
||||
def update_view(self):
|
||||
for device_message in filter(None, map(str.rstrip, self.simulator.device_messages())):
|
||||
self.ui.textEditDevices.append(device_message)
|
||||
|
||||
for log_msg in filter(None, map(str.rstrip, self.simulator.read_log_messages())):
|
||||
self.ui.textEditSimulation.append(log_msg)
|
||||
|
||||
transcript = self.__get_full_transcript()
|
||||
for line in transcript[self.current_transcript_index:]:
|
||||
self.ui.textEditTranscript.append(line)
|
||||
|
||||
self.current_transcript_index = len(transcript)
|
||||
current_repeat = str(self.simulator.current_repeat + 1) if self.simulator.is_simulating else "-"
|
||||
self.ui.lblCurrentRepeatValue.setText(current_repeat)
|
||||
|
||||
current_item = self.simulator.current_item.index() if self.simulator.is_simulating else "-"
|
||||
self.ui.lblCurrentItemValue.setText(current_item)
|
||||
|
||||
def update_rx_graphics_view(self):
|
||||
if self.scene_manager is None or not self.ui.graphicsViewPreview.isEnabled():
|
||||
return
|
||||
|
||||
self.scene_manager.end = self.simulator.sniffer.rcv_device.current_index
|
||||
self.scene_manager.init_scene()
|
||||
self.scene_manager.show_full_scene()
|
||||
self.ui.graphicsViewPreview.update()
|
||||
|
||||
def reset(self):
|
||||
self.ui.textEditDevices.clear()
|
||||
self.ui.textEditSimulation.clear()
|
||||
self.ui.textEditTranscript.clear()
|
||||
self.current_transcript_index = 0
|
||||
self.ui.lblCurrentRepeatValue.setText("-")
|
||||
self.ui.lblCurrentItemValue.setText("-")
|
||||
|
||||
def emit_editing_finished_signals(self):
|
||||
if self.device_settings_rx_widget:
|
||||
self.device_settings_rx_widget.emit_editing_finished_signals()
|
||||
|
||||
if self.device_settings_tx_widget:
|
||||
self.device_settings_tx_widget.emit_editing_finished_signals()
|
||||
|
||||
if self.sniff_settings_widget:
|
||||
self.sniff_settings_widget.emit_editing_finished_signals()
|
||||
|
||||
def update_transcript_view(self):
|
||||
self.ui.textEditTranscript.setText("\n".join(self.__get_full_transcript()))
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
self.timer.stop()
|
||||
self.simulator.stop()
|
||||
|
||||
self.simulator.cleanup()
|
||||
|
||||
self.emit_editing_finished_signals()
|
||||
if self.device_settings_rx_widget:
|
||||
self.device_settings_rx_widget.emit_device_parameters_changed()
|
||||
if self.device_settings_tx_widget:
|
||||
self.device_settings_tx_widget.emit_device_parameters_changed()
|
||||
if self.sniff_settings_widget:
|
||||
self.sniff_settings_widget.emit_sniff_parameters_changed()
|
||||
|
||||
super().closeEvent(event)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_simulation_started(self):
|
||||
for i in range(3):
|
||||
self.ui.tabWidgetSimulatorSettings.setTabEnabled(i, False)
|
||||
self.ui.checkBoxCaptureFullRX.setDisabled(True)
|
||||
self.reset()
|
||||
self.timer.start(self.update_interval)
|
||||
self.ui.btnStartStop.setIcon(QIcon.fromTheme("media-playback-stop"))
|
||||
self.ui.btnStartStop.setText("Stop")
|
||||
|
||||
if not self.rx_needed:
|
||||
return
|
||||
|
||||
rx_device = self.simulator.sniffer.rcv_device
|
||||
for item in self.scene_manager.scene.items():
|
||||
if isinstance(item, QGraphicsTextItem):
|
||||
self.scene_manager.scene.removeItem(item)
|
||||
|
||||
if hasattr(rx_device.data, "real"):
|
||||
self.ui.graphicsViewPreview.setEnabled(True)
|
||||
if self.ui.checkBoxCaptureFullRX.isChecked():
|
||||
self.scene_manager.plot_data = rx_device.data.real
|
||||
else:
|
||||
self.scene_manager.data_array = rx_device.data.real
|
||||
else:
|
||||
self.ui.graphicsViewPreview.setEnabled(False)
|
||||
if self.ui.checkBoxCaptureFullRX.isChecked():
|
||||
self.scene_manager.plot_data = np.array([], dtype=rx_device.data_type)
|
||||
else:
|
||||
self.scene_manager.data_array = np.array([], dtype=rx_device.data_type)
|
||||
self.scene_manager.scene.addText("Could not generate RX preview.")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_simulation_stopped(self):
|
||||
self.ui.tabWidgetSimulatorSettings.setTabEnabled(0, True)
|
||||
self.ui.tabWidgetSimulatorSettings.setTabEnabled(1, self.rx_needed)
|
||||
self.ui.tabWidgetSimulatorSettings.setTabEnabled(2, self.tx_needed)
|
||||
|
||||
self.timer.stop()
|
||||
self.update_view()
|
||||
self.ui.btnStartStop.setIcon(QIcon.fromTheme("media-playback-start"))
|
||||
self.ui.btnStartStop.setText("Start")
|
||||
self.ui.checkBoxCaptureFullRX.setEnabled(True)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_log_all_clicked(self):
|
||||
self.simulator_scene.log_all_items(True)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_log_none_clicked(self):
|
||||
self.simulator_scene.log_all_items(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_toggle_clicked(self):
|
||||
self.simulator_scene.log_toggle_selected_items()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_save_log_clicked(self):
|
||||
file_path = QFileDialog.getSaveFileName(self, "Save log", "", "Log file (*.log)")
|
||||
|
||||
if file_path[0] == "":
|
||||
return
|
||||
|
||||
log_string = self.ui.textEditSimulation.toPlainText()
|
||||
|
||||
try:
|
||||
with open(str(file_path[0]), "w") as f:
|
||||
f.write(log_string)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error saving log", e.args[0])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_save_transcript_clicked(self):
|
||||
file_path = QFileDialog.getSaveFileName(self, "Save transcript", "", "Text file (*.txt)")
|
||||
|
||||
if file_path[0] == "":
|
||||
return
|
||||
|
||||
transcript = self.ui.textEditTranscript.toPlainText()
|
||||
|
||||
try:
|
||||
with open(str(file_path[0]), "w") as f:
|
||||
f.write(transcript)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error saving transcript", e.args[0])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_start_stop_clicked(self):
|
||||
if self.simulator.is_simulating:
|
||||
self.simulator.stop()
|
||||
else:
|
||||
if self.rx_needed:
|
||||
self.device_settings_rx_widget.emit_editing_finished_signals()
|
||||
self.sniff_settings_widget.emit_editing_finished_signals()
|
||||
|
||||
self.simulator.sniffer.rcv_device.current_index = 0
|
||||
self.simulator.sniffer.rcv_device.resume_on_full_receive_buffer = not self.ui.checkBoxCaptureFullRX.isChecked()
|
||||
|
||||
if self.tx_needed:
|
||||
self.device_settings_tx_widget.emit_editing_finished_signals()
|
||||
|
||||
self.simulator.start()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_timer_timeout(self):
|
||||
self.update_view()
|
||||
self.update_rx_graphics_view()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_selected_rx_device_changed(self):
|
||||
dev_name = self.device_settings_rx_widget.ui.cbDevice.currentText()
|
||||
self.simulator.sniffer.device_name = dev_name
|
||||
self.device_settings_rx_widget.device = self.simulator.sniffer.rcv_device
|
||||
self.__set_rx_scene()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_selected_tx_device_changed(self):
|
||||
old_name = self.simulator.sender.device_name
|
||||
try:
|
||||
dev_name = self.device_settings_tx_widget.ui.cbDevice.currentText()
|
||||
self.simulator.sender.device_name = dev_name
|
||||
self.device_settings_tx_widget.device = self.simulator.sender.device
|
||||
except Exception as e:
|
||||
self.device_settings_tx_widget.ui.cbDevice.setCurrentText(old_name)
|
||||
Errors.exception(e)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_test_sniff_settings_clicked(self):
|
||||
def on_dialog_finished():
|
||||
self.device_settings_rx_widget.bootstrap(self.project_manager.simulator_rx_conf)
|
||||
self.sniff_settings_widget.bootstrap(self.project_manager.device_conf)
|
||||
|
||||
self.device_settings_rx_widget.emit_device_parameters_changed()
|
||||
self.sniff_settings_widget.emit_sniff_parameters_changed()
|
||||
|
||||
psd = ProtocolSniffDialog(self.project_manager, signals=self.sniff_settings_widget.signals, parent=self)
|
||||
psd.device_settings_widget.bootstrap(self.project_manager.simulator_rx_conf)
|
||||
psd.device_settings_widget.device_parameters_changed.connect(self.rx_parameters_changed.emit)
|
||||
psd.sniff_settings_widget.sniff_parameters_changed.connect(self.sniff_parameters_changed.emit)
|
||||
psd.finished.connect(on_dialog_finished)
|
||||
psd.ui.btnAccept.hide()
|
||||
psd.show()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_radio_button_transcript_hex_clicked(self):
|
||||
self.update_transcript_view()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_radio_button_transcript_bit_clicked(self):
|
||||
self.update_transcript_view()
|
||||
|
||||
def __set_rx_scene(self):
|
||||
if not self.rx_needed:
|
||||
return
|
||||
|
||||
if self.ui.checkBoxCaptureFullRX.isChecked():
|
||||
self.scene_manager = LiveSceneManager(np.array([], dtype=self.simulator.sniffer.rcv_device.data_type),
|
||||
parent=self)
|
||||
self.ui.graphicsViewPreview.setScene(self.scene_manager.scene)
|
||||
else:
|
||||
self.scene_manager = SniffSceneManager(np.array([], dtype=self.simulator.sniffer.rcv_device.data_type),
|
||||
parent=self)
|
||||
|
||||
self.ui.graphicsViewPreview.setScene(self.scene_manager.scene)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_checkbox_capture_full_rx_clicked(self):
|
||||
self.simulator.sniffer.rcv_device.resume_on_full_receive_buffer = not self.ui.checkBoxCaptureFullRX.isChecked()
|
||||
self.__set_rx_scene()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_save_rx_clicked(self):
|
||||
rx_device = self.simulator.sniffer.rcv_device
|
||||
if isinstance(rx_device.data, np.ndarray) or isinstance(rx_device.data, IQArray):
|
||||
data = IQArray(rx_device.data[:rx_device.current_index])
|
||||
filename = FileOperator.ask_signal_file_name_and_save("simulation_capture", data,
|
||||
sample_rate=rx_device.sample_rate, parent=self)
|
||||
if filename:
|
||||
data.tofile(filename)
|
||||
self.rx_file_saved.emit(filename)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_open_in_analysis_clicked(self):
|
||||
text = self.ui.textEditTranscript.toPlainText()
|
||||
if len(text) > 0:
|
||||
self.open_in_analysis_requested.emit(text)
|
||||
self.close()
|
@ -0,0 +1,172 @@
|
||||
from PyQt5.QtCore import QTimer, pyqtSlot
|
||||
from PyQt5.QtGui import QWheelEvent, QIcon, QPixmap, QResizeEvent
|
||||
from PyQt5.QtWidgets import QGraphicsScene
|
||||
|
||||
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
|
||||
from urh.dev.VirtualDevice import VirtualDevice, Mode
|
||||
from urh.signalprocessing.Spectrogram import Spectrogram
|
||||
from urh.ui.painting.FFTSceneManager import FFTSceneManager
|
||||
|
||||
|
||||
class SpectrumDialogController(SendRecvDialog):
|
||||
def __init__(self, project_manager, parent=None, testing_mode=False):
|
||||
super().__init__(project_manager, is_tx=False, parent=parent, testing_mode=testing_mode)
|
||||
|
||||
self.graphics_view = self.ui.graphicsViewFFT
|
||||
self.update_interval = 1
|
||||
self.ui.stackedWidget.setCurrentWidget(self.ui.page_spectrum)
|
||||
self.hide_receive_ui_items()
|
||||
self.hide_send_ui_items()
|
||||
|
||||
self.setWindowTitle("Spectrum Analyzer")
|
||||
self.setWindowIcon(QIcon(":/icons/icons/spectrum.svg"))
|
||||
self.ui.btnStart.setToolTip(self.tr("Start"))
|
||||
self.ui.btnStop.setToolTip(self.tr("Stop"))
|
||||
|
||||
self.scene_manager = FFTSceneManager(parent=self, graphic_view=self.graphics_view)
|
||||
|
||||
self.graphics_view.setScene(self.scene_manager.scene)
|
||||
self.graphics_view.scene_manager = self.scene_manager
|
||||
|
||||
self.ui.graphicsViewSpectrogram.setScene(QGraphicsScene())
|
||||
self.__clear_spectrogram()
|
||||
|
||||
self.gain_timer = QTimer(self)
|
||||
self.gain_timer.setSingleShot(True)
|
||||
|
||||
self.if_gain_timer = QTimer(self)
|
||||
self.if_gain_timer.setSingleShot(True)
|
||||
|
||||
self.bb_gain_timer = QTimer(self)
|
||||
self.bb_gain_timer.setSingleShot(True)
|
||||
|
||||
self.create_connects()
|
||||
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
|
||||
|
||||
def __clear_spectrogram(self):
|
||||
self.ui.graphicsViewSpectrogram.scene().clear()
|
||||
window_size = Spectrogram.DEFAULT_FFT_WINDOW_SIZE
|
||||
self.ui.graphicsViewSpectrogram.scene().setSceneRect(0, 0, window_size, 20 * window_size)
|
||||
self.spectrogram_y_pos = 0
|
||||
self.ui.graphicsViewSpectrogram.fitInView(self.ui.graphicsViewSpectrogram.sceneRect())
|
||||
|
||||
def __update_spectrogram(self):
|
||||
spectrogram = Spectrogram(self.device.data)
|
||||
spectrogram.data_min = -80
|
||||
spectrogram.data_max = 10
|
||||
scene = self.ui.graphicsViewSpectrogram.scene()
|
||||
pixmap = QPixmap.fromImage(spectrogram.create_spectrogram_image(transpose=True))
|
||||
pixmap_item = scene.addPixmap(pixmap)
|
||||
pixmap_item.moveBy(0, self.spectrogram_y_pos)
|
||||
self.spectrogram_y_pos += pixmap.height()
|
||||
if self.spectrogram_y_pos >= scene.sceneRect().height():
|
||||
scene.setSceneRect(0, 0, Spectrogram.DEFAULT_FFT_WINDOW_SIZE, self.spectrogram_y_pos)
|
||||
self.ui.graphicsViewSpectrogram.ensureVisible(pixmap_item)
|
||||
|
||||
def _eliminate_graphic_view(self):
|
||||
super()._eliminate_graphic_view()
|
||||
if self.ui.graphicsViewSpectrogram and self.ui.graphicsViewSpectrogram.scene() is not None:
|
||||
self.ui.graphicsViewSpectrogram.scene().clear()
|
||||
self.ui.graphicsViewSpectrogram.scene().setParent(None)
|
||||
self.ui.graphicsViewSpectrogram.setScene(None)
|
||||
|
||||
self.ui.graphicsViewSpectrogram = None
|
||||
|
||||
def create_connects(self):
|
||||
super().create_connects()
|
||||
self.graphics_view.freq_clicked.connect(self.on_graphics_view_freq_clicked)
|
||||
self.graphics_view.wheel_event_triggered.connect(self.on_graphics_view_wheel_event_triggered)
|
||||
|
||||
self.device_settings_widget.ui.sliderGain.valueChanged.connect(self.on_slider_gain_value_changed)
|
||||
self.device_settings_widget.ui.sliderBasebandGain.valueChanged.connect(
|
||||
self.on_slider_baseband_gain_value_changed)
|
||||
self.device_settings_widget.ui.sliderIFGain.valueChanged.connect(self.on_slider_if_gain_value_changed)
|
||||
self.device_settings_widget.ui.spinBoxFreq.editingFinished.connect(self.on_spinbox_frequency_editing_finished)
|
||||
|
||||
self.gain_timer.timeout.connect(self.device_settings_widget.ui.spinBoxGain.editingFinished.emit)
|
||||
self.if_gain_timer.timeout.connect(self.device_settings_widget.ui.spinBoxIFGain.editingFinished.emit)
|
||||
self.bb_gain_timer.timeout.connect(self.device_settings_widget.ui.spinBoxBasebandGain.editingFinished.emit)
|
||||
|
||||
def resizeEvent(self, event: QResizeEvent):
|
||||
if self.ui.graphicsViewSpectrogram and self.ui.graphicsViewSpectrogram.sceneRect():
|
||||
self.ui.graphicsViewSpectrogram.fitInView(self.ui.graphicsViewSpectrogram.sceneRect())
|
||||
|
||||
def update_view(self):
|
||||
if super().update_view():
|
||||
x, y = self.device.spectrum
|
||||
if x is None or y is None:
|
||||
return
|
||||
self.scene_manager.scene.frequencies = x
|
||||
self.scene_manager.plot_data = y
|
||||
self.scene_manager.init_scene()
|
||||
self.scene_manager.show_full_scene()
|
||||
self.graphics_view.fitInView(self.graphics_view.sceneRect())
|
||||
|
||||
try:
|
||||
self.__update_spectrogram()
|
||||
except MemoryError:
|
||||
self.__clear_spectrogram()
|
||||
self.__update_spectrogram()
|
||||
|
||||
def init_device(self):
|
||||
self.device = VirtualDevice(self.backend_handler, self.selected_device_name,
|
||||
Mode.spectrum,
|
||||
device_ip="192.168.10.2", parent=self)
|
||||
self._create_device_connects()
|
||||
|
||||
@pyqtSlot(QWheelEvent)
|
||||
def on_graphics_view_wheel_event_triggered(self, event: QWheelEvent):
|
||||
self.ui.sliderYscale.wheelEvent(event)
|
||||
|
||||
@pyqtSlot(float)
|
||||
def on_graphics_view_freq_clicked(self, freq: float):
|
||||
self.device_settings_widget.ui.spinBoxFreq.setValue(freq)
|
||||
self.device_settings_widget.ui.spinBoxFreq.editingFinished.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_frequency_editing_finished(self):
|
||||
frequency = self.device_settings_widget.ui.spinBoxFreq.value()
|
||||
self.device.frequency = frequency
|
||||
self.scene_manager.scene.center_freq = frequency
|
||||
self.scene_manager.clear_path()
|
||||
self.scene_manager.clear_peak()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_start_clicked(self):
|
||||
super().on_start_clicked()
|
||||
self.device.start()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_started(self):
|
||||
self.ui.graphicsViewSpectrogram.fitInView(self.ui.graphicsViewSpectrogram.scene().sceneRect())
|
||||
super().on_device_started()
|
||||
self.device_settings_widget.ui.spinBoxPort.setEnabled(False)
|
||||
self.device_settings_widget.ui.lineEditIP.setEnabled(False)
|
||||
self.device_settings_widget.ui.cbDevice.setEnabled(False)
|
||||
self.ui.btnStart.setEnabled(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_device_stopped(self):
|
||||
self.device_settings_widget.ui.spinBoxPort.setEnabled(True)
|
||||
self.device_settings_widget.ui.lineEditIP.setEnabled(True)
|
||||
self.device_settings_widget.ui.cbDevice.setEnabled(True)
|
||||
|
||||
super().on_device_stopped()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_clear_clicked(self):
|
||||
self.__clear_spectrogram()
|
||||
self.scene_manager.clear_path()
|
||||
self.scene_manager.clear_peak()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_slider_gain_value_changed(self, value: int):
|
||||
self.gain_timer.start(250)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_slider_if_gain_value_changed(self, value: int):
|
||||
self.if_gain_timer.start(250)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_slider_baseband_gain_value_changed(self, value: int):
|
||||
self.bb_gain_timer.start(250)
|
@ -0,0 +1,321 @@
|
||||
import array
|
||||
import copy
|
||||
from collections import OrderedDict
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QAbstractTableModel, QModelIndex, Qt, QRegExp
|
||||
from PyQt5.QtGui import QRegExpValidator
|
||||
from PyQt5.QtWidgets import QWidget, QHeaderView, QAbstractItemView, QLineEdit
|
||||
|
||||
from urh.signalprocessing.ChecksumLabel import ChecksumLabel
|
||||
from urh.signalprocessing.Message import Message
|
||||
from urh.ui.delegates.SpinBoxDelegate import SpinBoxDelegate
|
||||
from urh.ui.ui_checksum_options_widget import Ui_ChecksumOptions
|
||||
from urh.util import util
|
||||
from urh.util.GenericCRC import GenericCRC
|
||||
from urh.util.Logger import logger
|
||||
from urh.util.WSPChecksum import WSPChecksum
|
||||
|
||||
|
||||
class ChecksumWidget(QWidget):
|
||||
SPECIAL_CRCS = OrderedDict([
|
||||
("CC1101", GenericCRC(polynomial="16_standard", start_value=True)),
|
||||
])
|
||||
|
||||
|
||||
class RangeTableModel(QAbstractTableModel):
|
||||
header_labels = ["Start", "End"]
|
||||
|
||||
def __init__(self, checksum_label: ChecksumLabel, message: Message, proto_view: int, parent=None):
|
||||
"""
|
||||
|
||||
:param message:
|
||||
:type field_types: list of FieldType
|
||||
:param parent:
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.checksum_label = checksum_label
|
||||
self.message = message
|
||||
self.proto_view = proto_view
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
self.beginResetModel()
|
||||
self.endResetModel()
|
||||
|
||||
def columnCount(self, parent: QModelIndex = None, *args, **kwargs):
|
||||
return len(self.header_labels)
|
||||
|
||||
def rowCount(self, parent: QModelIndex = None, *args, **kwargs):
|
||||
return len(self.checksum_label.data_ranges)
|
||||
|
||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
||||
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
|
||||
return self.header_labels[section]
|
||||
return super().headerData(section, orientation, role)
|
||||
|
||||
def data(self, index: QModelIndex, role=Qt.DisplayRole):
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
i, j = index.row(), index.column()
|
||||
|
||||
if role == Qt.DisplayRole:
|
||||
data_range = self.checksum_label.data_ranges[i]
|
||||
if j == 0:
|
||||
return self.message.convert_index(data_range[0], 0, self.proto_view, True)[0] + 1
|
||||
elif j == 1:
|
||||
return self.message.convert_index(data_range[1], 0, self.proto_view, True)[0]
|
||||
return None
|
||||
|
||||
def setData(self, index: QModelIndex, value, role: int = ...):
|
||||
try:
|
||||
int_val = int(value)
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
i, j = index.row(), index.column()
|
||||
|
||||
if i > len(self.checksum_label.data_ranges):
|
||||
return False
|
||||
|
||||
data_range = self.checksum_label.data_ranges[i]
|
||||
|
||||
if j == 0:
|
||||
converted_index = self.message.convert_index(int_val - 1, self.proto_view, 0, True)[0]
|
||||
if converted_index < data_range[1]:
|
||||
data_range[0] = converted_index
|
||||
elif j == 1:
|
||||
converted_index = self.message.convert_index(int_val, self.proto_view, 0, True)[0]
|
||||
if converted_index > data_range[0]:
|
||||
data_range[1] = converted_index
|
||||
|
||||
return True
|
||||
|
||||
def flags(self, index):
|
||||
if not index.isValid():
|
||||
return Qt.NoItemFlags
|
||||
|
||||
try:
|
||||
_ = self.checksum_label.data_ranges[index.row()]
|
||||
except IndexError:
|
||||
return Qt.NoItemFlags
|
||||
|
||||
return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
|
||||
|
||||
def __init__(self, checksum_label: ChecksumLabel, message: Message, proto_view: int, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_ChecksumOptions()
|
||||
self.ui.setupUi(self)
|
||||
self.checksum_label = checksum_label
|
||||
self.data_range_table_model = self.RangeTableModel(checksum_label, message, proto_view, parent=self)
|
||||
self.ui.tableViewDataRanges.setItemDelegateForColumn(0, SpinBoxDelegate(1, 999999, self))
|
||||
self.ui.tableViewDataRanges.setItemDelegateForColumn(1, SpinBoxDelegate(1, 999999, self))
|
||||
self.ui.tableViewDataRanges.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
self.ui.tableViewDataRanges.setModel(self.data_range_table_model)
|
||||
self.ui.tableViewDataRanges.setEditTriggers(QAbstractItemView.AllEditTriggers)
|
||||
self.display_crc_data_ranges_in_table()
|
||||
self.ui.comboBoxCRCFunction.addItems([crc_name for crc_name in GenericCRC.DEFAULT_POLYNOMIALS])
|
||||
self.ui.comboBoxCRCFunction.addItems([special_crc_name for special_crc_name in self.SPECIAL_CRCS])
|
||||
self.ui.lineEditCRCPolynomial.setValidator(QRegExpValidator(QRegExp("[0-9,a-f]*")))
|
||||
self.ui.comboBoxCategory.clear()
|
||||
for _, member in self.checksum_label.Category.__members__.items():
|
||||
self.ui.comboBoxCategory.addItem(member.value)
|
||||
self.set_ui_for_category()
|
||||
self.setFocus()
|
||||
self.create_connects()
|
||||
|
||||
@property
|
||||
def proto_view(self):
|
||||
return self.data_range_table_model.proto_view
|
||||
|
||||
@proto_view.setter
|
||||
def proto_view(self, value):
|
||||
if value != self.data_range_table_model.proto_view:
|
||||
self.data_range_table_model.proto_view = value
|
||||
self.data_range_table_model.update()
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.comboBoxCRCFunction.currentIndexChanged.connect(self.on_combobox_crc_function_current_index_changed)
|
||||
self.ui.btnAddRange.clicked.connect(self.on_btn_add_range_clicked)
|
||||
self.ui.btnRemoveRange.clicked.connect(self.on_btn_remove_range_clicked)
|
||||
self.ui.lineEditCRCPolynomial.editingFinished.connect(self.on_line_edit_crc_polynomial_editing_finished)
|
||||
self.ui.lineEditStartValue.editingFinished.connect(self.on_line_edit_start_value_editing_finished)
|
||||
self.ui.lineEditFinalXOR.editingFinished.connect(self.on_line_edit_final_xor_editing_finished)
|
||||
self.ui.comboBoxCategory.currentIndexChanged.connect(self.on_combobox_category_current_index_changed)
|
||||
self.ui.radioButtonWSPAuto.clicked.connect(self.on_radio_button_wsp_auto_clicked)
|
||||
self.ui.radioButtonWSPChecksum4.clicked.connect(self.on_radio_button_wsp_checksum4_clicked)
|
||||
self.ui.radioButtonWSPChecksum8.clicked.connect(self.on_radio_button_wsp_checksum8_clicked)
|
||||
self.ui.radioButtonWSPCRC8.clicked.connect(self.on_radio_button_wsp_crc8_clicked)
|
||||
self.ui.checkBoxRefIn.clicked.connect(self.on_check_box_ref_in_clicked)
|
||||
self.ui.checkBoxRefOut.clicked.connect(self.on_check_box_ref_out_clicked)
|
||||
|
||||
def set_checksum_ui_elements(self):
|
||||
if self.checksum_label.is_generic_crc:
|
||||
self.ui.lineEditCRCPolynomial.setText(self.checksum_label.checksum.polynomial_as_hex_str)
|
||||
self.ui.lineEditStartValue.setText(util.bit2hex(self.checksum_label.checksum.start_value))
|
||||
self.ui.lineEditFinalXOR.setText(util.bit2hex(self.checksum_label.checksum.final_xor))
|
||||
self.ui.checkBoxRefIn.setChecked(self.checksum_label.checksum.lsb_first)
|
||||
self.ui.checkBoxRefOut.setChecked(self.checksum_label.checksum.reverse_all)
|
||||
self.__set_crc_function_index()
|
||||
self.__ensure_same_length()
|
||||
self.__set_crc_info_label()
|
||||
elif self.checksum_label.category == self.checksum_label.Category.wsp:
|
||||
if self.checksum_label.checksum.mode == WSPChecksum.ChecksumMode.auto:
|
||||
self.ui.radioButtonWSPAuto.setChecked(True)
|
||||
elif self.checksum_label.checksum.mode == WSPChecksum.ChecksumMode.checksum4:
|
||||
self.ui.radioButtonWSPChecksum4.setChecked(True)
|
||||
elif self.checksum_label.checksum.mode == WSPChecksum.ChecksumMode.checksum8:
|
||||
self.ui.radioButtonWSPChecksum8.setChecked(True)
|
||||
elif self.checksum_label.checksum.mode == WSPChecksum.ChecksumMode.crc8:
|
||||
self.ui.radioButtonWSPCRC8.setChecked(True)
|
||||
|
||||
def set_ui_for_category(self):
|
||||
self.ui.comboBoxCategory.setCurrentText(self.checksum_label.category.value)
|
||||
if self.checksum_label.category == self.checksum_label.Category.generic:
|
||||
self.ui.stackedWidget.setCurrentWidget(self.ui.page_crc)
|
||||
elif self.checksum_label.category == self.checksum_label.Category.wsp:
|
||||
self.ui.stackedWidget.setCurrentWidget(self.ui.page_wsp)
|
||||
else:
|
||||
raise ValueError("Unknown category")
|
||||
|
||||
self.set_checksum_ui_elements()
|
||||
|
||||
def display_crc_data_ranges_in_table(self):
|
||||
self.data_range_table_model.update()
|
||||
|
||||
def __set_crc_function_index(self):
|
||||
# Get the combobox index
|
||||
crc_found = False
|
||||
for crc_name in GenericCRC.DEFAULT_POLYNOMIALS:
|
||||
test_crc = GenericCRC(crc_name)
|
||||
if test_crc == self.checksum_label.checksum:
|
||||
self.ui.comboBoxCRCFunction.setCurrentText(crc_name)
|
||||
crc_found = True
|
||||
break
|
||||
|
||||
if not crc_found:
|
||||
for crc_name, crc in self.SPECIAL_CRCS.items():
|
||||
if self.checksum_label.checksum == crc:
|
||||
self.ui.comboBoxCRCFunction.setCurrentText(crc_name)
|
||||
crc_found = True
|
||||
break
|
||||
|
||||
if not crc_found:
|
||||
self.__add_and_select_custom_item()
|
||||
elif "Custom" in [self.ui.comboBoxCRCFunction.itemText(i) for i in range(self.ui.comboBoxCRCFunction.count())]:
|
||||
self.ui.comboBoxCRCFunction.removeItem(self.ui.comboBoxCRCFunction.count() - 1)
|
||||
|
||||
|
||||
def __set_crc_info_label(self):
|
||||
crc = self.checksum_label.checksum # type: GenericCRC
|
||||
self.ui.label_crc_info.setText("<b>CRC Summary:</b><ul>"
|
||||
"<li>Polynomial = {}<>"
|
||||
"<li>Length of checksum = {} bit</li>"
|
||||
"<li>start value length = {} bit</li>"
|
||||
"<li>final XOR length = {} bit</li>"
|
||||
"</ul>".format(crc.polynomial_to_html, crc.poly_order-1,
|
||||
len(crc.start_value), len(crc.final_xor)))
|
||||
|
||||
def __ensure_same_length(self):
|
||||
for dependant_line_edit in [self.ui.lineEditStartValue, self.ui.lineEditFinalXOR]: # type: QLineEdit
|
||||
if len(self.ui.lineEditCRCPolynomial.text()) < len(dependant_line_edit.text()):
|
||||
dependant_line_edit.setText(dependant_line_edit.text()[:len(self.ui.lineEditCRCPolynomial.text())])
|
||||
dependant_line_edit.editingFinished.emit()
|
||||
elif len(self.ui.lineEditCRCPolynomial.text()) > len(dependant_line_edit.text()):
|
||||
# pad zeros at front
|
||||
dependant_line_edit.setText("0" * (len(self.ui.lineEditCRCPolynomial.text()) - len(dependant_line_edit.text()))
|
||||
+ dependant_line_edit.text())
|
||||
dependant_line_edit.editingFinished.emit()
|
||||
|
||||
def __add_and_select_custom_item(self):
|
||||
if "Custom" not in [self.ui.comboBoxCRCFunction.itemText(i) for i in range(self.ui.comboBoxCRCFunction.count())]:
|
||||
self.ui.comboBoxCRCFunction.addItem("Custom")
|
||||
self.ui.comboBoxCRCFunction.blockSignals(True)
|
||||
self.ui.comboBoxCRCFunction.setCurrentText("Custom")
|
||||
self.ui.comboBoxCRCFunction.blockSignals(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_add_range_clicked(self):
|
||||
self.checksum_label.data_ranges.append([0, self.checksum_label.start])
|
||||
self.data_range_table_model.update()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_remove_range_clicked(self):
|
||||
if len(self.checksum_label.data_ranges) > 1:
|
||||
self.checksum_label.data_ranges.pop(-1)
|
||||
self.data_range_table_model.update()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_crc_function_current_index_changed(self, index: int):
|
||||
poly_str = self.ui.comboBoxCRCFunction.itemText(index)
|
||||
if poly_str in GenericCRC.DEFAULT_POLYNOMIALS:
|
||||
self.checksum_label.checksum.polynomial = self.checksum_label.checksum.choose_polynomial(poly_str)
|
||||
self.checksum_label.checksum.start_value = array.array("B", [0] * (self.checksum_label.checksum.poly_order - 1))
|
||||
self.checksum_label.checksum.final_xor = array.array("B", [0] * (self.checksum_label.checksum.poly_order - 1))
|
||||
elif poly_str in self.SPECIAL_CRCS:
|
||||
self.checksum_label.checksum = copy.deepcopy(self.SPECIAL_CRCS[poly_str])
|
||||
else:
|
||||
logger.error("Unknown CRC")
|
||||
return
|
||||
|
||||
self.ui.lineEditCRCPolynomial.setText(self.checksum_label.checksum.polynomial_as_hex_str)
|
||||
self.ui.lineEditStartValue.setText(util.bit2hex(self.checksum_label.checksum.start_value))
|
||||
self.ui.lineEditFinalXOR.setText(util.bit2hex(self.checksum_label.checksum.final_xor))
|
||||
self.ui.lineEditCRCPolynomial.editingFinished.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_crc_polynomial_editing_finished(self):
|
||||
self.checksum_label.checksum.set_polynomial_from_hex(self.ui.lineEditCRCPolynomial.text())
|
||||
self.__ensure_same_length()
|
||||
self.__set_crc_info_label()
|
||||
self.__set_crc_function_index()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_check_box_ref_in_clicked(self):
|
||||
self.checksum_label.checksum.lsb_first = self.ui.checkBoxRefIn.isChecked()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_check_box_ref_out_clicked(self):
|
||||
self.checksum_label.checksum.reverse_all = self.ui.checkBoxRefOut.isChecked()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_start_value_editing_finished(self):
|
||||
crc = self.checksum_label.checksum
|
||||
start_value = util.hex2bit(self.ui.lineEditStartValue.text())
|
||||
# pad with zeros at front
|
||||
start_value = array.array("B", [0]*(crc.poly_order - 1 - len(start_value))) + start_value
|
||||
crc.start_value = start_value[0:crc.poly_order-1]
|
||||
self.ui.lineEditStartValue.setText(util.bit2hex(crc.start_value))
|
||||
self.__set_crc_info_label()
|
||||
self.__set_crc_function_index()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_final_xor_editing_finished(self):
|
||||
crc = self.checksum_label.checksum
|
||||
final_xor = util.hex2bit(self.ui.lineEditFinalXOR.text())
|
||||
final_xor = array.array("B", [0] * (crc.poly_order - 1 - len(final_xor))) + final_xor
|
||||
crc.final_xor = final_xor[0:crc.poly_order-1]
|
||||
self.ui.lineEditFinalXOR.setText(util.bit2hex(crc.final_xor))
|
||||
self.__set_crc_info_label()
|
||||
self.__set_crc_function_index()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_category_current_index_changed(self, index: int):
|
||||
self.checksum_label.category = self.checksum_label.Category(self.ui.comboBoxCategory.currentText())
|
||||
self.set_ui_for_category()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_radio_button_wsp_auto_clicked(self):
|
||||
self.checksum_label.checksum.mode = WSPChecksum.ChecksumMode.auto
|
||||
|
||||
@pyqtSlot()
|
||||
def on_radio_button_wsp_checksum4_clicked(self):
|
||||
self.checksum_label.checksum.mode = WSPChecksum.ChecksumMode.checksum4
|
||||
|
||||
@pyqtSlot()
|
||||
def on_radio_button_wsp_checksum8_clicked(self):
|
||||
self.checksum_label.checksum.mode = WSPChecksum.ChecksumMode.checksum8
|
||||
|
||||
@pyqtSlot()
|
||||
def on_radio_button_wsp_crc8_clicked(self):
|
||||
self.checksum_label.checksum.mode = WSPChecksum.ChecksumMode.crc8
|
@ -0,0 +1,553 @@
|
||||
from statistics import median
|
||||
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import QRegExp, pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtGui import QRegExpValidator, QIcon
|
||||
from PyQt5.QtWidgets import QWidget, QSpinBox, QLabel, QComboBox, QSlider
|
||||
|
||||
from urh import settings
|
||||
from urh.dev import config
|
||||
from urh.dev.BackendHandler import BackendHandler, Backends
|
||||
from urh.dev.VirtualDevice import VirtualDevice
|
||||
from urh.plugins.NetworkSDRInterface.NetworkSDRInterfacePlugin import NetworkSDRInterfacePlugin
|
||||
from urh.plugins.PluginManager import PluginManager
|
||||
from urh.ui.ui_send_recv_device_settings import Ui_FormDeviceSettings
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class DeviceSettingsWidget(QWidget):
|
||||
selected_device_changed = pyqtSignal()
|
||||
gain_edited = pyqtSignal()
|
||||
device_parameters_changed = pyqtSignal(dict)
|
||||
|
||||
def __init__(self, project_manager: ProjectManager, is_tx: bool, backend_handler: BackendHandler = None,
|
||||
continuous_send_mode=False, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_FormDeviceSettings()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.__device = None # type: VirtualDevice
|
||||
|
||||
self.is_tx = is_tx
|
||||
self.is_rx = not is_tx
|
||||
if backend_handler is None:
|
||||
self.backend_handler = BackendHandler()
|
||||
else:
|
||||
self.backend_handler = backend_handler
|
||||
|
||||
if self.is_rx:
|
||||
self.ui.spinBoxNRepeat.hide()
|
||||
self.ui.labelNRepeat.hide()
|
||||
else:
|
||||
self.ui.labelDCCorrection.hide()
|
||||
self.ui.checkBoxDCCorrection.hide()
|
||||
|
||||
self.bw_sr_are_locked = settings.read("lock_bandwidth_sample_rate", True, bool)
|
||||
self.ui.cbDevice.clear()
|
||||
items = self.get_devices_for_combobox(continuous_send_mode)
|
||||
self.ui.cbDevice.addItems(items)
|
||||
self.bootstrap(project_manager.device_conf, enforce_default=True)
|
||||
|
||||
self.ui.btnLockBWSR.setChecked(self.bw_sr_are_locked)
|
||||
self.on_btn_lock_bw_sr_clicked()
|
||||
|
||||
ip_range = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])"
|
||||
ip_regex = QRegExp("^" + ip_range
|
||||
+ "\\." + ip_range
|
||||
+ "\\." + ip_range
|
||||
+ "\\." + ip_range + "$")
|
||||
self.ui.lineEditIP.setValidator(QRegExpValidator(ip_regex))
|
||||
|
||||
self.create_connects()
|
||||
self.sync_gain_sliders()
|
||||
|
||||
def bootstrap(self, conf_dict: dict, enforce_default=False):
|
||||
def set_val(ui_widget, key: str, default):
|
||||
try:
|
||||
value = conf_dict[key]
|
||||
except KeyError:
|
||||
value = default if enforce_default else None
|
||||
|
||||
if value is not None:
|
||||
ui_widget.setValue(value)
|
||||
|
||||
self.set_bandwidth_status()
|
||||
|
||||
self.ui.cbDevice.setCurrentText(conf_dict.get("name", ""))
|
||||
dev_name = self.ui.cbDevice.currentText()
|
||||
self.set_device_ui_items_visibility(dev_name, overwrite_settings=True)
|
||||
|
||||
set_val(self.ui.spinBoxFreq, "frequency", config.DEFAULT_FREQUENCY)
|
||||
set_val(self.ui.spinBoxSampleRate, "sample_rate", config.DEFAULT_SAMPLE_RATE)
|
||||
set_val(self.ui.spinBoxBandwidth, "bandwidth", config.DEFAULT_BANDWIDTH)
|
||||
set_val(self.ui.spinBoxGain, self.rx_tx_prefix + "gain", config.DEFAULT_GAIN)
|
||||
set_val(self.ui.spinBoxIFGain, self.rx_tx_prefix + "if_gain", config.DEFAULT_IF_GAIN)
|
||||
set_val(self.ui.spinBoxBasebandGain, self.rx_tx_prefix + "baseband_gain", config.DEFAULT_BB_GAIN)
|
||||
set_val(self.ui.spinBoxFreqCorrection, "freq_correction", config.DEFAULT_FREQ_CORRECTION)
|
||||
set_val(self.ui.spinBoxNRepeat, "num_sending_repeats", settings.read('num_sending_repeats', 1, type=int))
|
||||
|
||||
self.ui.lineEditSubdevice.setText(conf_dict.get("subdevice", ""))
|
||||
|
||||
if self.rx_tx_prefix + "antenna_index" in conf_dict:
|
||||
self.ui.comboBoxAntenna.setCurrentIndex(conf_dict[self.rx_tx_prefix + "antenna_index"])
|
||||
|
||||
if self.rx_tx_prefix + "gain" not in conf_dict:
|
||||
self.set_default_rf_gain()
|
||||
|
||||
if self.rx_tx_prefix + "if_gain" not in conf_dict:
|
||||
self.set_default_if_gain()
|
||||
|
||||
if self.rx_tx_prefix + "baseband_gain" not in conf_dict:
|
||||
self.set_default_bb_gain()
|
||||
|
||||
if self.is_rx:
|
||||
checked = conf_dict.get("apply_dc_correction", True)
|
||||
if isinstance(checked, str):
|
||||
checked = True if checked == "True" else False
|
||||
self.ui.checkBoxDCCorrection.setChecked(checked)
|
||||
|
||||
checked = conf_dict.get("bias_tee_enabled", False)
|
||||
if isinstance(checked, str):
|
||||
checked = True if checked == "True" else False
|
||||
self.ui.checkBoxBiasTee.setChecked(checked)
|
||||
|
||||
self.emit_editing_finished_signals()
|
||||
|
||||
@property
|
||||
def device(self) -> VirtualDevice:
|
||||
return self.__device
|
||||
|
||||
@device.setter
|
||||
def device(self, value: VirtualDevice):
|
||||
self.__device = value
|
||||
|
||||
@property
|
||||
def rx_tx_prefix(self) -> str:
|
||||
return "rx_" if self.is_rx else "tx_"
|
||||
|
||||
@property
|
||||
def selected_device_conf(self) -> dict:
|
||||
device_name = self.ui.cbDevice.currentText()
|
||||
key = device_name if device_name in config.DEVICE_CONFIG.keys() else "Fallback"
|
||||
return config.DEVICE_CONFIG[key]
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.spinBoxFreq.editingFinished.connect(self.on_spinbox_frequency_editing_finished)
|
||||
self.ui.spinBoxSampleRate.editingFinished.connect(self.on_spinbox_sample_rate_editing_finished)
|
||||
|
||||
self.ui.spinBoxGain.editingFinished.connect(self.on_spinbox_gain_editing_finished)
|
||||
self.ui.spinBoxGain.valueChanged.connect(self.on_spinbox_gain_value_changed)
|
||||
self.ui.sliderGain.valueChanged.connect(self.on_slider_gain_value_changed)
|
||||
|
||||
self.ui.spinBoxIFGain.editingFinished.connect(self.on_spinbox_if_gain_editing_finished)
|
||||
self.ui.spinBoxIFGain.valueChanged.connect(self.on_spinbox_if_gain_value_changed)
|
||||
self.ui.sliderIFGain.valueChanged.connect(self.on_slider_if_gain_value_changed)
|
||||
|
||||
self.ui.spinBoxBasebandGain.editingFinished.connect(self.on_spinbox_baseband_gain_editing_finished)
|
||||
self.ui.spinBoxBasebandGain.valueChanged.connect(self.on_spinbox_baseband_gain_value_changed)
|
||||
self.ui.sliderBasebandGain.valueChanged.connect(self.on_slider_baseband_gain_value_changed)
|
||||
|
||||
self.ui.spinBoxBandwidth.editingFinished.connect(self.on_spinbox_bandwidth_editing_finished)
|
||||
self.ui.spinBoxPort.editingFinished.connect(self.on_spinbox_port_editing_finished)
|
||||
self.ui.lineEditIP.editingFinished.connect(self.on_line_edit_ip_editing_finished)
|
||||
self.ui.lineEditSubdevice.editingFinished.connect(self.on_line_edit_subdevice_editing_finished)
|
||||
|
||||
self.ui.comboBoxAntenna.currentIndexChanged.connect(self.on_combobox_antenna_current_index_changed)
|
||||
self.ui.comboBoxChannel.currentIndexChanged.connect(self.on_combobox_channel_current_index_changed)
|
||||
|
||||
self.ui.spinBoxFreqCorrection.editingFinished.connect(self.on_spinbox_freq_correction_editing_finished)
|
||||
self.ui.comboBoxDirectSampling.currentIndexChanged.connect(self.on_combobox_direct_sampling_index_changed)
|
||||
|
||||
self.ui.cbDevice.currentIndexChanged.connect(self.on_cb_device_current_index_changed)
|
||||
|
||||
self.ui.spinBoxNRepeat.editingFinished.connect(self.on_num_repeats_changed)
|
||||
self.ui.btnLockBWSR.clicked.connect(self.on_btn_lock_bw_sr_clicked)
|
||||
|
||||
self.ui.btnRefreshDeviceIdentifier.clicked.connect(self.on_btn_refresh_device_identifier_clicked)
|
||||
self.ui.comboBoxDeviceIdentifier.currentIndexChanged.connect(
|
||||
self.on_combo_box_device_identifier_current_index_changed)
|
||||
|
||||
self.ui.comboBoxDeviceIdentifier.editTextChanged.connect(self.on_combo_box_device_identifier_edit_text_changed)
|
||||
|
||||
self.ui.checkBoxBiasTee.clicked.connect(self.on_check_box_bias_tee_clicked)
|
||||
self.ui.checkBoxDCCorrection.clicked.connect(self.on_check_box_dc_correction_clicked)
|
||||
|
||||
def set_gain_defaults(self):
|
||||
self.set_default_rf_gain()
|
||||
self.set_default_if_gain()
|
||||
self.set_default_bb_gain()
|
||||
|
||||
def set_default_rf_gain(self):
|
||||
conf = self.selected_device_conf
|
||||
prefix = self.rx_tx_prefix
|
||||
if prefix + "rf_gain" in conf:
|
||||
key = prefix + "rf_gain"
|
||||
gain = conf[key][int(np.percentile(range(len(conf[key])), 25))]
|
||||
self.ui.spinBoxGain.setValue(gain)
|
||||
|
||||
def set_default_if_gain(self):
|
||||
conf = self.selected_device_conf
|
||||
prefix = self.rx_tx_prefix
|
||||
if prefix + "if_gain" in conf:
|
||||
key = prefix + "if_gain"
|
||||
if_gain = conf[key][int(median(range(len(conf[key]))))]
|
||||
self.ui.spinBoxIFGain.setValue(if_gain)
|
||||
|
||||
def set_default_bb_gain(self):
|
||||
conf = self.selected_device_conf
|
||||
prefix = self.rx_tx_prefix
|
||||
if prefix + "baseband_gain" in conf:
|
||||
key = prefix + "baseband_gain"
|
||||
baseband_gain = conf[key][int(np.percentile(list(range(len(conf[key]))), 25))]
|
||||
self.ui.spinBoxBasebandGain.setValue(baseband_gain)
|
||||
|
||||
def sync_gain_sliders(self):
|
||||
self.ui.spinBoxGain.valueChanged.emit(self.ui.spinBoxGain.value())
|
||||
self.ui.spinBoxIFGain.valueChanged.emit(self.ui.spinBoxIFGain.value())
|
||||
self.ui.spinBoxBasebandGain.valueChanged.emit(self.ui.spinBoxBasebandGain.value())
|
||||
|
||||
def set_device_ui_items_visibility(self, device_name: str, overwrite_settings=True):
|
||||
key = device_name if device_name in config.DEVICE_CONFIG.keys() else "Fallback"
|
||||
conf = config.DEVICE_CONFIG[key]
|
||||
key_ui_dev_param_map = {"center_freq": "Freq", "sample_rate": "SampleRate", "bandwidth": "Bandwidth"}
|
||||
|
||||
for key, ui_item in key_ui_dev_param_map.items():
|
||||
spinbox = getattr(self.ui, "spinBox" + ui_item) # type: QSpinBox
|
||||
label = getattr(self.ui, "label" + ui_item) # type: QLabel
|
||||
if key in conf:
|
||||
spinbox.setVisible(True)
|
||||
label.setVisible(True)
|
||||
|
||||
if isinstance(conf[key], list):
|
||||
spinbox.setMinimum(min(conf[key]))
|
||||
spinbox.setMaximum(max(conf[key]))
|
||||
spinbox.setSingleStep(conf[key][1] - conf[key][0])
|
||||
spinbox.auto_update_step_size = False
|
||||
if "default_" + key in conf:
|
||||
spinbox.setValue(conf["default_" + key])
|
||||
else:
|
||||
spinbox.setMinimum(conf[key].start)
|
||||
spinbox.setMaximum(conf[key].stop)
|
||||
spinbox.auto_update_step_size = True
|
||||
spinbox.adjust_step()
|
||||
else:
|
||||
spinbox.setVisible(False)
|
||||
label.setVisible(False)
|
||||
|
||||
self.ui.btnLockBWSR.setVisible("sample_rate" in conf and "bandwidth" in conf)
|
||||
|
||||
if self.device is not None:
|
||||
self.ui.labelSubdevice.setVisible(self.device.subdevice is not None)
|
||||
self.ui.lineEditSubdevice.setVisible(self.device.subdevice is not None)
|
||||
|
||||
if "freq_correction" in conf:
|
||||
self.ui.labelFreqCorrection.setVisible(True)
|
||||
self.ui.spinBoxFreqCorrection.setVisible(True)
|
||||
self.ui.spinBoxFreqCorrection.setMinimum(conf["freq_correction"].start)
|
||||
self.ui.spinBoxFreqCorrection.setMaximum(conf["freq_correction"].stop)
|
||||
self.ui.spinBoxFreqCorrection.setSingleStep(conf["freq_correction"].step)
|
||||
else:
|
||||
self.ui.labelFreqCorrection.setVisible(False)
|
||||
self.ui.spinBoxFreqCorrection.setVisible(False)
|
||||
|
||||
if "direct_sampling" in conf:
|
||||
self.ui.labelDirectSampling.setVisible(True)
|
||||
self.ui.comboBoxDirectSampling.setVisible(True)
|
||||
items = [self.ui.comboBoxDirectSampling.itemText(i) for i in range(self.ui.comboBoxDirectSampling.count())]
|
||||
if items != conf["direct_sampling"]:
|
||||
self.ui.comboBoxDirectSampling.clear()
|
||||
self.ui.comboBoxDirectSampling.addItems(conf["direct_sampling"])
|
||||
else:
|
||||
self.ui.labelDirectSampling.setVisible(False)
|
||||
self.ui.comboBoxDirectSampling.setVisible(False)
|
||||
|
||||
prefix = self.rx_tx_prefix
|
||||
key_ui_gain_map = {prefix + "rf_gain": "Gain", prefix + "if_gain": "IFGain",
|
||||
prefix + "baseband_gain": "BasebandGain"}
|
||||
for conf_key, ui_element in key_ui_gain_map.items():
|
||||
getattr(self.ui, "label" + ui_element).setVisible(conf_key in conf)
|
||||
|
||||
spinbox = getattr(self.ui, "spinBox" + ui_element) # type: QSpinBox
|
||||
slider = getattr(self.ui, "slider" + ui_element) # type: QSlider
|
||||
|
||||
if conf_key in conf:
|
||||
gain_values = conf[conf_key]
|
||||
assert len(gain_values) >= 2
|
||||
spinbox.setMinimum(gain_values[0])
|
||||
spinbox.setMaximum(gain_values[-1])
|
||||
if overwrite_settings:
|
||||
spinbox.setValue(gain_values[len(gain_values) // 2])
|
||||
spinbox.setSingleStep(gain_values[1] - gain_values[0])
|
||||
spinbox.setVisible(True)
|
||||
|
||||
slider.setMaximum(len(gain_values) - 1)
|
||||
else:
|
||||
spinbox.setVisible(False)
|
||||
slider.setVisible(False)
|
||||
getattr(self.ui, "slider" + ui_element).setVisible(conf_key in conf)
|
||||
|
||||
if overwrite_settings:
|
||||
key_ui_channel_ant_map = {prefix + "antenna": "Antenna", prefix + "channel": "Channel"}
|
||||
for conf_key, ui_element in key_ui_channel_ant_map.items():
|
||||
getattr(self.ui, "label" + ui_element).setVisible(conf_key in conf)
|
||||
combobox = getattr(self.ui, "comboBox" + ui_element) # type: QComboBox
|
||||
if conf_key in conf:
|
||||
combobox.clear()
|
||||
combobox.addItems(conf[conf_key])
|
||||
if conf_key + "_default_index" in conf:
|
||||
combobox.setCurrentIndex(conf[conf_key + "_default_index"])
|
||||
|
||||
combobox.setVisible(True)
|
||||
else:
|
||||
combobox.setVisible(False)
|
||||
|
||||
multi_dev_support = hasattr(self.device, "has_multi_device_support") and self.device.has_multi_device_support
|
||||
self.ui.labelDeviceIdentifier.setVisible(multi_dev_support)
|
||||
self.ui.btnRefreshDeviceIdentifier.setVisible(multi_dev_support)
|
||||
self.ui.comboBoxDeviceIdentifier.setVisible(multi_dev_support)
|
||||
self.ui.lineEditIP.setVisible("ip" in conf)
|
||||
self.ui.labelIP.setVisible("ip" in conf)
|
||||
self.ui.spinBoxPort.setVisible("port" in conf)
|
||||
self.ui.labelPort.setVisible("port" in conf)
|
||||
show_dc_correction = self.is_rx and self.device is not None and self.device.apply_dc_correction is not None
|
||||
self.ui.checkBoxDCCorrection.setVisible(show_dc_correction)
|
||||
self.ui.labelDCCorrection.setVisible(show_dc_correction)
|
||||
|
||||
show_bias_tee = "bias_tee_enabled" in conf and self.device is not None and self.device.bias_tee_enabled is not None
|
||||
self.ui.labelBiasTee.setVisible(show_bias_tee)
|
||||
self.ui.checkBoxBiasTee.setVisible(show_bias_tee)
|
||||
|
||||
def get_devices_for_combobox(self, continuous_send_mode):
|
||||
items = []
|
||||
for device_name in self.backend_handler.DEVICE_NAMES:
|
||||
dev = self.backend_handler.device_backends[device_name.lower()]
|
||||
if self.is_tx and dev.is_enabled and dev.supports_tx:
|
||||
if not continuous_send_mode:
|
||||
items.append(device_name)
|
||||
elif dev.selected_backend != Backends.grc:
|
||||
items.append(device_name)
|
||||
elif self.is_rx and dev.is_enabled and dev.supports_rx:
|
||||
items.append(device_name)
|
||||
|
||||
if PluginManager().is_plugin_enabled("NetworkSDRInterface"):
|
||||
items.append(NetworkSDRInterfacePlugin.NETWORK_SDR_NAME)
|
||||
|
||||
return items
|
||||
|
||||
def set_bandwidth_status(self):
|
||||
if hasattr(self, "device") and self.device is not None and self.device.backend != Backends.none:
|
||||
self.ui.spinBoxBandwidth.setEnabled(self.device.bandwidth_is_adjustable)
|
||||
self.ui.btnLockBWSR.setEnabled(self.device.bandwidth_is_adjustable)
|
||||
|
||||
if not self.device.bandwidth_is_adjustable:
|
||||
self.bw_sr_are_locked = False
|
||||
self.ui.spinBoxBandwidth.setToolTip(self.tr("Your driver of RTL-SDR does not support "
|
||||
"setting the bandwidth. "
|
||||
"If you need this feature, install a recent version."))
|
||||
else:
|
||||
self.ui.spinBoxBandwidth.setToolTip("")
|
||||
self.bw_sr_are_locked = self.ui.btnLockBWSR.isChecked()
|
||||
|
||||
def emit_editing_finished_signals(self):
|
||||
self.ui.spinBoxFreq.editingFinished.emit()
|
||||
self.ui.spinBoxBandwidth.editingFinished.emit()
|
||||
self.ui.spinBoxGain.editingFinished.emit()
|
||||
self.ui.spinBoxIFGain.editingFinished.emit()
|
||||
self.ui.spinBoxBasebandGain.editingFinished.emit()
|
||||
self.ui.spinBoxNRepeat.editingFinished.emit()
|
||||
self.ui.spinBoxSampleRate.editingFinished.emit()
|
||||
self.ui.spinBoxFreqCorrection.editingFinished.emit()
|
||||
self.ui.lineEditIP.editingFinished.emit()
|
||||
self.ui.lineEditSubdevice.editingFinished.emit()
|
||||
self.ui.spinBoxPort.editingFinished.emit()
|
||||
self.ui.comboBoxAntenna.currentIndexChanged.emit(self.ui.comboBoxAntenna.currentIndex())
|
||||
self.ui.comboBoxChannel.currentIndexChanged.emit(self.ui.comboBoxChannel.currentIndex())
|
||||
self.ui.checkBoxDCCorrection.clicked.emit(self.ui.checkBoxDCCorrection.isChecked())
|
||||
self.ui.checkBoxBiasTee.clicked.emit(self.ui.checkBoxBiasTee.isChecked())
|
||||
|
||||
def emit_device_parameters_changed(self):
|
||||
settings = {"name": str(self.device.name)}
|
||||
for attrib in ("frequency", "sample_rate", "bandwidth", "gain", "if_gain", "baseband_gain", "freq_correction",
|
||||
"antenna_index", "num_sending_repeats", "apply_dc_correction", "subdevice", "bias_tee_enabled"):
|
||||
try:
|
||||
value = getattr(self.device, attrib, None)
|
||||
if value is not None:
|
||||
if "gain" in attrib or attrib == "antenna_index":
|
||||
attrib = self.rx_tx_prefix + attrib
|
||||
settings[attrib] = value
|
||||
except (ValueError, AttributeError):
|
||||
continue
|
||||
|
||||
self.device_parameters_changed.emit(settings)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_lock_bw_sr_clicked(self):
|
||||
self.bw_sr_are_locked = self.ui.btnLockBWSR.isChecked()
|
||||
settings.write("lock_bandwidth_sample_rate", self.bw_sr_are_locked)
|
||||
if self.bw_sr_are_locked:
|
||||
self.ui.btnLockBWSR.setIcon(QIcon(":/icons/icons/lock.svg"))
|
||||
self.ui.spinBoxBandwidth.setValue(self.ui.spinBoxSampleRate.value())
|
||||
self.ui.spinBoxBandwidth.editingFinished.emit()
|
||||
else:
|
||||
self.ui.btnLockBWSR.setIcon(QIcon(":/icons/icons/unlock.svg"))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_sample_rate_editing_finished(self):
|
||||
self.device.sample_rate = self.ui.spinBoxSampleRate.value()
|
||||
if self.bw_sr_are_locked:
|
||||
self.ui.spinBoxBandwidth.setValue(self.ui.spinBoxSampleRate.value())
|
||||
self.device.bandwidth = self.ui.spinBoxBandwidth.value()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_frequency_editing_finished(self):
|
||||
self.device.frequency = self.ui.spinBoxFreq.value()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_bandwidth_editing_finished(self):
|
||||
self.device.bandwidth = self.ui.spinBoxBandwidth.value()
|
||||
if self.bw_sr_are_locked:
|
||||
self.ui.spinBoxSampleRate.setValue(self.ui.spinBoxBandwidth.value())
|
||||
self.device.sample_rate = self.ui.spinBoxSampleRate.value()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_ip_editing_finished(self):
|
||||
self.device.ip = self.ui.lineEditIP.text()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_subdevice_editing_finished(self):
|
||||
self.device.subdevice = self.ui.lineEditSubdevice.text()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_port_editing_finished(self):
|
||||
self.device.port = self.ui.spinBoxPort.value()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_antenna_current_index_changed(self, index: int):
|
||||
self.device.antenna_index = index
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_channel_current_index_changed(self, index: int):
|
||||
self.device.channel_index = index
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_freq_correction_editing_finished(self):
|
||||
self.device.freq_correction = self.ui.spinBoxFreqCorrection.value()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_direct_sampling_index_changed(self, index: int):
|
||||
self.device.direct_sampling_mode = index
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_gain_editing_finished(self):
|
||||
self.device.gain = self.ui.spinBoxGain.value()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_gain_value_changed(self, value: int):
|
||||
dev_conf = self.selected_device_conf
|
||||
try:
|
||||
self.ui.sliderGain.setValue(dev_conf[self.rx_tx_prefix + "rf_gain"].index(value))
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_slider_gain_value_changed(self, value: int):
|
||||
dev_conf = self.selected_device_conf
|
||||
self.ui.spinBoxGain.setValue(dev_conf[self.rx_tx_prefix + "rf_gain"][value])
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_if_gain_editing_finished(self):
|
||||
self.device.if_gain = self.ui.spinBoxIFGain.value()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_slider_if_gain_value_changed(self, value: int):
|
||||
dev_conf = self.selected_device_conf
|
||||
self.ui.spinBoxIFGain.setValue(dev_conf[self.rx_tx_prefix + "if_gain"][value])
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_if_gain_value_changed(self, value: int):
|
||||
dev_conf = self.selected_device_conf
|
||||
try:
|
||||
self.ui.sliderIFGain.setValue(dev_conf[self.rx_tx_prefix + "if_gain"].index(value))
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
|
||||
@pyqtSlot()
|
||||
def on_num_repeats_changed(self):
|
||||
self.device.num_sending_repeats = self.ui.spinBoxNRepeat.value()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spinbox_baseband_gain_editing_finished(self):
|
||||
self.device.baseband_gain = self.ui.spinBoxBasebandGain.value()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_slider_baseband_gain_value_changed(self, value: int):
|
||||
dev_conf = self.selected_device_conf
|
||||
self.ui.spinBoxBasebandGain.setValue(dev_conf[self.rx_tx_prefix + "baseband_gain"][value])
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_spinbox_baseband_gain_value_changed(self, value: int):
|
||||
dev_conf = self.selected_device_conf
|
||||
try:
|
||||
self.ui.sliderBasebandGain.setValue(dev_conf[self.rx_tx_prefix + "baseband_gain"].index(value))
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
|
||||
def update_for_new_device(self, overwrite_settings=True):
|
||||
if self.device is not None:
|
||||
self.device.free_data()
|
||||
|
||||
# Here init_device of dialogs gets called
|
||||
self.selected_device_changed.emit()
|
||||
|
||||
dev_name = self.ui.cbDevice.currentText()
|
||||
self.set_device_ui_items_visibility(dev_name, overwrite_settings=overwrite_settings)
|
||||
|
||||
if overwrite_settings:
|
||||
self.set_gain_defaults()
|
||||
|
||||
self.sync_gain_sliders()
|
||||
self.set_bandwidth_status()
|
||||
|
||||
self.ui.comboBoxDeviceIdentifier.clear()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_cb_device_current_index_changed(self):
|
||||
self.update_for_new_device(overwrite_settings=True)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_refresh_device_identifier_clicked(self):
|
||||
if self.device is None:
|
||||
return
|
||||
self.ui.comboBoxDeviceIdentifier.clear()
|
||||
self.ui.comboBoxDeviceIdentifier.addItems(self.device.get_device_list())
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_check_box_bias_tee_clicked(self, checked: bool):
|
||||
if self.device is not None:
|
||||
self.device.bias_tee_enabled = bool(checked)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_check_box_dc_correction_clicked(self, checked: bool):
|
||||
self.device.apply_dc_correction = bool(checked)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_combo_box_device_identifier_current_index_changed(self):
|
||||
if self.device is not None:
|
||||
self.device.device_serial = self.ui.comboBoxDeviceIdentifier.currentText()
|
||||
self.device.device_number = self.ui.comboBoxDeviceIdentifier.currentIndex()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_combo_box_device_identifier_edit_text_changed(self, new_text: str):
|
||||
self.device.device_serial = new_text
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from urh.controller.MainController import MainController
|
||||
|
||||
app = QApplication([])
|
||||
mc = MainController()
|
||||
widget = DeviceSettingsWidget(mc.project_manager, is_tx=False)
|
||||
|
||||
widget.show()
|
||||
app.exec_()
|
@ -0,0 +1,88 @@
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
|
||||
from urh import settings
|
||||
from urh.controller.dialogs.ModulatorDialog import ModulatorDialog
|
||||
from urh.signalprocessing.Modulator import Modulator
|
||||
from urh.ui.ui_modulation_settings_widget import Ui_ModulationSettings
|
||||
|
||||
|
||||
class ModulationSettingsWidget(QWidget):
|
||||
def __init__(self, modulators, selected_index=0, signal_tree_model=None, parent=None):
|
||||
"""
|
||||
|
||||
:type modulators: list of Modulator
|
||||
:param parent:
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_ModulationSettings()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.ui.labelModulationProfile.setVisible(settings.read("multiple_modulations", False, bool))
|
||||
self.ui.comboBoxModulationProfiles.setVisible(settings.read("multiple_modulations", False, bool))
|
||||
|
||||
self.signal_tree_model = signal_tree_model
|
||||
self.modulators = modulators # type: list[Modulator]
|
||||
for modulator in self.modulators:
|
||||
self.ui.comboBoxModulationProfiles.addItem(modulator.name)
|
||||
|
||||
self.ui.comboBoxModulationProfiles.setCurrentIndex(selected_index)
|
||||
|
||||
self.show_selected_modulation_infos()
|
||||
self.create_connects()
|
||||
|
||||
@property
|
||||
def selected_modulator(self) -> Modulator:
|
||||
return self.modulators[self.ui.comboBoxModulationProfiles.currentIndex()]
|
||||
|
||||
@selected_modulator.setter
|
||||
def selected_modulator(self, value: Modulator):
|
||||
if value in self.modulators:
|
||||
self.ui.comboBoxModulationProfiles.setCurrentIndex(self.modulators.index(value))
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.comboBoxModulationProfiles.currentIndexChanged.connect(self.on_cb_modulation_type_current_index_changed)
|
||||
self.ui.btnConfigurationDialog.clicked.connect(self.on_btn_configuration_dialog_clicked)
|
||||
|
||||
def show_selected_modulation_infos(self):
|
||||
modulator = self.selected_modulator
|
||||
self.ui.labelCarrierFrequencyValue.setText(modulator.carrier_frequency_str)
|
||||
self.ui.labelSamplesPerSymbolValue.setText(modulator.samples_per_symbol_str)
|
||||
self.ui.labelSampleRateValue.setText(modulator.sample_rate_str)
|
||||
self.ui.labelModulationTypeValue.setText(modulator.modulation_type_verbose)
|
||||
|
||||
self.ui.labelParameters.setText(modulator.parameter_type_str)
|
||||
self.ui.labelParameterValues.setText(modulator.parameters_string)
|
||||
self.ui.labelBitsPerSymbol.setText(str(modulator.bits_per_symbol))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_cb_modulation_type_current_index_changed(self):
|
||||
self.show_selected_modulation_infos()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_configuration_dialog_clicked(self):
|
||||
dialog = ModulatorDialog(self.modulators, tree_model=self.signal_tree_model, parent=self)
|
||||
dialog.ui.comboBoxCustomModulations.setCurrentIndex(self.ui.comboBoxModulationProfiles.currentIndex())
|
||||
dialog.finished.connect(self.refresh_modulators_from_dialog)
|
||||
dialog.show()
|
||||
dialog.initialize("10101011010010")
|
||||
|
||||
@pyqtSlot()
|
||||
def refresh_modulators_from_dialog(self):
|
||||
current_index = 0
|
||||
if type(self.sender()) == ModulatorDialog:
|
||||
current_index = self.sender().ui.comboBoxCustomModulations.currentIndex()
|
||||
|
||||
self.ui.comboBoxModulationProfiles.clear()
|
||||
for modulator in self.modulators:
|
||||
self.ui.comboBoxModulationProfiles.addItem(modulator.name)
|
||||
|
||||
self.ui.comboBoxModulationProfiles.setCurrentIndex(current_index)
|
||||
self.show_selected_modulation_infos()
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
app = QApplication([""])
|
||||
w = ModulationSettingsWidget([Modulator("test")])
|
||||
w.show()
|
||||
app.exec()
|
@ -0,0 +1,47 @@
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtWidgets import QFrame, QVBoxLayout
|
||||
|
||||
from urh import settings
|
||||
from urh.models.PluginListModel import PluginListModel
|
||||
from urh.ui.ui_plugins import Ui_FramePlugins
|
||||
|
||||
|
||||
class PluginFrame(QFrame):
|
||||
def __init__(self, plugins, highlighted_plugins=None, parent=None):
|
||||
"""
|
||||
:type plugins: list of Plugin
|
||||
:type highlighted_plugins: list of Plugin
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_FramePlugins()
|
||||
self.ui.setupUi(self)
|
||||
self.model = PluginListModel(plugins, highlighted_plugins=highlighted_plugins)
|
||||
self.ui.listViewPlugins.setModel(self.model)
|
||||
self.settings_layout = QVBoxLayout()
|
||||
self.ui.groupBoxSettings.setLayout(self.settings_layout)
|
||||
self.create_connects()
|
||||
|
||||
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
|
||||
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.listViewPlugins.selectionModel().selectionChanged.connect(self.on_list_selection_changed)
|
||||
for plugin in self.model.plugins:
|
||||
if hasattr(plugin, "show_proto_sniff_dialog_clicked"):
|
||||
plugin.show_proto_sniff_dialog_clicked.connect(self.parent().parent().show_proto_sniff_dialog)
|
||||
|
||||
def save_enabled_states(self):
|
||||
for plugin in self.model.plugins:
|
||||
settings.write(plugin.name, plugin.enabled)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_list_selection_changed(self):
|
||||
i = self.ui.listViewPlugins.currentIndex().row()
|
||||
self.ui.txtEditPluginDescription.setText(self.model.plugins[i].description)
|
||||
|
||||
if self.settings_layout.count() > 0:
|
||||
widget = self.settings_layout.takeAt(0).widget()
|
||||
self.settings_layout.removeWidget(widget)
|
||||
widget.setParent(None)
|
||||
|
||||
self.settings_layout.addWidget(self.model.plugins[i].settings_frame)
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,229 @@
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtWidgets import QWidget, QCompleter, QDirModel
|
||||
|
||||
from urh import settings
|
||||
from urh.dev.BackendHandler import BackendHandler
|
||||
from urh.signalprocessing.ProtocolSniffer import ProtocolSniffer
|
||||
from urh.ui.ui_send_recv_sniff_settings import Ui_SniffSettings
|
||||
from urh.util.ProjectManager import ProjectManager
|
||||
|
||||
|
||||
class SniffSettingsWidget(QWidget):
|
||||
sniff_setting_edited = pyqtSignal()
|
||||
sniff_file_edited = pyqtSignal()
|
||||
sniff_parameters_changed = pyqtSignal(dict)
|
||||
|
||||
def __init__(self, device_name: str, project_manager: ProjectManager, signal=None, backend_handler=None,
|
||||
network_raw_mode=False, signals=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_SniffSettings()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
signals = signals if signals is not None else []
|
||||
self.project_manager = project_manager
|
||||
|
||||
for encoding in self.project_manager.decodings:
|
||||
self.ui.comboBox_sniff_encoding.addItem(encoding.name)
|
||||
|
||||
self.bootstrap(project_manager.device_conf, signal, enforce_default=True)
|
||||
|
||||
self.sniffer = ProtocolSniffer(samples_per_symbol=self.ui.spinbox_sniff_SamplesPerSymbol.value(),
|
||||
center=self.ui.spinbox_sniff_Center.value(),
|
||||
center_spacing=self.ui.spinBoxCenterSpacing.value(),
|
||||
noise=self.ui.spinbox_sniff_Noise.value(),
|
||||
tolerance=self.ui.spinbox_sniff_ErrorTolerance.value(),
|
||||
modulation_type=self.ui.combox_sniff_Modulation.currentText(),
|
||||
bits_per_symbol=self.ui.spinBoxBitsPerSymbol.value(),
|
||||
device=device_name,
|
||||
backend_handler=BackendHandler() if backend_handler is None else backend_handler,
|
||||
network_raw_mode=network_raw_mode)
|
||||
|
||||
self.sniffer.adaptive_noise = self.ui.checkBoxAdaptiveNoise.isChecked()
|
||||
self.sniffer.automatic_center = self.ui.checkBoxAutoCenter.isChecked()
|
||||
|
||||
self.__set_center_offset_visibility()
|
||||
|
||||
self.create_connects()
|
||||
self.ui.comboBox_sniff_encoding.currentIndexChanged.emit(self.ui.comboBox_sniff_encoding.currentIndex())
|
||||
self.ui.comboBox_sniff_viewtype.setCurrentIndex(settings.read('default_view', 0, int))
|
||||
|
||||
# Auto Complete like a Boss
|
||||
completer = QCompleter()
|
||||
completer.setModel(QDirModel(completer))
|
||||
self.ui.lineEdit_sniff_OutputFile.setCompleter(completer)
|
||||
|
||||
self.signals = signals
|
||||
|
||||
if len(signals) == 0:
|
||||
self.ui.label_sniff_Signal.hide()
|
||||
self.ui.btn_sniff_use_signal.hide()
|
||||
self.ui.comboBox_sniff_signal.hide()
|
||||
else:
|
||||
for signal in signals:
|
||||
self.ui.comboBox_sniff_signal.addItem(signal.name)
|
||||
|
||||
def __set_center_offset_visibility(self):
|
||||
visible = self.ui.spinBoxBitsPerSymbol.value() > 1
|
||||
self.ui.labelCenterSpacing.setVisible(visible)
|
||||
self.ui.spinBoxCenterSpacing.setVisible(visible)
|
||||
|
||||
def bootstrap(self, conf_dict: dict, signal=None, enforce_default=False):
|
||||
def set_val(widget, key: str, default):
|
||||
try:
|
||||
value = conf_dict[key]
|
||||
except KeyError:
|
||||
value = default if enforce_default else None
|
||||
|
||||
if value is not None:
|
||||
if hasattr(widget, "setValue"):
|
||||
widget.setValue(value)
|
||||
elif hasattr(widget, "setCurrentIndex"):
|
||||
widget.setCurrentIndex(value)
|
||||
|
||||
set_val(self.ui.spinbox_sniff_SamplesPerSymbol, "samples_per_symbol", signal.samples_per_symbol if signal else 100)
|
||||
set_val(self.ui.spinbox_sniff_Center, "center", signal.center if signal else 0.02)
|
||||
set_val(self.ui.spinBoxCenterSpacing, "center_spacing", signal.center_spacing if signal else 0.1)
|
||||
set_val(self.ui.spinbox_sniff_ErrorTolerance, "tolerance", signal.tolerance if signal else 5)
|
||||
set_val(self.ui.spinbox_sniff_Noise, "noise", signal.noise_threshold_relative if signal else 0.001)
|
||||
self.ui.combox_sniff_Modulation.setCurrentText(conf_dict.get("modulation_type", signal.modulation_type if signal else "FSK"))
|
||||
set_val(self.ui.spinBoxBitsPerSymbol, "bits_per_symbol", signal.bits_per_symbol if signal else 1)
|
||||
self.ui.comboBox_sniff_encoding.setCurrentText(conf_dict.get("decoding_name", ""))
|
||||
self.ui.checkBoxAdaptiveNoise.setChecked(bool(conf_dict.get("adaptive_noise", False)))
|
||||
self.ui.checkBoxAutoCenter.setChecked(bool(conf_dict.get("automatic_center", False)))
|
||||
self.ui.spinbox_sniff_Center.setDisabled(self.ui.checkBoxAutoCenter.isChecked())
|
||||
|
||||
self.emit_editing_finished_signals()
|
||||
|
||||
def create_connects(self):
|
||||
self.ui.spinbox_sniff_Noise.editingFinished.connect(self.on_noise_edited)
|
||||
self.ui.spinbox_sniff_Center.editingFinished.connect(self.on_center_edited)
|
||||
self.ui.spinBoxCenterSpacing.editingFinished.connect(self.on_center_spacing_edited)
|
||||
self.ui.spinbox_sniff_SamplesPerSymbol.editingFinished.connect(self.on_samples_per_symbol_edited)
|
||||
self.ui.spinbox_sniff_ErrorTolerance.editingFinished.connect(self.on_tolerance_edited)
|
||||
self.ui.combox_sniff_Modulation.currentTextChanged.connect(self.on_modulation_changed)
|
||||
self.ui.spinBoxBitsPerSymbol.editingFinished.connect(self.on_spin_box_bits_per_symbol_editing_finished)
|
||||
|
||||
self.ui.comboBox_sniff_viewtype.currentIndexChanged.connect(self.on_view_type_changed)
|
||||
self.ui.lineEdit_sniff_OutputFile.editingFinished.connect(self.on_line_edit_output_file_editing_finished)
|
||||
self.ui.comboBox_sniff_encoding.currentIndexChanged.connect(self.on_combobox_sniff_encoding_index_changed)
|
||||
self.ui.checkBox_sniff_Timestamp.clicked.connect(self.on_checkbox_sniff_timestamp_clicked)
|
||||
self.ui.btn_sniff_use_signal.clicked.connect(self.on_btn_sniff_use_signal_clicked)
|
||||
self.ui.checkBoxAdaptiveNoise.clicked.connect(self.on_check_box_adaptive_noise_clicked)
|
||||
self.ui.checkBoxAutoCenter.clicked.connect(self.on_check_box_auto_center_clicked)
|
||||
|
||||
def emit_editing_finished_signals(self):
|
||||
self.ui.spinbox_sniff_Noise.editingFinished.emit()
|
||||
self.ui.spinbox_sniff_Center.editingFinished.emit()
|
||||
self.ui.spinBoxCenterSpacing.editingFinished.emit()
|
||||
self.ui.spinbox_sniff_SamplesPerSymbol.editingFinished.emit()
|
||||
self.ui.spinBoxBitsPerSymbol.editingFinished.emit()
|
||||
self.ui.spinbox_sniff_ErrorTolerance.editingFinished.emit()
|
||||
self.ui.lineEdit_sniff_OutputFile.editingFinished.emit()
|
||||
self.ui.checkBoxAdaptiveNoise.clicked.emit()
|
||||
|
||||
def emit_sniff_parameters_changed(self):
|
||||
self.sniff_parameters_changed.emit(dict(samples_per_symbol=self.sniffer.signal.samples_per_symbol,
|
||||
center=self.sniffer.signal.center,
|
||||
center_spacing=self.sniffer.signal.center_spacing,
|
||||
noise=self.sniffer.signal.noise_threshold,
|
||||
tolerance=self.sniffer.signal.tolerance,
|
||||
modulation_type=self.sniffer.signal.modulation_type,
|
||||
bits_per_symbol=self.sniffer.signal.bits_per_symbol,
|
||||
decoding_name=self.sniffer.decoder.name,
|
||||
adaptive_noise=self.sniffer.adaptive_noise,
|
||||
automatic_center=self.sniffer.automatic_center))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_noise_edited(self):
|
||||
self.sniffer.signal.noise_threshold_relative = self.ui.spinbox_sniff_Noise.value()
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_center_edited(self):
|
||||
self.sniffer.signal.center = self.ui.spinbox_sniff_Center.value()
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_center_spacing_edited(self):
|
||||
self.sniffer.signal.center_spacing = self.ui.spinBoxCenterSpacing.value()
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_samples_per_symbol_edited(self):
|
||||
self.sniffer.signal.samples_per_symbol = self.ui.spinbox_sniff_SamplesPerSymbol.value()
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spin_box_bits_per_symbol_editing_finished(self):
|
||||
self.sniffer.signal.bits_per_symbol = self.ui.spinBoxBitsPerSymbol.value()
|
||||
self.__set_center_offset_visibility()
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_tolerance_edited(self):
|
||||
self.sniffer.signal.tolerance = self.ui.spinbox_sniff_ErrorTolerance.value()
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_modulation_changed(self, new_modulation: str):
|
||||
self.sniffer.signal.silent_set_modulation_type(new_modulation)
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_view_type_changed(self):
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_combobox_sniff_encoding_index_changed(self, index: int):
|
||||
if self.sniffer.decoder != self.project_manager.decodings[index]:
|
||||
self.sniffer.set_decoder_for_messages(self.project_manager.decodings[index])
|
||||
self.sniffer.decoder = self.project_manager.decodings[index]
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_output_file_editing_finished(self):
|
||||
self.ui.lineEdit_sniff_OutputFile.setStyleSheet("")
|
||||
text = self.ui.lineEdit_sniff_OutputFile.text()
|
||||
if text and not text.endswith(".txt"):
|
||||
text += ".txt"
|
||||
self.ui.lineEdit_sniff_OutputFile.setText(text)
|
||||
|
||||
if text and not os.path.isfile(text):
|
||||
try:
|
||||
open(text, "w").close()
|
||||
except Exception as e:
|
||||
self.ui.lineEdit_sniff_OutputFile.setStyleSheet("color:red;")
|
||||
return
|
||||
|
||||
self.sniffer.sniff_file = text
|
||||
self.sniff_file_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_checkbox_sniff_timestamp_clicked(self):
|
||||
self.sniff_setting_edited.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_btn_sniff_use_signal_clicked(self):
|
||||
try:
|
||||
signal = self.signals[self.ui.comboBox_sniff_signal.currentIndex()]
|
||||
except IndexError:
|
||||
return
|
||||
|
||||
self.ui.spinbox_sniff_SamplesPerSymbol.setValue(signal.samples_per_symbol)
|
||||
self.ui.spinbox_sniff_Center.setValue(signal.center)
|
||||
self.ui.spinbox_sniff_Noise.setValue(signal.noise_threshold_relative)
|
||||
self.ui.spinbox_sniff_ErrorTolerance.setValue(signal.tolerance)
|
||||
self.ui.combox_sniff_Modulation.setCurrentText(signal.modulation_type)
|
||||
|
||||
self.emit_editing_finished_signals()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_check_box_adaptive_noise_clicked(self):
|
||||
self.sniffer.adaptive_noise = self.ui.checkBoxAdaptiveNoise.isChecked()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_check_box_auto_center_clicked(self):
|
||||
self.sniffer.automatic_center = self.ui.checkBoxAutoCenter.isChecked()
|
||||
self.ui.spinbox_sniff_Center.setDisabled(self.ui.checkBoxAutoCenter.isChecked())
|
Reference in New Issue
Block a user