13 Commits
0.9.1 ... 0.9.3

Author SHA1 Message Date
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
39 changed files with 2863 additions and 1957 deletions

View File

@@ -9,4 +9,8 @@
<orderEntry type="jdk" jdkName="Python 3.8" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PackageRequirementsSettings">
<option name="versionSpecifier" value="Greater or equal (&gt;=x.y.z)" />
<option name="removeUnused" value="true" />
</component>
</module>

1
.idea/vcs.xml generated
View File

@@ -2,6 +2,5 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/lib/revpimodio2" vcs="Git" />
</component>
</project>

View File

@@ -2,10 +2,9 @@
# 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.14.1
#
# 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.
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets

View File

@@ -2,10 +2,9 @@
# 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.14.1
#
# 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.
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets

View File

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

View File

@@ -2,10 +2,9 @@
# 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.14.1
#
# 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.
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets

View File

@@ -2,10 +2,9 @@
# 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.14.1
#
# 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.
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -56,6 +55,7 @@ class Ui_win_files(object):
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_path_local.sizePolicy().hasHeightForWidth())
self.lbl_path_local.setSizePolicy(sizePolicy)
self.lbl_path_local.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 +98,7 @@ class Ui_win_files(object):
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_path_revpi.sizePolicy().hasHeightForWidth())
self.lbl_path_revpi.setSizePolicy(sizePolicy)
self.lbl_path_revpi.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)
@@ -162,11 +163,9 @@ class Ui_win_files(object):
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,10 +2,9 @@
# 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.14.1
#
# 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.
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,9 @@
# 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.14.1
#
# 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.
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -154,7 +153,7 @@ class Ui_win_revpicommander(object):
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,10 +2,9 @@
# 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.14.1
#
# 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.
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -41,6 +40,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 +66,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)
@@ -87,10 +88,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,10 +2,9 @@
# 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.14.1
#
# 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.
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets

View File

@@ -2,10 +2,9 @@
# 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.14.1
#
# 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.
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets

View File

@@ -2,10 +2,9 @@
# 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.14.1
#
# 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.
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -63,7 +62,7 @@ class Ui_diag_connections(object):
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.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.lbl_folder)
self.lbl_address = QtWidgets.QLabel(self.gb_properties)
self.lbl_address.setObjectName("lbl_address")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lbl_address)
@@ -92,7 +91,20 @@ class Ui_diag_connections(object):
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.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.cbb_folder)
self.lbl_timeout = QtWidgets.QLabel(self.gb_properties)
self.lbl_timeout.setObjectName("lbl_timeout")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.lbl_timeout)
self.sbx_timeout = QtWidgets.QSpinBox(self.gb_properties)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.sbx_timeout.sizePolicy().hasHeightForWidth())
self.sbx_timeout.setSizePolicy(sizePolicy)
self.sbx_timeout.setMinimum(5)
self.sbx_timeout.setMaximum(30)
self.sbx_timeout.setObjectName("sbx_timeout")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.sbx_timeout)
self.gridLayout.addWidget(self.gb_properties, 1, 0, 1, 2)
self.btn_box = QtWidgets.QDialogButtonBox(diag_connections)
self.btn_box.setOrientation(QtCore.Qt.Horizontal)
@@ -115,6 +127,8 @@ class Ui_diag_connections(object):
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."))
from . import ressources_rc

View File

@@ -2,10 +2,9 @@
# 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.14.1
#
# 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.
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets

135
include/ui/simulator_ui.py Normal file
View File

@@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'simulator.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
#
# WARNING! All changes made in this file will be lost!
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)
self.btn_start_nochange.clicked.connect(diag_simulator.accept)
self.btn_start_pictory.clicked.connect(diag_simulator.accept)
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,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

@@ -87,7 +87,7 @@
<string>/</string>
</property>
<property name="text">
<string>/</string>
<string notr="true">/</string>
</property>
</widget>
</item>
@@ -183,7 +183,7 @@
<string>/</string>
</property>
<property name="text">
<string>/</string>
<string notr="true">/</string>
</property>
</widget>
</item>

View File

@@ -226,7 +226,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>

View File

@@ -105,7 +105,7 @@
</property>
</widget>
</item>
<item row="3" column="0">
<item row="4" column="0">
<widget class="QLabel" name="lbl_folder">
<property name="text">
<string>Sub folder:</string>
@@ -151,7 +151,7 @@
</property>
</widget>
</item>
<item row="3" column="1">
<item row="4" column="1">
<widget class="QComboBox" name="cbb_folder">
<property name="editable">
<bool>true</bool>
@@ -163,6 +163,32 @@
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="lbl_timeout">
<property name="text">
<string>Connection timeout:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="sbx_timeout">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string> sec.</string>
</property>
<property name="minimum">
<number>5</number>
</property>
<property name="maximum">
<number>30</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>

245
include/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>

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
PyQt5>=5.14.1
revpimodio2>=2.5.6
zeroconf>=0.24.4

View File

@@ -6,10 +6,9 @@ __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
from zeroconf import IPVersion, ServiceBrowser, Zeroconf
import helper
import proginit as pi
@@ -32,7 +31,7 @@ class AvahiSearchThread(QtCore.QThread):
r"(?P<mac>([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})"
)
def _update_arp(self):
def _update_arp(self) -> None:
"""Find mac address in arp table."""
if osname == "posix":
with open("/proc/net/arp") as fh:
@@ -41,7 +40,7 @@ class AvahiSearchThread(QtCore.QThread):
if ip_mac:
self.__dict_arp[ip_mac.group("ip")] = ip_mac.group("mac")
def get_mac(self, ip: str):
def get_mac(self, ip: str) -> dict:
"""
Get mac address of ip, if known.
@@ -50,25 +49,22 @@ class AvahiSearchThread(QtCore.QThread):
"""
return self.__dict_arp.get(ip, "")
def remove_service(self, zeroconf: Zeroconf, conf_type: str, name: str):
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):
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
try:
ip = gethostbyname(info.server)
except Exception:
ip = self.tr("N/A")
self.added.emit(name, info.server, info.port, conf_type, ip)
for ip in info.parsed_addresses(IPVersion.V4Only):
self.added.emit(name, info.server, info.port, conf_type, ip)
def run(self):
def run(self) -> None:
pi.logger.debug("Started zero conf discovery.")
zeroconf = Zeroconf()
revpi_browser = ServiceBrowser(zeroconf, "_revpipyload._tcp.local.", self)
@@ -84,16 +80,36 @@ 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.known_hosts = {}
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):
self.restoreGeometry(helper.settings.value("avahisearch/geo", b''))
column_sizes = helper.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]))
def _load_known_hosts(self) -> None:
"""Load existing connections to show hostname of existing ip addresses"""
self.known_hosts.clear()
for i in range(helper.settings.beginReadArray("connections")):
helper.settings.setArrayIndex(i)
name = helper.settings.value("name", type=str)
folder = helper.settings.value("folder", type=str)
address = helper.settings.value("address", type=str)
self.known_hosts[address] = "{0}/{1}".format(folder, name) if folder else name
helper.settings.endArray()
def _restart_search(self) -> None:
"""Clean up an restart search thread."""
while self.tb_revpi.rowCount() > 0:
self.tb_revpi.removeRow(0)
@@ -104,17 +120,17 @@ class AvahiSearch(QtWidgets.QDialog, Ui_diag_search):
self.th_zero_conf.removed.connect(self.on_avahi_removed)
self.th_zero_conf.start()
def _save_connection(self, row: int, no_warn=False):
def _save_connection(self, row: int, no_warn=False) -> int:
"""
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)
:return: Array index of connection (found or saved) or -1
"""
item = self.tb_revpi.item(row, 0)
if not item:
return
return -1
folder_name = self.tr("Auto discovered")
selected_name = item.text()
@@ -132,8 +148,8 @@ class AvahiSearch(QtWidgets.QDialog, Ui_diag_search):
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)
)
"connection list as '{0}'."
).format(name)
)
helper.settings.endArray()
return i
@@ -153,20 +169,28 @@ class AvahiSearch(QtWidgets.QDialog, Ui_diag_search):
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)
)
"to folder '{1}' in your connections."
).format(selected_name, folder_name)
)
return i + 1
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
helper.settings.setValue("avahisearch/geo", self.saveGeometry())
helper.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(str, str, int, str, str)
def on_avahi_added(self, name: str, server: str, port: int, conf_type: str, ip: 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()):
@@ -189,14 +213,17 @@ class AvahiSearch(QtWidgets.QDialog, Ui_diag_search):
item_ip = self.tb_revpi.item(index, 1)
item_name.setIcon(QtGui.QIcon(":/main/ico/cpu.ico"))
item_name.setText(server[:-1])
if ip in self.known_hosts:
item_name.setText("{0} ({1})".format(server[:-1], self.known_hosts[ip]))
else:
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):
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:
@@ -204,20 +231,20 @@ class AvahiSearch(QtWidgets.QDialog, Ui_diag_search):
break
@QtCore.pyqtSlot(int, int)
def on_tb_revpi_cellDoubleClicked(self, row: int, column: 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")
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):
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_pressed(self):
def on_btn_connect_pressed(self) -> None:
"""Connect to selected Revolution Pi."""
pi.logger.debug("AvahiSearch.on_btn_connect_pressed")
if self.tb_revpi.currentRow() == -1:
@@ -226,7 +253,7 @@ class AvahiSearch(QtWidgets.QDialog, Ui_diag_search):
self.accept()
@QtCore.pyqtSlot()
def on_btn_save_pressed(self):
def on_btn_save_pressed(self) -> None:
"""Save selected Revolution Pi."""
pi.logger.debug("AvahiSearch.on_btn_save_pressed")
if self.tb_revpi.currentRow() == -1:
@@ -234,6 +261,6 @@ class AvahiSearch(QtWidgets.QDialog, Ui_diag_search):
self.connect_index = self._save_connection(self.tb_revpi.currentRow())
@QtCore.pyqtSlot()
def on_btn_restart_pressed(self):
def on_btn_restart_pressed(self) -> None:
"""Clean up an 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) 2021 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

@@ -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,7 +340,6 @@ 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]

View File

@@ -9,6 +9,7 @@ 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
@@ -25,6 +26,7 @@ class WidgetData(IntEnum):
has_error = 263
port = 264
object_name = 265
timeout = 266
last_dir_upload = 301
last_file_upload = 302
last_dir_pictory = 303
@@ -218,9 +220,10 @@ 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)
self.program_last_dir_upload = settings.value("last_dir_upload", ".", str)
self.program_last_file_upload = settings.value("last_file_upload", ".", str)
@@ -228,8 +231,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)
@@ -245,6 +248,7 @@ 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))
return False
@@ -256,7 +260,7 @@ class ConnectionManager(QtCore.QThread):
self.xml_mode = xml_mode
with self._lck_cli:
socket.setdefaulttimeout(5)
socket.setdefaulttimeout(timeout)
self._cli = sp
self._cli_connect.put_nowait((address, port))
@@ -271,7 +275,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
@@ -295,17 +300,18 @@ class ConnectionManager(QtCore.QThread):
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 +323,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 +342,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."""
@@ -443,15 +458,23 @@ class ConnectionManager(QtCore.QThread):
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

@@ -68,37 +68,29 @@ Nicht gespeicherte Änderunen gehen verloren</translation>
<context>
<name>AvahiSearch</name>
<message>
<location filename="../avahisearch.py" line="119"/>
<location filename="../avahisearch.py" line="115"/>
<source>Auto discovered</source>
<translation>Automatisch erkannt</translation>
</message>
<message>
<location filename="../avahisearch.py" line="132"/>
<location filename="../avahisearch.py" line="128"/>
<source>Already in list...</source>
<translation>Bereits in Liste...</translation>
</message>
<message>
<location filename="../avahisearch.py" line="132"/>
<source>The selected Revolution Pi is already saved in your connection list as &apos;{0}.</source>
<translation>Der ausgewählte RevPi ist schon in der Verbindungsliste als &apos;{0}&apos;.</translation>
</message>
<message>
<location filename="../avahisearch.py" line="153"/>
<location filename="../avahisearch.py" line="149"/>
<source>Success</source>
<translation>Erfolgreich</translation>
</message>
<message>
<location filename="../avahisearch.py" line="153"/>
<location filename="../avahisearch.py" line="149"/>
<source>The connection with the name &apos;{0}&apos; was successfully saved to folder &apos;{1}&apos; in your connections.</source>
<translation>Die Verbindung mit dem Namen &apos;{0}&apos; wurde erfolgreich im Ordner &apos;{1}&apos; gespeichert.</translation>
</message>
</context>
<context>
<name>AvahiSearchThread</name>
<message>
<location filename="../avahisearch.py" line="68"/>
<source>N/A</source>
<translation></translation>
<location filename="../avahisearch.py" line="128"/>
<source>The selected Revolution Pi is already saved in your connection list as &apos;{0}&apos;.</source>
<translation>Der ausgewählte RevPi ist schon in der Verbindungsliste als &apos;{0}&apos;.</translation>
</message>
</context>
<context>
@@ -139,22 +131,22 @@ Nicht gespeicherte Änderunen gehen verloren</translation>
<context>
<name>DebugIos</name>
<message>
<location filename="../debugios.py" line="212"/>
<location filename="../debugios.py" line="213"/>
<source>signed</source>
<translation></translation>
</message>
<message>
<location filename="../debugios.py" line="217"/>
<location filename="../debugios.py" line="218"/>
<source>big_endian</source>
<translation></translation>
</message>
<message>
<location filename="../debugios.py" line="206"/>
<location filename="../debugios.py" line="207"/>
<source>as text</source>
<translation></translation>
</message>
<message>
<location filename="../debugios.py" line="208"/>
<location filename="../debugios.py" line="209"/>
<source>as number</source>
<translation></translation>
</message>
@@ -166,13 +158,6 @@ Nicht gespeicherte Änderunen gehen verloren</translation>
<source>Question</source>
<translation>Frage</translation>
</message>
<message>
<location filename="../mqttmanager.py" line="79"/>
<source>Do you really want to quit?
Unsaved changes will be lost!</source>
<translation type="obsolete">Soll das Fenster wirklich geschlossen werden?
Nicht gespeicherte Änderunen gehen verloren!</translation>
</message>
<message>
<location filename="../mqttmanager.py" line="92"/>
<source>Error</source>
@@ -194,132 +179,93 @@ Ungesicherte Änderungen gehen verloren.</translation>
<context>
<name>RevPiCommander</name>
<message>
<location filename="../revpicommander.py" line="198"/>
<source>Select downloaded piCtory file...</source>
<translation>Heruntergeladene piCtory Datei auswählen...</translation>
</message>
<message>
<location filename="../revpicommander.py" line="198"/>
<source>piCtory file (*.rsc);;All files (*.*)</source>
<translation>piCtory Datei (*.rsc);;Alle Dateien (*.*)</translation>
</message>
<message>
<location filename="../revpicommander.py" line="218"/>
<location filename="../revpicommander.py" line="219"/>
<source>Simulator started...</source>
<translation>Simulator gestartet...</translation>
</message>
<message>
<location filename="../revpicommander.py" line="218"/>
<source>The simulator is running!
You can work with this simulator if your call RevPiModIO with this additional parameters:
procimg={procimg}
configrsc={config}
You can copy that from header textbox.</source>
<translation>Der Simulator läuft!
Du kannst mit der Simulation arbeiten, wenn du RevPiModIO mit diesen zusätzlichen Parametern instantiierst:
procimg={procimg}
configrsc={configrsc}
Dies kann aus der Textbox oben kopiert werden.</translation>
</message>
<message>
<location filename="../revpicommander.py" line="235"/>
<location filename="../revpicommander.py" line="228"/>
<source>Can not start...</source>
<translation>Kann nicht gestartet werden...</translation>
</message>
<message>
<location filename="../revpicommander.py" line="235"/>
<source>Can not start the simulator! Maybe the piCtory file is corrupt or you can not write to the location &apos;{0}&apos;.</source>
<translation>Kann Simulator nicht starten! Vielleicht ist die piCtory Datei defekt oder es gibt keine Schreibberechtigung in &apos;{0}`.</translation>
</message>
<message>
<location filename="../revpicommander.py" line="435"/>
<location filename="../revpicommander.py" line="431"/>
<source>Warning</source>
<translation>Warnung</translation>
</message>
<message>
<location filename="../revpicommander.py" line="249"/>
<location filename="../revpicommander.py" line="244"/>
<source>This version of Logviewer ist not supported in version {0} of RevPiPyLoad on your RevPi! You need at least version 0.4.1.</source>
<translation>Diese Version vom Logbetrachter wird in RevPiPyLoad Version {0} nicht unterstützt! Es wird mindestens Version 0.4.1 benötigt.</translation>
</message>
<message>
<location filename="../revpicommander.py" line="300"/>
<location filename="../revpicommander.py" line="294"/>
<source>XML-RPC access mode in the RevPiPyLoad configuration is too small to access this dialog!</source>
<translation>XML-RPC Zugriffsberechtigung in der RevPiPyLoad Konfiguraiton ist zu klein für diese Einstellungen!</translation>
</message>
<message>
<location filename="../revpicommander.py" line="462"/>
<location filename="../revpicommander.py" line="458"/>
<source>Error</source>
<translation>Fehler</translation>
</message>
<message>
<location filename="../revpicommander.py" line="280"/>
<location filename="../revpicommander.py" line="274"/>
<source>The Version of RevPiPyLoad on your Revolution Pi ({0}) is to old. This Version of RevPiCommander require at least version 0.6.0 of RevPiPyLoad. Please update your Revolution Pi!</source>
<translation>Die Version von RevPiPyLoad ({0}) auf dem Revolution Pi ist zu alt. Diese Version vom RevPiCommander braucht mindestens Version 0.6.0. Bitte aktualisiere deinen Revolution Pi!</translation>
</message>
<message>
<location filename="../revpicommander.py" line="333"/>
<location filename="../revpicommander.py" line="327"/>
<source>Question</source>
<translation>Frage</translation>
</message>
<message>
<location filename="../revpicommander.py" line="333"/>
<location filename="../revpicommander.py" line="327"/>
<source>Are you sure to reset piControl?
The pictory configuration will be reloaded. During that time the process image will be interrupted and could rise errors on running control programs!</source>
<translation>Soll piControl wirklich zurückgesetzt werden?
Die piCtory Konfiguration wird neu geladen. Das Prozessabbild wird in dieser Zeit nicht verfügbar sein und es könnten Fehler in Steuerungsprogrammen ausgelöst werden!</translation>
</message>
<message>
<location filename="../revpicommander.py" line="346"/>
<location filename="../revpicommander.py" line="340"/>
<source>Success</source>
<translation>Erfolgreich</translation>
</message>
<message>
<location filename="../revpicommander.py" line="346"/>
<location filename="../revpicommander.py" line="340"/>
<source>piControl reset executed successfully</source>
<translation>piControl wurde erfolgreich zurückgesetzt</translation>
</message>
<message>
<location filename="../revpicommander.py" line="353"/>
<location filename="../revpicommander.py" line="347"/>
<source>piControl reset could not be executed successfully</source>
<translation>piControl konnte nicht zurückgesetzt werden</translation>
</message>
<message>
<location filename="../revpicommander.py" line="411"/>
<location filename="../revpicommander.py" line="405"/>
<source>Reset to piCtory defaults...</source>
<translation>Standardwerte von piCtory laden...</translation>
</message>
<message>
<location filename="../revpicommander.py" line="411"/>
<source>Do you want to reset your process image to piCtory default values?
You have to stop other RevPiModIO programs before doing that, because they could reset the outputs.</source>
<translation>Soll das virtuelle Prozessabbild auf die piCtory Standardwerte zurückgesetzt werden?
Es sollten alle RevPiModIO Programme vorher beendet werden, da diese ihre IO Werte sofort wieder schreiben würden.</translation>
</message>
<message>
<location filename="../revpicommander.py" line="435"/>
<location filename="../revpicommander.py" line="431"/>
<source>The watch mode ist not supported in version {0} of RevPiPyLoad on your RevPi! You need at least version 0.5.3! Maybe the python3-revpimodio2 module is not installed on your RevPi at least version 2.0.0.</source>
<translation>Der SPS Betrachter ist in Version {0} von RevPiPyLoad auf dem Rev Pi nicht unterstützt! Es muss mindestens Version 0.5.3 installiert sein! Vielleicht fehlt auch das python3-revpimodio2 Modul, welches mindestens Version 2.0.0 haben muss.</translation>
</message>
<message>
<location filename="../revpicommander.py" line="447"/>
<location filename="../revpicommander.py" line="443"/>
<source>Can not load this function, because your ACL level is to low!
You need at least level 1 to read or level 3 to write.</source>
<translation>Für diese Funktion ist das Berechtigungslevel zu gering!
Es muss mindestens Level 1 zum Lesen oder Level 3 zu Schreiben sein.</translation>
</message>
<message>
<location filename="../revpicommander.py" line="462"/>
<location filename="../revpicommander.py" line="458"/>
<source>Can not load piCtory configuration.
Did you create a hardware configuration? Please check this in piCtory!</source>
<translation>Kann piCtory Konfiguration nicht laden.
Wurde eine Hardwarekonfiguration in piCtory erzeugt?</translation>
</message>
<message>
<location filename="../revpicommander.py" line="368"/>
<location filename="../revpicommander.py" line="362"/>
<source>Can not connect to RevPi XML-RPC Service!
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!!!
@@ -331,6 +277,45 @@ Das kann eine der folgenden Ursachen haben: Der Rev Pi ist nicht online, der XML
Führe &apos;sudo revpipyload_secure_installation&apos; auf dem Revolution Pi aus um diese Funktion zu konfigurieren!</translation>
</message>
<message>
<location filename="../revpicommander.py" line="219"/>
<source>The simulator is running!
You can work with this simulator if your call RevPiModIO with this additional parameters:
procimg={0}
configrsc={1}
You can copy that from header textbox.</source>
<translation>Der Simulator läuft!
Du kannst mit der Simulation arbeiten, wenn du RevPiModIO mit diesen zusätzlichen Parametern instantiierst:
procimg={0}
configrsc={1}
Dies kann aus der Textbox oben kopiert werden.</translation>
</message>
<message>
<location filename="../revpicommander.py" line="228"/>
<source>Can not start the simulator! Maybe the piCtory file is corrupt or you have no write permissions for &apos;{0}&apos;.</source>
<translation>Kann Simulator nicht starten! Vielleicht ist die piCtory Datei defekt oder es gibt keine Schreibberechtigung für &apos;{0}`.</translation>
</message>
<message>
<location filename="../revpicommander.py" line="405"/>
<source>Do you want to reset your process image to {0} values?
You have to stop other RevPiModIO programs before doing that, because they could reset the outputs.</source>
<translation>Soll das virtuelle Prozessabbild auf {0} zurückgesetzt werden?
Es sollten alle RevPiModIO Programme vorher beendet werden, da diese ihre IO Werte sofort wieder schreiben würden.</translation>
</message>
<message>
<location filename="../revpicommander.py" line="405"/>
<source>zero</source>
<translation>null</translation>
</message>
<message>
<location filename="../revpicommander.py" line="405"/>
<source>piCtory default</source>
<translation>piCtory Standardwerte</translation>
</message>
</context>
<context>
<name>RevPiFiles</name>
@@ -349,11 +334,6 @@ Führe &apos;sudo revpipyload_secure_installation&apos; auf dem Revolution Pi au
<source>Can not stop plc program on Revolution Pi.</source>
<translation>Kann SPS Programm auf Rev Pi nicht stoppen.</translation>
</message>
<message>
<location filename="../revpifiles.py" line="110"/>
<source>Information...</source>
<translation type="obsolete">Information...</translation>
</message>
<message>
<location filename="../revpifiles.py" line="119"/>
<source>The Revolution Pi could not process some parts of the transmission.</source>
@@ -480,7 +460,7 @@ Wählen Sie &apos;Ja&apos; zum Überschreiben, &apos;Nein&apos; um nur fehlende
<context>
<name>RevPiLogfile</name>
<message>
<location filename="../revpilogfile.py" line="201"/>
<location filename="../revpilogfile.py" line="200"/>
<source>Can not access log file on the RevPi</source>
<translation>Kann auf Logbuch vom RevPi nicht zugreifen</translation>
</message>
@@ -579,17 +559,17 @@ Ungesicherte Änderungen gehen verloren.</translation>
<context>
<name>RevPiPlcList</name>
<message>
<location filename="../revpiplclist.py" line="29"/>
<location filename="../revpiplclist.py" line="28"/>
<source>New connection</source>
<translation>Neue Verbindung</translation>
</message>
<message>
<location filename="../revpiplclist.py" line="146"/>
<location filename="../revpiplclist.py" line="147"/>
<source>Question</source>
<translation>Frage</translation>
</message>
<message>
<location filename="../revpiplclist.py" line="146"/>
<location filename="../revpiplclist.py" line="147"/>
<source>Do you really want to quit?
Unsaved changes will be lost.</source>
<translation>Soll das Fenster wirklich geschlossen werden?
@@ -599,7 +579,7 @@ Ungesicherte Änderungen gehen verloren.</translation>
<context>
<name>RevPiProgram</name>
<message>
<location filename="../revpiprogram.py" line="689"/>
<location filename="../revpiprogram.py" line="688"/>
<source>Error</source>
<translation>Fehler</translation>
</message>
@@ -656,7 +636,7 @@ Please try again.</source>
Versuche es erneut.</translation>
</message>
<message>
<location filename="../revpiprogram.py" line="699"/>
<location filename="../revpiprogram.py" line="698"/>
<source>Success</source>
<translation>Erfolgreich</translation>
</message>
@@ -824,7 +804,7 @@ Dies ist kein Fehler, wenn das SPS Startprogramm bereits auf dem Rev Pi ist. Pr
<translation>piCtory Datei speichern...</translation>
</message>
<message>
<location filename="../revpiprogram.py" line="647"/>
<location filename="../revpiprogram.py" line="646"/>
<source>piCtory file (*.rsc);;All files (*.*)</source>
<translation>piCtory Datei (*.rsc);;Alle Dateien (*.*)</translation>
</message>
@@ -841,33 +821,46 @@ Dies ist kein Fehler, wenn das SPS Startprogramm bereits auf dem Rev Pi ist. Pr
{0}.</translation>
</message>
<message>
<location filename="../revpiprogram.py" line="647"/>
<location filename="../revpiprogram.py" line="646"/>
<source>Upload piCtory file...</source>
<translation>piCtory datei hochladen...</translation>
</message>
<message>
<location filename="../revpiprogram.py" line="668"/>
<location filename="../revpiprogram.py" line="667"/>
<source>Save piControl file...</source>
<translation>piCtory Datei speichern...</translation>
</message>
<message>
<location filename="../revpiprogram.py" line="668"/>
<location filename="../revpiprogram.py" line="667"/>
<source>Process image file (*.img);;All files (*.*)</source>
<translation>Processabbild (*.img);;Alle Dateien (*.*)</translation>
</message>
<message>
<location filename="../revpiprogram.py" line="689"/>
<location filename="../revpiprogram.py" line="688"/>
<source>Could not load process image from Revolution Pi.</source>
<translation>Kann Prozessabbild von Revolution Pi nicht laden.</translation>
</message>
<message>
<location filename="../revpiprogram.py" line="699"/>
<location filename="../revpiprogram.py" line="698"/>
<source>Process image successfully loaded and saved to:
{0}.</source>
<translation>Prozessabbild erfolgreich geladen und gespeichert als:
{0}.</translation>
</message>
</context>
<context>
<name>Simulator</name>
<message>
<location filename="../simulator.py" line="79"/>
<source>Select downloaded piCtory file...</source>
<translation>Heruntergeladene piCtory Datei auswählen...</translation>
</message>
<message>
<location filename="../simulator.py" line="79"/>
<source>piCtory file (*.rsc);;All files (*.*)</source>
<translation>piCtory Datei (*.rsc);;Alle Dateien (*.*)</translation>
</message>
</context>
<context>
<name>diag_aclmanager</name>
<message>
@@ -973,6 +966,16 @@ Dies ist kein Fehler, wenn das SPS Startprogramm bereits auf dem Rev Pi ist. Pr
<source>Port (Default {0}):</source>
<translation>Port (Standard {0}):</translation>
</message>
<message>
<location filename="../../include/ui_dev/revpiplclist.ui" line="169"/>
<source>Connection timeout:</source>
<translation>Verbindungs-Timeout:</translation>
</message>
<message>
<location filename="../../include/ui_dev/revpiplclist.ui" line="182"/>
<source> sec.</source>
<translation> Sek.</translation>
</message>
</context>
<context>
<name>diag_mqtt</name>
@@ -1324,11 +1327,6 @@ For example: revpi0000/data</source>
<source>RevPiPyLoad version on RevPi:</source>
<translation>RevPiPyLoad Version auf RevPi:</translation>
</message>
<message>
<location filename="../../include/ui_dev/revpiinfo.ui" line="111"/>
<source>0.0.0</source>
<translation></translation>
</message>
<message>
<location filename="../../include/ui_dev/revpiinfo.ui" line="69"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://revpimodio.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;https://revpimodio.org/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
@@ -1397,6 +1395,89 @@ applicable law.
<translation>Zero-conf Name</translation>
</message>
</context>
<context>
<name>diag_simulator</name>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="14"/>
<source>piControl simulator</source>
<translation>piControl Simulator</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="20"/>
<source>Simulator settings</source>
<translation>Simulatoreinstellungen</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="26"/>
<source>Last used:</source>
<translation>Zuletzt verwendet:</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="33"/>
<source>piCtory file:</source>
<translation>piCtory Datei:</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="50"/>
<source>select...</source>
<translation>auswählen...</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="57"/>
<source>Process image:</source>
<translation>Prozessabbild:</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="74"/>
<source>Stop action:</source>
<translation>Stopaktion:</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="81"/>
<source>Restart action:</source>
<translation>Neustartaktion:</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="102"/>
<source>Restore piCtory default values</source>
<translation>piCtory Standardwerte setzen</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="112"/>
<source>Reset everything to ZERO</source>
<translation>Alles auf NULL setzen</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="122"/>
<source>RevPiModIO integration</source>
<translation>RevPiModIO Integration</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="154"/>
<source>Start with piCtory default values</source>
<translation>Start mit piCtory Standardwerten</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="164"/>
<source>Start with empty process image</source>
<translation>Start mit leerem Prozessabbild</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="174"/>
<source>Start without changing actual process image</source>
<translation>Start ohne Veränderung des Prozessabbilds</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="95"/>
<source>Remove process image file</source>
<translation>Prozessabbilddatei löschen</translation>
</message>
<message>
<location filename="../../include/ui_dev/simulator.ui" line="128"/>
<source>You can work with this simulator if you call RevPiModIO with this additional parameters:</source>
<translation>Mit diesem Simulator kann gearbeitet werden, indem zum Aufruf von RevPiModIO folgende Parameter hinzugefügt werden:</translation>
</message>
</context>
<context>
<name>wid_debugcontrol</name>
<message>
@@ -1506,7 +1587,7 @@ applicable law.
<translation>Dateiliste neu laden</translation>
</message>
<message>
<location filename="../../include/ui_dev/files.ui" line="186"/>
<location filename="../../include/ui_dev/files.ui" line="183"/>
<source>/</source>
<translation></translation>
</message>
@@ -1556,7 +1637,7 @@ applicable law.
<message>
<location filename="../../include/ui_dev/revpicommander.ui" line="83"/>
<source>PLC watch &amp;mode</source>
<translation>SPS &amp;Betrachter</translation>
<translation>SPS &amp;Monitor</translation>
</message>
<message>
<location filename="../../include/ui_dev/revpicommander.ui" line="103"/>
@@ -1670,8 +1751,8 @@ applicable law.
</message>
<message>
<location filename="../../include/ui_dev/revpicommander.ui" line="229"/>
<source>Start local si&amp;mulator...</source>
<translation>Lokalen Si&amp;mulator starten...</translation>
<source>RevPi si&amp;mulator...</source>
<translation>RevPi Si&amp;mulator...</translation>
</message>
</context>
<context>

View File

@@ -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,7 +5,7 @@
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__license__ = "GPLv3"
__version__ = "0.9.1"
__version__ = "0.9.3"
import webbrowser
from os.path import basename, dirname, join
@@ -22,6 +22,7 @@ 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
@@ -69,24 +70,43 @@ 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:
if not helper.cm.pyload_connect(settings_index):
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!"
)
)
@QtCore.pyqtSlot(str)
def on_cm_connection_error_observed(self, message: str):
"""
@@ -127,11 +147,17 @@ 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_connection.setText("configrsc=\"{0}\", procimg=\"{1}\"".format(
helper.cm.simulating_configrsc,
helper.cm.simulating_procimg,
))
else:
self.txt_connection.setText("{0} - {1}:{2}".format(
helper.cm.name,
helper.cm.address,
helper.cm.port
))
self.win_files = RevPiFiles(self)
@QtCore.pyqtSlot(str, str)
@@ -186,59 +212,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 +257,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 +371,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 +407,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
@@ -477,8 +476,8 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
if __name__ == "__main__":
import sys
#QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
#QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
# QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
# QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
app = QtWidgets.QApplication(sys.argv)
@@ -497,7 +496,7 @@ if __name__ == "__main__":
win = RevPiCommander()
win.show()
exit_code = app.exec_()
exit_code = app.exec()
# Clean up workers
helper.cm.requestInterruption()

View File

@@ -13,6 +13,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
import helper
import proginit as pi
from backgroundworker import BackgroundWorker
from helper import WidgetData
from ui.files_ui import Ui_win_files
@@ -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

@@ -20,9 +20,9 @@ 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)
@@ -31,7 +31,7 @@ class DataThread(QtCore.QThread):
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 +45,7 @@ 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 = xmlcall(start_position, self.max_block).data # type: bytes
eof = True
if buff_log == b'\x16': # 'ESC'
@@ -56,20 +56,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:
@@ -109,7 +112,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 +123,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

@@ -25,7 +25,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
@@ -51,6 +50,7 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
con_item.setText(0, helper.settings.value("name", "Revolution Pi", str))
con_item.setText(1, helper.settings.value("address", "127.0.0.1", str))
con_item.setData(0, WidgetData.port, helper.settings.value("port", self.__default_port, int))
con_item.setData(0, WidgetData.timeout, helper.settings.value("timeout", 5, int))
con_item.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"))
@@ -95,6 +95,7 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
helper.settings.setValue("folder", parent.text(0) if parent else "")
helper.settings.setValue("name", node.text(0))
helper.settings.setValue("port", node.data(0, WidgetData.port))
helper.settings.setValue("timeout", node.data(0, WidgetData.timeout))
if node.data(0, WidgetData.last_dir_upload):
helper.settings.setValue("last_dir_upload", node.data(0, WidgetData.last_dir_upload))
@@ -192,6 +193,7 @@ 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)
def _get_folder_item(self, name: str):
@@ -235,6 +237,7 @@ 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:
@@ -275,6 +278,7 @@ 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)
sub_folder = self._get_folder_item(self.cbb_folder.currentText())
if sub_folder:
sub_folder.addChild(self.__current_item)
@@ -303,6 +307,12 @@ 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(str)
def on_cbb_folder_editTextChanged(self, text: str):
pi.logger.debug("RevPiPlcList.on_cbb_folder_editTextChanged({0})".format(text))

View File

@@ -9,7 +9,7 @@ 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
@@ -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 # # # # #

122
revpicommander/simulator.py Normal file
View File

@@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-
"""Simulator for piControl."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2021 Sven Sager"
__license__ = "GPLv3"
from os import W_OK, access
from os.path import basename, dirname, exists, join
from PyQt5 import QtCore, QtGui, QtWidgets
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)
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

@@ -19,7 +19,7 @@ class MyEggInfo(distutils.command.install_egg_info.install_egg_info):
setup(
version="0.9.1",
version="0.9.3",
python_requires="~=3.4",
requires=["PyQt5", "revpimodio2", "zeroconf"],

View File

@@ -9,19 +9,21 @@ SOURCES = revpicommander/aclmanager.py \
revpicommander/revpioption.py \
revpicommander/revpiplclist.py \
revpicommander/revpiprogram.py \
revpicommander/simulator.py \
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/mqttmanager.ui \
include/ui_dev/revpiinfo.ui \
include/ui_dev/revpilogfile.ui \
include/ui_dev/revpioption.ui \
include/ui_dev/revpiplclist.ui \
include/ui_dev/revpiprogram.ui \
include/ui_dev/simulator.ui \
include/ui_dev/revpicommander.ui
TRANSLATIONS = revpicommander/locale/revpicommander_de.ts