37 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
8a2cd9f304 New release 2021-04-25 13:27:42 +02:00
6be0c8d32f Modified translations 2021-04-25 13:17:10 +02:00
3ea43e2931 Bugfix for simulator to prevent GUI crash after first use 2021-04-25 12:44:40 +02:00
cda60e119c Add a simulator dialog to manage simulator settings 2021-04-25 11:55:19 +02:00
f0e6f64389 Add parameter to change connection timeout (default was and is 5 seconds) 2021-04-18 11:03:51 +02:00
2341e2be95 Bugfix for translations strings and format function 2021-04-15 16:09:03 +02:00
9f8409f375 Get AVAHI IPv4 addresses with service_info 2021-04-10 15:42:04 +02:00
91 changed files with 7051 additions and 3216 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,11 +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)" />
</component>
</module>

8
.idea/vcs.xml generated
View File

@@ -1,7 +1,13 @@
<?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" />
<mapping directory="$PROJECT_DIR$/lib/revpimodio2" vcs="Git" />
</component>
</project>

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,218 +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="3" 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="3" 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>
</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

7
requirements.txt Normal file
View File

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

View File

@@ -1,239 +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 socket import gethostbyname
from PyQt5 import QtCore, QtGui, QtWidgets
from zeroconf import 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
try:
ip = gethostbyname(info.server)
except Exception:
ip = self.tr("N/A")
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.1",
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):
@@ -112,7 +112,8 @@ class DebugIos(QtWidgets.QMainWindow, Ui_win_debugios):
self.splitter.setSizes([1, 1])
def _create_widget(self, name: str, byte_length: int, bit_address: int, byteorder: str, signed: bool, read_only: bool):
def _create_widget(
self, name: str, byte_length: int, bit_address: int, byteorder: str, signed: bool, read_only: bool):
"""Create widget in functions address space to use lambda functions."""
if bit_address >= 0:
val = QtWidgets.QCheckBox()
@@ -339,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
@@ -9,13 +9,17 @@ import socket
from enum import IntEnum
from http.client import CannotSendRequest
from os import environ, remove
from os.path import exists
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):
@@ -25,6 +29,8 @@ class WidgetData(IntEnum):
has_error = 263
port = 264
object_name = 265
timeout = 266
host_name = 267
last_dir_upload = 301
last_file_upload = 302
last_dir_pictory = 303
@@ -37,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):
@@ -52,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)
@@ -68,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 = ""
@@ -164,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
@@ -204,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
"""
@@ -218,9 +242,17 @@ class ConnectionManager(QtCore.QThread):
settings.beginReadArray("connections")
settings.setArrayIndex(settings_index)
address = settings.value("address")
name = settings.value("name")
port = settings.value("port", defaultValue=55123)
address = settings.value("address", str)
name = settings.value("name", str)
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)
@@ -228,8 +260,8 @@ class ConnectionManager(QtCore.QThread):
self.program_last_dir_picontrol = settings.value("last_dir_picontrol", ".", str)
self.program_last_dir_selected = settings.value("last_dir_selected", ".", str)
self.program_last_pictory_file = settings.value("last_pictory_file", "{0}.rsc".format(name), str)
self.program_last_tar_file = settings.value("last_tar_file", "{0}.tgz".format(name))
self.program_last_zip_file = settings.value("last_zip_file", "{0}.zip".format(name))
self.program_last_tar_file = settings.value("last_tar_file", "{0}.tgz".format(name), str)
self.program_last_zip_file = settings.value("last_zip_file", "{0}.zip".format(name), str)
self.develop_watch_files = settings.value("watch_files", [], list)
self.develop_watch_path = settings.value("watch_path", "", str)
self.debug_geos = settings.value("debug_geos", {}, dict)
@@ -237,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:
@@ -245,20 +312,43 @@ class ConnectionManager(QtCore.QThread):
xml_funcs = sp.system.listMethods()
xml_mode = sp.xmlmodus()
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(5)
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()
@@ -271,7 +361,8 @@ class ConnectionManager(QtCore.QThread):
self._revpi.cleanup()
self._revpi_output.cleanup()
remove(self._revpi.procimg)
if settings.value("simulator/stop_remove", False, bool):
remove(self._revpi.procimg)
self._revpi = None
self._revpi_output = None
@@ -280,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()
@@ -293,19 +384,24 @@ 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):
def pyload_simulate(self, configrsc: str, procimg: str, clean_existing: bool):
"""Start the simulator for piControl on local computer."""
pi.logger.debug("ConnectionManager.start_simulate")
with open(procimg, "wb") as fh:
fh.write(b'\x00' * 4096)
if not exists(procimg) or clean_existing:
with open(procimg, "wb") as fh:
fh.write(b'\x00' * 4096)
try:
import revpimodio2
# Prepare process image with default values
# Prepare process image with default values for outputs
self._revpi_output = revpimodio2.RevPiModIO(configrsc=configrsc, procimg=procimg)
self._revpi_output.setdefaultvalues()
self._revpi_output.writeprocimg()
@@ -317,11 +413,15 @@ class ConnectionManager(QtCore.QThread):
self.xml_funcs = ["psstart", "psstop", "ps_devices", "ps_inps", "ps_outs", "ps_values", "ps_setvalue"]
self.connection_established.emit()
except Exception as e:
pi.logger.exception(e)
self.connection_error_observed.emit(str(e))
self._revpi_output = None
self._revpi = None
remove(procimg)
if settings.value("simulator/stop_remove", False, bool):
remove(procimg)
return self._revpi is not None
@@ -332,9 +432,14 @@ class ConnectionManager(QtCore.QThread):
def reset_simulator(self):
"""Reset all io to piCtory defaults."""
pi.logger.debug("ConnectionManager.reset_simulator")
self._revpi_output.writeprocimg()
self._revpi.setdefaultvalues()
self._revpi.writeprocimg()
if settings.value("simulator/restart_zero", False, bool):
with open(self._revpi.procimg, "wb") as fh:
fh.write(b'\x00' * 4096)
self._revpi.readprocimg()
else:
self._revpi_output.writeprocimg()
self._revpi.setdefaultvalues()
self._revpi.writeprocimg()
def run(self):
"""Thread worker to check status of RevPiPyLoad."""
@@ -345,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()
@@ -365,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)
@@ -436,22 +558,36 @@ 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):
def connected(self) -> bool:
"""True if we have an active connection."""
return self._cli is not None
@property
def simulating(self):
def simulating(self) -> bool:
"""True, if simulating mode is running."""
return self._revpi is not None
@property
def simulating_configrsc(self) -> str:
return self._revpi.configrsc if self._revpi else ""
@property
def simulating_procimg(self) -> str:
return self._revpi.procimg if self._revpi else ""
cm = ConnectionManager()
"""Clobal connection manager instance."""

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
@@ -85,11 +85,6 @@ parser = ArgumentParser(
prog=programname,
description="Program description"
)
# parser.add_argument(
# "-c", "--conffile", dest="conffile",
# default="{0}.conf".format(programname),
# help="Application configuration file"
# )
parser.add_argument(
"-f", "--logfile", dest="logfile",
help="Save log entries to this file"

View File

@@ -5,24 +5,25 @@
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__license__ = "GPLv3"
__version__ = "0.9.1"
__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 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):
@@ -62,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()
@@ -69,24 +72,33 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
def _set_gui_control_states(self):
"""Setup states of actions and buttons."""
connected = helper.cm.connected
self.men_plc.setEnabled(connected)
self.act_logs.setEnabled(connected)
self.act_options.setEnabled(connected and helper.cm.xml_mode >= 2)
self.act_program.setEnabled(connected and helper.cm.xml_mode >= 2)
self.act_developer.setEnabled(connected and helper.cm.xml_mode >= 3)
self.act_pictory.setEnabled(connected)
self.act_reset.setEnabled(connected and helper.cm.xml_mode >= 3)
self.act_disconnect.setEnabled(connected)
self.btn_plc_start.setEnabled(connected)
self.btn_plc_stop.setEnabled(connected)
self.btn_plc_restart.setEnabled(connected)
self.btn_plc_logs.setEnabled(connected)
self.btn_plc_debug.setEnabled(connected and helper.cm.xml_mode >= 1)
if helper.cm.simulating:
self.btn_plc_stop.setEnabled(True) # Stop simulator
self.btn_plc_restart.setEnabled(True) # Reset simulator values
self.btn_plc_debug.setEnabled(True)
else:
connected = helper.cm.connected
self.men_plc.setEnabled(connected)
self.act_logs.setEnabled(connected)
self.act_options.setEnabled(connected and helper.cm.xml_mode >= 2)
self.act_program.setEnabled(connected and helper.cm.xml_mode >= 2)
self.act_developer.setEnabled(connected and helper.cm.xml_mode >= 3)
self.act_pictory.setEnabled(connected)
self.act_reset.setEnabled(connected and helper.cm.xml_mode >= 3)
self.act_disconnect.setEnabled(connected)
self.btn_plc_start.setEnabled(connected)
self.btn_plc_stop.setEnabled(connected)
self.btn_plc_restart.setEnabled(connected)
self.btn_plc_logs.setEnabled(connected)
self.btn_plc_debug.setEnabled(connected and helper.cm.xml_mode >= 1)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# 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):
"""
@@ -102,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()
@@ -127,11 +141,15 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
pi.logger.debug("RevPiCommander.on_cm_connection_established")
self._set_gui_control_states()
self.txt_connection.setText("{0} - {1}:{2}".format(
helper.cm.name,
helper.cm.address,
helper.cm.port
))
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_host.setText(helper.cm.name)
self.txt_connection.setText(helper.cm.address)
self.win_files = RevPiFiles(self)
@QtCore.pyqtSlot(str, str)
@@ -154,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()
@@ -186,59 +208,41 @@ 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()
@QtCore.pyqtSlot()
def on_act_simulator_triggered(self):
"""Start the simulator function."""
helper.cm.pyload_disconnect()
diag_open = QtWidgets.QFileDialog(
self, self.tr("Select downloaded piCtory file..."),
helper.settings.value("simulator_pictory", ".", str),
self.tr("piCtory file (*.rsc);;All files (*.*)")
)
diag_open.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
diag_open.setFileMode(QtWidgets.QFileDialog.ExistingFile)
diag_open.setDefaultSuffix("rsc")
if diag_open.exec() != QtWidgets.QFileDialog.AcceptSave or len(diag_open.selectedFiles()) != 1:
diag = Simulator(self)
if diag.exec() != QtWidgets.QDialog.Accepted:
diag.deleteLater()
return
configrsc_file = diag_open.selectedFiles()[0]
dir_name = dirname(configrsc_file)
procimg_file = join(dir_name, "{0}.img".format(
basename(configrsc_file).rsplit(".", maxsplit=1)[0]
))
helper.settings.setValue("simulator_pictory", configrsc_file)
helper.cm.pyload_disconnect()
configrsc_file = helper.settings.value("simulator/configrsc", "", str)
procimg_file = helper.settings.value("simulator/procimg", "", str)
if helper.cm.pyload_simulate(configrsc_file, procimg_file):
if helper.cm.pyload_simulate(configrsc_file, procimg_file, diag.cbx_stop_remove.isChecked()):
QtWidgets.QMessageBox.information(
self, self.tr("Simulator started..."), self.tr(
"The simulator is running!\n\nYou can work with this simulator if your call "
"RevPiModIO with this additional parameters:\nprocimg={procimg}\nconfigrsc={config}\n\n"
"RevPiModIO with this additional parameters:\nprocimg={0}\nconfigrsc={1}\n\n"
"You can copy that from header textbox."
).format(procimg=procimg_file, config=configrsc_file)
).format(procimg_file, configrsc_file)
)
self.txt_connection.setText(
"configrsc=\"{0}\", procimg=\"{1}\"".format(configrsc_file, procimg_file)
)
# Stop button will disable simulating
self.btn_plc_stop.setEnabled(True)
self.btn_plc_restart.setEnabled(True)
self.btn_plc_debug.setEnabled(True)
else:
pi.logger.error("Can not start simulator")
QtWidgets.QMessageBox.critical(
self, self.tr("Can not start..."), self.tr(
"Can not start the simulator! Maybe the piCtory file is corrupt "
"or you can not write to the location '{0}'.".format(dir_name)
)
"or you have no write permissions for '{0}'."
).format(procimg_file)
)
diag.deleteLater()
@QtCore.pyqtSlot()
def on_act_logs_triggered(self):
"""Show log window."""
@@ -249,8 +253,7 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
QtWidgets.QMessageBox.warning(
self, self.tr("Warning"), self.tr(
"This version of Logviewer ist not supported in version {0} "
"of RevPiPyLoad on your RevPi! You need at least version "
"0.4.1."
"of RevPiPyLoad on your RevPi! You need at least version 0.4.1."
).format(helper.cm.call_remote_function("version", default_value="-"))
)
return None
@@ -364,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):
@@ -410,10 +403,12 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
if helper.cm.simulating:
rc = QtWidgets.QMessageBox.question(
self, self.tr("Reset to piCtory defaults..."), self.tr(
"Do you want to reset your process image to piCtory default values?\n"
"Do you want to reset your process image to {0} values?\n"
"You have to stop other RevPiModIO programs before doing that, "
"because they could reset the outputs."
)
).format(
self.tr("zero") if helper.settings.value("simulator/restart_zero", False, bool)
else self.tr("piCtory default"))
) == QtWidgets.QMessageBox.Yes
if rc:
# Set piCtory default values in process image
@@ -456,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(
@@ -474,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
@@ -497,10 +495,15 @@ if __name__ == "__main__":
win = RevPiCommander()
win.show()
exit_code = app.exec_()
exit_code = app.exec()
# Clean up workers
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):
@@ -20,18 +20,17 @@ 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
@@ -45,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'
@@ -56,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:
@@ -83,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
@@ -109,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)
@@ -120,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):
@@ -25,7 +24,6 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
def __init__(self, parent=None):
super(RevPiPlcList, self).__init__(parent)
self.setupUi(self)
self.setFixedSize(self.size())
self.__default_name = self.tr("New connection")
self.__default_port = 55123
@@ -43,28 +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.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,51 +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))
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()
@@ -192,8 +204,13 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
self.txt_name.setEnabled(con_item)
self.txt_address.setEnabled(con_item)
self.sbx_port.setEnabled(con_item)
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()):
@@ -235,10 +252,16 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
self.txt_name.setText(current.text(0))
self.txt_address.setText(current.text(1))
self.sbx_port.setValue(current.data(0, WidgetData.port))
self.sbx_timeout.setValue(current.data(0, WidgetData.timeout))
if current.parent() is None:
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))
@@ -275,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)
@@ -303,6 +330,30 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
return
self.__current_item.setData(0, WidgetData.port, value)
@QtCore.pyqtSlot(int)
def on_sbx_timeout_valueChanged(self, value: int):
if self.__current_item.type() != NodeType.CON:
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
@@ -9,14 +9,14 @@ import os
import tarfile
import zipfile
from shutil import rmtree
from tempfile import mkdtemp, mkstemp
from tempfile import mkdtemp
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):
@@ -174,7 +174,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program):
self._load_settings()
self._apply_acl()
return super(RevPiProgram, self).exec()
def reject(self) -> None:
@@ -635,8 +635,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program):
QtWidgets.QMessageBox.information(
self, self.tr("Success"), self.tr(
"piCtory configuration successfully loaded and saved to:\n{0}."
"".format(filename)
)
).format(filename)
)
@QtCore.pyqtSlot()
@@ -699,8 +698,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program):
QtWidgets.QMessageBox.information(
self, self.tr("Success"), self.tr(
"Process image successfully loaded and saved to:\n{0}."
"".format(filename)
)
).format(filename)
)
# endregion # # # # #

View File

@@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-
"""Simulator for piControl."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
from os import W_OK, access
from os.path import basename, dirname, exists, join
from PyQt5 import QtCore, QtGui, QtWidgets
from . import helper
from .ui.simulator_ui import Ui_diag_simulator
class Simulator(QtWidgets.QDialog, Ui_diag_simulator):
"""
This is a configuration dialog for the simulator of piControl. The
selected values will be saved in QSettings section 'simulator' and can be
accessed by simulator starting classes.
"""
def __init__(self, parent=None):
super(Simulator, self).__init__(parent)
self.setupUi(self)
self.clean_procimg = False
self.max_items = 5
self.cbb_history.addItem("", "")
lst_configrsc = helper.settings.value("simulator/history_configrsc", [], list)
lst_procimg = helper.settings.value("simulator/history_procimg", [], list)
for i in range(len(lst_configrsc)):
self.cbb_history.addItem(lst_configrsc[i], lst_procimg[i])
self.cbx_stop_remove.setChecked(helper.settings.value("simulator/stop_remove", False, bool))
self.rb_restart_pictory.setChecked(helper.settings.value("simulator/restart_pictory", False, bool))
self.rb_restart_zero.setChecked(helper.settings.value("simulator/restart_zero", False, bool))
self.btn_start_pictory.setEnabled(False)
self.btn_start_empty.setEnabled(False)
self.btn_start_nochange.setEnabled(False)
self.txt_configrsc.textChanged.connect(self.on_txt_textChanged)
self.txt_procimg.textChanged.connect(self.on_txt_textChanged)
def _save_gui(self) -> None:
helper.settings.setValue("simulator/stop_remove", self.cbx_stop_remove.isChecked())
helper.settings.setValue("simulator/restart_pictory", self.rb_restart_pictory.isChecked())
helper.settings.setValue("simulator/restart_zero", self.rb_restart_zero.isChecked())
def accept(self) -> None:
self.cbb_history.removeItem(0)
if self.cbb_history.findText(self.txt_configrsc.text()) == -1:
self.cbb_history.addItem(self.txt_configrsc.text(), self.txt_procimg.text())
if self.cbb_history.count() > self.max_items:
self.cbb_history.removeItem(self.max_items)
helper.settings.setValue("simulator/configrsc", self.txt_configrsc.text())
helper.settings.setValue("simulator/procimg", self.txt_procimg.text())
self._save_gui()
lst_configrsc = []
lst_procimg = []
for i in range(self.cbb_history.count()):
lst_configrsc.append(self.cbb_history.itemText(i))
lst_procimg.append(self.cbb_history.itemData(i))
helper.settings.setValue("simulator/history_configrsc", lst_configrsc)
helper.settings.setValue("simulator/history_procimg", lst_procimg)
self.clean_procimg = self.sender() is self.btn_start_empty
super(Simulator, self).accept()
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
self._save_gui()
@QtCore.pyqtSlot()
def on_btn_configrsc_clicked(self) -> None:
diag_open = QtWidgets.QFileDialog(
self, self.tr("Select downloaded piCtory file..."),
helper.settings.value("simulator/last_dir", ".", str),
self.tr("piCtory file (*.rsc);;All files (*.*)")
)
diag_open.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
diag_open.setFileMode(QtWidgets.QFileDialog.ExistingFile)
diag_open.setDefaultSuffix("rsc")
if diag_open.exec() != QtWidgets.QFileDialog.AcceptSave or len(diag_open.selectedFiles()) != 1:
diag_open.deleteLater()
return
configrsc_file = diag_open.selectedFiles()[0]
dir_name = dirname(configrsc_file)
procimg_file = join(dir_name, "{0}.img".format(basename(configrsc_file).rsplit(".", maxsplit=1)[0]))
self.txt_configrsc.setText(configrsc_file)
self.txt_procimg.setText(procimg_file)
helper.settings.setValue("simulator/last_dir", dir_name)
diag_open.deleteLater()
@QtCore.pyqtSlot(int)
def on_cbb_history_currentIndexChanged(self, index: int) -> None:
if index == 0:
return
self.txt_configrsc.setText(self.cbb_history.itemText(index))
self.txt_procimg.setText(self.cbb_history.itemData(index))
@QtCore.pyqtSlot(str)
def on_txt_textChanged(self, text: str) -> None:
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) 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)
self.btn_start_nochange.setEnabled(file_access and exists(procimg_file))
else:
self.txt_info.clear()
self.btn_start_pictory.setEnabled(False)
self.btn_start_empty.setEnabled(False)
self.btn_start_nochange.setEnabled(False)

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,7 +2,7 @@
# Form implementation generated from reading ui file 'aclmanager.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
# 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.
@@ -87,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)
@@ -94,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)
@@ -101,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)
@@ -121,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)
@@ -147,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,7 +2,7 @@
# Form implementation generated from reading ui file 'avahisearch.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
# 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.
@@ -65,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):
@@ -82,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,7 +2,7 @@
# Form implementation generated from reading ui file 'debugcontrol.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
# 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.
@@ -36,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)
@@ -64,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,7 +2,7 @@
# Form implementation generated from reading ui file 'debugios.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
# 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.

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'files.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
# 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.
@@ -56,6 +56,8 @@ 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)
self.gridLayout_2.setColumnStretch(0, 1)
@@ -98,6 +100,8 @@ 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)
self.lbl_select_revpi = QtWidgets.QLabel(self.gb_select_revpi)
@@ -161,12 +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.lbl_path_local.setText(_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_path_revpi.setText(_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,7 +2,7 @@
# Form implementation generated from reading ui file 'mqttmanager.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
# 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.
@@ -118,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,7 +2,7 @@
# Form implementation generated from reading ui file 'revpicommander.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
# 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.
@@ -14,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")
@@ -68,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")
@@ -78,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")
@@ -117,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)
@@ -126,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"))
@@ -138,23 +178,17 @@ 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", "Start local si&mulator..."))
self.act_simulator.setText(_translate("win_revpicommander", "RevPi si&mulator..."))
from . import ressources_rc

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'revpiinfo.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
# 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.
@@ -41,6 +41,7 @@ class Ui_diag_revpiinfo(object):
font.setBold(True)
font.setWeight(75)
self.lbl_version_pyload.setFont(font)
self.lbl_version_pyload.setText("0.0.0")
self.lbl_version_pyload.setAlignment(QtCore.Qt.AlignCenter)
self.lbl_version_pyload.setObjectName("lbl_version_pyload")
self.gridLayout.addWidget(self.lbl_version_pyload, 3, 1, 1, 1)
@@ -66,6 +67,7 @@ class Ui_diag_revpiinfo(object):
font = QtGui.QFont()
font.setPointSize(14)
self.lbl_version_control.setFont(font)
self.lbl_version_control.setText("0.0.0")
self.lbl_version_control.setObjectName("lbl_version_control")
self.horizontalLayout.addWidget(self.lbl_version_control)
self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 3)
@@ -78,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):
@@ -87,10 +89,8 @@ class Ui_diag_revpiinfo(object):
diag_revpiinfo.setWindowTitle(_translate("diag_revpiinfo", "Program information"))
self.lbl_head.setText(_translate("diag_revpiinfo", "RevPi Python PLC - Commander"))
self.lbl_lbl_version_pyload.setText(_translate("diag_revpiinfo", "RevPiPyLoad version on RevPi:"))
self.lbl_version_pyload.setText(_translate("diag_revpiinfo", "0.0.0"))
self.lbl_link.setText(_translate("diag_revpiinfo", "<html><head/><body><p><a href=\"https://revpimodio.org/\"><span style=\" text-decoration: underline; color:#0000ff;\">https://revpimodio.org/</span></a></p></body></html>"))
self.lbl_lbl_version_control.setText(_translate("diag_revpiinfo", "Version:"))
self.lbl_version_control.setText(_translate("diag_revpiinfo", "0.0.0"))
self.lbl_info.setText(_translate("diag_revpiinfo", "RevPiModIO, RevPiPyLoad and RevPiPyControl are community driven projects. They are all free and open source software.\n"
"All of them comes with ABSOLUTELY NO WARRANTY, to the extent permitted by\n"
"applicable law.\n"

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'revpilogfile.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
# 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.

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'revpioption.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
# 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.
@@ -116,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,7 +2,7 @@
# Form implementation generated from reading ui file 'revpiplclist.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
# 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.
@@ -14,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")
@@ -54,67 +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(3, 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(3, QtWidgets.QFormLayout.FieldRole, self.cbb_folder)
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,7 +2,7 @@
# Form implementation generated from reading ui file 'revpiprogram.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
# 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.
@@ -105,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

@@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'simulator.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_simulator(object):
def setupUi(self, diag_simulator):
diag_simulator.setObjectName("diag_simulator")
diag_simulator.resize(522, 500)
self.verticalLayout = QtWidgets.QVBoxLayout(diag_simulator)
self.verticalLayout.setObjectName("verticalLayout")
self.gb_settings = QtWidgets.QGroupBox(diag_simulator)
self.gb_settings.setObjectName("gb_settings")
self.gridLayout = QtWidgets.QGridLayout(self.gb_settings)
self.gridLayout.setObjectName("gridLayout")
self.lbl_history = QtWidgets.QLabel(self.gb_settings)
self.lbl_history.setObjectName("lbl_history")
self.gridLayout.addWidget(self.lbl_history, 0, 0, 1, 1)
self.lbl_configrsc = QtWidgets.QLabel(self.gb_settings)
self.lbl_configrsc.setObjectName("lbl_configrsc")
self.gridLayout.addWidget(self.lbl_configrsc, 1, 0, 1, 1)
self.txt_configrsc = QtWidgets.QLineEdit(self.gb_settings)
self.txt_configrsc.setFocusPolicy(QtCore.Qt.NoFocus)
self.txt_configrsc.setText("")
self.txt_configrsc.setObjectName("txt_configrsc")
self.gridLayout.addWidget(self.txt_configrsc, 1, 1, 1, 1)
self.btn_configrsc = QtWidgets.QPushButton(self.gb_settings)
self.btn_configrsc.setObjectName("btn_configrsc")
self.gridLayout.addWidget(self.btn_configrsc, 1, 2, 1, 1)
self.lbl_procimg = QtWidgets.QLabel(self.gb_settings)
self.lbl_procimg.setObjectName("lbl_procimg")
self.gridLayout.addWidget(self.lbl_procimg, 2, 0, 1, 1)
self.txt_procimg = QtWidgets.QLineEdit(self.gb_settings)
self.txt_procimg.setFocusPolicy(QtCore.Qt.NoFocus)
self.txt_procimg.setText("")
self.txt_procimg.setObjectName("txt_procimg")
self.gridLayout.addWidget(self.txt_procimg, 2, 1, 1, 1)
self.lbl_stop = QtWidgets.QLabel(self.gb_settings)
self.lbl_stop.setObjectName("lbl_stop")
self.gridLayout.addWidget(self.lbl_stop, 3, 0, 1, 1)
self.lbl_restart = QtWidgets.QLabel(self.gb_settings)
self.lbl_restart.setObjectName("lbl_restart")
self.gridLayout.addWidget(self.lbl_restart, 4, 0, 1, 1)
self.cbb_history = QtWidgets.QComboBox(self.gb_settings)
self.cbb_history.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
self.cbb_history.setObjectName("cbb_history")
self.gridLayout.addWidget(self.cbb_history, 0, 1, 1, 2)
self.cbx_stop_remove = QtWidgets.QCheckBox(self.gb_settings)
self.cbx_stop_remove.setObjectName("cbx_stop_remove")
self.gridLayout.addWidget(self.cbx_stop_remove, 3, 1, 1, 2)
self.rb_restart_pictory = QtWidgets.QRadioButton(self.gb_settings)
self.rb_restart_pictory.setChecked(True)
self.rb_restart_pictory.setObjectName("rb_restart_pictory")
self.gridLayout.addWidget(self.rb_restart_pictory, 4, 1, 1, 2)
self.rb_restart_zero = QtWidgets.QRadioButton(self.gb_settings)
self.rb_restart_zero.setObjectName("rb_restart_zero")
self.gridLayout.addWidget(self.rb_restart_zero, 5, 1, 1, 2)
self.verticalLayout.addWidget(self.gb_settings)
self.gb_info = QtWidgets.QGroupBox(diag_simulator)
self.gb_info.setObjectName("gb_info")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.gb_info)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.lbl_info = QtWidgets.QLabel(self.gb_info)
self.lbl_info.setWordWrap(True)
self.lbl_info.setObjectName("lbl_info")
self.verticalLayout_2.addWidget(self.lbl_info)
self.txt_info = QtWidgets.QPlainTextEdit(self.gb_info)
self.txt_info.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
self.txt_info.setReadOnly(True)
self.txt_info.setPlainText("")
self.txt_info.setObjectName("txt_info")
self.verticalLayout_2.addWidget(self.txt_info)
self.verticalLayout.addWidget(self.gb_info)
self.btn_start_pictory = QtWidgets.QPushButton(diag_simulator)
self.btn_start_pictory.setShortcut("Ctrl+1")
self.btn_start_pictory.setObjectName("btn_start_pictory")
self.verticalLayout.addWidget(self.btn_start_pictory)
self.btn_start_empty = QtWidgets.QPushButton(diag_simulator)
self.btn_start_empty.setShortcut("Ctrl+2")
self.btn_start_empty.setObjectName("btn_start_empty")
self.verticalLayout.addWidget(self.btn_start_empty)
self.btn_start_nochange = QtWidgets.QPushButton(diag_simulator)
self.btn_start_nochange.setShortcut("Ctrl+3")
self.btn_start_nochange.setObjectName("btn_start_nochange")
self.verticalLayout.addWidget(self.btn_start_nochange)
self.retranslateUi(diag_simulator)
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)
diag_simulator.setTabOrder(self.cbx_stop_remove, self.rb_restart_pictory)
diag_simulator.setTabOrder(self.rb_restart_pictory, self.rb_restart_zero)
diag_simulator.setTabOrder(self.rb_restart_zero, self.txt_info)
diag_simulator.setTabOrder(self.txt_info, self.btn_start_pictory)
diag_simulator.setTabOrder(self.btn_start_pictory, self.btn_start_empty)
diag_simulator.setTabOrder(self.btn_start_empty, self.btn_start_nochange)
def retranslateUi(self, diag_simulator):
_translate = QtCore.QCoreApplication.translate
diag_simulator.setWindowTitle(_translate("diag_simulator", "piControl simulator"))
self.gb_settings.setTitle(_translate("diag_simulator", "Simulator settings"))
self.lbl_history.setText(_translate("diag_simulator", "Last used:"))
self.lbl_configrsc.setText(_translate("diag_simulator", "piCtory file:"))
self.btn_configrsc.setText(_translate("diag_simulator", "select..."))
self.lbl_procimg.setText(_translate("diag_simulator", "Process image:"))
self.lbl_stop.setText(_translate("diag_simulator", "Stop action:"))
self.lbl_restart.setText(_translate("diag_simulator", "Restart action:"))
self.cbx_stop_remove.setText(_translate("diag_simulator", "Remove process image file"))
self.rb_restart_pictory.setText(_translate("diag_simulator", "Restore piCtory default values"))
self.rb_restart_zero.setText(_translate("diag_simulator", "Reset everything to ZERO"))
self.gb_info.setTitle(_translate("diag_simulator", "RevPiModIO integration"))
self.lbl_info.setText(_translate("diag_simulator", "You can work with this simulator if you call RevPiModIO with this additional parameters:"))
self.btn_start_pictory.setText(_translate("diag_simulator", "Start with piCtory default values"))
self.btn_start_empty.setText(_translate("diag_simulator", "Start with empty process image"))
self.btn_start_nochange.setText(_translate("diag_simulator", "Start without changing actual process image"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
diag_simulator = QtWidgets.QDialog()
ui = Ui_diag_simulator()
ui.setupUi(diag_simulator)
diag_simulator.show()
sys.exit(app.exec_())

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,29 +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/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/mqttmanager.ui \
include/ui_dev/files.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/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,10 +84,10 @@
</sizepolicy>
</property>
<property name="toolTip">
<string>/</string>
<string notr="true">/</string>
</property>
<property name="text">
<string>/</string>
<string notr="true">/</string>
</property>
</widget>
</item>
@@ -180,10 +180,10 @@
</sizepolicy>
</property>
<property name="toolTip">
<string>/</string>
<string notr="true">/</string>
</property>
<property name="text">
<string>/</string>
<string notr="true">/</string>
</property>
</widget>
</item>

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">
@@ -226,7 +283,7 @@
</action>
<action name="act_simulator">
<property name="text">
<string>Start local si&amp;mulator...</string>
<string>RevPi si&amp;mulator...</string>
</property>
</action>
</widget>

View File

@@ -56,7 +56,7 @@
</font>
</property>
<property name="text">
<string>0.0.0</string>
<string notr="true">0.0.0</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -108,7 +108,7 @@
</font>
</property>
<property name="text">
<string>0.0.0</string>
<string notr="true">0.0.0</string>
</property>
</widget>
</item>

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>

245
ui_dev/simulator.ui Normal file
View File

@@ -0,0 +1,245 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>diag_simulator</class>
<widget class="QDialog" name="diag_simulator">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>522</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
<string>piControl simulator</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="gb_settings">
<property name="title">
<string>Simulator settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="lbl_history">
<property name="text">
<string>Last used:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lbl_configrsc">
<property name="text">
<string>piCtory file:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="txt_configrsc">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="btn_configrsc">
<property name="text">
<string>select...</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lbl_procimg">
<property name="text">
<string>Process image:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="txt_procimg">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="lbl_stop">
<property name="text">
<string>Stop action:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="lbl_restart">
<property name="text">
<string>Restart action:</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QComboBox" name="cbb_history">
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QCheckBox" name="cbx_stop_remove">
<property name="text">
<string>Remove process image file</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QRadioButton" name="rb_restart_pictory">
<property name="text">
<string>Restore piCtory default values</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QRadioButton" name="rb_restart_zero">
<property name="text">
<string>Reset everything to ZERO</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_info">
<property name="title">
<string>RevPiModIO integration</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="lbl_info">
<property name="text">
<string>You can work with this simulator if you call RevPiModIO with this additional parameters:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="txt_info">
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="plainText">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_start_pictory">
<property name="text">
<string>Start with piCtory default values</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+1</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_start_empty">
<property name="text">
<string>Start with empty process image</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+2</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_start_nochange">
<property name="text">
<string>Start without changing actual process image</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+3</string>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>cbb_history</tabstop>
<tabstop>btn_configrsc</tabstop>
<tabstop>cbx_stop_remove</tabstop>
<tabstop>rb_restart_pictory</tabstop>
<tabstop>rb_restart_zero</tabstop>
<tabstop>txt_info</tabstop>
<tabstop>btn_start_pictory</tabstop>
<tabstop>btn_start_empty</tabstop>
<tabstop>btn_start_nochange</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>btn_start_empty</sender>
<signal>clicked()</signal>
<receiver>diag_simulator</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>268</x>
<y>447</y>
</hint>
<hint type="destinationlabel">
<x>268</x>
<y>249</y>
</hint>
</hints>
</connection>
<connection>
<sender>btn_start_nochange</sender>
<signal>clicked()</signal>
<receiver>diag_simulator</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>268</x>
<y>478</y>
</hint>
<hint type="destinationlabel">
<x>268</x>
<y>249</y>
</hint>
</hints>
</connection>
<connection>
<sender>btn_start_pictory</sender>
<signal>clicked()</signal>
<receiver>diag_simulator</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>268</x>
<y>416</y>
</hint>
<hint type="destinationlabel">
<x>268</x>
<y>249</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>