示例#1
0
class Settings(AdvancedSettings, ImageSettings, EncoderSettings,
               StreamingSettings, RecordingSettings, AudioSettings):
    def __init__(self):
        super().__init__()
        organization = "de.dunkelstern" if sys.platform.startswith(
            'darwin') else "dunkelstern"
        self.settings_instance = QSettings(organization, "PiCamPro")
        self.load()

    def save(self) -> None:
        """
        Save the settings to permanent storage
        """
        self.validate()

        for key, value in self.__dict__.items():
            if key.startswith('_') or callable(value) or key in (
                    'settings_instance', 'dirty_values', 'value_ranges'):
                continue
            section = 'unknown'
            for klass in self.__class__.__mro__:
                if issubclass(klass, SettingsProto):
                    if key in klass.__dict__:
                        section = klass.__name__.replace('Settings',
                                                         '').lower()
            self.settings_instance.setValue('{}/{}'.format(section, key),
                                            value)

    def load(self) -> None:
        """
        Load settings from permanent storage, usually will be performed on
        initialization.
        """
        for key in self.settings_instance.allKeys():
            value = self.settings_instance.value(key)
            klass, prop = key.split('/')
            klass = klass.title() + 'Settings'
            for c in Settings.__mro__:
                if c.__name__ == klass:
                    klass = c
                    break
            if klass.__dict__[prop] is True or klass.__dict__[prop] is False:
                value = bool(value)
            elif isinstance(klass.__dict__[prop], int):
                value = int(value)
            setattr(self, prop, value)
        self.dirty_values = {}
示例#2
0
    def test_save_settings(self, temp_settings):
        settings = temp_settings
        settings.exercises = [['sample3', [0, 0, 0]]]
        settings.instrument = 99
        settings.save_settings()

        q_settings = QSettings()
        saved_instrument = q_settings.value('instrument')
        saved_exercises = []
        q_settings.beginReadArray('exercises')
        for ex in q_settings.allKeys():
            if ex == 'size':
                continue
            saved_exercises.append([ex, q_settings.value(ex)])
        q_settings.endArray()

        assert saved_instrument == 99
        assert saved_exercises == [['sample3', [0, 0, 0]]]
示例#3
0
 def test_exercises_exist(self):
     settings_reader = QSettings()
     settings_reader.beginReadArray('exercises')
     assert len(settings_reader.allKeys()) > 1
     settings_reader.endArray()
示例#4
0
class PluginManager():
    """Creates and manages the plugins.

    Serves as a bridge between the editor and the plugins.

    """
    def __init__(self, parent):
        self.parent = parent
        self.settings = QSettings(c.SETTINGS_PATH, QSettings.IniFormat)
        self.keyboard_settings = QSettings(c.KEYBOARD_SETTINGS_PATH,
                                           QSettings.IniFormat)
        self.theme = self.settings.value(c.THEME, defaultValue=c.THEME_D)
        self.get_keyboard_shortcuts()
        self.get_plugins()
        self.do_not_delete = [
            c.SAVE_KEY, c.WORD_BY_WORD_KEY_NEXT, c.FORWARD_KEY,
            c.PLAY_PAUSE_KEY, c.BACKWARDS_KEY, c.HELP_KEY,
            c.WORD_BY_WORD_KEY_PREV
        ]

    def get_keyboard_shortcuts(self):
        """Saves the current occupied shortcuts in a list"""

        self.occupied_keys = []
        for key in self.keyboard_settings.allKeys():
            keyboard_value = self.keyboard_settings.value(key)
            if len(keyboard_value) != 0:
                self.occupied_keys.append(keyboard_value)

    def get_plugins(self):
        """Creates all Plugins and saves them in the plugin-list."""

        self.plugin_list = []
        for (dirpath, dirname, filenames) in walk(c.PLUGIN_PATH):
            for file in filenames:
                if c.PLUGIN_POST not in file or "__pycache__" in dirpath:
                    continue
                try:
                    module_path = os.path.join(dirpath, file)
                    temp_spec = ilu.spec_from_file_location(
                        file.split(".")[0], module_path)
                    temp_module = ilu.module_from_spec(temp_spec)
                    temp_spec.loader.exec_module(temp_module)
                    temp_plugin = temp_module.Plugin(self)
                    if issubclass(type(temp_plugin), IPlugin):
                        self.plugin_list.append(temp_plugin)
                        print("Imported {} as a Plugin".format(file))
                except BaseException as e:
                    print("Error while importing {} with Exception {}".format(
                        file, e))

    def get_plugin_licences(self):
        """Gets all used plugin-licences."""

        licences = {}
        for (dirpath, dirname, filenames) in walk(c.PLUGIN_PATH):
            if dirpath.lower().endswith("licences"):
                for filename in filenames:
                    licences[filename.split(".")[0]] = dirpath

        return licences

    def get_toolbar_actions(self, parent):
        """Queries all toolbar actions from the plugins.

        Handles the Keyboard-Shortcuts from this toolbar-actions and prevents overlapping.

        Args:
          parent: The Parent-Window.

        Returns:
          Returns list of the QActions from all plugins.
        """

        actions = []
        for plugin in self.plugin_list:
            plugin_actions = plugin.get_toolbar_action(parent)
            plugin_name = plugin.get_name()
            for action in plugin_actions:
                if action is None or plugin_name is None or not plugin_name:
                    continue
                key_name = plugin_name.upper().replace(" ", "_") + "_KEY"
                saved_key = self.keyboard_settings.value(key_name,
                                                         defaultValue=None)
                plugin_shortcut = action.shortcut()
                if saved_key is None and not plugin_shortcut.isEmpty():
                    plugin_shortcut_upper = plugin_shortcut.toString().upper()
                    if plugin_shortcut_upper not in self.occupied_keys:
                        self.keyboard_settings.setValue(
                            key_name, plugin_shortcut_upper)
                        self.occupied_keys.append(saved_key)
                    else:
                        self.keyboard_settings.setValue(key_name, "")
                    self.do_not_delete.append(key_name)
                if saved_key is not None:
                    action.setShortcut(QKeySequence(saved_key))
                actions.append(action)
        self.clear_keyboard_settings()
        return actions

    def clear_keyboard_settings(self):
        """Deletes all unused shortcuts from the keyboard settings."""

        all_keys = set(self.keyboard_settings.allKeys())
        to_delete = all_keys.difference(set(self.do_not_delete))
        for key in to_delete:
            print("removed", key)
            self.keyboard_settings.remove(key)

    def get_word_by_word_actions(self, word, meta_data, pos):
        """Queries all QPushButtons of the plugins.

        Args:
          word: str: The current word
          word_meta_data: List[dict]: The meta_data from the word.
          word_pos: int:  The current word position

        """

        for plugin in self.plugin_list:
            plugin.get_word_action(word, meta_data, pos)

    def project_loaded(self):
        """Calls the project_loaded method of all plugins."""

        for plugin in self.plugin_list:
            plugin.project_loaded()

    def add_new_word_by_word_action(self, btns, name, word, word_pos):
        """Adds a new Word-Action (QPushButton) to the editor word by word list.

        Args:
          btns: The QPushButtons from the plugin.
          name: The Name of the Buttons.
          word: The word for which these buttons are.
          word_pos: The position of the word.

        """

        btns = btns
        for btn in btns:
            btn.setShortcut(None)
        self.parent.add_new_word_by_word_action(btns, name, word, word_pos)

    def get_selection(self):
        """Gets the current selected word(s) in the editor.

        Returns:
          Selected word(s)

        """

        return self.parent.get_selection()

    def replace_selection(self, new_word):
        """Replaces the current selection in the editor.

        Args:
          new_word: the word(s) which should replace the current selection

        """

        self.parent.replace_selection(new_word)

    def get_text(self):
        """Returns the full text from the editor.

        Returns:
          The full text from the editor.

        """

        return self.parent.get_text()

    def set_text(self, new_text):
        """Sets the text in the editor with the given new_text.

        Args:
          new_text: The new text.

        """

        self.parent.set_text(new_text)

    def get_word_at(self, pos):
        """Gets a the word at the given position.

        Args:
          pos: Position of the desired word.

        Returns:
          The word at the position.
        """

        return self.parent.get_word_at(pos)

    def set_word_at(self, word, pos, replace_old):
        """Sets the word at a given position.

        Args:
          word: The word which should be set.
          pos: The position on which the word should be set.
          replace_old: true: replace the old word on the positon, false: set it before.

        """

        self.parent.set_word_at(word, pos, replace_old)

    def get_setting(self, key, default):
        """Get a specific setting.

        Args:
          key: The settings-key
          default: The default value if there is no setting for the key.

        Returns:
          The settings value for the given key or the default if nothing is there.
        """

        return self.settings.value(key, defaultValue=default)

    def get_language(self):
        """Returns the language of the current project.

        Returns:
            The language of the current project.

        """

        return self.parent.get_language()

    def set_hint_text(self, text):
        """Sets the hint text (left bottom corner) in the editor.

        Args:
          text: The Hint text.

        """

        self.parent.set_hint_text(text)

    def set_text_with_line_breaks(self, new_text):
        """Sets the Text in the editor but tries to restore the given linebreaks.

        Args:
          new_text: The new text.

        """

        self.parent.set_text_with_line_breaks(new_text)

    def get_project_folder_path(self):
        """Get the current project folder path

        Returns:
          Project folder path.

        """

        return self.parent.get_project_folder_path()
示例#5
0
class Warmuppy:

    def __init__(self):

        super().__init__()

        pygame.init()

        # Instance variables
        self.settings = QSettings()
        self.timers = []
        self.exercises = []
        self.notes = NOTES

        # Load settings
        self.bpm = int(self.settings.value('bpm', DEFAULT_BPM))
        self.cut = float(self.settings.value('cut', DEFAULT_CUT))
        self.step = int(self.settings.value('step', DEFAULT_STEP))
        prev = self.settings.value('preview', DEFAULT_PREVIEW)
        self.preview = prev in ['true', True]
        prolonged = self.settings.value('prolong', DEFAULT_PROLONG)
        self.prolong = prolonged in ['true', True]
        self.preview_time = int(
            self.settings.value('preview_time', DEFAULT_PREVIEW_TIME)
        )
        self.prolong_time = int(
            self.settings.value('prolong_time', DEFAULT_PROLONG_TIME)
        )
        self.note = int(self.settings.value('note', 0))
        self.octave = int(self.settings.value('octave', 3))
        self.instrument = int(
            self.settings.value('instrument', DEFAULT_INSTRUMENT)
        )

        self.settings.beginReadArray('exercises')
        for ex in self.settings.allKeys():
            if ex == 'size':
                continue
            exv = self.settings.value(ex)
            if isinstance(exv,str):
                exv = [exv]
            self.exercises.append([
                ex,
                exv
            ])
        self.settings.endArray()
        if not self.exercises:
            self.settings.beginWriteArray('exercises')
            for ex in DEFAULT_EXERCISES:
                self.exercises.append(ex)
                self.settings.setValue(ex[0], ex[1])
            self.settings.endArray()
        self.exercise = DEFAULT_EXERCISE
        self.settings.setValue('instrument', self.instrument)

    def change_note(self, note):
        note_id = self.notes.index(note)
        logging.debug(f"New note is {note} ({note_id})")
        self.note = note_id
        self.settings.setValue('note', note_id)

    def change_octave(self, octave):
        logging.debug(f"New octave is {octave}")
        self.octave = octave
        self.settings.setValue('octave', octave)

    def change_exercise(self, i):
        if not self.exercises:
            logging.debug("No exercise to change to, skipping")
            return
        try:
            ex = self.exercises[i]
            self.exercise = i
        except IndexError:
            ex = self.exercises[0]
            self.exercise = 0
        logging.debug(f"Selected exercise {ex[0]} ({ex[1]})")

    # Generate boilerplate setters
    for x in "bpm cut step preview_time prolong_time".split():
        exec(f'''def change_{x}(self,{x}):
                    logging.debug("New {x} : %s",{x})
                    self.settings.setValue('{x}',{x})
                    self.{x} = {x}''')

    def change_preview(self, p):
        logging.debug(f"Setting preview as {p}")
        self.preview = p
        self.settings.setValue('preview', p)

    def change_prolong(self, p):
        logging.debug(f"Setting prolong as {p}")
        self.prolong = p
        self.settings.setValue('prolong', p)

    # Go {step} semitones up or down
    def bump_note(self, up):

        # Beginning of 3rd octave + selected octave * semitones + note
        base_note = 24 + (self.octave - 1) * 12 + self.note
        logging.debug(f"Current note: {base_note}")
        # Highest playable note is the last of the 8th octave
        max_note = 8 * 12 + 24
        # Lowest is C3
        min_note = 25

        # Compute new note
        if up:
            if base_note < max_note:
                base_note += self.step
        else:
            if base_note > min_note:
                base_note -= self.step
        logging.debug(f"New note: {base_note}")

        # Compute new octave
        octave = (base_note - 24) // 12 + 1
        logging.debug(f"New octave should be #{octave}")

        # Compute relative note for the UI
        note_id = (base_note - 24) % 12
        note = self.notes[note_id]
        logging.debug(f"New note should be #{note}")

        return octave, note

    # Generate a midi with the exercise and then play it
    def play_exercise(self):
        self.stop()

        # Load selected exercise
        ex = self.exercises[self.exercise]
        name = ex[0]
        seq = ex[1]
        logging.debug(f"Starting exercise '{name}' (pattern: {seq})")

        # Init midi file
        midi_file = tempfile.NamedTemporaryFile(delete=False)
        mid = MidiFile()
        track = MidiTrack()
        mid.tracks.append(track)
        track.append(
            Message('program_change', program=self.instrument, time=0)
        )

        # Compute starting note: octave * semitones + relative note
        base_note = 24 + (self.octave - 1) * 12 + self.note
        # Seconds per beat
        seconds = 60 / self.bpm
        # Note duration is one beat minus the cut, in milliseconds
        base_duration = (seconds - self.cut) * 1000

        # Prepend the base note to the midi if the preview is selected
        timer_delay = 0
        if self.preview:
            timer_delay = int(base_duration*self.preview_time)
            track.append(
                Message('note_on', note=base_note, velocity=100, time=0)
            )
            track.append(
                Message('note_off', note=base_note, time=timer_delay)
            )

        timer_data = []
        # Add the rest of the notes
        for idx, p in enumerate(seq):

            # Normalise the note step as a string
            item = str(p)

            # Extract the step
            step = int(sub(r'[^0-9]','',item))

            # If the number has dashes or dots, add half a beat or a quarter beat for each, respectively
            duration = base_duration * (1 + item.count('-')*0.5 + item.count('.')*0.25)

            # Calculate percentage of current step
            current_index = idx + 1
            percent = (current_index / len(seq)) * 100

            # If this is the last note and the user wants to, prolong it
            if current_index == len(seq) and self.prolong:
                logging.debug(f"prolonging {step}")
                delay = int(base_duration*self.prolong_time)
            else:
                delay = int(duration)

            # Append the note to the midi
            track.append(
                Message('note_on', note=(base_note+step), velocity=100, time=0)
            )
            track.append(
                Message('note_off', note=(base_note+step), time=delay)
            )
            timer_data.append([percent, timer_delay])
            timer_delay += duration

        # Save midi file and load it with pygame separately,
        #   to avoid race conditions
        mid.save(file=midi_file)
        midi_file.flush()
        midi_file.close()
        pygame.mixer.music.load(midi_file.name)

        pygame.mixer.music.play()

        # Cleanup
        if 'WARMUPPY_KEEP_MIDI' in os.environ:
            copyfile(midi_file.name, os.environ['WARMUPPY_KEEP_MIDI'])
        os.remove(midi_file.name)

        return timer_data

    def stop(self):
        # Stop the music
        pygame.mixer.music.stop()

    # Settings window told us that something changed, so reload everything
    def reload_settings(self):
        logging.debug("Settings saved, reloading")
        self.instrument = int(
            self.settings.value('instrument', DEFAULT_INSTRUMENT)
        )
        self.exercises = []
        self.settings.beginReadArray('exercises')
        for ex in self.settings.allKeys():
            if ex == 'size':
                continue
            self.exercises.append([
                ex,
                self.settings.value(ex)
            ])
        self.settings.endArray()
示例#6
0
class SettingsWidget(QWidget):
    """Widget to show and edit the settings."""
    def __init__(self):
        super(SettingsWidget, self).__init__()
        self.settings = QSettings(c.SETTINGS_PATH, QSettings.IniFormat)
        self.keyboard_settings = QSettings(c.KEYBOARD_SETTINGS_PATH,
                                           QSettings.IniFormat)

        self.value_dict_settings = {}
        self.value_dict_keyboard = {}
        self.f_reg = re.compile("F[1-9]|1[0-2]")
        self.ctrl_reg = re.compile("CTRL\+[a-xA-X0-9]")
        self.forbidden_reg = re.compile("CTRL\+[vVcCzZyY]")

        v_box = QVBoxLayout()

        theme = QGroupBox("Theme")
        theme_layout = QFormLayout()
        theme_value = QComboBox()
        theme_value.addItem(c.THEME_D)
        theme_value.addItem(c.THEME_L)
        theme_value.setCurrentText(
            self.settings.value(c.THEME, defaultValue=c.THEME_D))
        theme_layout.addRow(QLabel(c.THEME.capitalize()), theme_value)
        theme.setLayout(theme_layout)
        self.value_dict_settings[c.THEME] = theme_value

        keyboard = QGroupBox("Keyboard")
        keyboard_layout = QFormLayout()
        info_label = QLabel("Only F-Keys and CTRL+ Combinations are allowed.")
        info_label.setWordWrap(True)
        keyboard_layout.addRow(info_label)

        for key in self.keyboard_settings.allKeys():
            line_edit = QLineEdit()
            line_edit.setText(self.keyboard_settings.value(key))
            keyboard_layout.addRow(key, line_edit)
            self.value_dict_keyboard[key] = line_edit

        keyboard.setLayout(keyboard_layout)

        plugins = QGroupBox("Plug-ins")
        plugins_layout = QFormLayout()
        show_empty_buttons_check = QCheckBox(
            "Show Plug-ins without Buttons in Word By Word - List")
        show_empty_buttons_check.setChecked(
            self.settings.value(c.SHOW_EMPTY_BUTTONS,
                                defaultValue=True,
                                type=bool))
        plugins_layout.addRow(show_empty_buttons_check)
        self.value_dict_settings[
            c.SHOW_EMPTY_BUTTONS] = show_empty_buttons_check
        plugins.setLayout(plugins_layout)

        self.note = QLabel("Settings will be applied after restart")

        save = QPushButton("Save")
        save.clicked.connect(self.save_values)

        v_box.addWidget(theme)
        v_box.addWidget(keyboard)
        v_box.addWidget(plugins)
        v_box.addWidget(self.note)
        v_box.addWidget(save)

        self.setLayout(v_box)

    def save_values(self):
        """Saves the current values."""

        saved = True
        for key in self.value_dict_settings.keys():
            widget = self.value_dict_settings.get(key)
            if isinstance(widget, QLineEdit):
                self.settings.setValue(key, widget.text().strip())
            if isinstance(widget, QComboBox):
                self.settings.setValue(key, widget.currentText().strip())
            if isinstance(widget, QCheckBox):
                self.settings.setValue(key, widget.isChecked())

        occupied = []
        self.keyboard_settings.clear()
        for key in self.value_dict_keyboard.keys():
            value = self.value_dict_keyboard.get(key).text().strip().replace(
                " ", "").upper()
            if len(value) == 0 or ((bool(self.f_reg.search(value))
                                    or bool(self.ctrl_reg.search(value))) and
                                   not bool(self.forbidden_reg.search(value))):
                if value not in occupied:
                    self.keyboard_settings.setValue(key, value)
                    if len(value) != 0:
                        occupied.append(value)
                else:
                    saved = False
                    self.note.setStyleSheet("QLabel {color: red;}")
                    self.note.setText("Duplicate Values found!")
                    self.keyboard_settings.setValue(key, "")
        if saved:
            self.note.setStyleSheet("QLabel {color: green;}")
            self.note.setText("Saved!")

    def reset_note(self):
        """Resets the little now text at the bottom."""

        self.note.setStyleSheet("")
        self.note.setText("Settings will be applied after restart")
示例#7
0
class Settings:
    def __init__(self):

        # Standard constructor stuff
        super().__init__()
        self.settings = QSettings()

        if not pygame.get_init():
            pygame.init()
        # Instance variables
        self.exercises = []
        self.instrument = int(self.settings.value('instrument'))

        # Load exercises from stored settings
        self.settings.beginReadArray('exercises')
        for ex in self.settings.allKeys():
            if ex == 'size':
                continue
            self.exercises.append([ex, self.settings.value(ex)])
        self.settings.endArray()

    def remove_exercise(self, exercise_name):
        # Replace self.exercises with a copy without the selected exercise
        new_exercises = []
        for ex in self.exercises:
            if ex[0] != exercise_name:
                new_exercises.append(ex)
        self.exercises = new_exercises

    def reload_exercise(self, exercise_name, exercise_text):
        # Load all exercise names
        exercise_names = []
        for ex in self.exercises:
            exercise_names.append(ex[0])
        new_exercises = []
        # If the reloaded exercise is existing then update it in memory,
        #   otherwise just add it
        exercise_contents = exercise_text.split()
        if exercise_name in exercise_names:
            for ex in self.exercises:
                if ex[0] == exercise_name:
                    new_exercises.append([ex[0], exercise_contents])
                else:
                    new_exercises.append([ex[0], ex[1]])
            self.exercises = new_exercises
        else:
            self.exercises.append([exercise_name, exercise_contents])

    def set_instrument(self, instrument_id):
        self.instrument = instrument_id

    def save_settings(self):
        self.settings.beginWriteArray('exercises')
        for key in self.settings.allKeys():
            self.settings.remove(key) if key != 'size' else None
        for ex in self.exercises:
            self.settings.setValue(ex[0], ex[1])
        self.settings.endArray()
        self.settings.setValue('instrument', self.instrument)
        self.settings.sync()

    def preview(self):
        pygame.mixer.music.stop()
        midi_file = tempfile.NamedTemporaryFile(delete=False)
        mid = MidiFile()
        track = MidiTrack()
        mid.tracks.append(track)
        instrument = self.instrument
        track.append(Message('program_change', program=instrument, time=0))
        note = 60
        track.append(Message('note_on', note=note, velocity=100, time=0))
        track.append(Message('note_off', note=note, time=2000))
        mid.save(file=midi_file)
        midi_file.flush()
        midi_file.close()
        pygame.mixer.music.load(midi_file.name)
        pygame.mixer.music.play()
        if 'WARMUPPY_KEEP_MIDI' in os.environ:
            copyfile(midi_file.name, os.environ['WARMUPPY_KEEP_MIDI'])
        os.remove(midi_file.name)
示例#8
0
class MainWidget(QWidget):
    port_select = Signal(str)

    port_connect = Signal()

    add_wifi = Signal(str, str)
    show_settings = Signal()

    load_settings = Signal()

    def __init__(self, parent=None):
        super(MainWidget, self).__init__(parent)
        self.ui = Ui_MainWidget()
        self.ui.setupUi(self)

        self.settings = QSettings()

        self.port: str = ''

        self.ui.pushButtonConnect.clicked.connect(self.on_connect_click)
        self.ui.listWidgetAvailablePorts.itemChanged.connect(
            self.on_port_select)
        self.ui.listWidgetAvailablePorts.itemClicked.connect(
            self.on_port_select)
        self.ui.pushButtonAddWiFiAP.clicked.connect(self.on_add_wifi_click)

        self.ui.pushButtonShowSettings.clicked.connect(self.on_show_settings)
        self.ui.pushButtonLoadSettings.clicked.connect(
            self.on_load_settings_click)

    def showEvent(self, event: QShowEvent):
        super().showEvent(event)
        self.ui.listWidgetAvailablePorts.clear()
        for port in list(serial.tools.list_ports.comports()):
            self.ui.listWidgetAvailablePorts.addItem(port.device)
        # if self.ui.listWidgetAvailablePorts.count() > 0:
        #     self.ui.listWidgetAvailablePorts.setCurrentRow(0)

        if 'mwg' in self.settings.allKeys():
            self.setGeometry(self.settings.value('mwg'))

    def closeEvent(self, event: PySide2.QtGui.QCloseEvent):
        super().closeEvent(event)
        self.settings.setValue('mwg', self.geometry())

    def on_connect_click(self):
        self.port_connect.emit()

    def on_port_select(self, item):
        self.port = item.text()
        self.port_select.emit(self.port)

    def on_add_wifi_click(self):
        self.add_wifi.emit(self.ui.lineEditSSID.text(),
                           self.ui.lineEditAPPassword.text())

    def on_show_settings(self):
        self.show_settings.emit()

    def on_load_settings_click(self):
        self.load_settings.emit()

    @Slot()
    def on_data_received(self, data: str):
        js = None
        try:
            js = json.loads(data)
        except JSONDecodeError as e:
            print(f'Error: {e} for message {data}')
            # self.ui.plainTextEditSerialLog.setPlainText(f'Error: {e} for message {data}')
        if js is not None:
            json_text = json.dumps(js, separators=(', ', ':'), indent=4)
            # print(json_text)
            self.ui.plainTextEditSerialLog.setPlainText(json_text)
        else:
            # self.ui.plainTextEditSerialLog.setPlainText(data)
            print(data)