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

View File

@ -0,0 +1,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

View File

@ -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!

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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)

View File

@ -0,0 +1 @@
__author__ = 'joe'

View File

@ -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.

View File

@ -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>

View File

@ -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()

View File

@ -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.

View File

@ -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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This is the port number of your external TCP application. URH will connect to this port, when &lt;span style=&quot; font-weight:600;&quot;&gt;sending&lt;/span&gt; data.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This is the IP address of your external TCP application. URH will connect to this address, when &lt;span style=&quot; font-weight:600;&quot;&gt;sending&lt;/span&gt; data.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Open &lt;a href=&quot;open_proto_sniffer&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;protocol sniffer&lt;/span&gt;&lt;/a&gt; (&lt;span style=&quot; font-style:italic;&quot;&gt;File -&amp;gt; Sniff protocol...&lt;/span&gt;) to use it.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This is the TCP port number URH listens on. Your external TCP application can connect and send data to this port, &lt;span style=&quot; font-weight:600;&quot;&gt;while&lt;/span&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;receiving is active &lt;/span&gt;in URH.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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, &lt;span style=&quot; font-weight:600;&quot;&gt;while receiving is active &lt;/span&gt;in URH.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>

View 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)

View File

@ -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

View File

@ -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)))

View File

@ -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.

View File

@ -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>

View File

@ -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

View File

@ -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)

View File

@ -0,0 +1 @@
__author__ = 'joe'

View File

@ -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.

View File

@ -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>

View File

@ -0,0 +1 @@
__author__ = 'joe'