Add urh
This commit is contained in:
@ -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)
|
Reference in New Issue
Block a user