mirror of
https://github.com/jopohl/urh.git
synced 2026-03-09 01:36:48 +01:00
* rename signalFunctions -> signal_functions * basic methods for auto interpretation * cythonize k means * reorder tests * remove check and update gitignore * estimate tolerance and implement score for choosing modulation type * use absdiff * remove comment * cythonize messsage segmentation * improve message segementation and add test for xavax * integrate OOK special case * add check if psk is possible * integrate xavax and improve score * improve noise detection * add test for noise detection multiple messages * improve noise detection * homematic fix: use percetange of signal length instead of num flanks * homematic has some trash at start of messages, which counts as flanks * additonally set score to 0 if found only one bit length lower 5 * calculate minimum bit length from tolerance * improve noise noise detection * refactor limit and propose new limit calculation * improve minimum bit length penalty * only increase score for mod_type if bit length surpasses a minimum * this way scoring loop later becomes easier and * score is more accurate as there is no division needed which does not scale well with the length of message vectors * remove demodulated complex files and demod live in tests * remove enocean.coco * add a new check to prevent PSK misclassification * add tolerance unit test * use z=2 for finding outlier free max in tolerance estimation * prevent numpy warnings * adapt threshold of unit test * normalize the score by dividing by plateau vector length * improve OOK segmentation: Use minimum pulse length instead pause length for reference * use 50 percentile for detecting n_digits in plateau rounding * add elektromaten integration test * improve center detection to deal with varying signal power levels * use 10% clustering for rect signal * calculate min and max of each cluster * return max(minima) + min(maxima) / 2 * improve the center aggregation, separate between modulation types * add validity checks if message can be ASK or FSK modulated * use a weighted mean for center estimation: 60/40 for high/low * improve bit length estimation: use decimal deviation for filtering * add scislo test * improve tolerance estimation: use 50 percentile + revert to normal mean for bitlength estimation * add haar wavelet transform * add median filter * rename to Wavelet * add signal generation with configurable snr for lab test * add method for testdata generation * prepare fsk test: generate messages and estimate parameters * improve performance of plateau length filtering * remove unused import * improve robustness * improve robustness * add fsk error plot * only append bit length if it surpasses minimum * fix plot title * improve noise level detection, prevent it from being too low * integrate all modulations to test * increase pause threshold for ook * improve tolerance estimation * improve noise detection: take maximum of all maxima of noise clusters * improve scoring algorithm to prevent PSK misclassify as FSK * use histogram based approach for center detection * modulation detection with wavelets * fix median filter when at end of data * integrate modulation detection with wavelets * improve robustness * improve psk parameters * improve psk threshold * improve robustness * swap psk angles for easier demod * better xticks * add message segmentation test and fix noise generation snr * add error print * update audi test * fix runtime warning * improve accuracy of center detection * avoid warning * remove unused functions * fine tune fsk fft threshold * update esaver test * improve fsk fft threshold * change test order * update enocean test signal * update enocean test signal * enhance bit length estimation: use a threshold divisor histogram * improve noise estimation: round to fourth digit * update enocean signal * consider special case if message pause is 0 * remove unused * improve noise detection * improve center detection * improve center detection * prevent warning * refactor * cythonize get_plateau_lengths * improve syntax * use c++ sort * optimize PSK threshold * optimize coverage * fix buffer types * integrate new auto detection routine * update test * remove unused stuff * fix tests * backward compat * backward compat * update test * add threshold for large signals for performance * update changelog * make algorithm more robust against short bit length outliers * make multi button for selecting auto detect options * update unittest
313 lines
15 KiB
Python
313 lines
15 KiB
Python
import array
|
|
import os
|
|
import tempfile
|
|
|
|
from PyQt5.QtCore import QDir, QPoint, Qt
|
|
from PyQt5.QtTest import QTest
|
|
|
|
from tests.QtTestCase import QtTestCase
|
|
from urh.controller.GeneratorTabController import GeneratorTabController
|
|
from urh.controller.MainController import MainController
|
|
|
|
|
|
class TestGenerator(QtTestCase):
|
|
def test_generation(self):
|
|
"""
|
|
Complex test including much functionality
|
|
1) Load a Signal
|
|
2) Set Decoding in Compareframe
|
|
3) Move with encoding to Generator
|
|
4) Generate datafile
|
|
5) Read datafile and compare with original signal
|
|
|
|
"""
|
|
# Load a Signal
|
|
self.add_signal_to_form("ask.complex")
|
|
signal_frame = self.form.signal_tab_controller.signal_frames[0]
|
|
signal_frame.ui.cbModulationType.setCurrentIndex(0) # ASK
|
|
signal_frame.ui.spinBoxInfoLen.setValue(295)
|
|
signal_frame.ui.spinBoxCenterOffset.setValue(-0.1667)
|
|
signal_frame.refresh()
|
|
signal_frame.ui.cbProtoView.setCurrentIndex(0)
|
|
|
|
proto = "1011001001011011011011011011011011001000000"
|
|
self.assertTrue(signal_frame.ui.txtEdProto.toPlainText().startswith(proto))
|
|
|
|
# Set Decoding
|
|
self.form.ui.tabWidget.setCurrentIndex(1)
|
|
cfc = self.form.compare_frame_controller
|
|
cfc.ui.cbDecoding.setCurrentIndex(1) # NRZ-I
|
|
proto_inv = cfc.proto_analyzer.decoded_proto_bits_str[0]
|
|
self.assertTrue(self.__is_inv_proto(proto, proto_inv))
|
|
|
|
# Move with encoding to generator
|
|
gframe = self.form.generator_tab_controller
|
|
gframe.ui.cbViewType.setCurrentIndex(0)
|
|
self.add_signal_to_generator(signal_index=0)
|
|
self.assertEqual(array.array("B", list(map(int, proto_inv))), gframe.table_model.display_data[0])
|
|
self.assertNotEqual(array.array("B", list(map(int, proto))), gframe.table_model.display_data[0])
|
|
|
|
# Generate Datafile
|
|
modulator = gframe.modulators[0]
|
|
modulator.modulation_type = 0
|
|
modulator.samples_per_bit = 295
|
|
buffer = gframe.prepare_modulation_buffer(gframe.total_modulated_samples, show_error=False)
|
|
modulated_data = gframe.modulate_data(buffer)
|
|
filename = os.path.join(QDir.tempPath(), "test_generator.complex")
|
|
modulated_data.tofile(filename)
|
|
|
|
# Reload datafile and see if bits match
|
|
self.form.add_signalfile(filename)
|
|
self.assertEqual(len(self.form.signal_tab_controller.signal_frames), 2)
|
|
signal_frame = self.form.signal_tab_controller.signal_frames[1]
|
|
|
|
self.assertEqual(signal_frame.signal.num_samples, 14377)
|
|
signal_frame.ui.cbProtoView.setCurrentIndex(0)
|
|
self.assertEqual(signal_frame.ui.lineEditSignalName.text(), "test_generator")
|
|
signal_frame.ui.cbModulationType.setCurrentIndex(0) # ASK
|
|
signal_frame.ui.spinBoxNoiseTreshold.setValue(0)
|
|
signal_frame.ui.spinBoxNoiseTreshold.editingFinished.emit()
|
|
|
|
signal_frame.ui.spinBoxInfoLen.setValue(295)
|
|
signal_frame.ui.spinBoxInfoLen.editingFinished.emit()
|
|
|
|
signal_frame.ui.spinBoxCenterOffset.setValue(0.1)
|
|
signal_frame.ui.spinBoxCenterOffset.editingFinished.emit()
|
|
|
|
signal_frame.ui.spinBoxTolerance.setValue(6)
|
|
signal_frame.ui.spinBoxTolerance.editingFinished.emit()
|
|
|
|
self.assertEqual(len(signal_frame.proto_analyzer.messages), 1)
|
|
gen_proto = signal_frame.ui.txtEdProto.toPlainText()
|
|
gen_proto = gen_proto[:gen_proto.index(" ")]
|
|
self.assertTrue(proto.startswith(gen_proto))
|
|
|
|
def test_close_signal(self):
|
|
self.add_signal_to_form("ask.complex")
|
|
sframe = self.form.signal_tab_controller.signal_frames[0]
|
|
sframe.ui.cbModulationType.setCurrentIndex(0) # ASK
|
|
sframe.ui.spinBoxInfoLen.setValue(295)
|
|
sframe.ui.spinBoxCenterOffset.setValue(-0.1667)
|
|
sframe.refresh()
|
|
|
|
# Move with encoding to generator
|
|
gframe = self.form.generator_tab_controller
|
|
gframe.ui.cbViewType.setCurrentIndex(0)
|
|
item = gframe.tree_model.rootItem.children[0].children[0]
|
|
index = gframe.tree_model.createIndex(0, 0, item)
|
|
rect = gframe.ui.treeProtocols.visualRect(index)
|
|
QTest.mousePress(gframe.ui.treeProtocols.viewport(), Qt.LeftButton, pos=rect.center())
|
|
self.assertEqual(gframe.ui.treeProtocols.selectedIndexes()[0], index)
|
|
mimedata = gframe.tree_model.mimeData(gframe.ui.treeProtocols.selectedIndexes())
|
|
gframe.table_model.dropMimeData(mimedata, 1, -1, -1, gframe.table_model.createIndex(0, 0))
|
|
self.assertEqual(gframe.table_model.row_count, self.form.compare_frame_controller.protocol_model.row_count)
|
|
self.form.ui.tabWidget.setCurrentIndex(0)
|
|
self.form.on_selected_tab_changed(0)
|
|
sframe.ui.btnCloseSignal.click()
|
|
self.form.ui.tabWidget.setCurrentIndex(1)
|
|
self.form.on_selected_tab_changed(1)
|
|
self.form.ui.tabWidget.setCurrentIndex(2)
|
|
self.form.on_selected_tab_changed(2)
|
|
self.assertEqual(1, 1)
|
|
|
|
def test_create_table_context_menu(self):
|
|
# Context menu should only contain one item (add new message)
|
|
self.assertEqual(self.form.generator_tab_controller.table_model.rowCount(), 0)
|
|
self.form.generator_tab_controller.ui.tableMessages.context_menu_pos = QPoint(0, 0)
|
|
menu = self.form.generator_tab_controller.ui.tableMessages.create_context_menu()
|
|
self.assertEqual(len(menu.actions()), 1)
|
|
|
|
# Add data to test entries in context menu
|
|
self.add_signal_to_form("ask.complex")
|
|
gframe = self.form.generator_tab_controller
|
|
index = gframe.tree_model.createIndex(0, 0, gframe.tree_model.rootItem.children[0].children[0])
|
|
mimedata = gframe.tree_model.mimeData([index])
|
|
gframe.table_model.dropMimeData(mimedata, 1, -1, -1, gframe.table_model.createIndex(0, 0))
|
|
|
|
self.assertGreater(self.form.generator_tab_controller.table_model.rowCount(), 0)
|
|
menu = self.form.generator_tab_controller.ui.tableMessages.create_context_menu()
|
|
n_items = len(menu.actions())
|
|
self.assertGreater(n_items, 1)
|
|
|
|
# If there is a selection, additional items should be present in context menu
|
|
gframe.ui.tableMessages.selectRow(0)
|
|
menu = self.form.generator_tab_controller.ui.tableMessages.create_context_menu()
|
|
self.assertGreater(len(menu.actions()), n_items)
|
|
|
|
def test_add_empty_row_behind(self):
|
|
self.assertEqual(self.form.generator_tab_controller.table_model.rowCount(), 0)
|
|
gframe = self.form.generator_tab_controller
|
|
gframe.ui.cbViewType.setCurrentIndex(0)
|
|
gframe.table_model.add_empty_row_behind(-1, 30)
|
|
self.assertEqual(self.form.generator_tab_controller.table_model.rowCount(), 1)
|
|
|
|
# Add data to test
|
|
self.add_signal_to_form("ask.complex")
|
|
|
|
index = gframe.tree_model.createIndex(0, 0, gframe.tree_model.rootItem.children[0].children[0])
|
|
mimedata = gframe.tree_model.mimeData([index])
|
|
gframe.table_model.dropMimeData(mimedata, 1, -1, -1, gframe.table_model.createIndex(0, 0))
|
|
self.assertEqual(self.form.generator_tab_controller.table_model.rowCount(), 2)
|
|
self.assertNotEqual(len(self.form.generator_tab_controller.table_model.display_data[1]), 30)
|
|
gframe.table_model.add_empty_row_behind(0, 30)
|
|
self.assertEqual(self.form.generator_tab_controller.table_model.rowCount(), 3)
|
|
self.assertEqual(len(self.form.generator_tab_controller.table_model.display_data[1]), 30)
|
|
self.assertNotEqual(len(self.form.generator_tab_controller.table_model.display_data[2]), 30)
|
|
|
|
def test_create_fuzzing_list_view_context_menu(self):
|
|
self.form.generator_tab_controller.ui.tabWidget.setCurrentIndex(2)
|
|
# Context menu should be empty if table is empty
|
|
self.assertEqual(self.form.generator_tab_controller.table_model.rowCount(), 0)
|
|
self.form.generator_tab_controller.ui.tableMessages.context_menu_pos = QPoint(0, 0)
|
|
menu = self.form.generator_tab_controller.ui.listViewProtoLabels.create_context_menu()
|
|
self.assertEqual(len(menu.actions()), 0)
|
|
|
|
# Add data to test entries in context menu
|
|
self.add_signal_to_form("fsk.complex")
|
|
self.form.compare_frame_controller.add_protocol_label(0, 10, 0, 0, False)
|
|
self.assertEqual(1, len(self.form.compare_frame_controller.proto_analyzer.protocol_labels))
|
|
gframe = self.form.generator_tab_controller
|
|
index = gframe.tree_model.createIndex(0, 0, gframe.tree_model.rootItem.children[0].children[0])
|
|
mimedata = gframe.tree_model.mimeData([index])
|
|
gframe.table_model.dropMimeData(mimedata, 1, -1, -1, gframe.table_model.createIndex(0, 0))
|
|
|
|
self.assertGreater(self.form.generator_tab_controller.table_model.rowCount(), 0)
|
|
# Select a row so there is a message for that fuzzing labels can be shown
|
|
self.form.generator_tab_controller.ui.tableMessages.selectRow(0)
|
|
menu = self.form.generator_tab_controller.ui.listViewProtoLabels.create_context_menu()
|
|
n_items = len(menu.actions())
|
|
self.assertGreater(n_items, 0)
|
|
|
|
def test_pauses_widget(self):
|
|
assert isinstance(self.form, MainController)
|
|
self.form.generator_tab_controller.ui.tabWidget.setCurrentIndex(1)
|
|
|
|
menu = self.form.generator_tab_controller.ui.lWPauses.create_context_menu()
|
|
self.assertEqual(len(menu.actions()), 1)
|
|
|
|
self.form.generator_tab_controller.ui.lWPauses.addItem("10")
|
|
menu = self.form.generator_tab_controller.ui.lWPauses.create_context_menu()
|
|
self.assertEqual(len(menu.actions()), 2)
|
|
|
|
def test_add_column(self):
|
|
# Add data to test
|
|
self.add_signal_to_form("ask.complex")
|
|
gframe = self.form.generator_tab_controller
|
|
gframe.ui.cbViewType.setCurrentText("Bit")
|
|
|
|
index = gframe.tree_model.createIndex(0, 0, gframe.tree_model.rootItem.children[0].children[0])
|
|
mimedata = gframe.tree_model.mimeData([index])
|
|
gframe.table_model.dropMimeData(mimedata, 1, -1, -1, gframe.table_model.createIndex(0, 0))
|
|
self.assertEqual(self.form.generator_tab_controller.table_model.rowCount(), 1)
|
|
|
|
l1 = len(self.form.generator_tab_controller.table_model.protocol.messages[0])
|
|
self.form.generator_tab_controller.table_model.insert_column(0, [0])
|
|
self.assertEqual(l1 + 1, len(self.form.generator_tab_controller.table_model.protocol.messages[0]))
|
|
|
|
self.form.generator_tab_controller.generator_undo_stack.undo()
|
|
self.assertEqual(l1, len(self.form.generator_tab_controller.table_model.protocol.messages[0]))
|
|
|
|
self.form.generator_tab_controller.generator_undo_stack.redo()
|
|
self.assertEqual(l1 + 1, len(self.form.generator_tab_controller.table_model.protocol.messages[0]))
|
|
|
|
def test_clear(self):
|
|
self.add_signal_to_form("ask.complex")
|
|
self.add_signal_to_generator(0)
|
|
|
|
gframe = self.form.generator_tab_controller # type: GeneratorTabController
|
|
rows = gframe.table_model.rowCount()
|
|
self.assertGreater(rows, 0)
|
|
gframe.ui.tableMessages.on_clear_action_triggered()
|
|
self.assertEqual(gframe.table_model.rowCount(), 0)
|
|
gframe.generator_undo_stack.undo()
|
|
self.assertEqual(gframe.table_model.rowCount(), rows)
|
|
|
|
def test_edit_data(self):
|
|
# load some bits from txt
|
|
filename = os.path.join(tempfile.gettempdir(), "testdata.txt")
|
|
data = ["101010101111", "1010101011110000", "10101010000111111"]
|
|
|
|
with open(filename, "w") as f:
|
|
f.writelines("\n".join(data))
|
|
|
|
self.wait_before_new_file()
|
|
self.form.add_files([filename])
|
|
self.add_signal_to_generator(signal_index=0)
|
|
self.form.generator_tab_controller.ui.cbViewType.setCurrentText("Bit")
|
|
|
|
table_model = self.form.generator_tab_controller.table_model
|
|
self.assertEqual(table_model.rowCount(), 3)
|
|
|
|
self.assertEqual(table_model.display_data[1][7], 0)
|
|
self.__set_model_data(table_model, row=1, column=7, value="1")
|
|
self.assertEqual(table_model.display_data[1][7], 1)
|
|
self.__set_model_data(table_model, row=1, column=7, value="0")
|
|
self.assertEqual(table_model.display_data[1][7], 0)
|
|
|
|
self.form.generator_tab_controller.ui.cbViewType.setCurrentText("Hex")
|
|
self.assertEqual(table_model.display_data[2][1], 10)
|
|
self.__set_model_data(table_model, row=2, column=1, value="e")
|
|
self.assertEqual(table_model.display_data[2][1], 14)
|
|
|
|
self.assertLess(len(table_model.display_data[1]), 5)
|
|
self.__set_model_data(table_model, row=1, column=4, value="3")
|
|
self.assertEqual(table_model.display_data[1][4], 3)
|
|
|
|
self.assertEqual(table_model.protocol.plain_hex_str[0], "aaf")
|
|
self.__set_model_data(table_model, row=0, column=4, value="b")
|
|
self.assertEqual(table_model.protocol.plain_hex_str[0], "aaf0b")
|
|
|
|
def test_fuzzing_label_list_view(self):
|
|
self.add_signal_to_form("ask.complex")
|
|
gframe = self.form.generator_tab_controller # type: GeneratorTabController
|
|
gframe.ui.cbViewType.setCurrentText("Bit")
|
|
|
|
self.add_signal_to_generator(0)
|
|
gframe.ui.tabWidget.setCurrentWidget(gframe.ui.tab_fuzzing)
|
|
|
|
gframe.ui.tableMessages.selectRow(0)
|
|
self.assertEqual(gframe.label_list_model.rowCount(), 0)
|
|
gframe.create_fuzzing_label(0, 10, 20)
|
|
self.assertEqual(gframe.label_list_model.rowCount(), 1)
|
|
|
|
model = gframe.label_list_model
|
|
lbl = model.labels[0]
|
|
self.assertTrue(bool(lbl.fuzz_me))
|
|
self.assertEqual(len(lbl.fuzz_values), 1)
|
|
|
|
self.assertTrue(bool(model.data(model.index(0,0), role=Qt.CheckStateRole)), True)
|
|
model.setData(model.index(0,0), Qt.Unchecked, role=Qt.CheckStateRole)
|
|
self.assertFalse(lbl.fuzz_me)
|
|
|
|
model.setData(model.index(0,0), "test", role=Qt.EditRole)
|
|
self.assertEqual("test (empty)", model.data(model.index(0,0), role=Qt.DisplayRole))
|
|
|
|
lbl.fuzz_values.append("101010")
|
|
model.update()
|
|
self.assertEqual("test (1)", model.data(model.index(0, 0), role=Qt.DisplayRole))
|
|
|
|
|
|
def __set_model_data(self, model, row, column, value):
|
|
model.setData(model.createIndex(row, column), value, role=Qt.EditRole)
|
|
|
|
def __is_inv_proto(self, proto1: str, proto2: str):
|
|
if len(proto1) != len(proto2):
|
|
return False
|
|
|
|
for c1, c2 in zip(proto1, proto2):
|
|
if not self.__is_inv_bits(c1, c2):
|
|
return False
|
|
|
|
return True
|
|
|
|
def __is_inv_bits(self, a: str, b: str):
|
|
# We only check bits here
|
|
if a not in ("0", "1") or b not in ("0", "1"):
|
|
return True
|
|
|
|
if a == "0" and b == "1":
|
|
return True
|
|
if a == "1" and b == "0":
|
|
return True
|
|
return False
|