From 44d9ea45616751204b5ca52e2976e93932253138 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Mon, 2 Dec 2024 10:01:14 +0100 Subject: [PATCH 01/17] build: Let target venv run combined with other targets The venv target always had to be created as a separate command. Now it is possible to use it with other targets in a command. --- Makefile | 55 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index 2b00ec0..412e413 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,14 @@ MAKEFLAGS = --no-print-directory --no-builtin-rules .DEFAULT_GOAL = all # Variables -PACKAGE = revpicommander -APP_NAME = RevPi\ Commander -APP_IDENT = org.revpimodio.revpicommander +PACKAGE = revpicommander +APP_NAME = RevPi\ Commander +APP_IDENT = org.revpimodio.revpicommander APPLE_SIG = "Developer ID Application: Sven Sager (U3N5843D9K)" +# Python interpreter to use for venv creation +SYSTEM_PYTHON = python3 + # Set path to create the virtual environment with package name ifdef PYTHON3_VENV VENV_PATH = $(PYTHON3_VENV)/$(PACKAGE) @@ -15,39 +18,37 @@ else VENV_PATH = venv endif -# If virtualenv exists, use it. If not, use PATH to find commands -SYSTEM_PYTHON = python3 -PYTHON = $(or $(wildcard $(VENV_PATH)/bin/python), $(SYSTEM_PYTHON)) - -APP_VERSION = $(shell "$(PYTHON)" src/$(PACKAGE) --version | cut -d ' ' -f 2) - -all: build_ui build_rc test build - +# Set targets for "all"-target +all: build-ui build-rc build .PHONY: all -## Environment -venv-info: - @echo Environment for $(APP_NAME) $(APP_VERSION) - @echo Using path: "$(VENV_PATH)" - exit 0 - +## Virtual environment creation with SYSTEM_PYTHON venv: # Start with empty environment "$(SYSTEM_PYTHON)" -m venv "$(VENV_PATH)" - source "$(VENV_PATH)/bin/activate" && \ - python3 -m pip install --upgrade pip && \ - python3 -m pip install -r requirements.txt - exit 0 + "$(VENV_PATH)/bin/pip" install --upgrade pip + "$(VENV_PATH)/bin/pip" install --upgrade -r requirements.txt venv-ssp: # Include system installed site-packages and add just missing modules "$(SYSTEM_PYTHON)" -m venv --system-site-packages "$(VENV_PATH)" - source "$(VENV_PATH)/bin/activate" && \ - python3 -m pip install --upgrade pip && \ - python3 -m pip install -r requirements.txt - exit 0 + "$(VENV_PATH)/bin/pip" install --upgrade pip + "$(VENV_PATH)/bin/pip" install --upgrade -r requirements.txt -.PHONY: venv-info venv venv-ssp +.PHONY: venv venv-ssp + +# Choose python interpreter from venv or system +PYTHON = $(or $(wildcard $(VENV_PATH)/bin/python), $(SYSTEM_PYTHON)) + +# Read app version from program +APP_VERSION = $(shell "$(PYTHON)" src/$(PACKAGE) --version | cut -d ' ' -f 2) + +# Environment info +venv-info: + @echo Environment for $(APP_NAME) $(APP_VERSION) + @echo Using path: "$(VENV_PATH)" + +.PHONY: venv-info ## Compile Qt UI files to python code build-ui: @@ -164,6 +165,8 @@ clean: rm -rf build dist src/*.egg-info # PyInstaller created files rm -rf *.spec + # Pycaches + find . -type d -name '__pycache__' -exec rm -r {} \+ distclean: clean # Virtual environment From 1a087f213df5de4a17ecafd1d3eab695e56ca4a7 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Mon, 2 Dec 2024 10:37:40 +0100 Subject: [PATCH 02/17] build: Use right backslashes in make.bat file for Windows Calling programs is written with a single backslash. When passing parameters to a program, double backslashes should be used, otherwise a single one could be interpreted as an escape character. --- make.bat | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/make.bat b/make.bat index 4975839..cd2bb32 100644 --- a/make.bat +++ b/make.bat @@ -2,7 +2,7 @@ set PACKAGE=revpicommander set APP_NAME=RevPi Commander -set PYTHON=venv\\Scripts\\python.exe +set PYTHON=venv\Scripts\python.exe if "%1" == "venv" goto venv if "%1" == "test" goto test @@ -24,7 +24,7 @@ goto end :venv python -m venv venv - venv\\Scripts\\pip.exe install -r requirements.txt + venv\Scripts\pip.exe install -r requirements.txt goto end :test @@ -41,7 +41,7 @@ goto end mkdir dist %PYTHON% -m piplicenses ^ --format=markdown ^ - --output-file dist/bundled-libraries.md + --output-file dist\\bundled-libraries.md %PYTHON% -m piplicenses ^ --with-authors ^ --with-urls ^ @@ -49,7 +49,7 @@ goto end --with-license-file ^ --no-license-path ^ --format=json ^ - --output-file dist/open-source-licenses.json + --output-file dist\\open-source-licenses.json %PYTHON% -m piplicenses ^ --with-authors ^ --with-urls ^ @@ -57,18 +57,18 @@ goto end --with-license-file ^ --no-license-path ^ --format=plain-vertical ^ - --output-file dist/open-source-licenses.txt + --output-file dist\\open-source-licenses.txt %PYTHON% -m PyInstaller -n "%APP_NAME%" ^ - --add-data="dist/bundled-libraries.md;%PACKAGE%\open-source-licenses" ^ - --add-data="dist/open-source-licenses.*;%PACKAGE%\open-source-licenses" ^ - --add-data="src\%PACKAGE%\locale;.\%PACKAGE%\locale" ^ - --add-data="data\%PACKAGE%.ico;." ^ + --add-data="dist\\bundled-libraries.md;%PACKAGE%\\open-source-licenses" ^ + --add-data="dist\\open-source-licenses.*;%PACKAGE%\\open-source-licenses" ^ + --add-data="src\\%PACKAGE%\\locale;.\\%PACKAGE%\\locale" ^ + --add-data="data\\%PACKAGE%.ico;." ^ --icon=data\\%PACKAGE%.ico ^ --noconfirm ^ --clean ^ --onedir ^ --windowed ^ - src\\%PACKAGE%\\__main__.py + src\%PACKAGE%\__main__.py goto end :distclean @@ -76,7 +76,7 @@ goto end :clean rmdir /S /Q .pytest_cache - rmdir /S /Q build dist src\%PACKAGE%.egg-info + rmdir /S /Q build dist src\\%PACKAGE%.egg-info del /Q *.spec :end From f08ea8ebc6f5b04b51c3f937480bd5649ace0ec7 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Mon, 2 Dec 2024 10:49:31 +0100 Subject: [PATCH 03/17] build(app): Collect complete zeroconf module with PyInstaller From `zeroconf` version 0.128.5 `zeroconf._utils` has been changed. The PyInstaller does not collect all submodules automatically, this is now forced via `--collect-submodules`. --- Makefile | 2 ++ make.bat | 1 + 2 files changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 412e413..cb4ac02 100644 --- a/Makefile +++ b/Makefile @@ -110,6 +110,7 @@ app-licenses: app: build-ui build-rc app-licenses "$(PYTHON)" -m PyInstaller -n $(APP_NAME) \ + --collect-submodules=zeroconf \ --add-data="src/$(PACKAGE)/locale:./$(PACKAGE)/locale" \ --add-data="dist/bundled-libraries.md:$(PACKAGE)/open-source-licenses" \ --add-data="dist/open-source-licenses.*:$(PACKAGE)/open-source-licenses" \ @@ -124,6 +125,7 @@ app: build-ui build-rc app-licenses app-mac: build-ui build-rc app-licenses "$(PYTHON)" -m PyInstaller -n $(APP_NAME) \ + --collect-submodules=zeroconf \ --add-data="src/$(PACKAGE)/locale:./$(PACKAGE)/locale" \ --add-data="dist/bundled-libraries.md:$(PACKAGE)/open-source-licenses" \ --add-data="dist/open-source-licenses.*:$(PACKAGE)/open-source-licenses" \ diff --git a/make.bat b/make.bat index cd2bb32..106a350 100644 --- a/make.bat +++ b/make.bat @@ -59,6 +59,7 @@ goto end --format=plain-vertical ^ --output-file dist\\open-source-licenses.txt %PYTHON% -m PyInstaller -n "%APP_NAME%" ^ + --collect-submodules=zeroconf ^ --add-data="dist\\bundled-libraries.md;%PACKAGE%\\open-source-licenses" ^ --add-data="dist\\open-source-licenses.*;%PACKAGE%\\open-source-licenses" ^ --add-data="src\\%PACKAGE%\\locale;.\\%PACKAGE%\\locale" ^ From 6504fe962b451d1f4b06f3ece94463a11b19964b Mon Sep 17 00:00:00 2001 From: Eva-Maria Zanger Date: Wed, 20 May 2026 15:13:32 +0200 Subject: [PATCH 04/17] refactor: Improve GUI text and translations Signed-off-by: Sven Sager --- src/revpicommander/aclmanager.py | 20 +- src/revpicommander/avahisearch.py | 2 +- src/revpicommander/backgroundworker.py | 2 +- src/revpicommander/debugcontrol.py | 10 +- src/revpicommander/debugios.py | 20 +- src/revpicommander/helper.py | 48 +- .../locale/revpicommander_de.qm | Bin 58810 -> 57430 bytes .../locale/revpicommander_de.ts | 2148 +++++++++-------- src/revpicommander/mqttmanager.py | 4 +- src/revpicommander/revpicommander.py | 77 +- src/revpicommander/revpifiles.py | 60 +- src/revpicommander/revpiinfo.py | 2 +- src/revpicommander/revpilogfile.py | 2 +- src/revpicommander/revpioption.py | 32 +- src/revpicommander/revpiplclist.py | 6 +- src/revpicommander/revpiprogram.py | 104 +- src/revpicommander/simulator.py | 4 +- src/revpicommander/sshauth.py | 8 +- src/revpicommander/ui/aclmanager_ui.py | 8 +- src/revpicommander/ui/avahisearch_ui.py | 18 +- src/revpicommander/ui/backgroundworker_ui.py | 4 +- src/revpicommander/ui/debugcontrol_ui.py | 24 +- src/revpicommander/ui/debugios_ui.py | 2 +- src/revpicommander/ui/files_ui.py | 10 +- src/revpicommander/ui/mqttmanager_ui.py | 8 +- src/revpicommander/ui/oss_licenses_ui.py | 8 +- src/revpicommander/ui/revpicommander_ui.py | 12 +- src/revpicommander/ui/revpiinfo_ui.py | 11 +- src/revpicommander/ui/revpilogfile_ui.py | 10 +- src/revpicommander/ui/revpioption_ui.py | 30 +- src/revpicommander/ui/revpiplclist_ui.py | 12 +- src/revpicommander/ui/revpiprogram_ui.py | 28 +- src/revpicommander/ui/simulator_ui.py | 22 +- src/revpicommander/ui/sshauth_ui.py | 10 +- ui_dev/aclmanager.ui | 6 +- ui_dev/avahisearch.ui | 16 +- ui_dev/backgroundworker.ui | 2 +- ui_dev/debugcontrol.ui | 22 +- ui_dev/files.ui | 8 +- ui_dev/mqttmanager.ui | 6 +- ui_dev/oss_licenses.ui | 6 +- ui_dev/revpicommander.ui | 10 +- ui_dev/revpiinfo.ui | 9 +- ui_dev/revpilogfile.ui | 8 +- ui_dev/revpioption.ui | 28 +- ui_dev/revpiplclist.ui | 10 +- ui_dev/revpiprogram.ui | 26 +- ui_dev/simulator.ui | 20 +- ui_dev/sshauth.ui | 8 +- 49 files changed, 1490 insertions(+), 1461 deletions(-) diff --git a/src/revpicommander/aclmanager.py b/src/revpicommander/aclmanager.py index 348e264..966a7c8 100644 --- a/src/revpicommander/aclmanager.py +++ b/src/revpicommander/aclmanager.py @@ -57,10 +57,10 @@ class AclManager(QtWidgets.QDialog, Ui_diag_aclmanager): self.__mrk_message_shown += 1 QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "There are errors in the ACL list!\nCheck the ALC levels of the " - "red lines in the table. The ACL levels or ip addresses are " - "invalid. If you save this dialog again, we will remove the " - "wrong entries automatically." + "ACL list contains errors.\nCheck rows highlighted in red. " + "The ACL levels or IP addresses are invalid. " + "If you save this dialog, invalid entries will be " + "removed automatically." ) ) return True @@ -97,7 +97,7 @@ class AclManager(QtWidgets.QDialog, Ui_diag_aclmanager): if self._changes_done(): ask = QtWidgets.QMessageBox.question( self, self.tr("Question"), self.tr( - "Do you really want to quit? \nUnsaved changes will be lost" + "Quit without saving?\nUnsaved changes will be lost." ) ) == QtWidgets.QMessageBox.Yes @@ -126,7 +126,7 @@ class AclManager(QtWidgets.QDialog, Ui_diag_aclmanager): while self.tb_acls.rowCount() > 0: self.tb_acls.removeRow(0) self.cbb_level.clear() - self.cbb_level.addItem(self.tr("Select..."), -1) + self.cbb_level.addItem(self.tr("Select"), -1) self.lbl_level_info.clear() self.__re_ipacl = compile( @@ -186,9 +186,9 @@ class AclManager(QtWidgets.QDialog, Ui_diag_aclmanager): has_error = False tool_tip = "" else: - brush = QtGui.QBrush(QtGui.QColor("red")) + brush = QtGui.QBrush(QtGui.QColor("#CC6666")) has_error = True - tool_tip = self.tr("This entry has an invalid ACL level or wrong IP format!") + tool_tip = self.tr("Invalid ACL level or IP address format.") for row in range(self.tb_acls.rowCount()): item_0 = self.tb_acls.item(row, 0) @@ -370,8 +370,8 @@ class AclManager(QtWidgets.QDialog, Ui_diag_aclmanager): else: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Can not save new ACL entry! Check format of ip address " - "and acl level is in value list." + "Cannot save ACL entry. Check IP address format " + "and ACL level." ) ) diff --git a/src/revpicommander/avahisearch.py b/src/revpicommander/avahisearch.py index 6c93953..dc20d4f 100644 --- a/src/revpicommander/avahisearch.py +++ b/src/revpicommander/avahisearch.py @@ -167,7 +167,7 @@ class AvahiSearch(QtWidgets.QDialog, Ui_diag_search): return None settings = RevPiSettings() - settings.folder = self.tr("Auto discovered") + settings.folder = self.tr("Automatically discovered") settings.name = item.data(WidgetData.host_name) settings.address = item.data(WidgetData.address) settings.port = item.data(WidgetData.port) diff --git a/src/revpicommander/backgroundworker.py b/src/revpicommander/backgroundworker.py index 5e55311..546a560 100644 --- a/src/revpicommander/backgroundworker.py +++ b/src/revpicommander/backgroundworker.py @@ -20,7 +20,7 @@ class BackgroundWorker(QtCore.QThread): def __init__(self, parent=None, interruption_text: str = None): super().__init__(parent) - self._interruption_text = interruption_text or self.tr("User requested cancellation...") + self._interruption_text = interruption_text or self.tr("Cancellation requested") def check_cancel(self) -> bool: """ diff --git a/src/revpicommander/debugcontrol.py b/src/revpicommander/debugcontrol.py index bfccec0..2e52a35 100644 --- a/src/revpicommander/debugcontrol.py +++ b/src/revpicommander/debugcontrol.py @@ -135,7 +135,7 @@ class DebugControl(QtWidgets.QWidget, Ui_wid_debugcontrol): self.cbx_refresh.setChecked(False) for win in self.dict_windows.values(): # type: DebugIos win.stat_bar.showMessage( - self.tr("Driver reset for piControl detected."), + self.tr("piControl driver reset detected"), 10000 ) self.reload_devices() @@ -175,7 +175,7 @@ class DebugControl(QtWidgets.QWidget, Ui_wid_debugcontrol): for win in self.dict_windows.values(): # type: DebugIos win.stat_bar.setStyleSheet("background-color: red;") win.stat_bar.showMessage(self.tr( - "Error while getting values from Revolution Pi." + "Error while getting values from RevPi" ), 5000) return @@ -229,9 +229,9 @@ class DebugControl(QtWidgets.QWidget, Ui_wid_debugcontrol): win.set_value(io[0], value_procimg) if self.cbx_refresh.isChecked(): - win.stat_bar.showMessage(self.tr("Auto update values..."), 1000) + win.stat_bar.showMessage(self.tr("Updating values"), 1000) else: - win.stat_bar.showMessage(self.tr("Values updated..."), 2000) + win.stat_bar.showMessage(self.tr("Values updated"), 2000) if self.driver_reset_detected: # Show values, which we can recover to empty process image @@ -269,7 +269,7 @@ class DebugControl(QtWidgets.QWidget, Ui_wid_debugcontrol): # Create error message device_name = self.dict_devices[lst_result[0]] str_errmsg += self.tr( - "Error set value of device '{0}' Output '{1}': {2}\n" + "Error setting value for device '{0}', output '{1}': {2}\n" ).format(device_name, lst_result[1], lst_result[3]) else: self.dict_windows[lst_result[0]].reset_change_value_colors(lst_result[1]) diff --git a/src/revpicommander/debugios.py b/src/revpicommander/debugios.py index 2dd99ec..0e99182 100644 --- a/src/revpicommander/debugios.py +++ b/src/revpicommander/debugios.py @@ -266,12 +266,12 @@ class DebugIos(QtWidgets.QMainWindow, Ui_win_debugios): switching_cycles = helper.cm.call_remote_function( "ps_switching_cycles", sender.objectName(), - default_value=self.tr("Can not display"), + default_value=self.tr("Cannot display"), ) if type(switching_cycles) is not list: switching_cycles = [switching_cycles] for i in range(len(switching_cycles)): - relais_counter = self.tr(" Relais {0}").format(i + 1) + relais_counter = self.tr(" Relay {0}").format(i + 1) if len(switching_cycles) == 1: relais_counter = "" men.addAction( @@ -284,29 +284,29 @@ class DebugIos(QtWidgets.QMainWindow, Ui_win_debugios): if sender.property("byte_length") > 4: # Textbox needs format buttons - act_as_text = QtWidgets.QAction(self.tr("as text")) + act_as_text = QtWidgets.QAction(self.tr("As text")) men.addAction(act_as_text) - act_as_number = QtWidgets.QAction(self.tr("as number")) + act_as_number = QtWidgets.QAction(self.tr("As number")) men.addAction(act_as_number) men.addSeparator() else: act_as_text = None act_as_number = None - act_signed = QtWidgets.QAction(self.tr("signed"), men) + act_signed = QtWidgets.QAction(self.tr("Signed"), men) act_signed.setCheckable(True) act_signed.setChecked(sender.property("signed") or False) if sender.property("bit_address") == -1: men.addAction(act_signed) - act_byteorder = QtWidgets.QAction(self.tr("big_endian"), men) + act_byteorder = QtWidgets.QAction(self.tr("Big-endian"), men) act_byteorder.setCheckable(True) act_byteorder.setChecked(sender.property("big_endian") or False) if sender.property("bit_address") == -1: men.addAction(act_byteorder) if sender.property("byte_length") > 2: - act_wordorder = QtWidgets.QAction(self.tr("switch wordorder")) + act_wordorder = QtWidgets.QAction(self.tr("Swap word order")) act_wordorder.setCheckable(True) act_wordorder.setChecked(sender.property("word_order") == "big") men.addAction(act_wordorder) @@ -338,7 +338,7 @@ class DebugIos(QtWidgets.QMainWindow, Ui_win_debugios): helper.cm.call_remote_function("ps_reset_counter", sender.objectName(), raise_exception=True) except Exception as e: log.error(e) - QtWidgets.QMessageBox.critical(self, self.tr("Error"), self.tr("Could not reset the counter value")) + QtWidgets.QMessageBox.critical(self, self.tr("Error"), self.tr("Cannot reset counter value.")) if sender.property("frm"): sender.setProperty("frm", "{0}{1}".format( @@ -451,8 +451,8 @@ class DebugIos(QtWidgets.QMainWindow, Ui_win_debugios): except UnicodeDecodeError: child.setProperty("struct_type", "number") QtWidgets.QMessageBox.warning( - self, self.tr("Can not use format text"), self.tr( - "Can not convert bytes {0} to a text for IO '{1}'. Switch to number format instead!" + self, self.tr("Cannot use text format."), self.tr( + "Cannot convert bytes {0} to text for I/O '{1}'. Switch to number format instead." ).format(value, io_name) ) if child.property("struct_type") == "number": diff --git a/src/revpicommander/helper.py b/src/revpicommander/helper.py index dd747e0..cf01c23 100644 --- a/src/revpicommander/helper.py +++ b/src/revpicommander/helper.py @@ -372,7 +372,7 @@ class ConnectionManager(QtCore.QThread): self._clear_settings() self.connect_error.emit( self.tr("Error"), self.tr( - "Could not establish a SSH connection to server:\n\n{0}" + "Cannot connect to SSH server:\n\n{0}" ).format(str(e)), ConnectionFail.SSH_CONNECT, revpi_settings, @@ -398,11 +398,11 @@ class ConnectionManager(QtCore.QThread): if revpi_settings.ssh_use_tunnel: self.connect_error.emit( self.tr("Error"), self.tr( - "Can not connect to RevPiPyLoad service through SSH tunnel!\n\n" - "This could have the following reasons:\n" - "- The RevPiPyLoad service is not running (activate it on your Revolution Pi)\n" - "- The RevPiPyLoad XML-RPC service is NOT bind to localhost\n" - "- The ACL permission is not set for 127.0.0.1!!!" + "Cannot connect to RevPiPyLoad service through SSH tunnel.\n\n" + "Possible reasons:\n" + "- RevPiPyLoad service is not running. Activate service on your RevPi.\n" + "- RevPiPyLoad XML-RPC service is not bound to localhost.\n" + "- ACL permission is not set for 127.0.0.1." ), ConnectionFail.NO_XML_RPC_VIA_TUNNEL, revpi_settings, @@ -410,14 +410,14 @@ class ConnectionManager(QtCore.QThread): else: self.connect_error.emit( self.tr("Error"), self.tr( - "Can not connect to RevPiPyLoad XML-RPC service! \n\n" - "This could have the following reasons:\n" - "- The Revolution Pi is not online\n" - "- The RevPiPyLoad service is not running (activate it on your Revolution Pi)\n" - "- The RevPiPyLoad XML-RPC service is bind to localhost, only\n" - "- The ACL permission is not set for your IP!!!\n\n" - "Use 'Connect via SSH' to use an encrypted connection or run " - "'sudo revpipyload_secure_installation' on Revolution Pi to setup direct remote access!" + "Cannot connect to RevPiPyLoad XML-RPC service.\n\n" + "Possible reasons:\n" + "- RevPi is offline.\n" + "- RevPiPyLoad service is not running. Activate service on your RevPi.\n" + "- RevPiPyLoad XML-RPC service is bound to localhost only.\n" + "- The ACL permission is not set for your IP.\n\n" + "Use 'Connect via SSH' to use encrypted connection or run " + "'sudo revpipyload_secure_installation' on RevPi to set up direct remote access." ), ConnectionFail.NO_XML_RPC, revpi_settings, @@ -546,10 +546,10 @@ class ConnectionManager(QtCore.QThread): if self._revpi is not None: sp = None - self.status_changed.emit(self.tr("SIMULATING"), "yellow") + self.status_changed.emit(self.tr("Simulating"), "#E3DE48") elif self._cli is None: sp = None - self.status_changed.emit(self.tr("NOT CONNECTED"), "lightblue") + self.status_changed.emit(self.tr("Not connected"), "lightblue") elif not self._cli_connect.empty(): # Get new connection information to create object in this thread item = self._cli_connect.get() @@ -566,7 +566,7 @@ class ConnectionManager(QtCore.QThread): log.warning(e) except Exception as e: log.warning(e) - self.status_changed.emit(self.tr("SERVER ERROR"), "red") + self.status_changed.emit(self.tr("Server error"), "#CC6666") self._has_error = True self.connection_error_observed.emit("{0} | {1}".format(e, type(e))) @@ -596,19 +596,19 @@ class ConnectionManager(QtCore.QThread): self.connection_recovered.emit() if plc_exit_code == -1: - self.status_changed.emit(self.tr("RUNNING"), "green") + self.status_changed.emit(self.tr("Running"), "green") elif plc_exit_code == -2: - self.status_changed.emit(self.tr("PLC FILE NOT FOUND"), "red") + self.status_changed.emit(self.tr("PLC file not found"), "#CC6666") elif plc_exit_code == -3: - self.status_changed.emit(self.tr("NOT RUNNING (NO STATUS)"), "yellow") + self.status_changed.emit(self.tr("Not running (no status)"), "#E3DE48") elif plc_exit_code == -9: - self.status_changed.emit(self.tr("PROGRAM KILLED"), "red") + self.status_changed.emit(self.tr("Program killed"), "#CC6666") elif plc_exit_code == -15: - self.status_changed.emit(self.tr("PROGRAM TERMED"), "red") + self.status_changed.emit(self.tr("Program terminated"), "#CC6666") elif plc_exit_code == 0: - self.status_changed.emit(self.tr("NOT RUNNING"), "yellow") + self.status_changed.emit(self.tr("Not running"), "#E3DE48") else: - self.status_changed.emit(self.tr("FINISHED WITH CODE {0}").format(plc_exit_code), "yellow") + self.status_changed.emit(self.tr("Finished with exit code {0}").format(plc_exit_code), "#E3DE48") self.msleep(self._cycle_time) diff --git a/src/revpicommander/locale/revpicommander_de.qm b/src/revpicommander/locale/revpicommander_de.qm index 059aa4780842814a6172329fae4088ffd872faf3..6d5c6367723693a22c1ab9a37e09a5a0452a8de5 100644 GIT binary patch literal 57430 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 E1?v|P1ONa4 literal 58810 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 V;)u3@5YVqLVRNqosnpig{2wPss^kCw diff --git a/src/revpicommander/locale/revpicommander_de.ts b/src/revpicommander/locale/revpicommander_de.ts index 4f61164..c6f9f7e 100644 --- a/src/revpicommander/locale/revpicommander_de.ts +++ b/src/revpicommander/locale/revpicommander_de.ts @@ -8,18 +8,18 @@ Error Fehler - - - There are errors in the ACL list! -Check the ALC levels of the red lines in the table. The ACL levels or ip addresses are invalid. If you save this dialog again, we will remove the wrong entries automatically. - Es gibt Fehler in der ACL Liste -Prüfe die roten Einträge in der Tabelle. ACL Level oder IP Adressen sind nicht gültig. Beim erneuten Speichern werden ungültige Einträge automatisch gelöscht. - Unsaved entry Nicht gespeicherter Eintrag + + + ACL list contains errors. +Check rows highlighted in red. The ACL levels or IP addresses are invalid. If you save this dialog, invalid entries will be removed automatically. + Die ACL-Liste enthält Fehler. +Überprüfe die ACL-Stufen der rot markierten Zeilen. Die ACL-Stufen oder IP-Adressen sind ungültig. Beim erneuten Speichern werden die ungültigen Einträge automatisch entfernt. + You worked on a new ACL entry. Do you want to save that entry, too? @@ -32,25 +32,30 @@ Prüfe die roten Einträge in der Tabelle. ACL Level oder IP Adressen sind nicht - Do you really want to quit? -Unsaved changes will be lost - Soll das Fenster wirklich geschlossen werden? -Nicht gespeicherte Änderunen gehen verloren + Quit without saving? +Unsaved changes will be lost. + Ohne Speichern schließen? +Nicht gespeicherte Änderungen gehen verloren. + + + + Invalid ACL level or IP address format. + Ungültige ACL-Stufe oder IP-Adressformat. + + + + Cannot save ACL entry. Check IP address format and ACL level. + ACL-Eintrag kann nicht gespeichert werden. IP-Adressformat und ACL-Stufe prüfen. - Select... - Auswahl... + Select + Auswählen Level - - - - - This entry has an invalid ACL level or wrong IP format! - Dieser Eintrag hat ein ungütiges ACL Lebel oder ein falsches IP Format! + Stufe @@ -59,17 +64,12 @@ Nicht gespeicherte Änderunen gehen verloren Sollen die folgenden Einträge wirklich gelöscht werden? {0} - - - Can not save new ACL entry! Check format of ip address and acl level is in value list. - Kann neuen ACL Eintrag nicht speichern! Bitte IP Adresse und ACL Level prüfen. - AvahiSearch - Auto discovered + Automatically discovered Automatisch erkannt @@ -80,56 +80,6 @@ Nicht gespeicherte Änderunen gehen verloren ConnectionManager - - - SIMULATING - SIMULATION - - - - NOT CONNECTED - NICHT VERBUNDEN - - - - SERVER ERROR - SERVER FEHLER - - - - RUNNING - LÄUFT - - - - PLC FILE NOT FOUND - SPS PROGRAMM NICHT GEFUNDEN - - - - NOT RUNNING (NO STATUS) - LÄUFT NICHT (KEIN STATUS) - - - - PROGRAM KILLED - PROGRAMM GETÖTET - - - - PROGRAM TERMED - PROGRAMM BEENDET - - - - NOT RUNNING - LÄUFT NICHT - - - - FINISHED WITH CODE {0} - BEENDET MIT CODE {0} - Error @@ -140,83 +90,133 @@ Nicht gespeicherte Änderunen gehen verloren The combination of username and password was rejected from the SSH server. Try again. - Die Kombination aus Benutzername und Password wurden vom SSH Server abgelehnt + Die Kombination aus Benutzername und Passwort wurde vom SSH-Server abgelehnt. -Bitte erneut versuchen. +Erneut versuchen. - Could not establish a SSH connection to server: + Cannot connect to SSH server: {0} - Konnte keine Verbindung zum SSH Server herstellen: + Kann keine Verbindung zum SSH-Server herstellen: {0} - - Can not connect to RevPiPyLoad XML-RPC service! + + Cannot connect to RevPiPyLoad service through SSH tunnel. -This could have the following reasons: -- The Revolution Pi is not online -- The RevPiPyLoad service is not running (activate it on your Revolution Pi) -- The RevPiPyLoad XML-RPC service is bind to localhost, only -- The ACL permission is not set for your IP!!! +Possible reasons: +- RevPiPyLoad service is not running. Activate service on your RevPi. +- RevPiPyLoad XML-RPC service is not bound to localhost. +- ACL permission is not set for 127.0.0.1. + Verbindung zum RevPiPyLoad-Dienst über den SSH-Tunnel kann nicht hergestellt werden. -Use 'Connect via SSH' to use an encrypted connection or run 'sudo revpipyload_secure_installation' on Revolution Pi to setup direct remote access! - Kann keine Verbindung zum RevPiPyLoad XML-RPC Dienst herstellen! - -Das kann eine der folgenden Ursachen haben: -- Der Revolution Pi ist nicht online -- Der RevPiPyLoad Dienst läuft nicht (aktiviere Diesen auf dem Revolution Pi) -- Der RevPiPyLoad XML-RPC Dienst ist nur an localhost gebunden -- Die Berechtigungen sind nicht für diese IP gesetzt!!! - -Benutze "Über SSH verbinden" um eine verschlüsselte Verbindung aufzubauen oder führe 'sudo revpipyload_secure_installation' auf dem Revolution Pi aus, um eine direkte Verbindung zu konfigurieren! +Mögliche Ursachen: +- Der RevPiPyLoad-Dienst läuft nicht. Dienst auf dem RevPi aktivieren. +- Der XML-RPC-Dienst von RevPiPyLoad ist nicht an localhost gebunden. +- Für 127.0.0.1 ist keine ACL-Berechtigung gesetzt. - - Can not connect to RevPiPyLoad service through SSH tunnel! + + Cannot connect to RevPiPyLoad XML-RPC service. -This could have the following reasons: -- The RevPiPyLoad service is not running (activate it on your Revolution Pi) -- The RevPiPyLoad XML-RPC service is NOT bind to localhost -- The ACL permission is not set for 127.0.0.1!!! - Kann keine Verbindung zum RevPiPyLoad Dienst über SSH herstellen! +Possible reasons: +- RevPi is offline. +- RevPiPyLoad service is not running. Activate service on your RevPi. +- RevPiPyLoad XML-RPC service is bound to localhost only. +- The ACL permission is not set for your IP. -Das kann eine der folgenden Ursachen haben: -- Der RevPiPyLoad Dienst läuft nicht (aktiviere Diesen auf dem Revolution Pi) -- Der RevPiPyLoad XML-RPC Dienst ist NICHT an localhost gebunden -- Die Berechtigungen sind nicht für 127.0.0.1 gesetzt!!! +Use 'Connect via SSH' to use encrypted connection or run 'sudo revpipyload_secure_installation' on RevPi to set up direct remote access. + Verbindung zum RevPiPyLoad-XML-RPC-Dienst kann nicht hergestellt werden. + +Mögliche Ursachen: +- RevPi ist offline. +- Der RevPiPyLoad-Dienst läuft nicht. Dienst auf dem RevPi aktivieren. +- Der RevPiPyLoad-XML-RPC-Dienst ist nur an localhost gebunden. +- Für die eigene IP-Adresse ist keine ACL-Berechtigung gesetzt. + +Für eine verschlüsselte Verbindung 'Über SSH verbinden' verwenden oder auf dem RevPi 'sudo revpipyload_secure_installation' ausführen, um den direkten Fernzugriff einzurichten. + + + + Simulating + Simulation läuft + + + + Not connected + Nicht verbunden + + + + Server error + Serverfehler + + + + Running + Läuft + + + + PLC file not found + PLC-Datei nicht gefunden + + + + Not running (no status) + Nicht aktiv (kein Status) + + + + Program killed + Programm zwangsweise beendet + + + + Program terminated + Programm beendet + + + + Not running + Nicht gestartet + + + + Finished with exit code {0} + Beendet mit Exit-Code {0} DebugControl - Driver reset for piControl detected. - Treiberneustart in piCtory erkannt. + piControl driver reset detected + Reset des piControl Treibers erkannt - Error while getting values from Revolution Pi. - Fehler bei Werteempfang von RevPi. + Error while getting values from RevPi + Fehler beim Abrufen der Werte vom RevPi - Auto update values... - Werte automatisch aktualisiert... + Updating values + Werte aktualisieren - Values updated... - Werte aktualisiert... + Values updated + Werte aktualisiert - Error set value of device '{0}' Output '{1}': {2} + Error setting value for device '{0}', output '{1}': {2} - Fehler beim Setzen des Ausgangs '{1}' auf Modul '{0}': {2} + Fehler beim Setzen des Werts für Gerät '{0}', Ausgang '{1}': {2} @@ -228,74 +228,69 @@ Das kann eine der folgenden Ursachen haben: DebugIos - - signed - - - - - big_endian - - - - - as text - - - - - as number - - - - - Can not use format text - Formatierung nicht möglich - - - - Can not convert bytes {0} to a text for IO '{1}'. Switch to number format instead! - Kann bytes {0} für '{1}' nicht in Text konvertieren. Wechseln Sie auf Nummernformat! - - - - switch wordorder - Wordorder tauschen - - - + Reset counter Zähler zurücksetzen - - can not display - kann nicht angezeigt werden + + Cannot display + Anzeige nicht möglich - - Relais {0} - + + Relay {0} + Relais {0} - + Switching cycles{0}: {1} Schaltzyklen{0}: {1} - + + As text + Als Text + + + + As number + Als Zahl + + + + Signed + Signiert + + + + Big-endian + Big-Endian + + + + Swap word order + Wortreihenfolge tauschen + + + Error Fehler - - Could not reset the counter value - Kann Zähler nicht zurücksetzen + + Cannot reset counter value. + Zählerwert kann nicht zurückgesetzt werden. - - Can not display - Kann nicht angezeigt werden + + Cannot use text format. + Textformat kann nicht verwendet werden. + + + + Cannot convert bytes {0} to text for I/O '{1}'. Switch to number format instead. + Bytes {0} können für I/O „{1}“ nicht in Text umgewandelt werden. Stattdessen das Zahlenformat verwenden. @@ -305,6 +300,13 @@ Das kann eine der folgenden Ursachen haben: Question Frage + + + Quit without saving? +Unsaved changes will be lost. + Ohne Speichern beenden? +Nicht gespeicherte Änderungen gehen verloren. + Error @@ -312,133 +314,141 @@ Das kann eine der folgenden Ursachen haben: - Can not load the MQTT settings dialog. Missing values! - Kann MQTT Einstellungen nicht laden. Es fehlen Werte! - - - - Do you really want to quit? -Unsaved changes will be lost. - Soll das Fenster wirklich geschlossen werden? -Ungesicherte Änderungen gehen verloren. + Cannot load the MQTT settings dialog. Missing values. + MQTT-Einstellungsdialog kann nicht geladen werden. Fehlende Werte. RevPiCommander - - - Simulator started... - Simulator gestartet... - - - - Can not start... - Kann nicht gestartet werden... - Warning Warnung - - - This version of Logviewer ist not supported in version {0} of RevPiPyLoad on your RevPi! You need at least version 0.4.1. - Diese Version vom Logbetrachter wird in RevPiPyLoad Version {0} nicht unterstützt! Es wird mindestens Version 0.4.1 benötigt. - - - - XML-RPC access mode in the RevPiPyLoad configuration is too small to access this dialog! - XML-RPC Zugriffsberechtigung in der RevPiPyLoad Konfiguraiton ist zu klein für diese Einstellungen! - Error Fehler - - - The Version of RevPiPyLoad on your Revolution Pi ({0}) is to old. This Version of RevPiCommander require at least version 0.6.0 of RevPiPyLoad. Please update your Revolution Pi! - Die Version von RevPiPyLoad ({0}) auf dem Revolution Pi ist zu alt. Diese Version vom RevPiCommander braucht mindestens Version 0.6.0. Bitte aktualisiere deinen Revolution Pi! - Question Frage - - - Are you sure to reset piControl? -The pictory configuration will be reloaded. During that time the process image will be interrupted and could rise errors on running control programs! - Soll piControl wirklich zurückgesetzt werden? -Die piCtory Konfiguration wird neu geladen. Das Prozessabbild wird in dieser Zeit nicht verfügbar sein und es könnten Fehler in Steuerungsprogrammen ausgelöst werden! - Success - Erfolgreich + Erfolg - - piControl reset executed successfully - piControl wurde erfolgreich zurückgesetzt + + Connecting to RevPi + Verbindung zum RevPi wird hergestellt - - piControl reset could not be executed successfully - piControl konnte nicht zurückgesetzt werden + + Connected to RevPi + Mit dem RevPi verbunden - - Reset to piCtory defaults... - Standardwerte von piCtory laden... + + Connecting + Verbinden - - The watch mode ist not supported in version {0} of RevPiPyLoad on your RevPi! You need at least version 0.5.3! Maybe the python3-revpimodio2 module is not installed on your RevPi at least version 2.0.0. - Der SPS Betrachter ist in Version {0} von RevPiPyLoad auf dem Rev Pi nicht unterstützt! Es muss mindestens Version 0.5.3 installiert sein! Vielleicht fehlt auch das python3-revpimodio2 Modul, welches mindestens Version 2.0.0 haben muss. - - - - Can not load this function, because your ACL level is to low! -You need at least level 1 to read or level 3 to write. - Für diese Funktion ist das Berechtigungslevel zu gering! -Es muss mindestens Level 1 zum Lesen oder Level 3 zu Schreiben sein. - - - - Can not load piCtory configuration. -Did you create a hardware configuration? Please check this in piCtory! - Kann piCtory Konfiguration nicht laden. -Wurde eine Hardwarekonfiguration in piCtory erzeugt? Bitte prüfe dies in piCtory! + + Cannot connect to the RevPiPyLoad service through the SSH tunnel. + +Service activation and reconnection in progress. The settings can be changed at any time via Cockpit. + Kann keine Verbindung zum RevPiPyLoad-Dienst über den SSH-Tunnel hergestellt werden. Dienstaktivierung und Wiederanbindung in Arbeit. Die Einstellungen können jederzeit über Cockpit geändert werden. - The simulator is running! - -You can work with this simulator if your call RevPiModIO with this additional parameters: -procimg={0} -configrsc={1} - -You can copy that from header textbox. - Der Simulator läuft! - -Du kannst mit der Simulation arbeiten, wenn du RevPiModIO mit diesen zusätzlichen Parametern instantiierst: -procimg={0} -configrsc={1} - -Dies kann aus der Textbox oben kopiert werden. + Simulator started + Simulator gestartet - Can not start the simulator! Maybe the piCtory file is corrupt or you have no write permissions for '{0}'. - Kann Simulator nicht starten! Vielleicht ist die piCtory Datei defekt oder es gibt keine Schreibberechtigung für '{0}'. + Cannot start + Kann nicht starten + + + + Simulator is running. + +Use the additional RevPiModIO parameters: +procimg={0} +configrsc={1} + +from the header text box. + Simulator läuft. + +Die zusätzlichen RevPiModIO-Parameter: +procimg={0} +configrsc={1} + +aus dem Textfeld in der Kopfzeile verwenden. + + + + Cannot start the simulator. The PiCtory file might be invalid or you do not have write permissions for '{0}'. + Simulator kann nicht gestartet werden. Die PiCtory Datei ist möglicherweise ungültig oder für „{0}“ fehlen Schreibrechte. + + + + This version of Log Viewer is not supported in version {0} of RevPiPyLoad on your RevPi. At least version 0.4.1 is required. + Diese Version des Log Viewers wird von RevPiPyLoad {0} auf dem RevPi nicht unterstützt. Mindestens Version 0.4.1 ist erforderlich. + + + + XML-RPC access mode in the RevPiPyLoad configuration is too low to access this dialog. + Der XML-RPC-Zugriffsmodus in der RevPiPyLoad Konfiguration ist zu niedrig, um auf diesen Dialog zuzugreifen. + + + + The version of RevPiPyLoad on your RevPi ({0}) is too old. This version of RevPi Commander requires at least version 0.6.0 of RevPiPyLoad. Update your RevPi. + Die Version von RevPiPyLoad auf dem RevPi ({0}) ist zu alt. Für diese Version von RevPi Commander wird mindestens RevPiPyLoad 0.6.0 benötigt. RevPi aktualisieren. + + + + Are you sure you want to reset piControl? +The PiCtory configuration will be reloaded. During that time, the process image will be interrupted and could cause errors on running control programs. + piControl wirklich zurücksetzen? +Die PiCtory-Konfiguration wird neu geladen. Das Prozessabbild wird dabei kurzzeitig unterbrochen und kann Fehler in laufenden Steuerungsprogrammen verursachen. + + + + piControl reset completed successfully. + piControl wurde erfolgreich zurückgesetzt. + + + + piControl reset could not be completed. + piControl konnte nicht zurückgesetzt werden. + + + + Reset to PiCtory defaults + Auf PiCtory Standardwerte zurücksetzen + + + + The watch mode is not supported in version {0} of RevPiPyLoad on your RevPi. At least version 0.5.3 is required. The python3-revpimodio2 module may be missing or older than version 2.0.0. + Der Watch-Modus wird von RevPiPyLoad {0} auf dem RevPi nicht unterstützt. Mindestens Version 0.5.3 ist erforderlich. Das Modul python3-revpimodio2 fehlt möglicherweise oder ist älter als Version 2.0.0. + + + + Cannot load this function, because your ACL level is too low. +At least level 1 to read or level 3 to write is required. + Diese Funktion kann nicht verwendet werden, da die ACL-Stufe zu niedrig ist. +Zum Lesen ist mindestens Stufe 1, zum Schreiben mindestens Stufe 3 erforderlich. Do you want to reset your process image to {0} values? You have to stop other RevPiModIO programs before doing that, because they could reset the outputs. - Soll das virtuelle Prozessabbild auf {0} zurückgesetzt werden? -Es sollten alle RevPiModIO Programme vorher beendet werden, da diese ihre IO Werte sofort wieder schreiben würden. + Prozessabbild auf die Werte aus {0} zurücksetzen? +Vorher müssen alle anderen RevPiModIO-Programme beendet werden, da diese die Ausgänge zurücksetzen könnten. @@ -447,154 +457,109 @@ Es sollten alle RevPiModIO Programme vorher beendet werden, da diese ihre IO Wer - piCtory default - piCtory Standardwerte + PiCtory defaults + PiCtory Standardwerte - - Revolution Pi connected! - Revolution Pi verbunden! - - - - Connecting... - Verbinde... - - - - Establish a connection to the Revolution Pi... - Baue eine Verbindung zum Revolution Pi auf... + + Cannot load PiCtory configuration. +Check hardware configuration in PiCtory. + PiCtory Konfiguration kann nicht geladen werden. +Hardwarekonfiguration in PiCtory prüfen. Information Information - - - Can not connect to RevPiPyLoad service through SSH tunnel! - -We are trying to activate this service now and reconnect. The settings can be changed at any time via 'webstatus'. - Vielleicht läuft der RevPiPyLoad Dienst nicht. - -Wir versuchen diesen Dienst jetzt zu aktivieren und verbinden uns neu. Die Einstellungen können über 'Webstatus' jederzeit geändert werden. - RevPiFiles - - - Please select... - Bitte auswählen... - Error Fehler - - - Can not stop plc program on Revolution Pi. - Kann SPS Programm auf Rev Pi nicht stoppen. - - - - The Revolution Pi could not process some parts of the transmission. - Der Revolution Pi hat Teile der Übertragung nicht durchgeführt. - - - - Errors occurred during transmission - Fehler bei Übertragung aufgetreten - Warning Warnung - - Could not start the plc program on Revolution Pi. - Kann das SPS Programm auf dem Revolution Pi nicht starten. + + Select + Auswählen - - Can not open last directory '{0}'. - Kann letztes Verzeichnis '{0}' nicht öffnen. + + Cannot stop PLC program on RevPi. + SPS-Programm auf dem RevPi kann nicht gestoppt werden. - - Stop scanning for files, because we found more than {0} files. - Dateisuche wurde angehalten, da mehr als {0} Dateien gefunden wurden. - - - - Could not load path of working dir - Kann Arbeitsverzeichnis nicht laden - - - - Can not load file list from Revolution Pi. - Kann Dateiliste vom Revolution Pi nicht laden. - - - - Select folder... - Ordner auswählen... - - - - Can not access the folder '{0}' to read files. - Keine Berechtigung für Zugriff auf Ordner '{0}'. - - - - Error... - Fehler... - - - - Error while download file '{0}'. - Fehler beim Herunterladen der Datei '{0}'. - - - - Override files... - Dateien überschreiben... - - - - One or more files does exist on your computer! Do you want to override the existingfiles? - -Select 'Yes' to override, 'No' to download only missing files. - Eine oder mehrere Dateien existieren auf diesem Computer! Sollen bestehende Dateien überschrieben werden? - -Wählen Sie 'Ja' zum Überschreiben, 'Nein' um nur fehlende Dateien zu laden. - - - - Delete files from Revolution Pi... - Dateien auf Rev Pi löschen... - - - - Do you want to delete {0} files from revolution pi? - Sollen {0} Dateien vom Revolution Pi gelöscht werden? - - - - Error while delete file '{0}'. - Fehler beim Löschen der Datei '{0}'. + + File transfer + Dateiübertragung Information Information + + + RevPi cannot process some parts of the transmission. + RevPi kann einige Teile der Übertragung nicht verarbeiten. + + + + Cannot start the PLC program on RevPi. + Es kann kein PLC-Programm auf RevPi gestartet werden. + + + + Deletes selected files immediately on RevPi. + Ausgewählte Dateien werden sofort von RevPi gelöscht. + + + + Cannot load the working directory path. + Der Arbeitsverzeichnispfad kann nicht geladen werden. + + + + Overwrite files + Überschreiben Dateien + + + + Cannot save settings on RevPi. +Try saving the values one more time and check the RevPiPyLoad log files if the error occurs again. + Es kann keine Einstellungen auf RevPi gespeichert werden. Versuche die Werte noch einmal zu speichern und überprüfe die Logdateien von RevPiPyLoad, wenn der Fehler erneut auftreten sollte. + + + + Errors occurred during transmission. + Fehler bei Übertragung aufgetreten. + - A PLC program has been uploaded. Please check the PLC program settings to see if the correct program is specified as the start program. - Ein SPS Programm wurde hochgeladen. Bitte prüfe die SPS Programmeinstellungen ob das richtige Startprogramm gewählt ist. + A PLC program has been uploaded. Check the PLC program settings to see if the correct program is specified as the start program. + Ein SPS-Programm wurde hochgeladen. Prüfen, ob das richtige Programm als Startprogramm festgelegt ist. + + + + Set as start program. + Als Startprogramm festlegen. + + + + Upgrade your RevPi. This function needs at least 'revpipyload' 0.11.0. + Aktualisiere dein RevPi. Diese Funktion benötigt mindestens 'revpipyload' 0.11.0. + + + + Upgrade your RevPi. This function needs at least 'revpipyload' 0.9.5. + Aktualisiere dein RevPi. Diese Funktion benötigt mindestens 'revpipyload' 0.9.5. @@ -602,44 +567,64 @@ Wählen Sie 'Ja' zum Überschreiben, 'Nein' um nur fehlende Lokales Verzeichnis wählen. - - File transfer... - Dateiübertragung... + + Cannot open last directory '{0}'. + Letztes Verzeichnis '{0}' kann nicht geöffnet werden. - - Upgrade your Revolution Pi! This function needs at least 'revpipyload' 0.11.0 - Aktualisiere deinen Revolution Pi! Diese Funktion benötigt mindestens 'revpipyload' 0.11.0 + + Stopped scanning for files because more than {0} files were found. + Die Suche nach Dateien wurde beendet, weil mehr als {0} Dateien gefunden wurden. - - Upgrade your Revolution Pi! This function needs at least 'revpipyload' 0.9.5 - Aktualisiere deinen Revolution Pi! Diese Funktion benötigt mindestens 'revpipyload' 0.9.5 + + Cannot load file list from RevPi. + Es kann keine Dateiliste von RevPi geladen werden. - - Deletes selected files immediately on the Revolution Pi - Löscht ausgewählte Dateien sofort auf dem Revolution Pi + + Select folder + Verzeichnis wählen - - The settings could not be saved on the Revolution Pi! -Try to save the values one mor time and check the log files of RevPiPyLoad if the error rises again. - Die Einstellungen konnten nicht auf dem Revolution Pi gespeichert werden! -Versuche es erneut und prüfe die Logdateien von RevPiPyLoad, wenn der Fehler erneut auftritt. + + Cannot access the folder '{0}' to read files. + Kann das Verzeichnis '{0}' nicht zum Lesen von Dateien zugreifen. - - Set as start file - Als Startdatei festlegen + + Error while downloading file '{0}'. + Fehler beim Herunterladen der Datei '{0}'. + + + + One or more files already exist on your computer. Do you want to overwrite the existing files? + +Select 'Yes' to overwrite, 'No' to download only missing files. + Eine oder mehrere Dateien sind bereits auf dem Computer vorhanden. Vorhandene Dateien überschreiben? 'Ja' überschreibt vorhandene Dateien, 'Nein', lädt nur fehlende Dateien herunter. + + + + Delete files from RevPi + Lösche Dateien von RevPi + + + + Do you want to delete {0} files from RevPi? + Möchten Sie {0} Dateien von RevPi löschen? + + + + Error while deleting file '{0}'. + Fehler beim Löschen der Dateis '{0}'. RevPiInfo - Can not load file list - Kann Dateiliste nicht laden + Cannot load file list. + Kann Dateiliste nicht laden. @@ -651,8 +636,8 @@ Versuche es erneut und prüfe die Logdateien von RevPiPyLoad, wenn der Fehler er RevPiLogfile - Can not access log file on the RevPi - Kann auf Logbuch vom RevPi nicht zugreifen + Cannot access log file on RevPi. + Auf die Logdatei auf dem RevPi kann nicht zugegriffen werden. @@ -662,89 +647,89 @@ Versuche es erneut und prüfe die Logdateien von RevPiPyLoad, wenn der Fehler er Question Frage + + + Running + Läuft + + + + Stopped + Gestoppt + + + + Read-only + Schreibgeschützt + + + + Read/write + Lesen/Schreiben + + + + Start/stop PLC program and read log files + SPS-Programm starten/stoppen und Logdateien lesen + - The settings will be set on the Revolution Pi now. + Applying settings on RevPi. ACL changes and service settings are applied immediately. - Die Einstellungen werden jetzt auf dem Revolution Pi angewendet. + Einstellungen werden auf dem RevPi angewendet. -Berechtigungseinstellungen werden sofort gesetzt. +ACL-Änderungen und Diensteinstellungen werden sofort übernommen. + + + + Cannot save settings on RevPi +Try saving the values one more time and check the RevPiPyLoad log files if the error occurs again. + Einstellungen können nicht auf dem RevPi gespeichert werden. +Speichern erneut versuchen. Tritt der Fehler erneut auf, die RevPiPyLoad-Logdateien prüfen. + + + + Quit without saving? +Unsaved changes will be lost. + Beenden ohne Speichern? +Nicht gespeicherte Änderungen gehen verloren. + + + + Are you sure you want to deactivate the XML-RPC server? RevPi will no longer be accessible from this program after saving settings. + XML-RPC-Server wirklich deaktivieren? Nach dem Speichern der Einstellungen kann nicht mehr über dieses Programm auf den RevPi zugegriffen werden. + + + + + Read I/Os in watch mode + + I/Os im Monitor-Modus lesen + + + + + Read properties and download PLC program + + Eigenschaften lesen und PLC-Programm herunterladen + + + + + Upload PLC program + + PLC-Programm hochladen + + + + + Set properties + + Eigenschaften setzen Error Fehler - - - The settings could not be saved on the Revolution Pi! -Try to save the values one mor time and check the log files of RevPiPyLoad if the error rises again. - Die Einstellungen konnten nicht auf dem Revolution Pi gespeichert werden! -Versuche es erneut und prüfe die Logdateien von RevPiPyLoad, wenn der Fehler erneut auftritt. - - - - Do you really want to quit? -Unsaved changes will be lost. - Soll das Fenster wirklich geschlossen werden? -Ungesicherte Änderungen gehen verloren. - - - - running - läuft - - - - stopped - angehalten - The MQTT service is not available on your RevPiPyLoad version. MQTT ist in der RevPiPyLoad Version nicht verfügbar. - - - read only - Nur lesen - - - - read and write - lesen und schreiben - - - - Are you sure you want to deactivate the XML-RPC server? You will NOT be able to access the Revolution Pi with this program after saving the options! - Willst du den XML-RPC Server wirklich deaktivieren? Du wirst dich NICHT mehr mit diesem Programm zum Revolution Pi verbinden können! - - - - Start/Stop PLC program and read logs - SPS Programm starten/stoppen und Logs lesen - - - - + read IOs in watch mode - + EAs in SPS Betrachter lesen - - - - + read properties and download PLC program - + Einstellungen lesen und SPS Programm herunterladen - - - - + upload PLC program - + SPS Programm hochladen - - - - + set properties - + Einstellungen ändern - RevPiPlcList @@ -755,19 +740,18 @@ Ungesicherte Änderungen gehen verloren. - Do you really want to quit? + Quit without saving? Unsaved changes will be lost. - Soll das Fenster wirklich geschlossen werden? -Ungesicherte Änderungen gehen verloren. + Ohne Speichern schließen? Nicht gespeicherte Änderungen gehen verloren. - If you remote this folder, all containing elements will be removed, too. + If you remove this folder, all contained items will be removed as well. -Do you want to delete folder and all elements? - Wird dieser Ordner gelöscht, betrifft dies auch alle Elemente im Ordner. +Do you want to delete the folder and all contained items? + Beim Löschen dieses Ordners werden auch alle enthaltenen Elemente gelöscht. -Wollen sie den Ordner und alle Elemente löschen? +Ordner und alle enthaltenen Elemente löschen? @@ -782,288 +766,289 @@ Wollen sie den Ordner und alle Elemente löschen? Error Fehler - - - You have to select a start program, before uploading the settings. - Es muss erst ein Startprogramm gewählt werden. - Question Frage - - - The settings will be set on the Revolution Pi now. - -If you made changes on the 'PCL Program' section, your plc program will restart now! - Die Einstellungen werden jetzt auf dem Revolution Pi angewendet. - -Sollte es Änderungen in dem SPS Programmabschnitt geben, wird das SPS Programm neu gestartet! - - - - The settings could not be saved on the Revolution Pi! -Try to save the values one mor time and check the log files of RevPiPyLoad if the error rises again. - Die Einstellungen konnten nicht auf dem Revolution Pi gespeichert werden! -Versuche es erneut und prüfe die Logdateien von RevPiPyLoad, wenn der Fehler erneut auftritt. - - - - Do you really want to quit? -Unsaved changes will be lost. - Soll das Fenster wirklich geschlossen werden? -Ungesicherte Änderungen gehen verloren. - - - - Reset driver... - Treiber zurücksetzen... - - - - Reset piControl driver after successful uploading new piCtory configuration? -The process image will be interrupted for a short time! - Soll piControl nach dem erfolgreichen Hochladen der neuen piCtory Konfiguration zurückgesetzt werden? -Das Prozessabbild wird kurzzeitig nicht verfügbar sein! - - - - Got an network error while send data to Revolution Pi. -Please try again. - Beim Senden der Daten an den Revolution Pi trat ein Netzwerkfehler auf. -Versuche es erneut. - Success - Erfolgreich - - - - The transfer of the piCtory configuration and the reset of piControl have been successfully executed. - Die piCtory Übertragung und der Reset von piControl wurden erfolgreich durchgeführt. - - - - The piCtory configuration was successfully transferred. - Die piCtory Konfiguration wurde erfolgreich übertragen. - - - - Can not process the transferred file. - Kann die Übertragene Datei nicht verarbeiten. - - - - Can not find main elements in piCtory file. - Konnte piCtory Struktur nicht erkennen. - - - - Contained devices could not be found on Revolution Pi. The configuration may be from a newer piCtory version! - Enthaltene Module können auf dem Revolution Pi nicht gefunden werden. Die Konfiguraiton könnte von einer neueren piCtory Version stammen! - - - - Could not load RAP catalog on Revolution Pi. - Kann RAP Katalog auf dem Revolution Pi nicht laden. - - - - The piCtory configuration could not be written on the Revolution Pi. - Die piCtory Konfiguration konnte nicht auf dem Revolution Pi geschrieben werden. + Erfolg Warning Warnung - - - The piCtroy configuration has been saved successfully. -An error occurred on piControl reset! - Die piCtory Konfiguration wurde erfolgreich hochgeladen. -Es trat jedoch ein Fehler beim Zurücksetzen von piControl auf! - - - - Save ZIP archive... - ZIP Archiv speichern... - ZIP archive (*.zip);;All files (*.*) - ZIP Archive (*.zip);;Alle Dateien (*.*) - - - - Save TGZ archive... - TGZ Archiv speichern... + ZIP-Archiv (*.zip);;Alle Dateien (*.*) TGZ archive (*.tgz);;All files (*.*) - TAR Archive (*.tgz);;Alle Dateien (*.*) - - - - Could not load PLC program from Revolution Pi. - Kann SPS Programm nicht vom Revolution Pi laden. - - - - Coud not save the archive or extract the files! -Please retry. - Konnte das Archiv nicht speichern oder extrahieren! -Versuche es erneut. + TAR-Archiv (*.tgz);;Alle Dateien (*.*) Transfer successfully completed. Übertragung erfolgreich abgeschlossen. - - - Upload content of ZIP archive... - ZIP Archiv hochladen... - - - - The selected file ist not a ZIP archive. - Die ausgewählte Datei ist kein ZIP Archiv. - - - - Upload content of TAR archive... - TAR Archiv hochladen... - TAR archive (*.tgz);;All files (*.*) - TAR Archive (*.tgz);;Alle Dateien (*.*) + TAR-Archiv (*.tgz);;Alle Dateien (*.*) - - The selected file ist not a TAR archive. - Die ausgewählte Datei ist kein TAR Archiv. + + You must select a start program before uploading the settings. + Vor dem Hochladen der Einstellungen muss ein Startprogramm ausgewählt werden. + + + + Saving settings on RevPi. + +If you made changes in the 'PLC Program' section, your PLC program will be restarted. + Einstellungen werden auf dem RevPi gespeichert. + +Bei Änderungen im Abschnitt „SPS-Programm“ wird das SPS-Programm neu gestartet. + + + + Cannot save settings on RevPi. +Try saving the values one more time and check the RevPiPyLoad log files if the error occurs again. + Einstellungen können nicht auf dem RevPi gespeichert werden. + +Speichern erneut versuchen. Tritt der Fehler weiterhin auf, die RevPiPyLoad-Logdateien prüfen. + + + + Quit without saving? +Unsaved changes will be lost. + Ohne Speichern beenden? +Nicht gespeicherte Änderungen gehen verloren. + + + + Reset driver + Treiber zurücksetzen + + + + Reset piControl driver after successfully uploading the new PiCtory configuration? +The process image will be interrupted for a short time. + piControl Treiber nach dem Hochladen der neuen PiCtory Konfiguration zurücksetzen? +Das Prozessabbild wird kurzzeitig unterbrochen. + + + + The transfer of the PiCtory configuration and the reset of piControl have been successfully executed. + Die Übertragung der PiCtory Konfiguration und das Zurücksetzen von piControl wurden erfolgreich ausgeführt. + + + + The PiCtory configuration was successfully transferred. + Die PiCtory Konfiguration wurde erfolgreich übertragen. + + + + Cannot process the transferred file. + Die übertragene Datei kann nicht verarbeitet werden. + + + + Cannot find main elements in PiCtory file. + Hauptelemente in der PiCtory Datei können nicht gefunden werden. + + + + Cannot find contained devices on RevPi. +The configuration may be from a newer PiCtory version. + Die auf dem RevPi vorhandenen Geräte können nicht gefunden werden. +Die Konfiguration stammt möglicherweise aus einer neueren PiCtory Version. + + + + Cannot load RAP catalog on RevPi. + RAP-Katalog kann auf dem RevPi nicht geladen werden. + + + + Cannot write PiCtory configuration on RevPi. + PiCtory Konfiguration kann nicht auf den RevPi geschrieben werden. + + + + Cannot load PLC program from RevPi. + SPS-Programm kann nicht vom RevPi geladen werden. + + + + Cannot save the archive or extract the files. +Try again. + Archiv kann nicht gespeichert oder Dateien können nicht entpackt werden. +Erneut versuchen. - No files to upload... - Keine Dateien zum Hochladen... - - - - Found no files to upload in given location or archive. - Konnte keine Dateien in der Quelle zum Hochladen finden. + No files to upload + Keine Dateien zum Hochladen vorhanden - There was an error deleting the files on the Revolution Pi. -Upload aborted! Please try again. - Beim Löschen der Dateien auf dem Revolution Pi ist ein Fehler aufgetreten. -Hochladen abgebrochen! Versuche es erneut. + Cannot delete files on RevPi. +Upload aborted. Try again. + Dateien auf dem RevPi können nicht gelöscht werden. +Hochladen abgebrochen. Erneut versuchen. + + + + Cannot find the selected PLC start program in the uploaded files. +This is not an error if the file already exists on RevPi. Check the PLC start program field. + Das ausgewählte SPS-Startprogramm wurde in den hochgeladenen Dateien nicht gefunden. +Dies ist kein Fehler, wenn die Datei bereits auf dem RevPi vorhanden ist. Das Feld SPS-Startprogramm prüfen. + + + + RevPi cannot process some parts of the transmission. + Der RevPi kann Teile der übertragenen Daten nicht verarbeiten. + + + + Cannot load PiCtory file from RevPi. + PiCtory Datei kann nicht vom RevPi geladen werden. + + + + PiCtory configuration saved to: +{0}. + Die PiCtory Konfiguration wurde gespeichert unter: {0}. + + + + Cannot load process image from RevPi. + Prozessabbild kann nicht vom RevPi geladen werden. + + + + Process image saved to: +{0}. + Das Prozessabbbild wurde gespeichert unter: {0}. + + + + The PiCtory configuration has been saved successfully. +An error occurred on piControl reset. + Die PiCtory-Konfiguration wurde erfolgreich gespeichert. +Beim Zurücksetzen von piControl ist ein Fehler aufgetreten. + + + + Network error while sending data to RevPi. +Try again. + Netzwerkfehler beim Senden von Daten an den RevPi. +Erneut versuchen. + + + + Save ZIP archive + ZIP-Archiv speichern + + + + Save TGZ archive + TGZ-Archiv speichern + + + + Upload content of ZIP archive + Inhalt des ZIP-Archivs hochladen + + + + The selected file is not a ZIP archive. + Die ausgewählte Datei ist kein ZIP-Archiv. + + + + Upload content of TAR archive + Inhalt des TAR-Archivs hochladen + + + + The selected file is not a TAR archive. + Die ausgewählte Datei ist kein TAR-Archiv. + + + + No files found in the selected location or archive. + Keine Dateien am ausgewählten Speicherort oder im Archiv gefunden. The PLC program was transferred successfully. - Das SPS Programm wurde erfolgreich übertragen. + Das SPS-Programm wurde erfolgreich übertragen. Information Information - - - Could not find the selected PLC start program in uploaded files. -This is not an error, if the file was already on the Revolution Pi. Check PLC start program field - Konnte eingestelltes SPS Starprogramm in hochgeladenen Dateien nicht finden. -Dies ist kein Fehler, wenn das SPS Startprogramm bereits auf dem Rev Pi ist. Prüfe SPS Programm Einstellungen - - There is no piCtory configuration in this archive. - Kann keine piCtory Konfiguration im Archiv finden. + There is no PiCtory configuration in this archive. + Keine PiCtory Konfiguration im Archiv gefunden. - - The Revolution Pi could not process some parts of the transmission. - Der Revolution Pi konnte Teile der Übertragung nicht verarbeiten. + + Save PiCtory file + PiCtory Datei speichern + + + + PiCtory file (*.rsc);;All files (*.*) + PiCtory Datei (*.rsc); Alle Dateien (*.*) + + + + Upload PiCtory file + PiCtory Datei hochladen + + + + Save piControl file + piControl Datei speichern Errors occurred during transmission. - Fehler bei Übertragung aufgetreten. - - - - Save piCtory file... - piCtory Datei speichern... - - - - piCtory file (*.rsc);;All files (*.*) - piCtory Datei (*.rsc);;Alle Dateien (*.*) - - - - Could not load piCtory file from Revolution Pi. - Kann piCtory Konfiguration nicht vom Revolution Pi laden. - - - - piCtory configuration successfully loaded and saved to: -{0}. - piCtory Konfiguration erfolgreich geladen und gespeichert als: -{0}. - - - - Upload piCtory file... - piCtory datei hochladen... - - - - Save piControl file... - piCtory Datei speichern... + Bei der Übertragung sind Fehler aufgetreten. Process image file (*.img);;All files (*.*) - Processabbild (*.img);;Alle Dateien (*.*) - - - - Could not load process image from Revolution Pi. - Kann Prozessabbild von Revolution Pi nicht laden. - - - - Process image successfully loaded and saved to: -{0}. - Prozessabbild erfolgreich geladen und gespeichert als: -{0}. + Prozessabbild (*.img);;Alle Dateien (*.*) SSHAuth - Could not save password - Konnte Kennwort nicht speichern + Cannot save password + Passwort kann nicht gespeichert werden - Could not save password to operating systems password save. + Cannot save password to operating system's password store. -Maybe your operating system does not support saving passwords. This could be due to missing libraries or programs. +The operating system may not support saving passwords. This could be due to missing libraries or programs. -This is not an error of RevPi Commander. - Konnte das Kennwort nicht im Kennwortspeicher des Betriebssystems speichern. +This is not a RevPi Commander error. + Passwort kann nicht im Passwortspeicher des Betriebssystems gespeichert werden. -Vielleicht untersützt das Betriebssystem keine Kennwortspeicherung. Dies könnte an fehlenden Bibliotheken oder Programmen liegen. +Das Betriebssystem unterstützt das Speichern von Passwörtern möglicherweise nicht. Ursache können fehlende Bibliotheken oder Programme sein. Dies ist kein Fehler von RevPi Commander. @@ -1072,31 +1057,26 @@ Dies ist kein Fehler von RevPi Commander. Simulator - Select downloaded piCtory file... - Heruntergeladene piCtory Datei auswählen... + Select downloaded PiCtory file + Heruntergeladene PiCtory Datei auswählen - piCtory file (*.rsc);;All files (*.*) - piCtory Datei (*.rsc);;Alle Dateien (*.*) + PiCtory file (*.rsc);;All files (*.*) + PiCtory Datei (*.rsc);;Alle Dateien (*.*) diag_aclmanager - IP access control list - IP Berechtigungsliste + IP Access Control List + IP-Zugriffsliste Existing ACLs - Aktuelle ACLs - - - - IP Address - IP Adresse + Vorhandene ACLs @@ -1113,10 +1093,15 @@ Dies ist kein Fehler von RevPi Commander. &Remove &Löschen + + + IP address + IP-Adresse + - Add / Edit access entry - Eintrag hinzufügen / bearbeiten + Add/Edit Access Entry + Zugriffseintrag hinzufügen/bearbeiten @@ -1126,12 +1111,12 @@ Dies ist kein Fehler von RevPi Commander. &Save entry - Eintrag &Speichern + Eintrag &speichern IP address: - IP Adresse: + IP-Adresse: @@ -1141,16 +1126,6 @@ Dies ist kein Fehler von RevPi Commander. diag_connections - - - Revolution Pi connections - Revolution Pi Verbindungen - - - - Connection name - Verbindungsname - Address @@ -1161,36 +1136,41 @@ Dies ist kein Fehler von RevPi Commander. Display name: Anzeigename: - - - Sub folder: - Unterordner: - Address (DNS/IP): Adresse (DNS/IP): - - - Port (Default {0}): - Port (Standard {0}): - Connection timeout: Verbindungs-Timeout: - - sec. - Sek. + + RevPi Connections + RevPi Verbindungen Connection Verbindung + + + Port (default {0}): + Port (Standardwert {0}): + + + + s + s + + + + Subfolder: + Unterordner: + Over SSH @@ -1199,17 +1179,22 @@ Dies ist kein Fehler von RevPi Commander. Connect over SSH tunnel: - Über SSH Tunnel verbinden: + Über SSH-Tunnel verbinden: SSH port: - SSH Port: + SSH-Port: SSH user name: - SSH Benutzername: + SSH-Benutzername: + + + + Connection Name + Verbindungsname @@ -1217,52 +1202,70 @@ Dies ist kein Fehler von RevPi Commander. MQTT settings - MQTT Einstellungen + MQTT-Einstellungen Base topic - Basistopic + Basis-Topic + + + + Base topic is the prefix for MQTT topics published by RevPi. Use "/" to structure broker topics. + +For example: revpi0000/data + Das Basis-Topic dient als Präfix für die von RevPi veröffentlichten MQTT-Topics. Zur Strukturierung der Topics im Broker "/" verwenden. + +Beispiel: revpi0000/data Base topic: - Basistopic: + Basis-Topic: Publish settings - Publish Einstellungen + Publish-Einstellungen Publish all exported values every n seconds: - Exportierte Werte all n Sekunden senden: + Alle exportierten Werte alle n Sekunden veröffentlichen: Send exported values immediately on value change - Exportierte Werte sofort bei Änderung senden + Exportierte Werte bei Änderung sofort senden Set outputs Ausgänge setzen + + + RevPi subscribes to an MQTT topic for setting output values. Publish the new I/O value as payload. + +Publish values with topic: [basetopic]/set/[outputname] + RevPi abonniert ein MQTT-Topic zum Setzen von Ausgangswerten. Den neuen I/O-Wert als Payload veröffentlichen. + +Topic für die Veröffentlichung von Werten: [basetopic]/set/[outputname] + - Allow MQTT to to set outputs on Revolution Pi - Erlaube per MQTT Ausgänge auf dem RevPi zu setzen + Allow MQTT to set outputs on RevPi + MQTT-Schreibzugriff auf Ausgänge erlauben Broker address: - Broker Adresse: + Broker-Adresse: Broker port: - Broker Port: + Broker-Port: @@ -1282,16 +1285,7 @@ Dies ist kein Fehler von RevPi Commander. Broker settings - Broker Einstellungen - - - - The base topic is the first part of any mqtt topic, the Revolution Pi will publish. You can use any character includig '/' to structure the messages on your broker. - -For example: revpi0000/data - Der Basistopic wird allen MQTT Topics vorangestellt, welche der Revolution Pi veröffentlicht. Es können alle Zeichen inklusive '/' verwendet werden, um die Nachrichten auf dem Broker zu strukturieren. - -Zum Beispiel: revpi0000/data + Broker-Einstellungen @@ -1303,19 +1297,10 @@ Zum Beispiel: revpi0000/data Topic: [basetopic]/event/[ioname] Topic: [basistopic]/io/[eaname] - - - The Revolution Pi will subscribe a topic on which your mqtt client can publish messages with the new io value as payload. - -Publish values with topic: [basetopic]/set/[outputname] - Der Revolution Pi abonniert ein Topic, auf dem die MQTT Clients über den Inhalt einer Nachricht einen neuen Ausgangswert setzen können. - -Sende Werte mit Topic: [basistopic]/set/[ausgangsname] - Client ID: - + Client-ID: @@ -1323,71 +1308,86 @@ Sende Werte mit Topic: [basistopic]/set/[ausgangsname] RevPi Python PLC Options - RevPi Python SPS Einstellungen - - - - Start / Stop behavior of PLC program - Start- / Stopverhalten des SPS Programms - - - - Replace IO file: - EA Ersetzungsdatei: - - - - ... sucessfully without error - ... erfolgreich beendet wird - - - - ... after exception and errors - ...durch Fehler abstürzt + RevPi Python-SPS-Einstellungen Start PLC program automatically - Starte SPS Programm automatisch + SPS-Programm automatisch starten Restart PLC program after exit or crash - Starte SPS Programm nach Absturz neu - - - - Set process image to NULL if program terminates... - Setze Prozessabbild auf NULL, wenn das Programm... - - - - Do not use replace io file - Keine Ersetzungsdatei verwenden + SPS-Programm nach Beenden oder Absturz neu starten Use static file from RevPiPyLoad - Statische Datei von RevPiPyLoad + Statische RevPiPyLoad Datei verwenden Use dynamic file from work directory - Dynamisch aus Arbeitsverzeichnis - - - - Give own path and filename - Eigener Pfad und Dateiname + Dynamische Datei aus dem Arbeitsverzeichnis verwenden Restart delay in seconds: - Neustartverzögerung in Sekunden: + Neustart-Verzögerung in Sekunden: + + + + PLC Start/Stop Behavior + SPS-Start und -Stopp-Verhalten + + + + Replace I/O file: + IO-Datei ersetzen: + + + + Restart after PiCtory changes + Nach PiCtory Änderungen neu starten + + + + Set process image to NULL if the program terminates + Prozessabbild beim Beenden des Programms auf NULL setzen + + + + Do not replace I/O file + IO-Datei nicht ersetzen + + + + Use custom path and file name + Benutzerdefinierten Pfad und Dateinamen verwenden + + + + Start piControl server + piControl Server starten + + + + after exceptions or errors + nach Ausnahmen oder Fehlern + + + + without errors + ohne Fehler + + + + PLC program behavior after PiCtory driver reset + Verhalten nach PiCtory Treiber-Reset - RevPiPyLoad server services + RevPiPyLoad Server Services RevPiPyLoad Serverdienste @@ -1398,12 +1398,7 @@ Sende Werte mit Topic: [basistopic]/set/[ausgangsname] MQTT process image publisher - MQTT Processabbild Publisher - - - - Start RevPi piControl server - Starte RevPi piControl Server + MQTT-Prozessabbild-Publisher @@ -1412,61 +1407,51 @@ Sende Werte mit Topic: [basistopic]/set/[ausgangsname] - piControl server is: - piControl Serverstatus: + piControl server: + piControl Server: - MQTT publish service is: - MQTT Servicestatus: + MQTT publish service: + MQTT-Publish-Dienst: Settings Einstellungen + + + Activate XML-RPC for RevPi Commander + XML-RPC für RevPi Commander aktivieren + Do nothing Keine Aktion - - - Restart after piCtory changed - Neustart nach piCtory Änderungen - Always restart the PLC program - SPS Programm immer neu starten + SPS-Programm immer neu starten Driver reset action: - Aktion bei Treiberneustart: - - - - PLC program behavior after piCtory driver reset clicked - Aktion nach piCtory Neustart mit SPS Programm - - - - Activate XML-RPC for RevPiCommander - Aktiviere XML-RPC für RevPiCommander + Aktion bei Treiber-Reset: diag_oss_licenses - Open-Source licenses - Open-Source Lizenzen + Open source licenses + Open-Source-Lizenzen Software - + Software @@ -1475,51 +1460,61 @@ Sende Werte mit Topic: [basistopic]/set/[ausgangsname] - More licenses... - Weitere Lizenzen... + More licenses + Weitere Lizenzen - Show more open-source software licenses - Weitere Open-Source Software Lizenzen anzeigen + Show more open source software licenses + Weitere Open-Source-Software-Lizenzen anzeigen diag_program - - - PLC program - PLC Programm - Python PLC start program: - Python PLC Startprogramm: + Python-SPS-Startprogramm: - - Set write permissions for plc program to workdirectory - Schreibberechtigung für Arbeitsverzeichnis auf RevPi setzen + + PLC Program + SPS-Programm - Program arguments: - Programargumente: + Arguments: + Argumente: + + + + Software watchdog (0 = disabled): + Software-Watchdog (0 = deaktiviert): + + + + Set write permissions for PLC program working directory + Schreibrechte für SPS-Arbeitsverzeichnis festlegen + + + + s + s - Transfair PLC program - PLC Programm übertragen + Transfer PLC program + Übertragung SPS-Programm ZIP archive - ZIP Archiv + ZIP-Archiv TGZ archive - TGZ Archiv + TGZ-Archiv @@ -1533,103 +1528,85 @@ Sende Werte mit Topic: [basistopic]/set/[ausgangsname] - Transfair format: + Transfer format: Übertragungsformat: - Including piCtory configuration - Inklusive piCtory Konfiguraiton + Include PiCtory configuration + PiCtory Konfiguration einbeziehen - Remove all files on Revolution Pi before upload - Alle Dateien auf Revolution Pi vor dem Hochladen löschen + Remove all files on RevPi before upload + Alle Dateien auf RevPi vor dem Hochladen löschen - Control files - Steuerdateien + Control Files + Datei-Steuerung - piCtory configuraiton - piCtory Konfiguration + PiCtory configuration + PiCtory Konfiguration - Process image from piControl0 - Prozessabbild von piControl0 - - - - sec. - Sek. - - - - Software watchdog (0=disabled): - Software watchdog (0=deaktiviert): + Process image from piControl + piControl Prozessabbild diag_revpiinfo + + + Program Information + Programminformation + + + + RevPi Commander (Python/PLC) + RevPi Commander (Python/PLC) + RevPiPyLoad version on RevPi: RevPiPyLoad Version auf RevPi: - - Program information - Programminformationen - - - - RevPi Python PLC - Commander - RevPi Python SPS - Commander + + RevPiModIO, RevPiPyLoad and RevPiPyControl are community driven projects. They are all free and open source software. +All of them come with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. + +(c) Sven Sager, License: GPLv2 + RevPiModIO, RevPiPyLoad und RevPiPyControl sind Community-Projekte. Sie sind freie Open-Source-Software. +Für diese Software wird, soweit gesetzlich zulässig, keinerlei Gewährleistung übernommen. + +(c) Sven Sager, License: GPLv Version: - - - - - RevPiModIO, RevPiPyLoad and RevPiPyControl are community driven projects. They are all free and open source software. -All of them comes with ABSOLUTELY NO WARRANTY, to the extent permitted by -applicable law. - -(c) Sven Sager, License: GPLv2 - + Version: diag_search - - - Search Revolution Pi devices - Revolution Pi Geräte suchen - - - - Searching for Revolution Pi devices in your network... - Netzwerk nach Revolution Pi Geräten durchsuchen... - Restart search Suche neu starten - - IP address - IP Adresse + + ZeroConf name + ZeroConf Name - - &Connect to Revolution Pi - Mit RevPi &verbinden + + IP address + IP-Adresse @@ -1637,9 +1614,19 @@ applicable law. Verbindung &speichern - - Zero-conf name - Zero-conf Name + + RevPi device search + RevPi Gerät suchen + + + + Searching for RevPi devices on the network + Suche nach RevPi Geräten im Netzwerk + + + + &Connect to RevPi + Mit RevPi &verbinden @@ -1649,70 +1636,70 @@ applicable law. Copy IP address - IP Adresse kopieren + IP-Adresse kopieren - Open piCtory - piCtory öffnen + Open PiCtory + PiCtory öffnen + + + + Connect via an encrypted SSH tunnel. + Verbinden über verschlüsselten SSH-Tunnel. + + + + Enable these connections on RevPi. + Aktiviere diese Verbindungen auf RevPi. + + + + Connect to RevPi + Verbinden mit RevPi Connect via SSH (recommended) Über SSH verbinden (empfohlen) - - - Establish a connection via encrypted SSH tunnel - Verbindung über verschlüsselten SSH Tunnel herstellen - Connect via XML-RPC Über XML-RPC verbinden - - - You have to configure your Revolution Pi to accept this connections - Sie müssen den Revolution Pi für diese Art der Verbindung konfigurieren - Connect Verbinden - - - Connect to Revoluton Pi - Mit Revolution Pi verbinden - diag_simulator - - - piControl simulator - piControl Simulator - - - - Simulator settings - Simulatoreinstellungen - Last used: Zuletzt verwendet: + + + piControl Simulator + piControl Simulator + + + + Simulator Settings + Simulatoreinstellungen + - piCtory file: - piCtory Datei: + PiCtory file: + Pictory Datei: - select... - auswählen... + Select + Auswählen @@ -1722,151 +1709,151 @@ applicable law. Stop action: - Stopaktion: + Stopp-Aktion: Restart action: - Neustartaktion: + Neustart-Aktion: - Restore piCtory default values - piCtory Standardwerte setzen + Restore PiCtory default values + PiCtory Standardwerte wiederherstellen - Reset everything to ZERO - Alles auf NULL setzen + Reset all values to NULL + Alle Werte auf NULL setzen - RevPiModIO integration + RevPiModIO Integration RevPiModIO Integration + + + To use this simulator, call RevPiModIO with the following additional parameters: + Zum Verwenden dieses Simulators RevPiModIO mit den folgenden zusätzlichen Parametern aufrufen: + - Start with piCtory default values - Start mit piCtory Standardwerten + Start with PiCtory default values + Mit PiCtory Standardwerten starten + + + + Start without changing the current process image + Mit aktuellem Prozessabbild starten Start with empty process image - Start mit leerem Prozessabbild - - - - Start without changing actual process image - Start ohne Veränderung des Prozessabbilds + Mit leerem Prozessabbild starten Remove process image file Prozessabbilddatei löschen - - - You can work with this simulator if you call RevPiModIO with this additional parameters: - Mit diesem Simulator kann gearbeitet werden, indem zum Aufruf von RevPiModIO folgende Parameter hinzugefügt werden: - diag_sshauth SSH authentication - SSH Authentifizierung + SSH-Authentifizierung - SSH username: - SSH Benutzername: + SSH user name: + SSH-Benutzername: SSH password: - SSH Passwort: + SSH-Passwort: - Username and password will be saved in secured operating systems's password storage. - Benutzername und Kennwort werden im Passwortspeicher vom Betriebssystem gesichert. - - - - Save username and password - Benutzername und Kennwort merken + Save user name and password in secure password storage. + Benutzername und Kennwort im sicheren Passwortspeicher speichern. - Note: The default user for SSH is "pi" which differs from the web configuration. You can find the password on the sticker on the device. - Hinweis: Der Standardbenutzer für SSH ist "pi" dies weicht von der Web-Konfiguration ab. Das Kennwort finden sie auf dem Aufkleber am Gerät. + Default SSH user is "pi". The device password is on the RevPi housing sticker. + Der Standardbenutzer für SSH ist "pi". Das Gerätepasswort befindet sich auf dem Aufkleber am RevPi Gehäuse. + + + + Save user name and password + Benutzername und Kennwort speichern wid_debugcontrol - Revolution Pi devices - Revolution Pi Module + RevPi Devices + RevPi Geräte - Open to stay on top + Keep window on top Immer im Vordergrund - IO Control - EA Übertragung + I/O Control + I/O-Steuerung + + + + Read all I/O values and discard local changes (F4). + +Hold this button to refresh the I/Os every 200 ms. + Alle I/O-Werte lesen und lokale Änderungen verwerfen (F4). + +Gedrückt halten, um die I/O-Werte alle 200 ms zu aktualisieren. - Read &all IO values - &Alle EA Werte lesen + Read &all I/O values + &Alle I/O-Werte lesen + + + + Refresh all I/O values that have not been modified locally (F5). + +Hold this button to refresh the I/Os every 200 ms. + Alle lokal unveränderten I/O-Werte aktualisieren (F5). + +Gedrückt halten, um die I/O-Werte alle 200 ms zu aktualisieren. - &Refresh unchanged IOs - Unve&ränderte EAs lesen + &Refresh unchanged I/Os + &Ungeänderte I/Os aktualisieren - Write locally changed output values to process image (F6) - Schreibe lokal veränderte Ausgangswerte in das Prozessabbild (F6) + Write locally modified output values to process image (F6). + Lokal geänderte Ausgangswerte in das Prozessabbild übertragen (F6). &Write changed outputs - Ausgänge &schreiben + &Geänderte Ausgänge schreiben &Auto refresh values - &Automatisch aktualisieren + &Werte automatisch aktualisieren - and write outputs - und Ausgänge schreiben - - - - Read all IO values and discard local changes (F4) - -Hold this button pressed and it will refresh the IOs every 200 ms. - Alle EA Werte lesen und lokale Änderungen überschreiben (F4) - -Wird der Button gehalten, aktualisieren sich die EAs alle 200 ms. - - - - Refresh all IO values which are locally not changed (F5) - -Hold this button pressed and it will refresh the IOs every 200 ms. - Alle EA Werte aktualisieren, die lokal nicht geändert sind (F5) - -Wird der Button gehalten, aktualisieren sich die EAs alle 200 ms. + Write outputs + Ausgänge schreiben @@ -1881,12 +1868,12 @@ Wird der Button gehalten, aktualisieren sich die EAs alle 200 ms. win_files - File manager + File Manager Dateimanager - Local computer + Local Computer Lokaler Computer @@ -1904,6 +1891,11 @@ Wird der Button gehalten, aktualisieren sich die EAs alle 200 ms. Reload file list Dateiliste neu laden + + + RevPi + RevPi + RevPiPyLoad working directory: @@ -1911,13 +1903,8 @@ Wird der Button gehalten, aktualisieren sich die EAs alle 200 ms. - Stop - Upload - Start - Stoppen - Hochladen -Starten - - - - Revolution Pi - + Stop, upload, start + Stoppen, hochladen, starten @@ -1925,22 +1912,22 @@ Wird der Button gehalten, aktualisieren sich die EAs alle 200 ms. PLC &start - SPS &start + SPS &starten PLC s&top - SPS s&top + SPS s&toppen PLC restart - SPS Neustart + SPS neu starten PLC &logs - SPS &Logdateien + SPS-&Logdateien @@ -1950,7 +1937,7 @@ Wird der Button gehalten, aktualisieren sich die EAs alle 200 ms. PLC watch &mode - SPS &Monitor + SPS-&Monitor-Modus @@ -1973,59 +1960,104 @@ Wird der Button gehalten, aktualisieren sich die EAs alle 200 ms. &Verbindungen - - &Connections... - &Verbindungen... + + &Search RevPi + RevPi &suchen - - &Search Revolution Pi... - &Suche Revolution Pi... + + Visit &website + Zur &Website + + + + &Info + &Info + + + + PLC &options + SPS-&Optionen + + + + PLC progra&m + SPS-Progra&mm + + + + PLC de&veloper + SPS-Ent&wickler + + + + PiCtory configuration + PiCtory Konfiguration + + + + Reset driver + Treiber zurücksetzen + + + + RevPi si&mulator + RevPi Si&mulator &Quit &Beenden - - - Visit &webpage... - &Webseite besuchen... - - - - PLC &logs... - SPS &Logdateien... - - - - PLC &options... - SPS &Optionen... - - - - PLC progra&m... - SPS Progra&mm... - - - - PLC de&veloper... - SPS Ent&wickler... - - - - piCtory configuraiton... - piCtory Konfiguration... - &Disconnect &Trennen + + + &Connections... + &Verbindungen... + + + + &Search RevPi... + RevPi &suchen... + + + + Visit &website... + Zur &Website... + + + + &Info... + &Info... + + + + PLC &logs... + SPS-&Logdateien... + + + + PLC &options... + SPS-&Optionen... + + + + PLC progra&m... + SPS-Progra&mm... + + + + PiCtory configuration... + PiCtory Konfiguration... + Reset driver... - Treiber zurücksetzen... + Treiber zurücksetzen... @@ -2033,17 +2065,17 @@ Wird der Button gehalten, aktualisieren sich die EAs alle 200 ms. RevPi Si&mulator... - - &Info... - + + PLC de&veloper... + SPS-Ent&wickler... win_revpilogfile - RevPi Python PLC Logfiles - RevPi Python PLC Logdateien + RevPi Python PLC Log Files + RevPi Python SPS-Logdateien @@ -2052,23 +2084,23 @@ Wird der Button gehalten, aktualisieren sich die EAs alle 200 ms. - Linewrap + Line wrap Zeilenumbruch - RevPiPyLoad - Logfile - RevPiPyLoad - Logdatei + RevPiPyLoad Log File + RevPiPyLoad Logdatei + + + + Python PLC Program Log File + Python-SPS-Programm-Logdatei Clear view Ansicht leeren - - - Python PLC program - Logfile - Python PLC Programm - Logdatei - diff --git a/src/revpicommander/mqttmanager.py b/src/revpicommander/mqttmanager.py index c8c5287..1505051 100644 --- a/src/revpicommander/mqttmanager.py +++ b/src/revpicommander/mqttmanager.py @@ -81,7 +81,7 @@ class MqttManager(QtWidgets.QDialog, Ui_diag_mqtt): if self._changesdone(): ask = QtWidgets.QMessageBox.question( self, self.tr("Question"), self.tr( - "Do you really want to quit? \nUnsaved changes will be lost." + "Quit without saving?\nUnsaved changes will be lost." ) ) == QtWidgets.QMessageBox.Yes if ask: @@ -94,7 +94,7 @@ class MqttManager(QtWidgets.QDialog, Ui_diag_mqtt): if not self._load_settings(): QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Can not load the MQTT settings dialog. Missing values!" + "Cannot load the MQTT settings dialog. Missing values." ) ) return QtWidgets.QDialog.Rejected diff --git a/src/revpicommander/revpicommander.py b/src/revpicommander/revpicommander.py index d848c6d..f9ee948 100644 --- a/src/revpicommander/revpicommander.py +++ b/src/revpicommander/revpicommander.py @@ -142,9 +142,9 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): # If RevPiPyLoad is not running, we can try to activate it via ssh QtWidgets.QMessageBox.information( self, self.tr("Information"), self.tr( - "Can not connect to RevPiPyLoad service through SSH tunnel!\n\n" - "We are trying to activate this service now and reconnect. The settings can be " - "changed at any time via 'webstatus'." + "Cannot connect to the RevPiPyLoad service through the SSH tunnel.\n\n" + "Service activation and reconnection in progress. The settings can be " + "changed at any time via Cockpit." ), ) revpi_settings.ssh_enable_revpipyload = True @@ -259,9 +259,9 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): diag_connecting = BackgroundWaiter( revpi_settings.timeout, - self.tr("Establish a connection to the Revolution Pi..."), + self.tr("Connecting to RevPi"), self, - self.tr("Revolution Pi connected!"), + self.tr("Connected to RevPi"), ) helper.cm.connection_established.connect(diag_connecting.requestInterruption) @@ -294,7 +294,7 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): th_connecting.finished.connect(diag_connecting.requestInterruption) th_connecting.start() - diag_connecting.exec_dialog(self.tr("Connecting..."), False) + diag_connecting.exec_dialog(self.tr("Connecting"), False) @QtCore.pyqtSlot() def on_act_connections_triggered(self): @@ -328,18 +328,18 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): if helper.cm.pyload_simulate(configrsc_file, procimg_file, diag.clean_procimg): QtWidgets.QMessageBox.information( - self, self.tr("Simulator started..."), self.tr( - "The simulator is running!\n\nYou can work with this simulator if your call " - "RevPiModIO with this additional parameters:\nprocimg={0}\nconfigrsc={1}\n\n" - "You can copy that from header textbox." + self, self.tr("Simulator started"), self.tr( + "Simulator is running.\n\nUse the additional " + "RevPiModIO parameters:\nprocimg={0}\nconfigrsc={1}\n\n" + "from the header text box." ).format(procimg_file, configrsc_file) ) else: log.error("Can not start simulator") QtWidgets.QMessageBox.critical( - self, self.tr("Can not start..."), self.tr( - "Can not start the simulator! Maybe the piCtory file is corrupt " - "or you have no write permissions for '{0}'." + self, self.tr("Cannot start"), self.tr( + "Cannot start the simulator. The PiCtory file might be invalid " + "or you do not have write permissions for '{0}'." ).format(procimg_file) ) @@ -354,8 +354,8 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): if "load_plclog" not in helper.cm.xml_funcs: QtWidgets.QMessageBox.warning( self, self.tr("Warning"), self.tr( - "This version of Logviewer ist not supported in version {0} " - "of RevPiPyLoad on your RevPi! You need at least version 0.4.1." + "This version of Log Viewer is not supported in version {0} " + "of RevPiPyLoad on your RevPi. At least version 0.4.1 is required." ).format(helper.cm.call_remote_function("version", default_value="-")) ) return None @@ -375,7 +375,7 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): QtWidgets.QMessageBox.warning( self, self.tr("Warning"), self.tr( "XML-RPC access mode in the RevPiPyLoad " - "configuration is too small to access this dialog!" + "configuration is too low to access this dialog." ) ) return @@ -384,9 +384,9 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): if helper.cm.pyload_version < (0, 6, 0): QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "The Version of RevPiPyLoad on your Revolution Pi ({0}) is to old. " - "This Version of RevPiCommander require at least version 0.6.0 " - "of RevPiPyLoad. Please update your Revolution Pi!" + "The version of RevPiPyLoad on your RevPi ({0}) is too old. " + "This version of RevPi Commander requires at least version 0.6.0 " + "of RevPiPyLoad. Update your RevPi." ) ) return @@ -405,7 +405,7 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): QtWidgets.QMessageBox.warning( self, self.tr("Warning"), self.tr( "XML-RPC access mode in the RevPiPyLoad " - "configuration is too small to access this dialog!" + "configuration is too low to access this dialog." ) ) return @@ -414,7 +414,7 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): @QtCore.pyqtSlot() def on_act_developer_triggered(self): - """Extent developer mode to main window.""" + """Extend developer mode to main window.""" if not helper.cm.connected: return @@ -437,10 +437,10 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): ask = QtWidgets.QMessageBox.question( self, self.tr("Question"), self.tr( - "Are you sure to reset piControl?\n" - "The pictory configuration will be reloaded. During that time " - "the process image will be interrupted and could rise errors " - "on running control programs!" + "Are you sure you want to reset piControl?\n" + "The PiCtory configuration will be reloaded. During that time, " + "the process image will be interrupted and could cause errors " + "on running control programs." ) ) if ask != QtWidgets.QMessageBox.Yes: @@ -450,14 +450,14 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): if ec == 0: QtWidgets.QMessageBox.information( self, self.tr("Success"), self.tr( - "piControl reset executed successfully" + "piControl reset completed successfully." ) ) else: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "piControl reset could not be executed successfully" + "piControl reset could not be completed." ) ) @@ -474,7 +474,7 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): @QtCore.pyqtSlot() def on_act_webpage_triggered(self): """Open project page in default browser of operating system.""" - webbrowser.open("https://revpimodio.org") + webbrowser.open("https://revpimodio2.readthedocs.io/en/latest/") @QtCore.pyqtSlot() def on_act_info_triggered(self): @@ -504,13 +504,13 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): """Restart plc program on revolution pi.""" if helper.cm.simulating: rc = QtWidgets.QMessageBox.question( - self, self.tr("Reset to piCtory defaults..."), self.tr( + self, self.tr("Reset to PiCtory defaults"), self.tr( "Do you want to reset your process image to {0} values?\n" "You have to stop other RevPiModIO programs before doing that, " "because they could reset the outputs." ).format( self.tr("zero") if helper.settings.value("simulator/restart_zero", False, bool) - else self.tr("piCtory default")) + else self.tr("PiCtory defaults")) ) == QtWidgets.QMessageBox.Yes if rc: # Set piCtory default values in process image @@ -531,10 +531,10 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): elif "psstart" not in helper.cm.xml_funcs: QtWidgets.QMessageBox.warning( self, self.tr("Warning"), self.tr( - "The watch mode ist not supported in version {0} " - "of RevPiPyLoad on your RevPi! You need at least version " - "0.5.3! Maybe the python3-revpimodio2 module is not " - "installed on your RevPi at least version 2.0.0." + "The watch mode is not supported in version {0} " + "of RevPiPyLoad on your RevPi. At least version 0.5.3 " + "is required. The python3-revpimodio2 module may be missing " + "or older than version 2.0.0." ).format(helper.cm.call_remote_function("version", "-")) ) self.btn_plc_debug.setChecked(False) @@ -543,8 +543,8 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): elif helper.cm.xml_mode < 1 and not helper.cm.simulating: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Can not load this function, because your ACL level is to low!\n" - "You need at least level 1 to read or level 3 to write." + "Cannot load this function, because your ACL level is too low.\n" + "At least level 1 to read or level 3 to write is required." ) ) self.btn_plc_debug.setChecked(False) @@ -558,9 +558,8 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): debugcontrol.deleteLater() QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Can not load piCtory configuration. \n" - "Did you create a hardware configuration? " - "Please check this in piCtory!" + "Cannot load PiCtory configuration.\n" + "Check hardware configuration in PiCtory." ) ) self.btn_plc_debug.setChecked(False) diff --git a/src/revpicommander/revpifiles.py b/src/revpicommander/revpifiles.py index 591700e..bdfb637 100644 --- a/src/revpicommander/revpifiles.py +++ b/src/revpicommander/revpifiles.py @@ -84,7 +84,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): self.dc_settings = {} self.tree_files_counter = 0 self.tree_files_counter_max = 10000 - self.lbl_path_local.setText(helper.cm.settings.watch_path or self.tr("Please select...")) + self.lbl_path_local.setText(helper.cm.settings.watch_path or self.tr("Select")) self.lbl_path_local.setToolTip(self.lbl_path_local.text()) self.btn_all.setEnabled(False) @@ -120,13 +120,13 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): if stop_restart and helper.cm.call_remote_function("plcstop") is None: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Can not stop plc program on Revolution Pi." + "Cannot stop PLC program on RevPi." ) ) return uploader = UploadFiles(self.file_list_local(), self) - if uploader.exec_dialog(self.tr("File transfer...")) == QtWidgets.QDialog.Rejected: + if uploader.exec_dialog(self.tr("File transfer")) == QtWidgets.QDialog.Rejected: return if uploader.ec == 0: @@ -134,7 +134,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): if not uploader.plc_program_included: QtWidgets.QMessageBox.information( self, self.tr("Information"), self.tr( - "A PLC program has been uploaded. Please check the " + "A PLC program has been uploaded. Check the " "PLC program settings to see if the correct program " "is specified as the start program." ) @@ -143,7 +143,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): elif uploader.ec == -1: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "The Revolution Pi could not process some parts of the " + "RevPi cannot process some parts of the " "transmission." ) ) @@ -151,13 +151,13 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): elif uploader.ec == -2: QtWidgets.QMessageBox.critical( self, self.tr("Error"), - self.tr("Errors occurred during transmission") + self.tr("Errors occurred during transmission.") ) if stop_restart and helper.cm.call_remote_function("plcstart", default_value=1) != 0: QtWidgets.QMessageBox.warning( self, self.tr("Warning"), self.tr( - "Could not start the plc program on Revolution Pi." + "Cannot start the PLC program on RevPi." ) ) @@ -169,7 +169,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): if "set_plcprogram" in helper.cm.xml_funcs: self.btn_mark_plcprogram.setEnabled(False) self.btn_mark_plcprogram.setToolTip(self.tr( - "Set as start file" + "Set as start program." )) if len(self.tree_files_revpi.selectedItems()) == 1: item = self.tree_files_revpi.selectedItems()[0] @@ -177,7 +177,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): else: self.btn_mark_plcprogram.setEnabled(False) self.btn_mark_plcprogram.setToolTip(self.tr( - "Upgrade your Revolution Pi! This function needs at least 'revpipyload' 0.11.0" + "Upgrade your RevPi. This function needs at least 'revpipyload' 0.11.0." )) self.btn_all.setEnabled(state_local) @@ -186,17 +186,17 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): if "plcdeletefile" not in helper.cm.xml_funcs: self.btn_delete_revpi.setEnabled(False) self.btn_delete_revpi.setToolTip(self.tr( - "Upgrade your Revolution Pi! This function needs at least 'revpipyload' 0.9.5" + "Upgrade your RevPi. This function needs at least 'revpipyload' 0.9.5." )) else: self.btn_delete_revpi.setEnabled(state_revpi) self.btn_delete_revpi.setToolTip(self.tr( - "Deletes selected files immediately on the Revolution Pi" + "Deletes selected files immediately on RevPi." )) if "plcdownload_file" not in helper.cm.xml_funcs: self.btn_to_left.setEnabled(False) self.btn_to_left.setToolTip(self.tr( - "Upgrade your Revolution Pi! This function needs at least 'revpipyload' 0.9.5" + "Upgrade your RevPi. This function needs at least 'revpipyload' 0.9.5." )) elif not helper.cm.settings.watch_path: self.btn_to_left.setEnabled(False) @@ -275,7 +275,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): if not os.path.exists(base_dir): QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Can not open last directory '{0}'." + "Cannot open last directory '{0}'." ).format(base_dir) ) return @@ -332,7 +332,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): if not silent and self.tree_files_counter > self.tree_files_counter_max: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Stop scanning for files, because we found more than {0} files." + "Stopped scanning for files because more than {0} files were found." ).format(self.tree_files_counter_max) ) @@ -373,7 +373,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): lst_revpi = helper.cm.call_remote_function("get_filelist") self.dc_settings = helper.cm.call_remote_function("get_config", default_value={}) self.lbl_path_revpi.setText( - self.dc_settings.get("plcworkdir", self.tr("Could not load path of working dir")) + self.dc_settings.get("plcworkdir", self.tr("Cannot load the working directory path.")) ) self.lbl_path_revpi.setToolTip(self.lbl_path_revpi.text()) @@ -439,7 +439,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): elif not silent: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Can not load file list from Revolution Pi." + "Cannot load file list from RevPi." ) ) @@ -470,7 +470,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): log.debug("RevPiFiles.on_btn_select_clicked") diag_folder = QtWidgets.QFileDialog( - self, self.tr("Select folder..."), + self, self.tr("Select folder"), helper.cm.settings.watch_path, ) diag_folder.setFileMode(QtWidgets.QFileDialog.DirectoryOnly) @@ -482,7 +482,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): if not os.access(selected_dir, os.R_OK): QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Can not access the folder '{0}' to read files." + "Cannot access the folder '{0}' to read files." ) ) helper.cm.settings.watch_files = [] @@ -531,17 +531,17 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): rc = rc.data if not rc: QtWidgets.QMessageBox.critical( - self, self.tr("Error..."), self.tr( - "Error while download file '{0}'." + self, self.tr("Error"), self.tr( + "Error while downloading file '{0}'." ).format(file_name) ) else: file_name = os.path.join(helper.cm.settings.watch_path, file_name) if override is None and os.path.exists(file_name): rc_diag = QtWidgets.QMessageBox.question( - self, self.tr("Override files..."), self.tr( - "One or more files does exist on your computer! Do you want to override the existing" - "files?\n\nSelect 'Yes' to override, 'No' to download only missing files." + self, self.tr("Overwrite files"), self.tr( + "One or more files already exist on your computer. Do you want to overwrite the existing " + "files?\n\nSelect 'Yes' to overwrite, 'No' to download only missing files." ), buttons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel ) @@ -571,8 +571,8 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): lst_delete.append(item.data(0, WidgetData.file_name)) rc = QtWidgets.QMessageBox.question( - self, self.tr("Delete files from Revolution Pi..."), self.tr( - "Do you want to delete {0} files from revolution pi?" + self, self.tr("Delete files from RevPi"), self.tr( + "Do you want to delete {0} files from RevPi?" ).format(len(lst_delete)) ) if rc != QtWidgets.QMessageBox.Yes: @@ -582,8 +582,8 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): rc = helper.cm.call_remote_function("plcdeletefile", file_name, default_value=False) if not rc: QtWidgets.QMessageBox.critical( - self, self.tr("Error..."), self.tr( - "Error while delete file '{0}'." + self, self.tr("Error"), self.tr( + "Error while deleting file '{0}'." ).format(file_name) ) @@ -601,9 +601,9 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): if saved is None: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "The settings could not be saved on the Revolution Pi!\n" - "Try to save the values one mor time and check the log " - "files of RevPiPyLoad if the error rises again." + "Cannot save settings on RevPi.\n" + "Try saving the values one more time and check the " + "RevPiPyLoad log files if the error occurs again." ) ) diff --git a/src/revpicommander/revpiinfo.py b/src/revpicommander/revpiinfo.py index 9959bfd..7d8ee33 100644 --- a/src/revpicommander/revpiinfo.py +++ b/src/revpicommander/revpiinfo.py @@ -44,7 +44,7 @@ class RevPiInfo(QtWidgets.QDialog, Ui_diag_revpiinfo): elif helper.cm.connected: lst = helper.cm.call_remote_function( "get_filelist", - default_value=[self.tr("Can not load file list")] + default_value=[self.tr("Cannot load file list.")] ) else: lst = [self.tr("Not connected")] diff --git a/src/revpicommander/revpilogfile.py b/src/revpicommander/revpilogfile.py index 14b8e14..a1d9b61 100644 --- a/src/revpicommander/revpilogfile.py +++ b/src/revpicommander/revpilogfile.py @@ -207,7 +207,7 @@ class RevPiLogfile(QtWidgets.QMainWindow, Ui_win_revpilogfile): if not success: textwidget.clear() - textwidget.setPlainText(self.tr("Can not access log file on the RevPi")) + textwidget.setPlainText(self.tr("Cannot access log file on RevPi.")) elif text != "": # Function will add \n automatically textwidget.appendPlainText(text.strip("\n")) diff --git a/src/revpicommander/revpioption.py b/src/revpicommander/revpioption.py index 69f7543..6e9c3b4 100644 --- a/src/revpicommander/revpioption.py +++ b/src/revpicommander/revpioption.py @@ -187,7 +187,7 @@ class RevPiOption(QtWidgets.QDialog, Ui_diag_options): ask = QtWidgets.QMessageBox.question( self, self.tr("Question"), self.tr( - "The settings will be set on the Revolution Pi now.\n\n" + "Applying settings on RevPi.\n\n" "ACL changes and service settings are applied immediately." ) ) == QtWidgets.QMessageBox.Yes @@ -234,9 +234,9 @@ class RevPiOption(QtWidgets.QDialog, Ui_diag_options): else: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "The settings could not be saved on the Revolution Pi!\n" - "Try to save the values one mor time and check the log " - "files of RevPiPyLoad if the error rises again." + "Cannot save settings on RevPi\n" + "Try saving the values one more time and check the " + "RevPiPyLoad log files if the error occurs again." ) ) @@ -244,7 +244,7 @@ class RevPiOption(QtWidgets.QDialog, Ui_diag_options): if self._changesdone(): ask = QtWidgets.QMessageBox.question( self, self.tr("Question"), self.tr( - "Do you really want to quit? \nUnsaved changes will be lost." + "Quit without saving?\nUnsaved changes will be lost." ) ) == QtWidgets.QMessageBox.Yes @@ -273,7 +273,7 @@ class RevPiOption(QtWidgets.QDialog, Ui_diag_options): default_value=False ) self.lbl_server_status.setText( - self.tr("running") if running else self.tr("stopped") + self.tr("Running") if running else self.tr("Stopped") ) self.lbl_server_status.setStyleSheet( "color: green" if running else "color: red" @@ -292,7 +292,7 @@ class RevPiOption(QtWidgets.QDialog, Ui_diag_options): self.cbx_mqtt.setToolTip("") self.btn_mqtt.setVisible(True) self.lbl_mqtt_status.setText( - self.tr("running") if running else self.tr("stopped") + self.tr("Running") if running else self.tr("Stopped") ) self.lbl_mqtt_status.setStyleSheet( "color: green" if running else "color: red" @@ -323,8 +323,8 @@ class RevPiOption(QtWidgets.QDialog, Ui_diag_options): def on_btn_aclplcserver_clicked(self): """Start ACL manager to edit ACL entries.""" self.diag_aclmanager.setup_acl_manager(self.acl_plcserver, { - 0: self.tr("read only"), - 1: self.tr("read and write"), + 0: self.tr("Read-only"), + 1: self.tr("Read/write"), }) self.diag_aclmanager.read_only = helper.cm.xml_mode < 4 if self.diag_aclmanager.exec() == QtWidgets.QDialog.Accepted: @@ -345,8 +345,8 @@ class RevPiOption(QtWidgets.QDialog, Ui_diag_options): self.mrk_xml_ask = QtWidgets.QMessageBox.question( self, self.tr("Question"), self.tr( "Are you sure you want to deactivate the XML-RPC server? " - "You will NOT be able to access the Revolution Pi with " - "this program after saving the options!" + "RevPi will no longer be accessible from " + "this program after saving settings." ) ) == QtWidgets.QMessageBox.No if self.mrk_xml_ask: @@ -355,11 +355,11 @@ class RevPiOption(QtWidgets.QDialog, Ui_diag_options): @QtCore.pyqtSlot() def on_btn_aclxmlrpc_clicked(self): self.diag_aclmanager.setup_acl_manager(self.acl_xmlrpc, { - 0: self.tr("Start/Stop PLC program and read logs"), - 1: self.tr("+ read IOs in watch mode"), - 2: self.tr("+ read properties and download PLC program"), - 3: self.tr("+ upload PLC program"), - 4: self.tr("+ set properties") + 0: self.tr("Start/stop PLC program and read log files"), + 1: self.tr("+ Read I/Os in watch mode"), + 2: self.tr("+ Read properties and download PLC program"), + 3: self.tr("+ Upload PLC program"), + 4: self.tr("+ Set properties") }) self.diag_aclmanager.read_only = helper.cm.xml_mode < 4 if self.diag_aclmanager.exec() == QtWidgets.QDialog.Accepted: diff --git a/src/revpicommander/revpiplclist.py b/src/revpicommander/revpiplclist.py index 977b5de..6c3a81c 100644 --- a/src/revpicommander/revpiplclist.py +++ b/src/revpicommander/revpiplclist.py @@ -134,7 +134,7 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): if self.changes: ask = QtWidgets.QMessageBox.question( self, self.tr("Question"), self.tr( - "Do you really want to quit? \nUnsaved changes will be lost." + "Quit without saving?\nUnsaved changes will be lost." ) ) == QtWidgets.QMessageBox.Yes @@ -303,8 +303,8 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): if item_to_remove.childCount(): rc = QtWidgets.QMessageBox.question( self, self.tr("Question"), self.tr( - "If you remote this folder, all containing elements will be removed, too. \n\n" - "Do you want to delete folder and all elements?" + "If you remove this folder, all contained items will be removed as well.\n\n" + "Do you want to delete the folder and all contained items?" ), ) if rc != QtWidgets.QMessageBox.Yes: diff --git a/src/revpicommander/revpiprogram.py b/src/revpicommander/revpiprogram.py index 13b2089..9fc4b50 100644 --- a/src/revpicommander/revpiprogram.py +++ b/src/revpicommander/revpiprogram.py @@ -105,7 +105,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): if self.cbb_plcprogram.currentText() == "": QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "You have to select a start program, before uploading the " + "You must select a start program before uploading the " "settings." ) ) @@ -113,9 +113,9 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): ask = QtWidgets.QMessageBox.question( self, self.tr("Question"), self.tr( - "The settings will be set on the Revolution Pi now.\n\n" - "If you made changes on the 'PCL Program' section, your plc " - "program will restart now!" + "Saving settings on RevPi.\n\n" + "If you made changes in the 'PLC Program' section, your PLC " + "program will be restarted." ) ) == QtWidgets.QMessageBox.Yes @@ -137,9 +137,9 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): else: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "The settings could not be saved on the Revolution Pi!\n" - "Try to save the values one mor time and check the log " - "files of RevPiPyLoad if the error rises again." + "Cannot save settings on RevPi.\n" + "Try saving the values one more time and check the " + "RevPiPyLoad log files if the error occurs again." ) ) @@ -147,7 +147,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): if self._changesdone(): ask = QtWidgets.QMessageBox.question( self, self.tr("Question"), self.tr( - "Do you really want to quit? \nUnsaved changes will be lost." + "Quit without saving?\nUnsaved changes will be lost." ) ) == QtWidgets.QMessageBox.Yes @@ -186,10 +186,10 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): fh.close() ask = QtWidgets.QMessageBox.question( - self, self.tr("Reset driver..."), self.tr( - "Reset piControl driver after successful uploading new piCtory " + self, self.tr("Reset driver"), self.tr( + "Reset piControl driver after successfully uploading the new PiCtory " "configuration?\nThe process image will be interrupted for a " - "short time!" + "short time." ), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel ) if ask == QtWidgets.QMessageBox.Cancel: @@ -202,8 +202,8 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): if ec is None: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Got an network error while send data to Revolution Pi.\n" - "Please try again." + "Network error while sending data to RevPi.\n" + "Try again." ) ) elif ec == 0: @@ -211,7 +211,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): if ask == QtWidgets.QMessageBox.Yes: QtWidgets.QMessageBox.information( self, self.tr("Success"), self.tr( - "The transfer of the piCtory configuration " + "The transfer of the PiCtory configuration " "and the reset of piControl have been " "successfully executed." ), @@ -219,47 +219,47 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): else: QtWidgets.QMessageBox.information( self, self.tr("Success"), self.tr( - "The piCtory configuration was successfully transferred." + "The PiCtory configuration was successfully transferred." ) ) elif ec == -1: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Can not process the transferred file." + "Cannot process the transferred file." ) ) elif ec == -2: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Can not find main elements in piCtory file." + "Cannot find main elements in PiCtory file." ) ) elif ec == -4: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Contained devices could not be found on Revolution " - "Pi. The configuration may be from a newer piCtory version!" + "Cannot find contained devices on RevPi.\n" + "The configuration may be from a newer PiCtory version." ) ) elif ec == -5: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Could not load RAP catalog on Revolution Pi." + "Cannot load RAP catalog on RevPi." ) ) elif ec < 0: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "The piCtory configuration could not be " - "written on the Revolution Pi." + "Cannot write PiCtory configuration " + "on RevPi." ) ) elif ec > 0: QtWidgets.QMessageBox.warning( self, self.tr("Warning"), self.tr( - "The piCtroy configuration has been saved successfully.\n" - "An error occurred on piControl reset!" + "The PiCtory configuration has been saved successfully.\n" + "An error occurred on piControl reset." ) ) @@ -321,7 +321,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): if self.cbb_format.currentIndex() == 0: # Save files as zip archive diag_save = QtWidgets.QFileDialog( - self, self.tr("Save ZIP archive..."), + self, self.tr("Save ZIP archive"), helper.cm.settings.last_zip_file or "{0}.zip".format(helper.cm.settings.name), self.tr("ZIP archive (*.zip);;All files (*.*)") ) @@ -339,7 +339,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): elif self.cbb_format.currentIndex() == 1: # Save files as TarGz archive diag_save = QtWidgets.QFileDialog( - self, self.tr("Save TGZ archive..."), + self, self.tr("Save TGZ archive"), helper.cm.settings.last_tar_file or "{0}.tgz".format(helper.cm.settings.name), self.tr("TGZ archive (*.tgz);;All files (*.*)") ) @@ -367,7 +367,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): if plcfile is None: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Could not load PLC program from Revolution Pi." + "Cannot load PLC program from RevPi." ) ) @@ -380,8 +380,8 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): log.error(e) QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Coud not save the archive or extract the files!\n" - "Please retry.") + "Cannot save the archive or extract the files.\n" + "Try again.") ) else: QtWidgets.QMessageBox.information( @@ -411,7 +411,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): if self.cbb_format.currentIndex() == 0: # Upload zip archive content diag_open = QtWidgets.QFileDialog( - self, self.tr("Upload content of ZIP archive..."), + self, self.tr("Upload content of ZIP archive"), helper.cm.settings.last_file_upload, self.tr("ZIP archive (*.zip);;All files (*.*)") ) @@ -437,7 +437,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): else: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "The selected file ist not a ZIP archive." + "The selected file is not a ZIP archive." ) ) return @@ -445,7 +445,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): elif self.cbb_format.currentIndex() == 1: # Upload TarGz content diag_open = QtWidgets.QFileDialog( - self, self.tr("Upload content of TAR archive..."), + self, self.tr("Upload content of TAR archive"), helper.cm.settings.last_file_upload, self.tr("TAR archive (*.tgz);;All files (*.*)") ) @@ -471,7 +471,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): else: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "The selected file ist not a TAR archive." + "The selected file is not a TAR archive." ) ) return @@ -479,8 +479,8 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): # No files selected if len(lst_files) == 0: QtWidgets.QMessageBox.warning( - self, self.tr("No files to upload..."), self.tr( - "Found no files to upload in given location or archive." + self, self.tr("No files to upload"), self.tr( + "No files found in the selected location or archive." ) ) remove_temp() @@ -491,8 +491,8 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): if self.cbx_clear.isChecked() and not clean_revpi: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "There was an error deleting the files on the Revolution Pi.\n" - "Upload aborted! Please try again." + "Cannot delete files on RevPi.\n" + "Upload aborted. Try again." ) ) remove_temp() @@ -545,10 +545,10 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): if plc_program_not_in_upload: QtWidgets.QMessageBox.warning( self, self.tr("Information"), self.tr( - "Could not find the selected PLC start program in " - "uploaded files.\nThis is not an error, if the file " - "was already on the Revolution Pi. Check PLC start " - "program field" + "Cannot find the selected PLC start program in " + "the uploaded files.\nThis is not an error if the file " + "already exists on RevPi. Check the PLC start program " + "field." ) ) @@ -558,14 +558,14 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): else: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "There is no piCtory configuration in this archive." + "There is no PiCtory configuration in this archive." ) ) elif ec == -1: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "The Revolution Pi could not process some parts of the transmission." + "RevPi cannot process some parts of the transmission." ) ) @@ -590,12 +590,12 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): return diag_save = QtWidgets.QFileDialog( - self, self.tr("Save piCtory file..."), + self, self.tr("Save PiCtory file"), os.path.join( helper.cm.settings.last_dir_pictory, "{0}.rsc".format(helper.cm.settings.name) ), - self.tr("piCtory file (*.rsc);;All files (*.*)") + self.tr("PiCtory file (*.rsc);;All files (*.*)") ) diag_save.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) diag_save.setDefaultSuffix("rsc") @@ -610,7 +610,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): if bin_buffer is None: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Could not load piCtory file from Revolution Pi." + "Cannot load PiCtory file from RevPi." ) ) else: @@ -620,7 +620,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): QtWidgets.QMessageBox.information( self, self.tr("Success"), self.tr( - "piCtory configuration successfully loaded and saved to:\n{0}." + "PiCtory configuration saved to:\n{0}." ).format(filename) ) @@ -630,9 +630,9 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): return diag_open = QtWidgets.QFileDialog( - self, self.tr("Upload piCtory file..."), + self, self.tr("Upload PiCtory file"), helper.cm.settings.last_pictory_file or "{0}.rsc".format(helper.cm.settings.name), - self.tr("piCtory file (*.rsc);;All files (*.*)") + self.tr("PiCtory file (*.rsc);;All files (*.*)") ) diag_open.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen) diag_open.setFileMode(QtWidgets.QFileDialog.ExistingFile) @@ -652,7 +652,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): diag_save = QtWidgets.QFileDialog( self, - self.tr("Save piControl file..."), + self.tr("Save piControl file"), os.path.join( helper.cm.settings.last_dir_picontrol, "{0}.img".format(helper.cm.settings.name) @@ -673,7 +673,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): if bin_buffer is None: QtWidgets.QMessageBox.critical( self, self.tr("Error"), self.tr( - "Could not load process image from Revolution Pi." + "Cannot load process image from RevPi." ) ) else: @@ -683,7 +683,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): QtWidgets.QMessageBox.information( self, self.tr("Success"), self.tr( - "Process image successfully loaded and saved to:\n{0}." + "Process image saved to:\n{0}." ).format(filename) ) diff --git a/src/revpicommander/simulator.py b/src/revpicommander/simulator.py index 6aa2c4b..3b7f462 100644 --- a/src/revpicommander/simulator.py +++ b/src/revpicommander/simulator.py @@ -77,9 +77,9 @@ class Simulator(QtWidgets.QDialog, Ui_diag_simulator): @QtCore.pyqtSlot() def on_btn_configrsc_clicked(self) -> None: diag_open = QtWidgets.QFileDialog( - self, self.tr("Select downloaded piCtory file..."), + self, self.tr("Select downloaded PiCtory file"), helper.settings.value("simulator/last_dir", ".", str), - self.tr("piCtory file (*.rsc);;All files (*.*)") + self.tr("PiCtory file (*.rsc);;All files (*.*)") ) diag_open.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen) diag_open.setFileMode(QtWidgets.QFileDialog.ExistingFile) diff --git a/src/revpicommander/sshauth.py b/src/revpicommander/sshauth.py index 8598d3c..7aaa3c9 100644 --- a/src/revpicommander/sshauth.py +++ b/src/revpicommander/sshauth.py @@ -49,11 +49,11 @@ class SSHAuth(QtWidgets.QDialog, Ui_diag_sshauth): log.error(e) self._in_keyring = False QtWidgets.QMessageBox.warning( - self, self.tr("Could not save password"), self.tr( - "Could not save password to operating systems password save.\n\n" - "Maybe your operating system does not support saving passwords. " + self, self.tr("Cannot save password"), self.tr( + "Cannot save password to operating system's password store.\n\n" + "The operating system may not support saving passwords. " "This could be due to missing libraries or programs.\n\n" - "This is not an error of RevPi Commander." + "This is not a RevPi Commander error." ) ) else: diff --git a/src/revpicommander/ui/aclmanager_ui.py b/src/revpicommander/ui/aclmanager_ui.py index 1a6c777..bd2dffa 100644 --- a/src/revpicommander/ui/aclmanager_ui.py +++ b/src/revpicommander/ui/aclmanager_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'aclmanager.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -137,15 +137,15 @@ class Ui_diag_aclmanager(object): def retranslateUi(self, diag_aclmanager): _translate = QtCore.QCoreApplication.translate - diag_aclmanager.setWindowTitle(_translate("diag_aclmanager", "IP access control list")) + diag_aclmanager.setWindowTitle(_translate("diag_aclmanager", "IP Access Control List")) self.gb_acls.setTitle(_translate("diag_aclmanager", "Existing ACLs")) item = self.tb_acls.horizontalHeaderItem(0) - item.setText(_translate("diag_aclmanager", "IP Address")) + item.setText(_translate("diag_aclmanager", "IP address")) item = self.tb_acls.horizontalHeaderItem(1) item.setText(_translate("diag_aclmanager", "Access Level")) self.btn_edit.setText(_translate("diag_aclmanager", "&Edit")) self.btn_remove.setText(_translate("diag_aclmanager", "&Remove")) - self.gb_edit.setTitle(_translate("diag_aclmanager", "Add / Edit access entry")) + self.gb_edit.setTitle(_translate("diag_aclmanager", "Add/Edit Access Entry")) self.btn_clear.setText(_translate("diag_aclmanager", "Clear fields")) self.btn_add.setText(_translate("diag_aclmanager", "&Save entry")) self.lbl_ip.setText(_translate("diag_aclmanager", "IP address:")) diff --git a/src/revpicommander/ui/avahisearch_ui.py b/src/revpicommander/ui/avahisearch_ui.py index 7e5fa50..6ed1e4e 100644 --- a/src/revpicommander/ui/avahisearch_ui.py +++ b/src/revpicommander/ui/avahisearch_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'avahisearch.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -84,25 +84,25 @@ class Ui_diag_search(object): def retranslateUi(self, diag_search): _translate = QtCore.QCoreApplication.translate - diag_search.setWindowTitle(_translate("diag_search", "Search Revolution Pi devices")) - self.lbl_search.setText(_translate("diag_search", "Searching for Revolution Pi devices in your network...")) + diag_search.setWindowTitle(_translate("diag_search", "RevPi device search")) + self.lbl_search.setText(_translate("diag_search", "Searching for RevPi devices on the network")) self.btn_restart.setToolTip(_translate("diag_search", "Restart search")) self.tb_revpi.setSortingEnabled(True) item = self.tb_revpi.horizontalHeaderItem(0) - item.setText(_translate("diag_search", "Zero-conf name")) + item.setText(_translate("diag_search", "ZeroConf name")) item = self.tb_revpi.horizontalHeaderItem(1) item.setText(_translate("diag_search", "IP address")) - self.btn_connect.setText(_translate("diag_search", "&Connect to Revolution Pi")) + self.btn_connect.setText(_translate("diag_search", "&Connect to RevPi")) self.btn_save.setText(_translate("diag_search", "&Save connection")) self.act_copy_host.setText(_translate("diag_search", "Copy host name")) self.act_copy_ip.setText(_translate("diag_search", "Copy IP address")) - self.act_open_pictory.setText(_translate("diag_search", "Open piCtory")) + self.act_open_pictory.setText(_translate("diag_search", "Open PiCtory")) self.act_connect_ssh.setText(_translate("diag_search", "Connect via SSH (recommended)")) - self.act_connect_ssh.setToolTip(_translate("diag_search", "Establish a connection via encrypted SSH tunnel")) + self.act_connect_ssh.setToolTip(_translate("diag_search", "Connect via an encrypted SSH tunnel.")) self.act_connect_xmlrpc.setText(_translate("diag_search", "Connect via XML-RPC")) - self.act_connect_xmlrpc.setToolTip(_translate("diag_search", "You have to configure your Revolution Pi to accept this connections")) + self.act_connect_xmlrpc.setToolTip(_translate("diag_search", "Enable these connections on RevPi.")) self.act_connect.setText(_translate("diag_search", "Connect")) - self.act_connect.setToolTip(_translate("diag_search", "Connect to Revoluton Pi")) + self.act_connect.setToolTip(_translate("diag_search", "Connect to RevPi")) from . import ressources_rc diff --git a/src/revpicommander/ui/backgroundworker_ui.py b/src/revpicommander/ui/backgroundworker_ui.py index c0e78f1..7d1200f 100644 --- a/src/revpicommander/ui/backgroundworker_ui.py +++ b/src/revpicommander/ui/backgroundworker_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'backgroundworker.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -19,7 +19,7 @@ class Ui_diag_backgroundworker(object): self.verticalLayout = QtWidgets.QVBoxLayout(diag_backgroundworker) self.verticalLayout.setObjectName("verticalLayout") self.lbl_status = QtWidgets.QLabel(diag_backgroundworker) - self.lbl_status.setText("Status message...") + self.lbl_status.setText("Status message") self.lbl_status.setObjectName("lbl_status") self.verticalLayout.addWidget(self.lbl_status) self.pgb_status = QtWidgets.QProgressBar(diag_backgroundworker) diff --git a/src/revpicommander/ui/debugcontrol_ui.py b/src/revpicommander/ui/debugcontrol_ui.py index f1556fb..44d42d0 100644 --- a/src/revpicommander/ui/debugcontrol_ui.py +++ b/src/revpicommander/ui/debugcontrol_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'debugcontrol.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -65,21 +65,21 @@ class Ui_wid_debugcontrol(object): def retranslateUi(self, wid_debugcontrol): _translate = QtCore.QCoreApplication.translate - self.gb_devices.setTitle(_translate("wid_debugcontrol", "Revolution Pi devices")) - self.cbx_stay_on_top.setText(_translate("wid_debugcontrol", "Open to stay on top")) - self.gb_control.setTitle(_translate("wid_debugcontrol", "IO Control")) - self.btn_read_io.setToolTip(_translate("wid_debugcontrol", "Read all IO values and discard local changes (F4)\n" + self.gb_devices.setTitle(_translate("wid_debugcontrol", "RevPi Devices")) + self.cbx_stay_on_top.setText(_translate("wid_debugcontrol", "Keep window on top")) + self.gb_control.setTitle(_translate("wid_debugcontrol", "I/O Control")) + self.btn_read_io.setToolTip(_translate("wid_debugcontrol", "Read all I/O values and discard local changes (F4).\n" "\n" -"Hold this button pressed and it will refresh the IOs every 200 ms.")) - self.btn_read_io.setText(_translate("wid_debugcontrol", "Read &all IO values")) - self.btn_refresh_io.setToolTip(_translate("wid_debugcontrol", "Refresh all IO values which are locally not changed (F5)\n" +"Hold this button to refresh the I/Os every 200 ms.")) + self.btn_read_io.setText(_translate("wid_debugcontrol", "Read &all I/O values")) + self.btn_refresh_io.setToolTip(_translate("wid_debugcontrol", "Refresh all I/O values that have not been modified locally (F5).\n" "\n" -"Hold this button pressed and it will refresh the IOs every 200 ms.")) - self.btn_refresh_io.setText(_translate("wid_debugcontrol", "&Refresh unchanged IOs")) - self.btn_write_o.setToolTip(_translate("wid_debugcontrol", "Write locally changed output values to process image (F6)")) +"Hold this button to refresh the I/Os every 200 ms.")) + self.btn_refresh_io.setText(_translate("wid_debugcontrol", "&Refresh unchanged I/Os")) + self.btn_write_o.setToolTip(_translate("wid_debugcontrol", "Write locally modified output values to process image (F6).")) self.btn_write_o.setText(_translate("wid_debugcontrol", "&Write changed outputs")) self.cbx_refresh.setText(_translate("wid_debugcontrol", "&Auto refresh values")) - self.cbx_write.setText(_translate("wid_debugcontrol", "and write outputs")) + self.cbx_write.setText(_translate("wid_debugcontrol", "Write outputs")) if __name__ == "__main__": diff --git a/src/revpicommander/ui/debugios_ui.py b/src/revpicommander/ui/debugios_ui.py index 7ed9cd8..62be264 100644 --- a/src/revpicommander/ui/debugios_ui.py +++ b/src/revpicommander/ui/debugios_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'debugios.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. diff --git a/src/revpicommander/ui/files_ui.py b/src/revpicommander/ui/files_ui.py index 7a074ee..157d7cb 100644 --- a/src/revpicommander/ui/files_ui.py +++ b/src/revpicommander/ui/files_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'files.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -169,17 +169,17 @@ class Ui_win_files(object): def retranslateUi(self, win_files): _translate = QtCore.QCoreApplication.translate - win_files.setWindowTitle(_translate("win_files", "File manager")) - self.gb_select_local.setTitle(_translate("win_files", "Local computer")) + win_files.setWindowTitle(_translate("win_files", "File Manager")) + self.gb_select_local.setTitle(_translate("win_files", "Local Computer")) self.lbl_select_local.setText(_translate("win_files", "Path to development root:")) self.btn_select_local.setToolTip(_translate("win_files", "Open developer root directory")) self.btn_refresh_local.setToolTip(_translate("win_files", "Reload file list")) self.tree_files_local.setSortingEnabled(True) - self.gb_select_revpi.setTitle(_translate("win_files", "Revolution Pi")) + self.gb_select_revpi.setTitle(_translate("win_files", "RevPi")) self.lbl_select_revpi.setText(_translate("win_files", "RevPiPyLoad working directory:")) self.btn_refresh_revpi.setToolTip(_translate("win_files", "Reload file list")) self.tree_files_revpi.setSortingEnabled(True) - self.btn_all.setText(_translate("win_files", "Stop - Upload - Start")) + self.btn_all.setText(_translate("win_files", "Stop, upload, start")) from . import ressources_rc diff --git a/src/revpicommander/ui/mqttmanager_ui.py b/src/revpicommander/ui/mqttmanager_ui.py index 2ddc421..84e0755 100644 --- a/src/revpicommander/ui/mqttmanager_ui.py +++ b/src/revpicommander/ui/mqttmanager_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'mqttmanager.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -126,7 +126,7 @@ class Ui_diag_mqtt(object): _translate = QtCore.QCoreApplication.translate diag_mqtt.setWindowTitle(_translate("diag_mqtt", "MQTT settings")) self.gb_basetopic.setTitle(_translate("diag_mqtt", "Base topic")) - self.lbl_basetopic_description.setText(_translate("diag_mqtt", "The base topic is the first part of any mqtt topic, the Revolution Pi will publish. You can use any character includig \'/\' to structure the messages on your broker.\n" + self.lbl_basetopic_description.setText(_translate("diag_mqtt", "Base topic is the prefix for MQTT topics published by RevPi. Use \"/\" to structure broker topics.\n" "\n" "For example: revpi0000/data")) self.lbl_basetopic.setText(_translate("diag_mqtt", "Base topic:")) @@ -136,10 +136,10 @@ class Ui_diag_mqtt(object): self.cbx_send_on_event.setText(_translate("diag_mqtt", "Send exported values immediately on value change")) self.lbl_topic_event.setText(_translate("diag_mqtt", "Topic: [basetopic]/event/[ioname]")) self.gb_write_outputs.setTitle(_translate("diag_mqtt", "Set outputs")) - self.lbl_write_outputs.setText(_translate("diag_mqtt", "The Revolution Pi will subscribe a topic on which your mqtt client can publish messages with the new io value as payload.\n" + self.lbl_write_outputs.setText(_translate("diag_mqtt", "RevPi subscribes to an MQTT topic for setting output values. Publish the new I/O value as payload.\n" "\n" "Publish values with topic: [basetopic]/set/[outputname]")) - self.cbx_write_outputs.setText(_translate("diag_mqtt", "Allow MQTT to to set outputs on Revolution Pi")) + self.cbx_write_outputs.setText(_translate("diag_mqtt", "Allow MQTT to set outputs on RevPi")) self.gb_broker.setTitle(_translate("diag_mqtt", "Broker settings")) self.lbl_broker_address.setText(_translate("diag_mqtt", "Broker address:")) self.lbl_port.setText(_translate("diag_mqtt", "Broker port:")) diff --git a/src/revpicommander/ui/oss_licenses_ui.py b/src/revpicommander/ui/oss_licenses_ui.py index 06217ed..2b83b0f 100644 --- a/src/revpicommander/ui/oss_licenses_ui.py +++ b/src/revpicommander/ui/oss_licenses_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'oss_licenses.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -59,14 +59,14 @@ class Ui_diag_oss_licenses(object): def retranslateUi(self, diag_oss_licenses): _translate = QtCore.QCoreApplication.translate - diag_oss_licenses.setWindowTitle(_translate("diag_oss_licenses", "Open-Source licenses")) + diag_oss_licenses.setWindowTitle(_translate("diag_oss_licenses", "Open source licenses")) self.tb_oss_licenses.setSortingEnabled(True) item = self.tb_oss_licenses.horizontalHeaderItem(0) item.setText(_translate("diag_oss_licenses", "Software")) item = self.tb_oss_licenses.horizontalHeaderItem(1) item.setText(_translate("diag_oss_licenses", "License")) - self.action_start.setText(_translate("diag_oss_licenses", "More licenses...")) - self.action_start.setToolTip(_translate("diag_oss_licenses", "Show more open-source software licenses")) + self.action_start.setText(_translate("diag_oss_licenses", "More licenses")) + self.action_start.setToolTip(_translate("diag_oss_licenses", "Show more open source software licenses")) if __name__ == "__main__": diff --git a/src/revpicommander/ui/revpicommander_ui.py b/src/revpicommander/ui/revpicommander_ui.py index 5d488b4..04754fc 100644 --- a/src/revpicommander/ui/revpicommander_ui.py +++ b/src/revpicommander/ui/revpicommander_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'revpicommander.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -177,17 +177,17 @@ class Ui_win_revpicommander(object): self.men_plc.setTitle(_translate("win_revpicommander", "&PLC")) self.men_connections.setTitle(_translate("win_revpicommander", "&Connections")) self.act_connections.setText(_translate("win_revpicommander", "&Connections...")) - self.act_search.setText(_translate("win_revpicommander", "&Search Revolution Pi...")) + self.act_search.setText(_translate("win_revpicommander", "&Search RevPi...")) self.act_quit.setText(_translate("win_revpicommander", "&Quit")) - self.act_webpage.setText(_translate("win_revpicommander", "Visit &webpage...")) - self.act_info.setText(_translate("win_revpicommander", "&Info...")) + self.act_webpage.setText(_translate("win_revpicommander", "Visit &website")) + self.act_info.setText(_translate("win_revpicommander", "&Info")) self.act_logs.setText(_translate("win_revpicommander", "PLC &logs...")) self.act_options.setText(_translate("win_revpicommander", "PLC &options...")) self.act_program.setText(_translate("win_revpicommander", "PLC progra&m...")) self.act_developer.setText(_translate("win_revpicommander", "PLC de&veloper...")) - self.act_pictory.setText(_translate("win_revpicommander", "piCtory configuraiton...")) + self.act_pictory.setText(_translate("win_revpicommander", "PiCtory configuration")) self.act_disconnect.setText(_translate("win_revpicommander", "&Disconnect")) - self.act_reset.setText(_translate("win_revpicommander", "Reset driver...")) + self.act_reset.setText(_translate("win_revpicommander", "Reset driver")) self.act_simulator.setText(_translate("win_revpicommander", "RevPi si&mulator...")) from . import ressources_rc diff --git a/src/revpicommander/ui/revpiinfo_ui.py b/src/revpicommander/ui/revpiinfo_ui.py index 7494cdc..2963236 100644 --- a/src/revpicommander/ui/revpiinfo_ui.py +++ b/src/revpicommander/ui/revpiinfo_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'revpiinfo.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -75,7 +75,7 @@ class Ui_diag_revpiinfo(object): spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.gridLayout.addItem(spacerItem, 5, 0, 1, 1) self.lbl_link = QtWidgets.QLabel(diag_revpiinfo) - self.lbl_link.setText("

https://revpimodio.org/

") + self.lbl_link.setText("

https://revpimodio2.readthedocs.io/en/latest/

") self.lbl_link.setOpenExternalLinks(True) self.lbl_link.setObjectName("lbl_link") self.gridLayout.addWidget(self.lbl_link, 6, 0, 1, 2) @@ -87,13 +87,12 @@ class Ui_diag_revpiinfo(object): def retranslateUi(self, diag_revpiinfo): _translate = QtCore.QCoreApplication.translate - diag_revpiinfo.setWindowTitle(_translate("diag_revpiinfo", "Program information")) - self.lbl_head.setText(_translate("diag_revpiinfo", "RevPi Python PLC - Commander")) + diag_revpiinfo.setWindowTitle(_translate("diag_revpiinfo", "Program Information")) + self.lbl_head.setText(_translate("diag_revpiinfo", "RevPi Commander (Python/PLC)")) self.lbl_lbl_version_pyload.setText(_translate("diag_revpiinfo", "RevPiPyLoad version on RevPi:")) self.lbl_lbl_version_control.setText(_translate("diag_revpiinfo", "Version:")) self.lbl_info.setText(_translate("diag_revpiinfo", "RevPiModIO, RevPiPyLoad and RevPiPyControl are community driven projects. They are all free and open source software.\n" -"All of them comes with ABSOLUTELY NO WARRANTY, to the extent permitted by\n" -"applicable law.\n" +"All of them come with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law.\n" "\n" "(c) Sven Sager, License: GPLv2")) diff --git a/src/revpicommander/ui/revpilogfile_ui.py b/src/revpicommander/ui/revpilogfile_ui.py index 179a22a..099c0ea 100644 --- a/src/revpicommander/ui/revpilogfile_ui.py +++ b/src/revpicommander/ui/revpilogfile_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'revpilogfile.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -79,13 +79,13 @@ class Ui_win_revpilogfile(object): def retranslateUi(self, win_revpilogfile): _translate = QtCore.QCoreApplication.translate - win_revpilogfile.setWindowTitle(_translate("win_revpilogfile", "RevPi Python PLC Logfiles")) + win_revpilogfile.setWindowTitle(_translate("win_revpilogfile", "RevPi Python PLC Log Files")) self.cbx_stay_on_top.setText(_translate("win_revpilogfile", "Stay on top of all windows")) - self.cbx_wrap.setText(_translate("win_revpilogfile", "Linewrap")) - self.lbl_daemon.setText(_translate("win_revpilogfile", "RevPiPyLoad - Logfile")) + self.cbx_wrap.setText(_translate("win_revpilogfile", "Line wrap")) + self.lbl_daemon.setText(_translate("win_revpilogfile", "RevPiPyLoad Log File")) self.btn_daemon.setText(_translate("win_revpilogfile", "Clear view")) self.btn_app.setText(_translate("win_revpilogfile", "Clear view")) - self.lbl_app.setText(_translate("win_revpilogfile", "Python PLC program - Logfile")) + self.lbl_app.setText(_translate("win_revpilogfile", "Python PLC Program Log File")) if __name__ == "__main__": diff --git a/src/revpicommander/ui/revpioption_ui.py b/src/revpicommander/ui/revpioption_ui.py index 59b4583..cd07174 100644 --- a/src/revpicommander/ui/revpioption_ui.py +++ b/src/revpicommander/ui/revpioption_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'revpioption.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -123,34 +123,34 @@ class Ui_diag_options(object): def retranslateUi(self, diag_options): _translate = QtCore.QCoreApplication.translate diag_options.setWindowTitle(_translate("diag_options", "RevPi Python PLC Options")) - self.gb_plc.setTitle(_translate("diag_options", "Start / Stop behavior of PLC program")) - self.lbl_replace_io.setText(_translate("diag_options", "Replace IO file:")) - self.cbx_zeroonerror.setText(_translate("diag_options", "... after exception and errors")) + self.gb_plc.setTitle(_translate("diag_options", "PLC Start/Stop Behavior")) + self.lbl_replace_io.setText(_translate("diag_options", "Replace I/O file:")) + self.cbx_zeroonerror.setText(_translate("diag_options", "after exceptions or errors")) self.cbx_autostart.setText(_translate("diag_options", "Start PLC program automatically")) self.cbb_reset_driver_action.setItemText(0, _translate("diag_options", "Do nothing")) - self.cbb_reset_driver_action.setItemText(1, _translate("diag_options", "Restart after piCtory changed")) + self.cbb_reset_driver_action.setItemText(1, _translate("diag_options", "Restart after PiCtory changes")) self.cbb_reset_driver_action.setItemText(2, _translate("diag_options", "Always restart the PLC program")) self.lbl_reset_driver_action.setText(_translate("diag_options", "Driver reset action:")) - self.lbl_plc_zero.setText(_translate("diag_options", "Set process image to NULL if program terminates...")) - self.cbb_replace_io.setItemText(0, _translate("diag_options", "Do not use replace io file")) + self.lbl_plc_zero.setText(_translate("diag_options", "Set process image to NULL if the program terminates")) + self.cbb_replace_io.setItemText(0, _translate("diag_options", "Do not replace I/O file")) self.cbb_replace_io.setItemText(1, _translate("diag_options", "Use static file from RevPiPyLoad")) self.cbb_replace_io.setItemText(2, _translate("diag_options", "Use dynamic file from work directory")) - self.cbb_replace_io.setItemText(3, _translate("diag_options", "Give own path and filename")) - self.cbx_zeroonexit.setText(_translate("diag_options", "... sucessfully without error")) + self.cbb_replace_io.setItemText(3, _translate("diag_options", "Use custom path and file name")) + self.cbx_zeroonexit.setText(_translate("diag_options", "without errors")) self.lbl_plc_delay.setText(_translate("diag_options", "Restart delay in seconds:")) self.cbx_autoreload.setText(_translate("diag_options", "Restart PLC program after exit or crash")) - self.lbl_lbl_reset_driver_action.setText(_translate("diag_options", "PLC program behavior after piCtory driver reset clicked")) - self.gb_server.setTitle(_translate("diag_options", "RevPiPyLoad server services")) + self.lbl_lbl_reset_driver_action.setText(_translate("diag_options", "PLC program behavior after PiCtory driver reset")) + self.gb_server.setTitle(_translate("diag_options", "RevPiPyLoad Server Services")) self.btn_aclplcserver.setText(_translate("diag_options", "Edit ACL")) self.cbx_mqtt.setText(_translate("diag_options", "MQTT process image publisher")) - self.cbx_plcserver.setText(_translate("diag_options", "Start RevPi piControl server")) + self.cbx_plcserver.setText(_translate("diag_options", "Start piControl server")) self.lbl_server_status.setText(_translate("diag_options", "status")) - self.lbl_lbl_server_status.setText(_translate("diag_options", "piControl server is:")) + self.lbl_lbl_server_status.setText(_translate("diag_options", "piControl server:")) self.lbl_mqtt_status.setText(_translate("diag_options", "status")) - self.lbl_lbl_mqtt_status.setText(_translate("diag_options", "MQTT publish service is:")) + self.lbl_lbl_mqtt_status.setText(_translate("diag_options", "MQTT publish service:")) self.btn_mqtt.setText(_translate("diag_options", "Settings")) self.btn_aclxmlrpc.setText(_translate("diag_options", "Edit ACL")) - self.cbx_xmlrpc.setText(_translate("diag_options", "Activate XML-RPC for RevPiCommander")) + self.cbx_xmlrpc.setText(_translate("diag_options", "Activate XML-RPC for RevPi Commander")) if __name__ == "__main__": diff --git a/src/revpicommander/ui/revpiplclist_ui.py b/src/revpicommander/ui/revpiplclist_ui.py index cb943fe..325970d 100644 --- a/src/revpicommander/ui/revpiplclist_ui.py +++ b/src/revpicommander/ui/revpiplclist_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'revpiplclist.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -155,19 +155,19 @@ class Ui_diag_connections(object): def retranslateUi(self, diag_connections): _translate = QtCore.QCoreApplication.translate - diag_connections.setWindowTitle(_translate("diag_connections", "Revolution Pi connections")) + diag_connections.setWindowTitle(_translate("diag_connections", "RevPi Connections")) self.lbl_name.setText(_translate("diag_connections", "Display name:")) self.lbl_address.setText(_translate("diag_connections", "Address (DNS/IP):")) - self.lbl_port.setText(_translate("diag_connections", "Port (Default {0}):")) + self.lbl_port.setText(_translate("diag_connections", "Port (default {0}):")) self.lbl_timeout.setText(_translate("diag_connections", "Connection timeout:")) - self.sbx_timeout.setSuffix(_translate("diag_connections", " sec.")) - self.lbl_folder.setText(_translate("diag_connections", "Sub folder:")) + self.sbx_timeout.setSuffix(_translate("diag_connections", " s")) + self.lbl_folder.setText(_translate("diag_connections", "Subfolder:")) self.tab_properties.setTabText(self.tab_properties.indexOf(self.tab_connection), _translate("diag_connections", "Connection")) self.lbl_ssh_use_tunnel.setText(_translate("diag_connections", "Connect over SSH tunnel:")) self.lbl_ssh_port.setText(_translate("diag_connections", "SSH port:")) self.lbl_ssh_user.setText(_translate("diag_connections", "SSH user name:")) self.tab_properties.setTabText(self.tab_properties.indexOf(self.tab_ssh), _translate("diag_connections", "Over SSH")) - self.tre_connections.headerItem().setText(0, _translate("diag_connections", "Connection name")) + self.tre_connections.headerItem().setText(0, _translate("diag_connections", "Connection Name")) self.tre_connections.headerItem().setText(1, _translate("diag_connections", "Address")) from . import ressources_rc diff --git a/src/revpicommander/ui/revpiprogram_ui.py b/src/revpicommander/ui/revpiprogram_ui.py index 4b57bf6..0e43c6c 100644 --- a/src/revpicommander/ui/revpiprogram_ui.py +++ b/src/revpicommander/ui/revpiprogram_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'revpiprogram.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -112,27 +112,27 @@ class Ui_diag_program(object): def retranslateUi(self, diag_program): _translate = QtCore.QCoreApplication.translate - diag_program.setWindowTitle(_translate("diag_program", "PLC program")) - self.gb_plc.setTitle(_translate("diag_program", "PLC program")) - self.lbl_plcarguments.setText(_translate("diag_program", "Program arguments:")) - self.lbl_plcprogram_watchdog.setText(_translate("diag_program", "Software watchdog (0=disabled):")) - self.cbx_plcworkdir_set_uid.setText(_translate("diag_program", "Set write permissions for plc program to workdirectory")) + diag_program.setWindowTitle(_translate("diag_program", "PLC Program")) + self.gb_plc.setTitle(_translate("diag_program", "PLC Program")) + self.lbl_plcarguments.setText(_translate("diag_program", "Arguments:")) + self.lbl_plcprogram_watchdog.setText(_translate("diag_program", "Software watchdog (0 = disabled):")) + self.cbx_plcworkdir_set_uid.setText(_translate("diag_program", "Set write permissions for PLC program working directory")) self.lbl_plcprogram.setText(_translate("diag_program", "Python PLC start program:")) - self.sbx_plcprogram_watchdog.setSuffix(_translate("diag_program", " sec.")) - self.cb_transfair.setTitle(_translate("diag_program", "Transfair PLC program")) + self.sbx_plcprogram_watchdog.setSuffix(_translate("diag_program", " s")) + self.cb_transfair.setTitle(_translate("diag_program", "Transfer PLC program")) self.cbb_format.setItemText(0, _translate("diag_program", "ZIP archive")) self.cbb_format.setItemText(1, _translate("diag_program", "TGZ archive")) self.btn_program_upload.setText(_translate("diag_program", "Upload")) self.btn_program_download.setText(_translate("diag_program", "Download")) - self.lbl_format.setText(_translate("diag_program", "Transfair format:")) - self.cbx_pictory.setText(_translate("diag_program", "Including piCtory configuration")) - self.cbx_clear.setText(_translate("diag_program", "Remove all files on Revolution Pi before upload")) - self.gb_control.setTitle(_translate("diag_program", "Control files")) + self.lbl_format.setText(_translate("diag_program", "Transfer format:")) + self.cbx_pictory.setText(_translate("diag_program", "Include PiCtory configuration")) + self.cbx_clear.setText(_translate("diag_program", "Remove all files on RevPi before upload")) + self.gb_control.setTitle(_translate("diag_program", "Control Files")) self.btn_procimg_download.setText(_translate("diag_program", "Download")) self.btn_pictory_download.setText(_translate("diag_program", "Download")) self.btn_pictory_upload.setText(_translate("diag_program", "Upload")) - self.lbl_pictory.setText(_translate("diag_program", "piCtory configuraiton")) - self.lbl_procimg.setText(_translate("diag_program", "Process image from piControl0")) + self.lbl_pictory.setText(_translate("diag_program", "PiCtory configuration")) + self.lbl_procimg.setText(_translate("diag_program", "Process image from piControl")) if __name__ == "__main__": diff --git a/src/revpicommander/ui/simulator_ui.py b/src/revpicommander/ui/simulator_ui.py index 591331d..37fb002 100644 --- a/src/revpicommander/ui/simulator_ui.py +++ b/src/revpicommander/ui/simulator_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'simulator.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -108,22 +108,22 @@ class Ui_diag_simulator(object): def retranslateUi(self, diag_simulator): _translate = QtCore.QCoreApplication.translate - diag_simulator.setWindowTitle(_translate("diag_simulator", "piControl simulator")) - self.gb_settings.setTitle(_translate("diag_simulator", "Simulator settings")) + diag_simulator.setWindowTitle(_translate("diag_simulator", "piControl Simulator")) + self.gb_settings.setTitle(_translate("diag_simulator", "Simulator Settings")) self.lbl_history.setText(_translate("diag_simulator", "Last used:")) - self.lbl_configrsc.setText(_translate("diag_simulator", "piCtory file:")) - self.btn_configrsc.setText(_translate("diag_simulator", "select...")) + self.lbl_configrsc.setText(_translate("diag_simulator", "PiCtory file:")) + self.btn_configrsc.setText(_translate("diag_simulator", "Select")) self.lbl_procimg.setText(_translate("diag_simulator", "Process image:")) self.lbl_stop.setText(_translate("diag_simulator", "Stop action:")) self.lbl_restart.setText(_translate("diag_simulator", "Restart action:")) self.cbx_stop_remove.setText(_translate("diag_simulator", "Remove process image file")) - self.rb_restart_pictory.setText(_translate("diag_simulator", "Restore piCtory default values")) - self.rb_restart_zero.setText(_translate("diag_simulator", "Reset everything to ZERO")) - self.gb_info.setTitle(_translate("diag_simulator", "RevPiModIO integration")) - self.lbl_info.setText(_translate("diag_simulator", "You can work with this simulator if you call RevPiModIO with this additional parameters:")) - self.btn_start_pictory.setText(_translate("diag_simulator", "Start with piCtory default values")) + self.rb_restart_pictory.setText(_translate("diag_simulator", "Restore PiCtory default values")) + self.rb_restart_zero.setText(_translate("diag_simulator", "Reset all values to NULL")) + self.gb_info.setTitle(_translate("diag_simulator", "RevPiModIO Integration")) + self.lbl_info.setText(_translate("diag_simulator", "To use this simulator, call RevPiModIO with the following additional parameters:")) + self.btn_start_pictory.setText(_translate("diag_simulator", "Start with PiCtory default values")) self.btn_start_empty.setText(_translate("diag_simulator", "Start with empty process image")) - self.btn_start_nochange.setText(_translate("diag_simulator", "Start without changing actual process image")) + self.btn_start_nochange.setText(_translate("diag_simulator", "Start without changing the current process image")) if __name__ == "__main__": diff --git a/src/revpicommander/ui/sshauth_ui.py b/src/revpicommander/ui/sshauth_ui.py index 63c6e8b..dda43fa 100644 --- a/src/revpicommander/ui/sshauth_ui.py +++ b/src/revpicommander/ui/sshauth_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'sshauth.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.11 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -64,11 +64,11 @@ class Ui_diag_sshauth(object): def retranslateUi(self, diag_sshauth): _translate = QtCore.QCoreApplication.translate diag_sshauth.setWindowTitle(_translate("diag_sshauth", "SSH authentication")) - self.lbl_username.setText(_translate("diag_sshauth", "SSH username:")) + self.lbl_username.setText(_translate("diag_sshauth", "SSH user name:")) self.lbl_password.setText(_translate("diag_sshauth", "SSH password:")) - self.cbx_save_password.setToolTip(_translate("diag_sshauth", "Username and password will be saved in secured operating systems\'s password storage.")) - self.cbx_save_password.setText(_translate("diag_sshauth", "Save username and password")) - self.lbl_info.setText(_translate("diag_sshauth", "Note: The default user for SSH is \"pi\" which differs from the web configuration. You can find the password on the sticker on the device.")) + self.cbx_save_password.setToolTip(_translate("diag_sshauth", "Save user name and password in secure password storage.")) + self.cbx_save_password.setText(_translate("diag_sshauth", "Save user name and password")) + self.lbl_info.setText(_translate("diag_sshauth", "Default SSH user is \"pi\". The device password is on the RevPi housing sticker.")) if __name__ == "__main__": diff --git a/ui_dev/aclmanager.ui b/ui_dev/aclmanager.ui index d3a91c3..5a54ac9 100644 --- a/ui_dev/aclmanager.ui +++ b/ui_dev/aclmanager.ui @@ -11,7 +11,7 @@ - IP access control list + IP Access Control List @@ -51,7 +51,7 @@ - IP Address + IP address @@ -97,7 +97,7 @@ - Add / Edit access entry + Add/Edit Access Entry diff --git a/ui_dev/avahisearch.ui b/ui_dev/avahisearch.ui index 35ce2e9..cb91156 100644 --- a/ui_dev/avahisearch.ui +++ b/ui_dev/avahisearch.ui @@ -11,7 +11,7 @@ - Search Revolution Pi devices + RevPi device search @@ -19,7 +19,7 @@ - Searching for Revolution Pi devices in your network... + Searching for RevPi devices on the network @@ -85,7 +85,7 @@ - Zero-conf name + ZeroConf name @@ -98,7 +98,7 @@ - &Connect to Revolution Pi + &Connect to RevPi @@ -132,7 +132,7 @@ - Open piCtory + Open PiCtory @@ -140,7 +140,7 @@ Connect via SSH (recommended) - Establish a connection via encrypted SSH tunnel + Connect via an encrypted SSH tunnel. @@ -148,7 +148,7 @@ Connect via XML-RPC - You have to configure your Revolution Pi to accept this connections + Enable these connections on RevPi. @@ -156,7 +156,7 @@ Connect - Connect to Revoluton Pi + Connect to RevPi diff --git a/ui_dev/backgroundworker.ui b/ui_dev/backgroundworker.ui index 67b7841..ac16b08 100644 --- a/ui_dev/backgroundworker.ui +++ b/ui_dev/backgroundworker.ui @@ -17,7 +17,7 @@ - Status message... + Status message diff --git a/ui_dev/debugcontrol.ui b/ui_dev/debugcontrol.ui index f7a429a..3933649 100644 --- a/ui_dev/debugcontrol.ui +++ b/ui_dev/debugcontrol.ui @@ -32,7 +32,7 @@ - Revolution Pi devices + RevPi Devices @@ -40,25 +40,25 @@ - Open to stay on top + Keep window on top - IO Control + I/O Control - Read all IO values and discard local changes (F4) + Read all I/O values and discard local changes (F4). -Hold this button pressed and it will refresh the IOs every 200 ms. +Hold this button to refresh the I/Os every 200 ms. - Read &all IO values + Read &all I/O values true @@ -74,12 +74,12 @@ Hold this button pressed and it will refresh the IOs every 200 ms. - Refresh all IO values which are locally not changed (F5) + Refresh all I/O values that have not been modified locally (F5). -Hold this button pressed and it will refresh the IOs every 200 ms. +Hold this button to refresh the I/Os every 200 ms. - &Refresh unchanged IOs + &Refresh unchanged I/Os true @@ -95,7 +95,7 @@ Hold this button pressed and it will refresh the IOs every 200 ms. - Write locally changed output values to process image (F6) + Write locally modified output values to process image (F6). &Write changed outputs @@ -112,7 +112,7 @@ Hold this button pressed and it will refresh the IOs every 200 ms. - and write outputs + Write outputs diff --git a/ui_dev/files.ui b/ui_dev/files.ui index 03297e5..c5936c2 100644 --- a/ui_dev/files.ui +++ b/ui_dev/files.ui @@ -11,7 +11,7 @@ - File manager + File Manager @@ -28,7 +28,7 @@ - Local computer + Local Computer @@ -168,7 +168,7 @@ - Revolution Pi + RevPi @@ -328,7 +328,7 @@ - Stop - Upload - Start + Stop, upload, start diff --git a/ui_dev/mqttmanager.ui b/ui_dev/mqttmanager.ui index ff683a5..1015364 100644 --- a/ui_dev/mqttmanager.ui +++ b/ui_dev/mqttmanager.ui @@ -29,7 +29,7 @@ - The base topic is the first part of any mqtt topic, the Revolution Pi will publish. You can use any character includig '/' to structure the messages on your broker. + Base topic is the prefix for MQTT topics published by RevPi. Use "/" to structure broker topics. For example: revpi0000/data @@ -113,7 +113,7 @@ For example: revpi0000/data - The Revolution Pi will subscribe a topic on which your mqtt client can publish messages with the new io value as payload. + RevPi subscribes to an MQTT topic for setting output values. Publish the new I/O value as payload. Publish values with topic: [basetopic]/set/[outputname] @@ -125,7 +125,7 @@ Publish values with topic: [basetopic]/set/[outputname] - Allow MQTT to to set outputs on Revolution Pi + Allow MQTT to set outputs on RevPi diff --git a/ui_dev/oss_licenses.ui b/ui_dev/oss_licenses.ui index e3f2fdd..e0af404 100644 --- a/ui_dev/oss_licenses.ui +++ b/ui_dev/oss_licenses.ui @@ -11,7 +11,7 @@ - Open-Source licenses + Open source licenses @@ -87,10 +87,10 @@ - More licenses... + More licenses - Show more open-source software licenses + Show more open source software licenses diff --git a/ui_dev/revpicommander.ui b/ui_dev/revpicommander.ui index d41eff6..c5a964b 100644 --- a/ui_dev/revpicommander.ui +++ b/ui_dev/revpicommander.ui @@ -210,7 +210,7 @@ - &Search Revolution Pi... + &Search RevPi... Ctrl+F @@ -223,12 +223,12 @@ - Visit &webpage... + Visit &website - &Info... + &Info @@ -265,7 +265,7 @@ - piCtory configuraiton... + PiCtory configuration @@ -278,7 +278,7 @@ - Reset driver... + Reset driver diff --git a/ui_dev/revpiinfo.ui b/ui_dev/revpiinfo.ui index 93ba128..0d2a7d9 100644 --- a/ui_dev/revpiinfo.ui +++ b/ui_dev/revpiinfo.ui @@ -11,7 +11,7 @@ - Program information + Program Information @@ -24,7 +24,7 @@ - RevPi Python PLC - Commander + RevPi Commander (Python/PLC) Qt::AlignCenter @@ -114,8 +114,7 @@ RevPiModIO, RevPiPyLoad and RevPiPyControl are community driven projects. They are all free and open source software. -All of them comes with ABSOLUTELY NO WARRANTY, to the extent permitted by -applicable law. +All of them come with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. (c) Sven Sager, License: GPLv2 @@ -140,7 +139,7 @@ applicable law. - <html><head/><body><p><a href="https://revpimodio.org/"><span style=" text-decoration: underline; color:#0000ff;">https://revpimodio.org/</span></a></p></body></html> + <html><head/><body><p><a href="https://revpimodio2.readthedocs.io/en/latest/"><span style=" text-decoration: underline; color:#0000ff;">https://revpimodio2.readthedocs.io/en/latest/</span></a></p></body></html> true diff --git a/ui_dev/revpilogfile.ui b/ui_dev/revpilogfile.ui index 6d837f0..ef8814f 100644 --- a/ui_dev/revpilogfile.ui +++ b/ui_dev/revpilogfile.ui @@ -11,7 +11,7 @@ - RevPi Python PLC Logfiles + RevPi Python PLC Log Files @@ -25,7 +25,7 @@ - Linewrap + Line wrap @@ -42,7 +42,7 @@ - RevPiPyLoad - Logfile + RevPiPyLoad Log File @@ -89,7 +89,7 @@ - Python PLC program - Logfile + Python PLC Program Log File diff --git a/ui_dev/revpioption.ui b/ui_dev/revpioption.ui index 4b4946f..76662d1 100644 --- a/ui_dev/revpioption.ui +++ b/ui_dev/revpioption.ui @@ -17,20 +17,20 @@ - Start / Stop behavior of PLC program + PLC Start/Stop Behavior - Replace IO file: + Replace I/O file: - ... after exception and errors + after exceptions or errors @@ -50,7 +50,7 @@ - Restart after piCtory changed + Restart after PiCtory changes @@ -70,7 +70,7 @@ - Set process image to NULL if program terminates... + Set process image to NULL if the program terminates @@ -78,7 +78,7 @@ - Do not use replace io file + Do not replace I/O file @@ -93,7 +93,7 @@ - Give own path and filename + Use custom path and file name @@ -101,7 +101,7 @@ - ... sucessfully without error + without errors @@ -135,7 +135,7 @@ - PLC program behavior after piCtory driver reset clicked + PLC program behavior after PiCtory driver reset @@ -145,7 +145,7 @@ - RevPiPyLoad server services + RevPiPyLoad Server Services @@ -165,7 +165,7 @@ - Start RevPi piControl server + Start piControl server @@ -182,7 +182,7 @@ - piControl server is: + piControl server: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -202,7 +202,7 @@ - MQTT publish service is: + MQTT publish service: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -226,7 +226,7 @@ - Activate XML-RPC for RevPiCommander + Activate XML-RPC for RevPi Commander diff --git a/ui_dev/revpiplclist.ui b/ui_dev/revpiplclist.ui index bdb0901..9dfd1fa 100644 --- a/ui_dev/revpiplclist.ui +++ b/ui_dev/revpiplclist.ui @@ -11,7 +11,7 @@ - Revolution Pi connections + RevPi Connections @@ -47,7 +47,7 @@ - Port (Default {0}): + Port (default {0}): @@ -86,7 +86,7 @@ - sec. + s 5 @@ -99,7 +99,7 @@ - Sub folder: + Subfolder: @@ -194,7 +194,7 @@ - Connection name + Connection Name diff --git a/ui_dev/revpiprogram.ui b/ui_dev/revpiprogram.ui index 1d67d19..a22640f 100644 --- a/ui_dev/revpiprogram.ui +++ b/ui_dev/revpiprogram.ui @@ -11,33 +11,33 @@ - PLC program + PLC Program - PLC program + PLC Program - Program arguments: + Arguments: - Software watchdog (0=disabled): + Software watchdog (0 = disabled): - Set write permissions for plc program to workdirectory + Set write permissions for PLC program working directory @@ -57,7 +57,7 @@ - sec. + s 600 @@ -70,7 +70,7 @@ - Transfair PLC program + Transfer PLC program @@ -104,21 +104,21 @@ - Transfair format: + Transfer format: - Including piCtory configuration + Include PiCtory configuration - Remove all files on Revolution Pi before upload + Remove all files on RevPi before upload @@ -128,7 +128,7 @@ - Control files + Control Files @@ -155,14 +155,14 @@ - piCtory configuraiton + PiCtory configuration - Process image from piControl0 + Process image from piControl diff --git a/ui_dev/simulator.ui b/ui_dev/simulator.ui index f7a549c..21dcb9a 100644 --- a/ui_dev/simulator.ui +++ b/ui_dev/simulator.ui @@ -11,13 +11,13 @@ - piControl simulator + piControl Simulator - Simulator settings + Simulator Settings @@ -30,7 +30,7 @@ - piCtory file: + PiCtory file: @@ -47,7 +47,7 @@ - select... + Select @@ -99,7 +99,7 @@ - Restore piCtory default values + Restore PiCtory default values true @@ -109,7 +109,7 @@ - Reset everything to ZERO + Reset all values to NULL @@ -119,13 +119,13 @@ - RevPiModIO integration + RevPiModIO Integration - You can work with this simulator if you call RevPiModIO with this additional parameters: + To use this simulator, call RevPiModIO with the following additional parameters: true @@ -151,7 +151,7 @@ - Start with piCtory default values + Start with PiCtory default values Ctrl+1 @@ -171,7 +171,7 @@ - Start without changing actual process image + Start without changing the current process image Ctrl+3 diff --git a/ui_dev/sshauth.ui b/ui_dev/sshauth.ui index 2c061b1..965f903 100644 --- a/ui_dev/sshauth.ui +++ b/ui_dev/sshauth.ui @@ -26,7 +26,7 @@ - SSH username: + SSH user name: @@ -53,10 +53,10 @@ - Username and password will be saved in secured operating systems's password storage. + Save user name and password in secure password storage. - Save username and password + Save user name and password @@ -79,7 +79,7 @@ - Note: The default user for SSH is "pi" which differs from the web configuration. You can find the password on the sticker on the device. + Default SSH user is "pi". The device password is on the RevPi housing sticker. true From 0f04b210d0ed701574a27495d2134e9a7804f6d1 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Tue, 28 Jul 2026 07:51:09 +0200 Subject: [PATCH 05/17] chore: Update Jetbrains IDE settings --- .idea/misc.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.idea/misc.xml b/.idea/misc.xml index f07de1c..841cb98 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,8 @@ + + From c4531a33bf5bd4cb34178785e5683b5c84da951b Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Fri, 24 Jul 2026 10:46:35 +0200 Subject: [PATCH 06/17] feat: Option to use unix sockets for xml-rpc --- src/revpicommander/helper.py | 73 ++++++++++++++++++++++-------- src/revpicommander/revpiplclist.py | 11 +++++ 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/src/revpicommander/helper.py b/src/revpicommander/helper.py index cf01c23..d8d30a0 100644 --- a/src/revpicommander/helper.py +++ b/src/revpicommander/helper.py @@ -15,7 +15,7 @@ from queue import Queue from re import search from threading import Lock from uuid import uuid4 -from xmlrpc.client import Binary, ServerProxy +from xmlrpc.client import Binary, ServerProxy, Transport from PyQt5 import QtCore from paramiko.ssh_exception import AuthenticationException @@ -32,6 +32,21 @@ homedir = environ.get("HOME", "") or environ.get("APPDATA", "") """Home dir of user.""" +class UnixStreamTransport(Transport): + """Transport for xmlrpc to use unix domain sockets.""" + + def __init__(self, socket_path): + super().__init__() + self._socket_path = socket_path + + def make_connection(self, host): + import http.client + conn = http.client.HTTPConnection("localhost") + conn.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + conn.sock.connect(self._socket_path) + return conn + + class ConnectionFail(IntEnum): NO_XML_RPC = 1 SSH_CONNECT = 2 @@ -91,6 +106,11 @@ class RevPiSettings: if load_index is not None: self.load_from_index(load_index) + @property + def is_unix_socket(self) -> bool: + """Check if connection is a unix domain socket.""" + return self.address.startswith("/") or self.address.startswith("./") + def load_from_index(self, settings_index: int) -> None: """Load settings from 'connections' index.""" self._settings.beginReadArray("connections") @@ -129,8 +149,11 @@ class RevPiSettings: pass # These values must exists - if not (self.name and self.address and self.port): - raise ValueError("Could not geht all required values from saved settings") + if not (self.name and self.address): + raise ValueError("Could not get all required values from saved settings") + + if not self.is_unix_socket and not self.port: + raise ValueError("Port is required for IP connections") self._settings.endArray() @@ -379,10 +402,10 @@ class ConnectionManager(QtCore.QThread): ) return False - sp = ServerProxy("http://127.0.0.1:{0}".format(ssh_tunnel_port)) + sp = create_server_proxy(revpi_settings, ssh_tunnel_port) else: - sp = ServerProxy("http://{0}:{1}".format(revpi_settings.address, revpi_settings.port)) + sp = create_server_proxy(revpi_settings) # Load values and test connection to Revolution Pi try: @@ -435,10 +458,7 @@ class ConnectionManager(QtCore.QThread): with self._lck_cli: self.ssh_tunnel_server = ssh_tunnel_server self._cli = sp - self._cli_connect.put_nowait(( - "127.0.0.1" if revpi_settings.ssh_use_tunnel else revpi_settings.address, - ssh_tunnel_port if revpi_settings.ssh_use_tunnel else revpi_settings.port - )) + self._cli_connect.put_nowait((revpi_settings, ssh_tunnel_port)) self.connection_established.emit() @@ -552,8 +572,8 @@ class ConnectionManager(QtCore.QThread): self.status_changed.emit(self.tr("Not connected"), "lightblue") elif not self._cli_connect.empty(): # Get new connection information to create object in this thread - item = self._cli_connect.get() - sp = ServerProxy("http://{0}:{1}".format(*item)) + revpi_settings, ssh_tunnel_port = self._cli_connect.get() + sp = create_server_proxy(revpi_settings, ssh_tunnel_port) self._cli_connect.task_done() if sp: @@ -582,7 +602,7 @@ class ConnectionManager(QtCore.QThread): self.settings.ssh_user, self.ssh_pass ) - sp = ServerProxy("http://127.0.0.1:{0}".format(ssh_tunnel_port)) + sp = create_server_proxy(self.settings, ssh_tunnel_port) with self._lck_cli: self.ssh_tunnel_server = ssh_tunnel_server self._cli = sp @@ -669,12 +689,8 @@ class ConnectionManager(QtCore.QThread): Use connection_recovered signal to figure out new parameters. """ - if not self.settings.ssh_use_tunnel and self.settings.address and self.settings.port: - return ServerProxy("http://{0}:{1}".format(self.settings.address, self.settings.port)) - if self.settings.ssh_use_tunnel and self.ssh_tunnel_server and self.ssh_tunnel_server.connected: - return ServerProxy("http://127.0.0.1:{0}".format(self.ssh_tunnel_server.local_tunnel_port)) - - return None + ssh_tunnel_port = self.ssh_tunnel_server.local_tunnel_port if self.ssh_tunnel_server else None + return create_server_proxy(self.settings, ssh_tunnel_port) @property def connected(self) -> bool: @@ -699,6 +715,27 @@ cm = ConnectionManager() """Clobal connection manager instance.""" +def create_server_proxy(revpi_settings: RevPiSettings, ssh_tunnel_port: int = None) -> ServerProxy: + """ + Create a ServerProxy instance based on the given settings. + + :param revpi_settings: Revolution Pi saved connection settings + :param ssh_tunnel_port: Use this port if an SSH tunnel is already established + :return: ServerProxy instance + """ + if revpi_settings.is_unix_socket: + return ServerProxy("http://localhost", transport=UnixStreamTransport(revpi_settings.address)) + + if ssh_tunnel_port: + return ServerProxy("http://127.0.0.1:{0}".format(ssh_tunnel_port)) + + if revpi_settings.ssh_use_tunnel: + # This case is usually handled by passing ssh_tunnel_port after connecting the tunnel + return ServerProxy("http://127.0.0.1:{0}".format(revpi_settings.port)) + + return ServerProxy("http://{0}:{1}".format(revpi_settings.address, revpi_settings.port)) + + def all_revpi_settings() -> [RevPiSettings]: """Get all revpi settings objects.""" # Get length of array and close it, the RevPiSettings-class need it diff --git a/src/revpicommander/revpiplclist.py b/src/revpicommander/revpiplclist.py index 6c3a81c..572b4b6 100644 --- a/src/revpicommander/revpiplclist.py +++ b/src/revpicommander/revpiplclist.py @@ -206,6 +206,16 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): self.sbx_ssh_port.setEnabled(con_item) self.txt_ssh_user.setEnabled(con_item) + if con_item: + address = self.txt_address.text() + is_unix = address.startswith("/") or address.startswith("./") + if is_unix: + self.sbx_port.setEnabled(False) + self.cbx_ssh_use_tunnel.setChecked(False) + self.cbx_ssh_use_tunnel.setEnabled(False) + self.sbx_ssh_port.setEnabled(False) + self.txt_ssh_user.setEnabled(False) + def _get_folder_item(self, name: str): """Find the folder entry by name.""" for i in range(self.tre_connections.topLevelItemCount()): @@ -369,6 +379,7 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): settings = self.__current_item.data(0, WidgetData.revpi_settings) # type: RevPiSettings settings.address = text self.changes = True + self._edit_state() @QtCore.pyqtSlot(int) def on_sbx_port_valueChanged(self, value: int): From 50915fd07cf0075cac5644877d51cdb050657c7a Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Wed, 29 Jul 2026 08:11:54 +0200 Subject: [PATCH 07/17] fix: State management in connection manager --- src/revpicommander/revpiplclist.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/revpicommander/revpiplclist.py b/src/revpicommander/revpiplclist.py index 572b4b6..091ada2 100644 --- a/src/revpicommander/revpiplclist.py +++ b/src/revpicommander/revpiplclist.py @@ -253,7 +253,6 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): def on_tre_connections_currentItemChanged( self, current: QtWidgets.QTreeWidgetItem, previous: QtWidgets.QTreeWidgetItem): - self._edit_state() self._load_cbb_folder() if current and current.type() == NodeType.CON: @@ -281,6 +280,8 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): self.__current_item = QtWidgets.QTreeWidgetItem() self.cbb_folder.setCurrentText(current.text(0) if current else "") + self._edit_state() + @QtCore.pyqtSlot() def on_btn_up_clicked(self): self._move_item(-1) @@ -329,6 +330,8 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): elif item_to_remove and item_to_remove.type() == NodeType.CON: remove_item(item_to_remove) + self._edit_state() + @QtCore.pyqtSlot() def on_btn_add_clicked(self, settings_preset: RevPiSettings = None): """Create new element.""" From 23c09949bb609e50e2691636908f6743c6d348fa Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Tue, 4 Aug 2026 11:27:07 +0200 Subject: [PATCH 08/17] refactor: Replace `paramiko` with `asyncssh` for SSH connections Switches the SSH implementation from `paramiko` to `asyncssh` for improved capabilities and performance. Updates logic in SSH tunnel handling, authentication, and dependencies. Signed-off-by: Sven Sager --- requirements.txt | 2 +- setup.py | 2 +- src/revpicommander/helper.py | 4 +- src/revpicommander/proginit.py | 4 +- src/revpicommander/ssh_tunneling/server.py | 236 +++++++++++---------- 5 files changed, 131 insertions(+), 117 deletions(-) diff --git a/requirements.txt b/requirements.txt index 383199c..6d0be75 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,6 @@ wheel # Runtime dependencies, must match install_requires in setup.py keyring>=23.13.1 PyQt5>=5.14.1 -paramiko>=2.12.0 +asyncssh>=2.14.0 revpimodio2>=2.5.6 zeroconf>=0.24.4 diff --git a/setup.py b/setup.py index 227874b..3b29520 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setup( install_requires=[ "keyring", "PyQt5", - "paramiko", + "asyncssh", "revpimodio2", "zeroconf" ], diff --git a/src/revpicommander/helper.py b/src/revpicommander/helper.py index d8d30a0..a2e674c 100644 --- a/src/revpicommander/helper.py +++ b/src/revpicommander/helper.py @@ -18,7 +18,7 @@ from uuid import uuid4 from xmlrpc.client import Binary, ServerProxy, Transport from PyQt5 import QtCore -from paramiko.ssh_exception import AuthenticationException +import asyncssh from . import proginit as pi from .ssh_tunneling.server import SSHLocalTunnel @@ -380,7 +380,7 @@ class ConnectionManager(QtCore.QThread): if getattr(revpi_settings, "ssh_enable_revpipyload", False): ssh_tunnel_server.send_cmd("sudo systemctl enable --now revpipyload") - except AuthenticationException: + except asyncssh.PermissionDenied: self.connect_error.emit( self.tr("Error"), self.tr( "The combination of username and password was rejected from the SSH server.\n\n" diff --git a/src/revpicommander/proginit.py b/src/revpicommander/proginit.py index e45f8da..4101308 100644 --- a/src/revpicommander/proginit.py +++ b/src/revpicommander/proginit.py @@ -100,8 +100,8 @@ def reconfigure_logger(): def filter(self, record: logging.LogRecord) -> bool: remove_record = False - # Remove paramiko ssh module - remove_record = remove_record or record.name.startswith("paramiko") + # Remove asyncssh module + remove_record = remove_record or record.name.startswith("asyncssh") return not remove_record diff --git a/src/revpicommander/ssh_tunneling/server.py b/src/revpicommander/ssh_tunneling/server.py index a2b6542..691a46d 100644 --- a/src/revpicommander/ssh_tunneling/server.py +++ b/src/revpicommander/ssh_tunneling/server.py @@ -1,65 +1,21 @@ # -*- coding: utf-8 -*- """ Connect to a remote host and tunnel a port. - -This was crated on base of the paramiko library demo file forward.py, see on -GitHub https://github.com/paramiko/paramiko/blob/main/demos/forward.py """ __author__ = "Sven Sager" -__copyright__ = "Copyright (C) 2023 Sven Sager" +__copyright__ = "Copyright (C) 2023-2026 Sven Sager" __license__ = "GPLv2" -import select +import asyncio +import threading from logging import getLogger -from socketserver import BaseRequestHandler, ThreadingTCPServer -from threading import Thread from typing import Tuple, Union -from paramiko.client import MissingHostKeyPolicy, SSHClient -from paramiko.rsakey import RSAKey -from paramiko.ssh_exception import PasswordRequiredException -from paramiko.transport import Transport +import asyncssh log = getLogger("ssh_tunneling") -class ForwardServer(ThreadingTCPServer): - daemon_threads = True - allow_reuse_address = True - - -class Handler(BaseRequestHandler): - def handle(self): - try: - chan = self.ssh_transport.open_channel( - "direct-tcpip", - (self.chain_host, self.chain_port), - self.request.getpeername(), - ) - except Exception as e: - log.error(e) - return - if chan is None: - log.error("Could not create a ssh channel") - return - - while True: - r, w, x = select.select([self.request, chan], [], [], 5.0) - if self.request in r: - data = self.request.recv(1024) - if len(data) == 0: - break - chan.send(data) - if chan in r: - data = chan.recv(1024) - if len(data) == 0: - break - self.request.send(data) - - chan.close() - self.request.close() - - class SSHLocalTunnel: def __init__(self, remote_tunnel_port: int, ssh_host: str, ssh_port: int = 22): @@ -74,39 +30,66 @@ class SSHLocalTunnel: self._ssh_host = ssh_host self._ssh_port = ssh_port - self._th_server = Thread() + self._loop: asyncio.AbstractEventLoop | None = None + self._stop_event: asyncio.Event | None = None + self._thread: threading.Thread | None = None - self._ssh_client = SSHClient() - self._ssh_client.set_missing_host_key_policy(MissingHostKeyPolicy()) + self._started = threading.Event() + self._stopped = threading.Event() - self._ssh_transport = None # type: Transport - self._forward_server = None # type: ThreadingTCPServer - self._local_tunnel_port = None # type: int + self._startup_error: Exception | None = None + self._runtime_error: Exception | None = None - def __th_target(self): - """Server thread for socket mirror.""" - self._forward_server.serve_forever() + self._conn: asyncssh.SSHClientConnection | None = None + self._server: asyncssh.SSHForwarder | None = None + self._local_tunnel_port: int | None = None - def _configure_forward_server(self) -> int: - """ - Configure forward server for port mirror. + def _thread_main(self, username, password=None, client_keys=None, passphrase=None): + try: + asyncio.run(self._run(username, password, client_keys, passphrase)) + except Exception as exc: + if not self._started.is_set(): + self._startup_error = exc + self._started.set() + else: + self._runtime_error = exc + finally: + self._stopped.set() - :return: Local port on wich the remote port is connected - """ - self._ssh_transport = self._ssh_client.get_transport() + async def _run(self, username, password=None, client_keys=None, passphrase=None): + self._loop = asyncio.get_running_loop() + self._stop_event = asyncio.Event() - class SubHandler(Handler): - chain_host = "127.0.0.1" - chain_port = self._remote_tunnel_port - ssh_transport = self._ssh_transport + try: + async with asyncssh.connect( + host=self._ssh_host, + port=self._ssh_port, + username=username, + password=password, + client_keys=client_keys, + passphrase=passphrase, + known_hosts=None, # Analog zu MissingHostKeyPolicy() + config=None, # Do not parse local config + ) as conn: + self._conn = conn + # Forward local port 0 (dynamic) to remote 127.0.0.1:remote_tunnel_port + self._server = await conn.forward_local_port( + '127.0.0.1', 0, '127.0.0.1', self._remote_tunnel_port + ) + self._local_tunnel_port = self._server.get_port() - self._forward_server = ForwardServer(("127.0.0.1", 0), SubHandler) - self._local_tunnel_port = self._forward_server.socket.getsockname()[1] + self._started.set() + await self._stop_event.wait() - self._th_server = Thread(target=self.__th_target) - self._th_server.start() + self._server.close() + await self._server.wait_closed() - return self._local_tunnel_port + except Exception as exc: + if not self._started.is_set(): + self._startup_error = exc + self._started.set() + return + raise def connect_by_credentials(self, username: str, password: str) -> int: """ @@ -114,17 +97,31 @@ class SSHLocalTunnel: :return: Local port on wich the remote port is connected """ - if self._th_server.is_alive(): + if self._thread and self._thread.is_alive(): raise RuntimeError("Already connected") - self._ssh_client.connect( - hostname=self._ssh_host, - port=self._ssh_port, - username=username, - password=password, - ) + self._started.clear() + self._stopped.clear() + self._startup_error = None + self._runtime_error = None - return self._configure_forward_server() + self._thread = threading.Thread( + target=self._thread_main, + args=(username, password), + daemon=True + ) + self._thread.start() + + if not self._started.wait(20.0): + self.disconnect() + raise TimeoutError("SSH connection timed out") + + if self._startup_error: + error = self._startup_error + self.disconnect() + raise error + + return self._local_tunnel_port def connect_by_keyfile(self, username: str, key_file: str, key_password: str = None) -> int: """ @@ -132,62 +129,79 @@ class SSHLocalTunnel: :return: Local port on wich the remote port is connected """ - if self._th_server.is_alive(): + if self._thread and self._thread.is_alive(): raise RuntimeError("Already connected") - if self.key_file_password_protected(key_file): - private_key = RSAKey.from_private_key_file(key_file, key_password) - else: - private_key = RSAKey.from_private_key_file(key_file) + self._started.clear() + self._stopped.clear() + self._startup_error = None + self._runtime_error = None - self._ssh_client.connect( - hostname=self._ssh_host, - port=self._ssh_port, - username=username, - pkey=private_key, - look_for_keys=True, + self._thread = threading.Thread( + target=self._thread_main, + args=(username, None, [key_file], key_password), + daemon=True ) + self._thread.start() - return self._configure_forward_server() + if not self._started.wait(20.0): + self.disconnect() + raise TimeoutError("SSH connection timed out") + + if self._startup_error: + error = self._startup_error + self.disconnect() + raise error + + return self._local_tunnel_port def disconnect(self): """Close SSH tunnel connection.""" + if self._loop and self._stop_event: + self._loop.call_soon_threadsafe(self._stop_event.set) + + if self._thread and self._thread.is_alive(): + self._thread.join(timeout=5.0) + + self._conn = None + self._server = None self._local_tunnel_port = None - if self._forward_server: - self._forward_server.shutdown() - self._forward_server.server_close() - if self._ssh_transport: - self._ssh_transport.close() - self._ssh_client.close() + self._thread = None + self._loop = None + self._stop_event = None @staticmethod def key_file_password_protected(key_file: str) -> bool: + # asyncssh doesn't have a direct equivalent without trying to load it. + # But we can try to load it with an empty passphrase. try: - RSAKey.from_private_key_file(key_file) - except PasswordRequiredException: + asyncssh.read_private_key(key_file, passphrase=None) + return False + except asyncssh.KeyImportError: + return True + except Exception: return True - return False def send_cmd(self, cmd: str, timeout: float = None) -> Union[Tuple[str, str], Tuple[None, None]]: """ Send simple command to ssh host. - The output of stdout and stderr is returned as a tuple of two elements. - This elements could be None, in case of an internal error. - :param cmd: Shell command to execute on remote host :param timeout: Timeout for execution :return: Tuple with stdout and stderr """ - if not self._th_server.is_alive(): + if not self.connected: raise RuntimeError("Not connected") - try: - _, stdout, stderr = self._ssh_client.exec_command(cmd, 1024, timeout) - buffer_out = stdout.read() - buffer_err = stderr.read() + # Running async command from sync context + async def _exec(): + result = await self._conn.run(cmd, timeout=timeout) + return result.stdout, result.stderr - return buffer_out.decode(), buffer_err.decode() + try: + future = asyncio.run_coroutine_threadsafe(_exec(), self._loop) + stdout, stderr = future.result(timeout=timeout) + return stdout, stderr except Exception as e: log.error(e) return None, None @@ -195,7 +209,7 @@ class SSHLocalTunnel: @property def connected(self): """Check connection state of ssh tunnel.""" - return self._ssh_transport and self._ssh_transport.is_active() + return self._conn is not None and self._started.is_set() and not self._stopped.is_set() @property def local_tunnel_port(self) -> int: From 0582925de6aeb943ebbda3ad42d7f0aa45b6e175 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Tue, 4 Aug 2026 12:16:44 +0200 Subject: [PATCH 09/17] feat: Enhance SSH tunnel handling with Unix socket support Support forwarding Unix sockets in SSH tunnels by extending `SSHLocalTunnel`. Update logic to detect and handle remote Unix socket configurations. Signed-off-by: Sven Sager --- src/revpicommander/helper.py | 36 +++++++++++++++++++++- src/revpicommander/ssh_tunneling/server.py | 21 ++++++++----- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/revpicommander/helper.py b/src/revpicommander/helper.py index a2e674c..900554e 100644 --- a/src/revpicommander/helper.py +++ b/src/revpicommander/helper.py @@ -16,6 +16,7 @@ from re import search from threading import Lock from uuid import uuid4 from xmlrpc.client import Binary, ServerProxy, Transport +from configparser import ConfigParser from PyQt5 import QtCore import asyncssh @@ -365,10 +366,12 @@ class ConnectionManager(QtCore.QThread): ssh_tunnel_server = None ssh_tunnel_port = 0 + ssh_tunnel_socket = None socket.setdefaulttimeout(revpi_settings.timeout) if revpi_settings.ssh_use_tunnel: + # We first connect to find out which target to tunnel ssh_tunnel_server = SSHLocalTunnel( revpi_settings.port, revpi_settings.address, @@ -377,6 +380,37 @@ class ConnectionManager(QtCore.QThread): try: ssh_tunnel_port = ssh_tunnel_server.connect_by_credentials(revpi_settings.ssh_user, ssh_pass) + # Check for Unix socket on remote system + try: + stdout, stderr = ssh_tunnel_server.send_cmd("cat /etc/revpipyload/revpipyload.conf") + if stdout: + config = ConfigParser() + config.read_string(stdout) + if config.has_section("XMLRPC"): + bindip = config.get("XMLRPC", "bindip", fallback="").strip() + if bindip == "socket": + ssh_tunnel_socket = "/run/revpipyload/xmlrpc.socket" + elif bindip.startswith("/") or bindip.startswith("./"): + ssh_tunnel_socket = bindip + + if ssh_tunnel_socket: + log.debug("Using remote unix socket: %s", ssh_tunnel_socket) + # Forward local port 0 (dynamic) to remote unix socket + ssh_tunnel_server.disconnect() + ssh_tunnel_server = SSHLocalTunnel( + ssh_tunnel_socket, + revpi_settings.address, + revpi_settings.ssh_port + ) + ssh_tunnel_port = ssh_tunnel_server.connect_by_credentials( + revpi_settings.ssh_user, ssh_pass + ) + else: + log.debug("Using remote TCP socket: %s", bindip) + + except Exception as e: + log.warning(f"Could not check remote config for unix socket: {e}") + if getattr(revpi_settings, "ssh_enable_revpipyload", False): ssh_tunnel_server.send_cmd("sudo systemctl enable --now revpipyload") @@ -723,7 +757,7 @@ def create_server_proxy(revpi_settings: RevPiSettings, ssh_tunnel_port: int = No :param ssh_tunnel_port: Use this port if an SSH tunnel is already established :return: ServerProxy instance """ - if revpi_settings.is_unix_socket: + if not ssh_tunnel_port and revpi_settings.is_unix_socket: return ServerProxy("http://localhost", transport=UnixStreamTransport(revpi_settings.address)) if ssh_tunnel_port: diff --git a/src/revpicommander/ssh_tunneling/server.py b/src/revpicommander/ssh_tunneling/server.py index 691a46d..d00a25a 100644 --- a/src/revpicommander/ssh_tunneling/server.py +++ b/src/revpicommander/ssh_tunneling/server.py @@ -18,15 +18,15 @@ log = getLogger("ssh_tunneling") class SSHLocalTunnel: - def __init__(self, remote_tunnel_port: int, ssh_host: str, ssh_port: int = 22): + def __init__(self, remote_target: Union[int, str], ssh_host: str, ssh_port: int = 22): """ - Connect to a ssh remote host and tunnel a port to your host. + Connect to a ssh remote host and tunnel a port or unix socket to your host. - :param remote_tunnel_port: Port on the remote host to tunnel through ssh + :param remote_target: Port or unix socket path on the remote host to tunnel through ssh :param ssh_host: ssh remote host address :param ssh_port: ssh remote host port """ - self._remote_tunnel_port = remote_tunnel_port + self._remote_target = remote_target self._ssh_host = ssh_host self._ssh_port = ssh_port @@ -72,10 +72,15 @@ class SSHLocalTunnel: config=None, # Do not parse local config ) as conn: self._conn = conn - # Forward local port 0 (dynamic) to remote 127.0.0.1:remote_tunnel_port - self._server = await conn.forward_local_port( - '127.0.0.1', 0, '127.0.0.1', self._remote_tunnel_port - ) + # Forward local port 0 (dynamic) to remote target (port or unix socket) + if isinstance(self._remote_target, int): + self._server = await conn.forward_local_port( + '127.0.0.1', 0, '127.0.0.1', self._remote_target + ) + else: + self._server = await conn.forward_local_port_to_path( + '127.0.0.1', 0, self._remote_target + ) self._local_tunnel_port = self._server.get_port() self._started.set() From 2f40a3642067de1dc44f9800d1d56092f6405306 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Thu, 6 Aug 2026 08:34:15 +0200 Subject: [PATCH 10/17] feat: Add support for default local socket in connection settings Introduces a checkbox to toggle the use of the default local Unix socket for XML-RPC connections. Updates UI layout and enables dynamic state handling for related fields, such as address and port, based on socket usage. Ensures proper state synchronization and settings persistence. Signed-off-by: Sven Sager --- src/revpicommander/helper.py | 5 ++- src/revpicommander/revpiplclist.py | 50 ++++++++++++++++-------- src/revpicommander/ui/revpiplclist_ui.py | 21 ++++++---- ui_dev/revpiplclist.ui | 24 ++++++++---- 4 files changed, 68 insertions(+), 32 deletions(-) diff --git a/src/revpicommander/helper.py b/src/revpicommander/helper.py index 900554e..f40b016 100644 --- a/src/revpicommander/helper.py +++ b/src/revpicommander/helper.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Helper functions for this application.""" __author__ = "Sven Sager" -__copyright__ = "Copyright (C) 2023 Sven Sager" +__copyright__ = "Copyright (C) 2023-2026 Sven Sager" __license__ = "GPLv2" import pickle @@ -206,7 +206,8 @@ class RevPiSettings: self._settings.setValue("port", self.port) self._settings.setValue("timeout", self.timeout) - self._settings.setValue("ssh_use_tunnel", self.ssh_use_tunnel) + # Disable SSH tunnel if unix socket is used. SSH will check the type on the remove system + self._settings.setValue("ssh_use_tunnel", self.ssh_use_tunnel and not self.is_unix_socket) self._settings.setValue("ssh_port", self.ssh_port) self._settings.setValue("ssh_user", self.ssh_user) self._settings.setValue("ssh_saved_password", self.ssh_saved_password) diff --git a/src/revpicommander/revpiplclist.py b/src/revpicommander/revpiplclist.py index 091ada2..0207f1a 100644 --- a/src/revpicommander/revpiplclist.py +++ b/src/revpicommander/revpiplclist.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Saved connections of Revolution Pi devices.""" __author__ = "Sven Sager" -__copyright__ = "Copyright (C) 2023 Sven Sager" +__copyright__ = "Copyright (C) 2023-2026 Sven Sager" __license__ = "GPLv2" from enum import IntEnum @@ -16,6 +16,7 @@ from . import proginit as pi from .helper import RevPiSettings, WidgetData from .ui.revpiplclist_ui import Ui_diag_connections +DEFAULT_SOCKET_ADDRESS = "/run/revpipyload/xmlrpc.socket" log = getLogger(__name__) @@ -40,6 +41,8 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): self.lbl_port.setText(self.lbl_port.text().format(self.__default_port)) self.sbx_port.setValue(self.__default_port) + self._mrk_address = "" + # Dirty workaround to remove default button to prevent action on ENTER key, while user edit texts self.__btn_dummy = QtWidgets.QPushButton(self) self.__btn_dummy.setVisible(False) @@ -189,12 +192,21 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): up_ok = index > 0 down_ok = index < self.tre_connections.topLevelItemCount() - 1 + address = self.txt_address.text() + is_unix_socket = address.startswith("/") or address.startswith("./") + is_unix_default = address.lower() == DEFAULT_SOCKET_ADDRESS + + # Value isn't saved in settings, resulting of address value + with QtCore.QSignalBlocker(self.cbx_local_socket): + self.cbx_local_socket.setChecked(is_unix_default) + self.btn_up.setEnabled(up_ok) self.btn_down.setEnabled(down_ok) self.btn_delete.setEnabled(con_item or dir_item) self.txt_name.setEnabled(con_item) - self.txt_address.setEnabled(con_item) - self.sbx_port.setEnabled(con_item) + self.txt_address.setEnabled(con_item and not is_unix_default) + self.cbx_local_socket.setEnabled(con_item) + self.sbx_port.setEnabled(con_item and not is_unix_socket) self.sbx_timeout.setEnabled(con_item) self.cbb_folder.setEnabled(con_item or dir_item) self.cbb_folder.setEditable(dir_item) @@ -202,19 +214,9 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): # Disable auto complete, this would override a new typed name with existing one self.cbb_folder.setCompleter(None) - self.cbx_ssh_use_tunnel.setEnabled(con_item) - self.sbx_ssh_port.setEnabled(con_item) - self.txt_ssh_user.setEnabled(con_item) - - if con_item: - address = self.txt_address.text() - is_unix = address.startswith("/") or address.startswith("./") - if is_unix: - self.sbx_port.setEnabled(False) - self.cbx_ssh_use_tunnel.setChecked(False) - self.cbx_ssh_use_tunnel.setEnabled(False) - self.sbx_ssh_port.setEnabled(False) - self.txt_ssh_user.setEnabled(False) + self.cbx_ssh_use_tunnel.setEnabled(con_item and not is_unix_socket) + self.sbx_ssh_port.setEnabled(con_item and not is_unix_socket) + self.txt_ssh_user.setEnabled(con_item and not is_unix_socket) def _get_folder_item(self, name: str): """Find the folder entry by name.""" @@ -280,6 +282,7 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): self.__current_item = QtWidgets.QTreeWidgetItem() self.cbb_folder.setCurrentText(current.text(0) if current else "") + self._mrk_address = "" self._edit_state() @QtCore.pyqtSlot() @@ -400,6 +403,21 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): settings.timeout = value self.changes = True + @QtCore.pyqtSlot(int) + def on_cbx_local_socket_stateChanged(self, check_state: int): + if self.__current_item.type() != NodeType.CON: + return + + if check_state == QtCore.Qt.CheckState.Checked: + # Backup fields to restore the text if unchecked + self._mrk_address = self.txt_address.text() + self.txt_address.setText(DEFAULT_SOCKET_ADDRESS) + self.on_txt_address_textEdited(self.txt_address.text()) + else: + # Restore old address if it is not the default to unlock the address field + self.txt_address.setText("" if self._mrk_address == DEFAULT_SOCKET_ADDRESS else self._mrk_address) + self.on_txt_address_textEdited(self.txt_address.text()) + @QtCore.pyqtSlot(int) def on_cbx_ssh_use_tunnel_stateChanged(self, check_state: int): if self.__current_item.type() != NodeType.CON: diff --git a/src/revpicommander/ui/revpiplclist_ui.py b/src/revpicommander/ui/revpiplclist_ui.py index 325970d..0353acb 100644 --- a/src/revpicommander/ui/revpiplclist_ui.py +++ b/src/revpicommander/ui/revpiplclist_ui.py @@ -37,7 +37,7 @@ class Ui_diag_connections(object): self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.txt_address) self.lbl_port = QtWidgets.QLabel(self.tab_connection) self.lbl_port.setObjectName("lbl_port") - self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.lbl_port) + self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.lbl_port) self.sbx_port = QtWidgets.QSpinBox(self.tab_connection) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -48,10 +48,10 @@ class Ui_diag_connections(object): self.sbx_port.setMaximum(65535) self.sbx_port.setProperty("value", 55123) self.sbx_port.setObjectName("sbx_port") - self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.sbx_port) + self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.sbx_port) self.lbl_timeout = QtWidgets.QLabel(self.tab_connection) self.lbl_timeout.setObjectName("lbl_timeout") - self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.lbl_timeout) + self.formLayout_2.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.lbl_timeout) self.sbx_timeout = QtWidgets.QSpinBox(self.tab_connection) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -61,17 +61,23 @@ class Ui_diag_connections(object): self.sbx_timeout.setMinimum(5) self.sbx_timeout.setMaximum(30) self.sbx_timeout.setObjectName("sbx_timeout") - self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.sbx_timeout) + self.formLayout_2.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.sbx_timeout) self.lbl_folder = QtWidgets.QLabel(self.tab_connection) self.lbl_folder.setObjectName("lbl_folder") - self.formLayout_2.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.lbl_folder) + self.formLayout_2.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.lbl_folder) self.cbb_folder = QtWidgets.QComboBox(self.tab_connection) self.cbb_folder.setEditable(True) self.cbb_folder.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) self.cbb_folder.setObjectName("cbb_folder") self.cbb_folder.addItem("") self.cbb_folder.setItemText(0, "") - self.formLayout_2.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.cbb_folder) + self.formLayout_2.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.cbb_folder) + self.lbl_local_socket = QtWidgets.QLabel(self.tab_connection) + self.lbl_local_socket.setObjectName("lbl_local_socket") + self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.lbl_local_socket) + self.cbx_local_socket = QtWidgets.QCheckBox(self.tab_connection) + self.cbx_local_socket.setObjectName("cbx_local_socket") + self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.cbx_local_socket) self.tab_properties.addTab(self.tab_connection, "") self.tab_ssh = QtWidgets.QWidget() self.tab_ssh.setObjectName("tab_ssh") @@ -157,11 +163,12 @@ class Ui_diag_connections(object): _translate = QtCore.QCoreApplication.translate diag_connections.setWindowTitle(_translate("diag_connections", "RevPi Connections")) self.lbl_name.setText(_translate("diag_connections", "Display name:")) - self.lbl_address.setText(_translate("diag_connections", "Address (DNS/IP):")) + self.lbl_address.setText(_translate("diag_connections", "Address (DNS/IP/Socket):")) self.lbl_port.setText(_translate("diag_connections", "Port (default {0}):")) self.lbl_timeout.setText(_translate("diag_connections", "Connection timeout:")) self.sbx_timeout.setSuffix(_translate("diag_connections", " s")) self.lbl_folder.setText(_translate("diag_connections", "Subfolder:")) + self.lbl_local_socket.setText(_translate("diag_connections", "Use default local socket:")) self.tab_properties.setTabText(self.tab_properties.indexOf(self.tab_connection), _translate("diag_connections", "Connection")) self.lbl_ssh_use_tunnel.setText(_translate("diag_connections", "Connect over SSH tunnel:")) self.lbl_ssh_port.setText(_translate("diag_connections", "SSH port:")) diff --git a/ui_dev/revpiplclist.ui b/ui_dev/revpiplclist.ui index 9dfd1fa..d5a6735 100644 --- a/ui_dev/revpiplclist.ui +++ b/ui_dev/revpiplclist.ui @@ -37,21 +37,21 @@ - Address (DNS/IP): + Address (DNS/IP/Socket): - + Port (default {0}): - + @@ -70,14 +70,14 @@ - + Connection timeout: - + @@ -96,14 +96,14 @@ - + Subfolder: - + true @@ -118,6 +118,16 @@ + + + + Use default local socket: + + + + + + From 31ad5fe2283f7441b004550b16b3b2b2af3a9bda Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Thu, 6 Aug 2026 08:38:40 +0200 Subject: [PATCH 11/17] feat(i18n): Update translations for revpiplclist.ui --- .../locale/revpicommander_de.qm | Bin 57430 -> 57585 bytes .../locale/revpicommander_de.ts | 67 +++++++++++------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/revpicommander/locale/revpicommander_de.qm b/src/revpicommander/locale/revpicommander_de.qm index 6d5c6367723693a22c1ab9a37e09a5a0452a8de5..b0907fbd73b432fc9f64ed039f52da0ddd65df89 100644 GIT binary patch delta 2023 zcmYjSdsGzX75~ld% zmB>SwB7z1`KoJ2W1h9xt;HbP8P#SEkC&Xe}(Mo(onuK2Uk9N=5x$}MBef@s--fym* zm*2l2-)o5Y6p$qYISJ3@kpjqCl1J z3iSO1=R0ns?}kO}Ajvo^%BAyub76Y8Y?LV+v|iH7A=4rV*i(%>-!kBs6%Jq%kT8M+ z4$idikMdE9vAPI{;(CGB4U(R*x0TRSn+e2Zqrb5ksHw$>Y$tGdT6&AI_A1QAboxJm z1F;MONcIFXLdB0j(+p!&&H!H+nKwdS0{+y+gwz((dYXCPg@AoIELAW@%c>nrgC-1U zPh-A%c@8j=Bi&=H#VZBOgvtQae<$f=2C*YUrYIT#E=5TmvcTo{WPksu3mA-)efP@) zU}3K8Nq09;-7aSrZUhW*a&5|0VBd@KIZDFkzD%zFn=Q~_l-gwWOp`PwbIfXqm7jOG z0oY~AM+SRDk7=Zx|>${8W z&%ViqWTp~XlkEB+BHZR@_V+zO!2We?YDW_A_Q!1c>1+b#!)6pJfw~3k!QW~zAJNy? z%G;g5U)Hi`MJ*9?f}|dB?qaV_`T-Zdmn!52-=@zv>$T;8kjS}YPf*+#&c7rPC=s}@ z*e3LjS*z7;zq^%83`yKmeRRZ%5M~PduX@XS31X8 z+qEoIeEn__aOF+K_#>*1m!IP1lnYSHO7}SZ@&|&}>ISjgBzP|6fYXtJr$k!87ljoz zQ^b0{V4PVFq-_`cRufaD2L->xbYS?Aw2C*#E*DCXygsW#$e5v4aH|%!HBx13*F2Zz zVBt%<7T}bPP|@%`mF8Wc!b~W8>xITVLSyI_+IlFMkB@NfW;8H5CJadVRHpsH;H(ia zZhJ0GV}f~Q4RAbB7_%fmHX_hIVLy;8cuF{^a0aTsoEk@NuH)4MK5pT1;I_12CE%x@zEVSLbX7}`}9$d)vHwNcK{YqDjVM%AU{xLFp_@W6_qk9daoURQxdRwm&Q&(Eirsd z^O6%4$Er``Se{P_?Uxjyqil$kTt&U|b4}6%>bqlE(p#dz_p9BSlFeR#-b2&+b`VWz zmR4V>1yWkI^Iw)|oW`_4ZV@yw*R|`dX>tr|ZFnpNEf3d*7miRvJd{p|j+R~TXh*8% z6Sgqz#O5dTeejbeMMv?}Ze8dMK^>Zw^s2>y%@MjvBWb;}Lf7g!0Z7HV_NF=t+N(RW zvK;i&ozu~E*||?QTxTMN9_vQCp2~9FxB2UU&u-9tbah?xpH)|L7&qzTV*4eobfIiP zG~!`G3Y_32Jaxm<-^M2VV_` delta 1899 zcmX9<4OCQh7XQ6B@4a~+^AQpZkPQTdLLrduf)s;%tRM7+=yKLnNCb7)LOW zNCe?65vYIyilQWf5TKv}X~@?!s70IWiPfwH-PqKXH1>9!Idks$|KEN0-rw*3?wy~{ zX|9~r>@ly{0vaKH!m;XC{s z(4P$7@c^>>@jN#~_A;K&r{@EfqKr7!o>DaA{9<1!vK^vj4J)+3z3y z0?f{5AN2MDHM2C_?A3sIgT}D!GEnl7#z`W4f%7z`f4EYxdbO2xXWZ4RthcqV{4tgnggnM#3RdB(DduH@F5c6-&XC;+zD3J?2Vc`g|%0> zs$1Q_=Pg{PY#?IF$(jJ)9`5oFk-)j7YNf^;(RiD8UQz*wn|QyRG0JP>qe?dcpQQ84 zrhfy%C-}IH#AkaHzoMlbI2yyJBm@CvoB8xTZvn$5bz0-jY*gJjXSS_Q4dT45^;h_d z(qDlS$M}I;gyqoBy#3DHf9Ua51$40)(UZ(zX5V=!n#L}K=t>+7LIBvJ1Jx~jS@$Dg`(U7 zEJL!dP~6^4Am#|wg-0n-sL;4O8|a)C`fUX2-Bw{Bb_D3UD-514BT{Dw!?T)zN>9PI zE*4msC5#GYASzn85lBin)S&wFK@KyuF5?vST%Xo!N(Cyy)C@jMTBtp`i*BnwQ@eO) zx8@RU^P9!M#dhuJJ?c+Lr1tuRA8>f9dWSd7Un3eEuaUIe#o)O-vDz;NtK{|SdGRHe z2`czk(K0n3*uF!ITtG~fNn+%t4C?M$HB2zG7k*O{1e4WA%$y=A1k{LG4b<7grykqk zHgUh(DWLTOvGVx8fT0UwrHxQtaupkP6B=``*xEuZ6LC~;6NAo_W6Xxai; zvL4&ytD^1YT59#Ecy$KZV@3%Fi0(@{k~B^Ja>5Sj8P5tDsz`NOSnu$rbi}O_SY?yC z|8tO(`AD^Dy{(@XNJBjY;lLBpEr|t=drG%csaji>H0etMax};eHacG#kbS0TfX}zd zbFXFs!Uj3{6{@~5K~7&n!ncpf=@(qdf3=(uCrRKj5w=nJ7&Dh9t59yR(55IhszIW=b+O!DmPY+qE`Pf{8wmA~C!@-N zdaHadkLI+mQoc9tMvHQmB82yo7F`u(>AQf#8pS0dkLI{p3FO|P<@J;jS{VyJu(2@Q0XkaP-(A|qO*+3Pw188lZh`${(xaCys)~{i zIq72W(77mA_uh{bJk6pjT@gtw$kvrJG!Q4Y>S}uF`JRKi4u(Ki-qiK1cA-uDjjqQ^ zRy(4*9!k$o`|Bo6x9OiDSF9)dU-p(; z?;2yL2jv-P#&gNU zNS;l#Do?Q^9Zma`%Xt=0gd+*5NJbicli`8ch{Ef%Lj17=G4Mf%_mAF)AU1`yy*W0O ConnectionManager - + Error Fehler - + The combination of username and password was rejected from the SSH server. Try again. @@ -95,7 +95,7 @@ Try again. Erneut versuchen. - + Cannot connect to SSH server: {0} @@ -104,7 +104,7 @@ Erneut versuchen. {0} - + Cannot connect to RevPiPyLoad service through SSH tunnel. Possible reasons: @@ -119,7 +119,7 @@ Mögliche Ursachen: - Für 127.0.0.1 ist keine ACL-Berechtigung gesetzt. - + Cannot connect to RevPiPyLoad XML-RPC service. Possible reasons: @@ -140,52 +140,52 @@ Mögliche Ursachen: Für eine verschlüsselte Verbindung 'Über SSH verbinden' verwenden oder auf dem RevPi 'sudo revpipyload_secure_installation' ausführen, um den direkten Fernzugriff einzurichten. - + Simulating Simulation läuft - + Not connected Nicht verbunden - + Server error Serverfehler - + Running Läuft - + PLC file not found PLC-Datei nicht gefunden - + Not running (no status) Nicht aktiv (kein Status) - + Program killed Programm zwangsweise beendet - + Program terminated Programm beendet - + Not running Nicht gestartet - + Finished with exit code {0} Beendet mit Exit-Code {0} @@ -734,18 +734,18 @@ Nicht gespeicherte Änderungen gehen verloren. RevPiPlcList - + Question Frage - + Quit without saving? Unsaved changes will be lost. Ohne Speichern schließen? Nicht gespeicherte Änderungen gehen verloren. - + If you remove this folder, all contained items will be removed as well. Do you want to delete the folder and all contained items? @@ -754,7 +754,7 @@ Do you want to delete the folder and all contained items? Ordner und alle enthaltenen Elemente löschen? - + New folder Neuer Ordner @@ -1127,7 +1127,7 @@ Dies ist kein Fehler von RevPi Commander. diag_connections - + Address Adresse @@ -1139,7 +1139,7 @@ Dies ist kein Fehler von RevPi Commander. Address (DNS/IP): - Adresse (DNS/IP): + Adresse (DNS/IP): @@ -1172,30 +1172,45 @@ Dies ist kein Fehler von RevPi Commander. Unterordner: - + Over SSH Über SSH - + Connect over SSH tunnel: Über SSH-Tunnel verbinden: - + SSH port: SSH-Port: - + SSH user name: SSH-Benutzername: - + Connection Name Verbindungsname + + + Address (DNS/IP/Socket): + Adresse (DNS/IP/Socket): + + + + Use default local socket + Lokalen Standardsocket benutzen: + + + + Use default local socket: + Lokalen Standardsocket benutzen: + diag_mqtt From 393ec93649ee4b658a8a4c17c57269d3ea41cca8 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Fri, 7 Aug 2026 06:09:51 +0200 Subject: [PATCH 12/17] fix: Endlessloop during revpipyload activation on remote host RevPi Commander can activate the `revpipyload` service with an SSH connection on the remote host and starts a new connection. If the activation of the service failed, the user was in an endless loop and had to end the process via the operating system. Signed-off-by: Sven Sager --- src/revpicommander/revpicommander.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/revpicommander/revpicommander.py b/src/revpicommander/revpicommander.py index f9ee948..3dced74 100644 --- a/src/revpicommander/revpicommander.py +++ b/src/revpicommander/revpicommander.py @@ -129,7 +129,8 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): # region # REGION: Connection management @QtCore.pyqtSlot(str, str, ConnectionFail, RevPiSettings) - def on_cm_connect_error(self, title: str, text: str, fail_code: ConnectionFail, revpi_settings: RevPiSettings): + def on_cm_connect_error(self, title: str, text: str, fail_code: ConnectionFail, + revpi_settings: RevPiSettings): """ Slot to get information of pyload_connect connection errors. @@ -138,7 +139,8 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): :param fail_code: Type of error :param revpi_settings: Settings of the revpi with the error """ - if fail_code is ConnectionFail.NO_XML_RPC_VIA_TUNNEL: + if (fail_code is ConnectionFail.NO_XML_RPC_VIA_TUNNEL + and not getattr(revpi_settings, "ssh_enable_revpipyload", False)): # If RevPiPyLoad is not running, we can try to activate it via ssh QtWidgets.QMessageBox.information( self, self.tr("Information"), self.tr( From 7eda0ac20c96dfc4bcd8774da99fed026ab2b4a4 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Fri, 7 Aug 2026 06:26:48 +0200 Subject: [PATCH 13/17] refactor: Include exit status in `send_cmd` and improve decoding logic Extend `send_cmd` to return `exit_status` along with `stdout` and `stderr`. Adjust handling to ensure proper decoding of byte outputs to strings. Signed-off-by: Sven Sager --- src/revpicommander/helper.py | 2 +- src/revpicommander/ssh_tunneling/server.py | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/revpicommander/helper.py b/src/revpicommander/helper.py index f40b016..e69c83a 100644 --- a/src/revpicommander/helper.py +++ b/src/revpicommander/helper.py @@ -383,7 +383,7 @@ class ConnectionManager(QtCore.QThread): # Check for Unix socket on remote system try: - stdout, stderr = ssh_tunnel_server.send_cmd("cat /etc/revpipyload/revpipyload.conf") + stdout, stderr, exit_code = ssh_tunnel_server.send_cmd("cat /etc/revpipyload/revpipyload.conf") if stdout: config = ConfigParser() config.read_string(stdout) diff --git a/src/revpicommander/ssh_tunneling/server.py b/src/revpicommander/ssh_tunneling/server.py index d00a25a..84d4573 100644 --- a/src/revpicommander/ssh_tunneling/server.py +++ b/src/revpicommander/ssh_tunneling/server.py @@ -187,13 +187,13 @@ class SSHLocalTunnel: except Exception: return True - def send_cmd(self, cmd: str, timeout: float = None) -> Union[Tuple[str, str], Tuple[None, None]]: + def send_cmd(self, cmd: str, timeout: float = None) -> Union[Tuple[str, str, int], Tuple[None, None, None]]: """ Send simple command to ssh host. :param cmd: Shell command to execute on remote host :param timeout: Timeout for execution - :return: Tuple with stdout and stderr + :return: Tuple with stdout, stderr, exit status """ if not self.connected: raise RuntimeError("Not connected") @@ -201,15 +201,19 @@ class SSHLocalTunnel: # Running async command from sync context async def _exec(): result = await self._conn.run(cmd, timeout=timeout) - return result.stdout, result.stderr + return result.stdout, result.stderr, result.exit_status try: future = asyncio.run_coroutine_threadsafe(_exec(), self._loop) - stdout, stderr = future.result(timeout=timeout) - return stdout, stderr + stdout, stderr, exit_status = future.result(timeout=timeout) + if type(stdout) is bytes: + stdout = stdout.decode(errors="ignore") + if type(stderr) is bytes: + stderr = stderr.decode(errors="ignore") + return stdout, stderr, exit_status except Exception as e: log.error(e) - return None, None + return None, None, None @property def connected(self): From e7b5848664c66211c5d1d9c9900dd8e6f6fd3916 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Fri, 7 Aug 2026 07:07:22 +0200 Subject: [PATCH 14/17] feat(ssh): Add ability to send stdin input to `send_cmd` Extend the `send_cmd` function to support passing input to stdin when executing remote shell commands via SSH. Signed-off-by: Sven Sager --- src/revpicommander/ssh_tunneling/server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/revpicommander/ssh_tunneling/server.py b/src/revpicommander/ssh_tunneling/server.py index 84d4573..b1448a2 100644 --- a/src/revpicommander/ssh_tunneling/server.py +++ b/src/revpicommander/ssh_tunneling/server.py @@ -187,12 +187,13 @@ class SSHLocalTunnel: except Exception: return True - def send_cmd(self, cmd: str, timeout: float = None) -> Union[Tuple[str, str, int], Tuple[None, None, None]]: + def send_cmd(self, cmd: str, timeout: float = None, stdin: str = None) -> Union[Tuple[str, str, int], Tuple[None, None, None]]: """ Send simple command to ssh host. :param cmd: Shell command to execute on remote host :param timeout: Timeout for execution + :param stdin: Send this string to stdin :return: Tuple with stdout, stderr, exit status """ if not self.connected: @@ -200,7 +201,7 @@ class SSHLocalTunnel: # Running async command from sync context async def _exec(): - result = await self._conn.run(cmd, timeout=timeout) + result = await self._conn.run(cmd, timeout=timeout, input=stdin) return result.stdout, result.stderr, result.exit_status try: From 86c644e83061077b2d3435b6e1cb4fc3da2c5edd Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Fri, 7 Aug 2026 07:11:00 +0200 Subject: [PATCH 15/17] fix: Handle sudo authentication failure during RevPiPyLoad activation Improve logic to handle scenarios where sudo requires a password for activating RevPiPyLoad, including user notification and error handling. Signed-off-by: Sven Sager --- src/revpicommander/helper.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/revpicommander/helper.py b/src/revpicommander/helper.py index e69c83a..09bbaa5 100644 --- a/src/revpicommander/helper.py +++ b/src/revpicommander/helper.py @@ -413,7 +413,34 @@ class ConnectionManager(QtCore.QThread): log.warning(f"Could not check remote config for unix socket: {e}") if getattr(revpi_settings, "ssh_enable_revpipyload", False): - ssh_tunnel_server.send_cmd("sudo systemctl enable --now revpipyload") + cmd_activate_pyload = "systemctl enable --now revpipyload" + + # Test sudo requires password authentication + _, _, exit_code = ssh_tunnel_server.send_cmd("sudo -n true") + if exit_code == 0: + # No password required + ssh_tunnel_server.send_cmd(f"sudo {cmd_activate_pyload}") + else: + # Execute command with sudo password + _, _, exit_code = ssh_tunnel_server.send_cmd( + f"sudo -S {cmd_activate_pyload}", + stdin=ssh_pass, + ) + if exit_code != 0: + log.error( + "Sudo authentification failed for user %s", + revpi_settings.ssh_user, + ) + self.connect_error.emit( + self.tr("Error"), self.tr( + "Can not activate RevPiPyLoad on remote RevPi.\n" + f"Sudo authentification failed for user {revpi_settings.ssh_user}. " + "Please activate RevPiPyLoad manually via Cockpit or CLI." + ), + ConnectionFail.NO_XML_RPC_VIA_TUNNEL, + revpi_settings, + ) + return False except asyncssh.PermissionDenied: self.connect_error.emit( From 810bf054be87280389bc6684e72b70fcd3882339 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Fri, 7 Aug 2026 07:17:53 +0200 Subject: [PATCH 16/17] feat(i18n): Update German translation files and add new error messages Update translation entries in `revpicommander_de.ts` and regenerate the `.qm` file. Includes additions for handling new error states and messaging, such as SSH authentication failures and RevPiPyLoad activation issues. Signed-off-by: Sven Sager --- .../locale/revpicommander_de.qm | Bin 57585 -> 58246 bytes .../locale/revpicommander_de.ts | 89 ++++++++++-------- 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/src/revpicommander/locale/revpicommander_de.qm b/src/revpicommander/locale/revpicommander_de.qm index b0907fbd73b432fc9f64ed039f52da0ddd65df89..263fbaa1793416d9faa36c1fa02556d130d89b81 100644 GIT binary patch delta 3469 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 diff --git a/src/revpicommander/locale/revpicommander_de.ts b/src/revpicommander/locale/revpicommander_de.ts index 6c6d451..dfde087 100644 --- a/src/revpicommander/locale/revpicommander_de.ts +++ b/src/revpicommander/locale/revpicommander_de.ts @@ -81,12 +81,12 @@ Nicht gespeicherte Änderungen gehen verloren. ConnectionManager - + Error Fehler - + The combination of username and password was rejected from the SSH server. Try again. @@ -95,7 +95,7 @@ Try again. Erneut versuchen. - + Cannot connect to SSH server: {0} @@ -104,7 +104,7 @@ Erneut versuchen. {0} - + Cannot connect to RevPiPyLoad service through SSH tunnel. Possible reasons: @@ -119,7 +119,7 @@ Mögliche Ursachen: - Für 127.0.0.1 ist keine ACL-Berechtigung gesetzt. - + Cannot connect to RevPiPyLoad XML-RPC service. Possible reasons: @@ -140,55 +140,62 @@ Mögliche Ursachen: Für eine verschlüsselte Verbindung 'Über SSH verbinden' verwenden oder auf dem RevPi 'sudo revpipyload_secure_installation' ausführen, um den direkten Fernzugriff einzurichten. - + Simulating Simulation läuft - + Not connected Nicht verbunden - + Server error Serverfehler - + Running Läuft - + PLC file not found PLC-Datei nicht gefunden - + Not running (no status) Nicht aktiv (kein Status) - + Program killed Programm zwangsweise beendet - + Program terminated Programm beendet - + Not running Nicht gestartet - + Finished with exit code {0} Beendet mit Exit-Code {0} + + + Can not activate RevPiPyLoad on remote RevPi. +Sudo authentification failed for user {revpi_settings.ssh_user}. Please activate RevPiPyLoad manually via Cockpit or CLI. + RevPiPyLoad kann auf dem verbundenen RevPi nicht aktiviert werden. +Die Sudo-Authentifizierung für den Benutzer {revpi_settings.ssh_user} ist fehlgeschlagen. Bitte aktivieren Sie RevPiPyLoad manuell über Cockpit oder die CLI. + DebugControl @@ -321,59 +328,59 @@ Nicht gespeicherte Änderungen gehen verloren. RevPiCommander - + Warning Warnung - + Error Fehler - + Question Frage - + Success Erfolg - + Connecting to RevPi Verbindung zum RevPi wird hergestellt - + Connected to RevPi Mit dem RevPi verbunden - + Connecting Verbinden - + Cannot connect to the RevPiPyLoad service through the SSH tunnel. Service activation and reconnection in progress. The settings can be changed at any time via Cockpit. Kann keine Verbindung zum RevPiPyLoad-Dienst über den SSH-Tunnel hergestellt werden. Dienstaktivierung und Wiederanbindung in Arbeit. Die Einstellungen können jederzeit über Cockpit geändert werden. - + Simulator started Simulator gestartet - + Cannot start Kann nicht starten - + Simulator is running. Use the additional RevPiModIO parameters: @@ -390,85 +397,85 @@ configrsc={1} aus dem Textfeld in der Kopfzeile verwenden. - + Cannot start the simulator. The PiCtory file might be invalid or you do not have write permissions for '{0}'. Simulator kann nicht gestartet werden. Die PiCtory Datei ist möglicherweise ungültig oder für „{0}“ fehlen Schreibrechte. - + This version of Log Viewer is not supported in version {0} of RevPiPyLoad on your RevPi. At least version 0.4.1 is required. Diese Version des Log Viewers wird von RevPiPyLoad {0} auf dem RevPi nicht unterstützt. Mindestens Version 0.4.1 ist erforderlich. - + XML-RPC access mode in the RevPiPyLoad configuration is too low to access this dialog. Der XML-RPC-Zugriffsmodus in der RevPiPyLoad Konfiguration ist zu niedrig, um auf diesen Dialog zuzugreifen. - + The version of RevPiPyLoad on your RevPi ({0}) is too old. This version of RevPi Commander requires at least version 0.6.0 of RevPiPyLoad. Update your RevPi. Die Version von RevPiPyLoad auf dem RevPi ({0}) ist zu alt. Für diese Version von RevPi Commander wird mindestens RevPiPyLoad 0.6.0 benötigt. RevPi aktualisieren. - + Are you sure you want to reset piControl? The PiCtory configuration will be reloaded. During that time, the process image will be interrupted and could cause errors on running control programs. piControl wirklich zurücksetzen? Die PiCtory-Konfiguration wird neu geladen. Das Prozessabbild wird dabei kurzzeitig unterbrochen und kann Fehler in laufenden Steuerungsprogrammen verursachen. - + piControl reset completed successfully. piControl wurde erfolgreich zurückgesetzt. - + piControl reset could not be completed. piControl konnte nicht zurückgesetzt werden. - + Reset to PiCtory defaults Auf PiCtory Standardwerte zurücksetzen - + The watch mode is not supported in version {0} of RevPiPyLoad on your RevPi. At least version 0.5.3 is required. The python3-revpimodio2 module may be missing or older than version 2.0.0. Der Watch-Modus wird von RevPiPyLoad {0} auf dem RevPi nicht unterstützt. Mindestens Version 0.5.3 ist erforderlich. Das Modul python3-revpimodio2 fehlt möglicherweise oder ist älter als Version 2.0.0. - + Cannot load this function, because your ACL level is too low. At least level 1 to read or level 3 to write is required. Diese Funktion kann nicht verwendet werden, da die ACL-Stufe zu niedrig ist. Zum Lesen ist mindestens Stufe 1, zum Schreiben mindestens Stufe 3 erforderlich. - + Do you want to reset your process image to {0} values? You have to stop other RevPiModIO programs before doing that, because they could reset the outputs. Prozessabbild auf die Werte aus {0} zurücksetzen? Vorher müssen alle anderen RevPiModIO-Programme beendet werden, da diese die Ausgänge zurücksetzen könnten. - + zero null - + PiCtory defaults PiCtory Standardwerte - + Cannot load PiCtory configuration. Check hardware configuration in PiCtory. PiCtory Konfiguration kann nicht geladen werden. Hardwarekonfiguration in PiCtory prüfen. - + Information Information From 10e838e22996d66b17ded830e113047e9d0e746c Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Fri, 7 Aug 2026 08:20:06 +0200 Subject: [PATCH 17/17] chore: Release 0.12.0 Signed-off-by: Sven Sager --- setup.iss | 2 +- src/revpicommander/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.iss b/setup.iss index 7630680..aeb1e15 100644 --- a/setup.iss +++ b/setup.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "RevPi Commander" -#define MyAppVersion "0.11.0" +#define MyAppVersion "0.12.0" #define MyAppPublisher "Sven Sager" #define MyAppURL "https://revpimodio.org/" #define MyAppICO "data\revpicommander.ico" diff --git a/src/revpicommander/__init__.py b/src/revpicommander/__init__.py index 805d057..aacb0ee 100644 --- a/src/revpicommander/__init__.py +++ b/src/revpicommander/__init__.py @@ -4,4 +4,4 @@ __author__ = "Sven Sager" __copyright__ = "Copyright (C) 2023 Sven Sager" __license__ = "GPLv2" __package__ = "revpicommander" -__version__ = "0.11.0" +__version__ = "0.12.0"