Add urh
This commit is contained in:
@ -0,0 +1,245 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
from PyQt5 import uic
|
||||
from PyQt5.QtCore import QRegExp, Qt, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtGui import QBrush, QColor, QPen, QRegExpValidator
|
||||
from PyQt5.QtWidgets import QApplication, QDialog
|
||||
|
||||
from urh.plugins.Plugin import SignalEditorPlugin
|
||||
from urh.signalprocessing.IQArray import IQArray
|
||||
from urh.ui.painting.SceneManager import SceneManager
|
||||
from urh.util.Formatter import Formatter
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class InsertSinePlugin(SignalEditorPlugin):
|
||||
insert_sine_wave_clicked = pyqtSignal()
|
||||
|
||||
INSERT_INDICATOR_COLOR = QColor(0, 255, 0, 80)
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.__dialog_ui = None # type: QDialog
|
||||
self.complex_wave = None
|
||||
|
||||
self.__amplitude = 0.5
|
||||
self.__frequency = 10
|
||||
self.__phase = 0
|
||||
self.__sample_rate = 1e6
|
||||
self.__num_samples = int(1e6)
|
||||
|
||||
self.original_data = None
|
||||
self.draw_data = None
|
||||
self.position = 0
|
||||
|
||||
super().__init__(name="InsertSine")
|
||||
|
||||
@property
|
||||
def dialog_ui(self) -> QDialog:
|
||||
if self.__dialog_ui is None:
|
||||
dir_name = os.path.dirname(os.readlink(__file__)) if os.path.islink(__file__) else os.path.dirname(__file__)
|
||||
|
||||
logging.getLogger().setLevel(logging.WARNING)
|
||||
self.__dialog_ui = uic.loadUi(os.path.realpath(os.path.join(dir_name, "insert_sine_dialog.ui")))
|
||||
logging.getLogger().setLevel(logger.level)
|
||||
|
||||
self.__dialog_ui.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.__dialog_ui.setModal(True)
|
||||
self.__dialog_ui.doubleSpinBoxAmplitude.setValue(self.__amplitude)
|
||||
self.__dialog_ui.doubleSpinBoxFrequency.setValue(self.__frequency)
|
||||
self.__dialog_ui.doubleSpinBoxPhase.setValue(self.__phase)
|
||||
self.__dialog_ui.doubleSpinBoxSampleRate.setValue(self.__sample_rate)
|
||||
self.__dialog_ui.doubleSpinBoxNSamples.setValue(self.__num_samples)
|
||||
self.__dialog_ui.lineEditTime.setValidator(
|
||||
QRegExpValidator(QRegExp(r"[0-9]+([nmµ]?|([\.,][0-9]{1,3}[nmµ]?))?$"))
|
||||
)
|
||||
|
||||
scene_manager = SceneManager(self.dialog_ui.graphicsViewSineWave)
|
||||
self.__dialog_ui.graphicsViewSineWave.scene_manager = scene_manager
|
||||
self.insert_indicator = scene_manager.scene.addRect(0, -2, 0, 4,
|
||||
QPen(QColor(Qt.transparent), 0),
|
||||
QBrush(self.INSERT_INDICATOR_COLOR))
|
||||
self.insert_indicator.stackBefore(scene_manager.scene.selection_area)
|
||||
|
||||
self.set_time()
|
||||
|
||||
return self.__dialog_ui
|
||||
|
||||
@property
|
||||
def amplitude(self) -> float:
|
||||
return self.__amplitude
|
||||
|
||||
@amplitude.setter
|
||||
def amplitude(self, value: float):
|
||||
if value != self.amplitude:
|
||||
self.__amplitude = value
|
||||
self.draw_sine_wave()
|
||||
|
||||
@property
|
||||
def frequency(self) -> float:
|
||||
return self.__frequency
|
||||
|
||||
@frequency.setter
|
||||
def frequency(self, value: float):
|
||||
if value != self.frequency:
|
||||
self.__frequency = value
|
||||
self.draw_sine_wave()
|
||||
|
||||
@property
|
||||
def phase(self) -> float:
|
||||
return self.__phase
|
||||
|
||||
@phase.setter
|
||||
def phase(self, value: float):
|
||||
if value != self.phase:
|
||||
self.__phase = value
|
||||
self.draw_sine_wave()
|
||||
|
||||
@property
|
||||
def sample_rate(self) -> float:
|
||||
return self.__sample_rate
|
||||
|
||||
@sample_rate.setter
|
||||
def sample_rate(self, value: float):
|
||||
if value != self.sample_rate:
|
||||
self.__sample_rate = value
|
||||
self.set_time()
|
||||
self.draw_sine_wave()
|
||||
|
||||
@property
|
||||
def num_samples(self) -> int:
|
||||
return self.__num_samples
|
||||
|
||||
@num_samples.setter
|
||||
def num_samples(self, value: int):
|
||||
value = int(value)
|
||||
if value != self.num_samples:
|
||||
self.__num_samples = value
|
||||
self.set_time()
|
||||
self.draw_sine_wave()
|
||||
|
||||
def create_connects(self):
|
||||
pass
|
||||
|
||||
def create_dialog_connects(self):
|
||||
self.dialog_ui.doubleSpinBoxAmplitude.editingFinished.connect(
|
||||
self.on_double_spin_box_amplitude_editing_finished)
|
||||
self.dialog_ui.doubleSpinBoxFrequency.editingFinished.connect(
|
||||
self.on_double_spin_box_frequency_editing_finished)
|
||||
self.dialog_ui.doubleSpinBoxPhase.editingFinished.connect(self.on_double_spin_box_phase_editing_finished)
|
||||
self.dialog_ui.doubleSpinBoxSampleRate.editingFinished.connect(
|
||||
self.on_double_spin_box_sample_rate_editing_finished)
|
||||
self.dialog_ui.doubleSpinBoxNSamples.editingFinished.connect(self.on_spin_box_n_samples_editing_finished)
|
||||
self.dialog_ui.lineEditTime.editingFinished.connect(self.on_line_edit_time_editing_finished)
|
||||
self.dialog_ui.buttonBox.accepted.connect(self.on_button_box_accept)
|
||||
self.dialog_ui.buttonBox.rejected.connect(self.on_button_box_reject)
|
||||
self.__dialog_ui.finished.connect(self.on_dialog_finished)
|
||||
|
||||
def get_insert_sine_dialog(self, original_data, position, sample_rate=None, num_samples=None) -> QDialog:
|
||||
if sample_rate is not None:
|
||||
self.__sample_rate = sample_rate
|
||||
self.dialog_ui.doubleSpinBoxSampleRate.setValue(sample_rate)
|
||||
|
||||
if num_samples is not None:
|
||||
self.__num_samples = int(num_samples)
|
||||
self.dialog_ui.doubleSpinBoxNSamples.setValue(num_samples)
|
||||
|
||||
self.original_data = original_data
|
||||
self.position = position
|
||||
|
||||
self.set_time()
|
||||
self.draw_sine_wave()
|
||||
self.create_dialog_connects()
|
||||
|
||||
return self.dialog_ui
|
||||
|
||||
def draw_sine_wave(self):
|
||||
if self.dialog_ui.graphicsViewSineWave.scene_manager:
|
||||
self.dialog_ui.graphicsViewSineWave.scene_manager.clear_path()
|
||||
|
||||
QApplication.instance().setOverrideCursor(Qt.WaitCursor)
|
||||
self.__set_status_of_editable_elements(enabled=False)
|
||||
|
||||
t = np.arange(0, self.num_samples) / self.sample_rate
|
||||
arg = 2 * np.pi * self.frequency * t + self.phase
|
||||
|
||||
self.complex_wave = np.empty(len(arg), dtype=np.complex64)
|
||||
self.complex_wave.real = np.cos(arg)
|
||||
self.complex_wave.imag = np.sin(arg)
|
||||
self.complex_wave = IQArray(self.amplitude * self.complex_wave).convert_to(self.original_data.dtype)
|
||||
|
||||
self.draw_data = np.insert(self.original_data[:, 0], self.position, self.complex_wave[:, 0])
|
||||
y, h = self.dialog_ui.graphicsViewSineWave.view_rect().y(), self.dialog_ui.graphicsViewSineWave.view_rect().height()
|
||||
self.insert_indicator.setRect(self.position, y - h, self.num_samples, 2 * h + abs(y))
|
||||
|
||||
self.__set_status_of_editable_elements(enabled=True)
|
||||
QApplication.instance().restoreOverrideCursor()
|
||||
self.dialog_ui.graphicsViewSineWave.plot_data(self.draw_data)
|
||||
self.dialog_ui.graphicsViewSineWave.show_full_scene()
|
||||
|
||||
def __set_status_of_editable_elements(self, enabled: bool):
|
||||
for obj in ("doubleSpinBoxAmplitude", "doubleSpinBoxFrequency", "doubleSpinBoxPhase",
|
||||
"doubleSpinBoxSampleRate", "doubleSpinBoxNSamples", "lineEditTime", "buttonBox"):
|
||||
getattr(self.dialog_ui, obj).setEnabled(enabled)
|
||||
|
||||
def set_time(self):
|
||||
self.dialog_ui.lineEditTime.setText(Formatter.science_time(self.num_samples / self.sample_rate, decimals=3,
|
||||
append_seconds=False, remove_spaces=True))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_double_spin_box_amplitude_editing_finished(self):
|
||||
self.amplitude = self.dialog_ui.doubleSpinBoxAmplitude.value()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_double_spin_box_frequency_editing_finished(self):
|
||||
self.frequency = self.dialog_ui.doubleSpinBoxFrequency.value()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_double_spin_box_phase_editing_finished(self):
|
||||
self.phase = self.dialog_ui.doubleSpinBoxPhase.value()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_double_spin_box_sample_rate_editing_finished(self):
|
||||
self.sample_rate = self.dialog_ui.doubleSpinBoxSampleRate.value()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_spin_box_n_samples_editing_finished(self):
|
||||
self.num_samples = self.dialog_ui.doubleSpinBoxNSamples.value()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_line_edit_time_editing_finished(self):
|
||||
time_str = self.dialog_ui.lineEditTime.text().replace(",", ".")
|
||||
suffix = ""
|
||||
try:
|
||||
t = float(time_str)
|
||||
except ValueError:
|
||||
suffix = time_str[-1]
|
||||
try:
|
||||
t = float(time_str[:-1])
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
factor = 10 ** -9 if suffix == "n" else 10 ** -6 if suffix == "µ" else 10 ** -3 if suffix == "m" else 1
|
||||
time_val = t * factor
|
||||
|
||||
if self.sample_rate * time_val >= 1:
|
||||
self.dialog_ui.doubleSpinBoxNSamples.setValue(self.sample_rate * time_val)
|
||||
self.dialog_ui.doubleSpinBoxNSamples.editingFinished.emit()
|
||||
else:
|
||||
self.set_time()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_button_box_reject(self):
|
||||
self.dialog_ui.reject()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_button_box_accept(self):
|
||||
self.insert_sine_wave_clicked.emit()
|
||||
self.dialog_ui.accept()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_dialog_finished(self):
|
||||
self.sender().graphicsViewSineWave.eliminate()
|
||||
self.__dialog_ui = None
|
@ -0,0 +1,3 @@
|
||||
This plugin enables you to insert custom sine waves into your signal.
|
||||
You will find a new context menu entry in interpretation signal view.
|
||||
Transform URH into a full fledged signal editor!
|
@ -0,0 +1,201 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DialogCustomSine</class>
|
||||
<widget class="QDialog" name="DialogCustomSine">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>601</width>
|
||||
<height>326</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Insert sine wave</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Time (seconds):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Frequency (Hz):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="KillerDoubleSpinBox" name="doubleSpinBoxSampleRate">
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>99999999999999991433150857216.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDoubleSpinBox" name="doubleSpinBoxPhase">
|
||||
<property name="suffix">
|
||||
<string>°</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>360.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Samples:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Sample Rate:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Phase:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="KillerDoubleSpinBox" name="doubleSpinBoxFrequency">
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>9999999999999999931398190359470212947659194368.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Amplitude:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="KillerDoubleSpinBox" name="doubleSpinBoxNSamples">
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>9999999999999999455752309870428160.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="lineEditTime"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QDoubleSpinBox" name="doubleSpinBoxAmplitude">
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.001000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="ZoomableGraphicView" name="graphicsViewSineWave">
|
||||
<property name="renderHints">
|
||||
<set>QPainter::Antialiasing|QPainter::HighQualityAntialiasing|QPainter::TextAntialiasing</set>
|
||||
</property>
|
||||
<property name="resizeAnchor">
|
||||
<enum>QGraphicsView::NoAnchor</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ZoomableGraphicView</class>
|
||||
<extends>QGraphicsView</extends>
|
||||
<header>urh.ui.views.ZoomableGraphicView.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>KillerDoubleSpinBox</class>
|
||||
<extends>QDoubleSpinBox</extends>
|
||||
<header>urh.ui.KillerDoubleSpinBox.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>doubleSpinBoxAmplitude</tabstop>
|
||||
<tabstop>doubleSpinBoxFrequency</tabstop>
|
||||
<tabstop>doubleSpinBoxPhase</tabstop>
|
||||
<tabstop>doubleSpinBoxSampleRate</tabstop>
|
||||
<tabstop>doubleSpinBoxNSamples</tabstop>
|
||||
<tabstop>lineEditTime</tabstop>
|
||||
<tabstop>graphicsViewSineWave</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>InsertSineWaveSettings</class>
|
||||
<widget class="QFrame" name="InsertSineWaveSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Frame</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>No settings available.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -0,0 +1,54 @@
|
||||
import copy
|
||||
|
||||
from PyQt5.QtWidgets import QUndoCommand
|
||||
|
||||
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
|
||||
from urh.signalprocessing.Message import Message
|
||||
|
||||
|
||||
class MessageBreakAction(QUndoCommand):
|
||||
def __init__(self, proto_analyzer: ProtocolAnalyzer, msg_nr: int, pos: int):
|
||||
super().__init__()
|
||||
self.proto_analyzer = proto_analyzer
|
||||
self.msg_nr = msg_nr
|
||||
self.pos = pos
|
||||
self.orig_messages = copy.deepcopy(proto_analyzer.messages)
|
||||
|
||||
self.setText("Break message behind selection")
|
||||
|
||||
def redo(self):
|
||||
message = self.proto_analyzer.messages[self.msg_nr]
|
||||
message1 = Message(plain_bits=message.plain_bits[:self.pos], pause=0,
|
||||
rssi=message.rssi, decoder=message.decoder, message_type=message.message_type,
|
||||
samples_per_symbol=message.samples_per_symbol)
|
||||
message2 = Message(plain_bits=message.plain_bits[self.pos:], pause=message.pause,
|
||||
rssi=message.rssi, decoder=message.decoder, message_type=message.message_type,
|
||||
samples_per_symbol=message.samples_per_symbol)
|
||||
self.proto_analyzer.messages[self.msg_nr] = message1
|
||||
self.proto_analyzer.messages.insert(self.msg_nr + 1, message2)
|
||||
|
||||
def undo(self):
|
||||
self.proto_analyzer.messages = self.orig_messages
|
||||
|
||||
def __get_zero_seq_indexes(self, message: str, following_zeros: int):
|
||||
"""
|
||||
:rtype: list[tuple of int]
|
||||
"""
|
||||
|
||||
result = []
|
||||
if following_zeros > len(message):
|
||||
return result
|
||||
|
||||
zero_counter = 0
|
||||
for i in range(0, len(message)):
|
||||
if message[i] == "0":
|
||||
zero_counter += 1
|
||||
else:
|
||||
if zero_counter >= following_zeros:
|
||||
result.append((i - zero_counter, i))
|
||||
zero_counter = 0
|
||||
|
||||
if zero_counter >= following_zeros:
|
||||
result.append((len(message) - 1 - following_zeros, len(message) - 1))
|
||||
|
||||
return result
|
@ -0,0 +1,39 @@
|
||||
from PyQt5.QtWidgets import QAction, QUndoStack, QMessageBox
|
||||
|
||||
|
||||
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
|
||||
from ..Plugin import ProtocolPlugin
|
||||
from ..MessageBreak.MessageBreakAction import MessageBreakAction
|
||||
|
||||
|
||||
class MessageBreakPlugin(ProtocolPlugin):
|
||||
def __init__(self):
|
||||
super().__init__(name="MessageBreak")
|
||||
self.undo_stack = None
|
||||
self.command = None
|
||||
""":type: QUndoAction """
|
||||
|
||||
def get_action(self, parent, undo_stack: QUndoStack, sel_range, protocol: ProtocolAnalyzer, view: int):
|
||||
"""
|
||||
:type parent: QTableView
|
||||
:type undo_stack: QUndoStack
|
||||
:type protocol_analyzers: list of ProtocolAnalyzer
|
||||
"""
|
||||
min_row, max_row, start, end = sel_range
|
||||
if min_row == -1 or max_row == -1 or start == -1 or end == -1:
|
||||
return None
|
||||
|
||||
if max_row != min_row:
|
||||
return None
|
||||
|
||||
end = protocol.convert_index(end, view, 0, True, message_indx=min_row)[0]
|
||||
# factor = 1 if view == 0 else 4 if view == 1 else 8
|
||||
|
||||
self.command = MessageBreakAction(protocol, max_row, end)
|
||||
action = QAction(self.command.text(), parent)
|
||||
action.triggered.connect(self.action_triggered)
|
||||
self.undo_stack = undo_stack
|
||||
return action
|
||||
|
||||
def action_triggered(self):
|
||||
self.undo_stack.push(self.command)
|
@ -0,0 +1 @@
|
||||
__author__ = 'joe'
|
@ -0,0 +1,6 @@
|
||||
This plugin enables you to break a protocol message on an arbitrary position.
|
||||
This is helpful when you have redundancy in your messages.
|
||||
|
||||
After enabling this plugin you will see a new action in the context menu of the message table in Analysis.
|
||||
Note, this action is only available if you select a SINGLE message.
|
||||
If you select multiple messages, the action will not appear in the context menu.
|
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FrameSyncCropSettings</class>
|
||||
<widget class="QFrame" name="FrameSyncCropSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>295</width>
|
||||
<height>79</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Frame</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>No settings available.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>33</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -0,0 +1,376 @@
|
||||
import socket
|
||||
import socketserver
|
||||
import threading
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal
|
||||
|
||||
from urh import settings
|
||||
from urh.plugins.Plugin import SDRPlugin
|
||||
from urh.signalprocessing.IQArray import IQArray
|
||||
from urh.signalprocessing.Message import Message
|
||||
from urh.util.Errors import Errors
|
||||
from urh.util.Logger import logger
|
||||
from urh.util.RingBuffer import RingBuffer
|
||||
|
||||
|
||||
class NetworkSDRInterfacePlugin(SDRPlugin):
|
||||
DATA_TYPE = np.float32
|
||||
|
||||
NETWORK_SDR_NAME = "Network SDR" # Display text for device combo box
|
||||
show_proto_sniff_dialog_clicked = pyqtSignal()
|
||||
sending_status_changed = pyqtSignal(bool)
|
||||
sending_stop_requested = pyqtSignal()
|
||||
current_send_message_changed = pyqtSignal(int)
|
||||
|
||||
send_connection_established = pyqtSignal()
|
||||
receive_server_started = pyqtSignal()
|
||||
error_occurred = pyqtSignal(str)
|
||||
|
||||
class MyTCPHandler(socketserver.BaseRequestHandler):
|
||||
def handle(self):
|
||||
size = 2 * np.dtype(NetworkSDRInterfacePlugin.DATA_TYPE).itemsize
|
||||
received = self.request.recv(65536 * size)
|
||||
self.data = received
|
||||
|
||||
while received:
|
||||
received = self.request.recv(65536 * size)
|
||||
self.data += received
|
||||
|
||||
if len(self.data) == 0:
|
||||
return
|
||||
|
||||
if hasattr(self.server, "received_bits"):
|
||||
for data in filter(None, self.data.split(b"\n")):
|
||||
self.server.received_bits.append(NetworkSDRInterfacePlugin.bytearray_to_bit_str(data))
|
||||
else:
|
||||
while len(self.data) % size != 0:
|
||||
self.data += self.request.recv(len(self.data) % size)
|
||||
|
||||
received = np.frombuffer(self.data, dtype=NetworkSDRInterfacePlugin.DATA_TYPE)
|
||||
received = received.reshape((len(received)//2, 2))
|
||||
|
||||
if len(received) + self.server.current_receive_index >= len(self.server.receive_buffer):
|
||||
self.server.current_receive_index = 0
|
||||
|
||||
self.server.receive_buffer[
|
||||
self.server.current_receive_index:self.server.current_receive_index + len(received)] = received
|
||||
self.server.current_receive_index += len(received)
|
||||
|
||||
def __init__(self, raw_mode=False, resume_on_full_receive_buffer=False, spectrum=False, sending=False):
|
||||
"""
|
||||
|
||||
:param raw_mode: If true, sending and receiving raw samples if false bits are received/sent
|
||||
"""
|
||||
super().__init__(name="NetworkSDRInterface")
|
||||
self.client_ip = self.qsettings.value("client_ip", defaultValue="127.0.0.1", type=str)
|
||||
self.server_ip = ""
|
||||
|
||||
self.samples_to_send = None # set in virtual device constructor
|
||||
|
||||
self.client_port = self.qsettings.value("client_port", defaultValue=2222, type=int)
|
||||
self.server_port = self.qsettings.value("server_port", defaultValue=4444, type=int)
|
||||
|
||||
self.is_in_spectrum_mode = spectrum
|
||||
self.resume_on_full_receive_buffer = resume_on_full_receive_buffer
|
||||
self.__is_sending = False
|
||||
self.__sending_interrupt_requested = False
|
||||
|
||||
self.sending_repeats = 1 # only used in raw mode
|
||||
self.current_sent_sample = 0
|
||||
self.current_sending_repeat = 0
|
||||
|
||||
self.sending_is_continuous = False
|
||||
self.continuous_send_ring_buffer = None
|
||||
self.num_samples_to_send = None # Only used for continuous send mode
|
||||
|
||||
self.raw_mode = raw_mode
|
||||
if not sending:
|
||||
if self.raw_mode:
|
||||
num_samples = settings.get_receive_buffer_size(self.resume_on_full_receive_buffer,
|
||||
self.is_in_spectrum_mode)
|
||||
try:
|
||||
self.receive_buffer = IQArray(None, dtype=self.DATA_TYPE, n=num_samples)
|
||||
except MemoryError:
|
||||
logger.warning("Could not allocate buffer with {0:d} samples, trying less...")
|
||||
i = 0
|
||||
while True:
|
||||
try:
|
||||
i += 2
|
||||
self.receive_buffer = IQArray(None, dtype=self.DATA_TYPE, n=num_samples // i)
|
||||
logger.debug("Using buffer with {0:d} samples instead.".format(num_samples // i))
|
||||
break
|
||||
except MemoryError:
|
||||
continue
|
||||
else:
|
||||
self.received_bits = []
|
||||
|
||||
@property
|
||||
def is_sending(self) -> bool:
|
||||
return self.__is_sending
|
||||
|
||||
@is_sending.setter
|
||||
def is_sending(self, value: bool):
|
||||
if value != self.__is_sending:
|
||||
self.__is_sending = value
|
||||
self.sending_status_changed.emit(self.__is_sending)
|
||||
|
||||
@property
|
||||
def sending_finished(self) -> bool:
|
||||
return self.current_sending_repeat >= self.sending_repeats if self.sending_repeats > 0 else False
|
||||
|
||||
@property
|
||||
def received_data(self):
|
||||
if self.raw_mode:
|
||||
return self.receive_buffer[:self.current_receive_index]
|
||||
else:
|
||||
return self.received_bits
|
||||
|
||||
@property
|
||||
def current_receive_index(self):
|
||||
if hasattr(self, "server") and hasattr(self.server, "current_receive_index"):
|
||||
return self.server.current_receive_index
|
||||
else:
|
||||
return 0
|
||||
|
||||
@current_receive_index.setter
|
||||
def current_receive_index(self, value):
|
||||
if hasattr(self, "server") and hasattr(self.server, "current_receive_index"):
|
||||
self.server.current_receive_index = value
|
||||
else:
|
||||
pass
|
||||
|
||||
def free_data(self):
|
||||
if self.raw_mode:
|
||||
self.receive_buffer = IQArray(None, dtype=self.DATA_TYPE, n=0)
|
||||
else:
|
||||
self.received_bits[:] = []
|
||||
|
||||
def create_connects(self):
|
||||
self.settings_frame.lineEditClientIP.setText(self.client_ip)
|
||||
self.settings_frame.spinBoxClientPort.setValue(self.client_port)
|
||||
self.settings_frame.spinBoxServerPort.setValue(self.server_port)
|
||||
|
||||
self.settings_frame.lineEditClientIP.editingFinished.connect(self.on_linedit_client_ip_editing_finished)
|
||||
self.settings_frame.lineEditServerIP.editingFinished.connect(self.on_linedit_server_ip_editing_finished)
|
||||
self.settings_frame.spinBoxClientPort.editingFinished.connect(self.on_spinbox_client_port_editing_finished)
|
||||
self.settings_frame.spinBoxServerPort.editingFinished.connect(self.on_spinbox_server_port_editing_finished)
|
||||
|
||||
self.settings_frame.lOpenProtoSniffer.linkActivated.connect(self.on_lopenprotosniffer_link_activated)
|
||||
|
||||
def start_tcp_server_for_receiving(self):
|
||||
self.server = socketserver.TCPServer((self.server_ip, self.server_port), self.MyTCPHandler)
|
||||
self.server.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
if self.raw_mode:
|
||||
self.server.receive_buffer = self.receive_buffer
|
||||
self.server.current_receive_index = 0
|
||||
else:
|
||||
self.server.received_bits = self.received_bits
|
||||
|
||||
self.server_thread = threading.Thread(target=self.server.serve_forever)
|
||||
self.server_thread.daemon = True
|
||||
self.server_thread.start()
|
||||
|
||||
logger.debug("Started TCP server for receiving")
|
||||
|
||||
self.receive_server_started.emit()
|
||||
|
||||
def stop_tcp_server(self):
|
||||
if hasattr(self, "server"):
|
||||
logger.debug("Shutdown TCP server")
|
||||
self.server.shutdown()
|
||||
self.server.server_close()
|
||||
if hasattr(self, "server_thread"):
|
||||
self.server_thread.join()
|
||||
|
||||
def send_data(self, data, sock: socket.socket) -> str:
|
||||
try:
|
||||
sock.sendall(data)
|
||||
return ""
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
def send_raw_data(self, data: IQArray, num_repeats: int):
|
||||
byte_data = data.to_bytes()
|
||||
rng = iter(int, 1) if num_repeats <= 0 else range(0, num_repeats) # <= 0 = forever
|
||||
|
||||
sock = self.prepare_send_connection()
|
||||
if sock is None:
|
||||
return
|
||||
|
||||
try:
|
||||
for _ in rng:
|
||||
if self.__sending_interrupt_requested:
|
||||
break
|
||||
self.send_data(byte_data, sock)
|
||||
self.current_sent_sample = len(data)
|
||||
self.current_sending_repeat += 1
|
||||
finally:
|
||||
self.shutdown_socket(sock)
|
||||
|
||||
def prepare_send_connection(self):
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.connect((self.client_ip, self.client_port))
|
||||
return sock
|
||||
except Exception as e:
|
||||
msg = "Could not establish connection " + str(e)
|
||||
self.error_occurred.emit(msg)
|
||||
logger.error(msg)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def shutdown_socket(sock):
|
||||
try:
|
||||
sock.shutdown(socket.SHUT_RDWR)
|
||||
except OSError:
|
||||
pass
|
||||
sock.close()
|
||||
|
||||
def send_raw_data_continuously(self, ring_buffer: RingBuffer, num_samples_to_send: int, num_repeats: int):
|
||||
rng = iter(int, 1) if num_repeats <= 0 else range(0, num_repeats) # <= 0 = forever
|
||||
samples_per_iteration = 65536 // 2
|
||||
sock = self.prepare_send_connection()
|
||||
if sock is None:
|
||||
return
|
||||
|
||||
try:
|
||||
for _ in rng:
|
||||
|
||||
if self.__sending_interrupt_requested:
|
||||
break
|
||||
|
||||
while num_samples_to_send is None or self.current_sent_sample < num_samples_to_send:
|
||||
while ring_buffer.is_empty and not self.__sending_interrupt_requested:
|
||||
time.sleep(0.1)
|
||||
|
||||
if self.__sending_interrupt_requested:
|
||||
break
|
||||
|
||||
if num_samples_to_send is None:
|
||||
n = samples_per_iteration
|
||||
else:
|
||||
n = max(0, min(samples_per_iteration, num_samples_to_send - self.current_sent_sample))
|
||||
|
||||
data = ring_buffer.pop(n, ensure_even_length=True)
|
||||
if len(data) > 0:
|
||||
self.send_data(data, sock)
|
||||
self.current_sent_sample += len(data)
|
||||
|
||||
self.current_sending_repeat += 1
|
||||
self.current_sent_sample = 0
|
||||
|
||||
self.current_sent_sample = num_samples_to_send
|
||||
finally:
|
||||
self.shutdown_socket(sock)
|
||||
|
||||
def __send_messages(self, messages, sample_rates):
|
||||
"""
|
||||
|
||||
:type messages: list of Message
|
||||
:type sample_rates: list of int
|
||||
:param sample_rates: Sample Rate for each messages, this is needed to calculate the wait time,
|
||||
as the pause for a message is given in samples
|
||||
:return:
|
||||
"""
|
||||
self.is_sending = True
|
||||
sock = self.prepare_send_connection()
|
||||
if sock is None:
|
||||
return
|
||||
try:
|
||||
for i, msg in enumerate(messages):
|
||||
if self.__sending_interrupt_requested:
|
||||
break
|
||||
assert isinstance(msg, Message)
|
||||
wait_time = msg.pause / sample_rates[i]
|
||||
|
||||
self.current_send_message_changed.emit(i)
|
||||
error = self.send_data(self.bit_str_to_bytearray(msg.encoded_bits_str) + b"\n", sock)
|
||||
if not error:
|
||||
logger.debug("Sent message {0}/{1}".format(i + 1, len(messages)))
|
||||
logger.debug("Waiting message pause: {0:.2f}s".format(wait_time))
|
||||
if self.__sending_interrupt_requested:
|
||||
break
|
||||
time.sleep(wait_time)
|
||||
else:
|
||||
logger.critical("Could not connect to {0}:{1}".format(self.client_ip, self.client_port))
|
||||
break
|
||||
logger.debug("Sending finished")
|
||||
finally:
|
||||
self.is_sending = False
|
||||
self.shutdown_socket(sock)
|
||||
|
||||
def start_message_sending_thread(self, messages, sample_rates):
|
||||
"""
|
||||
|
||||
:type messages: list of Message
|
||||
:type sample_rates: list of int
|
||||
:param sample_rates: Sample Rate for each messages, this is needed to calculate the wait time,
|
||||
as the pause for a message is given in samples
|
||||
:return:
|
||||
"""
|
||||
self.__sending_interrupt_requested = False
|
||||
self.sending_thread = threading.Thread(target=self.__send_messages, args=(messages, sample_rates))
|
||||
self.sending_thread.daemon = True
|
||||
self.sending_thread.start()
|
||||
|
||||
self.send_connection_established.emit()
|
||||
|
||||
def start_raw_sending_thread(self):
|
||||
self.__sending_interrupt_requested = False
|
||||
if self.sending_is_continuous:
|
||||
self.sending_thread = threading.Thread(target=self.send_raw_data_continuously,
|
||||
args=(self.continuous_send_ring_buffer,
|
||||
self.num_samples_to_send, self.sending_repeats))
|
||||
else:
|
||||
self.sending_thread = threading.Thread(target=self.send_raw_data,
|
||||
args=(self.samples_to_send, self.sending_repeats))
|
||||
|
||||
self.sending_thread.daemon = True
|
||||
self.sending_thread.start()
|
||||
|
||||
self.send_connection_established.emit()
|
||||
|
||||
def stop_sending_thread(self):
|
||||
self.__sending_interrupt_requested = True
|
||||
|
||||
if hasattr(self, "sending_thread"):
|
||||
self.sending_thread.join()
|
||||
|
||||
self.sending_stop_requested.emit()
|
||||
|
||||
@staticmethod
|
||||
def bytearray_to_bit_str(arr: bytearray) -> str:
|
||||
return "".join("{:08b}".format(a) for a in arr)
|
||||
|
||||
@staticmethod
|
||||
def bit_str_to_bytearray(bits: str) -> bytearray:
|
||||
bits += "0" * ((8 - len(bits) % 8) % 8)
|
||||
return bytearray((int(bits[i:i + 8], 2) for i in range(0, len(bits), 8)))
|
||||
|
||||
def on_linedit_client_ip_editing_finished(self):
|
||||
ip = self.settings_frame.lineEditClientIP.text()
|
||||
self.client_ip = ip
|
||||
self.qsettings.setValue('client_ip', self.client_ip)
|
||||
|
||||
def on_linedit_server_ip_editing_finished(self):
|
||||
# Does nothing, because field is disabled
|
||||
ip = self.settings_frame.lineEditServerIP.text()
|
||||
self.server_ip = ip
|
||||
self.qsettings.setValue('server_ip', self.server_ip)
|
||||
|
||||
def on_spinbox_client_port_editing_finished(self):
|
||||
self.client_port = self.settings_frame.spinBoxClientPort.value()
|
||||
self.qsettings.setValue('client_port', str(self.client_port))
|
||||
|
||||
def on_spinbox_server_port_editing_finished(self):
|
||||
self.server_port = self.settings_frame.spinBoxServerPort.value()
|
||||
self.qsettings.setValue('server_port', str(self.server_port))
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_lopenprotosniffer_link_activated(self, link: str):
|
||||
if link == "open_proto_sniffer":
|
||||
self.show_proto_sniff_dialog_clicked.emit()
|
@ -0,0 +1,4 @@
|
||||
With this plugin you can interface external applications using TCP.
|
||||
You can use your external application for performing protocol simulation on logical level or advanced modulation/decoding.
|
||||
If you activate this plugin, a new SDR will be selectable in protocol sniffer dialog.
|
||||
Furthermore, a new button below generator table will be created.
|
@ -0,0 +1,208 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Frame</class>
|
||||
<widget class="QFrame" name="Frame">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>666</width>
|
||||
<height>338</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Frame</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxClient">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Send data to external application</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="spinBoxClientPort">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>This is the port number of your external TCP application. URH will connect to this port, when <span style=" font-weight:600;">sending</span> data.</p></body></html></string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1337</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>IP address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>TCP Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="lineEditClientIP">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>This is the IP address of your external TCP application. URH will connect to this address, when <span style=" font-weight:600;">sending</span> data.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>127.0.0.1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxServer">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Receive data from external application</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="3" column="0" colspan="3">
|
||||
<widget class="QLabel" name="lOpenProtoSniffer">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Open <a href="open_proto_sniffer"><span style=" text-decoration: underline; color:#0000ff;">protocol sniffer</span></a> (<span style=" font-style:italic;">File -&gt; Sniff protocol...</span>) to use it.</p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>URH listens on</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QSpinBox" name="spinBoxServerPort">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>This is the TCP port number URH listens on. Your external TCP application can connect and send data to this port, <span style=" font-weight:600;">while</span><span style=" font-weight:600;">receiving is active </span>in URH.</p></body></html></string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1337</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>TCP Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLineEdit" name="lineEditServerIP">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>This is the IP address URH listens on, 127.0.0.1 will work in most cases. Your external TCP application can connect and send data to this address, <span style=" font-weight:600;">while receiving is active </span>in URH.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>any</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>IP address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>317</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
79
Software/Universal Radio Hacker/src/urh/plugins/Plugin.py
Normal file
79
Software/Universal Radio Hacker/src/urh/plugins/Plugin.py
Normal file
@ -0,0 +1,79 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from PyQt5 import uic
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, Qt, QSettings
|
||||
from PyQt5.QtWidgets import QUndoCommand, QUndoStack
|
||||
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class Plugin(QObject):
|
||||
enabled_changed = pyqtSignal()
|
||||
|
||||
def __init__(self, name: str):
|
||||
super().__init__()
|
||||
self.__enabled = Qt.Unchecked
|
||||
self.name = name
|
||||
self.plugin_path = ""
|
||||
self.description = ""
|
||||
self.__settings_frame = None
|
||||
self.qsettings = QSettings(QSettings.IniFormat, QSettings.UserScope, "urh", self.name + "-plugin")
|
||||
|
||||
@property
|
||||
def settings_frame(self):
|
||||
if self.__settings_frame is None:
|
||||
logging.getLogger().setLevel(logging.WARNING)
|
||||
self.__settings_frame = uic.loadUi(os.path.join(self.plugin_path, "settings.ui"))
|
||||
logging.getLogger().setLevel(logger.level)
|
||||
|
||||
self.create_connects()
|
||||
return self.__settings_frame
|
||||
|
||||
@property
|
||||
def enabled(self) -> bool:
|
||||
return self.__enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, value: bool):
|
||||
if value != self.__enabled:
|
||||
self.__enabled = Qt.Checked if value else Qt.Unchecked
|
||||
self.enabled_changed.emit()
|
||||
|
||||
def load_description(self):
|
||||
descr_file = os.path.join(self.plugin_path, "descr.txt")
|
||||
try:
|
||||
with open(descr_file, "r") as f:
|
||||
self.description = f.read()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
def destroy_settings_frame(self):
|
||||
self.__settings_frame = None
|
||||
|
||||
def create_connects(self):
|
||||
pass
|
||||
|
||||
|
||||
class ProtocolPlugin(Plugin):
|
||||
def __init__(self, name: str):
|
||||
Plugin.__init__(self, name)
|
||||
|
||||
def get_action(self, parent, undo_stack: QUndoStack, sel_range, groups,
|
||||
view: int) -> QUndoCommand:
|
||||
"""
|
||||
:type parent: QTableView
|
||||
:type undo_stack: QUndoStack
|
||||
:type groups: list of ProtocolGroups
|
||||
"""
|
||||
raise NotImplementedError("Abstract Method.")
|
||||
|
||||
|
||||
class SDRPlugin(Plugin):
|
||||
def __init__(self, name: str):
|
||||
Plugin.__init__(self, name)
|
||||
|
||||
|
||||
class SignalEditorPlugin(Plugin):
|
||||
def __init__(self, name: str):
|
||||
Plugin.__init__(self, name)
|
@ -0,0 +1,56 @@
|
||||
import importlib
|
||||
import os
|
||||
|
||||
from urh import settings
|
||||
from urh.plugins.Plugin import Plugin, ProtocolPlugin
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
class PluginManager(object):
|
||||
def __init__(self):
|
||||
self.plugin_path = os.path.dirname(os.path.realpath(__file__))
|
||||
self.installed_plugins = self.load_installed_plugins()
|
||||
|
||||
@property
|
||||
def protocol_plugins(self):
|
||||
return [p for p in self.installed_plugins if isinstance(p, ProtocolPlugin)]
|
||||
|
||||
def load_installed_plugins(self):
|
||||
""" :rtype: list of Plugin """
|
||||
result = []
|
||||
plugin_dirs = [d for d in os.listdir(self.plugin_path) if os.path.isdir(os.path.join(self.plugin_path, d))]
|
||||
for d in plugin_dirs:
|
||||
if d == "__pycache__":
|
||||
continue
|
||||
try:
|
||||
class_module = self.load_plugin(d)
|
||||
plugin = class_module()
|
||||
plugin.plugin_path = os.path.join(self.plugin_path, plugin.name)
|
||||
plugin.load_description()
|
||||
if plugin.name in settings.all_keys():
|
||||
plugin.enabled = settings.read(plugin.name, False, type=bool)
|
||||
else:
|
||||
plugin.enabled = False
|
||||
result.append(plugin)
|
||||
except ImportError as e:
|
||||
logger.warning("Could not load plugin {0} ({1})".format(d, e))
|
||||
continue
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def load_plugin(plugin_name):
|
||||
classname = plugin_name + "Plugin"
|
||||
module_path = "urh.plugins." + plugin_name + "." + classname
|
||||
|
||||
module = importlib.import_module(module_path)
|
||||
return getattr(module, classname)
|
||||
|
||||
def is_plugin_enabled(self, plugin_name: str):
|
||||
return any(plugin_name == p.name for p in self.installed_plugins if p.enabled)
|
||||
|
||||
def get_plugin_by_name(self, plugin_name):
|
||||
for plugin in self.installed_plugins:
|
||||
if plugin.name == plugin_name:
|
||||
return plugin
|
||||
return None
|
@ -0,0 +1,232 @@
|
||||
import os
|
||||
import time
|
||||
from subprocess import PIPE, Popen
|
||||
from threading import Thread
|
||||
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
|
||||
from urh import settings
|
||||
from urh.plugins.Plugin import SDRPlugin
|
||||
from urh.signalprocessing.Message import Message
|
||||
from urh.util.Errors import Errors
|
||||
from urh.util.Logger import logger
|
||||
|
||||
|
||||
## rfcat commands
|
||||
# freq = 433920000
|
||||
# mod = "MOD_ASK_OOK"
|
||||
# pktlen = 10
|
||||
# syncmode = 0
|
||||
# syncbytes = "\xCA\xFE\xAF\xFE"
|
||||
# baud = 4800
|
||||
# sendbytes = "\xCA\xFE\xAF\xFE"
|
||||
# num_preamble = 0
|
||||
#
|
||||
# cmd_ping = "d.ping()"
|
||||
# cmd_freq = "d.setFreq({})".format(freq)
|
||||
# cmd_mod = "d.setMdmModulation({})".format(mod)
|
||||
# cmd_pktlen = "d.makePktFLEN({})".format(pktlen)
|
||||
# cmd_syncmode = "d.setMdmSyncMode({})".format(syncmode)
|
||||
# cmd_syncbytes = "d.setMdmSyncWord({})".format(syncbytes)
|
||||
# cmd_baud = "d.setMdmDRate({})".format(baud)
|
||||
# cmd_sendbytes = "d.RFxmit('{}')".format(sendbytes)
|
||||
# cmd_maxpower = "d.setMaxPower()"
|
||||
# cmd_recvbytes = "d.RFrecv()[0]"
|
||||
# cmd_preamble = "d.setMdmNumPreamble({})".format(num_preamble)
|
||||
# cmd_showconfig = "print d.reprRadioConfig()"
|
||||
|
||||
class RfCatPlugin(SDRPlugin):
|
||||
rcv_index_changed = pyqtSignal(int, int) # int arguments are just for compatibility with native and grc backend
|
||||
show_proto_sniff_dialog_clicked = pyqtSignal()
|
||||
sending_status_changed = pyqtSignal(bool)
|
||||
sending_stop_requested = pyqtSignal()
|
||||
current_send_message_changed = pyqtSignal(int)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(name="RfCat")
|
||||
self.rfcat_executable = self.qsettings.value("rfcat_executable", defaultValue="rfcat", type=str)
|
||||
self.rfcat_is_open = False
|
||||
self.initialized = False
|
||||
self.ready = True
|
||||
self.__is_sending = False
|
||||
self.__sending_interrupt_requested = False
|
||||
self.current_sent_sample = 0
|
||||
self.current_sending_repeat = 0
|
||||
self.modulators = 0
|
||||
|
||||
def __del__(self):
|
||||
self.close_rfcat()
|
||||
|
||||
@property
|
||||
def is_sending(self) -> bool:
|
||||
return self.__is_sending
|
||||
|
||||
@is_sending.setter
|
||||
def is_sending(self, value: bool):
|
||||
if value != self.__is_sending:
|
||||
self.__is_sending = value
|
||||
self.sending_status_changed.emit(self.__is_sending)
|
||||
|
||||
@property
|
||||
def rfcat_is_found(self):
|
||||
return self.is_rfcat_executable(self.rfcat_executable)
|
||||
|
||||
def is_rfcat_executable(self, rfcat_executable):
|
||||
fpath, fname = os.path.split(rfcat_executable)
|
||||
if fpath:
|
||||
if os.path.isfile(rfcat_executable) and os.access(rfcat_executable, os.X_OK):
|
||||
return True
|
||||
else:
|
||||
for path in os.environ["PATH"].split(os.pathsep):
|
||||
path = path.strip('"')
|
||||
exe_file = os.path.join(path, rfcat_executable)
|
||||
if os.path.isfile(exe_file) and os.access(exe_file, os.X_OK):
|
||||
return True
|
||||
return False
|
||||
|
||||
def enable_or_disable_send_button(self, rfcat_executable):
|
||||
if self.is_rfcat_executable(rfcat_executable):
|
||||
self.settings_frame.info.setText("Info: Executable can be opened.")
|
||||
else:
|
||||
self.settings_frame.info.setText("Info: Executable cannot be opened! Disabling send button.")
|
||||
logger.debug("RfCat executable cannot be opened! Disabling send button.")
|
||||
|
||||
def create_connects(self):
|
||||
self.settings_frame.rfcat_executable.setText(self.rfcat_executable)
|
||||
self.settings_frame.rfcat_executable.editingFinished.connect(self.on_edit_rfcat_executable_editing_finished)
|
||||
self.enable_or_disable_send_button(self.rfcat_executable)
|
||||
|
||||
def on_edit_rfcat_executable_editing_finished(self):
|
||||
rfcat_executable = self.settings_frame.rfcat_executable.text()
|
||||
self.enable_or_disable_send_button(rfcat_executable)
|
||||
self.rfcat_executable = rfcat_executable
|
||||
self.qsettings.setValue('rfcat_executable', self.rfcat_executable)
|
||||
|
||||
def free_data(self):
|
||||
if self.raw_mode:
|
||||
self.receive_buffer = np.empty(0)
|
||||
else:
|
||||
self.received_bits[:] = []
|
||||
|
||||
def write_to_rfcat(self, buf):
|
||||
self.process.stdin.write(buf.encode("utf-8") + b"\n")
|
||||
self.process.stdin.flush()
|
||||
|
||||
def open_rfcat(self):
|
||||
if not self.rfcat_is_open:
|
||||
try:
|
||||
self.process = Popen([self.rfcat_executable, '-r'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
self.rfcat_is_open = True
|
||||
logger.debug("Successfully opened RfCat ({})".format(self.rfcat_executable))
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug("Could not open RfCat! ({})".format(e))
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def close_rfcat(self):
|
||||
if self.rfcat_is_open:
|
||||
try:
|
||||
self.process.kill()
|
||||
self.rfcat_is_open = False
|
||||
except Exception as e:
|
||||
logger.debug("Could not close rfcat: {}".format(e))
|
||||
|
||||
def set_parameter(self, param: str, log=True): # returns error (True/False)
|
||||
try:
|
||||
self.write_to_rfcat(param)
|
||||
self.ready = False
|
||||
if log:
|
||||
logger.debug(param)
|
||||
except OSError as e:
|
||||
logger.info("Could not set parameter {0}:{1} ({2})".format(param, e))
|
||||
return True
|
||||
return False
|
||||
|
||||
def read_async(self):
|
||||
self.set_parameter("d.RFrecv({})[0]".format(500), log=False)
|
||||
|
||||
def configure_rfcat(self, modulation = "MOD_ASK_OOK", freq = 433920000, sample_rate = 2000000, samples_per_symbol = 500):
|
||||
self.set_parameter("d.setMdmModulation({})".format(modulation), log=False)
|
||||
self.set_parameter("d.setFreq({})".format(int(freq)), log=False)
|
||||
self.set_parameter("d.setMdmSyncMode(0)", log=False)
|
||||
self.set_parameter("d.setMdmDRate({})".format(int(sample_rate // samples_per_symbol)), log=False)
|
||||
self.set_parameter("d.setMaxPower()", log=False)
|
||||
logger.info("Configured RfCat to Modulation={}, Frequency={} Hz, Datarate={} baud".format(modulation, int(freq), int(sample_rate // samples_per_symbol)))
|
||||
|
||||
def send_data(self, data) -> str:
|
||||
prepared_data = "d.RFxmit(b{})".format(str(data)[11:-1]) #[11:-1] Removes "bytearray(b...)
|
||||
self.set_parameter(prepared_data, log=False)
|
||||
|
||||
def __send_messages(self, messages, sample_rates):
|
||||
if len(messages):
|
||||
self.is_sending = True
|
||||
else:
|
||||
return False
|
||||
|
||||
# Open and configure RfCat
|
||||
if not self.open_rfcat():
|
||||
return False
|
||||
modulation = self.modulators[messages[0].modulator_index].modulation_type
|
||||
if modulation == "ASK":
|
||||
modulation = "MOD_ASK_OOK"
|
||||
elif modulation == "FSK":
|
||||
modulation = "MOD_2FSK"
|
||||
elif modulation == "GFSK":
|
||||
modulation = "MOD_GFSK"
|
||||
elif modulation == "PSK":
|
||||
modulation = "MOD_MSK"
|
||||
else: # Fallback
|
||||
modulation = "MOD_ASK_OOK"
|
||||
self.configure_rfcat(modulation=modulation, freq=self.project_manager.device_conf["frequency"],
|
||||
sample_rate=sample_rates[0], samples_per_symbol=messages[0].samples_per_symbol)
|
||||
|
||||
repeats_from_settings = settings.read('num_sending_repeats', type=int)
|
||||
repeats = repeats_from_settings if repeats_from_settings > 0 else -1
|
||||
while (repeats > 0 or repeats == -1) and self.__sending_interrupt_requested == False:
|
||||
logger.debug("Start iteration ({} left)".format(repeats if repeats > 0 else "infinite"))
|
||||
for i, msg in enumerate(messages):
|
||||
if self.__sending_interrupt_requested:
|
||||
break
|
||||
assert isinstance(msg, Message)
|
||||
wait_time = msg.pause / sample_rates[i]
|
||||
|
||||
self.current_send_message_changed.emit(i)
|
||||
error = self.send_data(self.bit_str_to_bytearray(msg.encoded_bits_str))
|
||||
if not error:
|
||||
logger.debug("Sent message {0}/{1}".format(i+1, len(messages)))
|
||||
logger.debug("Waiting message pause: {0:.2f}s".format(wait_time))
|
||||
if self.__sending_interrupt_requested:
|
||||
break
|
||||
time.sleep(wait_time)
|
||||
else:
|
||||
self.is_sending = False
|
||||
Errors.generic_error("Could not connect to {0}:{1}".format(self.client_ip, self.client_port), msg=error)
|
||||
break
|
||||
if repeats > 0:
|
||||
repeats -= 1
|
||||
logger.debug("Sending finished")
|
||||
self.is_sending = False
|
||||
|
||||
def start_message_sending_thread(self, messages, sample_rates, modulators, project_manager):
|
||||
self.modulators = modulators
|
||||
self.project_manager = project_manager
|
||||
self.__sending_interrupt_requested = False
|
||||
self.sending_thread = Thread(target=self.__send_messages, args=(messages, sample_rates))
|
||||
self.sending_thread.daemon = True
|
||||
self.sending_thread.start()
|
||||
|
||||
def stop_sending_thread(self):
|
||||
self.__sending_interrupt_requested = True
|
||||
self.sending_stop_requested.emit()
|
||||
|
||||
@staticmethod
|
||||
def bytearray_to_bit_str(arr: bytearray) -> str:
|
||||
return "".join("{:08b}".format(a) for a in arr)
|
||||
|
||||
@staticmethod
|
||||
def bit_str_to_bytearray(bits: str) -> bytearray:
|
||||
bits += "0" * ((8 - len(bits) % 8) % 8)
|
||||
return bytearray((int(bits[i:i+8], 2) for i in range(0, len(bits), 8)))
|
@ -0,0 +1,2 @@
|
||||
With this plugin we support sending bytestreams via RfCat (e.g. using YARD Stick One).
|
||||
Therefore a new button below generator table will be created.
|
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>RfCatSettings</class>
|
||||
<widget class="QFrame" name="RfCatSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Frame</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Command to execute 'rfcat'. You can write the full path before 'rfcat' or just 'rfcat' when it is executable from every path.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="rfcat_executable">
|
||||
<property name="text">
|
||||
<string>rfcat</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="info">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -0,0 +1,69 @@
|
||||
from PyQt5.QtWidgets import QUndoCommand
|
||||
|
||||
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
|
||||
|
||||
|
||||
class ZeroHideAction(QUndoCommand):
|
||||
def __init__(self, protocol: ProtocolAnalyzer, following_zeros: int, view: int, zero_hide_offsets: dict):
|
||||
super().__init__()
|
||||
self.protocol = protocol
|
||||
self.following_zeros = following_zeros
|
||||
self.viewtype = view
|
||||
|
||||
self.setText("Hide zero sequences >= " + str(self.following_zeros))
|
||||
|
||||
self.zero_hide_offsets = zero_hide_offsets
|
||||
|
||||
def redo(self):
|
||||
factor = 1
|
||||
if self.viewtype == 1:
|
||||
factor = 4
|
||||
elif self.viewtype == 2:
|
||||
factor = 8
|
||||
|
||||
pa = self.protocol
|
||||
self.zero_hide_offsets.clear()
|
||||
for i in range(pa.num_messages):
|
||||
message = pa.messages[i]
|
||||
if self.viewtype == 0:
|
||||
data = message.decoded_bits_str
|
||||
elif self.viewtype == 1:
|
||||
data = message.decoded_hex_str
|
||||
else:
|
||||
data = message.decoded_ascii_str
|
||||
|
||||
zero_sequences = self.__get_zero_seq_indexes(data, self.following_zeros)
|
||||
|
||||
self.zero_hide_offsets[i] = {start: end-start for start, end in zero_sequences}
|
||||
for seq in reversed(zero_sequences):
|
||||
full_bits = pa.messages[i].decoded_bits
|
||||
start = seq[0] * factor
|
||||
end = seq[1] * factor
|
||||
pa.messages[i].decoded_bits = full_bits[:start] + full_bits[end:]
|
||||
|
||||
def undo(self):
|
||||
self.zero_hide_offsets.clear()
|
||||
self.protocol.clear_decoded_bits()
|
||||
|
||||
def __get_zero_seq_indexes(self, message: str, following_zeros: int):
|
||||
"""
|
||||
:rtype: list[tuple of int]
|
||||
"""
|
||||
|
||||
result = []
|
||||
if following_zeros > len(message):
|
||||
return result
|
||||
|
||||
zero_counter = 0
|
||||
for i in range(0, len(message)):
|
||||
if message[i] == "0":
|
||||
zero_counter += 1
|
||||
else:
|
||||
if zero_counter >= following_zeros:
|
||||
result.append((i-zero_counter, i))
|
||||
zero_counter = 0
|
||||
|
||||
if zero_counter >= following_zeros:
|
||||
result.append((len(message) - zero_counter, len(message)))
|
||||
|
||||
return result
|
@ -0,0 +1,36 @@
|
||||
from PyQt5.QtWidgets import QAction, QUndoStack
|
||||
|
||||
from ..Plugin import ProtocolPlugin
|
||||
from ..ZeroHide.ZeroHideAction import ZeroHideAction
|
||||
|
||||
|
||||
class ZeroHidePlugin(ProtocolPlugin):
|
||||
def __init__(self):
|
||||
super().__init__(name="ZeroHide")
|
||||
|
||||
self.following_zeros = 5 if 'following_zeros' not in self.qsettings.allKeys() else self.qsettings.value('following_zeros', type=int)
|
||||
self.undo_stack = None
|
||||
self.command = None
|
||||
self.zero_hide_offsets = dict()
|
||||
|
||||
def create_connects(self):
|
||||
self.settings_frame.spinBoxFollowingZeros.setValue(self.following_zeros)
|
||||
self.settings_frame.spinBoxFollowingZeros.valueChanged.connect(self.set_following_zeros)
|
||||
|
||||
def set_following_zeros(self):
|
||||
self.following_zeros = self.settings_frame.spinBoxFollowingZeros.value()
|
||||
self.qsettings.setValue('following_zeros', self.following_zeros)
|
||||
|
||||
def get_action(self, parent, undo_stack: QUndoStack, sel_range, protocol, view: int):
|
||||
"""
|
||||
:type parent: QTableView
|
||||
:type undo_stack: QUndoStack
|
||||
"""
|
||||
self.command = ZeroHideAction(protocol, self.following_zeros, view, self.zero_hide_offsets)
|
||||
action = QAction(self.command.text(), parent)
|
||||
action.triggered.connect(self.action_triggered)
|
||||
self.undo_stack = undo_stack
|
||||
return action
|
||||
|
||||
def action_triggered(self):
|
||||
self.undo_stack.push(self.command)
|
@ -0,0 +1 @@
|
||||
__author__ = 'joe'
|
@ -0,0 +1,2 @@
|
||||
This plugin allows you to entirely crop long sequences of zeros in your protocol and focus on relevant data.
|
||||
You can set a threshold below. All sequences of directly following zeros, which are longer than this threshold will be removed.
|
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FrameSyncCropSettings</class>
|
||||
<widget class="QFrame" name="FrameSyncCropSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>295</width>
|
||||
<height>79</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Frame</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Threshold:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinBoxFollowingZeros">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>9999999</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>following zeros</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>33</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -0,0 +1 @@
|
||||
__author__ = 'joe'
|
Reference in New Issue
Block a user