From 7be0da6e18cf1ba53044803074622c7fed88867e Mon Sep 17 00:00:00 2001 From: Jose Velazquez Date: Tue, 15 Mar 2022 10:56:55 -0600 Subject: [PATCH] Adding timestamps to Recorded Signals and ProtocolAnalyzer messages --- src/urh/controller/MainController.py | 12 ++++++------ src/urh/controller/dialogs/ReceiveDialog.py | 18 +++++++----------- src/urh/dev/VirtualDevice.py | 16 +++++++++++++++- src/urh/dev/native/Device.py | 18 ++++++++++++++++++ src/urh/signalprocessing/Message.py | 4 ++-- src/urh/signalprocessing/ProtocolAnalyzer.py | 3 ++- src/urh/signalprocessing/RecordedFile.py | 6 ++++++ src/urh/signalprocessing/Signal.py | 7 ++++++- 8 files changed, 62 insertions(+), 22 deletions(-) create mode 100644 src/urh/signalprocessing/RecordedFile.py diff --git a/src/urh/controller/MainController.py b/src/urh/controller/MainController.py index dc0326fcb6..beba7f27c0 100644 --- a/src/urh/controller/MainController.py +++ b/src/urh/controller/MainController.py @@ -285,7 +285,7 @@ def add_simulator_profile(self, filename): self.ui.tabWidget.setCurrentIndex(3) self.simulator_tab_controller.load_simulator_file(filename) - def add_signalfile(self, filename: str, group_id=0, enforce_sample_rate=None): + def add_signalfile(self, filename: str, group_id=0, enforce_sample_rate=None, signal_timestamp=0): if not os.path.exists(filename): QMessageBox.critical(self, self.tr("File not Found"), self.tr("The file {0} could not be found. Was it moved or renamed?").format( @@ -301,7 +301,7 @@ def add_signalfile(self, filename: str, group_id=0, enforce_sample_rate=None): else: sample_rate = self.project_manager.device_conf["sample_rate"] - signal = Signal(filename, sig_name, sample_rate=sample_rate) + signal = Signal(filename, sig_name, sample_rate=sample_rate, timestamp=signal_timestamp) self.file_proxy_model.open_files.add(filename) self.add_signal(signal, group_id) @@ -738,11 +738,11 @@ def on_show_spectrum_dialog_action_triggered(self): r.device_parameters_changed.connect(pm.set_device_parameters) r.show() - @pyqtSlot(list, float) - def on_signals_recorded(self, file_names: list, sample_rate: float): + @pyqtSlot(list) + def on_signals_recorded(self, recorded_files: list): QApplication.instance().setOverrideCursor(Qt.WaitCursor) - for filename in file_names: - self.add_signalfile(filename, enforce_sample_rate=sample_rate) + for recorded_file in recorded_files: + self.add_signalfile(recorded_file.filename, enforce_sample_rate=recorded_file.sample_rate, signal_timestamp=recorded_file.timestamp) QApplication.instance().restoreOverrideCursor() @pyqtSlot() diff --git a/src/urh/controller/dialogs/ReceiveDialog.py b/src/urh/controller/dialogs/ReceiveDialog.py index 4338b3aa5c..593830865b 100644 --- a/src/urh/controller/dialogs/ReceiveDialog.py +++ b/src/urh/controller/dialogs/ReceiveDialog.py @@ -9,9 +9,10 @@ from urh.util import FileOperator from urh.util.Formatter import Formatter from datetime import datetime +from urh.signalprocessing.RecordedFile import RecordedFile class ReceiveDialog(SendRecvDialog): - files_recorded = pyqtSignal(list, float) + files_recorded = pyqtSignal(list) def __init__(self, project_manager, parent=None, testing_mode=False): try: @@ -47,12 +48,7 @@ def save_before_close(self): 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) + self.files_recorded.emit(self.recorded_files) return True def update_view(self): @@ -94,8 +90,8 @@ def on_save_clicked(self): 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, + timestamp_str = datetime.fromtimestamp(dev.data_timestamp).strftime("%Y%m%d_%H%M%S") + initial_name = "{0}-{1}-{2}Hz-{3}Sps".format(dev.name, timestamp_str, big_val(dev.frequency), big_val(dev.sample_rate)) if dev.bandwidth_is_adjustable: @@ -106,5 +102,5 @@ def on_save_clicked(self): 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) + if filename is not None and filename not in (x.filename for x in self.recorded_files): + self.recorded_files.append(RecordedFile(filename, dev.sample_rate, dev.data_timestamp)) diff --git a/src/urh/dev/VirtualDevice.py b/src/urh/dev/VirtualDevice.py index a88782c35c..6caf5d8eb9 100644 --- a/src/urh/dev/VirtualDevice.py +++ b/src/urh/dev/VirtualDevice.py @@ -40,6 +40,7 @@ def __init__(self, backend_handler, name: str, mode: Mode, freq=None, sample_rat self.name = name self.mode = mode self.backend_handler = backend_handler + self.__data_timestamp = 0 freq = config.DEFAULT_FREQUENCY if freq is None else freq sample_rate = config.DEFAULT_SAMPLE_RATE if sample_rate is None else sample_rate @@ -354,7 +355,10 @@ def baseband_gain(self, value): @property def sample_rate(self): - return self.__dev.sample_rate + try: + return self.__dev.sample_rate + except: + return 1e6 @sample_rate.setter def sample_rate(self, value): @@ -490,6 +494,15 @@ def data(self, value): else: logger.warning("{}:{} has no data".format(self.__class__.__name__, self.backend.name)) + @property + def data_timestamp(self): + if self.backend == Backends.native: + try: + self.__data_timestamp = self.__dev.first_data_timestamp # more accurate timestamp + except: + pass + return self.__data_timestamp + def free_data(self): if self.backend == Backends.grc: self.__dev.data = None @@ -599,6 +612,7 @@ def spectrum(self): raise ValueError("Spectrum x only available in spectrum mode") def start(self): + self.__data_timestamp = time.time() if self.backend == Backends.grc: self.__dev.setTerminationEnabled(True) self.__dev.terminate() diff --git a/src/urh/dev/native/Device.py b/src/urh/dev/native/Device.py index 484a95a61b..37a04f9588 100644 --- a/src/urh/dev/native/Device.py +++ b/src/urh/dev/native/Device.py @@ -266,6 +266,7 @@ def __init__(self, center_freq, sample_rate, bandwidth, gain, if_gain=1, baseban self.error_codes = {} self.device_messages = [] + self.__first_data_timestamp = 0 self.receive_process_function = self.device_receive self.send_process_function = self.device_send @@ -573,8 +574,13 @@ def set_device_direct_sampling_mode(self, value): self.parent_ctrl_conn.send((self.Command.SET_DIRECT_SAMPLING_MODE.name, int(value))) except (BrokenPipeError, OSError): pass + + @property + def first_data_timestamp(self): + return self.__first_data_timestamp def start_rx_mode(self): + self.__first_data_timestamp = 0 self.init_recv_buffer() self.parent_data_conn, self.child_data_conn = Pipe(duplex=False) self.parent_ctrl_conn, self.child_ctrl_conn = Pipe() @@ -684,8 +690,20 @@ def read_receiving_queue(self): while self.is_receiving: try: byte_buffer = self.parent_data_conn.recv_bytes() + + if (self.__first_data_timestamp == 0): + self.__first_data_timestamp = time.time() + calculating_timestamp = True + else: + calculating_timestamp = False + samples = self.bytes_to_iq(byte_buffer) n_samples = len(samples) + + if (calculating_timestamp): + # Timestamp accurate correction + self.__first_data_timestamp -= n_samples / self.sample_rate + if n_samples == 0: continue diff --git a/src/urh/signalprocessing/Message.py b/src/urh/signalprocessing/Message.py index b84df419cc..4681645160 100644 --- a/src/urh/signalprocessing/Message.py +++ b/src/urh/signalprocessing/Message.py @@ -25,7 +25,7 @@ class Message(object): "alignment_offset", "bits_per_symbol"] def __init__(self, plain_bits, pause: int, message_type: MessageType, rssi=0, modulator_index=0, decoder=None, - fuzz_created=False, bit_sample_pos=None, samples_per_symbol=100, participant=None, bits_per_symbol=1): + fuzz_created=False, bit_sample_pos=None, samples_per_symbol=100, participant=None, bits_per_symbol=1, timestamp=0): """ :param pause: pause AFTER the message in samples @@ -44,7 +44,7 @@ def __init__(self, plain_bits, pause: int, message_type: MessageType, rssi=0, mo self.participant = participant # type: Participant self.message_type = message_type # type: MessageType - self.timestamp = time.time() + self.timestamp = timestamp self.absolute_time = 0 # set in Compare Frame self.relative_time = 0 # set in Compare Frame diff --git a/src/urh/signalprocessing/ProtocolAnalyzer.py b/src/urh/signalprocessing/ProtocolAnalyzer.py index 238eaa226e..35d2ce1281 100644 --- a/src/urh/signalprocessing/ProtocolAnalyzer.py +++ b/src/urh/signalprocessing/ProtocolAnalyzer.py @@ -239,9 +239,10 @@ def get_protocol_from_signal(self): middle_bit_pos = bit_sample_pos[i][int(len(bits) / 2)] start, end = middle_bit_pos, middle_bit_pos + samples_per_symbol rssi = np.mean(signal.iq_array.subarray(start, end).magnitudes_normalized) + message_timestamp = signal.timestamp + (bit_sample_pos[i][0] / signal.sample_rate) message = Message(bits, pause, message_type=self.default_message_type, samples_per_symbol=samples_per_symbol, rssi=rssi, decoder=self.decoder, - bit_sample_pos=bit_sample_pos[i], bits_per_symbol=signal.bits_per_symbol) + bit_sample_pos=bit_sample_pos[i], bits_per_symbol=signal.bits_per_symbol, timestamp=message_timestamp) self.messages.append(message) i += 1 diff --git a/src/urh/signalprocessing/RecordedFile.py b/src/urh/signalprocessing/RecordedFile.py new file mode 100644 index 0000000000..0ffcfe0075 --- /dev/null +++ b/src/urh/signalprocessing/RecordedFile.py @@ -0,0 +1,6 @@ + +class RecordedFile: + def __init__(self, filename, sample_rate, timestamp): + self.filename = filename + self.sample_rate = sample_rate + self.timestamp = timestamp diff --git a/src/urh/signalprocessing/Signal.py b/src/urh/signalprocessing/Signal.py index 0a13e37f12..c63006e4a6 100644 --- a/src/urh/signalprocessing/Signal.py +++ b/src/urh/signalprocessing/Signal.py @@ -39,7 +39,7 @@ class Signal(QObject): protocol_needs_update = pyqtSignal() data_edited = pyqtSignal() # On Crop/Mute/Delete etc. - def __init__(self, filename: str, name="Signal", modulation: str = None, sample_rate: float = 1e6, parent=None): + def __init__(self, filename: str, name="Signal", modulation: str = None, sample_rate: float = 1e6, timestamp: float = 0, parent=None): super().__init__(parent) self.__name = name self.__tolerance = 5 @@ -51,6 +51,7 @@ def __init__(self, filename: str, name="Signal", modulation: str = None, sample_ self.__center = 0 self._noise_threshold = 0 self.__sample_rate = sample_rate + self.__timestamp = timestamp self.noise_min_plot = 0 self.noise_max_plot = 0 self.block_protocol_update = False @@ -178,6 +179,10 @@ def sample_rate(self, val): self.__sample_rate = val self.sample_rate_changed.emit(val) + @property + def timestamp(self): + return self.__timestamp + @property def parameter_cache(self) -> dict: """