30 Commits

Author SHA1 Message Date
bfe30513a7 Update Windows setup script to match pyinstaller output 2023-01-07 14:24:10 +01:00
09c95e2319 Update translation file 2023-01-07 13:57:45 +01:00
2c77d7e513 Update Makefile rules 2023-01-07 11:15:39 +01:00
d1a0457981 Choose connection type (ssh or xml-rpc) in avahisearch dialog 2023-01-07 11:12:58 +01:00
0c7192e1d4 Configure and start ssh tunneled connections 2023-01-07 10:36:53 +01:00
2f595f66aa Prepare SSH tunnel server and password GUI 2023-01-06 17:59:24 +01:00
da5d0a4a59 Use cm.call_remote_function to fetch log files via XML-RPC 2023-01-06 17:05:12 +01:00
dee2da0d8c QTranslator uses ui_languages to find translation
Loading translations was very static, now we use the array, the user
set in the gui to find a translation file.
2023-01-06 13:56:33 +01:00
d0e6eefd0e Add PyInstaller script for Windows 2023-01-05 15:55:30 +01:00
7f9111b5b0 Update Makefile and requirements.txt for build and clean ui files 2023-01-04 22:26:47 +01:00
2ac1477441 Add icons to commander main window for start, stop and so on 2023-01-04 21:43:20 +01:00
a557759063 Switch to setuptools for setup.py 2023-01-04 19:59:47 +01:00
3f2f3e0478 Switch project so src layout
This will make it possible to build pip packages.
Add a make file to compile UI files and build mac and win applications.
2023-01-04 18:12:59 +01:00
2400bd6951 Add context menu with copy and piCtory actions to revpi search dialog 2023-01-03 13:45:32 +01:00
9ec1f1b622 Bugfix: could not open debugcontrol, because of new layout names 2023-01-03 13:24:13 +01:00
0eb28f442e Add translation of status codes 2023-01-03 11:33:13 +01:00
375df9eaba Redesign main window with text boxes to copy host name or ip 2023-01-03 11:08:32 +01:00
0a7eb43d94 Implement update function of ZeroConf ServiceListener 2023-01-03 11:07:11 +01:00
03567dfefe Improved shortcuts to meet the needs of different operating systems 2022-12-01 10:06:04 +01:00
cef990df26 Fix qt parents of menu entries in connections 2022-12-01 09:57:04 +01:00
42f43726f6 Add Windows files for pyinstaller and setup generation 2022-11-01 14:52:58 +01:00
0164498a87 Design things: enable HighDpi, dependency versions, icon text 2021-11-19 10:33:13 +01:00
ea9a13e18c Bugfix: RevPi simulator could not be startet without existing proc.img file
The simulator will create a new proc.img file on startup if it does not exist.
2021-06-27 11:45:01 +02:00
0831e8253a Fix error in debugios.py with long byte values
Long byte values > 4 Bytes are handled as TEXT values. In some cases this could crash the sps monitor. Now, if we are not able to convert the bytes to a str(), we'll switch that io to "number" format.
2021-06-14 19:51:14 +02:00
c1e082107d New release 2021-06-13 21:32:47 +02:00
f215b3bbc1 Improved RevPi search window
Show names of already saved connections in parentheses
Show connection error message if we can not connect to RevPi
Save and restore geometry and column width
2021-06-13 21:30:59 +02:00
1354568eb6 Reduce max block to load for log files on slow networks 2021-06-13 20:35:35 +02:00
6bf453790a Fix crash during add new connection 2021-06-04 15:41:43 +02:00
61d543e6c7 Fix error on decoding broken log files 2021-06-03 21:14:18 +02:00
4eec6306a9 Add upload progress display in developer, bugfix on file download in developer 2021-04-26 12:07:47 +02:00
91 changed files with 6788 additions and 3590 deletions

4
.idea/misc.xml generated
View File

@@ -4,9 +4,9 @@
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectPlainTextFileTypeManager">
<file url="file://$PROJECT_DIR$/revpicommander/locale/revpicommander_de.ts" />
<file url="file://$PROJECT_DIR$/src/revpicommander/locale/revpicommander_de.ts" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (revpicommander)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>

View File

@@ -2,15 +2,13 @@
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/include" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/revpicommander" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/lib/revpimodio2" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.8" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.9 (revpicommander)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PackageRequirementsSettings">
<option name="versionSpecifier" value="Greater or equal (&gt;=x.y.z)" />
<option name="removeUnused" value="true" />
</component>
</module>

7
.idea/vcs.xml generated
View File

@@ -1,5 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CommitMessageInspectionProfile">
<profile version="1.0">
<inspection_tool class="BodyLimit" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SubjectBodySeparation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SubjectLimit" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>

View File

@@ -1,8 +1,13 @@
include MANIFEST.in
include stdeb.cfg
recursive-include data *
recursive-include include *.py
recursive-include lib *.py
recursive-include revpicommander *.py *.qm
global-exclude *.pyc
recursive-include src/revpicommander *.py *.qm
recursive-include ui_dev *
include LICENSE.txt
include make_installer_win.bat
include Makefile
include MANIFEST.in
include README.md
include requirements.txt
include setup.iss
include setup.py
include stdeb.cfg
include translate.pro

83
Makefile Normal file
View File

@@ -0,0 +1,83 @@
SHELL := bash
MAKEFLAGS = --no-print-directory --no-builtin-rules
.DEFAULT_GOAL = all
# Variables
PACKAGE = revpicommander
# If virtualenv exists, use it. If not, use PATH to find
SYSTEM_PYTHON = $(or $(shell which python3), $(shell which python))
PYTHON = $(or $(wildcard venv/bin/python), $(SYSTEM_PYTHON))
SYSTEM_PYUIC5 = $(shell which pyuic5)
PYUIC5 = $(or $(wildcard venv/bin/pyuic5), $(SYSTEM_PYUIC5))
SYSTEM_PYRCC5 = $(shell which pyrcc5)
PYRCC5 = $(or $(wildcard venv/bin/pyrcc5), $(SYSTEM_PYRCC5))
SYSTEM_PYLUP5 = $(shell which pylupdate5)
PYLUP5 = $(or $(wildcard venv/bin/pylupdate5), $(SYSTEM_PYLUP5))
all: build_ui build_rc build
.PHONY: all
## Compile Qt UI files to python code
build_ui:
cd ui_dev && for ui_file in *.ui; do \
file_name=$${ui_file%.ui}; \
$(PYUIC5) $${ui_file} -o ../src/$(PACKAGE)/ui/$${file_name}_ui.py -x --from-imports; \
echo $${file_name}; \
done
build_rc:
cd ui_dev && for rc_file in *.qrc; do \
file_name=$${rc_file%.qrc}; \
$(PYRCC5) $${rc_file} -o ../src/$(PACKAGE)/ui/$${file_name}_rc.py; \
echo $${file_name}; \
done
update_translation:
$(PYLUP5) translate.pro
.PHONY: build_ui build_rc update_translation
## Environment
venv:
rm -rf venv
$(SYSTEM_PYTHON) -m venv venv
deps:
$(PYTHON) -m pip install --upgrade pip -r requirements.txt
.PHONY: venv deps
## Build, install
build: build_ui build_rc
$(PYTHON) -m setup sdist
$(PYTHON) -m setup bdist_wheel
install:
$(PYTHON) -m pip install dist/$(PACKAGE)-*.whl
.PHONY: build install
## PyInstaller
installer_mac: build
$(PYTHON) -m PyInstaller -n "RevPi Commander" \
--add-data="src/$(PACKAGE)/locale:./revpicommander/locale" \
--add-data="data/$(PACKAGE).icns:." \
--icon=data/$(PACKAGE).icns \
--noconfirm \
--clean \
--onedir \
--windowed \
src/$(PACKAGE)/__main__.py
installer_win: all
make_installer_win.bat
.PHONY: installer_mac installer_win
## Clean
clean:
rm -rf build dist src/*.egg-info *.spec
.PHONY: clean

View File

@@ -1,7 +1,6 @@
[Desktop Entry]
Name=RevPi PLC Commander
Comment=Controls the Python PLC program on your Revolution PI
Name[de]=RevPi PLC Steuerung
Comment[de]=Kontrolliert das Python PLC Programm auf dem Revolution PI
Exec=/usr/bin/revpicommander
Icon=revpicommander

BIN
data/revpicommander.icns Normal file

Binary file not shown.

View File

@@ -1,244 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>diag_connections</class>
<widget class="QDialog" name="diag_connections">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>520</width>
<height>508</height>
</rect>
</property>
<property name="windowTitle">
<string>Revolution Pi connections</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTreeWidget" name="tre_connections">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<column>
<property name="text">
<string>Connection name</string>
</property>
</column>
<column>
<property name="text">
<string>Address</string>
</property>
</column>
</widget>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="vl_edit">
<item>
<spacer name="vs_edit">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_up">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="ressources.qrc">
<normaloff>:/action/ico/arrow-up.ico</normaloff>:/action/ico/arrow-up.ico</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_down">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="ressources.qrc">
<normaloff>:/action/ico/arrow-down.ico</normaloff>:/action/ico/arrow-down.ico</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_delete">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="ressources.qrc">
<normaloff>:/action/ico/edit-delete.ico</normaloff>:/action/ico/edit-delete.ico</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_add">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="ressources.qrc">
<normaloff>:/action/ico/edit-add.ico</normaloff>:/action/ico/edit-add.ico</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="gb_properties">
<property name="title">
<string>Connection properties</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="lbl_name">
<property name="text">
<string>Display name:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="lbl_folder">
<property name="text">
<string>Sub folder:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lbl_address">
<property name="text">
<string>Address (DNS/IP):</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lbl_port">
<property name="text">
<string>Port (Default {0}):</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="txt_name"/>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="txt_address"/>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="sbx_port">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>55123</number>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="cbb_folder">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string/>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="lbl_timeout">
<property name="text">
<string>Connection timeout:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="sbx_timeout">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string> sec.</string>
</property>
<property name="minimum">
<number>5</number>
</property>
<property name="maximum">
<number>30</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QDialogButtonBox" name="btn_box">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Discard|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="ressources.qrc"/>
</resources>
<connections>
<connection>
<sender>btn_box</sender>
<signal>accepted()</signal>
<receiver>diag_connections</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>btn_box</sender>
<signal>rejected()</signal>
<receiver>diag_connections</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

18
make_installer_win.bat Normal file
View File

@@ -0,0 +1,18 @@
@echo off
set PACKAGE=revpicommander
rem python -m venv venv
rem venv\\Scripts\\activate.bat
rem pip install -r requirements.txt
pyinstaller -n "RevPi Commander" ^
--add-data="src\%PACKAGE%\locale;.\revpicommander\locale" ^
--add-data="data\%PACKAGE%.ico;." ^
--icon=data\\%PACKAGE%.ico ^
--noconfirm ^
--clean ^
--onedir ^
--windowed ^
src\\%PACKAGE%\\__main__.py
rem deactivate

View File

@@ -1,3 +1,7 @@
Pyinstaller
PyQt5>=5.14.1
revpimodio2>=2.5.6
zeroconf>=0.24.4
zeroconf>=0.24.4
setuptools>=65.6.3
wheel
paramiko>=2.12.0

View File

@@ -1,235 +0,0 @@
# -*- coding: utf-8 -*-
"""Revolution Pi search with zeroconf."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2020 Sven Sager"
__license__ = "GPLv3"
from os import name as osname
from re import compile
from PyQt5 import QtCore, QtGui, QtWidgets
from zeroconf import IPVersion, ServiceBrowser, Zeroconf
import helper
import proginit as pi
from helper import WidgetData
from ui.avahisearch_ui import Ui_diag_search
class AvahiSearchThread(QtCore.QThread):
"""Search thread for Revolution Pi with installed RevPiPyLoad."""
added = QtCore.pyqtSignal(str, str, int, str, str)
removed = QtCore.pyqtSignal(str, str)
def __init__(self, parent=None):
super(AvahiSearchThread, self).__init__(parent)
self._cycle_wait_ms = 1000
self.__dict_arp = {}
self.re_posix = compile(
r"(?P<ip>(\d{1,3}\.){3}\d{1,3}).*"
r"(?P<mac>([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})"
)
def _update_arp(self):
"""Find mac address in arp table."""
if osname == "posix":
with open("/proc/net/arp") as fh:
for line in fh.readlines():
ip_mac = self.re_posix.search(line)
if ip_mac:
self.__dict_arp[ip_mac.group("ip")] = ip_mac.group("mac")
def get_mac(self, ip: str):
"""
Get mac address of ip, if known.
:param ip: IP address to find mac address
:return: MAC address as string or empty string, if unknown
"""
return self.__dict_arp.get(ip, "")
def remove_service(self, zeroconf: Zeroconf, conf_type: str, name: str):
"""Revolution Pi disappeared."""
pi.logger.debug("AvahiSearchThread.remove_service")
self.removed.emit(name, conf_type)
def add_service(self, zeroconf: Zeroconf, conf_type: str, name: str):
"""New Revolution Pi found."""
pi.logger.debug("AvahiSearchThread.add_service")
info = zeroconf.get_service_info(conf_type, name)
if not info:
return
for ip in info.parsed_addresses(IPVersion.V4Only):
self.added.emit(name, info.server, info.port, conf_type, ip)
def run(self):
pi.logger.debug("Started zero conf discovery.")
zeroconf = Zeroconf()
revpi_browser = ServiceBrowser(zeroconf, "_revpipyload._tcp.local.", self)
while not self.isInterruptionRequested():
# Just hanging around :)
self.msleep(self._cycle_wait_ms)
zeroconf.close()
pi.logger.debug("Stopped zero conf discovery.")
class AvahiSearch(QtWidgets.QDialog, Ui_diag_search):
def __init__(self, parent=None):
super(AvahiSearch, self).__init__(parent)
self.setupUi(self)
self.setFixedSize(self.size())
self.connect_index = -1
self.th_zero_conf = AvahiSearchThread(self)
self.tb_revpi.setColumnWidth(0, 250)
self.btn_connect.setEnabled(False)
self.btn_save.setEnabled(False)
def _restart_search(self):
"""Clean up an restart search thread."""
while self.tb_revpi.rowCount() > 0:
self.tb_revpi.removeRow(0)
self.th_zero_conf.requestInterruption()
self.th_zero_conf = AvahiSearchThread(self)
self.th_zero_conf.added.connect(self.on_avahi_added)
self.th_zero_conf.removed.connect(self.on_avahi_removed)
self.th_zero_conf.start()
def _save_connection(self, row: int, no_warn=False):
"""
Save the connection from given row to settings.
:param row: Row with connection data
:param no_warn: If True, no message boxes will appear
:return: Array index of connection (found or saved)
"""
item = self.tb_revpi.item(row, 0)
if not item:
return
folder_name = self.tr("Auto discovered")
selected_name = item.text()
selected_address = item.data(WidgetData.address)
selected_port = item.data(WidgetData.port)
i = 0
for i in range(helper.settings.beginReadArray("connections")):
helper.settings.setArrayIndex(i)
name = helper.settings.value("name", type=str)
address = helper.settings.value("address", type=str)
port = helper.settings.value("port", type=int)
if address.lower() == selected_address.lower() and port == selected_port:
if not no_warn:
QtWidgets.QMessageBox.information(
self, self.tr("Already in list..."), self.tr(
"The selected Revolution Pi is already saved in your "
"connection list as '{0}'."
).format(name)
)
helper.settings.endArray()
return i
helper.settings.endArray()
helper.settings.beginWriteArray("connections")
helper.settings.setArrayIndex(i + 1)
helper.settings.setValue("address", selected_address)
helper.settings.setValue("folder", folder_name)
helper.settings.setValue("name", selected_name)
helper.settings.setValue("port", selected_port)
helper.settings.endArray()
if not no_warn:
QtWidgets.QMessageBox.information(
self, self.tr("Success"), self.tr(
"The connection with the name '{0}' was successfully saved "
"to folder '{1}' in your connections."
).format(selected_name, folder_name)
)
return i + 1
def exec(self) -> int:
self._restart_search()
rc = super(AvahiSearch, self).exec()
self.th_zero_conf.requestInterruption()
return rc
@QtCore.pyqtSlot(str, str, int, str, str)
def on_avahi_added(self, name: str, server: str, port: int, conf_type: str, ip: str):
"""New Revolution Pi found."""
index = -1
for i in range(self.tb_revpi.rowCount()):
if self.tb_revpi.item(i, 0).data(WidgetData.object_name) == name:
index = i
break
if index == -1:
# New Row
item_name = QtWidgets.QTableWidgetItem()
item_ip = QtWidgets.QTableWidgetItem()
index = self.tb_revpi.rowCount()
self.tb_revpi.insertRow(index)
self.tb_revpi.setItem(index, 0, item_name)
self.tb_revpi.setItem(index, 1, item_ip)
else:
# Update row
item_name = self.tb_revpi.item(index, 0)
item_ip = self.tb_revpi.item(index, 1)
item_name.setIcon(QtGui.QIcon(":/main/ico/cpu.ico"))
item_name.setText(server[:-1])
item_name.setData(WidgetData.object_name, name)
item_name.setData(WidgetData.address, ip)
item_name.setData(WidgetData.port, port)
item_ip.setText(ip)
@QtCore.pyqtSlot(str, str)
def on_avahi_removed(self, name: str, conf_type: str):
"""Revolution Pi disappeared."""
for i in range(self.tb_revpi.rowCount()):
if self.tb_revpi.item(i, 0).data(WidgetData.object_name) == name:
self.tb_revpi.removeRow(i)
break
@QtCore.pyqtSlot(int, int)
def on_tb_revpi_cellDoubleClicked(self, row: int, column: int):
"""Connect to double clicked Revolution Pi."""
pi.logger.debug("AvahiSearch.on_tb_revpi_cellDoubleClicked")
self.connect_index = self._save_connection(row, no_warn=True)
self.accept()
@QtCore.pyqtSlot(int, int, int, int)
def on_tb_revpi_currentCellChanged(self, row: int, column: int, last_row: int, last_column: int):
"""Manage state of buttons."""
self.btn_connect.setEnabled(row >= 0)
self.btn_save.setEnabled(row >= 0)
@QtCore.pyqtSlot()
def on_btn_connect_pressed(self):
"""Connect to selected Revolution Pi."""
pi.logger.debug("AvahiSearch.on_btn_connect_pressed")
if self.tb_revpi.currentRow() == -1:
return
self.connect_index = self._save_connection(self.tb_revpi.currentRow(), no_warn=True)
self.accept()
@QtCore.pyqtSlot()
def on_btn_save_pressed(self):
"""Save selected Revolution Pi."""
pi.logger.debug("AvahiSearch.on_btn_save_pressed")
if self.tb_revpi.currentRow() == -1:
return
self.connect_index = self._save_connection(self.tb_revpi.currentRow())
@QtCore.pyqtSlot()
def on_btn_restart_pressed(self):
"""Clean up an restart search thread."""
self._restart_search()

48
setup.iss Executable file
View File

@@ -0,0 +1,48 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "RevPi Commander"
#define MyAppVersion "0.9.10rc1"
#define MyAppPublisher "Sven Sager"
#define MyAppURL "https://revpimodio.org/"
#define MyAppICO "data\revpicommander.ico"
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{21E8D429-0C18-462F-AFC0-56EA664DE629}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={autopf}\{#MyAppName}
DisableProgramGroupPage=yes
LicenseFile=LICENSE.txt
; Uncomment the following line to run in non administrative install mode (install for current user only.)
;PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
OutputDir=dist
OutputBaseFilename={#MyAppName} {#MyAppVersion}
SetupIconFile={#MyAppICO}
Compression=lzma
SolidCompression=yes
WizardStyle=modern
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "german"; MessagesFile: "compiler:Languages\German.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "dist\{#MyAppName}\{#MyAppName}.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "dist\{#MyAppName}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppName}.exe"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppName}.exe"; Tasks: desktopicon

View File

@@ -1,55 +1,46 @@
# -*- coding: utf-8 -*-
"""Setupscript fuer RevPiPyLoad."""
"""Setupscript for RevPiCommander."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__license__ = "LGPLv3"
import distutils.command.install_egg_info
from distutils.core import setup
from glob import glob
class MyEggInfo(distutils.command.install_egg_info.install_egg_info):
u"""Disable egg_info installation, seems pointless for a non-library."""
def run(self):
u"""just pass egg_info."""
pass
__license__ = "GPLv3"
from setuptools import find_namespace_packages, setup
setup(
version="0.9.2",
python_requires="~=3.4",
requires=["PyQt5", "revpimodio2", "zeroconf"],
scripts=["data/revpicommander"],
data_files=[
("share/applications", ["data/revpicommander.desktop"]),
("share/icons/hicolor/32x32/apps", ["data/revpicommander.png"]),
("share/revpicommander", glob("revpicommander/*.py")),
("share/revpicommander/ui", glob("include/ui/*.py")),
("share/revpicommander/locale/", glob("revpicommander/locale/*.qm")),
],
# Additional meta-data
name="revpicommander",
version="0.9.10rc1",
packages=find_namespace_packages("src"),
package_dir={'': 'src'},
include_package_data=True,
install_requires=[
"PyQt5",
"revpimodio2",
"zeroconf"
],
entry_points={
'console_scripts': [
'revpicommander = revpicommander.revpicommander:main',
],
'gui_scripts': [
'RevPiCommander = revpicommander.revpicommander:main',
],
},
url="https://revpimodio.org/revpipyplc/",
license="GPLv3",
author="Sven Sager",
author_email="akira@narux.de",
maintainer="Sven Sager",
maintainer_email="akira@revpimodio.org",
url="https://revpimodio.org/revpipyplc/",
description="GUI for Revolution Pi to upload programs and do IO-Checks",
long_description=""
"The RevPiCommander is a GUI tool to manage your revolution Pi over the\n"
"network. You can search for RevPis in your network, manage the settings\n"
"of RevPiPyLoad and do IO checks on your local machine. Developing your\n"
"control program is very easy with the developer, upload and debug it\n"
"over the network.",
long_description="The RevPiCommander is a GUI tool to manage your revolution Pi over the\n"
"network. You can search for RevPis in your network, manage the settings\n"
"of RevPiPyLoad and do IO checks on your local machine. Developing your\n"
"control program is very easy with the developer, upload and debug it\n"
"over the network.",
classifiers=[
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: POSIX :: Linux",
],
license="GPLv3",
cmdclass={"install_egg_info": MyEggInfo},
)

View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
"""Package: RevPiCommander."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
"""Start main application of this package."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
# If we are running from a wheel, add the wheel to sys.path
if __package__ == "":
from os.path import dirname
from sys import path
# __file__ is package-*.whl/package/__main__.py
# Resulting path is the name of the wheel itself
package_path = dirname(dirname(__file__))
path.insert(0, package_path)
if __name__ == "__main__":
import sys
from revpicommander.revpicommander import main
# Run the main application of this package
sys.exit(main())

View File

@@ -1,15 +1,15 @@
# -*- coding: utf-8 -*-
"""Manager for ACL lists."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
from re import compile
from PyQt5 import QtCore, QtGui, QtWidgets
from helper import WidgetData
from ui.aclmanager_ui import Ui_diag_aclmanager
from .helper import WidgetData
from .ui.aclmanager_ui import Ui_diag_aclmanager
class AclManager(QtWidgets.QDialog, Ui_diag_aclmanager):

View File

@@ -0,0 +1,405 @@
# -*- coding: utf-8 -*-
"""Revolution Pi search with zeroconf."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
import webbrowser
from os import name as osname
from re import compile
from sys import platform
from PyQt5 import QtCore, QtGui, QtWidgets
from zeroconf import IPVersion, ServiceBrowser, Zeroconf
from . import proginit as pi
from .helper import WidgetData, settings
from .ui.avahisearch_ui import Ui_diag_search
def find_settings_index(address: str, port: int) -> int:
"""
Search index of saved settings.
:param address: Host or IP address of Revolution Pi
:param port: Port to connect
:return: Index of settings array or -1, if not found
"""
settings_index = -1
for i in range(settings.beginReadArray("connections")):
settings.setArrayIndex(i)
_address = settings.value("address", type=str)
_port = settings.value("port", type=int)
if address.lower() == _address.lower() and port == _port:
settings_index = i
break
settings.endArray()
return settings_index
class AvahiSearchThread(QtCore.QThread):
"""Search thread for Revolution Pi with installed RevPiPyLoad."""
added = QtCore.pyqtSignal(str, str, int, str, str)
removed = QtCore.pyqtSignal(str, str)
updated = QtCore.pyqtSignal(str, str, int, str, str)
def __init__(self, parent=None):
super(AvahiSearchThread, self).__init__(parent)
self._cycle_wait_ms = 1000
self.__dict_arp = {}
self.re_posix = compile(
r"(?P<ip>(\d{1,3}\.){3}\d{1,3}).*"
r"(?P<mac>([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})"
)
def _update_arp(self) -> None:
"""Find mac address in arp table."""
if osname == "posix":
with open("/proc/net/arp") as fh:
for line in fh.readlines():
ip_mac = self.re_posix.search(line)
if ip_mac:
self.__dict_arp[ip_mac.group("ip")] = ip_mac.group("mac")
def get_mac(self, ip: str) -> dict:
"""
Get mac address of ip, if known.
:param ip: IP address to find mac address
:return: MAC address as string or empty string, if unknown
"""
return self.__dict_arp.get(ip, "")
def remove_service(self, zeroconf: Zeroconf, conf_type: str, name: str) -> None:
"""Revolution Pi disappeared."""
pi.logger.debug("AvahiSearchThread.remove_service")
self.removed.emit(name, conf_type)
def add_service(self, zeroconf: Zeroconf, conf_type: str, name: str) -> None:
"""New Revolution Pi found."""
pi.logger.debug("AvahiSearchThread.add_service")
info = zeroconf.get_service_info(conf_type, name)
if not info:
return
for ip in info.parsed_addresses(IPVersion.V4Only):
self.added.emit(name, info.server, info.port, conf_type, ip)
def update_service(self, zeroconf: Zeroconf, conf_type: str, name: str) -> None:
"""New data of revolution pi"""
pi.logger.debug("AvahiSearchThread.add_service")
info = zeroconf.get_service_info(conf_type, name)
if not info:
return
for ip in info.parsed_addresses(IPVersion.V4Only):
self.updated.emit(name, info.server, info.port, conf_type, ip)
def run(self) -> None:
pi.logger.debug("Started zero conf discovery.")
zeroconf = Zeroconf()
revpi_browser = ServiceBrowser(zeroconf, "_revpipyload._tcp.local.", self)
while not self.isInterruptionRequested():
# Just hanging around :)
self.msleep(self._cycle_wait_ms)
zeroconf.close()
pi.logger.debug("Stopped zero conf discovery.")
class AvahiSearch(QtWidgets.QDialog, Ui_diag_search):
def __init__(self, parent=None):
super(AvahiSearch, self).__init__(parent)
self.setupUi(self)
self.clipboard = QtGui.QGuiApplication.clipboard()
self.connect_index = -1
self.known_hosts = {}
self.th_zero_conf = AvahiSearchThread(self)
self.tb_revpi.setColumnWidth(0, 250)
self.btn_connect.setEnabled(False)
self.btn_save.setEnabled(False)
self.restoreGeometry(settings.value("avahisearch/geo", b''))
column_sizes = settings.value("avahisearch/column_sizes", [], type=list)
if len(column_sizes) == self.tb_revpi.columnCount():
for i in range(self.tb_revpi.columnCount()):
self.tb_revpi.setColumnWidth(i, int(column_sizes[i]))
# Global context menus
self.cm_connect_actions = QtWidgets.QMenu(self)
self.cm_connect_actions.addAction(self.act_connect_ssh)
self.cm_connect_actions.addAction(self.act_connect_xmlrpc)
self.cm_quick_actions = QtWidgets.QMenu(self)
self.cm_quick_actions.addAction(self.act_open_pictory)
self.cm_quick_actions.addSeparator()
self.cm_quick_actions.addAction(self.act_copy_ip)
self.cm_quick_actions.addAction(self.act_copy_host)
self.cm_quick_actions.addSeparator()
self.cm_quick_actions.addAction(self.act_connect_ssh)
self.cm_quick_actions.addAction(self.act_connect_xmlrpc)
self.tb_revpi.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.tb_revpi.customContextMenuRequested.connect(self._cm_quick_exec)
@QtCore.pyqtSlot(QtCore.QPoint)
def _cm_connect_exec(self, position: QtCore.QPoint) -> None:
row = self.tb_revpi.currentRow()
if row == -1:
return
item = self.tb_revpi.item(row, 0)
settings_index = find_settings_index(item.data(WidgetData.address), item.data(WidgetData.port))
if settings_index >= 0:
self.connect_index = settings_index
self.accept()
return
action = self.cm_connect_actions.exec(position)
if action:
action.trigger()
@QtCore.pyqtSlot(QtCore.QPoint)
def _cm_quick_exec(self, position: QtCore.QPoint) -> None:
if self.tb_revpi.currentItem() is None:
return
sender = self.sender()
action = self.cm_quick_actions.exec(sender.mapToGlobal(position))
if action:
action.trigger()
def _load_known_hosts(self) -> None:
"""Load existing connections to show hostname of existing ip addresses"""
self.known_hosts.clear()
for i in range(settings.beginReadArray("connections")):
settings.setArrayIndex(i)
name = settings.value("name", type=str)
folder = settings.value("folder", type=str)
address = settings.value("address", type=str)
self.known_hosts[address] = "{0}/{1}".format(folder, name) if folder else name
settings.endArray()
def _restart_search(self) -> None:
"""Clean up and restart search thread."""
while self.tb_revpi.rowCount() > 0:
self.tb_revpi.removeRow(0)
self.th_zero_conf.requestInterruption()
self.th_zero_conf = AvahiSearchThread(self)
self.th_zero_conf.added.connect(self.on_avahi_added)
self.th_zero_conf.updated.connect(self.on_avahi_added)
self.th_zero_conf.removed.connect(self.on_avahi_removed)
self.th_zero_conf.start()
def _save_connection(self, row: int, ssh_tunnel: bool, no_warn=False) -> int:
"""
Save the connection from given row to settings.
:param row: Row with connection data
:param ssh_tunnel: Save as SSH tunnel connection
:param no_warn: If True, no message boxes will appear
:return: Array index of connection (found or saved) or -1
"""
item = self.tb_revpi.item(row, 0)
if not item:
return -1
folder_name = self.tr("Auto discovered")
selected_name = item.text()
selected_address = item.data(WidgetData.address)
selected_port = item.data(WidgetData.port)
i = 0
for i in range(settings.beginReadArray("connections")):
settings.setArrayIndex(i)
name = settings.value("name", type=str)
address = settings.value("address", type=str)
port = settings.value("port", type=int)
if address.lower() == selected_address.lower() and port == selected_port:
if not no_warn:
QtWidgets.QMessageBox.information(
self, self.tr("Already in list..."), self.tr(
"The selected Revolution Pi is already saved in your "
"connection list as '{0}'."
).format(name)
)
settings.endArray()
return i
settings.endArray()
settings.beginWriteArray("connections")
settings.setArrayIndex(i + 1)
settings.setValue("address", selected_address)
settings.setValue("folder", folder_name)
settings.setValue("name", selected_name)
settings.setValue("port", selected_port)
settings.setValue("ssh_use_tunnel", ssh_tunnel)
settings.setValue("ssh_port", 22)
settings.setValue("ssh_user", "pi")
settings.endArray()
if not no_warn:
QtWidgets.QMessageBox.information(
self, self.tr("Success"), self.tr(
"The connection with the name '{0}' was successfully saved "
"to folder '{1}' in your connections."
).format(selected_name, folder_name)
)
return i + 1
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
settings.setValue("avahisearch/geo", self.saveGeometry())
settings.setValue("avahisearch/column_sizes", [
self.tb_revpi.columnWidth(i)
for i in range(self.tb_revpi.columnCount())
])
def exec(self) -> int:
self._load_known_hosts()
self._restart_search()
rc = super(AvahiSearch, self).exec()
self.th_zero_conf.requestInterruption()
return rc
@QtCore.pyqtSlot()
def on_act_connect_ssh_triggered(self) -> None:
"""Copy ip address of selected item to clipboard."""
pi.logger.debug("AvahiSearch.on_act_connect_ssh_triggered")
if self.tb_revpi.currentRow() == -1:
return
self.connect_index = self._save_connection(self.tb_revpi.currentRow(), True, no_warn=True)
self.accept()
@QtCore.pyqtSlot()
def on_act_connect_xmlrpc_triggered(self) -> None:
"""Copy ip address of selected item to clipboard."""
pi.logger.debug("AvahiSearch.on_act_connect_xmlrpc_triggered")
if self.tb_revpi.currentRow() == -1:
return
self.connect_index = self._save_connection(self.tb_revpi.currentRow(), False, no_warn=True)
self.accept()
@QtCore.pyqtSlot()
def on_act_copy_host_triggered(self) -> None:
"""Copy ip address of selected item to clipboard."""
selected_items = self.tb_revpi.selectedItems()
if not selected_items:
return
item = selected_items[0]
host_name = item.data(WidgetData.host_name)
if platform == "win32":
# Strip hostname on Windows systems, it can not resolve .local addresses
host_name = host_name[:host_name.find(".")]
self.clipboard.setText(host_name)
@QtCore.pyqtSlot()
def on_act_copy_ip_triggered(self) -> None:
"""Copy ip address of selected item to clipboard."""
selected_items = self.tb_revpi.selectedItems()
if not selected_items:
return
item = selected_items[0]
self.clipboard.setText(item.data(WidgetData.address))
@QtCore.pyqtSlot()
def on_act_open_pictory_triggered(self) -> None:
"""Open piCtory in default browser of operating system."""
selected_items = self.tb_revpi.selectedItems()
if not selected_items:
return
item = selected_items[0]
if platform == "win32":
webbrowser.open("http://{0}/".format(item.data(WidgetData.address)))
else:
webbrowser.open("http://{0}/".format(item.data(WidgetData.host_name)))
@QtCore.pyqtSlot(str, str, int, str, str)
def on_avahi_added(self, name: str, server: str, port: int, conf_type: str, ip: str) -> None:
"""New Revolution Pi found."""
index = -1
for i in range(self.tb_revpi.rowCount()):
if self.tb_revpi.item(i, 0).data(WidgetData.object_name) == name:
index = i
break
if index == -1:
# New Row
item_name = QtWidgets.QTableWidgetItem()
item_ip = QtWidgets.QTableWidgetItem()
index = self.tb_revpi.rowCount()
self.tb_revpi.insertRow(index)
self.tb_revpi.setItem(index, 0, item_name)
self.tb_revpi.setItem(index, 1, item_ip)
else:
# Update row
item_name = self.tb_revpi.item(index, 0)
item_ip = self.tb_revpi.item(index, 1)
host_name = server[:-1]
item_name.setIcon(QtGui.QIcon(":/main/ico/cpu.ico"))
if ip in self.known_hosts:
item_name.setText("{0} ({1})".format(host_name, self.known_hosts[ip]))
else:
item_name.setText(host_name)
item_name.setData(WidgetData.object_name, name)
item_name.setData(WidgetData.address, ip)
item_name.setData(WidgetData.port, port)
item_name.setData(WidgetData.host_name, host_name)
item_ip.setText(ip)
@QtCore.pyqtSlot(str, str)
def on_avahi_removed(self, name: str, conf_type: str) -> None:
"""Revolution Pi disappeared."""
for i in range(self.tb_revpi.rowCount()):
if self.tb_revpi.item(i, 0).data(WidgetData.object_name) == name:
self.tb_revpi.removeRow(i)
break
@QtCore.pyqtSlot(int, int)
def on_tb_revpi_cellDoubleClicked(self, row: int, column: int) -> None:
"""Connect to double-clicked Revolution Pi."""
pi.logger.debug("AvahiSearch.on_tb_revpi_cellDoubleClicked")
cur = QtGui.QCursor()
self._cm_connect_exec(cur.pos())
@QtCore.pyqtSlot(int, int, int, int)
def on_tb_revpi_currentCellChanged(self, row: int, column: int, last_row: int, last_column: int) -> None:
"""Manage state of buttons."""
self.btn_connect.setEnabled(row >= 0)
self.btn_save.setEnabled(row >= 0)
@QtCore.pyqtSlot()
def on_btn_connect_clicked(self) -> None:
"""Connect to selected Revolution Pi."""
pi.logger.debug("AvahiSearch.on_btn_connect_clicked")
# Open context menu under the button
pos = self.btn_connect.pos()
pos.setY(pos.y() + self.btn_connect.height())
self._cm_connect_exec(self.mapToGlobal(pos))
@QtCore.pyqtSlot()
def on_btn_save_clicked(self) -> None:
"""Save selected Revolution Pi."""
pi.logger.debug("AvahiSearch.on_btn_save_clicked")
if self.tb_revpi.currentRow() == -1:
return
self.connect_index = self._save_connection(self.tb_revpi.currentRow(), ssh_tunnel=True)
@QtCore.pyqtSlot()
def on_btn_restart_clicked(self) -> None:
"""Clean up and restart search thread."""
self._restart_search()

View File

@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
"""File transfer system to handle QThreads."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
from logging import getLogger
from PyQt5 import QtCore, QtGui, QtWidgets
from .ui.backgroundworker_ui import Ui_diag_backgroundworker
log = getLogger()
class BackgroundWorker(QtCore.QThread):
steps_todo = QtCore.pyqtSignal(int)
steps_done = QtCore.pyqtSignal(int)
status_message = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(BackgroundWorker, self).__init__(parent)
def check_cancel(self) -> bool:
"""
Check for interruption of thread and show message
:return: True, if interruption was requested
"""
if self.isInterruptionRequested():
self.status_message.emit(self.tr("User requested cancellation..."))
self.msleep(750)
return True
return False
def exec_dialog(self) -> int:
diag = WorkerDialog(self, self.parent())
rc = diag.exec()
diag.deleteLater()
return rc
def wait_interruptable(self, seconds=-1) -> None:
"""Save function to wait and get the cancel buttons."""
counter = seconds * 4
while counter != 0:
counter -= 1
self.msleep(250)
if self._check_cancel():
break
def run(self) -> None:
"""Worker thread to import pictures from camera."""
log.debug("BackgroundWorker.run")
self.status_message.emit("Started dummy thread...")
self.wait_interruptable(5)
self.status_message.emit("Completed dummy thread.")
self._save_wait(3)
class WorkerDialog(QtWidgets.QDialog, Ui_diag_backgroundworker):
def __init__(self, worker_thread: BackgroundWorker, parent=None):
"""
Base of dialog to show progress from a background thread.
:param worker_thread: Thread with the logic work to do
:param parent: QtWidget
"""
super(WorkerDialog, self).__init__(parent)
self.setupUi(self)
self._canceled = False
self._th = worker_thread
self._th.finished.connect(self.on_th_finished)
self._th.steps_todo.connect(self.pgb_status.setMaximum)
self._th.steps_done.connect(self.pgb_status.setValue)
self._th.status_message.connect(self.lbl_status.setText)
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
a0.ignore()
def exec(self) -> int:
self._th.start()
return super(WorkerDialog, self).exec()
@QtCore.pyqtSlot()
def on_th_finished(self) -> None:
"""Check the result of import thread."""
if self._canceled:
self.reject()
else:
self.accept()
@QtCore.pyqtSlot(QtWidgets.QAbstractButton)
def on_btn_box_clicked(self, button: QtWidgets.QAbstractButton) -> None:
"""Control buttons for dialog."""
role = self.btn_box.buttonRole(button)
log.debug("WorkerDialog.on_btn_box_clicked({0})".format(role))
if role == QtWidgets.QDialogButtonBox.RejectRole:
self._th.requestInterruption()
self._canceled = True

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Debug control widget to append to main window."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2020 Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
import pickle
@@ -9,10 +9,10 @@ from xmlrpc.client import Binary, Fault, MultiCall, MultiCallIterator
from PyQt5 import QtCore, QtWidgets
import helper
import proginit as pi
from debugios import DebugIos
from ui.debugcontrol_ui import Ui_wid_debugcontrol
from .import helper
from . import proginit as pi
from .debugios import DebugIos
from .ui.debugcontrol_ui import Ui_wid_debugcontrol
class PsValues(QtCore.QThread):
@@ -186,7 +186,7 @@ class DebugControl(QtWidgets.QWidget, Ui_wid_debugcontrol):
win = self.dict_windows[position]
for io in self.dict_ios[io_type][position]: # type: list
# ['name', bitlength, byte_address, 'bmk', bitaddress, 'byteorder', signed]
# ['name', bytelen, byte_address, 'bmk', bitaddress, 'byteorder', signed]
value_procimg = bytes(ba_values[io[2]:io[2] + io[1]])
if io[4] >= 0:
# Bit-IO

View File

@@ -1,16 +1,16 @@
# -*- coding: utf-8 -*-
"""One device of the Revolution Pi."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2020 Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
import struct
from PyQt5 import QtCore, QtGui, QtWidgets
import helper
import proginit as pi
from ui.debugios_ui import Ui_win_debugios
from . import helper
from . import proginit as pi
from .ui.debugios_ui import Ui_win_debugios
class DebugIos(QtWidgets.QMainWindow, Ui_win_debugios):
@@ -340,19 +340,26 @@ class DebugIos(QtWidgets.QMainWindow, Ui_win_debugios):
:param value: New value as bytes or bool for widget
:param just_last_value: Just set last value property
"""
# child = self.findChild(self.search_class, io_name)
child = self.__qwa[io_name]
if child.property("frm"):
value = struct.unpack(child.property("frm"), value)[0]
elif type(value) == bytes:
if child.property("struct_type") == "text":
try:
value = value.decode("utf-8")
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!"
).format(value, io_name)
)
if child.property("struct_type") == "number":
value = str(int.from_bytes(
value,
byteorder="big" if child.property("big_endian") else "little",
signed=child.property("signed") or False
))
else:
value = value.decode()
child.setProperty("last_value", value)
if not just_last_value:

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Helper functions for this application."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2020 Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
import pickle
@@ -14,9 +14,12 @@ from queue import Queue
from threading import Lock
from xmlrpc.client import Binary, ServerProxy
from PyQt5 import QtCore
from PyQt5 import QtCore, QtWidgets
from paramiko.ssh_exception import AuthenticationException
import proginit as pi
from . import proginit as pi
from .ssh_tunneling.server import SSHLocalTunnel
from .sshauth import SSHAuth, SSHAuthType
class WidgetData(IntEnum):
@@ -27,6 +30,7 @@ class WidgetData(IntEnum):
port = 264
object_name = 265
timeout = 266
host_name = 267
last_dir_upload = 301
last_file_upload = 302
last_dir_pictory = 303
@@ -39,6 +43,9 @@ class WidgetData(IntEnum):
watch_files = 310
watch_path = 311
debug_geos = 312
ssh_use_tunnel = 313
ssh_port = 315
ssh_user = 316
class ConnectionManager(QtCore.QThread):
@@ -54,6 +61,8 @@ class ConnectionManager(QtCore.QThread):
"""This will be triggered, if a connection error was detected."""
status_changed = QtCore.pyqtSignal(str, str)
"""Status message and color suggestion."""
connection_recovered = QtCore.pyqtSignal()
"""After errors the connection is established again, could have other port information (SSH)."""
def __init__(self, parent=None, cycle_time_ms=1000):
super(ConnectionManager, self).__init__(parent)
@@ -70,6 +79,12 @@ class ConnectionManager(QtCore.QThread):
self.name = ""
self.port = 55123
self.ssh_tunnel_server = None # type: SSHLocalTunnel
self.ssh_use_tunnel = False
self.ssh_port = 22
self.ssh_user = "pi"
self.ssh_pass = ""
# Sync this with revpiplclist to preserve settings
self.program_last_dir_upload = ""
self.program_last_file_upload = ""
@@ -166,6 +181,12 @@ class ConnectionManager(QtCore.QThread):
self.address = ""
self.name = ""
self.port = 55123
self.ssh_use_tunnel = False
self.ssh_port = 22
self.ssh_user = "pi"
self.ssh_pass = ""
self.pyload_version = (0, 0, 0)
self.xml_funcs.clear()
self.xml_mode = -1
@@ -206,11 +227,12 @@ class ConnectionManager(QtCore.QThread):
settings.endArray()
def pyload_connect(self, settings_index: int):
def pyload_connect(self, settings_index: int, parent=None) -> bool:
"""
Create a new connection from settings object.
:param settings_index: Index of settings array 'connections'
:param parent: Qt parent window for dialog positioning
:return: True, if the connection was successfully established
"""
@@ -225,6 +247,13 @@ class ConnectionManager(QtCore.QThread):
port = settings.value("port", 55123, int)
timeout = settings.value("timeout", 5, int)
ssh_tunnel_server = None
ssh_use_tunnel = settings.value("ssh_use_tunnel", False, bool)
ssh_port = settings.value("ssh_port", 22, int)
ssh_user = settings.value("ssh_user", "pi", str)
ssh_tunnel_port = 0
ssh_pass = ""
self.program_last_dir_upload = settings.value("last_dir_upload", ".", str)
self.program_last_file_upload = settings.value("last_file_upload", ".", str)
self.program_last_dir_pictory = settings.value("last_dir_pictory", ".", str)
@@ -240,7 +269,42 @@ class ConnectionManager(QtCore.QThread):
settings.endArray()
socket.setdefaulttimeout(2)
sp = ServerProxy("http://{0}:{1}".format(address, port))
if ssh_use_tunnel:
while True:
diag_ssh_auth = SSHAuth(SSHAuthType.PASS, parent)
diag_ssh_auth.username = ssh_user
if not diag_ssh_auth.exec() == QtWidgets.QDialog.Accepted:
self._clear_settings()
return False
ssh_user = diag_ssh_auth.username
ssh_pass = diag_ssh_auth.password
ssh_tunnel_server = SSHLocalTunnel(port, address, ssh_port)
try:
ssh_tunnel_port = ssh_tunnel_server.connect_by_credentials(ssh_user, ssh_pass)
break
except AuthenticationException:
QtWidgets.QMessageBox.critical(
parent, self.tr("Error"), self.tr(
"The combination of username and password was rejected from the SSH server.\n\n"
"Try again."
)
)
except Exception as e:
# todo: Check some more kinds of exceptions and nice user info
self._clear_settings()
QtWidgets.QMessageBox.critical(
parent, self.tr("Error"), self.tr(
"Could not establish a SSH connection to server:\n\n{0}"
).format(str(e))
)
return False
sp = ServerProxy("http://127.0.0.1:{0}".format(ssh_tunnel_port))
else:
sp = ServerProxy("http://{0}:{1}".format(address, port))
# Load values and test connection to Revolution Pi
try:
@@ -250,19 +314,41 @@ class ConnectionManager(QtCore.QThread):
except Exception as e:
pi.logger.exception(e)
self.connection_error_observed.emit(str(e))
if not self.ssh_use_tunnel:
# todo: Change message, that user can use ssh
QtWidgets.QMessageBox.critical(
parent, self.tr("Error"), self.tr(
"Can not connect to RevPi XML-RPC Service! \n\n"
"This could have the following reasons: The RevPi is not "
"online, the XML-RPC service is not running / bind to "
"localhost or the ACL permission is not set for your "
"IP!!!\n\nRun 'sudo revpipyload_secure_installation' on "
"Revolution Pi to setup this function!"
)
)
return False
self.address = address
self.name = name
self.port = port
self.ssh_use_tunnel = ssh_use_tunnel
self.ssh_port = ssh_port
self.ssh_user = ssh_user
self.ssh_pass = ssh_pass
self.pyload_version = pyload_version
self.xml_funcs = xml_funcs
self.xml_mode = xml_mode
with self._lck_cli:
socket.setdefaulttimeout(timeout)
self.ssh_tunnel_server = ssh_tunnel_server
self._cli = sp
self._cli_connect.put_nowait((address, port))
self._cli_connect.put_nowait((
"127.0.0.1" if ssh_use_tunnel else address,
ssh_tunnel_port if ssh_use_tunnel else port
))
self.connection_established.emit()
@@ -285,7 +371,7 @@ class ConnectionManager(QtCore.QThread):
elif self._cli is not None:
# Tell all widget, that we want do disconnect, to save the settings
# Tell all widget, that we want to disconnect, to save the settings
self.connection_disconnecting.emit()
self._save_settings()
@@ -298,6 +384,10 @@ class ConnectionManager(QtCore.QThread):
self._clear_settings()
self._cli = None
if self.ssh_tunnel_server:
self.ssh_tunnel_server.disconnect()
self.ssh_tunnel_server = None
self.connection_disconnected.emit()
def pyload_simulate(self, configrsc: str, procimg: str, clean_existing: bool):
@@ -360,10 +450,10 @@ class ConnectionManager(QtCore.QThread):
if self._revpi is not None:
sp = None
self.status_changed.emit("SIMULATING", "yellow")
self.status_changed.emit(self.tr("SIMULATING"), "yellow")
elif self._cli is None:
sp = None
self.status_changed.emit("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()
@@ -380,23 +470,40 @@ class ConnectionManager(QtCore.QThread):
pi.logger.warning(e)
except Exception as e:
pi.logger.warning(e)
self.status_changed.emit("SERVER ERROR", "red")
self.status_changed.emit(self.tr("SERVER ERROR"), "red")
self.connection_error_observed.emit("{0} | {1}".format(e, type(e)))
if self.ssh_tunnel_server and not self.ssh_tunnel_server.connected:
self.ssh_tunnel_server.disconnect()
ssh_tunnel_server = SSHLocalTunnel(self.port, self.address, self.ssh_port)
try:
ssh_tunnel_port = self.ssh_tunnel_server.connect_by_credentials(
self.ssh_user,
self.ssh_pass
)
sp = ServerProxy("http://127.0.0.1:{0}".format(ssh_tunnel_port))
with self._lck_cli:
self.ssh_tunnel_server = ssh_tunnel_server
self._cli = sp
self.connection_recovered.emit()
except Exception:
pass
else:
if plc_exit_code == -1:
self.status_changed.emit("RUNNING", "green")
self.status_changed.emit(self.tr("RUNNING"), "green")
elif plc_exit_code == -2:
self.status_changed.emit("FILE NOT FOUND", "red")
self.status_changed.emit(self.tr("PLC FILE NOT FOUND"), "red")
elif plc_exit_code == -3:
self.status_changed.emit("NOT RUNNING (NO STATUS)", "yellow")
self.status_changed.emit(self.tr("NOT RUNNING (NO STATUS)"), "yellow")
elif plc_exit_code == -9:
self.status_changed.emit("PROGRAM KILLED", "red")
self.status_changed.emit(self.tr("PROGRAM KILLED"), "red")
elif plc_exit_code == -15:
self.status_changed.emit("PROGRAM TERMED", "red")
self.status_changed.emit(self.tr("PROGRAM TERMED"), "red")
elif plc_exit_code == 0:
self.status_changed.emit("NOT RUNNING", "yellow")
self.status_changed.emit(self.tr("NOT RUNNING"), "yellow")
else:
self.status_changed.emit("FINISHED WITH CODE {0}".format(plc_exit_code), "yellow")
self.status_changed.emit(self.tr("FINISHED WITH CODE {0}").format(plc_exit_code), "yellow")
self.msleep(self._cycle_time)
@@ -451,11 +558,17 @@ class ConnectionManager(QtCore.QThread):
return default_value
def get_cli(self):
"""Connection proxy of actual connection."""
if self.address and self.port:
"""
Connection proxy of actual connection.
Use connection_recovered signal to figure out new parameters.
"""
if not self.ssh_use_tunnel and self.address and self.port:
return ServerProxy("http://{0}:{1}".format(self.address, self.port))
else:
return None
if self.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
@property
def connected(self) -> bool:

View File

@@ -2,13 +2,13 @@
"""Options for MQTT system."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
from PyQt5 import QtGui, QtWidgets
import proginit as pi
from ui.mqttmanager_ui import Ui_diag_mqtt
from . import proginit as pi
from .ui.mqttmanager_ui import Ui_diag_mqtt
class MqttManager(QtWidgets.QDialog, Ui_diag_mqtt):

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Global program initialization."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2019 Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "LGPLv3"
import logging

View File

@@ -5,25 +5,25 @@
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__license__ = "GPLv3"
__version__ = "0.9.2"
__version__ = "0.9.10rc1"
import webbrowser
from os.path import basename, dirname, join
from PyQt5 import QtCore, QtGui, QtWidgets
import helper
import proginit as pi
import revpilogfile
from avahisearch import AvahiSearch
from debugcontrol import DebugControl
from revpifiles import RevPiFiles
from revpiinfo import RevPiInfo
from revpioption import RevPiOption
from revpiplclist import RevPiPlcList
from revpiprogram import RevPiProgram
from simulator import Simulator
from ui.revpicommander_ui import Ui_win_revpicommander
from . import helper
from . import proginit as pi
from . import revpilogfile
from .avahisearch import AvahiSearch
from .debugcontrol import DebugControl
from .revpifiles import RevPiFiles
from .revpiinfo import RevPiInfo
from .revpioption import RevPiOption
from .revpiplclist import RevPiPlcList
from .revpiprogram import RevPiProgram
from .simulator import Simulator
from .ui.revpicommander_ui import Ui_win_revpicommander
class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
@@ -63,6 +63,8 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
self.restoreGeometry(helper.settings.value("geo", b''))
self.setWindowFlag(QtCore.Qt.WindowMaximizeButtonHint, False)
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
pi.logger.debug("RevPiCommander.closeEvent")
helper.cm.pyload_disconnect()
@@ -94,6 +96,9 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# region # REGION: Connection management
def _pyload_connect(self, settings_index: int) -> None:
helper.cm.pyload_connect(settings_index, self)
@QtCore.pyqtSlot(str)
def on_cm_connection_error_observed(self, message: str):
"""
@@ -109,6 +114,8 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
pi.logger.debug("RevPiCommander.on_cm_connection_disconnected")
self._set_gui_control_states()
self.txt_host.setVisible(True)
self.txt_host.clear()
self.txt_connection.clear()
@QtCore.pyqtSlot()
@@ -135,16 +142,14 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
self._set_gui_control_states()
if helper.cm.simulating:
self.txt_host.setVisible(False)
self.txt_connection.setText("configrsc=\"{0}\", procimg=\"{1}\"".format(
helper.cm.simulating_configrsc,
helper.cm.simulating_procimg,
))
else:
self.txt_connection.setText("{0} - {1}:{2}".format(
helper.cm.name,
helper.cm.address,
helper.cm.port
))
self.txt_host.setText(helper.cm.name)
self.txt_connection.setText(helper.cm.address)
self.win_files = RevPiFiles(self)
@QtCore.pyqtSlot(str, str)
@@ -167,24 +172,28 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
for i in range(helper.settings.beginReadArray("connections")):
helper.settings.setArrayIndex(i)
act = QtWidgets.QAction(self)
act.setText(helper.settings.value("name"))
if helper.settings.value("folder"):
if helper.settings.value("folder") not in self.dict_men_connections_subfolder:
men_sub = QtWidgets.QMenu(self.men_connections)
men_sub.setTitle(helper.settings.value("folder"))
self.dict_men_connections_subfolder[helper.settings.value("folder")] = men_sub
self.men_connections.addMenu(men_sub)
parent_menu = self.dict_men_connections_subfolder[helper.settings.value("folder")]
else:
parent_menu = self.men_connections
display_name = helper.settings.value("name")
if helper.settings.value("ssh_use_tunnel", False, bool):
display_name += " (SSH)"
act = QtWidgets.QAction(parent_menu)
act.setText(display_name)
act.setData(i)
act.setToolTip("{0}:{1}".format(
helper.settings.value("address"),
helper.settings.value("port"),
))
if helper.settings.value("folder"):
if helper.settings.value("folder") not in self.dict_men_connections_subfolder:
men_sub = QtWidgets.QMenu(self)
men_sub.setTitle(helper.settings.value("folder"))
self.dict_men_connections_subfolder[helper.settings.value("folder")] = men_sub
self.men_connections.addMenu(men_sub)
self.dict_men_connections_subfolder[helper.settings.value("folder")].addAction(act)
else:
self.men_connections.addAction(act)
parent_menu.addAction(act)
helper.settings.endArray()
@@ -199,7 +208,7 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
"""Search for Revolution Pi with zero conf."""
if self.diag_search.exec() == QtWidgets.QDialog.Accepted:
if self.diag_search.connect_index >= 0:
helper.cm.pyload_connect(self.diag_search.connect_index)
self._pyload_connect(self.diag_search.connect_index)
self._load_men_connections()
@@ -358,17 +367,7 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
@QtCore.pyqtSlot(QtWidgets.QAction)
def on_men_connections_triggered(self, action: QtWidgets.QAction):
"""A connection is selected in the men_connections menu."""
if not helper.cm.pyload_connect(action.data()):
QtWidgets.QMessageBox.critical(
self, self.tr("Error"), self.tr(
"Can not connect to RevPi XML-RPC Service! \n\n"
"This could have the following reasons: The RevPi is not "
"online, the XML-RPC service is not running / bind to "
"localhost or the ACL permission is not set for your "
"IP!!!\n\nRun 'sudo revpipyload_secure_installation' on "
"Revolution Pi to setup this function!"
)
)
self._pyload_connect(action.data())
@QtCore.pyqtSlot()
def on_act_webpage_triggered(self):
@@ -452,7 +451,7 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
debugcontrol = DebugControl(self.centralwidget)
if debugcontrol.reload_devices():
self.wid_debugcontrol = debugcontrol
self.gl.addWidget(self.wid_debugcontrol, 7, 0)
self.gl.addWidget(self.wid_debugcontrol)
else:
debugcontrol.deleteLater()
QtWidgets.QMessageBox.critical(
@@ -470,20 +469,23 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
# endregion # # # # #
if __name__ == "__main__":
import sys
def main() -> int:
from sys import argv
# QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
# QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
if hasattr(QtCore.Qt, 'AA_EnableHighDpiScaling'):
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'):
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
app = QtWidgets.QApplication(sys.argv)
app = QtWidgets.QApplication(argv)
try:
# Setup translation from file with system language
locale = QtCore.QLocale.system().name()
translator = QtCore.QTranslator()
locale_file_name = "revpicommander_{0}".format(locale)
translator.load(join(dirname(__file__), "locale", locale_file_name), suffix=".qm")
translator.load(
QtCore.QLocale.system(),
"revpicommander", "_", join(dirname(__file__), "locale"), ".qm"
)
app.installTranslator(translator)
except Exception:
pass
@@ -499,4 +501,9 @@ if __name__ == "__main__":
helper.cm.requestInterruption()
helper.cm.wait()
sys.exit(exit_code)
return exit_code
if __name__ == "__main__":
import sys
sys.exit(main())

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""File manager for up und download PLC program."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2020 Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
import gzip
@@ -11,10 +11,11 @@ from xmlrpc.client import Binary
from PyQt5 import QtCore, QtGui, QtWidgets
import helper
import proginit as pi
from helper import WidgetData
from ui.files_ui import Ui_win_files
from . import helper
from . import proginit as pi
from .backgroundworker import BackgroundWorker
from .helper import WidgetData
from .ui.files_ui import Ui_win_files
class NodeType(IntEnum):
@@ -22,6 +23,57 @@ class NodeType(IntEnum):
DIR = 1001
class UploadFiles(BackgroundWorker):
def __init__(self, file_list: list, parent):
super(UploadFiles, self).__init__(parent)
self.ec = 1
self.file_list = file_list
self.plc_program_included = False # Will be True, when opt_program was found in files
def run(self) -> None:
self.steps_todo.emit(len(self.file_list))
# Get config to find actual auto start program for warnings
opt_program = helper.cm.call_remote_function("get_config", default_value={})
opt_program = opt_program.get("plcprogram", "none.py")
progress_counter = 0
for file_name in self.file_list:
progress_counter += 1
# Remove base dir of file to set relative for PyLoad
send_name = file_name.replace(helper.cm.develop_watch_path, "")[1:]
self.status_message.emit(send_name)
# Check whether this is the auto start program
if send_name == opt_program:
self.plc_program_included = True
# Transfer file
try:
with open(file_name, "rb") as fh:
upload_status = helper.cm.call_remote_function(
"plcupload", Binary(gzip.compress(fh.read())), send_name,
default_value=False
)
except Exception as e:
pi.logger.error(e)
self.ec = -2
return
if not upload_status:
self.ec = -1
return
self.steps_done.emit(progress_counter)
if self.check_cancel():
return
self.ec = 0
class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files):
def __init__(self, parent=None):
@@ -72,41 +124,13 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files):
)
return
# Get config to find actual auto start program for warnings
opt_program = helper.cm.call_remote_function("get_config", default_value={})
opt_program = opt_program.get("plcprogram", "none.py")
uploaded = True # Will be False, when opt_program was found in files
ec = 0
uploader = UploadFiles(self.file_list_local(), self)
if uploader.exec_dialog() == QtWidgets.QDialog.Rejected:
return
# todo: Do this in a thread with status bar to prevent freezing program on long upload times
for file_name in self.file_list_local():
# todo: Check exception of local file
with open(file_name, "rb") as fh:
# Remove base dir of file to set relative for PyLoad
send_name = file_name.replace(helper.cm.develop_watch_path, "")[1:]
# Check whether this is the auto start program
if send_name == opt_program:
uploaded = False
# Transfer file
try:
upload_status = helper.cm.call_remote_function(
"plcupload", Binary(gzip.compress(fh.read())), send_name,
default_value=False
)
except Exception as e:
pi.logger.error(e)
ec = -2
break
if not upload_status:
ec = -1
break
if ec == 0:
if uploader.ec == 0:
# Tell user, we did not find the auto start program in files
if uploaded:
if not uploader.plc_program_included:
QtWidgets.QMessageBox.information(
self, self.tr("Information"), self.tr(
"A PLC program has been uploaded. Please check the "
@@ -115,7 +139,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files):
)
)
elif ec == -1:
elif uploader.ec == -1:
QtWidgets.QMessageBox.critical(
self, self.tr("Error"), self.tr(
"The Revolution Pi could not process some parts of the "
@@ -123,7 +147,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files):
)
)
elif ec == -2:
elif uploader.ec == -2:
QtWidgets.QMessageBox.critical(
self, self.tr("Error"),
self.tr("Errors occurred during transmission")
@@ -489,16 +513,16 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files):
else:
file_name = os.path.join(helper.cm.develop_watch_path, file_name)
if override is None and os.path.exists(file_name):
rc = QtWidgets.QMessageBox.question(
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."
),
buttons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel
)
if rc == QtWidgets.QMessageBox.Cancel:
if rc_diag == QtWidgets.QMessageBox.Cancel:
return
override = rc == QtWidgets.QMessageBox.Yes
override = rc_diag == QtWidgets.QMessageBox.Yes
if os.path.exists(file_name) and not override:
pi.logger.debug("Skip existing file '{0}'".format(file_name))

View File

@@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
"""Program information of local an remote system."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
from PyQt5 import QtCore, QtGui, QtWidgets
import helper
from ui.revpiinfo_ui import Ui_diag_revpiinfo
from . import helper
from .ui.revpiinfo_ui import Ui_diag_revpiinfo
class RevPiInfo(QtWidgets.QDialog, Ui_diag_revpiinfo):

View File

@@ -1,16 +1,16 @@
# -*- coding: utf-8 -*-
"""View log files from Revolution Pi."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
from enum import IntEnum
from PyQt5 import QtCore, QtGui, QtWidgets
import helper
import proginit as pi
from ui.revpilogfile_ui import Ui_win_revpilogfile
from . import helper
from . import proginit as pi
from .ui.revpilogfile_ui import Ui_win_revpilogfile
class LogType(IntEnum):
@@ -22,15 +22,15 @@ class LogType(IntEnum):
class DataThread(QtCore.QThread):
error_detected = QtCore.pyqtSignal(str)
line_logged = QtCore.pyqtSignal(LogType, bool, str)
"""log_type, success, text"""
def __init__(self, parent=None, cycle_time=1000):
super(DataThread, self).__init__(parent)
self._cli = helper.cm.get_cli()
self._cycle_time = cycle_time
self._paused = True
self.error_count = 0
self.max_block = 256000
self.max_block = 16384 # 16 kByte
self.mrk_app = 0
self.mrk_daemon = 0
@@ -44,7 +44,12 @@ class DataThread(QtCore.QThread):
:return: tuple(position: int, EOF: bool)
"""
# Load max data from start position
buff_log = xmlcall(start_position, self.max_block).data
buff_log = helper.cm.call_remote_function(
xmlcall,
start_position,
self.max_block,
raise_exception=True
).data # type: bytes
eof = True
if buff_log == b'\x16': # 'ESC'
@@ -55,20 +60,23 @@ class DataThread(QtCore.QThread):
# The log file was rotated by log rotate on the Revolution Pi
start_position = 0
eof = False
pi.logger.info("RevPi started a new log file.")
elif buff_log:
start_position += len(buff_log)
eof = len(buff_log) < self.max_block
self.line_logged.emit(log_type, True, buff_log.decode("utf-8"))
self.line_logged.emit(log_type, True, buff_log.decode("utf-8", errors="replace"))
return start_position, eof
def pause(self):
"""Stop checking new log lines, but leave thread alive."""
pi.logger.debug("DataThread.pause")
self._paused = True
def resume(self):
"""Start checking for new log lines."""
pi.logger.debug("DataThread.resume")
self._paused = False
def run(self) -> None:
@@ -82,13 +90,13 @@ class DataThread(QtCore.QThread):
while not (eof_app or self.isInterruptionRequested()):
self.mrk_app, eof_app = self._load_log(
LogType.APP,
self._cli.load_applog,
"load_applog",
self.mrk_app,
)
while not (eof_daemon or self.isInterruptionRequested()):
self.mrk_daemon, eof_daemon = self._load_log(
LogType.DAEMON,
self._cli.load_plclog,
"load_plclog",
self.mrk_daemon,
)
self.error_count = 0
@@ -108,7 +116,7 @@ class RevPiLogfile(QtWidgets.QMainWindow, Ui_win_revpilogfile):
super(RevPiLogfile, self).__init__(parent)
self.setupUi(self)
self.th_data = DataThread()
self.th_data = DataThread(self)
self.err_daemon = 0
helper.cm.connection_established.connect(self.on_cm_connection_established)
@@ -119,7 +127,7 @@ class RevPiLogfile(QtWidgets.QMainWindow, Ui_win_revpilogfile):
def _create_data_thread(self):
self.th_data.deleteLater()
self.th_data = DataThread()
self.th_data = DataThread(self)
self.th_data.error_detected.connect(self.txt_daemon.setPlainText)
self.th_data.line_logged.connect(self.on_line_logged)
self.th_data.start()

View File

@@ -1,16 +1,16 @@
# -*- coding: utf-8 -*-
"""RevPiPyLoad options window."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
from PyQt5 import QtCore, QtGui, QtWidgets
import helper
import proginit as pi
from aclmanager import AclManager
from mqttmanager import MqttManager
from ui.revpioption_ui import Ui_diag_options
from . import helper
from . import proginit as pi
from .aclmanager import AclManager
from .mqttmanager import MqttManager
from .ui.revpioption_ui import Ui_diag_options
class RevPiOption(QtWidgets.QDialog, Ui_diag_options):

View File

@@ -1,17 +1,16 @@
# -*- coding: utf-8 -*-
"""Saved connections of Revolution Pi devices."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
from enum import IntEnum
from PyQt5 import QtCore, QtGui, QtWidgets
import helper
import proginit as pi
from helper import WidgetData
from ui.revpiplclist_ui import Ui_diag_connections
from . import proginit as pi
from .helper import WidgetData, settings
from .ui.revpiplclist_ui import Ui_diag_connections
class NodeType(IntEnum):
@@ -42,29 +41,38 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
self.tre_connections.clear()
self.cbb_folder.clear()
self.cbb_folder.addItem("")
for i in range(helper.settings.beginReadArray("connections")):
helper.settings.setArrayIndex(i)
for i in range(settings.beginReadArray("connections")):
settings.setArrayIndex(i)
con_item = QtWidgets.QTreeWidgetItem(NodeType.CON)
con_item.setIcon(0, QtGui.QIcon(":/main/ico/cpu.ico"))
con_item.setText(0, helper.settings.value("name", "Revolution Pi", str))
con_item.setText(1, helper.settings.value("address", "127.0.0.1", str))
con_item.setData(0, WidgetData.port, helper.settings.value("port", self.__default_port, int))
con_item.setData(0, WidgetData.timeout, helper.settings.value("timeout", 5, int))
con_item.setText(0, settings.value("name", "Revolution Pi", str))
con_item.setText(1, settings.value("address", "127.0.0.1", str))
con_item.setData(0, WidgetData.port, settings.value("port", self.__default_port, int))
con_item.setData(0, WidgetData.timeout, settings.value("timeout", 5, int))
con_item.setData(0, WidgetData.last_dir_upload, helper.settings.value("last_dir_upload"))
con_item.setData(0, WidgetData.last_file_upload, helper.settings.value("last_file_upload"))
con_item.setData(0, WidgetData.last_dir_pictory, helper.settings.value("last_dir_pictory"))
con_item.setData(0, WidgetData.last_dir_picontrol, helper.settings.value("last_dir_picontrol"))
con_item.setData(0, WidgetData.last_dir_selected, helper.settings.value("last_dir_selected"))
con_item.setData(0, WidgetData.last_pictory_file, helper.settings.value("last_pictory_file"))
con_item.setData(0, WidgetData.last_tar_file, helper.settings.value("last_tar_file"))
con_item.setData(0, WidgetData.last_zip_file, helper.settings.value("last_zip_file"))
con_item.setData(0, WidgetData.watch_files, helper.settings.value("watch_files"))
con_item.setData(0, WidgetData.watch_path, helper.settings.value("watch_path"))
con_item.setData(0, WidgetData.debug_geos, helper.settings.value("debug_geos"))
con_item.setData(0, WidgetData.ssh_use_tunnel, settings.value("ssh_use_tunnel", False, bool))
con_item.setData(0, WidgetData.ssh_port, settings.value("ssh_port", 22, int))
con_item.setData(0, WidgetData.ssh_user, settings.value("ssh_user", "pi", str))
folder = helper.settings.value("folder", "", str)
con_item.setData(0, WidgetData.last_dir_upload, settings.value("last_dir_upload"))
con_item.setData(0, WidgetData.last_file_upload, settings.value("last_file_upload"))
con_item.setData(0, WidgetData.last_dir_pictory, settings.value("last_dir_pictory"))
con_item.setData(0, WidgetData.last_dir_picontrol, settings.value("last_dir_picontrol"))
con_item.setData(0, WidgetData.last_dir_selected, settings.value("last_dir_selected"))
con_item.setData(0, WidgetData.last_pictory_file, settings.value("last_pictory_file"))
con_item.setData(0, WidgetData.last_tar_file, settings.value("last_tar_file"))
con_item.setData(0, WidgetData.last_zip_file, settings.value("last_zip_file"))
con_item.setData(0, WidgetData.watch_files, settings.value("watch_files"))
con_item.setData(0, WidgetData.watch_path, settings.value("watch_path"))
try:
# Bytes with QSettings are a little difficult sometimes
con_item.setData(0, WidgetData.debug_geos, settings.value("debug_geos"))
except Exception:
# Just drop the geos of IO windows
pass
folder = settings.value("folder", "", str)
if folder:
sub_folder = self._get_folder_item(folder)
if sub_folder is None:
@@ -78,7 +86,7 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
else:
self.tre_connections.addTopLevelItem(con_item)
helper.settings.endArray()
settings.endArray()
self.tre_connections.expandAll()
self.changes = True
@@ -91,52 +99,55 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
def set_settings(node: QtWidgets.QTreeWidgetItem):
parent = node.parent()
helper.settings.setValue("address", node.text(1))
helper.settings.setValue("folder", parent.text(0) if parent else "")
helper.settings.setValue("name", node.text(0))
helper.settings.setValue("port", node.data(0, WidgetData.port))
helper.settings.setValue("timeout", node.data(0, WidgetData.timeout))
settings.setValue("address", node.text(1))
settings.setValue("folder", parent.text(0) if parent else "")
settings.setValue("name", node.text(0))
settings.setValue("port", node.data(0, WidgetData.port))
settings.setValue("timeout", node.data(0, WidgetData.timeout))
settings.setValue("ssh_use_tunnel", node.data(0, WidgetData.ssh_use_tunnel))
settings.setValue("ssh_port", node.data(0, WidgetData.ssh_port))
settings.setValue("ssh_user", node.data(0, WidgetData.ssh_user))
if node.data(0, WidgetData.last_dir_upload):
helper.settings.setValue("last_dir_upload", node.data(0, WidgetData.last_dir_upload))
settings.setValue("last_dir_upload", node.data(0, WidgetData.last_dir_upload))
if node.data(0, WidgetData.last_file_upload):
helper.settings.setValue("last_file_upload", node.data(0, WidgetData.last_file_upload))
settings.setValue("last_file_upload", node.data(0, WidgetData.last_file_upload))
if node.data(0, WidgetData.last_dir_pictory):
helper.settings.setValue("last_dir_pictory", node.data(0, WidgetData.last_dir_pictory))
settings.setValue("last_dir_pictory", node.data(0, WidgetData.last_dir_pictory))
if node.data(0, WidgetData.last_dir_picontrol):
helper.settings.setValue("last_dir_picontrol", node.data(0, WidgetData.last_dir_picontrol))
settings.setValue("last_dir_picontrol", node.data(0, WidgetData.last_dir_picontrol))
if node.data(0, WidgetData.last_dir_selected):
helper.settings.setValue("last_dir_selected", node.data(0, WidgetData.last_dir_selected))
settings.setValue("last_dir_selected", node.data(0, WidgetData.last_dir_selected))
if node.data(0, WidgetData.last_pictory_file):
helper.settings.setValue("last_pictory_file", node.data(0, WidgetData.last_pictory_file))
settings.setValue("last_pictory_file", node.data(0, WidgetData.last_pictory_file))
if node.data(0, WidgetData.last_tar_file):
helper.settings.setValue("last_tar_file", node.data(0, WidgetData.last_tar_file))
settings.setValue("last_tar_file", node.data(0, WidgetData.last_tar_file))
if node.data(0, WidgetData.last_zip_file):
helper.settings.setValue("last_zip_file", node.data(0, WidgetData.last_zip_file))
settings.setValue("last_zip_file", node.data(0, WidgetData.last_zip_file))
if node.data(0, WidgetData.watch_files):
helper.settings.setValue("watch_files", node.data(0, WidgetData.watch_files))
settings.setValue("watch_files", node.data(0, WidgetData.watch_files))
if node.data(0, WidgetData.watch_path):
helper.settings.setValue("watch_path", node.data(0, WidgetData.watch_path))
settings.setValue("watch_path", node.data(0, WidgetData.watch_path))
if node.data(0, WidgetData.debug_geos):
helper.settings.setValue("debug_geos", node.data(0, WidgetData.debug_geos))
settings.setValue("debug_geos", node.data(0, WidgetData.debug_geos))
helper.settings.remove("connections")
helper.settings.beginWriteArray("connections")
settings.remove("connections")
settings.beginWriteArray("connections")
counter_index = 0
for i in range(self.tre_connections.topLevelItemCount()):
root_item = self.tre_connections.topLevelItem(i)
if root_item.type() == NodeType.DIR:
for k in range(root_item.childCount()):
helper.settings.setArrayIndex(counter_index)
settings.setArrayIndex(counter_index)
set_settings(root_item.child(k))
counter_index += 1
elif root_item.type() == NodeType.CON:
helper.settings.setArrayIndex(counter_index)
settings.setArrayIndex(counter_index)
set_settings(root_item)
counter_index += 1
helper.settings.endArray()
settings.endArray()
self.changes = False
super(RevPiPlcList, self).accept()
@@ -196,6 +207,10 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
self.sbx_timeout.setEnabled(con_item)
self.cbb_folder.setEnabled(con_item or dir_item)
self.cbx_ssh_use_tunnel.setEnabled(con_item)
self.sbx_ssh_port.setEnabled(con_item)
self.txt_ssh_user.setEnabled(con_item)
def _get_folder_item(self, name: str):
"""Find the folder entry by name."""
for i in range(self.tre_connections.topLevelItemCount()):
@@ -242,6 +257,11 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
self.cbb_folder.setCurrentIndex(0)
else:
self.cbb_folder.setCurrentText(current.parent().text(0))
self.cbx_ssh_use_tunnel.setChecked(current.data(0, WidgetData.ssh_use_tunnel))
self.sbx_ssh_port.setValue(current.data(0, WidgetData.ssh_port))
self.txt_ssh_user.setText(current.data(0, WidgetData.ssh_user))
elif current and current.type() == NodeType.DIR:
self.__current_item = current
self.cbb_folder.setCurrentText(current.text(0))
@@ -278,6 +298,10 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
self.__current_item.setIcon(0, QtGui.QIcon(":/main/ico/cpu.ico"))
self.__current_item.setText(0, self.__default_name)
self.__current_item.setData(0, WidgetData.port, self.__default_port)
self.__current_item.setData(0, WidgetData.timeout, 5)
self.__current_item.setData(0, WidgetData.ssh_use_tunnel, False)
self.__current_item.setData(0, WidgetData.ssh_port, 22)
self.__current_item.setData(0, WidgetData.ssh_user, "pi")
sub_folder = self._get_folder_item(self.cbb_folder.currentText())
if sub_folder:
sub_folder.addChild(self.__current_item)
@@ -312,6 +336,24 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
return
self.__current_item.setData(0, WidgetData.timeout, value)
@QtCore.pyqtSlot(int)
def on_cbx_ssh_use_tunnel_stateChanged(self, check_state: int):
if self.__current_item.type() != NodeType.CON:
return
self.__current_item.setData(0, WidgetData.ssh_use_tunnel, check_state == QtCore.Qt.CheckState.Checked)
@QtCore.pyqtSlot(int)
def on_sbx_ssh_port_valueChanged(self, value: int):
if self.__current_item.type() != NodeType.CON:
return
self.__current_item.setData(0, WidgetData.ssh_port, value)
@QtCore.pyqtSlot(str)
def on_txt_ssh_user_textEdited(self, text):
if self.__current_item.type() != NodeType.CON:
return
self.__current_item.setData(0, WidgetData.ssh_user, text)
@QtCore.pyqtSlot(str)
def on_cbb_folder_editTextChanged(self, text: str):
pi.logger.debug("RevPiPlcList.on_cbb_folder_editTextChanged({0})".format(text))

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Revolution Pi PLC program configuration."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
import gzip
@@ -14,9 +14,9 @@ from xmlrpc.client import Binary
from PyQt5 import QtCore, QtGui, QtWidgets
import helper
import proginit as pi
from ui.revpiprogram_ui import Ui_diag_program
from . import helper
from . import proginit as pi
from .ui.revpiprogram_ui import Ui_diag_program
class RevPiProgram(QtWidgets.QDialog, Ui_diag_program):

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Simulator for piControl."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2021 Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
from os import W_OK, access
@@ -9,8 +9,8 @@ from os.path import basename, dirname, exists, join
from PyQt5 import QtCore, QtGui, QtWidgets
import helper
from ui.simulator_ui import Ui_diag_simulator
from . import helper
from .ui.simulator_ui import Ui_diag_simulator
class Simulator(QtWidgets.QDialog, Ui_diag_simulator):
@@ -110,7 +110,7 @@ class Simulator(QtWidgets.QDialog, Ui_diag_simulator):
configrsc_file = self.txt_configrsc.text()
procimg_file = self.txt_procimg.text()
if configrsc_file and procimg_file:
file_access = access(procimg_file, W_OK)
file_access = access(procimg_file, W_OK) if exists(procimg_file) else access(dirname(procimg_file), W_OK)
self.txt_info.setPlainText("configrsc=\"{0}\", procimg=\"{1}\"".format(configrsc_file, procimg_file))
self.btn_start_pictory.setEnabled(file_access)
self.btn_start_empty.setEnabled(file_access)

View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
"""Package of ssh tunnel connections."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"

View File

@@ -0,0 +1,173 @@
# -*- 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"
__license__ = "GPLv3"
import select
from socketserver import BaseRequestHandler, ThreadingTCPServer
from threading import Thread
from paramiko.client import SSHClient, WarningPolicy
from paramiko.rsakey import RSAKey
from paramiko.ssh_exception import PasswordRequiredException
from paramiko.transport import Transport
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:
return
if chan is None:
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):
"""
Connect to a ssh remote host and tunnel a port to your host.
:param remote_tunnel_port: Port 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._ssh_host = ssh_host
self._ssh_port = ssh_port
self._th_server = Thread()
self._ssh_client = SSHClient()
self._ssh_client.load_system_host_keys()
self._ssh_client.set_missing_host_key_policy(WarningPolicy())
self._ssh_transport = None # type: Transport
self._forward_server = None # type: ThreadingTCPServer
self._local_tunnel_port = None # type: int
def __th_target(self):
"""Server thread for socket mirror."""
self._forward_server.serve_forever()
def _configure_forward_server(self) -> int:
"""
Configure forward server for port mirror.
:return: Local port on wich the remote port is connected
"""
self._ssh_transport = self._ssh_client.get_transport()
class SubHandler(Handler):
chain_host = "127.0.0.1"
chain_port = self._remote_tunnel_port
ssh_transport = self._ssh_transport
self._forward_server = ForwardServer(("127.0.0.1", 0), SubHandler)
self._local_tunnel_port = self._forward_server.socket.getsockname()[1]
self._th_server = Thread(target=self.__th_target)
self._th_server.start()
return self._local_tunnel_port
def connect_by_credentials(self, username: str, password: str) -> int:
"""
Connect to a ssh remote host and tunnel specified port of localhost.
:return: Local port on wich the remote port is connected
"""
if self._th_server.is_alive():
raise RuntimeError("Already connected")
self._ssh_client.connect(
hostname=self._ssh_host,
port=self._ssh_port,
username=username,
password=password,
)
return self._configure_forward_server()
def connect_by_keyfile(self, username: str, key_file: str, key_password: str = None) -> int:
"""
Connect to a ssh remote host and tunnel specified port of localhost.
:return: Local port on wich the remote port is connected
"""
if self._th_server.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._ssh_client.connect(
hostname=self._ssh_host,
port=self._ssh_port,
username=username,
pkey=private_key,
look_for_keys=True,
)
return self._configure_forward_server()
def disconnect(self):
"""Close SSH tunnel connection."""
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()
@staticmethod
def key_file_password_protected(key_file: str) -> bool:
try:
RSAKey.from_private_key_file(key_file)
except PasswordRequiredException:
return True
return False
@property
def connected(self):
"""Check connection state of ssh tunnel."""
return self._ssh_transport and self._ssh_transport.is_active()
@property
def local_tunnel_port(self) -> int:
return self._local_tunnel_port

View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
"""Authentication dialog for SSH."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
from enum import Enum
from PyQt5 import QtWidgets
from revpicommander.ui.sshauth_ui import Ui_diag_sshauth
class SSHAuthType(Enum):
PASS = "pass"
KEYS = "keys"
class SSHAuth(QtWidgets.QDialog, Ui_diag_sshauth):
"""Version information window."""
def __init__(self, auth_type: SSHAuthType, parent=None):
super(SSHAuth, self).__init__(parent)
self.setupUi(self)
self.wid_password.setVisible(auth_type is SSHAuthType.PASS)
self.wid_keys.setVisible(auth_type is SSHAuthType.KEYS)
@property
def password(self) -> str:
return self.txt_password.text()
@property
def username(self) -> str:
return self.txt_username.text()
@username.setter
def username(self, value: str):
self.txt_username.setText(value)

View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
"""Package of compiled Qt ui files."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'aclmanager.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -86,6 +87,7 @@ class Ui_diag_aclmanager(object):
self.txt_ip_a.setObjectName("txt_ip_a")
self.hl_ip.addWidget(self.txt_ip_a)
self.lbl_ip_a = QtWidgets.QLabel(self.gb_edit)
self.lbl_ip_a.setText(".")
self.lbl_ip_a.setObjectName("lbl_ip_a")
self.hl_ip.addWidget(self.lbl_ip_a)
self.txt_ip_b = QtWidgets.QLineEdit(self.gb_edit)
@@ -93,6 +95,7 @@ class Ui_diag_aclmanager(object):
self.txt_ip_b.setObjectName("txt_ip_b")
self.hl_ip.addWidget(self.txt_ip_b)
self.lbl_ip_b = QtWidgets.QLabel(self.gb_edit)
self.lbl_ip_b.setText(".")
self.lbl_ip_b.setObjectName("lbl_ip_b")
self.hl_ip.addWidget(self.lbl_ip_b)
self.txt_ip_c = QtWidgets.QLineEdit(self.gb_edit)
@@ -100,6 +103,7 @@ class Ui_diag_aclmanager(object):
self.txt_ip_c.setObjectName("txt_ip_c")
self.hl_ip.addWidget(self.txt_ip_c)
self.lbl_ip_c = QtWidgets.QLabel(self.gb_edit)
self.lbl_ip_c.setText(".")
self.lbl_ip_c.setObjectName("lbl_ip_c")
self.hl_ip.addWidget(self.lbl_ip_c)
self.txt_ip_d = QtWidgets.QLineEdit(self.gb_edit)
@@ -120,8 +124,8 @@ class Ui_diag_aclmanager(object):
self.verticalLayout.addWidget(self.btn_box)
self.retranslateUi(diag_aclmanager)
self.btn_box.accepted.connect(diag_aclmanager.accept)
self.btn_box.rejected.connect(diag_aclmanager.reject)
self.btn_box.accepted.connect(diag_aclmanager.accept) # type: ignore
self.btn_box.rejected.connect(diag_aclmanager.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(diag_aclmanager)
diag_aclmanager.setTabOrder(self.tb_acls, self.btn_edit)
diag_aclmanager.setTabOrder(self.btn_edit, self.btn_remove)
@@ -146,9 +150,6 @@ class Ui_diag_aclmanager(object):
self.btn_add.setText(_translate("diag_aclmanager", "&Save entry"))
self.lbl_ip.setText(_translate("diag_aclmanager", "IP address:"))
self.lbl_level.setText(_translate("diag_aclmanager", "Access level:"))
self.lbl_ip_a.setText(_translate("diag_aclmanager", "."))
self.lbl_ip_b.setText(_translate("diag_aclmanager", "."))
self.lbl_ip_c.setText(_translate("diag_aclmanager", "."))
if __name__ == "__main__":

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'avahisearch.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -64,9 +65,19 @@ class Ui_diag_search(object):
self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Close)
self.btn_box.setObjectName("btn_box")
self.gridLayout.addWidget(self.btn_box, 3, 0, 1, 2)
self.act_copy_host = QtWidgets.QAction(diag_search)
self.act_copy_host.setObjectName("act_copy_host")
self.act_copy_ip = QtWidgets.QAction(diag_search)
self.act_copy_ip.setObjectName("act_copy_ip")
self.act_open_pictory = QtWidgets.QAction(diag_search)
self.act_open_pictory.setObjectName("act_open_pictory")
self.act_connect_ssh = QtWidgets.QAction(diag_search)
self.act_connect_ssh.setObjectName("act_connect_ssh")
self.act_connect_xmlrpc = QtWidgets.QAction(diag_search)
self.act_connect_xmlrpc.setObjectName("act_connect_xmlrpc")
self.retranslateUi(diag_search)
self.btn_box.rejected.connect(diag_search.reject)
self.btn_box.rejected.connect(diag_search.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(diag_search)
def retranslateUi(self, diag_search):
@@ -81,6 +92,13 @@ class Ui_diag_search(object):
item.setText(_translate("diag_search", "IP address"))
self.btn_connect.setText(_translate("diag_search", "&Connect to Revolution Pi"))
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_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_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"))
from . import ressources_rc

View File

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'backgroundworker.ui'
#
# Created by: PyQt5 UI code generator 5.15.7
#
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_diag_backgroundworker(object):
def setupUi(self, diag_backgroundworker):
diag_backgroundworker.setObjectName("diag_backgroundworker")
diag_backgroundworker.resize(418, 97)
diag_backgroundworker.setModal(True)
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.setObjectName("lbl_status")
self.verticalLayout.addWidget(self.lbl_status)
self.pgb_status = QtWidgets.QProgressBar(diag_backgroundworker)
self.pgb_status.setMinimumSize(QtCore.QSize(400, 0))
self.pgb_status.setObjectName("pgb_status")
self.verticalLayout.addWidget(self.pgb_status)
self.btn_box = QtWidgets.QDialogButtonBox(diag_backgroundworker)
self.btn_box.setOrientation(QtCore.Qt.Horizontal)
self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel)
self.btn_box.setCenterButtons(True)
self.btn_box.setObjectName("btn_box")
self.verticalLayout.addWidget(self.btn_box)
self.retranslateUi(diag_backgroundworker)
QtCore.QMetaObject.connectSlotsByName(diag_backgroundworker)
def retranslateUi(self, diag_backgroundworker):
_translate = QtCore.QCoreApplication.translate
diag_backgroundworker.setWindowTitle(_translate("diag_backgroundworker", "File transfer..."))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
diag_backgroundworker = QtWidgets.QDialog()
ui = Ui_diag_backgroundworker()
ui.setupUi(diag_backgroundworker)
diag_backgroundworker.show()
sys.exit(app.exec_())

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'debugcontrol.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -35,12 +36,15 @@ class Ui_wid_debugcontrol(object):
self.verticalLayout = QtWidgets.QVBoxLayout(self.gb_control)
self.verticalLayout.setObjectName("verticalLayout")
self.btn_read_io = QtWidgets.QPushButton(self.gb_control)
self.btn_read_io.setShortcut("F4")
self.btn_read_io.setObjectName("btn_read_io")
self.verticalLayout.addWidget(self.btn_read_io)
self.btn_refresh_io = QtWidgets.QPushButton(self.gb_control)
self.btn_refresh_io.setShortcut("F5")
self.btn_refresh_io.setObjectName("btn_refresh_io")
self.verticalLayout.addWidget(self.btn_refresh_io)
self.btn_write_o = QtWidgets.QPushButton(self.gb_control)
self.btn_write_o.setShortcut("F6")
self.btn_write_o.setObjectName("btn_write_o")
self.verticalLayout.addWidget(self.btn_write_o)
self.cbx_refresh = QtWidgets.QCheckBox(self.gb_control)
@@ -63,13 +67,10 @@ class Ui_wid_debugcontrol(object):
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)"))
self.btn_read_io.setText(_translate("wid_debugcontrol", "Read &all IO values"))
self.btn_read_io.setShortcut(_translate("wid_debugcontrol", "F4"))
self.btn_refresh_io.setToolTip(_translate("wid_debugcontrol", "Refresh all IO values which are locally not changed (F5)"))
self.btn_refresh_io.setText(_translate("wid_debugcontrol", "&Refresh unchanged IOs"))
self.btn_refresh_io.setShortcut(_translate("wid_debugcontrol", "F5"))
self.btn_write_o.setToolTip(_translate("wid_debugcontrol", "Write locally changed output values to process image (F6)"))
self.btn_write_o.setText(_translate("wid_debugcontrol", "&Write changed outputs"))
self.btn_write_o.setShortcut(_translate("wid_debugcontrol", "F6"))
self.cbx_refresh.setText(_translate("wid_debugcontrol", "&Auto refresh values"))
self.cbx_write.setText(_translate("wid_debugcontrol", "and write outputs"))

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'debugios.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'files.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -55,6 +56,7 @@ class Ui_win_files(object):
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_path_local.sizePolicy().hasHeightForWidth())
self.lbl_path_local.setSizePolicy(sizePolicy)
self.lbl_path_local.setToolTip("/")
self.lbl_path_local.setText("/")
self.lbl_path_local.setObjectName("lbl_path_local")
self.gridLayout_2.addWidget(self.lbl_path_local, 1, 0, 1, 3)
@@ -98,6 +100,7 @@ class Ui_win_files(object):
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_path_revpi.sizePolicy().hasHeightForWidth())
self.lbl_path_revpi.setSizePolicy(sizePolicy)
self.lbl_path_revpi.setToolTip("/")
self.lbl_path_revpi.setText("/")
self.lbl_path_revpi.setObjectName("lbl_path_revpi")
self.gridLayout_3.addWidget(self.lbl_path_revpi, 1, 0, 1, 2)
@@ -162,10 +165,8 @@ class Ui_win_files(object):
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.lbl_path_local.setToolTip(_translate("win_files", "/"))
self.tree_files_local.setSortingEnabled(True)
self.gb_select_revpi.setTitle(_translate("win_files", "Revolution Pi"))
self.lbl_path_revpi.setToolTip(_translate("win_files", "/"))
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)

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'mqttmanager.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -117,8 +118,8 @@ class Ui_diag_mqtt(object):
self.verticalLayout.addWidget(self.btn_box)
self.retranslateUi(diag_mqtt)
self.btn_box.accepted.connect(diag_mqtt.accept)
self.btn_box.rejected.connect(diag_mqtt.reject)
self.btn_box.accepted.connect(diag_mqtt.accept) # type: ignore
self.btn_box.rejected.connect(diag_mqtt.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(diag_mqtt)
def retranslateUi(self, diag_mqtt):

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'revpicommander.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -13,45 +14,78 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_win_revpicommander(object):
def setupUi(self, win_revpicommander):
win_revpicommander.setObjectName("win_revpicommander")
win_revpicommander.resize(318, 273)
win_revpicommander.resize(353, 299)
win_revpicommander.setWindowTitle("RevPi Commander")
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/main/ico/revpipycontrol.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
win_revpicommander.setWindowIcon(icon)
self.centralwidget = QtWidgets.QWidget(win_revpicommander)
self.centralwidget.setObjectName("centralwidget")
self.gl = QtWidgets.QGridLayout(self.centralwidget)
self.gl = QtWidgets.QVBoxLayout(self.centralwidget)
self.gl.setObjectName("gl")
self.hzl_connection = QtWidgets.QHBoxLayout()
self.hzl_connection.setObjectName("hzl_connection")
self.txt_host = QtWidgets.QLineEdit(self.centralwidget)
self.txt_host.setFocusPolicy(QtCore.Qt.NoFocus)
self.txt_host.setText("")
self.txt_host.setReadOnly(True)
self.txt_host.setObjectName("txt_host")
self.hzl_connection.addWidget(self.txt_host)
self.txt_connection = QtWidgets.QLineEdit(self.centralwidget)
self.txt_connection.setFocusPolicy(QtCore.Qt.NoFocus)
self.txt_connection.setText("")
self.txt_connection.setReadOnly(True)
self.txt_connection.setObjectName("txt_connection")
self.gl.addWidget(self.txt_connection, 0, 0, 1, 1)
self.hzl_connection.addWidget(self.txt_connection)
self.gl.addLayout(self.hzl_connection)
self.btn_plc_start = QtWidgets.QPushButton(self.centralwidget)
icon1 = QtGui.QIcon()
icon1.addPixmap(QtGui.QPixmap(":/action/ico/system-run.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.btn_plc_start.setIcon(icon1)
self.btn_plc_start.setObjectName("btn_plc_start")
self.gl.addWidget(self.btn_plc_start, 1, 0, 1, 1)
self.gl.addWidget(self.btn_plc_start)
self.btn_plc_stop = QtWidgets.QPushButton(self.centralwidget)
icon2 = QtGui.QIcon()
icon2.addPixmap(QtGui.QPixmap(":/action/ico/process-stop.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.btn_plc_stop.setIcon(icon2)
self.btn_plc_stop.setObjectName("btn_plc_stop")
self.gl.addWidget(self.btn_plc_stop, 2, 0, 1, 1)
self.gl.addWidget(self.btn_plc_stop)
self.btn_plc_restart = QtWidgets.QPushButton(self.centralwidget)
icon3 = QtGui.QIcon()
icon3.addPixmap(QtGui.QPixmap(":/action/ico/view-refresh.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.btn_plc_restart.setIcon(icon3)
self.btn_plc_restart.setObjectName("btn_plc_restart")
self.gl.addWidget(self.btn_plc_restart, 3, 0, 1, 1)
self.gl.addWidget(self.btn_plc_restart)
self.btn_plc_logs = QtWidgets.QPushButton(self.centralwidget)
icon4 = QtGui.QIcon()
icon4.addPixmap(QtGui.QPixmap(":/action/ico/applications-utilities.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.btn_plc_logs.setIcon(icon4)
self.btn_plc_logs.setObjectName("btn_plc_logs")
self.gl.addWidget(self.btn_plc_logs, 4, 0, 1, 1)
self.gl.addWidget(self.btn_plc_logs)
self.hzl_status = QtWidgets.QHBoxLayout()
self.hzl_status.setObjectName("hzl_status")
self.lbl_status = QtWidgets.QLabel(self.centralwidget)
self.lbl_status.setObjectName("lbl_status")
self.hzl_status.addWidget(self.lbl_status)
self.txt_status = QtWidgets.QLineEdit(self.centralwidget)
self.txt_status.setFocusPolicy(QtCore.Qt.NoFocus)
self.txt_status.setText("")
self.txt_status.setAlignment(QtCore.Qt.AlignCenter)
self.txt_status.setReadOnly(True)
self.txt_status.setObjectName("txt_status")
self.gl.addWidget(self.txt_status, 5, 0, 1, 1)
self.hzl_status.addWidget(self.txt_status)
self.gl.addLayout(self.hzl_status)
self.btn_plc_debug = QtWidgets.QPushButton(self.centralwidget)
self.btn_plc_debug.setMinimumSize(QtCore.QSize(300, 0))
icon5 = QtGui.QIcon()
icon5.addPixmap(QtGui.QPixmap(":/action/ico/edit-find.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.btn_plc_debug.setIcon(icon5)
self.btn_plc_debug.setCheckable(True)
self.btn_plc_debug.setObjectName("btn_plc_debug")
self.gl.addWidget(self.btn_plc_debug, 6, 0, 1, 1)
self.gl.addWidget(self.btn_plc_debug)
win_revpicommander.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(win_revpicommander)
self.menubar.setGeometry(QtCore.QRect(0, 0, 318, 22))
self.menubar.setGeometry(QtCore.QRect(0, 0, 353, 24))
self.menubar.setObjectName("menubar")
self.men_file = QtWidgets.QMenu(self.menubar)
self.men_file.setObjectName("men_file")
@@ -67,8 +101,10 @@ class Ui_win_revpicommander(object):
self.statusbar.setObjectName("statusbar")
win_revpicommander.setStatusBar(self.statusbar)
self.act_connections = QtWidgets.QAction(win_revpicommander)
self.act_connections.setShortcut("Ctrl+N")
self.act_connections.setObjectName("act_connections")
self.act_search = QtWidgets.QAction(win_revpicommander)
self.act_search.setShortcut("Ctrl+F")
self.act_search.setObjectName("act_search")
self.act_quit = QtWidgets.QAction(win_revpicommander)
self.act_quit.setObjectName("act_quit")
@@ -77,16 +113,21 @@ class Ui_win_revpicommander(object):
self.act_info = QtWidgets.QAction(win_revpicommander)
self.act_info.setObjectName("act_info")
self.act_logs = QtWidgets.QAction(win_revpicommander)
self.act_logs.setShortcut("Ctrl+L")
self.act_logs.setObjectName("act_logs")
self.act_options = QtWidgets.QAction(win_revpicommander)
self.act_options.setShortcut("Ctrl+O")
self.act_options.setObjectName("act_options")
self.act_program = QtWidgets.QAction(win_revpicommander)
self.act_program.setShortcut("Ctrl+P")
self.act_program.setObjectName("act_program")
self.act_developer = QtWidgets.QAction(win_revpicommander)
self.act_developer.setShortcut("Ctrl+D")
self.act_developer.setObjectName("act_developer")
self.act_pictory = QtWidgets.QAction(win_revpicommander)
self.act_pictory.setObjectName("act_pictory")
self.act_disconnect = QtWidgets.QAction(win_revpicommander)
self.act_disconnect.setShortcut("Ctrl+X")
self.act_disconnect.setObjectName("act_disconnect")
self.act_reset = QtWidgets.QAction(win_revpicommander)
self.act_reset.setObjectName("act_reset")
@@ -116,7 +157,7 @@ class Ui_win_revpicommander(object):
self.menubar.addAction(self.men_help.menuAction())
self.retranslateUi(win_revpicommander)
self.act_quit.triggered.connect(win_revpicommander.close)
self.act_quit.triggered.connect(win_revpicommander.close) # type: ignore
QtCore.QMetaObject.connectSlotsByName(win_revpicommander)
win_revpicommander.setTabOrder(self.btn_plc_start, self.btn_plc_stop)
win_revpicommander.setTabOrder(self.btn_plc_stop, self.btn_plc_restart)
@@ -125,11 +166,11 @@ class Ui_win_revpicommander(object):
def retranslateUi(self, win_revpicommander):
_translate = QtCore.QCoreApplication.translate
win_revpicommander.setWindowTitle(_translate("win_revpicommander", "RevPi Python PLC Commander"))
self.btn_plc_start.setText(_translate("win_revpicommander", "PLC &start"))
self.btn_plc_stop.setText(_translate("win_revpicommander", "PLC s&top"))
self.btn_plc_restart.setText(_translate("win_revpicommander", "PLC restart"))
self.btn_plc_logs.setText(_translate("win_revpicommander", "PLC &logs"))
self.lbl_status.setText(_translate("win_revpicommander", "Status:"))
self.btn_plc_debug.setText(_translate("win_revpicommander", "PLC watch &mode"))
self.men_file.setTitle(_translate("win_revpicommander", "&File"))
self.men_help.setTitle(_translate("win_revpicommander", "&Help"))
@@ -137,21 +178,15 @@ class Ui_win_revpicommander(object):
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.setShortcut(_translate("win_revpicommander", "Ctrl+F"))
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_logs.setText(_translate("win_revpicommander", "PLC &logs..."))
self.act_logs.setShortcut(_translate("win_revpicommander", "F3"))
self.act_options.setText(_translate("win_revpicommander", "PLC &options..."))
self.act_options.setShortcut(_translate("win_revpicommander", "Ctrl+O"))
self.act_program.setText(_translate("win_revpicommander", "PLC progra&m..."))
self.act_program.setShortcut(_translate("win_revpicommander", "Ctrl+P"))
self.act_developer.setText(_translate("win_revpicommander", "PLC de&veloper..."))
self.act_developer.setShortcut(_translate("win_revpicommander", "F9"))
self.act_pictory.setText(_translate("win_revpicommander", "piCtory configuraiton..."))
self.act_disconnect.setText(_translate("win_revpicommander", "&Disconnect"))
self.act_disconnect.setShortcut(_translate("win_revpicommander", "Ctrl+X"))
self.act_reset.setText(_translate("win_revpicommander", "Reset driver..."))
self.act_simulator.setText(_translate("win_revpicommander", "RevPi si&mulator..."))
from . import ressources_rc

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'revpiinfo.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -79,8 +80,8 @@ class Ui_diag_revpiinfo(object):
self.gridLayout.addItem(spacerItem, 5, 0, 1, 1)
self.retranslateUi(diag_revpiinfo)
self.btn_box.accepted.connect(diag_revpiinfo.accept)
self.btn_box.rejected.connect(diag_revpiinfo.reject)
self.btn_box.accepted.connect(diag_revpiinfo.accept) # type: ignore
self.btn_box.rejected.connect(diag_revpiinfo.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(diag_revpiinfo)
def retranslateUi(self, diag_revpiinfo):

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'revpilogfile.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'revpioption.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -115,8 +116,8 @@ class Ui_diag_options(object):
self.verticalLayout.addWidget(self.btn_box)
self.retranslateUi(diag_options)
self.btn_box.accepted.connect(diag_options.accept)
self.btn_box.rejected.connect(diag_options.reject)
self.btn_box.accepted.connect(diag_options.accept) # type: ignore
self.btn_box.rejected.connect(diag_options.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(diag_options)
def retranslateUi(self, diag_options):

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'revpiplclist.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -13,9 +14,98 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_diag_connections(object):
def setupUi(self, diag_connections):
diag_connections.setObjectName("diag_connections")
diag_connections.resize(520, 508)
diag_connections.resize(496, 569)
self.gridLayout = QtWidgets.QGridLayout(diag_connections)
self.gridLayout.setObjectName("gridLayout")
self.tab_properties = QtWidgets.QTabWidget(diag_connections)
self.tab_properties.setObjectName("tab_properties")
self.tab_connection = QtWidgets.QWidget()
self.tab_connection.setObjectName("tab_connection")
self.formLayout_2 = QtWidgets.QFormLayout(self.tab_connection)
self.formLayout_2.setObjectName("formLayout_2")
self.lbl_name = QtWidgets.QLabel(self.tab_connection)
self.lbl_name.setObjectName("lbl_name")
self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lbl_name)
self.txt_name = QtWidgets.QLineEdit(self.tab_connection)
self.txt_name.setObjectName("txt_name")
self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.txt_name)
self.lbl_address = QtWidgets.QLabel(self.tab_connection)
self.lbl_address.setObjectName("lbl_address")
self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lbl_address)
self.txt_address = QtWidgets.QLineEdit(self.tab_connection)
self.txt_address.setObjectName("txt_address")
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.sbx_port = QtWidgets.QSpinBox(self.tab_connection)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.sbx_port.sizePolicy().hasHeightForWidth())
self.sbx_port.setSizePolicy(sizePolicy)
self.sbx_port.setMinimum(1)
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.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.sbx_timeout = QtWidgets.QSpinBox(self.tab_connection)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.sbx_timeout.sizePolicy().hasHeightForWidth())
self.sbx_timeout.setSizePolicy(sizePolicy)
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.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.cbb_folder = QtWidgets.QComboBox(self.tab_connection)
self.cbb_folder.setEditable(True)
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.tab_properties.addTab(self.tab_connection, "")
self.tab_ssh = QtWidgets.QWidget()
self.tab_ssh.setObjectName("tab_ssh")
self.formLayout = QtWidgets.QFormLayout(self.tab_ssh)
self.formLayout.setObjectName("formLayout")
self.lbl_ssh_use_tunnel = QtWidgets.QLabel(self.tab_ssh)
self.lbl_ssh_use_tunnel.setObjectName("lbl_ssh_use_tunnel")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lbl_ssh_use_tunnel)
self.cbx_ssh_use_tunnel = QtWidgets.QCheckBox(self.tab_ssh)
self.cbx_ssh_use_tunnel.setText("")
self.cbx_ssh_use_tunnel.setChecked(True)
self.cbx_ssh_use_tunnel.setObjectName("cbx_ssh_use_tunnel")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.cbx_ssh_use_tunnel)
self.lbl_ssh_port = QtWidgets.QLabel(self.tab_ssh)
self.lbl_ssh_port.setObjectName("lbl_ssh_port")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lbl_ssh_port)
self.sbx_ssh_port = QtWidgets.QSpinBox(self.tab_ssh)
self.sbx_ssh_port.setMaximum(65535)
self.sbx_ssh_port.setProperty("value", 22)
self.sbx_ssh_port.setObjectName("sbx_ssh_port")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.sbx_ssh_port)
self.lbl_ssh_user = QtWidgets.QLabel(self.tab_ssh)
self.lbl_ssh_user.setObjectName("lbl_ssh_user")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.lbl_ssh_user)
self.txt_ssh_user = QtWidgets.QLineEdit(self.tab_ssh)
self.txt_ssh_user.setText("pi")
self.txt_ssh_user.setObjectName("txt_ssh_user")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.txt_ssh_user)
self.tab_properties.addTab(self.tab_ssh, "")
self.gridLayout.addWidget(self.tab_properties, 1, 0, 1, 2)
self.btn_box = QtWidgets.QDialogButtonBox(diag_connections)
self.btn_box.setOrientation(QtCore.Qt.Horizontal)
self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Discard|QtWidgets.QDialogButtonBox.Save)
self.btn_box.setObjectName("btn_box")
self.gridLayout.addWidget(self.btn_box, 2, 0, 1, 2)
self.tre_connections = QtWidgets.QTreeWidget(diag_connections)
self.tre_connections.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.tre_connections.setObjectName("tre_connections")
@@ -53,82 +143,29 @@ class Ui_diag_connections(object):
self.btn_add.setObjectName("btn_add")
self.vl_edit.addWidget(self.btn_add)
self.gridLayout.addLayout(self.vl_edit, 0, 1, 1, 1)
self.gb_properties = QtWidgets.QGroupBox(diag_connections)
self.gb_properties.setObjectName("gb_properties")
self.formLayout = QtWidgets.QFormLayout(self.gb_properties)
self.formLayout.setObjectName("formLayout")
self.lbl_name = QtWidgets.QLabel(self.gb_properties)
self.lbl_name.setObjectName("lbl_name")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lbl_name)
self.lbl_folder = QtWidgets.QLabel(self.gb_properties)
self.lbl_folder.setObjectName("lbl_folder")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.lbl_folder)
self.lbl_address = QtWidgets.QLabel(self.gb_properties)
self.lbl_address.setObjectName("lbl_address")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lbl_address)
self.lbl_port = QtWidgets.QLabel(self.gb_properties)
self.lbl_port.setObjectName("lbl_port")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.lbl_port)
self.txt_name = QtWidgets.QLineEdit(self.gb_properties)
self.txt_name.setObjectName("txt_name")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.txt_name)
self.txt_address = QtWidgets.QLineEdit(self.gb_properties)
self.txt_address.setObjectName("txt_address")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.txt_address)
self.sbx_port = QtWidgets.QSpinBox(self.gb_properties)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.sbx_port.sizePolicy().hasHeightForWidth())
self.sbx_port.setSizePolicy(sizePolicy)
self.sbx_port.setMinimum(1)
self.sbx_port.setMaximum(65535)
self.sbx_port.setProperty("value", 55123)
self.sbx_port.setObjectName("sbx_port")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.sbx_port)
self.cbb_folder = QtWidgets.QComboBox(self.gb_properties)
self.cbb_folder.setEditable(True)
self.cbb_folder.setObjectName("cbb_folder")
self.cbb_folder.addItem("")
self.cbb_folder.setItemText(0, "")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.cbb_folder)
self.lbl_timeout = QtWidgets.QLabel(self.gb_properties)
self.lbl_timeout.setObjectName("lbl_timeout")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.lbl_timeout)
self.sbx_timeout = QtWidgets.QSpinBox(self.gb_properties)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.sbx_timeout.sizePolicy().hasHeightForWidth())
self.sbx_timeout.setSizePolicy(sizePolicy)
self.sbx_timeout.setMinimum(5)
self.sbx_timeout.setMaximum(30)
self.sbx_timeout.setObjectName("sbx_timeout")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.sbx_timeout)
self.gridLayout.addWidget(self.gb_properties, 1, 0, 1, 2)
self.btn_box = QtWidgets.QDialogButtonBox(diag_connections)
self.btn_box.setOrientation(QtCore.Qt.Horizontal)
self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Discard|QtWidgets.QDialogButtonBox.Save)
self.btn_box.setObjectName("btn_box")
self.gridLayout.addWidget(self.btn_box, 2, 0, 1, 2)
self.retranslateUi(diag_connections)
self.btn_box.accepted.connect(diag_connections.accept)
self.btn_box.rejected.connect(diag_connections.reject)
self.tab_properties.setCurrentIndex(0)
self.btn_box.accepted.connect(diag_connections.accept) # type: ignore
self.btn_box.rejected.connect(diag_connections.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(diag_connections)
def retranslateUi(self, diag_connections):
_translate = QtCore.QCoreApplication.translate
diag_connections.setWindowTitle(_translate("diag_connections", "Revolution Pi connections"))
self.tre_connections.headerItem().setText(0, _translate("diag_connections", "Connection name"))
self.tre_connections.headerItem().setText(1, _translate("diag_connections", "Address"))
self.gb_properties.setTitle(_translate("diag_connections", "Connection properties"))
self.lbl_name.setText(_translate("diag_connections", "Display name:"))
self.lbl_folder.setText(_translate("diag_connections", "Sub folder:"))
self.lbl_address.setText(_translate("diag_connections", "Address (DNS/IP):"))
self.lbl_port.setText(_translate("diag_connections", "Port (Default {0}):"))
self.lbl_timeout.setText(_translate("diag_connections", "Connection timeout:"))
self.sbx_timeout.setSuffix(_translate("diag_connections", " sec."))
self.lbl_folder.setText(_translate("diag_connections", "Sub folder:"))
self.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(1, _translate("diag_connections", "Address"))
from . import ressources_rc

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'revpiprogram.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -104,8 +105,8 @@ class Ui_diag_program(object):
self.verticalLayout.addWidget(self.btn_box)
self.retranslateUi(diag_program)
self.btn_box.accepted.connect(diag_program.accept)
self.btn_box.rejected.connect(diag_program.reject)
self.btn_box.accepted.connect(diag_program.accept) # type: ignore
self.btn_box.rejected.connect(diag_program.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(diag_program)
diag_program.setTabOrder(self.cbb_plcprogram, self.txt_plcarguments)
diag_program.setTabOrder(self.txt_plcarguments, self.rbn_pythonversion_2)

View File

@@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'simulator.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING! All changes made in this file will be lost!
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -92,9 +93,9 @@ class Ui_diag_simulator(object):
self.verticalLayout.addWidget(self.btn_start_nochange)
self.retranslateUi(diag_simulator)
self.btn_start_empty.clicked.connect(diag_simulator.accept)
self.btn_start_nochange.clicked.connect(diag_simulator.accept)
self.btn_start_pictory.clicked.connect(diag_simulator.accept)
self.btn_start_empty.clicked.connect(diag_simulator.accept) # type: ignore
self.btn_start_nochange.clicked.connect(diag_simulator.accept) # type: ignore
self.btn_start_pictory.clicked.connect(diag_simulator.accept) # type: ignore
QtCore.QMetaObject.connectSlotsByName(diag_simulator)
diag_simulator.setTabOrder(self.cbb_history, self.btn_configrsc)
diag_simulator.setTabOrder(self.btn_configrsc, self.cbx_stop_remove)

View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'sshauth.ui'
#
# Created by: PyQt5 UI code generator 5.15.7
#
# 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.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_diag_sshauth(object):
def setupUi(self, diag_sshauth):
diag_sshauth.setObjectName("diag_sshauth")
diag_sshauth.setWindowModality(QtCore.Qt.ApplicationModal)
diag_sshauth.resize(275, 170)
self.verticalLayout = QtWidgets.QVBoxLayout(diag_sshauth)
self.verticalLayout.setObjectName("verticalLayout")
self.wid_password = QtWidgets.QWidget(diag_sshauth)
self.wid_password.setObjectName("wid_password")
self.formLayout = QtWidgets.QFormLayout(self.wid_password)
self.formLayout.setObjectName("formLayout")
self.lbl_username = QtWidgets.QLabel(self.wid_password)
self.lbl_username.setObjectName("lbl_username")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lbl_username)
self.lbl_password = QtWidgets.QLabel(self.wid_password)
self.lbl_password.setObjectName("lbl_password")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lbl_password)
self.txt_password = QtWidgets.QLineEdit(self.wid_password)
self.txt_password.setEchoMode(QtWidgets.QLineEdit.Password)
self.txt_password.setObjectName("txt_password")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.txt_password)
self.txt_username = QtWidgets.QLineEdit(self.wid_password)
self.txt_username.setObjectName("txt_username")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.txt_username)
self.verticalLayout.addWidget(self.wid_password)
self.wid_keys = QtWidgets.QWidget(diag_sshauth)
self.wid_keys.setObjectName("wid_keys")
self.verticalLayout.addWidget(self.wid_keys)
self.buttonBox = QtWidgets.QDialogButtonBox(diag_sshauth)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(diag_sshauth)
self.buttonBox.accepted.connect(diag_sshauth.accept) # type: ignore
self.buttonBox.rejected.connect(diag_sshauth.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(diag_sshauth)
def retranslateUi(self, diag_sshauth):
_translate = QtCore.QCoreApplication.translate
diag_sshauth.setWindowTitle(_translate("diag_sshauth", "SSH authentication"))
self.lbl_username.setText(_translate("diag_sshauth", "SSH username:"))
self.lbl_password.setText(_translate("diag_sshauth", "SSH password:"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
diag_sshauth = QtWidgets.QDialog()
ui = Ui_diag_sshauth()
ui.setupUi(diag_sshauth)
diag_sshauth.show()
sys.exit(app.exec_())

View File

@@ -1,6 +1,6 @@
[DEFAULT]
Debian-Version=1
Depends3=python3-pyqt5, python3-revpimodio2, python3-zeroconf
Depends3=python3-pyqt5, python3-revpimodio2 (>= 2.5.0), python3-zeroconf (>= 0.24.4)
Section=universe/x11
Suite=stable
X-Python3-Version: >=3.4

View File

@@ -1,31 +1,34 @@
SOURCES = revpicommander/aclmanager.py \
revpicommander/avahisearch.py \
revpicommander/debugcontrol.py \
revpicommander/debugios.py \
revpicommander/mqttmanager.py \
revpicommander/revpifiles.py \
revpicommander/revpiinfo.py \
revpicommander/revpilogfile.py \
revpicommander/revpioption.py \
revpicommander/revpiplclist.py \
revpicommander/revpiprogram.py \
revpicommander/simulator.py \
revpicommander/revpicommander.py
SOURCES = src/revpicommander/aclmanager.py \
src/revpicommander/avahisearch.py \
src/revpicommander/debugcontrol.py \
src/revpicommander/debugios.py \
src/revpicommander/helper.py \
src/revpicommander/mqttmanager.py \
src/revpicommander/revpifiles.py \
src/revpicommander/revpiinfo.py \
src/revpicommander/revpilogfile.py \
src/revpicommander/revpioption.py \
src/revpicommander/revpiplclist.py \
src/revpicommander/revpiprogram.py \
src/revpicommander/simulator.py \
src/revpicommander/revpicommander.py
FORMS = include/ui_dev/aclmanager.ui \
include/ui_dev/avahisearch.ui \
include/ui_dev/debugcontrol.ui \
include/ui_dev/debugios.ui \
include/ui_dev/files.ui \
include/ui_dev/mqttmanager.ui \
include/ui_dev/revpiinfo.ui \
include/ui_dev/revpilogfile.ui \
include/ui_dev/revpioption.ui \
include/ui_dev/revpiplclist.ui \
include/ui_dev/revpiprogram.ui \
include/ui_dev/simulator.ui \
include/ui_dev/revpicommander.ui
FORMS = ui_dev/aclmanager.ui \
ui_dev/avahisearch.ui \
ui_dev/backgroundworker.ui \
ui_dev/debugcontrol.ui \
ui_dev/debugios.ui \
ui_dev/files.ui \
ui_dev/mqttmanager.ui \
ui_dev/revpiinfo.ui \
ui_dev/revpilogfile.ui \
ui_dev/revpioption.ui \
ui_dev/revpiplclist.ui \
ui_dev/revpiprogram.ui \
ui_dev/simulator.ui \
ui_dev/sshauth.ui \
ui_dev/revpicommander.ui
TRANSLATIONS = revpicommander/locale/revpicommander_de.ts
TRANSLATIONS = src/revpicommander/locale/revpicommander_de.ts
CODECRORTR = UTF-8

View File

@@ -145,7 +145,7 @@
<item>
<widget class="QLabel" name="lbl_ip_a">
<property name="text">
<string>.</string>
<string notr="true">.</string>
</property>
</widget>
</item>
@@ -159,7 +159,7 @@
<item>
<widget class="QLabel" name="lbl_ip_b">
<property name="text">
<string>.</string>
<string notr="true">.</string>
</property>
</widget>
</item>
@@ -173,7 +173,7 @@
<item>
<widget class="QLabel" name="lbl_ip_c">
<property name="text">
<string>.</string>
<string notr="true">.</string>
</property>
</widget>
</item>

View File

@@ -120,6 +120,37 @@
</widget>
</item>
</layout>
<action name="act_copy_host">
<property name="text">
<string>Copy host name</string>
</property>
</action>
<action name="act_copy_ip">
<property name="text">
<string>Copy IP address</string>
</property>
</action>
<action name="act_open_pictory">
<property name="text">
<string>Open piCtory</string>
</property>
</action>
<action name="act_connect_ssh">
<property name="text">
<string>Connect via SSH (recommended)</string>
</property>
<property name="toolTip">
<string>Establish a connection via encrypted SSH tunnel</string>
</property>
</action>
<action name="act_connect_xmlrpc">
<property name="text">
<string>Connect via XML-RPC</string>
</property>
<property name="toolTip">
<string>You have to configure your Revolution Pi to accept this connections</string>
</property>
</action>
</widget>
<resources>
<include location="ressources.qrc"/>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>diag_backgroundworker</class>
<widget class="QDialog" name="diag_backgroundworker">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>418</width>
<height>97</height>
</rect>
</property>
<property name="windowTitle">
<string>File transfer...</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="lbl_status">
<property name="text">
<string notr="true">Status message...</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="pgb_status">
<property name="minimumSize">
<size>
<width>400</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="btn_box">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel</set>
</property>
<property name="centerButtons">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -59,7 +59,7 @@
<string>Read &amp;all IO values</string>
</property>
<property name="shortcut">
<string>F4</string>
<string notr="true">F4</string>
</property>
</widget>
</item>
@@ -72,7 +72,7 @@
<string>&amp;Refresh unchanged IOs</string>
</property>
<property name="shortcut">
<string>F5</string>
<string notr="true">F5</string>
</property>
</widget>
</item>
@@ -85,7 +85,7 @@
<string>&amp;Write changed outputs</string>
</property>
<property name="shortcut">
<string>F6</string>
<string notr="true">F6</string>
</property>
</widget>
</item>

View File

@@ -84,7 +84,7 @@
</sizepolicy>
</property>
<property name="toolTip">
<string>/</string>
<string notr="true">/</string>
</property>
<property name="text">
<string notr="true">/</string>
@@ -180,7 +180,7 @@
</sizepolicy>
</property>
<property name="toolTip">
<string>/</string>
<string notr="true">/</string>
</property>
<property name="text">
<string notr="true">/</string>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
ui_dev/ico/edit-find.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
ui_dev/ico/process-stop.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
ui_dev/ico/system-run.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
ui_dev/ico/system-search.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
ui_dev/ico/view-refresh.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -9,6 +9,12 @@
<file>ico/file-python.ico</file>
</qresource>
<qresource prefix="action">
<file>ico/applications-utilities.ico</file>
<file>ico/edit-find.ico</file>
<file>ico/process-stop.ico</file>
<file>ico/system-run.ico</file>
<file>ico/system-search.ico</file>
<file>ico/view-refresh.ico</file>
<file>ico/arrow-left.ico</file>
<file>ico/arrow-right.ico</file>
<file>ico/folder-open.ico</file>

View File

@@ -7,71 +7,121 @@
<rect>
<x>0</x>
<y>0</y>
<width>318</width>
<height>273</height>
<width>353</width>
<height>299</height>
</rect>
</property>
<property name="windowTitle">
<string>RevPi Python PLC Commander</string>
<string notr="true">RevPi Commander</string>
</property>
<property name="windowIcon">
<iconset resource="ressources.qrc">
<normaloff>:/main/ico/revpipycontrol.ico</normaloff>:/main/ico/revpipycontrol.ico</iconset>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gl">
<item row="0" column="0">
<widget class="QLineEdit" name="txt_connection">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
<layout class="QVBoxLayout" name="gl">
<item>
<layout class="QHBoxLayout" name="hzl_connection">
<item>
<widget class="QLineEdit" name="txt_host">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="txt_connection">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<item>
<widget class="QPushButton" name="btn_plc_start">
<property name="text">
<string>PLC &amp;start</string>
</property>
<property name="icon">
<iconset resource="ressources.qrc">
<normaloff>:/action/ico/system-run.ico</normaloff>:/action/ico/system-run.ico</iconset>
</property>
</widget>
</item>
<item row="2" column="0">
<item>
<widget class="QPushButton" name="btn_plc_stop">
<property name="text">
<string>PLC s&amp;top</string>
</property>
<property name="icon">
<iconset resource="ressources.qrc">
<normaloff>:/action/ico/process-stop.ico</normaloff>:/action/ico/process-stop.ico</iconset>
</property>
</widget>
</item>
<item row="3" column="0">
<item>
<widget class="QPushButton" name="btn_plc_restart">
<property name="text">
<string>PLC restart</string>
</property>
<property name="icon">
<iconset resource="ressources.qrc">
<normaloff>:/action/ico/view-refresh.ico</normaloff>:/action/ico/view-refresh.ico</iconset>
</property>
</widget>
</item>
<item row="4" column="0">
<item>
<widget class="QPushButton" name="btn_plc_logs">
<property name="text">
<string>PLC &amp;logs</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLineEdit" name="txt_status">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="readOnly">
<bool>true</bool>
<property name="icon">
<iconset resource="ressources.qrc">
<normaloff>:/action/ico/applications-utilities.ico</normaloff>:/action/ico/applications-utilities.ico</iconset>
</property>
</widget>
</item>
<item row="6" column="0">
<item>
<layout class="QHBoxLayout" name="hzl_status">
<item>
<widget class="QLabel" name="lbl_status">
<property name="text">
<string>Status:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="txt_status">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="btn_plc_debug">
<property name="minimumSize">
<size>
@@ -82,6 +132,10 @@
<property name="text">
<string>PLC watch &amp;mode</string>
</property>
<property name="icon">
<iconset resource="ressources.qrc">
<normaloff>:/action/ico/edit-find.ico</normaloff>:/action/ico/edit-find.ico</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
@@ -94,8 +148,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>318</width>
<height>22</height>
<width>353</width>
<height>24</height>
</rect>
</property>
<widget class="QMenu" name="men_file">
@@ -150,13 +204,16 @@
<property name="text">
<string>&amp;Connections...</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+N</string>
</property>
</action>
<action name="act_search">
<property name="text">
<string>&amp;Search Revolution Pi...</string>
</property>
<property name="shortcut">
<string>Ctrl+F</string>
<string notr="true">Ctrl+F</string>
</property>
</action>
<action name="act_quit">
@@ -179,7 +236,7 @@
<string>PLC &amp;logs...</string>
</property>
<property name="shortcut">
<string>F3</string>
<string notr="true">Ctrl+L</string>
</property>
</action>
<action name="act_options">
@@ -187,7 +244,7 @@
<string>PLC &amp;options...</string>
</property>
<property name="shortcut">
<string>Ctrl+O</string>
<string notr="true">Ctrl+O</string>
</property>
</action>
<action name="act_program">
@@ -195,7 +252,7 @@
<string>PLC progra&amp;m...</string>
</property>
<property name="shortcut">
<string>Ctrl+P</string>
<string notr="true">Ctrl+P</string>
</property>
</action>
<action name="act_developer">
@@ -203,7 +260,7 @@
<string>PLC de&amp;veloper...</string>
</property>
<property name="shortcut">
<string>F9</string>
<string notr="true">Ctrl+D</string>
</property>
</action>
<action name="act_pictory">
@@ -216,7 +273,7 @@
<string>&amp;Disconnect</string>
</property>
<property name="shortcut">
<string>Ctrl+X</string>
<string notr="true">Ctrl+X</string>
</property>
</action>
<action name="act_reset">

304
ui_dev/revpiplclist.ui Normal file
View File

@@ -0,0 +1,304 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>diag_connections</class>
<widget class="QDialog" name="diag_connections">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>496</width>
<height>569</height>
</rect>
</property>
<property name="windowTitle">
<string>Revolution Pi connections</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="2">
<widget class="QTabWidget" name="tab_properties">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_connection">
<attribute name="title">
<string>Connection</string>
</attribute>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="lbl_name">
<property name="text">
<string>Display name:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="txt_name"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lbl_address">
<property name="text">
<string>Address (DNS/IP):</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="txt_address"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lbl_port">
<property name="text">
<string>Port (Default {0}):</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="sbx_port">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>55123</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="lbl_timeout">
<property name="text">
<string>Connection timeout:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="sbx_timeout">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string> sec.</string>
</property>
<property name="minimum">
<number>5</number>
</property>
<property name="maximum">
<number>30</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="lbl_folder">
<property name="text">
<string>Sub folder:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="cbb_folder">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string/>
</property>
</item>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_ssh">
<attribute name="title">
<string>Over SSH</string>
</attribute>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="lbl_ssh_use_tunnel">
<property name="text">
<string>Connect over SSH tunnel:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="cbx_ssh_use_tunnel">
<property name="text">
<string notr="true"/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lbl_ssh_port">
<property name="text">
<string>SSH port:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="sbx_ssh_port">
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>22</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lbl_ssh_user">
<property name="text">
<string>SSH user name:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="txt_ssh_user">
<property name="text">
<string notr="true">pi</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QDialogButtonBox" name="btn_box">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Discard|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QTreeWidget" name="tre_connections">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<column>
<property name="text">
<string>Connection name</string>
</property>
</column>
<column>
<property name="text">
<string>Address</string>
</property>
</column>
</widget>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="vl_edit">
<item>
<spacer name="vs_edit">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_up">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="ressources.qrc">
<normaloff>:/action/ico/arrow-up.ico</normaloff>:/action/ico/arrow-up.ico</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_down">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="ressources.qrc">
<normaloff>:/action/ico/arrow-down.ico</normaloff>:/action/ico/arrow-down.ico</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_delete">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="ressources.qrc">
<normaloff>:/action/ico/edit-delete.ico</normaloff>:/action/ico/edit-delete.ico</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_add">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="ressources.qrc">
<normaloff>:/action/ico/edit-add.ico</normaloff>:/action/ico/edit-add.ico</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="ressources.qrc"/>
</resources>
<connections>
<connection>
<sender>btn_box</sender>
<signal>accepted()</signal>
<receiver>diag_connections</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>btn_box</sender>
<signal>rejected()</signal>
<receiver>diag_connections</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

100
ui_dev/sshauth.ui Normal file
View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>diag_sshauth</class>
<widget class="QDialog" name="diag_sshauth">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>275</width>
<height>170</height>
</rect>
</property>
<property name="windowTitle">
<string>SSH authentication</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QWidget" name="wid_password" native="true">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="lbl_username">
<property name="text">
<string>SSH username:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lbl_password">
<property name="text">
<string>SSH password:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="txt_password">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="txt_username"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="wid_keys" native="true"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>diag_sshauth</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>diag_sshauth</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>