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

View File

@ -0,0 +1,38 @@
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt
from PyQt5.QtWidgets import QDialog
from urh.ui.ui_advanced_modulation_settings import Ui_DialogAdvancedModSettings
class AdvancedModulationOptionsDialog(QDialog):
pause_threshold_edited = pyqtSignal(int)
message_length_divisor_edited = pyqtSignal(int)
def __init__(self, pause_threshold: int, message_length_divisor: int, parent=None):
super().__init__(parent)
self.ui = Ui_DialogAdvancedModSettings()
self.ui.setupUi(self)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.Window)
self.pause_threshold = pause_threshold
self.message_length_divisor = message_length_divisor
self.ui.spinBoxPauseThreshold.setValue(pause_threshold)
self.ui.spinBoxMessageLengthDivisor.setValue(message_length_divisor)
self.create_connects()
def create_connects(self):
self.ui.buttonBox.accepted.connect(self.on_accept_clicked)
self.ui.buttonBox.rejected.connect(self.reject)
@pyqtSlot()
def on_accept_clicked(self):
if self.pause_threshold != self.ui.spinBoxPauseThreshold.value():
self.pause_threshold_edited.emit(self.ui.spinBoxPauseThreshold.value())
if self.message_length_divisor != self.ui.spinBoxMessageLengthDivisor.value():
self.message_length_divisor_edited.emit(self.ui.spinBoxMessageLengthDivisor.value())
self.accept()

View File

@ -0,0 +1,236 @@
import csv
import os
import numpy as np
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import QDialog, QInputDialog, QApplication, QCompleter, QDirModel, QFileDialog
from urh.ui.ui_csv_wizard import Ui_DialogCSVImport
from urh.util import FileOperator, util
from urh.util.Errors import Errors
class CSVImportDialog(QDialog):
data_imported = pyqtSignal(str, float) # Complex Filename + Sample Rate
PREVIEW_ROWS = 100
COLUMNS = {"T": 0, "I": 1, "Q": 2}
def __init__(self, filename="", parent=None):
super().__init__(parent)
self.ui = Ui_DialogCSVImport()
self.ui.setupUi(self)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.Window)
self.ui.btnAutoDefault.hide()
completer = QCompleter()
completer.setModel(QDirModel(completer))
self.ui.lineEditFilename.setCompleter(completer)
self.filename = None # type: str
self.ui.lineEditFilename.setText(filename)
self.update_file()
self.ui.tableWidgetPreview.setColumnHidden(self.COLUMNS["T"], True)
self.update_preview()
self.create_connects()
def create_connects(self):
self.accepted.connect(self.on_accepted)
self.ui.lineEditFilename.editingFinished.connect(self.on_line_edit_filename_editing_finished)
self.ui.btnChooseFile.clicked.connect(self.on_btn_choose_file_clicked)
self.ui.btnAddSeparator.clicked.connect(self.on_btn_add_separator_clicked)
self.ui.comboBoxCSVSeparator.currentIndexChanged.connect(self.on_combobox_csv_separator_current_index_changed)
self.ui.spinBoxIDataColumn.valueChanged.connect(self.on_spinbox_i_data_column_value_changed)
self.ui.spinBoxQDataColumn.valueChanged.connect(self.on_spinbox_q_data_column_value_changed)
self.ui.spinBoxTimestampColumn.valueChanged.connect(self.on_spinbox_timestamp_value_changed)
def update_file(self):
filename = self.ui.lineEditFilename.text()
self.filename = filename
enable = util.file_can_be_opened(filename)
if enable:
with open(self.filename, encoding="utf-8-sig") as f:
lines = []
for i, line in enumerate(f):
if i >= self.PREVIEW_ROWS:
break
lines.append(line.strip())
self.ui.plainTextEditFilePreview.setPlainText("\n".join(lines))
else:
self.ui.plainTextEditFilePreview.clear()
self.ui.plainTextEditFilePreview.setEnabled(enable)
self.ui.comboBoxCSVSeparator.setEnabled(enable)
self.ui.spinBoxIDataColumn.setEnabled(enable)
self.ui.spinBoxQDataColumn.setEnabled(enable)
self.ui.spinBoxTimestampColumn.setEnabled(enable)
self.ui.tableWidgetPreview.setEnabled(enable)
self.ui.labelFileNotFound.setVisible(not enable)
def update_preview(self):
if not util.file_can_be_opened(self.filename):
self.update_file()
return
i_data_col = self.ui.spinBoxIDataColumn.value() - 1
q_data_col = self.ui.spinBoxQDataColumn.value() - 1
timestamp_col = self.ui.spinBoxTimestampColumn.value() - 1
self.ui.tableWidgetPreview.setRowCount(self.PREVIEW_ROWS)
with open(self.filename, encoding="utf-8-sig") as f:
csv_reader = csv.reader(f, delimiter=self.ui.comboBoxCSVSeparator.currentText())
row = -1
for line in csv_reader:
row += 1
result = self.parse_csv_line(line, i_data_col, q_data_col, timestamp_col)
if result is not None:
for key, value in result.items():
self.ui.tableWidgetPreview.setItem(row, self.COLUMNS[key], util.create_table_item(value))
else:
for col in self.COLUMNS.values():
self.ui.tableWidgetPreview.setItem(row, col, util.create_table_item("Invalid"))
if row >= self.PREVIEW_ROWS - 1:
break
self.ui.tableWidgetPreview.setRowCount(row + 1)
@staticmethod
def parse_csv_line(csv_line: str, i_data_col: int, q_data_col: int, timestamp_col: int):
result = dict()
if i_data_col >= 0:
try:
result["I"] = float(csv_line[i_data_col])
except:
return None
else:
result["I"] = 0.0
if q_data_col >= 0:
try:
result["Q"] = float(csv_line[q_data_col])
except:
return None
else:
result["Q"] = 0.0
if timestamp_col >= 0:
try:
result["T"] = float(csv_line[timestamp_col])
except:
return None
return result
@staticmethod
def parse_csv_file(filename: str, separator: str, i_data_col: int, q_data_col=-1, t_data_col=-1):
iq_data = []
timestamps = [] if t_data_col > -1 else None
with open(filename, encoding="utf-8-sig") as f:
csv_reader = csv.reader(f, delimiter=separator)
for line in csv_reader:
parsed = CSVImportDialog.parse_csv_line(line, i_data_col, q_data_col, t_data_col)
if parsed is None:
continue
iq_data.append(complex(parsed["I"], parsed["Q"]))
if timestamps is not None:
timestamps.append(parsed["T"])
iq_data = np.asarray(iq_data, dtype=np.complex64)
sample_rate = CSVImportDialog.estimate_sample_rate(timestamps)
return iq_data / abs(iq_data.max()), sample_rate
@staticmethod
def estimate_sample_rate(timestamps):
if timestamps is None or len(timestamps) < 2:
return None
previous_timestamp = timestamps[0]
durations = []
for timestamp in timestamps[1:CSVImportDialog.PREVIEW_ROWS]:
durations.append(abs(timestamp-previous_timestamp))
previous_timestamp = timestamp
return 1 / (sum(durations) / len(durations))
@pyqtSlot()
def on_line_edit_filename_editing_finished(self):
self.update_file()
self.update_preview()
@pyqtSlot()
def on_btn_choose_file_clicked(self):
filename, _ = QFileDialog.getOpenFileName(self, self.tr("Choose file"), directory=FileOperator.RECENT_PATH,
filter="CSV files (*.csv);;All files (*.*)")
if filename:
self.ui.lineEditFilename.setText(filename)
self.ui.lineEditFilename.editingFinished.emit()
@pyqtSlot()
def on_btn_add_separator_clicked(self):
sep, ok = QInputDialog.getText(self, "Enter Separator", "Separator:", text=",")
if ok and sep not in (self.ui.comboBoxCSVSeparator.itemText(i) for i in
range(self.ui.comboBoxCSVSeparator.count())):
if len(sep) == 1:
self.ui.comboBoxCSVSeparator.addItem(sep)
else:
Errors.generic_error("Invalid Separator", "Separator must be exactly one character.")
@pyqtSlot(int)
def on_combobox_csv_separator_current_index_changed(self, index: int):
self.update_preview()
@pyqtSlot(int)
def on_spinbox_i_data_column_value_changed(self, value: int):
self.update_preview()
@pyqtSlot(int)
def on_spinbox_q_data_column_value_changed(self, value: int):
self.update_preview()
@pyqtSlot(int)
def on_spinbox_timestamp_value_changed(self, value: int):
self.ui.tableWidgetPreview.setColumnHidden(self.COLUMNS["T"], value == 0)
self.update_preview()
@pyqtSlot()
def on_accepted(self):
QApplication.setOverrideCursor(Qt.WaitCursor)
iq_data, sample_rate = self.parse_csv_file(self.filename, self.ui.comboBoxCSVSeparator.currentText(),
self.ui.spinBoxIDataColumn.value()-1,
self.ui.spinBoxQDataColumn.value()-1,
self.ui.spinBoxTimestampColumn.value()-1)
target_filename = self.filename.rstrip(".csv")
if os.path.exists(target_filename + ".complex"):
i = 1
while os.path.exists(target_filename + "_" + str(i) + ".complex"):
i += 1
else:
i = None
target_filename = target_filename if not i else target_filename + "_" + str(i)
target_filename += ".complex"
iq_data.tofile(target_filename)
self.data_imported.emit(target_filename, sample_rate if sample_rate is not None else 0)
QApplication.restoreOverrideCursor()
if __name__ == '__main__':
app = QApplication(["urh"])
csv_dia = CSVImportDialog()
csv_dia.exec_()

View File

@ -0,0 +1,111 @@
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QCloseEvent
from urh.controller.dialogs.SendDialog import SendDialog
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
from urh.dev.VirtualDevice import VirtualDevice, Mode
from urh.signalprocessing.ContinuousModulator import ContinuousModulator
from urh.ui.painting.ContinuousSceneManager import ContinuousSceneManager
class ContinuousSendDialog(SendDialog):
def __init__(self, project_manager, messages, modulators, total_samples: int, parent, testing_mode=False):
super().__init__(project_manager, modulated_data=None, modulation_msg_indices=None,
continuous_send_mode=True, parent=parent, testing_mode=testing_mode)
self.messages = messages
self.modulators = modulators
self.graphics_view = self.ui.graphicsViewContinuousSend
self.ui.stackedWidget.setCurrentWidget(self.ui.page_continuous_send)
self.ui.progressBarSample.hide()
self.ui.lSamplesSentText.hide()
self.total_samples = total_samples
self.ui.progressBarMessage.setMaximum(len(messages))
num_repeats = self.device_settings_widget.ui.spinBoxNRepeat.value()
self.continuous_modulator = ContinuousModulator(messages, modulators, num_repeats=num_repeats)
self.scene_manager = ContinuousSceneManager(ring_buffer=self.continuous_modulator.ring_buffer, parent=self)
self.scene_manager.init_scene()
self.graphics_view.setScene(self.scene_manager.scene)
self.graphics_view.scene_manager = self.scene_manager
self.setWindowTitle("Send Signal (continuous mode)")
self.ui.lSamplesSentText.setText("Progress:")
self.create_connects()
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
def create_connects(self):
SendRecvDialog.create_connects(self)
def _update_send_indicator(self, width: int):
pass
def update_view(self):
super().update_view()
self.ui.progressBarMessage.setValue(self.continuous_modulator.current_message_index.value + 1)
self.scene_manager.init_scene()
self.scene_manager.show_full_scene()
self.graphics_view.update()
def closeEvent(self, event: QCloseEvent):
self.continuous_modulator.stop()
super().closeEvent(event)
@pyqtSlot()
def on_device_started(self):
super().on_device_started()
@pyqtSlot()
def on_device_stopped(self):
super().on_device_stopped()
self.continuous_modulator.stop(clear_buffer=False)
@pyqtSlot()
def on_stop_clicked(self):
super().on_stop_clicked()
self.continuous_modulator.stop()
self.continuous_modulator.current_message_index.value = 0
self.scene_manager.clear_path()
@pyqtSlot()
def on_start_clicked(self):
self.device_settings_widget.ui.spinBoxNRepeat.editingFinished.emit() # inform continuous modulator
if not self.continuous_modulator.is_running:
self.continuous_modulator.start()
super().on_start_clicked()
@pyqtSlot()
def on_clear_clicked(self):
self.continuous_modulator.stop()
self.continuous_modulator.current_message_index.value = 0
self.scene_manager.clear_path()
self.reset()
@pyqtSlot()
def on_num_repeats_changed(self):
super().on_num_repeats_changed()
self.continuous_modulator.num_repeats = self.device_settings_widget.ui.spinBoxNRepeat.value()
def on_selected_device_changed(self):
self.ui.txtEditErrors.clear()
super().on_selected_device_changed()
def init_device(self):
device_name = self.selected_device_name
num_repeats = self.device_settings_widget.ui.spinBoxNRepeat.value()
self.device = VirtualDevice(self.backend_handler, device_name, Mode.send,
device_ip="192.168.10.2", sending_repeats=num_repeats, parent=self)
self.ui.btnStart.setEnabled(True)
try:
self.device.is_send_continuous = True
self.device.continuous_send_ring_buffer = self.continuous_modulator.ring_buffer
self.device.num_samples_to_send = self.total_samples
self._create_device_connects()
except ValueError as e:
self.ui.txtEditErrors.setText("<font color='red'>" + str(e) + "<font>")
self.ui.btnStart.setEnabled(False)

View File

@ -0,0 +1,27 @@
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtWidgets import QDialog
from urh.ui.ui_costa import Ui_DialogCosta
class CostaOptionsDialog(QDialog):
def __init__(self, loop_bandwidth, parent=None):
super().__init__(parent)
self.ui = Ui_DialogCosta()
self.ui.setupUi(self)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.Window)
self.costas_loop_bandwidth = loop_bandwidth
self.ui.doubleSpinBoxLoopBandwidth.setValue(self.costas_loop_bandwidth)
self.create_connects()
def create_connects(self):
self.ui.buttonBox.accepted.connect(self.accept)
self.ui.buttonBox.rejected.connect(self.reject)
self.ui.doubleSpinBoxLoopBandwidth.valueChanged.connect(self.on_spinbox_loop_bandwidth_value_changed)
@pyqtSlot(float)
def on_spinbox_loop_bandwidth_value_changed(self, value):
self.costas_loop_bandwidth = value

View File

@ -0,0 +1,872 @@
import copy
import os
from PyQt5.QtCore import QDir, Qt, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QDropEvent, QDragEnterEvent, QIcon
from PyQt5.QtWidgets import QDialog, QTableWidgetItem, QFileDialog, QInputDialog, \
QLineEdit, QMessageBox
from urh import settings
from urh.signalprocessing.Encoding import Encoding
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
from urh.signalprocessing.Signal import Signal
from urh.ui.painting.SignalSceneManager import SignalSceneManager
from urh.ui.ui_decoding import Ui_Decoder
from urh.util.ProjectManager import ProjectManager
class DecoderDialog(QDialog):
def __init__(self, decodings, signals, project_manager: ProjectManager,
parent=None):
"""
:type decodings: list of Encoding
:type signals: list of Signal
"""
# Init
super().__init__(parent)
self.ui = Ui_Decoder()
self.ui.setupUi(self)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.Window)
# Variables
self.old_inpt_txt = ""
self.old_carrier_txt = ""
self.old_decoderchain = []
self.active_message = ""
self.old_cutmark = ""
self.old_morse = (1, 3)
self.project_manager = project_manager
# Initialize encoder
self.decodings = decodings
self.ui.combobox_decodings.clear()
for decoding in self.decodings:
self.ui.combobox_decodings.addItem(decoding.name)
self.chainstr = []
self.chainoptions = {}
self.set_e()
self.last_selected_item = ""
# Signals
self.signals = signals if signals is not None else []
for signal in signals:
if signal:
self.ui.combobox_signals.addItem(signal.name)
# Function lists
self.ui.basefunctions.addItem(settings.DECODING_EDGE)
self.ui.basefunctions.addItem(settings.DECODING_MORSE)
self.ui.basefunctions.addItem(settings.DECODING_SUBSTITUTION)
self.ui.basefunctions.addItem(settings.DECODING_EXTERNAL)
self.ui.additionalfunctions.addItem(settings.DECODING_INVERT)
self.ui.additionalfunctions.addItem(settings.DECODING_DIFFERENTIAL)
self.ui.additionalfunctions.addItem(settings.DECODING_BITORDER)
self.ui.additionalfunctions.addItem(settings.DECODING_REDUNDANCY)
self.ui.additionalfunctions.addItem(settings.DECODING_CARRIER)
self.ui.additionalfunctions.addItem(settings.DECODING_DATAWHITENING)
self.ui.additionalfunctions.addItem(settings.DECODING_ENOCEAN)
self.ui.additionalfunctions.addItem(settings.DECODING_CUT)
# Presets
self.setWindowTitle("Decoding")
self.setWindowIcon(QIcon(":/icons/icons/decoding.svg"))
self.setAcceptDrops(True)
self.inpt_text = "10010110"
self.ui.inpt.setText(self.inpt_text)
self.ui.optionWidget.setCurrentIndex(0)
self.decoder_update()
self.ui.substitution.setColumnCount(2)
self.ui.substitution.setRowCount(self.ui.substitution_rows.value())
self.ui.substitution.setHorizontalHeaderLabels(['From', 'To'])
self.ui.substitution.setColumnWidth(0, 190)
self.ui.substitution.setColumnWidth(1, 190)
self.ui.btnAddtoYourDecoding.hide()
self.ui.saveas.setVisible(False)
# Connects
self.create_connects()
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
def create_connects(self):
self.ui.inpt.textChanged.connect(self.decoder_update)
self.ui.multiple.valueChanged.connect(self.handle_multiple_changed)
self.ui.carrier.textChanged.connect(self.handle_carrier_changed)
self.ui.substitution_rows.valueChanged.connect(self.handle_substitution_rows_changed)
self.ui.substitution.itemChanged.connect(self.handle_substitution_changed)
self.ui.btnChooseDecoder.clicked.connect(self.choose_decoder)
self.ui.btnChooseEncoder.clicked.connect(self.choose_encoder)
self.ui.external_decoder.textEdited.connect(self.handle_external)
self.ui.external_encoder.textEdited.connect(self.handle_external)
self.ui.datawhitening_sync.textEdited.connect(self.handle_datawhitening)
self.ui.datawhitening_polynomial.textEdited.connect(self.handle_datawhitening)
self.ui.datawhitening_overwrite_crc.clicked.connect(self.handle_datawhitening)
self.ui.decoderchain.itemChanged.connect(self.decoderchainUpdate)
self.ui.decoderchain.internalMove.connect(self.decoderchainUpdate)
self.ui.decoderchain.deleteElement.connect(self.deleteElement)
self.ui.decoderchain.currentRowChanged.connect(self.on_decoder_chain_current_row_changed)
self.ui.basefunctions.currentRowChanged.connect(self.on_base_functions_current_row_changed)
self.ui.additionalfunctions.currentRowChanged.connect(self.on_additional_functions_current_row_changed)
self.ui.btnAddtoYourDecoding.clicked.connect(self.on_btn_add_to_your_decoding_clicked)
self.ui.combobox_decodings.currentIndexChanged.connect(self.set_e)
self.ui.combobox_signals.currentIndexChanged.connect(self.set_signal)
self.ui.saveas.clicked.connect(self.saveas)
self.ui.delete_decoding.clicked.connect(self.delete_decoding)
self.ui.rB_delbefore.clicked.connect(self.handle_cut)
self.ui.rB_delafter.clicked.connect(self.handle_cut)
self.ui.rB_delbeforepos.clicked.connect(self.handle_cut)
self.ui.rB_delafterpos.clicked.connect(self.handle_cut)
self.ui.cutmark.textEdited.connect(self.handle_cut)
self.ui.cutmark2.valueChanged.connect(self.handle_cut)
self.ui.morse_low.valueChanged.connect(self.handle_morse_changed)
self.ui.morse_high.valueChanged.connect(self.handle_morse_changed)
self.ui.morse_wait.valueChanged.connect(self.handle_morse_changed)
def closeEvent(self, event: QCloseEvent):
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
super().closeEvent(event)
def choose_decoder(self):
f, ok = QFileDialog.getOpenFileName(self, self.tr("Choose decoder program"), QDir.homePath())
if f and ok:
self.ui.external_decoder.setText(f)
self.handle_external()
def choose_encoder(self):
f, ok = QFileDialog.getOpenFileName(self, self.tr("Choose encoder program"), QDir.homePath())
if f and ok:
self.ui.external_encoder.setText(f)
self.handle_external()
def save_to_file(self):
if self.project_manager.project_file:
self.project_manager.decodings = self.decodings
else:
prefix = os.path.realpath(os.path.join(settings.get_qt_settings_filename(), ".."))
with open(os.path.join(prefix, settings.DECODINGS_FILE), "w") as f:
for i in range(0, self.ui.combobox_decodings.count()):
str = ""
for j in self.decodings[i].get_chain():
str += repr(j) + ", "
str += "\n"
f.write(str)
def saveas(self):
# Ask for a name
name, ok = QInputDialog.getText(self, self.tr("Save decoding"),
self.tr("Please enter a name:"), QLineEdit.Normal, self.e.chain[0])
if ok and name != "":
self.e.chain[0] = name
self.decoderchainUpdate()
# If name is already there, overwrite existing
for i in range(0, len(self.decodings)):
if name == self.decodings[i].name:
self.ui.combobox_decodings.setCurrentIndex(i)
self.decodings[i] = Encoding(self.chainstr)
self.set_e()
self.ui.saveas.setVisible(False)
self.save_to_file()
return
self.decodings.append(Encoding(self.chainstr))
self.ui.combobox_decodings.addItem(self.chainstr[0])
self.ui.combobox_decodings.setCurrentIndex(self.ui.combobox_decodings.count() - 1)
self.set_e()
self.save_to_file()
def delete_decoding(self):
num = self.ui.combobox_decodings.currentIndex()
if num >= 0:
reply = QMessageBox.question(self, self.tr("Delete Decoding?"),
self.tr("Do you really want to delete " + "'{}'?".format(
self.decodings[num].name)),
QMessageBox.Yes | QMessageBox.No)
if reply == QMessageBox.Yes:
self.decodings.pop(num)
self.ui.combobox_decodings.removeItem(num)
self.save_to_file()
def set_e(self):
if self.ui.combobox_decodings.count() < 1: # Empty list
return
self.e = copy.deepcopy(self.decodings[self.ui.combobox_decodings.currentIndex()])
""":type: encoding """
chain = self.e.get_chain()
self.ui.decoderchain.clear()
self.chainoptions.clear()
last_i = ""
for i in chain:
if i in [settings.DECODING_INVERT, settings.DECODING_ENOCEAN, settings.DECODING_DIFFERENTIAL,
settings.DECODING_REDUNDANCY, settings.DECODING_CARRIER, settings.DECODING_BITORDER,
settings.DECODING_EDGE, settings.DECODING_DATAWHITENING, settings.DECODING_SUBSTITUTION,
settings.DECODING_EXTERNAL, settings.DECODING_CUT, settings.DECODING_MORSE,
settings.DECODING_DISABLED_PREFIX]:
self.ui.decoderchain.addItem(i)
self.decoderchainUpdate()
last_i = self.ui.decoderchain.item(self.ui.decoderchain.count() - 1).text()
else:
if any(x in last_i for x in [settings.DECODING_REDUNDANCY, settings.DECODING_CARRIER,
settings.DECODING_SUBSTITUTION, settings.DECODING_EXTERNAL,
settings.DECODING_DATAWHITENING, settings.DECODING_CUT,
settings.DECODING_MORSE]):
self.chainoptions[last_i] = i
self.decoderchainUpdate()
self.decoder_update()
self.ui.saveas.setVisible(False)
def decoderchainUpdate(self):
# for i in range (0, self.ui.decoderchain.count()):
# print(i, "->", self.ui.decoderchain.item(i).text())
# print()
self.ui.saveas.setVisible(True)
self.eliminateDuplicates()
self.chainstr = [self.e.name]
for i in range(0, self.ui.decoderchain.count()):
op = self.ui.decoderchain.item(i).text()
# Is this function disabled?
if settings.DECODING_DISABLED_PREFIX in op:
continue
self.chainstr.append(op)
# Add parameters to chainstr
if settings.DECODING_REDUNDANCY in op:
# Read Multiple Value
if op in self.chainoptions:
self.chainstr.append(self.chainoptions[op])
else:
self.chainoptions[op] = 2
self.chainstr.append(2) # Default
elif settings.DECODING_CARRIER in op:
# Read Carrier Field and add string to chainstr
if op in self.chainoptions:
self.chainstr.append(self.chainoptions[op])
else:
self.chainoptions[op] = ""
self.chainstr.append("") # Default
elif settings.DECODING_SUBSTITUTION in op:
# Add substitution string to chainstr: Format = src0:dst0;src1:dst1;...
if op in self.chainoptions:
self.chainstr.append(self.chainoptions[op])
else:
self.chainoptions[op] = ""
self.chainstr.append("") # Default
elif settings.DECODING_EXTERNAL in op:
# Add program path's string to chainstr: Format = decoder;encoder
if op in self.chainoptions:
self.chainstr.append(self.chainoptions[op])
else:
self.chainoptions[op] = ""
self.chainstr.append("") # Default
elif settings.DECODING_DATAWHITENING in op:
# Add Data Whitening Parameters
if op in self.chainoptions:
self.chainstr.append(self.chainoptions[op])
else:
self.chainoptions[op] = ""
self.chainstr.append("0xe9cae9ca;0x21;0") # Default
elif settings.DECODING_CUT in op:
# Add cut parameters
if op in self.chainoptions:
self.chainstr.append(self.chainoptions[op])
else:
self.chainoptions[op] = ""
self.chainstr.append("0;1010") # Default
elif settings.DECODING_MORSE in op:
# Add morse parameters
if op in self.chainoptions:
self.chainstr.append(self.chainoptions[op])
else:
self.chainoptions[op] = ""
self.chainstr.append("1;3;1") # Default
self.e.set_chain(self.chainstr)
self.decoder_update()
def deleteElement(self):
if self.ui.decoderchain.count() == 0: # Clear all
self.chainoptions.clear()
else:
self.chainoptions.pop(self.ui.decoderchain.active_element_text, None)
self.decoderchainUpdate()
def eliminateDuplicates(self):
decoderchain_count = self.ui.decoderchain.count()
olddecoderchain_count = len(self.old_decoderchain)
# Special case for 1 element (add " ")
if decoderchain_count == 1:
tmp = self.ui.decoderchain.item(0).text()
if tmp[-1] != " " and not tmp[-1].isnumeric():
self.ui.decoderchain.takeItem(0)
self.ui.decoderchain.insertItem(0, tmp + " ")
# Ignore internal move (same count()) and removed elements and lists < 2 // len(self.old_decoderchain)+1 == self.ui.decoderchain.count()
if decoderchain_count > 1 and decoderchain_count > olddecoderchain_count:
elem = 0
while elem < olddecoderchain_count:
if self.ui.decoderchain.item(elem).text() == self.old_decoderchain[elem]:
elem += 1
else:
break
# Count number of current elements and append string "#<num>" to current text, if num > 1
txt = self.ui.decoderchain.item(elem).text()
num = 0
for i in range(0, decoderchain_count):
if txt in self.ui.decoderchain.item(i).text():
num += 1
if num > 1:
tmp_txt = txt + " #" + str(num)
else:
tmp_txt = txt + " "
# Check duplicate names
dup = False
for i in range(0, decoderchain_count):
if self.ui.decoderchain.item(i).text() == tmp_txt:
dup = True
break
if dup:
for i in range(1, num):
if i > 1:
tmp_txt = txt + " #" + str(i)
else:
tmp_txt = txt + " "
dup = False
for j in range(0, decoderchain_count):
if self.ui.decoderchain.item(j).text() == tmp_txt:
dup = True
break
if not dup:
break
# Replace current element with new "text #<num>"
if not dup:
self.ui.decoderchain.takeItem(elem)
self.ui.decoderchain.insertItem(elem, tmp_txt)
# Save current decoderchain to old_decoderchain
self.old_decoderchain = []
for i in range(0, decoderchain_count):
self.old_decoderchain.append(self.ui.decoderchain.item(i).text())
def decoder_update(self):
# Only allow {0, 1}
signaltype = self.ui.combobox_signals.currentIndex()
inpt_txt = self.ui.inpt.text()
if signaltype == 0:
if inpt_txt.count("0") + inpt_txt.count("1") < len(inpt_txt):
self.ui.inpt.setText(self.old_inpt_txt)
else:
self.old_inpt_txt = inpt_txt
# Write decoded bits
bit = self.e.str2bit(self.ui.inpt.text())
decoded = self.e.bit2str(self.e.decode(bit))
errors = "[Decoding Errors = " + str(self.e.analyze(bit)[0]) + "]"
self.ui.decoding_errors_label.setText(errors)
self.ui.output.setText(decoded)
self.ui.output.setCursorPosition(0)
if len(decoded) > 0:
if signaltype == 0:
temp_signal = SignalSceneManager.create_rectangle(inpt_txt)[0]
self.ui.graphicsView_signal.setScene(temp_signal)
self.ui.graphicsView_signal.update()
temp_decoded = SignalSceneManager.create_rectangle(decoded)[0]
self.ui.graphicsView_decoded.setScene(temp_decoded)
self.ui.graphicsView_decoded.update()
@pyqtSlot(int)
def on_base_functions_current_row_changed(self, index: int):
if self.ui.basefunctions.currentItem().text() is not None:
self.ui.decoderchain.setCurrentRow(-1)
self.set_information(0)
else:
self.ui.optionWidget.setCurrentIndex(0)
self.ui.info.clear()
@pyqtSlot(int)
def on_additional_functions_current_row_changed(self, index: int):
if self.ui.additionalfunctions.currentItem() is not None:
self.ui.decoderchain.setCurrentRow(-1)
self.set_information(1)
else:
self.ui.optionWidget.setCurrentIndex(0)
self.ui.info.clear()
@pyqtSlot(int)
def on_decoder_chain_current_row_changed(self, index: int):
if self.ui.decoderchain.currentItem() is not None:
self.set_information(2)
else:
self.ui.optionWidget.setCurrentIndex(0)
self.ui.info.clear()
def set_information(self, mode: int):
# Presets
decoderEdit = False
self.ui.optionWidget.setCurrentIndex(0)
txt = ""
# Determine selected element
if mode == 0:
element = self.ui.basefunctions.currentItem().text()
txt += element + ":\n"
self.last_selected_item = element
self.ui.btnAddtoYourDecoding.show()
elif mode == 1:
element = self.ui.additionalfunctions.currentItem().text()
txt += element + ":\n"
self.last_selected_item = element
self.ui.btnAddtoYourDecoding.show()
elif mode == 2:
decoderEdit = True
txt = "## In Your Decoding ##\n\n"
element = self.ui.decoderchain.currentItem().text()
if element[-1] == " ":
elementname = element[0:-1]
else:
elementname = element
txt += elementname + ":\n"
self.active_message = element
self.ui.btnAddtoYourDecoding.hide()
# Remove "[Disabled] " for further tasks
if settings.DECODING_DISABLED_PREFIX in element:
element = element[len(settings.DECODING_DISABLED_PREFIX):]
# Write info text and show options
if settings.DECODING_EDGE in element:
txt += "Trigger on signal edge, i.e. the transition between low and high.\n" \
"- Low to High (01) is 1\n" \
"- High to Low (10) is 0"
elif settings.DECODING_SUBSTITUTION in element:
txt += "A set of manual defined signal sequences FROM (e.g. 110, 100) is replaced by another set of " \
"sequences TO (e.g. 01, 10). Note that all FROM entries must have the same length, otherwise " \
"the result is unpredictable! (For TX: all TO entries must have the same length)"
self.ui.optionWidget.setCurrentIndex(3)
# Values can only be changed when editing decoder, otherwise default value
if not decoderEdit:
self.ui.substitution_rows.setValue(4)
self.ui.substitution.setRowCount(0)
self.ui.substitution.setRowCount(4)
else:
if element in self.chainoptions:
values = self.chainoptions[element]
if values == "":
self.ui.substitution_rows.setValue(4)
self.ui.substitution.setRowCount(0)
self.ui.substitution.setRowCount(4)
else:
arrs = self.e.get_subst_array(values)
if len(arrs[0]) == len(arrs[1]):
self.ui.substitution_rows.setValue(len(arrs[0]))
self.ui.substitution.setRowCount(len(arrs[0]))
for i in range(0, len(arrs[0])):
self.ui.substitution.setItem(i, 0, QTableWidgetItem(self.e.bit2str(arrs[0][i])))
self.ui.substitution.setItem(i, 1, QTableWidgetItem(self.e.bit2str(arrs[1][i])))
else:
self.ui.substitution_rows.setValue(4)
self.ui.substitution.setRowCount(0)
self.ui.substitution.setRowCount(4)
self.ui.substitution.setEnabled(decoderEdit)
self.ui.substitution_rows.setEnabled(decoderEdit)
elif settings.DECODING_EXTERNAL in element:
txt += "The decoding (and encoding) process is delegated to external programs or scripts via parameter.\n" \
"Example: Given the signal 10010110, your program is called as './decoder 10010110'. Your program " \
"computes and prints a corresponding set of 0s and 1s which is fed back into the decoding process. "
self.ui.optionWidget.setCurrentIndex(4)
# Values can only be changed when editing decoder, otherwise default value
if not decoderEdit:
self.ui.external_decoder.setText("")
self.ui.external_encoder.setText("")
else:
if element in self.chainoptions:
value = self.chainoptions[element]
if value == "":
self.ui.external_decoder.setText("")
self.ui.external_encoder.setText("")
else:
decstr, encstr = value.split(";")
self.ui.external_decoder.setText(decstr)
self.ui.external_encoder.setText(encstr)
else:
self.ui.external_decoder.setText("")
self.ui.external_encoder.setText("")
self.ui.external_decoder.setEnabled(decoderEdit)
self.ui.external_encoder.setEnabled(decoderEdit)
self.ui.btnChooseDecoder.setEnabled(decoderEdit)
self.ui.btnChooseEncoder.setEnabled(decoderEdit)
elif settings.DECODING_INVERT in element:
txt += "All bits are inverted, i.e. 0->1 and 1->0."
elif settings.DECODING_ENOCEAN in element:
txt += "Remove Wireless Short-Packet (WSP) encoding that is used by EnOcean standard."
elif settings.DECODING_DIFFERENTIAL in element:
txt += "Every transition between low and high (0->1 or 1->0) becomes 1, no transition (0->0 or 1->1) remains 0.\n" \
"The first signal bit is regarded as start value and directly copied.\n" \
"Example: 0011 becomes 0010 [0|(0->0)|(0->1)|(1->1)]."
elif settings.DECODING_BITORDER in element:
txt += "Every byte (8 bit) is reversed, i.e. the order of the bits 01234567 (e.g. least significant bit first) " \
"is changed to 76543210 (e.g. most significant bit first)."
elif settings.DECODING_REDUNDANCY in element:
txt += "If the source signal always has multiple redundant bits for one bit (e.g. 1111=1, 0000=0), the " \
"redundancy is removed here. You have to define the number of redundant bits."
self.ui.optionWidget.setCurrentIndex(1)
# Values can only be changed when editing decoder, otherwise default value
if not decoderEdit:
self.ui.multiple.setValue(2)
else:
if element in self.chainoptions:
value = self.chainoptions[element]
if value == "":
self.ui.multiple.setValue(2)
else:
self.ui.multiple.setValue(int(value))
else:
self.ui.multiple.setValue(2)
self.ui.multiple.setEnabled(decoderEdit)
elif settings.DECODING_MORSE in element:
txt += "If the signal is a morse code, e.g. 00111001001110011100, where information are " \
"transported with long and short sequences of 1 (0 just for padding), then this " \
"decoding evaluates those sequences (Example output: 1011)."
self.ui.optionWidget.setCurrentIndex(7)
# # Values can only be changed when editing decoder, otherwise default value
if not decoderEdit:
self.ui.morse_low.setValue(1)
self.ui.morse_high.setValue(3)
self.ui.morse_wait.setValue(1)
else:
if element in self.chainoptions:
value = self.chainoptions[element]
if value == "":
self.ui.morse_low.setValue(1)
self.ui.morse_high.setValue(3)
self.ui.morse_wait.setValue(1)
else:
try:
l, h, w = value.split(";")
self.ui.morse_low.setValue(int(l))
self.ui.morse_high.setValue(int(h))
self.ui.morse_wait.setValue(int(w))
except ValueError:
self.ui.morse_low.setValue(1)
self.ui.morse_high.setValue(3)
self.ui.morse_wait.setValue(1)
else:
self.ui.morse_low.setValue(1)
self.ui.morse_high.setValue(3)
self.ui.morse_wait.setValue(1)
self.ui.morse_low.setEnabled(decoderEdit)
self.ui.morse_high.setEnabled(decoderEdit)
self.ui.morse_wait.setEnabled(decoderEdit)
elif settings.DECODING_CARRIER in element:
txt += "A carrier is a fixed pattern like 1_1_1_1 where the actual data lies in between, e.g. 1a1a1b1. This " \
"function extracts the actual bit information (here: aab) from the signal at '_'/'.' positions.\n" \
"Examples:\n" \
"- Carrier = '1_' means 1_1_1_...\n" \
"- Carrier = '01_' means 01_01_01_01..."
self.ui.optionWidget.setCurrentIndex(2)
# Values can only be changed when editing decoder, otherwise default value
if not decoderEdit:
self.ui.carrier.setText("1_")
else:
if element in self.chainoptions:
value = self.chainoptions[element]
if value == "":
self.ui.carrier.setText("1_")
else:
self.ui.carrier.setText(value)
else:
self.ui.carrier.setText("1_")
self.ui.carrier.setEnabled(decoderEdit)
elif settings.DECODING_DATAWHITENING in element:
txt += "Texas Instruments CC110x chips allow a data whitening that is applied before sending the signals to HF. " \
"After a preamble (1010...) there is a fixed 16/32 bit sync word. The following data (incl. 16 bit CRC) " \
"is masked (XOR) with the output of a LFSR.\n" \
"This unmasks the data."
self.ui.optionWidget.setCurrentIndex(5)
# Values can only be changed when editing decoder, otherwise default value
if not decoderEdit:
self.ui.datawhitening_sync.setText("0xe9cae9ca")
self.ui.datawhitening_polynomial.setText("0x21")
self.ui.datawhitening_overwrite_crc.setChecked(False)
else:
if element in self.chainoptions:
value = self.chainoptions[element]
if value == "":
self.ui.datawhitening_sync.setText("0xe9cae9ca")
self.ui.datawhitening_polynomial.setText("0x21")
self.ui.datawhitening_overwrite_crc.setChecked(False)
else:
try:
whitening_sync, whitening_polynomial, whitening_overwrite_crc = value.split(";")
self.ui.datawhitening_sync.setText(whitening_sync)
self.ui.datawhitening_polynomial.setText(whitening_polynomial)
self.ui.datawhitening_overwrite_crc.setChecked(True if whitening_overwrite_crc == "1" else False)
except ValueError:
self.ui.datawhitening_sync.setText("0xe9cae9ca")
self.ui.datawhitening_polynomial.setText("0x21")
self.ui.datawhitening_overwrite_crc.setChecked(False)
self.ui.datawhitening_sync.setEnabled(decoderEdit)
self.ui.datawhitening_polynomial.setEnabled(decoderEdit)
self.ui.datawhitening_overwrite_crc.setEnabled(decoderEdit)
elif settings.DECODING_CUT in element:
txt += "This function enables you to cut data from your messages, in order to shorten or align them for a " \
"better view. Note that this decoding does NOT support encoding, because cut data is gone!\n" \
"Example:\n" \
"- Cut before '1010' would delete everything before first '1010' bits.\n" \
"- Cut before Position = 3 (in bit) would delete the first three bits.\n"
self.ui.optionWidget.setCurrentIndex(6)
# Values can only be changed when editing decoder, otherwise default value
if not decoderEdit:
self.ui.cutmark.setText("1010")
self.old_cutmark = self.ui.cutmark.text()
self.ui.cutmark2.setValue(1)
self.ui.rB_delbefore.setChecked(False)
self.ui.rB_delafter.setChecked(False)
self.ui.rB_delbeforepos.setChecked(False)
self.ui.rB_delafterpos.setChecked(False)
self.ui.cutmark.setEnabled(False)
self.ui.cutmark2.setEnabled(False)
else:
if element in self.chainoptions:
value = self.chainoptions[element]
if value == "":
self.ui.cutmark.setText("1010")
self.ui.cutmark.setEnabled(True)
self.old_cutmark = self.ui.cutmark.text()
self.ui.cutmark2.setValue(1)
self.ui.cutmark2.setEnabled(False)
self.ui.rB_delbefore.setChecked(True)
self.ui.rB_delafter.setChecked(False)
self.ui.rB_delbeforepos.setChecked(False)
self.ui.rB_delafterpos.setChecked(False)
else:
try:
cmode, cmark = value.split(";")
cmode = int(cmode)
if cmode == 0:
self.ui.rB_delbefore.setChecked(True)
self.ui.cutmark.setEnabled(True)
self.ui.cutmark2.setEnabled(False)
self.ui.cutmark.setText(cmark)
elif cmode == 1:
self.ui.rB_delafter.setChecked(True)
self.ui.cutmark.setEnabled(True)
self.ui.cutmark2.setEnabled(False)
self.ui.cutmark.setText(cmark)
elif cmode == 2:
self.ui.rB_delbeforepos.setChecked(True)
self.ui.cutmark.setEnabled(False)
self.ui.cutmark2.setEnabled(True)
self.ui.cutmark2.setValue(int(cmark))
elif cmode == 3:
self.ui.rB_delafterpos.setChecked(True)
self.ui.cutmark.setEnabled(False)
self.ui.cutmark2.setEnabled(True)
self.ui.cutmark2.setValue(int(cmark))
except ValueError:
self.ui.cutmark.setText("1010")
self.old_cutmark = self.ui.cutmark.text()
self.ui.cutmark2.setValue(1)
self.ui.rB_delbefore.setChecked(True)
self.ui.rB_delafter.setChecked(False)
self.ui.rB_delbeforepos.setChecked(False)
self.ui.rB_delafterpos.setChecked(False)
self.ui.cutmark.setEnabled(True)
self.ui.cutmark2.setEnabled(False)
else:
self.ui.cutmark.setText("1010")
self.old_cutmark = self.ui.cutmark.text()
self.ui.cutmark2.setValue(1)
self.ui.rB_delbefore.setChecked(True)
self.ui.rB_delafter.setChecked(False)
self.ui.rB_delbeforepos.setChecked(False)
self.ui.rB_delafterpos.setChecked(False)
self.ui.cutmark.setEnabled(True)
self.ui.cutmark2.setEnabled(False)
self.ui.rB_delbefore.setEnabled(decoderEdit)
self.ui.rB_delafter.setEnabled(decoderEdit)
self.ui.rB_delbeforepos.setEnabled(decoderEdit)
self.ui.rB_delafterpos.setEnabled(decoderEdit)
self.ui.info.setText(txt)
@pyqtSlot()
def handle_datawhitening(self):
datawhiteningstr = self.ui.datawhitening_sync.text() + ";" + self.ui.datawhitening_polynomial.text() + ";" + \
("1" if self.ui.datawhitening_overwrite_crc.isChecked() else "0")
if settings.DECODING_DATAWHITENING in self.active_message:
self.chainoptions[self.active_message] = datawhiteningstr
self.decoderchainUpdate()
@pyqtSlot()
def handle_external(self):
externalstr = self.ui.external_decoder.text() + ";" + self.ui.external_encoder.text()
if settings.DECODING_EXTERNAL in self.active_message:
self.chainoptions[self.active_message] = externalstr
self.decoderchainUpdate()
@pyqtSlot()
def handle_substitution_changed(self):
subststr = ""
for i in range(0, self.ui.substitution_rows.value()):
if self.ui.substitution.item(i, 0) and self.ui.substitution.item(i, 1):
subststr += self.ui.substitution.item(i, 0).text() + ":" + self.ui.substitution.item(i, 1).text() + ";"
if settings.DECODING_SUBSTITUTION in self.active_message:
self.chainoptions[self.active_message] = subststr
self.decoderchainUpdate()
@pyqtSlot()
def handle_substitution_rows_changed(self):
# Substitution Row Spinbox
self.ui.substitution.setRowCount(self.ui.substitution_rows.value())
self.decoderchainUpdate()
@pyqtSlot()
def handle_multiple_changed(self):
# Multiple Spinbox
val = self.ui.multiple.value()
if settings.DECODING_REDUNDANCY in self.active_message:
self.chainoptions[self.active_message] = val
self.decoderchainUpdate()
@pyqtSlot()
def handle_morse_changed(self):
# Multiple Spinbox
val_low = self.ui.morse_low.value()
val_high = self.ui.morse_high.value()
val_wait = self.ui.morse_wait.value()
if val_low >= val_high:
self.ui.morse_low.setValue(self.old_morse[0])
self.ui.morse_high.setValue(self.old_morse[1])
(val_low, val_high) = self.old_morse
else:
self.old_morse = (val_low, val_high)
if settings.DECODING_MORSE in self.active_message:
self.chainoptions[self.active_message] = "{};{};{}".format(val_low, val_high, val_wait)
self.decoderchainUpdate()
@pyqtSlot()
def handle_carrier_changed(self):
# Only allow {0, 1}
carrier_txt = self.ui.carrier.text()
if carrier_txt.count("0") + carrier_txt.count("1") + carrier_txt.count("_") + carrier_txt.count(
".") + carrier_txt.count("*") < len(carrier_txt):
self.ui.carrier.setText(self.old_carrier_txt)
else:
self.old_carrier_txt = carrier_txt
# Carrier Textbox
# self.e.carrier = self.e.str2bit(self.ui.carrier.text())
if settings.DECODING_CARRIER in self.active_message:
self.chainoptions[self.active_message] = carrier_txt
self.decoderchainUpdate()
@pyqtSlot()
def handle_cut(self):
cmode = 0
cmark = ""
if self.ui.rB_delbefore.isChecked() or self.ui.rB_delafter.isChecked():
# Activate right cutmark field
self.ui.cutmark.setEnabled(True)
self.ui.cutmark2.setEnabled(False)
# set cmode
if self.ui.rB_delafter.isChecked():
cmode = 1
# check values in cutmark
cmark = self.ui.cutmark.text()
if cmark.count("0") + cmark.count("1") < len(cmark):
self.ui.cutmark.setText(self.old_cutmark)
else:
self.old_cutmark = cmark
else:
# Activate right cutmark field
self.ui.cutmark.setEnabled(False)
self.ui.cutmark2.setEnabled(True)
# set cmode
if self.ui.rB_delbeforepos.isChecked():
cmode = 2
else:
cmode = 3
cmark = str(self.ui.cutmark2.value())
cut_text = str(cmode) + ";" + cmark
if settings.DECODING_CUT in self.active_message:
self.chainoptions[self.active_message] = cut_text
self.decoderchainUpdate()
@pyqtSlot()
def on_btn_add_to_your_decoding_clicked(self):
if self.last_selected_item != "":
self.ui.decoderchain.addItem(self.last_selected_item)
self.decoderchainUpdate()
self.ui.decoderchain.setCurrentRow(self.ui.decoderchain.count()-1)
def dragEnterEvent(self, event: QDragEnterEvent):
event.accept()
def dropEvent(self, event: QDropEvent):
# if not self.ui.decoderchain.geometry().contains(self.mapToGlobal(event.pos())):
if self.ui.decoderchain.currentItem() is not None:
self.chainoptions.pop(self.ui.decoderchain.currentItem().text(), None)
self.ui.decoderchain.takeItem(self.ui.decoderchain.currentRow())
self.decoderchainUpdate()
def set_signal(self):
indx = self.ui.combobox_signals.currentIndex()
if indx != 0:
self.ui.inpt.setReadOnly(True)
else:
self.ui.inpt.setReadOnly(False)
self.ui.inpt.setText("10010110")
self.decoder_update()
return
self.setCursor(Qt.WaitCursor)
signal = self.signals[indx - 1]
pa = ProtocolAnalyzer(signal)
pa.get_protocol_from_signal()
self.ui.inpt.setText("".join(pa.plain_bits_str))
self.ui.inpt.setCursorPosition(0)
if signal is not None and pa.messages:
last_message = pa.messages[-1]
lookup = {i: msg.bit_sample_pos for i, msg in enumerate(pa.messages)}
plot_data = signal.qad[lookup[0][0]:lookup[pa.num_messages - 1][len(last_message) - 1]]
self.ui.graphicsView_signal.plot_data(plot_data)
self.ui.graphicsView_signal.centerOn(0, 0)
self.unsetCursor()

View File

@ -0,0 +1,67 @@
from PyQt5.QtCore import pyqtSlot, Qt
from PyQt5.QtWidgets import QDialog, QLabel, QRadioButton
from urh import settings
from urh.signalprocessing.Filter import Filter
from urh.ui.ui_filter_bandwidth_dialog import Ui_DialogFilterBandwidth
class FilterBandwidthDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_DialogFilterBandwidth()
self.ui.setupUi(self)
self.setWindowFlags(Qt.Window)
bw_type = settings.read("bandpass_filter_bw_type", "Medium", str)
custom_bw = settings.read("bandpass_filter_custom_bw", 0.1, float)
for item in dir(self.ui):
item = getattr(self.ui, item)
if isinstance(item, QLabel):
name = item.objectName().replace("label", "")
key = next((key for key in Filter.BANDWIDTHS.keys() if name.startswith(key.replace(" ", ""))), None)
if key is not None and name.endswith("Bandwidth"):
item.setText("{0:n}".format(Filter.BANDWIDTHS[key]))
elif key is not None and name.endswith("KernelLength"):
item.setText(str(Filter.get_filter_length_from_bandwidth(Filter.BANDWIDTHS[key])))
elif isinstance(item, QRadioButton):
item.setChecked(bw_type.replace(" ", "_") == item.objectName().replace("radioButton", ""))
self.ui.doubleSpinBoxCustomBandwidth.setValue(custom_bw)
self.ui.spinBoxCustomKernelLength.setValue(Filter.get_filter_length_from_bandwidth(custom_bw))
self.create_connects()
def create_connects(self):
self.ui.doubleSpinBoxCustomBandwidth.valueChanged.connect(self.on_spin_box_custom_bandwidth_value_changed)
self.ui.spinBoxCustomKernelLength.valueChanged.connect(self.on_spin_box_custom_kernel_length_value_changed)
self.ui.buttonBox.accepted.connect(self.on_accepted)
@property
def checked_radiobutton(self):
for rb in dir(self.ui):
radio_button = getattr(self.ui, rb)
if isinstance(radio_button, QRadioButton) and radio_button.isChecked():
return radio_button
return None
@pyqtSlot(float)
def on_spin_box_custom_bandwidth_value_changed(self, bw: float):
self.ui.spinBoxCustomKernelLength.blockSignals(True)
self.ui.spinBoxCustomKernelLength.setValue(Filter.get_filter_length_from_bandwidth(bw))
self.ui.spinBoxCustomKernelLength.blockSignals(False)
@pyqtSlot(int)
def on_spin_box_custom_kernel_length_value_changed(self, filter_len: int):
self.ui.doubleSpinBoxCustomBandwidth.blockSignals(True)
self.ui.doubleSpinBoxCustomBandwidth.setValue(Filter.get_bandwidth_from_filter_length(filter_len))
self.ui.doubleSpinBoxCustomBandwidth.blockSignals(False)
@pyqtSlot()
def on_accepted(self):
if self.checked_radiobutton is not None:
bw_type = self.checked_radiobutton.objectName().replace("radioButton", "").replace("_", " ")
settings.write("bandpass_filter_bw_type", bw_type)
settings.write("bandpass_filter_custom_bw", self.ui.doubleSpinBoxCustomBandwidth.value())

View File

@ -0,0 +1,100 @@
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt
from PyQt5.QtWidgets import QDialog
from urh.signalprocessing.Filter import Filter, FilterType
from urh.ui.ui_filter_dialog import Ui_FilterDialog
class FilterDialog(QDialog):
filter_accepted = pyqtSignal(Filter)
def __init__(self, dsp_filter: Filter, parent=None):
super().__init__(parent)
self.ui = Ui_FilterDialog()
self.ui.setupUi(self)
self.setWindowFlags(Qt.Window)
self.error_message = ""
self.set_dsp_filter_status(dsp_filter.filter_type)
self.create_connects()
def set_dsp_filter_status(self, dsp_filter_type: FilterType):
if dsp_filter_type == FilterType.moving_average:
self.ui.radioButtonMovingAverage.setChecked(True)
self.ui.lineEditCustomTaps.setEnabled(False)
self.ui.spinBoxNumTaps.setEnabled(True)
elif dsp_filter_type == FilterType.dc_correction:
self.ui.radioButtonDCcorrection.setChecked(True)
self.ui.lineEditCustomTaps.setEnabled(False)
self.ui.spinBoxNumTaps.setEnabled(False)
else:
self.ui.radioButtonCustomTaps.setChecked(True)
self.ui.spinBoxNumTaps.setEnabled(True)
self.ui.lineEditCustomTaps.setEnabled(True)
def create_connects(self):
self.ui.radioButtonMovingAverage.clicked.connect(self.on_radio_button_moving_average_clicked)
self.ui.radioButtonCustomTaps.clicked.connect(self.on_radio_button_custom_taps_clicked)
self.ui.radioButtonDCcorrection.clicked.connect(self.on_radio_button_dc_correction_clicked)
self.ui.spinBoxNumTaps.valueChanged.connect(self.set_error_status)
self.ui.lineEditCustomTaps.textEdited.connect(self.set_error_status)
self.ui.buttonBox.accepted.connect(self.on_accept_clicked)
self.ui.buttonBox.rejected.connect(self.reject)
def build_filter(self) -> Filter:
if self.ui.radioButtonMovingAverage.isChecked():
n = self.ui.spinBoxNumTaps.value()
return Filter([1/n for _ in range(n)], filter_type=FilterType.moving_average)
elif self.ui.radioButtonDCcorrection.isChecked():
return Filter([], filter_type=FilterType.dc_correction)
else:
# custom filter
try:
taps = eval(self.ui.lineEditCustomTaps.text())
try:
taps = list(map(float, taps))
self.error_message = ""
return Filter(taps)
except (ValueError, TypeError) as e:
self.error_message = "Error casting taps:\n" + str(e)
return None
except SyntaxError as e:
self.error_message = "Error parsing taps:\n" + str(e)
return None
def set_error_status(self):
dsp_filter = self.build_filter()
if dsp_filter is None:
self.ui.lineEditCustomTaps.setStyleSheet("background: red")
self.ui.lineEditCustomTaps.setToolTip(self.error_message)
elif len(dsp_filter.taps) != self.ui.spinBoxNumTaps.value():
self.ui.lineEditCustomTaps.setStyleSheet("background: yellow")
self.ui.lineEditCustomTaps.setToolTip("The number of the filter taps does not match the configured number of taps. I will use your configured filter taps.")
else:
self.ui.lineEditCustomTaps.setStyleSheet("")
self.ui.lineEditCustomTaps.setToolTip("")
@pyqtSlot(bool)
def on_radio_button_moving_average_clicked(self, checked: bool):
if checked:
self.set_dsp_filter_status(FilterType.moving_average)
@pyqtSlot(bool)
def on_radio_button_custom_taps_clicked(self, checked: bool):
if checked:
self.set_dsp_filter_status(FilterType.custom)
self.set_error_status()
@pyqtSlot(bool)
def on_radio_button_dc_correction_clicked(self, checked: bool):
if checked:
self.set_dsp_filter_status(FilterType.dc_correction)
@pyqtSlot()
def on_accept_clicked(self):
dsp_filter = self.build_filter()
self.filter_accepted.emit(dsp_filter)
self.accept()

View File

@ -0,0 +1,362 @@
import math
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import QDialog, QInputDialog
from urh import settings
from urh.models.FuzzingTableModel import FuzzingTableModel
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
from urh.signalprocessing.ProtocolAnalyzerContainer import ProtocolAnalyzerContainer
from urh.ui.ui_fuzzing import Ui_FuzzingDialog
class FuzzingDialog(QDialog):
def __init__(self, protocol: ProtocolAnalyzerContainer, label_index: int, msg_index: int, proto_view: int,
parent=None):
super().__init__(parent)
self.ui = Ui_FuzzingDialog()
self.ui.setupUi(self)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.Window)
self.protocol = protocol
msg_index = msg_index if msg_index != -1 else 0
self.ui.spinBoxFuzzMessage.setValue(msg_index + 1)
self.ui.spinBoxFuzzMessage.setMinimum(1)
self.ui.spinBoxFuzzMessage.setMaximum(self.protocol.num_messages)
self.ui.comboBoxFuzzingLabel.addItems([l.name for l in self.message.message_type])
self.ui.comboBoxFuzzingLabel.setCurrentIndex(label_index)
self.proto_view = proto_view
self.fuzz_table_model = FuzzingTableModel(self.current_label, proto_view)
self.fuzz_table_model.remove_duplicates = self.ui.chkBRemoveDuplicates.isChecked()
self.ui.tblFuzzingValues.setModel(self.fuzz_table_model)
self.fuzz_table_model.update()
self.ui.spinBoxFuzzingStart.setValue(self.current_label_start + 1)
self.ui.spinBoxFuzzingEnd.setValue(self.current_label_end)
self.ui.spinBoxFuzzingStart.setMaximum(len(self.message_data))
self.ui.spinBoxFuzzingEnd.setMaximum(len(self.message_data))
self.update_message_data_string()
self.ui.tblFuzzingValues.resize_me()
self.create_connects()
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
@property
def message(self):
return self.protocol.messages[int(self.ui.spinBoxFuzzMessage.value() - 1)]
@property
def current_label_index(self):
return self.ui.comboBoxFuzzingLabel.currentIndex()
@property
def current_label(self) -> ProtocolLabel:
if len(self.message.message_type) == 0:
return None
cur_label = self.message.message_type[self.current_label_index].get_copy()
self.message.message_type[self.current_label_index] = cur_label
cur_label.fuzz_values = [fv for fv in cur_label.fuzz_values if fv] # Remove empty strings
if len(cur_label.fuzz_values) == 0:
cur_label.fuzz_values.append(self.message.plain_bits_str[cur_label.start:cur_label.end])
return cur_label
@property
def current_label_start(self):
if self.current_label and self.message:
return self.message.get_label_range(self.current_label, self.proto_view, False)[0]
else:
return -1
@property
def current_label_end(self):
if self.current_label and self.message:
return self.message.get_label_range(self.current_label, self.proto_view, False)[1]
else:
return -1
@property
def message_data(self):
if self.proto_view == 0:
return self.message.plain_bits_str
elif self.proto_view == 1:
return self.message.plain_hex_str
elif self.proto_view == 2:
return self.message.plain_ascii_str
else:
return None
def create_connects(self):
self.ui.spinBoxFuzzingStart.valueChanged.connect(self.on_fuzzing_start_changed)
self.ui.spinBoxFuzzingEnd.valueChanged.connect(self.on_fuzzing_end_changed)
self.ui.comboBoxFuzzingLabel.currentIndexChanged.connect(self.on_combo_box_fuzzing_label_current_index_changed)
self.ui.btnRepeatValues.clicked.connect(self.on_btn_repeat_values_clicked)
self.ui.btnAddRow.clicked.connect(self.on_btn_add_row_clicked)
self.ui.btnDelRow.clicked.connect(self.on_btn_del_row_clicked)
self.ui.tblFuzzingValues.deletion_wanted.connect(self.delete_lines)
self.ui.chkBRemoveDuplicates.stateChanged.connect(self.on_remove_duplicates_state_changed)
self.ui.sBAddRangeStart.valueChanged.connect(self.on_fuzzing_range_start_changed)
self.ui.sBAddRangeEnd.valueChanged.connect(self.on_fuzzing_range_end_changed)
self.ui.checkBoxLowerBound.stateChanged.connect(self.on_lower_bound_checked_changed)
self.ui.checkBoxUpperBound.stateChanged.connect(self.on_upper_bound_checked_changed)
self.ui.spinBoxLowerBound.valueChanged.connect(self.on_lower_bound_changed)
self.ui.spinBoxUpperBound.valueChanged.connect(self.on_upper_bound_changed)
self.ui.spinBoxRandomMinimum.valueChanged.connect(self.on_random_range_min_changed)
self.ui.spinBoxRandomMaximum.valueChanged.connect(self.on_random_range_max_changed)
self.ui.spinBoxFuzzMessage.valueChanged.connect(self.on_fuzz_msg_changed)
self.ui.btnAddFuzzingValues.clicked.connect(self.on_btn_add_fuzzing_values_clicked)
self.ui.comboBoxFuzzingLabel.editTextChanged.connect(self.set_current_label_name)
def update_message_data_string(self):
fuz_start = self.current_label_start
fuz_end = self.current_label_end
num_proto_bits = 10
num_fuz_bits = 16
proto_start = fuz_start - num_proto_bits
preambel = "... "
if proto_start <= 0:
proto_start = 0
preambel = ""
proto_end = fuz_end + num_proto_bits
postambel = " ..."
if proto_end >= len(self.message_data) - 1:
proto_end = len(self.message_data) - 1
postambel = ""
fuzamble = ""
if fuz_end - fuz_start > num_fuz_bits:
fuz_end = fuz_start + num_fuz_bits
fuzamble = "..."
self.ui.lPreBits.setText(preambel + self.message_data[proto_start:self.current_label_start])
self.ui.lFuzzedBits.setText(self.message_data[fuz_start:fuz_end] + fuzamble)
self.ui.lPostBits.setText(self.message_data[self.current_label_end:proto_end] + postambel)
self.set_add_spinboxes_maximum_on_label_change()
def closeEvent(self, event: QCloseEvent):
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
super().closeEvent(event)
@pyqtSlot(int)
def on_fuzzing_start_changed(self, value: int):
self.ui.spinBoxFuzzingEnd.setMinimum(self.ui.spinBoxFuzzingStart.value())
new_start = self.message.convert_index(value - 1, self.proto_view, 0, False)[0]
self.current_label.start = new_start
self.current_label.fuzz_values[:] = []
self.update_message_data_string()
self.fuzz_table_model.update()
self.ui.tblFuzzingValues.resize_me()
@pyqtSlot(int)
def on_fuzzing_end_changed(self, value: int):
self.ui.spinBoxFuzzingStart.setMaximum(self.ui.spinBoxFuzzingEnd.value())
new_end = self.message.convert_index(value - 1, self.proto_view, 0, False)[1] + 1
self.current_label.end = new_end
self.current_label.fuzz_values[:] = []
self.update_message_data_string()
self.fuzz_table_model.update()
self.ui.tblFuzzingValues.resize_me()
@pyqtSlot(int)
def on_combo_box_fuzzing_label_current_index_changed(self, index: int):
self.fuzz_table_model.fuzzing_label = self.current_label
self.fuzz_table_model.update()
self.update_message_data_string()
self.ui.tblFuzzingValues.resize_me()
self.ui.spinBoxFuzzingStart.blockSignals(True)
self.ui.spinBoxFuzzingStart.setValue(self.current_label_start + 1)
self.ui.spinBoxFuzzingStart.blockSignals(False)
self.ui.spinBoxFuzzingEnd.blockSignals(True)
self.ui.spinBoxFuzzingEnd.setValue(self.current_label_end)
self.ui.spinBoxFuzzingEnd.blockSignals(False)
@pyqtSlot()
def on_btn_add_row_clicked(self):
self.current_label.add_fuzz_value()
self.fuzz_table_model.update()
@pyqtSlot()
def on_btn_del_row_clicked(self):
min_row, max_row, _, _ = self.ui.tblFuzzingValues.selection_range()
self.delete_lines(min_row, max_row)
@pyqtSlot(int, int)
def delete_lines(self, min_row, max_row):
if min_row == -1:
self.current_label.fuzz_values = self.current_label.fuzz_values[:-1]
else:
self.current_label.fuzz_values = self.current_label.fuzz_values[:min_row] + self.current_label.fuzz_values[
max_row + 1:]
_ = self.current_label # if user deleted all, this will restore a fuzz value
self.fuzz_table_model.update()
@pyqtSlot()
def on_remove_duplicates_state_changed(self):
self.fuzz_table_model.remove_duplicates = self.ui.chkBRemoveDuplicates.isChecked()
self.fuzz_table_model.update()
self.remove_duplicates()
@pyqtSlot()
def set_add_spinboxes_maximum_on_label_change(self):
nbits = self.current_label.end - self.current_label.start # Use Bit Start/End for maximum calc.
if nbits >= 32:
nbits = 31
max_val = 2 ** nbits - 1
self.ui.sBAddRangeStart.setMaximum(max_val - 1)
self.ui.sBAddRangeEnd.setMaximum(max_val)
self.ui.sBAddRangeEnd.setValue(max_val)
self.ui.sBAddRangeStep.setMaximum(max_val)
self.ui.spinBoxLowerBound.setMaximum(max_val - 1)
self.ui.spinBoxUpperBound.setMaximum(max_val)
self.ui.spinBoxUpperBound.setValue(max_val)
self.ui.spinBoxBoundaryNumber.setMaximum(int(max_val / 2) + 1)
self.ui.spinBoxRandomMinimum.setMaximum(max_val - 1)
self.ui.spinBoxRandomMaximum.setMaximum(max_val)
self.ui.spinBoxRandomMaximum.setValue(max_val)
@pyqtSlot(int)
def on_fuzzing_range_start_changed(self, value: int):
self.ui.sBAddRangeEnd.setMinimum(value)
self.ui.sBAddRangeStep.setMaximum(self.ui.sBAddRangeEnd.value() - value)
@pyqtSlot(int)
def on_fuzzing_range_end_changed(self, value: int):
self.ui.sBAddRangeStart.setMaximum(value - 1)
self.ui.sBAddRangeStep.setMaximum(value - self.ui.sBAddRangeStart.value())
@pyqtSlot()
def on_lower_bound_checked_changed(self):
if self.ui.checkBoxLowerBound.isChecked():
self.ui.spinBoxLowerBound.setEnabled(True)
self.ui.spinBoxBoundaryNumber.setEnabled(True)
elif not self.ui.checkBoxUpperBound.isChecked():
self.ui.spinBoxLowerBound.setEnabled(False)
self.ui.spinBoxBoundaryNumber.setEnabled(False)
else:
self.ui.spinBoxLowerBound.setEnabled(False)
@pyqtSlot()
def on_upper_bound_checked_changed(self):
if self.ui.checkBoxUpperBound.isChecked():
self.ui.spinBoxUpperBound.setEnabled(True)
self.ui.spinBoxBoundaryNumber.setEnabled(True)
elif not self.ui.checkBoxLowerBound.isChecked():
self.ui.spinBoxUpperBound.setEnabled(False)
self.ui.spinBoxBoundaryNumber.setEnabled(False)
else:
self.ui.spinBoxUpperBound.setEnabled(False)
@pyqtSlot()
def on_lower_bound_changed(self):
self.ui.spinBoxUpperBound.setMinimum(self.ui.spinBoxLowerBound.value())
self.ui.spinBoxBoundaryNumber.setMaximum(math.ceil((self.ui.spinBoxUpperBound.value()
- self.ui.spinBoxLowerBound.value()) / 2))
@pyqtSlot()
def on_upper_bound_changed(self):
self.ui.spinBoxLowerBound.setMaximum(self.ui.spinBoxUpperBound.value() - 1)
self.ui.spinBoxBoundaryNumber.setMaximum(math.ceil((self.ui.spinBoxUpperBound.value()
- self.ui.spinBoxLowerBound.value()) / 2))
@pyqtSlot()
def on_random_range_min_changed(self):
self.ui.spinBoxRandomMaximum.setMinimum(self.ui.spinBoxRandomMinimum.value())
@pyqtSlot()
def on_random_range_max_changed(self):
self.ui.spinBoxRandomMinimum.setMaximum(self.ui.spinBoxRandomMaximum.value() - 1)
@pyqtSlot()
def on_btn_add_fuzzing_values_clicked(self):
if self.ui.comboBoxStrategy.currentIndex() == 0:
self.__add_fuzzing_range()
elif self.ui.comboBoxStrategy.currentIndex() == 1:
self.__add_fuzzing_boundaries()
elif self.ui.comboBoxStrategy.currentIndex() == 2:
self.__add_random_fuzzing_values()
def __add_fuzzing_range(self):
start = self.ui.sBAddRangeStart.value()
end = self.ui.sBAddRangeEnd.value()
step = self.ui.sBAddRangeStep.value()
self.fuzz_table_model.add_range(start, end + 1, step)
def __add_fuzzing_boundaries(self):
lower_bound = -1
if self.ui.spinBoxLowerBound.isEnabled():
lower_bound = self.ui.spinBoxLowerBound.value()
upper_bound = -1
if self.ui.spinBoxUpperBound.isEnabled():
upper_bound = self.ui.spinBoxUpperBound.value()
num_vals = self.ui.spinBoxBoundaryNumber.value()
self.fuzz_table_model.add_boundaries(lower_bound, upper_bound, num_vals)
def __add_random_fuzzing_values(self):
n = self.ui.spinBoxNumberRandom.value()
minimum = self.ui.spinBoxRandomMinimum.value()
maximum = self.ui.spinBoxRandomMaximum.value()
self.fuzz_table_model.add_random(n, minimum, maximum)
def remove_duplicates(self):
if self.ui.chkBRemoveDuplicates.isChecked():
for lbl in self.message.message_type:
seq = lbl.fuzz_values[:]
seen = set()
add_seen = seen.add
lbl.fuzz_values = [l for l in seq if not (l in seen or add_seen(l))]
@pyqtSlot()
def set_current_label_name(self):
self.current_label.name = self.ui.comboBoxFuzzingLabel.currentText()
self.ui.comboBoxFuzzingLabel.setItemText(self.ui.comboBoxFuzzingLabel.currentIndex(), self.current_label.name)
@pyqtSlot(int)
def on_fuzz_msg_changed(self, index: int):
self.ui.comboBoxFuzzingLabel.setDisabled(False)
sel_label_ind = self.ui.comboBoxFuzzingLabel.currentIndex()
self.ui.comboBoxFuzzingLabel.blockSignals(True)
self.ui.comboBoxFuzzingLabel.clear()
if len(self.message.message_type) == 0:
self.ui.comboBoxFuzzingLabel.setDisabled(True)
return
self.ui.comboBoxFuzzingLabel.addItems([lbl.name for lbl in self.message.message_type])
self.ui.comboBoxFuzzingLabel.blockSignals(False)
if sel_label_ind < self.ui.comboBoxFuzzingLabel.count():
self.ui.comboBoxFuzzingLabel.setCurrentIndex(sel_label_ind)
else:
self.ui.comboBoxFuzzingLabel.setCurrentIndex(0)
self.fuzz_table_model.fuzzing_label = self.current_label
self.fuzz_table_model.update()
self.update_message_data_string()
@pyqtSlot()
def on_btn_repeat_values_clicked(self):
num_repeats, ok = QInputDialog.getInt(self, self.tr("How many times shall values be repeated?"),
self.tr("Number of repeats:"), 1, 1)
if ok:
self.ui.chkBRemoveDuplicates.setChecked(False)
min_row, max_row, _, _ = self.ui.tblFuzzingValues.selection_range()
if min_row == -1:
start, end = 0, len(self.current_label.fuzz_values)
else:
start, end = min_row, max_row + 1
self.fuzz_table_model.repeat_fuzzing_values(start, end, num_repeats)

View File

@ -0,0 +1,111 @@
import copy
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import QDialog
from urh import settings
from urh.models.RulesetTableModel import RulesetTableModel
from urh.signalprocessing import Ruleset
from urh.signalprocessing.MessageType import MessageType
from urh.signalprocessing.Ruleset import Rule, OPERATION_DESCRIPTION
from urh.ui.delegates.ComboBoxDelegate import ComboBoxDelegate
from urh.ui.ui_messagetype_options import Ui_DialogMessageType
class MessageTypeDialog(QDialog):
def __init__(self, message_type: MessageType, parent=None):
super().__init__(parent)
self.ui = Ui_DialogMessageType()
self.ui.setupUi(self)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.Window)
operator_descriptions = list(OPERATION_DESCRIPTION.values())
operator_descriptions.sort()
self.setWindowTitle(self.tr("Rules for {}".format(message_type.name)))
self.message_type = message_type
self.original_ruleset = copy.deepcopy(message_type.ruleset)
self.original_assigned_status = message_type.assigned_by_ruleset
self.ruleset_table_model = RulesetTableModel(message_type.ruleset, operator_descriptions, parent=self)
self.ui.tblViewRuleset.setModel(self.ruleset_table_model)
self.ui.btnRemoveRule.setEnabled(len(message_type.ruleset) > 0)
self.set_ruleset_ui_status()
self.ui.rbAssignAutomatically.setChecked(self.message_type.assigned_by_ruleset)
self.ui.rbAssignManually.setChecked(self.message_type.assign_manually)
self.ui.tblViewRuleset.setItemDelegateForColumn(2, ComboBoxDelegate(["Bit", "Hex", "ASCII"], parent=self))
self.ui.tblViewRuleset.setItemDelegateForColumn(3, ComboBoxDelegate(operator_descriptions, parent=self))
for i in range(len(message_type.ruleset)):
self.open_editors(i)
self.ui.cbRulesetMode.setCurrentIndex(self.message_type.ruleset.mode.value)
self.create_connects()
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
def create_connects(self):
self.ui.btnAddRule.clicked.connect(self.on_btn_add_rule_clicked)
self.ui.btnRemoveRule.clicked.connect(self.on_btn_remove_rule_clicked)
self.ui.rbAssignAutomatically.clicked.connect(self.on_rb_assign_automatically_clicked)
self.ui.rbAssignManually.clicked.connect(self.on_rb_assign_manually_clicked)
self.ui.cbRulesetMode.currentIndexChanged.connect(self.on_cb_rulesetmode_current_index_changed)
self.ui.buttonBox.accepted.connect(self.accept)
self.ui.buttonBox.rejected.connect(self.on_rejected)
def set_ruleset_ui_status(self):
self.ui.tblViewRuleset.setEnabled(self.message_type.assigned_by_ruleset)
self.ui.btnRemoveRule.setEnabled(self.message_type.assigned_by_ruleset and len(self.message_type.ruleset) > 0)
self.ui.btnAddRule.setEnabled(self.message_type.assigned_by_ruleset)
self.ui.cbRulesetMode.setEnabled(self.message_type.assigned_by_ruleset)
def open_editors(self, row):
self.ui.tblViewRuleset.openPersistentEditor(self.ruleset_table_model.index(row, 2))
self.ui.tblViewRuleset.openPersistentEditor(self.ruleset_table_model.index(row, 3))
def closeEvent(self, event: QCloseEvent):
self.ui.tblViewRuleset.setItemDelegateForColumn(2, None)
self.ui.tblViewRuleset.setItemDelegateForColumn(3, None)
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
super().closeEvent(event)
@pyqtSlot()
def on_rejected(self):
self.message_type.ruleset = self.original_ruleset
self.message_type.assigned_by_ruleset = self.original_assigned_status
self.reject()
@pyqtSlot()
def on_btn_add_rule_clicked(self):
self.ui.btnRemoveRule.setEnabled(True)
self.message_type.ruleset.append(Rule(start=0, end=0, operator="=", target_value="1", value_type=0))
self.ruleset_table_model.update()
for i in range(len(self.message_type.ruleset)):
self.open_editors(i)
@pyqtSlot()
def on_btn_remove_rule_clicked(self):
self.ruleset_table_model.ruleset.remove(self.message_type.ruleset[-1])
self.ruleset_table_model.update()
self.ui.btnRemoveRule.setEnabled(len(self.message_type.ruleset) > 0)
@pyqtSlot()
def on_rb_assign_automatically_clicked(self):
self.message_type.assigned_by_ruleset = True
self.set_ruleset_ui_status()
@pyqtSlot()
def on_rb_assign_manually_clicked(self):
self.message_type.assigned_by_ruleset = False
self.set_ruleset_ui_status()
@pyqtSlot(int)
def on_cb_rulesetmode_current_index_changed(self, index: int):
self.message_type.ruleset.mode = Ruleset.Mode(index)

View File

@ -0,0 +1,68 @@
import math
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtWidgets import QDialog, QTableWidgetItem
from urh.ui.delegates.KillerSpinBoxDelegate import KillerSpinBoxDelegate
from urh.ui.delegates.SpinBoxDelegate import SpinBoxDelegate
from urh.ui.ui_modulation_parameters_dialog import Ui_DialogModulationParameters
class ModulationParametersDialog(QDialog):
def __init__(self, parameters: list, modulation_type: str, parent=None):
super().__init__(parent)
self.ui = Ui_DialogModulationParameters()
self.ui.setupUi(self)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.Window)
self.parameters = parameters
self.num_bits = int(math.log2(len(parameters)))
if "FSK" in modulation_type:
self.ui.tblSymbolParameters.setItemDelegateForColumn(1, KillerSpinBoxDelegate(-1e12, 1e12, self))
self.ui.tblSymbolParameters.horizontalHeaderItem(1).setText("Frequency in Hz")
elif "ASK" in modulation_type:
self.ui.tblSymbolParameters.horizontalHeaderItem(1).setText("Amplitude")
self.ui.tblSymbolParameters.setItemDelegateForColumn(1, SpinBoxDelegate(0, 100, self, "%"))
elif "PSK" in modulation_type:
self.ui.tblSymbolParameters.setItemDelegateForColumn(1, SpinBoxDelegate(-360, 360, self, "°"))
self.ui.tblSymbolParameters.horizontalHeaderItem(1).setText("Phase")
fmt = "{0:0" + str(self.num_bits) + "b}"
self.ui.tblSymbolParameters.setRowCount(len(parameters))
for i, parameter in enumerate(parameters):
item = QTableWidgetItem(fmt.format(i))
font = item.font()
font.setBold(True)
item.setFont(font)
item.setFlags(Qt.ItemIsEnabled)
self.ui.tblSymbolParameters.setItem(i, 0, item)
item = QTableWidgetItem()
item.setData(Qt.DisplayRole, self.parameters[i])
self.ui.tblSymbolParameters.setItem(i, 1, item)
self.ui.tblSymbolParameters.openPersistentEditor(self.ui.tblSymbolParameters.item(i, 1))
self.create_connects()
def create_connects(self):
self.ui.buttonBox.accepted.connect(self.on_accepted)
self.ui.buttonBox.rejected.connect(self.reject)
@pyqtSlot()
def on_accepted(self):
for i in range(self.ui.tblSymbolParameters.rowCount()):
self.parameters[i] = float(self.ui.tblSymbolParameters.item(i, 1).text())
self.accept()
if __name__ == '__main__':
from PyQt5.QtWidgets import QApplication
app = QApplication(["urh"])
dialog = ModulationParametersDialog([0, 100.0], "ASK")
dialog.show()
app.exec_()

View File

@ -0,0 +1,636 @@
from array import array
import numpy
from PyQt5.QtCore import Qt, pyqtSlot, QRegExp, QTimer
from PyQt5.QtGui import QCloseEvent, QResizeEvent, QKeyEvent, QIcon, QRegExpValidator
from PyQt5.QtWidgets import QDialog, QMessageBox, QLineEdit
from urh import settings
from urh.controller.dialogs.ModulationParametersDialog import ModulationParametersDialog
from urh.signalprocessing.IQArray import IQArray
from urh.signalprocessing.Modulator import Modulator
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
from urh.ui.ui_modulation import Ui_DialogModulation
from urh.util.Logger import logger
class ModulatorDialog(QDialog):
def __init__(self, modulators, tree_model=None, parent=None):
"""
:type modulators: list of Modulator
"""
super().__init__(parent)
self.ui = Ui_DialogModulation()
self.ui.setupUi(self)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.Window)
self.lock_samples_in_view = False
if tree_model is not None:
self.ui.treeViewSignals.setModel(tree_model)
self.ui.treeViewSignals.expandAll()
self.ui.gVOriginalSignal.signal_tree_root = tree_model.rootItem
self.ui.comboBoxCustomModulations.clear()
for modulator in modulators:
self.ui.comboBoxCustomModulations.addItem(modulator.name)
if len(modulators) == 1:
self.ui.btnRemoveModulation.setDisabled(True)
self.modulators = modulators
self.set_ui_for_current_modulator()
self.ui.cbShowDataBitsOnly.setText(self.tr("Show Only Data Sequence\n"))
self.ui.cbShowDataBitsOnly.setEnabled(False)
self.protocol = None # type: ProtocolAnalyzer
self.search_results = []
self.ui.cbShowDataBitsOnly.setEnabled(False)
self.ui.btnSearchNext.setEnabled(False)
self.ui.btnSearchPrev.setEnabled(False)
self.ui.chkBoxLockSIV.setDisabled(True)
self.original_bits = ""
self.restore_bits_action = self.ui.linEdDataBits.addAction(QIcon.fromTheme("edit-undo"),
QLineEdit.TrailingPosition)
self.restore_bits_action.setEnabled(False)
self.configure_parameters_action = self.ui.lineEditParameters.addAction(QIcon.fromTheme("configure"),
QLineEdit.TrailingPosition)
self.create_connects()
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
self.set_bits_per_symbol_enabled_status()
self.set_modulation_profile_status()
# Ensure full screen is shown after resize
QTimer.singleShot(100, self.show_full_scene)
def __cur_selected_mod_type(self):
s = self.ui.comboBoxModulationType.currentText()
return s[s.rindex("(") + 1:s.rindex(")")]
@staticmethod
def __trim_number(number):
if abs(number) >= 1e9: # giga
return numpy.round(number / 1e9) * 1e9
elif abs(number) >= 1e6: # mega
return numpy.round(number / 1e6) * 1e6
elif abs(number) >= 1e3: # Kilo
return numpy.round(number / 1e3) * 1e3
else:
return number
@staticmethod
def __ensure_multitude(num1, num2):
try:
if abs(num1) > abs(num2):
num1 = abs(int(num1 / num2)) * num2
else:
num2 = abs(int(num2 / num1)) * num1
return num1, num2
except Exception:
return num1, num2
def __set_gauss_ui_visibility(self, show: bool):
self.ui.lGaussBT.setVisible(show)
self.ui.lGaussWidth.setVisible(show)
self.ui.spinBoxGaussBT.setVisible(show)
self.ui.spinBoxGaussFilterWidth.setVisible(show)
self.ui.spinBoxGaussFilterWidth.setValue(self.current_modulator.gauss_filter_width)
self.ui.spinBoxGaussBT.setValue(self.current_modulator.gauss_bt)
def closeEvent(self, event: QCloseEvent):
self.ui.lineEditParameters.editingFinished.emit()
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
for gv in (self.ui.gVCarrier, self.ui.gVData, self.ui.gVModulated, self.ui.gVOriginalSignal):
# Eliminate graphic views to prevent segfaults
gv.eliminate()
super().closeEvent(event)
@property
def current_modulator(self):
return self.modulators[self.ui.comboBoxCustomModulations.currentIndex()]
def set_ui_for_current_modulator(self):
index = self.ui.comboBoxModulationType.findText("*(" + self.current_modulator.modulation_type + ")",
Qt.MatchWildcard)
self.ui.comboBoxModulationType.setCurrentIndex(index)
self.ui.doubleSpinBoxCarrierFreq.setValue(self.current_modulator.carrier_freq_hz)
self.ui.doubleSpinBoxCarrierPhase.setValue(self.current_modulator.carrier_phase_deg)
self.ui.spinBoxSamplesPerSymbol.setValue(self.current_modulator.samples_per_symbol)
self.ui.spinBoxSampleRate.setValue(self.current_modulator.sample_rate)
self.ui.spinBoxBitsPerSymbol.setValue(self.current_modulator.bits_per_symbol)
self.update_modulation_parameters()
def create_connects(self):
self.ui.doubleSpinBoxCarrierFreq.valueChanged.connect(self.on_carrier_freq_changed)
self.ui.doubleSpinBoxCarrierPhase.valueChanged.connect(self.on_carrier_phase_changed)
self.ui.spinBoxSamplesPerSymbol.valueChanged.connect(self.on_samples_per_symbol_changed)
self.ui.spinBoxSampleRate.valueChanged.connect(self.on_sample_rate_changed)
self.ui.linEdDataBits.textChanged.connect(self.on_data_bits_changed)
self.ui.spinBoxBitsPerSymbol.valueChanged.connect(self.on_bits_per_symbol_changed)
self.ui.comboBoxModulationType.currentIndexChanged.connect(self.on_modulation_type_changed)
self.ui.gVOriginalSignal.zoomed.connect(self.on_orig_signal_zoomed)
self.ui.cbShowDataBitsOnly.stateChanged.connect(self.on_show_data_bits_only_changed)
self.ui.btnSearchNext.clicked.connect(self.on_btn_next_search_result_clicked)
self.ui.btnSearchPrev.clicked.connect(self.on_btn_prev_search_result_clicked)
self.ui.comboBoxCustomModulations.editTextChanged.connect(self.on_custom_modulation_name_edited)
self.ui.comboBoxCustomModulations.currentIndexChanged.connect(self.on_custom_modulation_index_changed)
self.ui.btnAddModulation.clicked.connect(self.add_modulator)
self.ui.btnRemoveModulation.clicked.connect(self.on_remove_modulator_clicked)
self.ui.gVModulated.zoomed.connect(self.on_carrier_data_modulated_zoomed)
self.ui.gVCarrier.zoomed.connect(self.on_carrier_data_modulated_zoomed)
self.ui.gVData.zoomed.connect(self.on_carrier_data_modulated_zoomed)
self.ui.gVModulated.selection_width_changed.connect(self.on_modulated_selection_changed)
self.ui.gVOriginalSignal.selection_width_changed.connect(self.on_original_selection_changed)
self.ui.spinBoxGaussBT.valueChanged.connect(self.on_gauss_bt_changed)
self.ui.spinBoxGaussFilterWidth.valueChanged.connect(self.on_gauss_filter_width_changed)
self.ui.chkBoxLockSIV.stateChanged.connect(self.on_lock_siv_changed)
self.ui.gVOriginalSignal.signal_loaded.connect(self.handle_signal_loaded)
self.ui.btnAutoDetect.clicked.connect(self.on_btn_autodetect_clicked)
self.restore_bits_action.triggered.connect(self.on_restore_bits_action_triggered)
self.configure_parameters_action.triggered.connect(self.on_configure_parameters_action_triggered)
self.ui.lineEditParameters.editingFinished.connect(self.on_line_edit_parameters_editing_finished)
def draw_carrier(self):
self.ui.gVCarrier.plot_data(self.current_modulator.carrier_data)
def draw_data_bits(self):
self.ui.gVData.setScene(self.current_modulator.data_scene)
self.ui.gVData.update()
def draw_modulated(self):
self.ui.gVModulated.plot_data(self.current_modulator.modulate(pause=0).imag)
if self.lock_samples_in_view:
siv = self.ui.gVOriginalSignal.view_rect().width()
self.adjust_samples_in_view(siv)
else:
self.mark_samples_in_view()
def draw_original_signal(self, start=0, end=-1):
scene_manager = self.ui.gVOriginalSignal.scene_manager
if scene_manager is None:
return
if end == -1:
end = scene_manager.signal.num_samples
y = self.ui.gVOriginalSignal.view_rect().y()
h = self.ui.gVOriginalSignal.view_rect().height()
self.ui.gVOriginalSignal.setSceneRect(start, y, end - start, h)
self.ui.gVOriginalSignal.fitInView(self.ui.gVOriginalSignal.sceneRect())
scene_manager.show_scene_section(start, end)
self.ui.gVOriginalSignal.update()
if self.lock_samples_in_view:
self.adjust_samples_in_view(self.ui.gVModulated.view_rect().width())
else:
self.mark_samples_in_view()
def update_views(self):
self.ui.gVCarrier.update()
self.ui.gVData.update()
self.ui.gVModulated.update()
self.ui.gVOriginalSignal.update()
def search_data_sequence(self):
if not self.ui.cbShowDataBitsOnly.isEnabled() or not self.ui.cbShowDataBitsOnly.isChecked():
return
search_seq = self.ui.linEdDataBits.text()
if len(search_seq) == 0 or self.protocol is None:
return
self.search_results[:] = []
proto_bits = self.protocol.plain_bits_str
len_seq = len(search_seq)
for i, message in enumerate(proto_bits):
j = message.find(search_seq)
while j != -1:
self.search_results.append((i, j, j + len_seq))
j = message.find(search_seq, j + 1)
self.ui.lTotalSearchresults.setText(str(len(self.search_results)))
self.show_search_result(0)
def show_search_result(self, i: int):
if len(self.search_results) == 0:
self.ui.lCurrentSearchResult.setText("0")
self.ui.gVOriginalSignal.scene_manager.clear_path()
return
message, start_index, end_index = self.search_results[i]
start, nsamples = self.protocol.get_samplepos_of_bitseq(message, start_index, message, end_index, False)
self.draw_original_signal(start=start, end=start + nsamples)
self.ui.lCurrentSearchResult.setText(str(i + 1))
self.ui.btnSearchNext.setEnabled(i != len(self.search_results) - 1)
self.ui.btnSearchPrev.setEnabled(i > 0)
def add_modulator(self):
names = [m.name for m in self.modulators]
name = "Modulation"
number = 1
while name in names:
name = "Modulation " + str(number)
number += 1
self.modulators.append(Modulator(name))
self.ui.comboBoxCustomModulations.addItem(name)
self.ui.comboBoxCustomModulations.setCurrentIndex(len(self.modulators) - 1)
self.ui.btnRemoveModulation.setEnabled(True)
def adjust_samples_in_view(self, target_siv: float):
self.ui.gVOriginalSignal.scale(self.ui.gVOriginalSignal.view_rect().width() / target_siv, 1)
mod_zoom_factor = self.ui.gVModulated.view_rect().width() / target_siv
self.ui.gVModulated.scale(mod_zoom_factor, 1)
self.ui.gVCarrier.scale(mod_zoom_factor, 1)
self.ui.gVData.scale(mod_zoom_factor, 1)
self.mark_samples_in_view()
def detect_fsk_frequencies(self):
if not self.current_modulator.is_frequency_based:
return
frequencies = []
try:
if not self.current_modulator.is_binary_modulation:
raise NotImplementedError()
zero_freq = self.protocol.estimate_frequency_for_zero(self.current_modulator.sample_rate)
one_freq = self.protocol.estimate_frequency_for_one(self.current_modulator.sample_rate)
zero_freq = self.__trim_number(zero_freq)
one_freq = self.__trim_number(one_freq)
zero_freq, one_freq = self.__ensure_multitude(zero_freq, one_freq)
if zero_freq == one_freq:
# If frequencies are equal, it is very likely the zero freq is negative
zero_freq = -one_freq
frequencies = [zero_freq, one_freq]
except (AttributeError, NotImplementedError):
frequencies = self.current_modulator.get_default_parameters()
self.current_modulator.parameters = array("f", frequencies)
self.update_modulation_parameters()
def handle_signal_loaded(self, protocol):
self.setCursor(Qt.WaitCursor)
self.ui.cbShowDataBitsOnly.setEnabled(True)
self.ui.chkBoxLockSIV.setEnabled(True)
self.ui.btnAutoDetect.setEnabled(True)
self.protocol = protocol
# Apply bit length of original signal to current modulator
self.ui.spinBoxSamplesPerSymbol.setValue(self.ui.gVOriginalSignal.signal.samples_per_symbol)
# https://github.com/jopohl/urh/issues/130
self.ui.gVModulated.show_full_scene(reinitialize=True)
self.ui.gVCarrier.show_full_scene(reinitialize=True)
self.ui.gVData.show_full_scene(reinitialize=True)
self.unsetCursor()
def mark_samples_in_view(self):
self.ui.lSamplesInViewModulated.setText(str(int(self.ui.gVModulated.view_rect().width())))
if self.ui.gVOriginalSignal.scene_manager is not None:
self.ui.lSamplesInViewOrigSignal.setText(str(int(self.ui.gVOriginalSignal.view_rect().width())))
else:
self.ui.lSamplesInViewOrigSignal.setText("-")
return
if int(self.ui.gVOriginalSignal.view_rect().width()) != int(self.ui.gVModulated.view_rect().width()):
font = self.ui.lSamplesInViewModulated.font()
font.setBold(False)
self.ui.lSamplesInViewModulated.setFont(font)
self.ui.lSamplesInViewOrigSignal.setFont(font)
self.ui.lSamplesInViewOrigSignal.setStyleSheet("QLabel { color : red; }")
self.ui.lSamplesInViewModulated.setStyleSheet("QLabel { color : red; }")
else:
font = self.ui.lSamplesInViewModulated.font()
font.setBold(True)
self.ui.lSamplesInViewModulated.setFont(font)
self.ui.lSamplesInViewOrigSignal.setFont(font)
self.ui.lSamplesInViewOrigSignal.setStyleSheet("")
self.ui.lSamplesInViewModulated.setStyleSheet("")
def set_default_modulation_parameters(self):
self.current_modulator.parameters = self.current_modulator.get_default_parameters()
self.update_modulation_parameters()
def set_modulation_profile_status(self):
visible = settings.read("multiple_modulations", False, bool)
self.ui.btnAddModulation.setVisible(visible)
self.ui.btnRemoveModulation.setVisible(visible)
self.ui.comboBoxCustomModulations.setVisible(visible)
def resizeEvent(self, event: QResizeEvent):
self.update_views()
def keyPressEvent(self, event: QKeyEvent):
if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return:
return
else:
super().keyPressEvent(event)
def initialize(self, bits: str):
self.on_modulation_type_changed() # for drawing modulated signal initially
self.original_bits = bits
self.ui.linEdDataBits.setText(bits)
self.draw_original_signal()
self.ui.gVModulated.show_full_scene(reinitialize=True)
self.ui.gVModulated.auto_fit_view()
self.ui.gVData.show_full_scene(reinitialize=True)
self.ui.gVData.auto_fit_view()
self.ui.gVCarrier.show_full_scene(reinitialize=True)
self.ui.gVCarrier.auto_fit_view()
self.mark_samples_in_view()
def update_modulation_parameters(self):
n = len(self.current_modulator.parameters) - 1
if self.current_modulator.is_amplitude_based:
regex = r"(100|[0-9]{1,2})"
elif self.current_modulator.is_frequency_based:
regex = r"((-?[0-9]+)[.,]?[0-9]*[kKmMgG]?)"
elif self.current_modulator.is_phase_based:
regex = r"(-?(36[0]|3[0-5][0-9]|[12][0-9][0-9]|[1-9]?[0-9]))"
else:
raise ValueError("Unknown modulation type")
full_regex = r"^(" + regex + r"/){" + str(n) + "}" + regex + r"$"
self.ui.lineEditParameters.setValidator(QRegExpValidator(QRegExp(full_regex)))
self.ui.lineEditParameters.setText(self.current_modulator.parameters_string)
def set_bits_per_symbol_enabled_status(self):
if self.current_modulator.modulation_type == "OQPSK":
self.ui.spinBoxBitsPerSymbol.setEnabled(False)
self.ui.spinBoxBitsPerSymbol.setValue(2)
else:
self.ui.spinBoxBitsPerSymbol.setEnabled(True)
def show_full_scene(self):
for graphic_view in (self.ui.gVModulated, self.ui.gVData, self.ui.gVCarrier):
graphic_view.show_full_scene(reinitialize=True)
@pyqtSlot()
def on_carrier_freq_changed(self):
self.current_modulator.carrier_freq_hz = self.ui.doubleSpinBoxCarrierFreq.value()
self.draw_carrier()
self.draw_modulated()
@pyqtSlot()
def on_carrier_phase_changed(self):
self.current_modulator.carrier_phase_deg = self.ui.doubleSpinBoxCarrierPhase.value()
self.draw_carrier()
self.draw_modulated()
@pyqtSlot()
def on_samples_per_symbol_changed(self):
self.current_modulator.samples_per_symbol = self.ui.spinBoxSamplesPerSymbol.value()
self.draw_carrier()
self.draw_data_bits()
self.draw_modulated()
self.show_full_scene()
@pyqtSlot()
def on_data_bits_changed(self):
text = self.ui.linEdDataBits.text()
text = ''.join(c for c in text if c == "1" or c == "0")
self.ui.linEdDataBits.blockSignals(True)
self.ui.linEdDataBits.setText(text)
self.ui.linEdDataBits.blockSignals(False)
self.current_modulator.display_bits = text
self.draw_carrier()
self.draw_data_bits()
self.draw_modulated()
if len(text) > 0:
if len(text) > 24:
display_text = text[0:24] + "..."
else:
display_text = text
self.ui.cbShowDataBitsOnly.setToolTip(text)
self.ui.cbShowDataBitsOnly.setText(self.tr("Show Only Data Sequence\n") + "(" + display_text + ")")
else:
self.ui.cbShowDataBitsOnly.setToolTip("")
self.ui.cbShowDataBitsOnly.setText(self.tr("Show Only Data Sequence\n"))
self.search_data_sequence()
self.restore_bits_action.setEnabled(text != self.original_bits)
self.show_full_scene()
@pyqtSlot()
def on_sample_rate_changed(self):
if int(self.ui.spinBoxSampleRate.value()) > 0:
self.current_modulator.sample_rate = int(self.ui.spinBoxSampleRate.value())
self.draw_carrier()
self.draw_modulated()
@pyqtSlot()
def on_gauss_bt_changed(self):
self.current_modulator.gauss_bt = self.ui.spinBoxGaussBT.value()
self.draw_modulated()
@pyqtSlot()
def on_gauss_filter_width_changed(self):
self.current_modulator.gauss_filter_width = self.ui.spinBoxGaussFilterWidth.value()
self.draw_modulated()
@pyqtSlot()
def on_bits_per_symbol_changed(self):
if self.current_modulator.bits_per_symbol == self.ui.spinBoxBitsPerSymbol.value():
return
self.current_modulator.bits_per_symbol = self.ui.spinBoxBitsPerSymbol.value()
self.set_default_modulation_parameters()
self.draw_modulated()
self.show_full_scene()
@pyqtSlot()
def on_modulation_type_changed(self):
write_default_parameters = self.current_modulator.modulation_type != self.__cur_selected_mod_type()
self.current_modulator.modulation_type = self.__cur_selected_mod_type()
self.__set_gauss_ui_visibility(self.__cur_selected_mod_type() == "GFSK")
self.ui.labelParameters.setText(self.current_modulator.parameter_type_str)
if write_default_parameters:
self.set_default_modulation_parameters()
else:
self.update_modulation_parameters()
self.set_bits_per_symbol_enabled_status()
self.draw_modulated()
self.show_full_scene()
@pyqtSlot()
def on_orig_signal_zoomed(self):
start = self.ui.gVOriginalSignal.view_rect().x()
end = start + self.ui.gVOriginalSignal.view_rect().width()
self.ui.gVOriginalSignal.centerOn(start + (end - start) / 2, 0)
if self.lock_samples_in_view:
self.adjust_samples_in_view(self.ui.gVOriginalSignal.view_rect().width())
x = self.ui.gVOriginalSignal.view_rect().x() + self.ui.gVOriginalSignal.view_rect().width() / 2
y = 0
self.ui.gVModulated.centerOn(x, y)
self.ui.gVCarrier.centerOn(x, y)
self.ui.gVData.centerOn(x, y)
else:
self.mark_samples_in_view()
@pyqtSlot(float)
def on_carrier_data_modulated_zoomed(self, factor: float):
x = self.sender().view_rect().x() + self.sender().view_rect().width() / 2
y = 0
for gv in (self.ui.gVCarrier, self.ui.gVData, self.ui.gVModulated):
if gv == self.sender():
continue
if factor == -1:
gv.show_full_scene()
else:
gv.scale(factor, 1)
gv.centerOn(x, y)
if self.lock_samples_in_view:
self.adjust_samples_in_view(self.ui.gVModulated.view_rect().width())
self.ui.gVOriginalSignal.centerOn(x, y)
else:
self.mark_samples_in_view()
@pyqtSlot()
def on_custom_modulation_name_edited(self):
self.current_modulator.name = self.ui.comboBoxCustomModulations.currentText()
@pyqtSlot()
def on_custom_modulation_index_changed(self):
self.set_ui_for_current_modulator()
self.draw_carrier()
self.draw_data_bits()
self.draw_modulated()
@pyqtSlot()
def on_btn_next_search_result_clicked(self):
cur_search_result = int(self.ui.lCurrentSearchResult.text()) - 1
self.show_search_result(cur_search_result + 1)
@pyqtSlot()
def on_btn_prev_search_result_clicked(self):
cur_search_result = int(self.ui.lCurrentSearchResult.text()) - 1
self.show_search_result(cur_search_result - 1)
@pyqtSlot()
def on_show_data_bits_only_changed(self, redraw=True):
show_data_bits_only = self.ui.cbShowDataBitsOnly.isChecked()
if not self.ui.cbShowDataBitsOnly.isEnabled() or not show_data_bits_only:
self.ui.btnSearchPrev.setEnabled(False)
self.ui.btnSearchNext.setEnabled(False)
self.ui.lCurrentSearchResult.setText("-")
self.ui.lTotalSearchresults.setText("-")
else:
self.search_data_sequence()
if not redraw:
return
if self.ui.cbShowDataBitsOnly.isEnabled() and not show_data_bits_only:
self.draw_original_signal()
@pyqtSlot()
def on_remove_modulator_clicked(self):
index = self.ui.comboBoxCustomModulations.currentIndex()
self.ui.comboBoxCustomModulations.removeItem(index)
self.modulators.remove(self.modulators[index])
if len(self.modulators) == 1:
self.ui.btnRemoveModulation.setDisabled(True)
@pyqtSlot()
def on_lock_siv_changed(self):
self.lock_samples_in_view = self.ui.chkBoxLockSIV.isChecked()
if self.lock_samples_in_view:
self.adjust_samples_in_view(self.ui.gVModulated.view_rect().width())
@pyqtSlot()
def on_restore_bits_action_triggered(self):
self.ui.linEdDataBits.setText(self.original_bits)
@pyqtSlot()
def on_btn_autodetect_clicked(self):
signal = self.ui.gVOriginalSignal.scene_manager.signal
freq = self.current_modulator.estimate_carrier_frequency(signal, self.protocol)
if freq is None or freq == 0:
QMessageBox.information(self, self.tr("No results"),
self.tr("Unable to detect parameters from current signal"))
return
self.ui.doubleSpinBoxCarrierFreq.setValue(freq)
self.detect_fsk_frequencies()
@pyqtSlot(int)
def on_modulated_selection_changed(self, new_width: int):
self.ui.lModulatedSelectedSamples.setText(str(abs(new_width)))
@pyqtSlot(int)
def on_original_selection_changed(self, new_width: int):
self.ui.lOriginalSignalSamplesSelected.setText(str(abs(new_width)))
@pyqtSlot()
def on_configure_parameters_action_triggered(self):
self.ui.lineEditParameters.editingFinished.emit()
dialog = ModulationParametersDialog(self.current_modulator.parameters, self.current_modulator.modulation_type,
self)
dialog.accepted.connect(self.update_modulation_parameters)
dialog.show()
@pyqtSlot()
def on_line_edit_parameters_editing_finished(self):
if not self.ui.lineEditParameters.hasAcceptableInput():
return
text = self.ui.lineEditParameters.text()
parameters = []
for param in text.split("/"):
param = param.upper().replace(",", ".")
factor = 1
if param.endswith("G"):
factor = 10 ** 9
param = param[:-1]
elif param.endswith("M"):
factor = 10 ** 6
param = param[:-1]
elif param.endswith("K"):
factor = 10 ** 3
param = param[:-1]
try:
parameters.append(factor * float(param))
except ValueError:
logger.warning("Could not convert {} to number".format(param))
return
self.current_modulator.parameters[:] = array("f", parameters)
self.draw_modulated()
self.show_full_scene()

View File

@ -0,0 +1,511 @@
import os
import subprocess
import sys
import tempfile
import time
import numpy as np
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal, QSize, QAbstractTableModel, QModelIndex
from PyQt5.QtGui import QCloseEvent, QIcon, QPixmap
from PyQt5.QtWidgets import QDialog, QHBoxLayout, QCompleter, QDirModel, QApplication, QHeaderView, QRadioButton, \
QFileDialog, qApp
from urh import settings, colormaps
from urh.controller.widgets.PluginFrame import PluginFrame
from urh.dev.BackendHandler import BackendHandler, Backends
from urh.dev.native import ExtensionHelper
from urh.models.FieldTypeTableModel import FieldTypeTableModel
from urh.signalprocessing.FieldType import FieldType
from urh.signalprocessing.Modulator import Modulator
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
from urh.signalprocessing.Spectrogram import Spectrogram
from urh.ui.delegates.ComboBoxDelegate import ComboBoxDelegate
from urh.ui.ui_options import Ui_DialogOptions
from urh.util import util
class DeviceOptionsTableModel(QAbstractTableModel):
header_labels = ["Software Defined Radio", "Info", "Native backend (recommended)", "GNU Radio backend"]
def __init__(self, backend_handler: BackendHandler, parent=None):
self.backend_handler = backend_handler
super().__init__(parent)
def update(self):
self.beginResetModel()
self.endResetModel()
def columnCount(self, parent: QModelIndex = None, *args, **kwargs):
return len(self.header_labels)
def rowCount(self, parent: QModelIndex = None, *args, **kwargs):
return len(self.backend_handler.DEVICE_NAMES)
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.header_labels[section]
return super().headerData(section, orientation, role)
def get_device_at(self, index: int):
dev_key = self.backend_handler.get_key_from_device_display_text(self.backend_handler.DEVICE_NAMES[index])
return self.backend_handler.device_backends[dev_key]
def data(self, index: QModelIndex, role=Qt.DisplayRole):
if not index.isValid():
return None
i = index.row()
j = index.column()
device = self.get_device_at(i)
if role == Qt.DisplayRole:
if j == 0:
return self.backend_handler.DEVICE_NAMES[i]
elif j == 1:
if device.is_enabled:
if device.supports_rx and device.supports_tx:
device_info = "supports RX and TX"
elif device.supports_rx and not device.supports_tx:
device_info = "supports RX only"
elif not device.supports_rx and device.supports_tx:
device_info = "supports TX only"
else:
device_info = ""
else:
device_info = "disabled"
return device_info
elif j == 2:
return "" if device.has_native_backend else "not available"
elif j == 3:
return "" if device.has_gnuradio_backend else "not available"
elif role == Qt.CheckStateRole:
if j == 0 and (device.has_native_backend or device.has_gnuradio_backend):
return Qt.Checked if device.is_enabled else Qt.Unchecked
elif j == 2 and device.has_native_backend:
return Qt.Checked if device.selected_backend == Backends.native else Qt.Unchecked
elif j == 3 and device.has_gnuradio_backend:
return Qt.Checked if device.selected_backend == Backends.grc else Qt.Unchecked
def setData(self, index: QModelIndex, value, role=None):
if not index.isValid():
return False
i, j = index.row(), index.column()
device = self.get_device_at(i)
if role == Qt.CheckStateRole:
enabled = bool(value)
if j == 0:
device.is_enabled = enabled
if j == 2:
if enabled and device.has_native_backend:
device.selected_backend = Backends.native
elif not enabled and device.has_gnuradio_backend:
device.selected_backend = Backends.grc
elif j == 3:
if enabled and device.has_gnuradio_backend:
device.selected_backend = Backends.grc
elif not enabled and device.has_native_backend:
device.selected_backend = Backends.native
self.update()
device.write_settings()
return True
def flags(self, index: QModelIndex):
if not index.isValid():
return None
j = index.column()
device = self.get_device_at(index.row())
if j == 0 and not device.has_native_backend and not device.has_gnuradio_backend:
return Qt.NoItemFlags
if j in [1, 2, 3] and not device.is_enabled:
return Qt.NoItemFlags
if j == 2 and not device.has_native_backend:
return Qt.NoItemFlags
if j == 3 and not device.has_gnuradio_backend:
return Qt.NoItemFlags
flags = Qt.ItemIsEnabled
if j in [0, 2, 3]:
flags |= Qt.ItemIsUserCheckable
return flags
class OptionsDialog(QDialog):
values_changed = pyqtSignal(dict)
def __init__(self, installed_plugins, highlighted_plugins=None, parent=None):
super().__init__(parent)
self.backend_handler = BackendHandler()
self.ui = Ui_DialogOptions()
self.ui.setupUi(self)
self.setWindowFlags(Qt.Window)
self.device_options_model = DeviceOptionsTableModel(self.backend_handler, self)
self.device_options_model.update()
self.ui.tblDevices.setModel(self.device_options_model)
self.ui.tblDevices.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.ui.tblDevices.setItemDelegateForColumn(1, ComboBoxDelegate(["native", "GNU Radio"]))
self.setAttribute(Qt.WA_DeleteOnClose)
layout = QHBoxLayout(self.ui.tab_plugins)
self.plugin_controller = PluginFrame(installed_plugins, highlighted_plugins, parent=self)
layout.addWidget(self.plugin_controller)
self.ui.tab_plugins.setLayout(layout)
self.ui.btnViewBuildLog.hide()
self.build_log = ""
# We use bundled native device backends on windows, so no need to reconfigure them
self.ui.groupBoxNativeOptions.setVisible(sys.platform != "win32")
self.ui.labelIconTheme.setVisible(sys.platform == "linux")
self.ui.comboBoxIconTheme.setVisible(sys.platform == "linux")
self.ui.comboBoxTheme.setCurrentIndex(settings.read("theme_index", 0, int))
self.ui.comboBoxIconTheme.setCurrentIndex(settings.read("icon_theme_index", 0, int))
self.ui.checkBoxShowConfirmCloseDialog.setChecked(not settings.read('not_show_close_dialog', False, bool))
self.ui.checkBoxHoldShiftToDrag.setChecked(settings.read('hold_shift_to_drag', True, bool))
self.ui.checkBoxDefaultFuzzingPause.setChecked(settings.read('use_default_fuzzing_pause', True, bool))
self.ui.checkBoxAlignLabels.setChecked(settings.read('align_labels', True, bool))
self.ui.doubleSpinBoxRAMThreshold.setValue(100 * settings.read('ram_threshold', 0.6, float))
if self.backend_handler.gr_python_interpreter:
self.ui.lineEditGRPythonInterpreter.setText(self.backend_handler.gr_python_interpreter)
self.ui.doubleSpinBoxFuzzingPause.setValue(settings.read("default_fuzzing_pause", 10 ** 6, int))
self.ui.doubleSpinBoxFuzzingPause.setEnabled(settings.read('use_default_fuzzing_pause', True, bool))
self.ui.checkBoxMultipleModulations.setChecked(settings.read("multiple_modulations", False, bool))
self.ui.radioButtonLowModulationAccuracy.setChecked(Modulator.get_dtype() == np.int8)
self.ui.radioButtonMediumModulationAccuracy.setChecked(Modulator.get_dtype() == np.int16)
self.ui.radioButtonHighModulationAccuracy.setChecked(Modulator.get_dtype() == np.float32)
completer = QCompleter()
completer.setModel(QDirModel(completer))
self.ui.lineEditGRPythonInterpreter.setCompleter(completer)
self.ui.spinBoxFontSize.setValue(qApp.font().pointSize())
self.refresh_device_tab()
self.create_connects()
self.old_show_pause_as_time = False
self.field_type_table_model = FieldTypeTableModel([], parent=self)
self.ui.tblLabeltypes.setModel(self.field_type_table_model)
self.ui.tblLabeltypes.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.ui.tblLabeltypes.setItemDelegateForColumn(1, ComboBoxDelegate([f.name for f in FieldType.Function],
return_index=False, parent=self))
self.ui.tblLabeltypes.setItemDelegateForColumn(2, ComboBoxDelegate(ProtocolLabel.DISPLAY_FORMATS, parent=self))
self.read_options()
self.old_default_view = self.ui.comboBoxDefaultView.currentIndex()
self.old_num_sending_repeats = self.ui.spinBoxNumSendingRepeats.value()
self.ui.labelRebuildNativeStatus.setText("")
self.show_available_colormaps()
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
def create_connects(self):
self.ui.doubleSpinBoxFuzzingPause.valueChanged.connect(self.on_spinbox_fuzzing_pause_value_changed)
self.ui.lineEditGRPythonInterpreter.editingFinished.connect(self.on_gr_python_interpreter_path_edited)
self.ui.btnChooseGRPythonInterpreter.clicked.connect(self.on_btn_choose_gr_python_interpreter_clicked)
self.ui.comboBoxTheme.currentIndexChanged.connect(self.on_combo_box_theme_index_changed)
self.ui.checkBoxShowConfirmCloseDialog.clicked.connect(self.on_checkbox_confirm_close_dialog_clicked)
self.ui.checkBoxHoldShiftToDrag.clicked.connect(self.on_checkbox_hold_shift_to_drag_clicked)
self.ui.checkBoxAlignLabels.clicked.connect(self.on_checkbox_align_labels_clicked)
self.ui.checkBoxDefaultFuzzingPause.clicked.connect(self.on_checkbox_default_fuzzing_pause_clicked)
self.ui.btnAddLabelType.clicked.connect(self.on_btn_add_label_type_clicked)
self.ui.btnRemoveLabeltype.clicked.connect(self.on_btn_remove_label_type_clicked)
self.ui.radioButtonLowModulationAccuracy.clicked.connect(self.on_radio_button_low_modulation_accuracy_clicked)
self.ui.radioButtonMediumModulationAccuracy.clicked.connect(self.on_radio_button_medium_modulation_accuracy_clicked)
self.ui.radioButtonHighModulationAccuracy.clicked.connect(self.on_radio_button_high_modulation_accuracy_clicked)
self.ui.doubleSpinBoxRAMThreshold.valueChanged.connect(self.on_double_spinbox_ram_threshold_value_changed)
self.ui.btnRebuildNative.clicked.connect(self.on_btn_rebuild_native_clicked)
self.ui.comboBoxIconTheme.currentIndexChanged.connect(self.on_combobox_icon_theme_index_changed)
self.ui.checkBoxMultipleModulations.clicked.connect(self.on_checkbox_multiple_modulations_clicked)
self.ui.btnViewBuildLog.clicked.connect(self.on_btn_view_build_log_clicked)
self.ui.labelDeviceMissingInfo.linkActivated.connect(self.on_label_device_missing_info_link_activated)
self.ui.spinBoxFontSize.editingFinished.connect(self.on_spin_box_font_size_editing_finished)
def show_gnuradio_infos(self):
self.ui.lineEditGRPythonInterpreter.setText(self.backend_handler.gr_python_interpreter)
if self.backend_handler.gnuradio_is_installed:
self.ui.lineEditGRPythonInterpreter.setStyleSheet("background-color: lightgreen")
self.ui.lineEditGRPythonInterpreter.setToolTip("GNU Radio interface is working.")
else:
self.ui.lineEditGRPythonInterpreter.setStyleSheet("background-color: orange")
self.ui.lineEditGRPythonInterpreter.setToolTip("GNU Radio is not installed or incompatible with "
"the configured python interpreter.")
def read_options(self):
self.ui.comboBoxDefaultView.setCurrentIndex(settings.read('default_view', 0, type=int))
self.ui.spinBoxNumSendingRepeats.setValue(settings.read('num_sending_repeats', 0, type=int))
self.ui.checkBoxPauseTime.setChecked(settings.read('show_pause_as_time', False, type=bool))
self.old_show_pause_as_time = bool(self.ui.checkBoxPauseTime.isChecked())
self.field_type_table_model.field_types = FieldType.load_from_xml()
self.field_type_table_model.update()
def refresh_device_tab(self):
self.backend_handler.get_backends()
self.show_gnuradio_infos()
self.device_options_model.update()
def show_available_colormaps(self):
height = 50
selected = colormaps.read_selected_colormap_name_from_settings()
for colormap_name in sorted(colormaps.maps.keys()):
image = Spectrogram.create_colormap_image(colormap_name, height=height)
rb = QRadioButton(colormap_name)
rb.setObjectName(colormap_name)
rb.setChecked(colormap_name == selected)
rb.setIcon(QIcon(QPixmap.fromImage(image)))
rb.setIconSize(QSize(256, height))
self.ui.scrollAreaWidgetSpectrogramColormapContents.layout().addWidget(rb)
def closeEvent(self, event: QCloseEvent):
changed_values = {}
if bool(self.ui.checkBoxPauseTime.isChecked()) != self.old_show_pause_as_time:
changed_values['show_pause_as_time'] = bool(self.ui.checkBoxPauseTime.isChecked())
if self.old_default_view != self.ui.comboBoxDefaultView.currentIndex():
changed_values['default_view'] = self.ui.comboBoxDefaultView.currentIndex()
if self.old_num_sending_repeats != self.ui.spinBoxNumSendingRepeats.value():
changed_values["num_sending_repeats"] = self.ui.spinBoxNumSendingRepeats.value()
settings.write('default_view', self.ui.comboBoxDefaultView.currentIndex())
settings.write('num_sending_repeats', self.ui.spinBoxNumSendingRepeats.value())
settings.write('show_pause_as_time', self.ui.checkBoxPauseTime.isChecked())
FieldType.save_to_xml(self.field_type_table_model.field_types)
self.plugin_controller.save_enabled_states()
for plugin in self.plugin_controller.model.plugins:
plugin.destroy_settings_frame()
for i in range(self.ui.scrollAreaWidgetSpectrogramColormapContents.layout().count()):
widget = self.ui.scrollAreaWidgetSpectrogramColormapContents.layout().itemAt(i).widget()
if isinstance(widget, QRadioButton) and widget.isChecked():
selected_colormap_name = widget.objectName()
if selected_colormap_name != colormaps.read_selected_colormap_name_from_settings():
colormaps.choose_colormap(selected_colormap_name)
colormaps.write_selected_colormap_to_settings(selected_colormap_name)
changed_values["spectrogram_colormap"] = selected_colormap_name
break
self.values_changed.emit(changed_values)
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
super().closeEvent(event)
def set_gnuradio_status(self):
self.backend_handler.gr_python_interpreter = self.ui.lineEditGRPythonInterpreter.text()
self.refresh_device_tab()
@pyqtSlot()
def on_btn_add_label_type_clicked(self):
suffix = 1
field_type_names = {ft.caption for ft in self.field_type_table_model.field_types}
while "New Fieldtype #" + str(suffix) in field_type_names:
suffix += 1
caption = "New Fieldtype #" + str(suffix)
self.field_type_table_model.field_types.append(FieldType(caption, FieldType.Function.CUSTOM))
self.field_type_table_model.update()
@pyqtSlot()
def on_btn_remove_label_type_clicked(self):
if self.field_type_table_model.field_types:
selected_indices = {i.row() for i in self.ui.tblLabeltypes.selectedIndexes()}
if selected_indices:
for i in reversed(sorted(selected_indices)):
self.field_type_table_model.field_types.pop(i)
else:
self.field_type_table_model.field_types.pop()
self.field_type_table_model.update()
@pyqtSlot()
def on_double_spinbox_ram_threshold_value_changed(self):
val = self.ui.doubleSpinBoxRAMThreshold.value()
settings.write("ram_threshold", val / 100)
@pyqtSlot(bool)
def on_checkbox_confirm_close_dialog_clicked(self, checked: bool):
settings.write("not_show_close_dialog", not checked)
@pyqtSlot(int)
def on_combo_box_theme_index_changed(self, index: int):
settings.write('theme_index', index)
@pyqtSlot(int)
def on_combobox_icon_theme_index_changed(self, index: int):
settings.write('icon_theme_index', index)
util.set_icon_theme()
@pyqtSlot(bool)
def on_checkbox_hold_shift_to_drag_clicked(self, checked: bool):
settings.write("hold_shift_to_drag", checked)
@pyqtSlot(bool)
def on_checkbox_default_fuzzing_pause_clicked(self, checked: bool):
settings.write('use_default_fuzzing_pause', checked)
self.ui.doubleSpinBoxFuzzingPause.setEnabled(checked)
@pyqtSlot(float)
def on_spinbox_fuzzing_pause_value_changed(self, value: float):
settings.write("default_fuzzing_pause", int(value))
@pyqtSlot()
def on_gr_python_interpreter_path_edited(self):
self.set_gnuradio_status()
@pyqtSlot()
def on_btn_choose_gr_python_interpreter_clicked(self):
if sys.platform == "win32":
dialog_filter = "Executable (*.exe);;All files (*.*)"
else:
dialog_filter = ""
filename, _ = QFileDialog.getOpenFileName(self, self.tr("Choose python interpreter"), filter=dialog_filter)
if filename:
self.ui.lineEditGRPythonInterpreter.setText(filename)
self.set_gnuradio_status()
@pyqtSlot(bool)
def on_checkbox_align_labels_clicked(self, checked: bool):
settings.write("align_labels", checked)
@pyqtSlot()
def on_btn_rebuild_native_clicked(self):
library_dirs = None if not self.ui.lineEditLibDirs.text() \
else list(map(str.strip, self.ui.lineEditLibDirs.text().split(",")))
include_dirs = None if not self.ui.lineEditIncludeDirs.text() \
else list(map(str.strip, self.ui.lineEditIncludeDirs.text().split(",")))
extensions, _ = ExtensionHelper.get_device_extensions_and_extras(library_dirs=library_dirs, include_dirs=include_dirs)
self.ui.labelRebuildNativeStatus.setText(self.tr("Rebuilding device extensions..."))
QApplication.instance().processEvents()
build_cmd = [sys.executable, os.path.realpath(ExtensionHelper.__file__),
"build_ext", "--inplace", "-t", tempfile.gettempdir()]
if library_dirs:
build_cmd.extend(["-L", ":".join(library_dirs)])
if include_dirs:
build_cmd.extend(["-I", ":".join(include_dirs)])
subprocess.call([sys.executable, os.path.realpath(ExtensionHelper.__file__), "clean", "--all"])
p = subprocess.Popen(build_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
num_dots = 1
while p.poll() is None:
self.ui.labelRebuildNativeStatus.setText(self.tr("Rebuilding device extensions" + ". " * num_dots))
QApplication.instance().processEvents()
time.sleep(0.1)
num_dots %= 10
num_dots += 1
rc = p.returncode
if rc == 0:
self.ui.labelRebuildNativeStatus.setText(self.tr("<font color=green>"
"Rebuilt {0} device extensions. "
"</font>"
"Please restart URH.".format(len(extensions))))
else:
self.ui.labelRebuildNativeStatus.setText(self.tr("<font color='red'>"
"Failed to rebuild {0} device extensions. "
"</font>"
"Run URH as root (<b>sudo urh</b>) "
"and try again.".format(len(extensions))))
self.build_log = p.stdout.read().decode()
self.ui.btnViewBuildLog.show()
@pyqtSlot()
def on_checkbox_multiple_modulations_clicked(self):
settings.write("multiple_modulations", self.ui.checkBoxMultipleModulations.isChecked())
@pyqtSlot()
def on_btn_view_build_log_clicked(self):
if not self.build_log:
return
dialog = util.create_textbox_dialog(self.build_log, "Build log", parent=self)
dialog.show()
@pyqtSlot(str)
def on_label_device_missing_info_link_activated(self, link: str):
if link == "health_check":
info = ExtensionHelper.perform_health_check()
info += "\n" + BackendHandler.perform_soundcard_health_check()
if util.get_shared_library_path():
if sys.platform == "win32":
info += "\n\n[INFO] Used DLLs from " + util.get_shared_library_path()
else:
info += "\n\n[INFO] Used shared libraries from " + util.get_shared_library_path()
d = util.create_textbox_dialog(info, "Health check for native extensions", self)
d.show()
@pyqtSlot()
def on_spin_box_font_size_editing_finished(self):
settings.write("font_size", self.ui.spinBoxFontSize.value())
font = qApp.font()
font.setPointSize(self.ui.spinBoxFontSize.value())
qApp.setFont(font)
@pyqtSlot(bool)
def on_radio_button_high_modulation_accuracy_clicked(self, checked):
if checked:
settings.write("modulation_dtype", "float32")
@pyqtSlot(bool)
def on_radio_button_medium_modulation_accuracy_clicked(self, checked):
if checked:
settings.write("modulation_dtype", "int16")
@pyqtSlot(bool)
def on_radio_button_low_modulation_accuracy_clicked(self, checked):
if checked:
settings.write("modulation_dtype", "int8")
@staticmethod
def write_default_options():
keys = settings.all_keys()
if 'default_view' not in keys:
settings.write('default_view', 0)
if 'num_sending_repeats' not in keys:
settings.write('num_sending_repeats', 0)
if 'show_pause_as_time' not in keys:
settings.write('show_pause_as_time', False)
settings.sync() # Ensure conf dir is created to have field types in place
if not os.path.isfile(settings.FIELD_TYPE_SETTINGS):
FieldType.save_to_xml(FieldType.default_field_types())
bh = BackendHandler()
for be in bh.device_backends.values():
be.write_settings()

View File

@ -0,0 +1,187 @@
import os
from PyQt5.QtCore import QRegExp, Qt
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QRegExpValidator, QCloseEvent
from PyQt5.QtWidgets import QDialog, QCompleter, QDirModel
from urh import settings
from urh.controller.dialogs.SpectrumDialogController import SpectrumDialogController
from urh.dev import config
from urh.models.ParticipantTableModel import ParticipantTableModel
from urh.signalprocessing.Participant import Participant
from urh.ui.ui_project import Ui_ProjectDialog
from urh.util import FileOperator
from urh.util.Errors import Errors
from urh.util.ProjectManager import ProjectManager
class ProjectDialog(QDialog):
def __init__(self, new_project=True, project_manager: ProjectManager = None, parent=None):
super().__init__(parent)
if not new_project:
assert project_manager is not None
self.ui = Ui_ProjectDialog()
self.ui.setupUi(self)
self.setWindowFlags(Qt.Window)
if new_project:
self.participant_table_model = ParticipantTableModel([])
else:
self.participant_table_model = ParticipantTableModel(project_manager.participants)
self.ui.spinBoxSampleRate.setValue(project_manager.device_conf["sample_rate"])
self.ui.spinBoxFreq.setValue(project_manager.device_conf["frequency"])
self.ui.spinBoxBandwidth.setValue(project_manager.device_conf["bandwidth"])
self.ui.spinBoxGain.setValue(project_manager.device_conf.get("gain", config.DEFAULT_GAIN))
self.ui.txtEdDescription.setPlainText(project_manager.description)
self.ui.lineEdit_Path.setText(project_manager.project_path)
self.ui.lineEditBroadcastAddress.setText(project_manager.broadcast_address_hex)
self.ui.btnSelectPath.hide()
self.ui.lineEdit_Path.setDisabled(True)
self.setWindowTitle("Edit project settings")
self.ui.lNewProject.setText("Edit project")
self.ui.tblParticipants.setModel(self.participant_table_model)
self.participant_table_model.update()
self.ui.lineEditBroadcastAddress.setValidator(QRegExpValidator(QRegExp("([a-fA-F ]|[0-9]){,}")))
self.sample_rate = self.ui.spinBoxSampleRate.value()
self.freq = self.ui.spinBoxFreq.value()
self.bandwidth = self.ui.spinBoxBandwidth.value()
self.gain = self.ui.spinBoxGain.value()
self.description = self.ui.txtEdDescription.toPlainText()
self.broadcast_address_hex = self.ui.lineEditBroadcastAddress.text()
self.path = self.ui.lineEdit_Path.text()
self.new_project = new_project
self.committed = False
self.setModal(True)
completer = QCompleter()
completer.setModel(QDirModel(completer))
self.ui.lineEdit_Path.setCompleter(completer)
self.create_connects()
# add two participants
if self.participant_table_model.rowCount() == 0 and new_project:
self.ui.btnAddParticipant.click()
self.ui.btnAddParticipant.click()
if new_project:
self.ui.lineEdit_Path.setText(os.path.realpath(os.path.join(os.curdir, "new")))
self.on_line_edit_path_text_edited()
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
@property
def participants(self):
"""
:rtype: list of Participant
"""
return self.participant_table_model.participants
def create_connects(self):
self.ui.spinBoxFreq.valueChanged.connect(self.on_spin_box_frequency_value_changed)
self.ui.spinBoxSampleRate.valueChanged.connect(self.on_spin_box_sample_rate_value_changed)
self.ui.spinBoxBandwidth.valueChanged.connect(self.on_spin_box_bandwidth_value_changed)
self.ui.spinBoxGain.valueChanged.connect(self.on_spin_box_gain_value_changed)
self.ui.txtEdDescription.textChanged.connect(self.on_txt_edit_description_text_changed)
self.ui.lineEditBroadcastAddress.textEdited.connect(self.on_line_edit_broadcast_address_text_edited)
self.ui.btnAddParticipant.clicked.connect(self.ui.tblParticipants.on_add_action_triggered)
self.ui.btnRemoveParticipant.clicked.connect(self.ui.tblParticipants.on_remove_action_triggered)
self.ui.btnUp.clicked.connect(self.ui.tblParticipants.on_move_up_action_triggered)
self.ui.btnDown.clicked.connect(self.ui.tblParticipants.on_move_down_action_triggered)
self.ui.lineEdit_Path.textEdited.connect(self.on_line_edit_path_text_edited)
self.ui.buttonBox.accepted.connect(self.on_button_box_accepted)
self.ui.buttonBox.rejected.connect(self.reject)
self.ui.btnSelectPath.clicked.connect(self.on_btn_select_path_clicked)
self.ui.lOpenSpectrumAnalyzer.linkActivated.connect(self.on_spectrum_analyzer_link_activated)
def set_path(self, path):
self.path = path
self.ui.lineEdit_Path.setText(self.path)
name = os.path.basename(os.path.normpath(self.path))
self.ui.lblName.setText(name)
self.ui.lblNewPath.setVisible(not os.path.isdir(self.path))
def closeEvent(self, event: QCloseEvent):
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
super().closeEvent(event)
@pyqtSlot(float)
def on_spin_box_sample_rate_value_changed(self, value: float):
self.sample_rate = value
@pyqtSlot(float)
def on_spin_box_frequency_value_changed(self, value: float):
self.freq = value
@pyqtSlot(float)
def on_spin_box_bandwidth_value_changed(self, value: float):
self.bandwidth = value
@pyqtSlot(int)
def on_spin_box_gain_value_changed(self, value: int):
self.gain = value
@pyqtSlot()
def on_line_edit_path_text_edited(self):
self.set_path(self.ui.lineEdit_Path.text())
@pyqtSlot()
def on_txt_edit_description_text_changed(self):
self.description = self.ui.txtEdDescription.toPlainText()
@pyqtSlot()
def on_button_box_accepted(self):
self.path = os.path.realpath(self.path)
if not os.path.exists(self.path):
try:
os.makedirs(self.path)
except Exception:
pass
# Path should be created now, if not raise Error
if not os.path.exists(self.path):
Errors.invalid_path(self.path)
return
self.committed = True
self.accept()
@pyqtSlot(str)
def on_line_edit_broadcast_address_text_edited(self, value: str):
self.broadcast_address_hex = value
@pyqtSlot()
def on_btn_select_path_clicked(self):
directory = FileOperator.get_directory()
if directory:
self.set_path(directory)
@pyqtSlot(dict)
def set_recording_params_from_spectrum_analyzer_link(self, args: dict):
self.ui.spinBoxFreq.setValue(args["frequency"])
self.ui.spinBoxSampleRate.setValue(args["sample_rate"])
self.ui.spinBoxBandwidth.setValue(args["bandwidth"])
self.ui.spinBoxGain.setValue(args.get("gain", config.DEFAULT_GAIN))
@pyqtSlot(str)
def on_spectrum_analyzer_link_activated(self, link: str):
if link == "open_spectrum_analyzer":
r = SpectrumDialogController(ProjectManager(None), parent=self)
if r.has_empty_device_list:
Errors.no_device()
r.close()
return
r.device_parameters_changed.connect(self.set_recording_params_from_spectrum_analyzer_link)
r.show()

View File

@ -0,0 +1,128 @@
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
from PyQt5.QtGui import QKeyEvent, QCloseEvent
from PyQt5.QtWidgets import QDialog, QHeaderView, QAbstractItemView
from urh import settings
from urh.controller.widgets.ChecksumWidget import ChecksumWidget
from urh.models.PLabelTableModel import PLabelTableModel
from urh.signalprocessing.ChecksumLabel import ChecksumLabel
from urh.signalprocessing.FieldType import FieldType
from urh.signalprocessing.Message import Message
from urh.signalprocessing.MessageType import MessageType
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
from urh.simulator.SimulatorProtocolLabel import SimulatorProtocolLabel
from urh.ui.delegates.CheckBoxDelegate import CheckBoxDelegate
from urh.ui.delegates.ComboBoxDelegate import ComboBoxDelegate
from urh.ui.delegates.SpinBoxDelegate import SpinBoxDelegate
from urh.ui.ui_properties_dialog import Ui_DialogLabels
from urh.util import util
from urh.util.Logger import logger
class ProtocolLabelDialog(QDialog):
apply_decoding_changed = pyqtSignal(ProtocolLabel, MessageType)
SPECIAL_CONFIG_TYPES = [FieldType.Function.CHECKSUM]
def __init__(self, message: Message, viewtype: int, selected_index=None, parent=None):
super().__init__(parent)
self.ui = Ui_DialogLabels()
self.ui.setupUi(self)
util.set_splitter_stylesheet(self.ui.splitter)
field_types = FieldType.load_from_xml()
self.model = PLabelTableModel(message, field_types)
self.ui.tblViewProtoLabels.setItemDelegateForColumn(0, ComboBoxDelegate([ft.caption for ft in field_types],
is_editable=True,
return_index=False, parent=self))
self.ui.tblViewProtoLabels.setItemDelegateForColumn(1, SpinBoxDelegate(1, len(message), self))
self.ui.tblViewProtoLabels.setItemDelegateForColumn(2, SpinBoxDelegate(1, len(message), self))
self.ui.tblViewProtoLabels.setItemDelegateForColumn(3,
ComboBoxDelegate([""] * len(settings.LABEL_COLORS),
colors=settings.LABEL_COLORS,
parent=self))
self.ui.tblViewProtoLabels.setItemDelegateForColumn(4, CheckBoxDelegate(self))
self.ui.tblViewProtoLabels.setModel(self.model)
self.ui.tblViewProtoLabels.setEditTriggers(QAbstractItemView.AllEditTriggers)
self.ui.tblViewProtoLabels.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.ui.tblViewProtoLabels.resizeColumnsToContents()
self.setWindowFlags(Qt.Window)
self.setWindowTitle(self.tr("Edit Protocol Labels From Message Type %s") % message.message_type.name)
self.configure_special_config_tabs()
self.ui.splitter.setSizes([int(self.height() / 2), int(self.height() / 2)])
self.create_connects()
if selected_index is not None:
self.ui.tblViewProtoLabels.setCurrentIndex(self.model.index(selected_index, 0))
self.ui.cbProtoView.setCurrentIndex(viewtype)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.Window)
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
for i in range(self.model.rowCount()):
self.open_editors(i)
def configure_special_config_tabs(self):
self.ui.tabWidgetAdvancedSettings.clear()
for lbl in self.model.message_type: # type: ProtocolLabel
if isinstance(lbl, SimulatorProtocolLabel):
lbl = lbl.label
if lbl.field_type is not None and lbl.field_type.function in self.SPECIAL_CONFIG_TYPES:
if isinstance(lbl, ChecksumLabel):
w = ChecksumWidget(lbl, self.model.message, self.model.proto_view)
self.ui.tabWidgetAdvancedSettings.addTab(w, lbl.name)
else:
logger.error("No Special Config Dialog for field type " + lbl.field_type.caption)
if self.ui.tabWidgetAdvancedSettings.count() > 0:
self.ui.tabWidgetAdvancedSettings.setCurrentIndex(0)
self.ui.tabWidgetAdvancedSettings.setFocus()
self.ui.groupBoxAdvancedSettings.setVisible(self.ui.tabWidgetAdvancedSettings.count() > 0)
def create_connects(self):
self.ui.btnConfirm.clicked.connect(self.confirm)
self.ui.cbProtoView.currentIndexChanged.connect(self.set_view_index)
self.model.apply_decoding_changed.connect(self.on_apply_decoding_changed)
self.model.special_status_label_changed.connect(self.on_label_special_status_changed)
def open_editors(self, row):
self.ui.tblViewProtoLabels.openPersistentEditor(self.model.index(row, 4))
def keyPressEvent(self, event: QKeyEvent):
if event.key() == Qt.Key_Enter:
event.ignore()
else:
event.accept()
def closeEvent(self, event: QCloseEvent):
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
super().closeEvent(event)
@pyqtSlot()
def confirm(self):
self.close()
@pyqtSlot(int)
def set_view_index(self, ind):
self.model.proto_view = ind
self.model.update()
for i in range(self.ui.tabWidgetAdvancedSettings.count()):
self.ui.tabWidgetAdvancedSettings.widget(i).proto_view = ind
@pyqtSlot(ProtocolLabel)
def on_apply_decoding_changed(self, lbl: ProtocolLabel):
self.apply_decoding_changed.emit(lbl, self.model.message_type)
@pyqtSlot(ProtocolLabel)
def on_label_special_status_changed(self, lbl: ProtocolLabel):
self.configure_special_config_tabs()

View File

@ -0,0 +1,138 @@
import numpy as np
from PyQt5.QtCore import pyqtSlot, pyqtSignal
from PyQt5.QtGui import QIcon, QCloseEvent
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
from urh.controller.widgets.SniffSettingsWidget import SniffSettingsWidget
from urh.ui.painting.LiveSceneManager import LiveSceneManager
from urh.ui.painting.SniffSceneManager import SniffSceneManager
from urh.util import util
class ProtocolSniffDialog(SendRecvDialog):
protocol_accepted = pyqtSignal(list)
def __init__(self, project_manager, signal=None, signals=None, parent=None, testing_mode=False):
super().__init__(project_manager, is_tx=False, parent=parent, testing_mode=testing_mode)
self.graphics_view = self.ui.graphicsView_sniff_Preview
self.ui.stackedWidget.setCurrentWidget(self.ui.page_sniff)
self.hide_send_ui_items()
self.hide_receive_ui_items()
self.ui.sliderYscale.hide()
self.ui.label_y_scale.hide()
signals = [] if signals is None else signals
self.sniff_settings_widget = SniffSettingsWidget(project_manager=project_manager,
device_name=self.selected_device_name,
signal=signal, signals=signals,
backend_handler=self.backend_handler)
self.ui.scrollAreaWidgetContents_2.layout().insertWidget(1, self.sniff_settings_widget)
self.sniff_settings_widget.ui.btn_sniff_use_signal.setAutoDefault(False)
self.sniffer = self.sniff_settings_widget.sniffer
self.setWindowTitle(self.tr("Sniff Protocol"))
self.setWindowIcon(QIcon.fromTheme(":/icons/icons/sniffer.svg"))
self.ui.txtEd_sniff_Preview.setFont(util.get_monospace_font())
# set really in on_device_started
self.scene_manager = None # type: LiveSceneManager
self.create_connects()
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
@property
def view_type(self) -> int:
return self.sniff_settings_widget.ui.comboBox_sniff_viewtype.currentIndex()
@property
def show_timestamp(self) -> bool:
return self.sniff_settings_widget.ui.checkBox_sniff_Timestamp.isChecked()
def closeEvent(self, event: QCloseEvent):
self.sniff_settings_widget.emit_sniff_parameters_changed()
super().closeEvent(event)
def create_connects(self):
super().create_connects()
self.ui.btnAccept.clicked.connect(self.on_btn_accept_clicked)
self.sniff_settings_widget.sniff_parameters_changed.connect(self.device_parameters_changed.emit)
self.sniff_settings_widget.sniff_setting_edited.connect(self.on_sniff_setting_edited)
self.sniff_settings_widget.sniff_file_edited.connect(self.on_sniff_file_edited)
self.sniffer.message_sniffed.connect(self.on_message_sniffed)
self.sniffer.qt_signals.sniff_device_errors_changed.connect(self.on_device_errors_changed)
def init_device(self):
self.sniffer.device_name = self.selected_device_name
self.device = self.sniffer.rcv_device
self._create_device_connects()
self.scene_manager = SniffSceneManager(np.array([], dtype=self.device.data_type), parent=self)
def emit_editing_finished_signals(self):
super().emit_editing_finished_signals()
self.sniff_settings_widget.emit_editing_finished_signals()
def update_view(self):
if super().update_view():
self.scene_manager.end = self.device.current_index
self.scene_manager.init_scene()
self.scene_manager.show_full_scene()
self.graphics_view.update()
@pyqtSlot()
def on_device_started(self):
self.scene_manager.data_array = self.device.data.real if hasattr(self.device.data, "real") else None
super().on_device_started()
self.ui.btnStart.setEnabled(False)
self.set_device_ui_items_enabled(False)
@pyqtSlot()
def on_sniff_setting_edited(self):
self.ui.txtEd_sniff_Preview.setPlainText(self.sniffer.decoded_to_string(self.view_type,
include_timestamps=self.show_timestamp))
@pyqtSlot()
def on_start_clicked(self):
super().on_start_clicked()
self.sniffer.sniff()
@pyqtSlot()
def on_stop_clicked(self):
self.sniffer.stop()
@pyqtSlot()
def on_clear_clicked(self):
self.ui.txtEd_sniff_Preview.clear()
self.scene_manager.clear_path()
self.device.current_index = 0
self.sniffer.clear()
@pyqtSlot(int)
def on_message_sniffed(self, index: int):
try:
msg = self.sniffer.messages[index]
except IndexError:
return
new_data = self.sniffer.message_to_string(msg, self.view_type, include_timestamps=self.show_timestamp)
if new_data.strip():
self.ui.txtEd_sniff_Preview.appendPlainText(new_data)
self.ui.txtEd_sniff_Preview.verticalScrollBar().setValue(
self.ui.txtEd_sniff_Preview.verticalScrollBar().maximum())
@pyqtSlot()
def on_btn_accept_clicked(self):
self.protocol_accepted.emit(self.sniffer.messages)
self.close()
@pyqtSlot(str)
def on_device_errors_changed(self, txt: str):
self.ui.txtEditErrors.append(txt)
@pyqtSlot()
def on_sniff_file_edited(self):
self.ui.btnAccept.setDisabled(bool(self.sniffer.sniff_file))

View File

@ -0,0 +1,110 @@
import numpy as np
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QMessageBox
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
from urh.dev.VirtualDevice import Mode, VirtualDevice
from urh.ui.painting.LiveSceneManager import LiveSceneManager
from urh.util import FileOperator
from urh.util.Formatter import Formatter
from datetime import datetime
class ReceiveDialog(SendRecvDialog):
files_recorded = pyqtSignal(list, float)
def __init__(self, project_manager, parent=None, testing_mode=False):
try:
super().__init__(project_manager, is_tx=False, parent=parent, testing_mode=testing_mode)
except ValueError:
return
self.graphics_view = self.ui.graphicsViewReceive
self.ui.stackedWidget.setCurrentWidget(self.ui.page_receive)
self.hide_send_ui_items()
self.already_saved = True
self.recorded_files = []
self.setWindowTitle("Record Signal")
self.setWindowIcon(QIcon.fromTheme("media-record"))
# set really in on_device_started
self.scene_manager = None # type: LiveSceneManager
self.create_connects()
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
def create_connects(self):
super().create_connects()
self.ui.btnSave.clicked.connect(self.on_save_clicked)
def save_before_close(self):
if not self.already_saved and self.device.current_index > 0:
reply = QMessageBox.question(self, self.tr("Save data?"),
self.tr("Do you want to save the data you have captured so far?"),
QMessageBox.Yes | QMessageBox.No | QMessageBox.Abort)
if reply == QMessageBox.Yes:
self.on_save_clicked()
elif reply == QMessageBox.Abort:
return False
try:
sample_rate = self.device.sample_rate
except:
sample_rate = 1e6
self.files_recorded.emit(self.recorded_files, sample_rate)
return True
def update_view(self):
if super().update_view():
self.scene_manager.end = self.device.current_index
self.scene_manager.init_scene()
self.scene_manager.show_full_scene()
self.graphics_view.update()
def init_device(self):
self.device = VirtualDevice(self.backend_handler, self.selected_device_name, Mode.receive,
device_ip="192.168.10.2", parent=self)
self._create_device_connects()
self.scene_manager = LiveSceneManager(np.array([], dtype=self.device.data_type), parent=self)
@pyqtSlot()
def on_start_clicked(self):
super().on_start_clicked()
self.device.start()
@pyqtSlot()
def on_device_started(self):
self.scene_manager.plot_data = self.device.data.real if self.device.data is not None else None
super().on_device_started()
self.already_saved = False
self.ui.btnStart.setEnabled(False)
self.set_device_ui_items_enabled(False)
@pyqtSlot()
def on_clear_clicked(self):
self.scene_manager.clear_path()
self.reset()
@pyqtSlot()
def on_save_clicked(self):
data = self.device.data[:self.device.current_index]
dev = self.device
big_val = Formatter.big_value_with_suffix
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
initial_name = "{0}-{1}-{2}Hz-{3}Sps".format(dev.name, timestamp,
big_val(dev.frequency), big_val(dev.sample_rate))
if dev.bandwidth_is_adjustable:
initial_name += "-{}Hz".format(big_val(dev.bandwidth))
initial_name = initial_name.replace(Formatter.local_decimal_seperator(), "_").replace("_000", "")
filename = FileOperator.ask_signal_file_name_and_save(initial_name, data,
sample_rate=dev.sample_rate, parent=self)
self.already_saved = True
if filename is not None and filename not in self.recorded_files:
self.recorded_files.append(filename)

View File

@ -0,0 +1,153 @@
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QBrush, QColor, QIcon, QPen
from PyQt5.QtWidgets import QMessageBox
from urh import settings
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
from urh.dev.VirtualDevice import VirtualDevice, Mode
from urh.signalprocessing.IQArray import IQArray
from urh.signalprocessing.Signal import Signal
from urh.ui.painting.SignalSceneManager import SignalSceneManager
from urh.util import FileOperator
from urh.util.Logger import logger
class SendDialog(SendRecvDialog):
def __init__(self, project_manager, modulated_data, modulation_msg_indices=None, continuous_send_mode=False,
parent=None, testing_mode=False):
super().__init__(project_manager, is_tx=True, continuous_send_mode=continuous_send_mode,
parent=parent, testing_mode=testing_mode)
self.graphics_view = self.ui.graphicsViewSend
self.ui.stackedWidget.setCurrentWidget(self.ui.page_send)
self.hide_receive_ui_items()
self.ui.btnStart.setIcon(QIcon.fromTheme("media-playback-start"))
self.setWindowTitle("Send Signal")
self.setWindowIcon(QIcon.fromTheme("media-playback-start"))
self.ui.btnStart.setToolTip("Send data")
self.ui.btnStop.setToolTip("Stop sending")
self.device_is_sending = False
self.modulation_msg_indices = modulation_msg_indices
if self.modulation_msg_indices is not None:
self.ui.progressBarMessage.setMaximum(len(self.modulation_msg_indices))
else:
self.ui.progressBarMessage.hide()
self.ui.labelCurrentMessage.hide()
if modulated_data is not None:
assert isinstance(modulated_data, IQArray)
# modulated_data is none in continuous send mode
self.ui.progressBarSample.setMaximum(len(modulated_data))
samp_rate = self.device_settings_widget.ui.spinBoxSampleRate.value()
signal = Signal("", "Modulated Preview", sample_rate=samp_rate)
signal.iq_array = modulated_data
self.scene_manager = SignalSceneManager(signal, parent=self)
self.send_indicator = self.scene_manager.scene.addRect(0, -2, 0, 4,
QPen(QColor(Qt.transparent), 0),
QBrush(settings.SEND_INDICATOR_COLOR))
self.send_indicator.stackBefore(self.scene_manager.scene.selection_area)
self.scene_manager.init_scene()
self.graphics_view.set_signal(signal)
self.graphics_view.sample_rate = samp_rate
self.create_connects()
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
def create_connects(self):
super().create_connects()
self.graphics_view.save_as_clicked.connect(self.on_graphics_view_save_as_clicked)
self.scene_manager.signal.data_edited.connect(self.on_signal_data_edited)
def _update_send_indicator(self, width: int):
y, h = self.ui.graphicsViewSend.view_rect().y(), self.ui.graphicsViewSend.view_rect().height()
self.send_indicator.setRect(0, y - h, width, 2 * h + abs(y))
def set_current_message_progress_bar_value(self, current_sample: int):
if self.modulation_msg_indices is not None:
msg_index = next((i for i, sample in enumerate(self.modulation_msg_indices) if sample >= current_sample),
len(self.modulation_msg_indices))
self.ui.progressBarMessage.setValue(msg_index + 1)
def update_view(self):
if super().update_view():
self._update_send_indicator(self.device.current_index)
self.ui.progressBarSample.setValue(self.device.current_index)
self.set_current_message_progress_bar_value(self.device.current_index)
if not self.device.sending_finished:
self.ui.lblCurrentRepeatValue.setText(str(self.device.current_iteration + 1))
else:
self.ui.btnStop.click()
self.ui.lblCurrentRepeatValue.setText("Sending finished")
def init_device(self):
device_name = self.selected_device_name
num_repeats = self.device_settings_widget.ui.spinBoxNRepeat.value()
sts = self.scene_manager.signal.iq_array
self.device = VirtualDevice(self.backend_handler, device_name, Mode.send, samples_to_send=sts,
device_ip="192.168.10.2", sending_repeats=num_repeats, parent=self)
self._create_device_connects()
@pyqtSlot()
def on_graphics_view_save_as_clicked(self):
filename = FileOperator.ask_save_file_name("signal.complex")
if filename:
try:
try:
self.scene_manager.signal.sample_rate = self.device.sample_rate
except Exception as e:
logger.exception(e)
self.scene_manager.signal.save_as(filename)
except Exception as e:
QMessageBox.critical(self, self.tr("Error saving signal"), e.args[0])
@pyqtSlot()
def on_signal_data_edited(self):
signal = self.scene_manager.signal
self.ui.progressBarSample.setMaximum(signal.num_samples)
self.device.samples_to_send = signal.iq_array.data
self.scene_manager.init_scene()
self.ui.graphicsViewSend.redraw_view()
@pyqtSlot()
def on_start_clicked(self):
super().on_start_clicked()
if self.ui.progressBarSample.value() >= self.ui.progressBarSample.maximum() - 1:
self.on_clear_clicked()
if self.device_is_sending:
self.device.stop("Sending paused by user")
else:
self.device.start()
@pyqtSlot()
def on_stop_clicked(self):
super().on_stop_clicked()
self.on_clear_clicked()
@pyqtSlot()
def on_device_stopped(self):
super().on_device_stopped()
self.ui.btnStart.setIcon(QIcon.fromTheme("media-playback-start"))
self.ui.btnStart.setText("Start")
self.ui.btnStart.setToolTip("Start sending")
self.device_is_sending = False
@pyqtSlot()
def on_device_started(self):
super().on_device_started()
self.device_is_sending = True
self.ui.btnStart.setEnabled(True)
self.ui.btnStart.setIcon(QIcon.fromTheme("media-playback-pause"))
self.ui.btnStart.setText("Pause")
self.set_device_ui_items_enabled(False)
@pyqtSlot()
def on_clear_clicked(self):
self._update_send_indicator(0)
self.reset()

View File

@ -0,0 +1,287 @@
import locale
import time
from PyQt5.QtCore import pyqtSlot, QTimer, pyqtSignal, Qt
from PyQt5.QtGui import QCloseEvent, QTransform
from PyQt5.QtWidgets import QDialog, QGraphicsView
from urh import settings
from urh.controller.widgets.DeviceSettingsWidget import DeviceSettingsWidget
from urh.dev.BackendHandler import BackendHandler, Backends
from urh.dev.VirtualDevice import VirtualDevice
from urh.plugins.NetworkSDRInterface.NetworkSDRInterfacePlugin import NetworkSDRInterfacePlugin
from urh.ui.ui_send_recv import Ui_SendRecvDialog
from urh.util import util
from urh.util.Errors import Errors
from urh.util.Formatter import Formatter
from urh.util.Logger import logger
from urh.util.ProjectManager import ProjectManager
class SendRecvDialog(QDialog):
device_parameters_changed = pyqtSignal(dict)
def __init__(self, project_manager: ProjectManager, is_tx: bool, continuous_send_mode=False, parent=None, testing_mode=False):
super().__init__(parent)
self.is_tx = is_tx
self.update_interval = 25
# This flag is needed. Will cause memory leak otherwise.
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.Window)
self.testing_mode = testing_mode
self.ui = Ui_SendRecvDialog()
self.ui.setupUi(self)
util.set_splitter_stylesheet(self.ui.splitter)
self.ui.txtEditErrors.setFont(util.get_monospace_font())
self.graphics_view = None # type: QGraphicsView
self.backend_handler = BackendHandler()
self.ui.btnStop.setEnabled(False)
self.ui.btnSave.setEnabled(False)
self.start = 0
self.device_settings_widget = DeviceSettingsWidget(project_manager, is_tx,
backend_handler=self.backend_handler,
continuous_send_mode=continuous_send_mode)
self.ui.scrollAreaWidgetContents_2.layout().insertWidget(0, self.device_settings_widget)
if testing_mode:
self.device_settings_widget.ui.cbDevice.setCurrentText(NetworkSDRInterfacePlugin.NETWORK_SDR_NAME)
self.timer = QTimer(self)
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
self.ui.splitter.setSizes([int(0.4 * self.width()), int(0.6 * self.width())])
self.current_y_slider_value = 1
@property
def is_rx(self) -> bool:
return not self.is_tx
@property
def has_empty_device_list(self):
return self.device_settings_widget.ui.cbDevice.count() == 0
@property
def device(self) -> VirtualDevice:
return self.device_settings_widget.device
@device.setter
def device(self, value):
self.device_settings_widget.device = value
@property
def selected_device_name(self) -> str:
return self.device_settings_widget.ui.cbDevice.currentText()
def _eliminate_graphic_view(self):
if self.graphics_view is not None:
self.graphics_view.eliminate()
self.graphics_view = None
def hide_send_ui_items(self):
for item in ("lblCurrentRepeatValue", "progressBarMessage",
"lblRepeatText", "lSamplesSentText", "progressBarSample", "labelCurrentMessage"):
getattr(self.ui, item).hide()
def hide_receive_ui_items(self):
for item in ("lSamplesCaptured", "lSamplesCapturedText", "lSignalSize", "lSignalSizeText",
"lTime", "lTimeText", "btnSave", "labelReceiveBufferFull", "lReceiveBufferFullText"):
getattr(self.ui, item).hide()
def set_device_ui_items_enabled(self, enabled: bool):
self.device_settings_widget.setEnabled(enabled)
def create_connects(self):
self.ui.btnStart.clicked.connect(self.on_start_clicked)
self.ui.btnStop.clicked.connect(self.on_stop_clicked)
self.ui.btnClear.clicked.connect(self.on_clear_clicked)
self.timer.timeout.connect(self.update_view)
self.ui.sliderYscale.valueChanged.connect(self.on_slider_y_scale_value_changed)
self.device_settings_widget.selected_device_changed.connect(self.on_selected_device_changed)
self.device_settings_widget.device_parameters_changed.connect(self.device_parameters_changed.emit)
def _create_device_connects(self):
self.device.stopped.connect(self.on_device_stopped)
self.device.started.connect(self.on_device_started)
self.device.sender_needs_restart.connect(self._restart_device_thread)
def reset(self):
self.device.current_index = 0
self.device.current_iteration = 0
self.ui.lSamplesCaptured.setText("0")
self.ui.lSignalSize.setText("0")
self.ui.lTime.setText("0")
self.ui.lblCurrentRepeatValue.setText("-")
self.ui.progressBarSample.setValue(0)
self.ui.progressBarMessage.setValue(0)
self.ui.btnSave.setEnabled(False)
def init_device(self):
pass
def save_before_close(self):
return True
def emit_editing_finished_signals(self):
self.device_settings_widget.emit_editing_finished_signals()
@pyqtSlot()
def on_selected_device_changed(self):
if hasattr(self.scene_manager, "plot_data"):
self.scene_manager.plot_data = None
self.init_device()
self.graphics_view.scene_manager = self.scene_manager
self.graphics_view.setScene(self.scene_manager.scene)
@pyqtSlot()
def on_start_clicked(self):
self.emit_editing_finished_signals()
@pyqtSlot()
def on_stop_clicked(self):
self.device.stop("Stopped receiving: Stop button clicked")
@pyqtSlot()
def on_device_stopped(self):
if self.graphics_view is not None:
self.graphics_view.capturing_data = False
self.set_device_ui_items_enabled(True)
self.ui.btnStart.setEnabled(True)
self.ui.btnStop.setEnabled(False)
self.ui.btnSave.setEnabled(self.device.current_index > 0)
self.device_settings_widget.ui.comboBoxDeviceIdentifier.setEnabled(True)
self.device_settings_widget.ui.btnRefreshDeviceIdentifier.setEnabled(True)
self.device_settings_widget.set_bandwidth_status()
self.timer.stop()
self.update_view()
@pyqtSlot()
def on_device_started(self):
self.ui.txtEditErrors.clear()
if self.graphics_view is not None:
self.graphics_view.capturing_data = True
self.ui.btnSave.setEnabled(False)
self.ui.btnStart.setEnabled(False)
self.ui.btnStop.setEnabled(True)
self.device_settings_widget.ui.comboBoxDeviceIdentifier.setEnabled(False)
self.device_settings_widget.ui.btnRefreshDeviceIdentifier.setEnabled(False)
self.timer.start(self.update_interval)
def __parse_error_messages(self, messages):
messages = messages.lower()
if "no devices found for" in messages:
self.device.stop_on_error("Could not establish connection to USRP")
Errors.usrp_found()
self.on_clear_clicked()
elif any(e in messages for e in ("hackrf_error_not_found", "hackrf_error_libusb")):
self.device.stop_on_error("Could not establish connection to HackRF")
Errors.hackrf_not_found()
self.on_clear_clicked()
elif "no module named gnuradio" in messages:
self.device.stop_on_error("Did not find gnuradio.")
Errors.gnuradio_not_installed()
self.on_clear_clicked()
elif "rtlsdr-open: error code: -1" in messages:
self.device.stop_on_error("Could not open a RTL-SDR device.")
self.on_clear_clicked()
elif "rtlsdr-open: error code: -12" in messages:
self.device.stop_on_error("Could not open a RTL-SDR device")
Errors.rtlsdr_sdr_driver()
self.on_clear_clicked()
elif "Address already in use" in messages:
self._restart_device_thread()
def update_view(self):
txt = self.ui.txtEditErrors.toPlainText()
new_messages = self.device.read_messages()
self.__parse_error_messages(new_messages)
if len(new_messages) > 1:
self.ui.txtEditErrors.setPlainText(txt + new_messages)
self.ui.lSamplesCaptured.setText(Formatter.big_value_with_suffix(self.device.current_index, decimals=1))
self.ui.lSignalSize.setText(locale.format_string("%.2f", (8 * self.device.current_index) / (1024 ** 2)))
self.ui.lTime.setText(locale.format_string("%.2f", self.device.current_index / self.device.sample_rate))
if self.is_rx and self.device.data is not None and len(self.device.data) > 0:
self.ui.labelReceiveBufferFull.setText("{0}%".format(int(100 * self.device.current_index /
len(self.device.data))))
if self.device.current_index == 0:
return False
return True
def _restart_device_thread(self):
self.device.stop("Restarting with new port")
if self.device.backend == Backends.grc:
self.device.increase_gr_port()
self.device.start()
@pyqtSlot()
def on_clear_clicked(self):
pass
def closeEvent(self, event: QCloseEvent):
if self.device.backend is not Backends.none:
self.emit_editing_finished_signals()
self.timer.stop()
self.device.stop("Dialog closed. Killing recording process.")
logger.debug("Device stopped successfully.")
if not self.testing_mode:
if not self.save_before_close():
event.ignore()
return
time.sleep(0.1)
if self.device.backend not in (Backends.none, Backends.network):
# Backend none is selected, when no device is available
logger.debug("Cleaning up device")
self.device.cleanup()
logger.debug("Successfully cleaned up device")
self.device_settings_widget.emit_device_parameters_changed()
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
if self.device is not None:
self.device.free_data()
self.scene_manager.eliminate()
self._eliminate_graphic_view()
super().closeEvent(event)
@pyqtSlot(int)
def on_slider_y_scale_value_changed(self, new_value: int):
self.graphics_view.scale(1, new_value / self.current_y_slider_value)
self.graphics_view.centerOn(0, 0)
self.current_y_slider_value = new_value

View File

@ -0,0 +1,54 @@
import locale
import os
import time
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import QDialog
from urh import settings
from urh.ui.ui_signal_details import Ui_SignalDetails
from urh.util.Formatter import Formatter
class SignalDetailsDialog(QDialog):
def __init__(self, signal, parent=None):
super().__init__(parent)
self.signal = signal
self.ui = Ui_SignalDetails()
self.ui.setupUi(self)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.Window)
file = self.signal.filename
self.ui.lblName.setText(self.signal.name)
if os.path.isfile(file):
self.ui.lblFile.setText(file)
self.ui.lblFileSize.setText(locale.format_string("%.2fMB", os.path.getsize(file) / (1024 ** 2)))
self.ui.lFileCreated.setText(time.ctime(os.path.getctime(file)))
else:
self.ui.lblFile.setText(self.tr("signal file not found"))
self.ui.lblFileSize.setText("-")
self.ui.lFileCreated.setText("-")
self.ui.lblSamplesTotal.setText("{0:n}".format(self.signal.num_samples).replace(",", " "))
self.ui.dsb_sample_rate.setValue(self.signal.sample_rate)
self.set_duration()
self.ui.dsb_sample_rate.valueChanged.connect(self.on_dsb_sample_rate_value_changed)
self.restoreGeometry(settings.read("{}/geometry".format(self.__class__.__name__), type=bytes))
def closeEvent(self, event: QCloseEvent):
settings.write("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
super().closeEvent(event)
@pyqtSlot(float)
def on_dsb_sample_rate_value_changed(self, value: float):
self.signal.sample_rate = value
self.set_duration()
def set_duration(self):
dur = self.signal.num_samples / self.signal.sample_rate
self.ui.lDuration.setText(Formatter.science_time(dur))

View File

@ -0,0 +1,432 @@
import time
import numpy as np
from PyQt5.QtCore import QTimer, pyqtSlot, pyqtSignal, Qt
from PyQt5.QtGui import QIcon, QCloseEvent
from PyQt5.QtWidgets import QDialog, QFileDialog, QMessageBox, QGraphicsTextItem
from urh import settings
from urh.controller.dialogs.ProtocolSniffDialog import ProtocolSniffDialog
from urh.controller.widgets.DeviceSettingsWidget import DeviceSettingsWidget
from urh.controller.widgets.ModulationSettingsWidget import ModulationSettingsWidget
from urh.controller.widgets.SniffSettingsWidget import SniffSettingsWidget
from urh.dev.BackendHandler import BackendHandler
from urh.dev.EndlessSender import EndlessSender
from urh.signalprocessing.IQArray import IQArray
from urh.simulator.Simulator import Simulator
from urh.simulator.SimulatorConfiguration import SimulatorConfiguration
from urh.ui.SimulatorScene import SimulatorScene
from urh.ui.painting.LiveSceneManager import LiveSceneManager
from urh.ui.painting.SniffSceneManager import SniffSceneManager
from urh.ui.ui_simulator_dialog import Ui_DialogSimulator
from urh.util import util, FileOperator
from urh.util.Errors import Errors
from urh.util.ProjectManager import ProjectManager
class SimulatorDialog(QDialog):
rx_parameters_changed = pyqtSignal(dict)
tx_parameters_changed = pyqtSignal(dict)
sniff_parameters_changed = pyqtSignal(dict)
open_in_analysis_requested = pyqtSignal(str)
rx_file_saved = pyqtSignal(str)
def __init__(self, simulator_config, modulators,
expression_parser, project_manager: ProjectManager, signals: list = None,
signal_tree_model=None,
parent=None):
super().__init__(parent)
self.ui = Ui_DialogSimulator()
self.ui.setupUi(self)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.Window)
self.simulator_config = simulator_config # type: SimulatorConfiguration
self.rx_needed = self.simulator_config.rx_needed
self.tx_needed = self.simulator_config.tx_needed
self.current_transcript_index = 0
self.simulator_scene = SimulatorScene(mode=1,
simulator_config=self.simulator_config)
self.ui.gvSimulator.setScene(self.simulator_scene)
self.project_manager = project_manager
self.update_interval = 25
self.timer = QTimer(self)
self.backend_handler = BackendHandler()
if self.rx_needed:
self.device_settings_rx_widget = DeviceSettingsWidget(project_manager,
is_tx=False,
backend_handler=self.backend_handler)
self.sniff_settings_widget = SniffSettingsWidget(self.device_settings_rx_widget.ui.cbDevice.currentText(),
project_manager,
signal=None,
backend_handler=self.backend_handler,
network_raw_mode=True, signals=signals)
self.device_settings_rx_widget.device = self.sniff_settings_widget.sniffer.rcv_device
self.sniff_settings_widget.ui.lineEdit_sniff_OutputFile.hide()
self.sniff_settings_widget.ui.label_sniff_OutputFile.hide()
self.sniff_settings_widget.ui.label_sniff_viewtype.hide()
self.sniff_settings_widget.ui.checkBox_sniff_Timestamp.hide()
self.sniff_settings_widget.ui.comboBox_sniff_viewtype.hide()
self.ui.scrollAreaWidgetContentsRX.layout().insertWidget(0, self.device_settings_rx_widget)
self.ui.scrollAreaWidgetContentsRX.layout().insertWidget(1, self.sniff_settings_widget)
sniffer = self.sniff_settings_widget.sniffer
self.scene_manager = SniffSceneManager(np.array([], dtype=sniffer.rcv_device.data_type), parent=self)
self.ui.graphicsViewPreview.setScene(self.scene_manager.scene)
else:
self.device_settings_rx_widget = self.sniff_settings_widget = self.scene_manager = None
self.ui.tabWidgetSimulatorSettings.setTabEnabled(1, False)
self.ui.graphicsViewPreview.hide()
self.ui.btnSaveRX.hide()
self.ui.checkBoxCaptureFullRX.hide()
sniffer = None
if self.tx_needed:
self.device_settings_tx_widget = DeviceSettingsWidget(project_manager, is_tx=True,
backend_handler=self.backend_handler,
continuous_send_mode=True)
self.device_settings_tx_widget.ui.spinBoxNRepeat.hide()
self.device_settings_tx_widget.ui.labelNRepeat.hide()
self.modulation_settings_widget = ModulationSettingsWidget(modulators, signal_tree_model=signal_tree_model,
parent=None)
self.ui.scrollAreaWidgetContentsTX.layout().insertWidget(0, self.device_settings_tx_widget)
self.ui.scrollAreaWidgetContentsTX.layout().insertWidget(1, self.modulation_settings_widget)
send_device = self.device_settings_tx_widget.ui.cbDevice.currentText()
sender = EndlessSender(self.backend_handler, send_device)
else:
self.device_settings_tx_widget = self.modulation_settings_widget = None
self.ui.tabWidgetSimulatorSettings.setTabEnabled(2, False)
sender = None
self.simulator = Simulator(self.simulator_config, modulators, expression_parser, project_manager,
sniffer=sniffer, sender=sender)
if self.device_settings_tx_widget:
self.device_settings_tx_widget.device = self.simulator.sender.device
self.update_buttons()
self.create_connects()
if self.device_settings_rx_widget:
self.device_settings_rx_widget.bootstrap(project_manager.simulator_rx_conf)
if self.device_settings_tx_widget:
self.device_settings_tx_widget.bootstrap(project_manager.simulator_tx_conf)
self.ui.textEditTranscript.setFont(util.get_monospace_font())
if settings.read('default_view', 0, int) == 1:
self.ui.radioButtonTranscriptHex.setChecked(True)
def create_connects(self):
if self.rx_needed:
self.device_settings_rx_widget.selected_device_changed.connect(self.on_selected_rx_device_changed)
self.device_settings_rx_widget.device_parameters_changed.connect(self.rx_parameters_changed.emit)
self.sniff_settings_widget.sniff_parameters_changed.connect(self.sniff_parameters_changed.emit)
self.ui.btnSaveRX.clicked.connect(self.on_btn_save_rx_clicked)
self.ui.checkBoxCaptureFullRX.clicked.connect(self.on_checkbox_capture_full_rx_clicked)
self.ui.btnTestSniffSettings.clicked.connect(self.on_btn_test_sniff_settings_clicked)
self.ui.btnOpenInAnalysis.clicked.connect(self.on_btn_open_in_analysis_clicked)
if self.tx_needed:
self.device_settings_tx_widget.selected_device_changed.connect(self.on_selected_tx_device_changed)
self.device_settings_tx_widget.device_parameters_changed.connect(self.tx_parameters_changed.emit)
self.ui.radioButtonTranscriptBit.clicked.connect(self.on_radio_button_transcript_bit_clicked)
self.ui.radioButtonTranscriptHex.clicked.connect(self.on_radio_button_transcript_hex_clicked)
self.simulator_scene.selectionChanged.connect(self.update_buttons)
self.simulator_config.items_updated.connect(self.update_buttons)
self.ui.btnLogAll.clicked.connect(self.on_btn_log_all_clicked)
self.ui.btnLogNone.clicked.connect(self.on_btn_log_none_clicked)
self.ui.btnToggleLog.clicked.connect(self.on_btn_toggle_clicked)
self.ui.btnStartStop.clicked.connect(self.on_btn_start_stop_clicked)
self.ui.btnSaveLog.clicked.connect(self.on_btn_save_log_clicked)
self.ui.btnSaveTranscript.clicked.connect(self.on_btn_save_transcript_clicked)
self.timer.timeout.connect(self.on_timer_timeout)
self.simulator.simulation_started.connect(self.on_simulation_started)
self.simulator.simulation_stopped.connect(self.on_simulation_stopped)
def update_buttons(self):
selectable_items = self.simulator_scene.selectable_items()
all_items_selected = all(item.model_item.logging_active for item in selectable_items)
any_item_selected = any(item.model_item.logging_active for item in selectable_items)
self.ui.btnToggleLog.setEnabled(len(self.simulator_scene.selectedItems()))
self.ui.btnLogAll.setEnabled(not all_items_selected)
self.ui.btnLogNone.setEnabled(any_item_selected)
def __get_full_transcript(self) -> list:
return self.simulator.transcript.get_for_all_participants(all_rounds=True,
use_bit=self.ui.radioButtonTranscriptBit.isChecked())
def update_view(self):
for device_message in filter(None, map(str.rstrip, self.simulator.device_messages())):
self.ui.textEditDevices.append(device_message)
for log_msg in filter(None, map(str.rstrip, self.simulator.read_log_messages())):
self.ui.textEditSimulation.append(log_msg)
transcript = self.__get_full_transcript()
for line in transcript[self.current_transcript_index:]:
self.ui.textEditTranscript.append(line)
self.current_transcript_index = len(transcript)
current_repeat = str(self.simulator.current_repeat + 1) if self.simulator.is_simulating else "-"
self.ui.lblCurrentRepeatValue.setText(current_repeat)
current_item = self.simulator.current_item.index() if self.simulator.is_simulating else "-"
self.ui.lblCurrentItemValue.setText(current_item)
def update_rx_graphics_view(self):
if self.scene_manager is None or not self.ui.graphicsViewPreview.isEnabled():
return
self.scene_manager.end = self.simulator.sniffer.rcv_device.current_index
self.scene_manager.init_scene()
self.scene_manager.show_full_scene()
self.ui.graphicsViewPreview.update()
def reset(self):
self.ui.textEditDevices.clear()
self.ui.textEditSimulation.clear()
self.ui.textEditTranscript.clear()
self.current_transcript_index = 0
self.ui.lblCurrentRepeatValue.setText("-")
self.ui.lblCurrentItemValue.setText("-")
def emit_editing_finished_signals(self):
if self.device_settings_rx_widget:
self.device_settings_rx_widget.emit_editing_finished_signals()
if self.device_settings_tx_widget:
self.device_settings_tx_widget.emit_editing_finished_signals()
if self.sniff_settings_widget:
self.sniff_settings_widget.emit_editing_finished_signals()
def update_transcript_view(self):
self.ui.textEditTranscript.setText("\n".join(self.__get_full_transcript()))
def closeEvent(self, event: QCloseEvent):
self.timer.stop()
self.simulator.stop()
self.simulator.cleanup()
self.emit_editing_finished_signals()
if self.device_settings_rx_widget:
self.device_settings_rx_widget.emit_device_parameters_changed()
if self.device_settings_tx_widget:
self.device_settings_tx_widget.emit_device_parameters_changed()
if self.sniff_settings_widget:
self.sniff_settings_widget.emit_sniff_parameters_changed()
super().closeEvent(event)
@pyqtSlot()
def on_simulation_started(self):
for i in range(3):
self.ui.tabWidgetSimulatorSettings.setTabEnabled(i, False)
self.ui.checkBoxCaptureFullRX.setDisabled(True)
self.reset()
self.timer.start(self.update_interval)
self.ui.btnStartStop.setIcon(QIcon.fromTheme("media-playback-stop"))
self.ui.btnStartStop.setText("Stop")
if not self.rx_needed:
return
rx_device = self.simulator.sniffer.rcv_device
for item in self.scene_manager.scene.items():
if isinstance(item, QGraphicsTextItem):
self.scene_manager.scene.removeItem(item)
if hasattr(rx_device.data, "real"):
self.ui.graphicsViewPreview.setEnabled(True)
if self.ui.checkBoxCaptureFullRX.isChecked():
self.scene_manager.plot_data = rx_device.data.real
else:
self.scene_manager.data_array = rx_device.data.real
else:
self.ui.graphicsViewPreview.setEnabled(False)
if self.ui.checkBoxCaptureFullRX.isChecked():
self.scene_manager.plot_data = np.array([], dtype=rx_device.data_type)
else:
self.scene_manager.data_array = np.array([], dtype=rx_device.data_type)
self.scene_manager.scene.addText("Could not generate RX preview.")
@pyqtSlot()
def on_simulation_stopped(self):
self.ui.tabWidgetSimulatorSettings.setTabEnabled(0, True)
self.ui.tabWidgetSimulatorSettings.setTabEnabled(1, self.rx_needed)
self.ui.tabWidgetSimulatorSettings.setTabEnabled(2, self.tx_needed)
self.timer.stop()
self.update_view()
self.ui.btnStartStop.setIcon(QIcon.fromTheme("media-playback-start"))
self.ui.btnStartStop.setText("Start")
self.ui.checkBoxCaptureFullRX.setEnabled(True)
@pyqtSlot()
def on_btn_log_all_clicked(self):
self.simulator_scene.log_all_items(True)
@pyqtSlot()
def on_btn_log_none_clicked(self):
self.simulator_scene.log_all_items(False)
@pyqtSlot()
def on_btn_toggle_clicked(self):
self.simulator_scene.log_toggle_selected_items()
@pyqtSlot()
def on_btn_save_log_clicked(self):
file_path = QFileDialog.getSaveFileName(self, "Save log", "", "Log file (*.log)")
if file_path[0] == "":
return
log_string = self.ui.textEditSimulation.toPlainText()
try:
with open(str(file_path[0]), "w") as f:
f.write(log_string)
except Exception as e:
QMessageBox.critical(self, "Error saving log", e.args[0])
@pyqtSlot()
def on_btn_save_transcript_clicked(self):
file_path = QFileDialog.getSaveFileName(self, "Save transcript", "", "Text file (*.txt)")
if file_path[0] == "":
return
transcript = self.ui.textEditTranscript.toPlainText()
try:
with open(str(file_path[0]), "w") as f:
f.write(transcript)
except Exception as e:
QMessageBox.critical(self, "Error saving transcript", e.args[0])
@pyqtSlot()
def on_btn_start_stop_clicked(self):
if self.simulator.is_simulating:
self.simulator.stop()
else:
if self.rx_needed:
self.device_settings_rx_widget.emit_editing_finished_signals()
self.sniff_settings_widget.emit_editing_finished_signals()
self.simulator.sniffer.rcv_device.current_index = 0
self.simulator.sniffer.rcv_device.resume_on_full_receive_buffer = not self.ui.checkBoxCaptureFullRX.isChecked()
if self.tx_needed:
self.device_settings_tx_widget.emit_editing_finished_signals()
self.simulator.start()
@pyqtSlot()
def on_timer_timeout(self):
self.update_view()
self.update_rx_graphics_view()
@pyqtSlot()
def on_selected_rx_device_changed(self):
dev_name = self.device_settings_rx_widget.ui.cbDevice.currentText()
self.simulator.sniffer.device_name = dev_name
self.device_settings_rx_widget.device = self.simulator.sniffer.rcv_device
self.__set_rx_scene()
@pyqtSlot()
def on_selected_tx_device_changed(self):
old_name = self.simulator.sender.device_name
try:
dev_name = self.device_settings_tx_widget.ui.cbDevice.currentText()
self.simulator.sender.device_name = dev_name
self.device_settings_tx_widget.device = self.simulator.sender.device
except Exception as e:
self.device_settings_tx_widget.ui.cbDevice.setCurrentText(old_name)
Errors.exception(e)
@pyqtSlot()
def on_btn_test_sniff_settings_clicked(self):
def on_dialog_finished():
self.device_settings_rx_widget.bootstrap(self.project_manager.simulator_rx_conf)
self.sniff_settings_widget.bootstrap(self.project_manager.device_conf)
self.device_settings_rx_widget.emit_device_parameters_changed()
self.sniff_settings_widget.emit_sniff_parameters_changed()
psd = ProtocolSniffDialog(self.project_manager, signals=self.sniff_settings_widget.signals, parent=self)
psd.device_settings_widget.bootstrap(self.project_manager.simulator_rx_conf)
psd.device_settings_widget.device_parameters_changed.connect(self.rx_parameters_changed.emit)
psd.sniff_settings_widget.sniff_parameters_changed.connect(self.sniff_parameters_changed.emit)
psd.finished.connect(on_dialog_finished)
psd.ui.btnAccept.hide()
psd.show()
@pyqtSlot()
def on_radio_button_transcript_hex_clicked(self):
self.update_transcript_view()
@pyqtSlot()
def on_radio_button_transcript_bit_clicked(self):
self.update_transcript_view()
def __set_rx_scene(self):
if not self.rx_needed:
return
if self.ui.checkBoxCaptureFullRX.isChecked():
self.scene_manager = LiveSceneManager(np.array([], dtype=self.simulator.sniffer.rcv_device.data_type),
parent=self)
self.ui.graphicsViewPreview.setScene(self.scene_manager.scene)
else:
self.scene_manager = SniffSceneManager(np.array([], dtype=self.simulator.sniffer.rcv_device.data_type),
parent=self)
self.ui.graphicsViewPreview.setScene(self.scene_manager.scene)
@pyqtSlot()
def on_checkbox_capture_full_rx_clicked(self):
self.simulator.sniffer.rcv_device.resume_on_full_receive_buffer = not self.ui.checkBoxCaptureFullRX.isChecked()
self.__set_rx_scene()
@pyqtSlot()
def on_btn_save_rx_clicked(self):
rx_device = self.simulator.sniffer.rcv_device
if isinstance(rx_device.data, np.ndarray) or isinstance(rx_device.data, IQArray):
data = IQArray(rx_device.data[:rx_device.current_index])
filename = FileOperator.ask_signal_file_name_and_save("simulation_capture", data,
sample_rate=rx_device.sample_rate, parent=self)
if filename:
data.tofile(filename)
self.rx_file_saved.emit(filename)
@pyqtSlot()
def on_btn_open_in_analysis_clicked(self):
text = self.ui.textEditTranscript.toPlainText()
if len(text) > 0:
self.open_in_analysis_requested.emit(text)
self.close()

View File

@ -0,0 +1,172 @@
from PyQt5.QtCore import QTimer, pyqtSlot
from PyQt5.QtGui import QWheelEvent, QIcon, QPixmap, QResizeEvent
from PyQt5.QtWidgets import QGraphicsScene
from urh.controller.dialogs.SendRecvDialog import SendRecvDialog
from urh.dev.VirtualDevice import VirtualDevice, Mode
from urh.signalprocessing.Spectrogram import Spectrogram
from urh.ui.painting.FFTSceneManager import FFTSceneManager
class SpectrumDialogController(SendRecvDialog):
def __init__(self, project_manager, parent=None, testing_mode=False):
super().__init__(project_manager, is_tx=False, parent=parent, testing_mode=testing_mode)
self.graphics_view = self.ui.graphicsViewFFT
self.update_interval = 1
self.ui.stackedWidget.setCurrentWidget(self.ui.page_spectrum)
self.hide_receive_ui_items()
self.hide_send_ui_items()
self.setWindowTitle("Spectrum Analyzer")
self.setWindowIcon(QIcon(":/icons/icons/spectrum.svg"))
self.ui.btnStart.setToolTip(self.tr("Start"))
self.ui.btnStop.setToolTip(self.tr("Stop"))
self.scene_manager = FFTSceneManager(parent=self, graphic_view=self.graphics_view)
self.graphics_view.setScene(self.scene_manager.scene)
self.graphics_view.scene_manager = self.scene_manager
self.ui.graphicsViewSpectrogram.setScene(QGraphicsScene())
self.__clear_spectrogram()
self.gain_timer = QTimer(self)
self.gain_timer.setSingleShot(True)
self.if_gain_timer = QTimer(self)
self.if_gain_timer.setSingleShot(True)
self.bb_gain_timer = QTimer(self)
self.bb_gain_timer.setSingleShot(True)
self.create_connects()
self.device_settings_widget.update_for_new_device(overwrite_settings=False)
def __clear_spectrogram(self):
self.ui.graphicsViewSpectrogram.scene().clear()
window_size = Spectrogram.DEFAULT_FFT_WINDOW_SIZE
self.ui.graphicsViewSpectrogram.scene().setSceneRect(0, 0, window_size, 20 * window_size)
self.spectrogram_y_pos = 0
self.ui.graphicsViewSpectrogram.fitInView(self.ui.graphicsViewSpectrogram.sceneRect())
def __update_spectrogram(self):
spectrogram = Spectrogram(self.device.data)
spectrogram.data_min = -80
spectrogram.data_max = 10
scene = self.ui.graphicsViewSpectrogram.scene()
pixmap = QPixmap.fromImage(spectrogram.create_spectrogram_image(transpose=True))
pixmap_item = scene.addPixmap(pixmap)
pixmap_item.moveBy(0, self.spectrogram_y_pos)
self.spectrogram_y_pos += pixmap.height()
if self.spectrogram_y_pos >= scene.sceneRect().height():
scene.setSceneRect(0, 0, Spectrogram.DEFAULT_FFT_WINDOW_SIZE, self.spectrogram_y_pos)
self.ui.graphicsViewSpectrogram.ensureVisible(pixmap_item)
def _eliminate_graphic_view(self):
super()._eliminate_graphic_view()
if self.ui.graphicsViewSpectrogram and self.ui.graphicsViewSpectrogram.scene() is not None:
self.ui.graphicsViewSpectrogram.scene().clear()
self.ui.graphicsViewSpectrogram.scene().setParent(None)
self.ui.graphicsViewSpectrogram.setScene(None)
self.ui.graphicsViewSpectrogram = None
def create_connects(self):
super().create_connects()
self.graphics_view.freq_clicked.connect(self.on_graphics_view_freq_clicked)
self.graphics_view.wheel_event_triggered.connect(self.on_graphics_view_wheel_event_triggered)
self.device_settings_widget.ui.sliderGain.valueChanged.connect(self.on_slider_gain_value_changed)
self.device_settings_widget.ui.sliderBasebandGain.valueChanged.connect(
self.on_slider_baseband_gain_value_changed)
self.device_settings_widget.ui.sliderIFGain.valueChanged.connect(self.on_slider_if_gain_value_changed)
self.device_settings_widget.ui.spinBoxFreq.editingFinished.connect(self.on_spinbox_frequency_editing_finished)
self.gain_timer.timeout.connect(self.device_settings_widget.ui.spinBoxGain.editingFinished.emit)
self.if_gain_timer.timeout.connect(self.device_settings_widget.ui.spinBoxIFGain.editingFinished.emit)
self.bb_gain_timer.timeout.connect(self.device_settings_widget.ui.spinBoxBasebandGain.editingFinished.emit)
def resizeEvent(self, event: QResizeEvent):
if self.ui.graphicsViewSpectrogram and self.ui.graphicsViewSpectrogram.sceneRect():
self.ui.graphicsViewSpectrogram.fitInView(self.ui.graphicsViewSpectrogram.sceneRect())
def update_view(self):
if super().update_view():
x, y = self.device.spectrum
if x is None or y is None:
return
self.scene_manager.scene.frequencies = x
self.scene_manager.plot_data = y
self.scene_manager.init_scene()
self.scene_manager.show_full_scene()
self.graphics_view.fitInView(self.graphics_view.sceneRect())
try:
self.__update_spectrogram()
except MemoryError:
self.__clear_spectrogram()
self.__update_spectrogram()
def init_device(self):
self.device = VirtualDevice(self.backend_handler, self.selected_device_name,
Mode.spectrum,
device_ip="192.168.10.2", parent=self)
self._create_device_connects()
@pyqtSlot(QWheelEvent)
def on_graphics_view_wheel_event_triggered(self, event: QWheelEvent):
self.ui.sliderYscale.wheelEvent(event)
@pyqtSlot(float)
def on_graphics_view_freq_clicked(self, freq: float):
self.device_settings_widget.ui.spinBoxFreq.setValue(freq)
self.device_settings_widget.ui.spinBoxFreq.editingFinished.emit()
@pyqtSlot()
def on_spinbox_frequency_editing_finished(self):
frequency = self.device_settings_widget.ui.spinBoxFreq.value()
self.device.frequency = frequency
self.scene_manager.scene.center_freq = frequency
self.scene_manager.clear_path()
self.scene_manager.clear_peak()
@pyqtSlot()
def on_start_clicked(self):
super().on_start_clicked()
self.device.start()
@pyqtSlot()
def on_device_started(self):
self.ui.graphicsViewSpectrogram.fitInView(self.ui.graphicsViewSpectrogram.scene().sceneRect())
super().on_device_started()
self.device_settings_widget.ui.spinBoxPort.setEnabled(False)
self.device_settings_widget.ui.lineEditIP.setEnabled(False)
self.device_settings_widget.ui.cbDevice.setEnabled(False)
self.ui.btnStart.setEnabled(False)
@pyqtSlot()
def on_device_stopped(self):
self.device_settings_widget.ui.spinBoxPort.setEnabled(True)
self.device_settings_widget.ui.lineEditIP.setEnabled(True)
self.device_settings_widget.ui.cbDevice.setEnabled(True)
super().on_device_stopped()
@pyqtSlot()
def on_clear_clicked(self):
self.__clear_spectrogram()
self.scene_manager.clear_path()
self.scene_manager.clear_peak()
@pyqtSlot(int)
def on_slider_gain_value_changed(self, value: int):
self.gain_timer.start(250)
@pyqtSlot(int)
def on_slider_if_gain_value_changed(self, value: int):
self.if_gain_timer.start(250)
@pyqtSlot(int)
def on_slider_baseband_gain_value_changed(self, value: int):
self.bb_gain_timer.start(250)