Merge tag '0.12.0' into pkg/debian

Release version 0.12.0
This commit is contained in:
2026-08-07 08:42:51 +02:00
58 changed files with 1920 additions and 1686 deletions
+3
View File
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.11 (revpicommander)" />
</component>
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
+28 -23
View File
@@ -8,6 +8,9 @@ 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:
@@ -109,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" \
@@ -123,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" \
@@ -164,6 +167,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
+12 -11
View File
@@ -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,19 @@ 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;." ^
--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" ^
--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 +77,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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -19,7 +19,7 @@ setup(
install_requires=[
"keyring",
"PyQt5",
"paramiko",
"asyncssh",
"revpimodio2",
"zeroconf"
],
+1 -1
View File
@@ -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"
+10 -10
View File
@@ -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."
)
)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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:
"""
+5 -5
View File
@@ -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])
+9 -9
View File
@@ -271,7 +271,7 @@ class DebugIos(QtWidgets.QMainWindow, Ui_win_debugios):
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":
+147 -48
View File
@@ -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
@@ -15,10 +15,11 @@ 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 configparser import ConfigParser
from PyQt5 import QtCore
from paramiko.ssh_exception import AuthenticationException
import asyncssh
from . import proginit as pi
from .ssh_tunneling.server import SSHLocalTunnel
@@ -32,6 +33,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 +107,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 +150,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()
@@ -182,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)
@@ -342,10 +367,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,
@@ -354,10 +381,68 @@ class ConnectionManager(QtCore.QThread):
try:
ssh_tunnel_port = ssh_tunnel_server.connect_by_credentials(revpi_settings.ssh_user, ssh_pass)
if getattr(revpi_settings, "ssh_enable_revpipyload", False):
ssh_tunnel_server.send_cmd("sudo systemctl enable --now revpipyload")
# Check for Unix socket on remote system
try:
stdout, stderr, exit_code = 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
except AuthenticationException:
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):
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(
self.tr("Error"), self.tr(
"The combination of username and password was rejected from the SSH server.\n\n"
@@ -372,17 +457,17 @@ 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,
)
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:
@@ -398,11 +483,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 +495,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,
@@ -435,10 +520,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()
@@ -546,14 +628,14 @@ 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()
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:
@@ -566,7 +648,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)))
@@ -582,7 +664,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
@@ -596,19 +678,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)
@@ -669,12 +751,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 +777,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 not ssh_tunnel_port and 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
Binary file not shown.
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+42 -41
View File
@@ -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,13 +139,14 @@ 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(
"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 +261,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 +296,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 +330,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 +356,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 +377,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 +386,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 +407,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 +416,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 +439,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 +452,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 +476,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 +506,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 +533,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 +545,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 +560,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)
+28 -28
View File
@@ -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)
@@ -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)
@@ -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."
)
)
+1 -1
View File
@@ -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")]
+1 -1
View File
@@ -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"))
+16 -16
View File
@@ -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:
+42 -10
View File
@@ -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)
@@ -134,7 +137,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
@@ -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,9 +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)
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."""
@@ -243,7 +255,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:
@@ -271,6 +282,9 @@ 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()
def on_btn_up_clicked(self):
self._move_item(-1)
@@ -303,8 +317,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:
@@ -319,6 +333,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."""
@@ -369,6 +385,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):
@@ -386,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:
+51 -51
View File
@@ -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,7 +219,7 @@ 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."
)
)
@@ -232,34 +232,34 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program):
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)
)
+2 -2
View File
@@ -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)
+142 -118
View File
@@ -1,112 +1,100 @@
# -*- 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):
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
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 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._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 +102,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,70 +134,92 @@ 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:
return True
asyncssh.read_private_key(key_file, passphrase=None)
return False
except asyncssh.KeyImportError:
return True
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, stdin: str = None) -> Union[Tuple[str, str, int], Tuple[None, 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
:param stdin: Send this string to stdin
:return: Tuple with stdout, stderr, exit status
"""
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, input=stdin)
return result.stdout, result.stderr, result.exit_status
return buffer_out.decode(), buffer_err.decode()
try:
future = asyncio.run_coroutine_threadsafe(_exec(), self._loop)
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):
"""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:
+4 -4
View File
@@ -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:
+4 -4
View File
@@ -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:"))
+9 -9
View File
@@ -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
+2 -2
View File
@@ -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)
+12 -12
View File
@@ -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__":
+1 -1
View File
@@ -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.
+5 -5
View File
@@ -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
+4 -4
View File
@@ -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:"))
+4 -4
View File
@@ -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__":
+6 -6
View File
@@ -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
+5 -6
View File
@@ -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("<html><head/><body><p><a href=\"https://revpimodio.org/\"><span style=\" text-decoration: underline; color:#0000ff;\">https://revpimodio.org/</span></a></p></body></html>")
self.lbl_link.setText("<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>")
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"))
+4 -4
View File
@@ -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", "Line wrap"))
self.lbl_daemon.setText(_translate("win_revpilogfile", "RevPiPyLoad - Logfile"))
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__":
+14 -14
View File
@@ -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,31 +123,31 @@ 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 RevPi Commander"))
+19 -12
View File
@@ -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.
@@ -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")
@@ -155,19 +161,20 @@ 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_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", " sec."))
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:"))
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
+13 -13
View File
@@ -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:"))
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 to workdirectory"))
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__":
+11 -11
View File
@@ -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__":
+3 -3
View File
@@ -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.
@@ -66,9 +66,9 @@ class Ui_diag_sshauth(object):
diag_sshauth.setWindowTitle(_translate("diag_sshauth", "SSH authentication"))
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.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", "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.lbl_info.setText(_translate("diag_sshauth", "Default SSH user is \"pi\". The device password is on the RevPi housing sticker."))
if __name__ == "__main__":
+3 -3
View File
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>IP access control list</string>
<string>IP Access Control List</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
@@ -51,7 +51,7 @@
</attribute>
<column>
<property name="text">
<string>IP Address</string>
<string>IP address</string>
</property>
</column>
<column>
@@ -97,7 +97,7 @@
<item>
<widget class="QGroupBox" name="gb_edit">
<property name="title">
<string>Add / Edit access entry</string>
<string>Add/Edit Access Entry</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
+8 -8
View File
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Search Revolution Pi devices</string>
<string>RevPi device search</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
@@ -19,7 +19,7 @@
<item>
<widget class="QLabel" name="lbl_search">
<property name="text">
<string>Searching for Revolution Pi devices in your network...</string>
<string>Searching for RevPi devices on the network</string>
</property>
</widget>
</item>
@@ -85,7 +85,7 @@
</attribute>
<column>
<property name="text">
<string>Zero-conf name</string>
<string>ZeroConf name</string>
</property>
</column>
<column>
@@ -98,7 +98,7 @@
<item row="2" column="0">
<widget class="QPushButton" name="btn_connect">
<property name="text">
<string>&amp;Connect to Revolution Pi</string>
<string>&amp;Connect to RevPi</string>
</property>
</widget>
</item>
@@ -132,7 +132,7 @@
</action>
<action name="act_open_pictory">
<property name="text">
<string>Open piCtory</string>
<string>Open PiCtory</string>
</property>
</action>
<action name="act_connect_ssh">
@@ -140,7 +140,7 @@
<string>Connect via SSH (recommended)</string>
</property>
<property name="toolTip">
<string>Establish a connection via encrypted SSH tunnel</string>
<string>Connect via an encrypted SSH tunnel.</string>
</property>
</action>
<action name="act_connect_xmlrpc">
@@ -148,7 +148,7 @@
<string>Connect via XML-RPC</string>
</property>
<property name="toolTip">
<string>You have to configure your Revolution Pi to accept this connections</string>
<string>Enable these connections on RevPi.</string>
</property>
</action>
<action name="act_connect">
@@ -156,7 +156,7 @@
<string>Connect</string>
</property>
<property name="toolTip">
<string>Connect to Revoluton Pi</string>
<string>Connect to RevPi</string>
</property>
</action>
</widget>
+1 -1
View File
@@ -17,7 +17,7 @@
<item>
<widget class="QLabel" name="lbl_status">
<property name="text">
<string notr="true">Status message...</string>
<string notr="true">Status message</string>
</property>
</widget>
</item>
+11 -11
View File
@@ -32,7 +32,7 @@
<item row="0" column="0">
<widget class="QGroupBox" name="gb_devices">
<property name="title">
<string>Revolution Pi devices</string>
<string>RevPi Devices</string>
</property>
<layout class="QVBoxLayout" name="vl_devices"/>
</widget>
@@ -40,25 +40,25 @@
<item row="1" column="0">
<widget class="QCheckBox" name="cbx_stay_on_top">
<property name="text">
<string>Open to stay on top</string>
<string>Keep window on top</string>
</property>
</widget>
</item>
<item row="0" column="1" rowspan="2">
<widget class="QGroupBox" name="gb_control">
<property name="title">
<string>IO Control</string>
<string>I/O Control</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="btn_read_io">
<property name="toolTip">
<string>Read all IO values and discard local changes (F4)
<string>Read all I/O values and discard local changes (F4).
Hold this button pressed and it will refresh the IOs every 200 ms.</string>
Hold this button to refresh the I/Os every 200 ms.</string>
</property>
<property name="text">
<string>Read &amp;all IO values</string>
<string>Read &amp;all I/O values</string>
</property>
<property name="autoRepeat">
<bool>true</bool>
@@ -74,12 +74,12 @@ Hold this button pressed and it will refresh the IOs every 200 ms.</string>
<item>
<widget class="QPushButton" name="btn_refresh_io">
<property name="toolTip">
<string>Refresh all IO values which are locally not changed (F5)
<string>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.</string>
Hold this button to refresh the I/Os every 200 ms.</string>
</property>
<property name="text">
<string>&amp;Refresh unchanged IOs</string>
<string>&amp;Refresh unchanged I/Os</string>
</property>
<property name="autoRepeat">
<bool>true</bool>
@@ -95,7 +95,7 @@ Hold this button pressed and it will refresh the IOs every 200 ms.</string>
<item>
<widget class="QPushButton" name="btn_write_o">
<property name="toolTip">
<string>Write locally changed output values to process image (F6)</string>
<string>Write locally modified output values to process image (F6).</string>
</property>
<property name="text">
<string>&amp;Write changed outputs</string>
@@ -112,7 +112,7 @@ Hold this button pressed and it will refresh the IOs every 200 ms.</string>
<item>
<widget class="QCheckBox" name="cbx_write">
<property name="text">
<string>and write outputs</string>
<string>Write outputs</string>
</property>
</widget>
</item>
+4 -4
View File
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>File manager</string>
<string>File Manager</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
@@ -28,7 +28,7 @@
<item>
<widget class="QGroupBox" name="gb_select_local">
<property name="title">
<string>Local computer</string>
<string>Local Computer</string>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="1,0,0">
<item row="0" column="0">
@@ -168,7 +168,7 @@
<item>
<widget class="QGroupBox" name="gb_select_revpi">
<property name="title">
<string>Revolution Pi</string>
<string>RevPi</string>
</property>
<layout class="QGridLayout" name="gridLayout_3" columnstretch="1,0">
<item row="1" column="0" colspan="2">
@@ -328,7 +328,7 @@
<item row="1" column="0">
<widget class="QPushButton" name="btn_all">
<property name="text">
<string>Stop - Upload - Start</string>
<string>Stop, upload, start</string>
</property>
</widget>
</item>
+3 -3
View File
@@ -29,7 +29,7 @@
</size>
</property>
<property name="text">
<string>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.
<string>Base topic is the prefix for MQTT topics published by RevPi. Use "/" to structure broker topics.
For example: revpi0000/data</string>
</property>
@@ -113,7 +113,7 @@ For example: revpi0000/data</string>
</size>
</property>
<property name="text">
<string>The Revolution Pi will subscribe a topic on which your mqtt client can publish messages with the new io value as payload.
<string>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]</string>
</property>
@@ -125,7 +125,7 @@ Publish values with topic: [basetopic]/set/[outputname]</string>
<item row="0" column="0">
<widget class="QCheckBox" name="cbx_write_outputs">
<property name="text">
<string>Allow MQTT to to set outputs on Revolution Pi</string>
<string>Allow MQTT to set outputs on RevPi</string>
</property>
</widget>
</item>
+3 -3
View File
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Open-Source licenses</string>
<string>Open source licenses</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
@@ -87,10 +87,10 @@
</layout>
<action name="action_start">
<property name="text">
<string>More licenses...</string>
<string>More licenses</string>
</property>
<property name="toolTip">
<string>Show more open-source software licenses</string>
<string>Show more open source software licenses</string>
</property>
</action>
</widget>
+5 -5
View File
@@ -210,7 +210,7 @@
</action>
<action name="act_search">
<property name="text">
<string>&amp;Search Revolution Pi...</string>
<string>&amp;Search RevPi...</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+F</string>
@@ -223,12 +223,12 @@
</action>
<action name="act_webpage">
<property name="text">
<string>Visit &amp;webpage...</string>
<string>Visit &amp;website</string>
</property>
</action>
<action name="act_info">
<property name="text">
<string>&amp;Info...</string>
<string>&amp;Info</string>
</property>
</action>
<action name="act_logs">
@@ -265,7 +265,7 @@
</action>
<action name="act_pictory">
<property name="text">
<string>piCtory configuraiton...</string>
<string>PiCtory configuration</string>
</property>
</action>
<action name="act_disconnect">
@@ -278,7 +278,7 @@
</action>
<action name="act_reset">
<property name="text">
<string>Reset driver...</string>
<string>Reset driver</string>
</property>
</action>
<action name="act_simulator">
+4 -5
View File
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Program information</string>
<string>Program Information</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="3">
@@ -24,7 +24,7 @@
</font>
</property>
<property name="text">
<string>RevPi Python PLC - Commander</string>
<string>RevPi Commander (Python/PLC)</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -114,8 +114,7 @@
</property>
<property name="text">
<string>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</string>
</property>
@@ -140,7 +139,7 @@ applicable law.
<item row="6" column="0" colspan="2">
<widget class="QLabel" name="lbl_link">
<property name="text">
<string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://revpimodio.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;https://revpimodio.org/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://revpimodio2.readthedocs.io/en/latest/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;https://revpimodio2.readthedocs.io/en/latest/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
+3 -3
View File
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>RevPi Python PLC Logfiles</string>
<string>RevPi Python PLC Log Files</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_3">
@@ -42,7 +42,7 @@
<item row="0" column="0">
<widget class="QLabel" name="lbl_daemon">
<property name="text">
<string>RevPiPyLoad - Logfile</string>
<string>RevPiPyLoad Log File</string>
</property>
</widget>
</item>
@@ -89,7 +89,7 @@
<item row="0" column="0">
<widget class="QLabel" name="lbl_app">
<property name="text">
<string>Python PLC program - Logfile</string>
<string>Python PLC Program Log File</string>
</property>
</widget>
</item>
+13 -13
View File
@@ -17,20 +17,20 @@
<item>
<widget class="QGroupBox" name="gb_plc">
<property name="title">
<string>Start / Stop behavior of PLC program</string>
<string>PLC Start/Stop Behavior</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="6" column="0">
<widget class="QLabel" name="lbl_replace_io">
<property name="text">
<string>Replace IO file:</string>
<string>Replace I/O file:</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QCheckBox" name="cbx_zeroonerror">
<property name="text">
<string>... after exception and errors</string>
<string>after exceptions or errors</string>
</property>
</widget>
</item>
@@ -50,7 +50,7 @@
</item>
<item>
<property name="text">
<string>Restart after piCtory changed</string>
<string>Restart after PiCtory changes</string>
</property>
</item>
<item>
@@ -70,7 +70,7 @@
<item row="3" column="0" colspan="3">
<widget class="QLabel" name="lbl_plc_zero">
<property name="text">
<string>Set process image to NULL if program terminates...</string>
<string>Set process image to NULL if the program terminates</string>
</property>
</widget>
</item>
@@ -78,7 +78,7 @@
<widget class="QComboBox" name="cbb_replace_io">
<item>
<property name="text">
<string>Do not use replace io file</string>
<string>Do not replace I/O file</string>
</property>
</item>
<item>
@@ -93,7 +93,7 @@
</item>
<item>
<property name="text">
<string>Give own path and filename</string>
<string>Use custom path and file name</string>
</property>
</item>
</widget>
@@ -101,7 +101,7 @@
<item row="4" column="1" colspan="2">
<widget class="QCheckBox" name="cbx_zeroonexit">
<property name="text">
<string>... sucessfully without error</string>
<string>without errors</string>
</property>
</widget>
</item>
@@ -135,7 +135,7 @@
<item row="9" column="0" colspan="3">
<widget class="QLabel" name="lbl_lbl_reset_driver_action">
<property name="text">
<string>PLC program behavior after piCtory driver reset clicked</string>
<string>PLC program behavior after PiCtory driver reset</string>
</property>
</widget>
</item>
@@ -145,7 +145,7 @@
<item>
<widget class="QGroupBox" name="gb_server">
<property name="title">
<string>RevPiPyLoad server services</string>
<string>RevPiPyLoad Server Services</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
@@ -165,7 +165,7 @@
<item row="0" column="0">
<widget class="QCheckBox" name="cbx_plcserver">
<property name="text">
<string>Start RevPi piControl server</string>
<string>Start piControl server</string>
</property>
</widget>
</item>
@@ -182,7 +182,7 @@
<item row="1" column="0">
<widget class="QLabel" name="lbl_lbl_server_status">
<property name="text">
<string>piControl server is:</string>
<string>piControl server:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
@@ -202,7 +202,7 @@
<item row="3" column="0">
<widget class="QLabel" name="lbl_lbl_mqtt_status">
<property name="text">
<string>MQTT publish service is:</string>
<string>MQTT publish service:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+21 -11
View File
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Revolution Pi connections</string>
<string>RevPi Connections</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="2">
@@ -37,21 +37,21 @@
<item row="1" column="0">
<widget class="QLabel" name="lbl_address">
<property name="text">
<string>Address (DNS/IP):</string>
<string>Address (DNS/IP/Socket):</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="txt_address"/>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QLabel" name="lbl_port">
<property name="text">
<string>Port (Default {0}):</string>
<string>Port (default {0}):</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="3" column="1">
<widget class="QSpinBox" name="sbx_port">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
@@ -70,14 +70,14 @@
</property>
</widget>
</item>
<item row="3" column="0">
<item row="4" column="0">
<widget class="QLabel" name="lbl_timeout">
<property name="text">
<string>Connection timeout:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="4" column="1">
<widget class="QSpinBox" name="sbx_timeout">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
@@ -86,7 +86,7 @@
</sizepolicy>
</property>
<property name="suffix">
<string> sec.</string>
<string> s</string>
</property>
<property name="minimum">
<number>5</number>
@@ -96,14 +96,14 @@
</property>
</widget>
</item>
<item row="4" column="0">
<item row="5" column="0">
<widget class="QLabel" name="lbl_folder">
<property name="text">
<string>Subfolder:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="5" column="1">
<widget class="QComboBox" name="cbb_folder">
<property name="editable">
<bool>true</bool>
@@ -118,6 +118,16 @@
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lbl_local_socket">
<property name="text">
<string>Use default local socket:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="cbx_local_socket"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_ssh">
@@ -194,7 +204,7 @@
</property>
<column>
<property name="text">
<string>Connection name</string>
<string>Connection Name</string>
</property>
</column>
<column>
+12 -12
View File
@@ -11,19 +11,19 @@
</rect>
</property>
<property name="windowTitle">
<string>PLC program</string>
<string>PLC Program</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="gb_plc">
<property name="title">
<string>PLC program</string>
<string>PLC Program</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<widget class="QLabel" name="lbl_plcarguments">
<property name="text">
<string>Program arguments:</string>
<string>Arguments:</string>
</property>
</widget>
</item>
@@ -37,7 +37,7 @@
<item row="3" column="0" colspan="3">
<widget class="QCheckBox" name="cbx_plcworkdir_set_uid">
<property name="text">
<string>Set write permissions for plc program to workdirectory</string>
<string>Set write permissions for PLC program working directory</string>
</property>
</widget>
</item>
@@ -57,7 +57,7 @@
<item row="4" column="2">
<widget class="QSpinBox" name="sbx_plcprogram_watchdog">
<property name="suffix">
<string> sec.</string>
<string> s</string>
</property>
<property name="maximum">
<number>600</number>
@@ -70,7 +70,7 @@
<item>
<widget class="QGroupBox" name="cb_transfair">
<property name="title">
<string>Transfair PLC program</string>
<string>Transfer PLC program</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
@@ -104,21 +104,21 @@
<item row="0" column="0">
<widget class="QLabel" name="lbl_format">
<property name="text">
<string>Transfair format:</string>
<string>Transfer format:</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="cbx_pictory">
<property name="text">
<string>Including piCtory configuration</string>
<string>Include PiCtory configuration</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="cbx_clear">
<property name="text">
<string>Remove all files on Revolution Pi before upload</string>
<string>Remove all files on RevPi before upload</string>
</property>
</widget>
</item>
@@ -128,7 +128,7 @@
<item>
<widget class="QGroupBox" name="gb_control">
<property name="title">
<string>Control files</string>
<string>Control Files</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="1">
@@ -155,14 +155,14 @@
<item row="0" column="0">
<widget class="QLabel" name="lbl_pictory">
<property name="text">
<string>piCtory configuraiton</string>
<string>PiCtory configuration</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lbl_procimg">
<property name="text">
<string>Process image from piControl0</string>
<string>Process image from piControl</string>
</property>
</widget>
</item>
+10 -10
View File
@@ -11,13 +11,13 @@
</rect>
</property>
<property name="windowTitle">
<string>piControl simulator</string>
<string>piControl Simulator</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="gb_settings">
<property name="title">
<string>Simulator settings</string>
<string>Simulator Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
@@ -30,7 +30,7 @@
<item row="1" column="0">
<widget class="QLabel" name="lbl_configrsc">
<property name="text">
<string>piCtory file:</string>
<string>PiCtory file:</string>
</property>
</widget>
</item>
@@ -47,7 +47,7 @@
<item row="1" column="2">
<widget class="QPushButton" name="btn_configrsc">
<property name="text">
<string>select...</string>
<string>Select</string>
</property>
</widget>
</item>
@@ -99,7 +99,7 @@
<item row="4" column="1" colspan="2">
<widget class="QRadioButton" name="rb_restart_pictory">
<property name="text">
<string>Restore piCtory default values</string>
<string>Restore PiCtory default values</string>
</property>
<property name="checked">
<bool>true</bool>
@@ -109,7 +109,7 @@
<item row="5" column="1" colspan="2">
<widget class="QRadioButton" name="rb_restart_zero">
<property name="text">
<string>Reset everything to ZERO</string>
<string>Reset all values to NULL</string>
</property>
</widget>
</item>
@@ -119,13 +119,13 @@
<item>
<widget class="QGroupBox" name="gb_info">
<property name="title">
<string>RevPiModIO integration</string>
<string>RevPiModIO Integration</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="lbl_info">
<property name="text">
<string>You can work with this simulator if you call RevPiModIO with this additional parameters:</string>
<string>To use this simulator, call RevPiModIO with the following additional parameters:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
@@ -151,7 +151,7 @@
<item>
<widget class="QPushButton" name="btn_start_pictory">
<property name="text">
<string>Start with piCtory default values</string>
<string>Start with PiCtory default values</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+1</string>
@@ -171,7 +171,7 @@
<item>
<widget class="QPushButton" name="btn_start_nochange">
<property name="text">
<string>Start without changing actual process image</string>
<string>Start without changing the current process image</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+3</string>
+2 -2
View File
@@ -53,7 +53,7 @@
<item>
<widget class="QCheckBox" name="cbx_save_password">
<property name="toolTip">
<string>Username and password will be saved in secured operating systems's password storage.</string>
<string>Save user name and password in secure password storage.</string>
</property>
<property name="text">
<string>Save user name and password</string>
@@ -79,7 +79,7 @@
</sizepolicy>
</property>
<property name="text">
<string>Note: The default user for SSH is &quot;pi&quot; which differs from the web configuration. You can find the password on the sticker on the device.</string>
<string>Default SSH user is &quot;pi&quot;. The device password is on the RevPi housing sticker.</string>
</property>
<property name="wordWrap">
<bool>true</bool>