mirror of
https://github.com/naruxde/revpicommander.git
synced 2025-11-09 17:08:05 +01:00
Switch project so src layout
This will make it possible to build pip packages. Add a make file to compile UI files and build mac and win applications.
This commit is contained in:
5
src/revpicommander/__init__.py
Normal file
5
src/revpicommander/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Package: RevPiCommander."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
22
src/revpicommander/__main__.py
Normal file
22
src/revpicommander/__main__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Start main application of this package."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
# If we are running from a wheel, add the wheel to sys.path
|
||||
if __package__ == "":
|
||||
from os.path import dirname
|
||||
from sys import path
|
||||
|
||||
# __file__ is package-*.whl/package/__main__.py
|
||||
# Resulting path is the name of the wheel itself
|
||||
package_path = dirname(dirname(__file__))
|
||||
path.insert(0, package_path)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
from revpicommander.revpicommander import main
|
||||
|
||||
# Run the main application of this package
|
||||
sys.exit(main())
|
||||
430
src/revpicommander/aclmanager.py
Normal file
430
src/revpicommander/aclmanager.py
Normal file
@@ -0,0 +1,430 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Manager for ACL lists."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
from re import compile
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from .helper import WidgetData
|
||||
from .ui.aclmanager_ui import Ui_diag_aclmanager
|
||||
|
||||
|
||||
class AclManager(QtWidgets.QDialog, Ui_diag_aclmanager):
|
||||
"""ACL manager."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(AclManager, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
self.setFixedSize(self.size())
|
||||
|
||||
self.__re_ipacl = compile(r"(25[0-5]|(2[0-4]|[01]?\d|)\d)(\.(25[0-5]|(2[0-4]|[01]?\d|)\d)){3},-1")
|
||||
self.__dict_acltext = {}
|
||||
self.__cbb_level_loaded_index = 0
|
||||
self.__mrk_message_shown = 0
|
||||
self.__oldacl = ""
|
||||
self.__read_only = False
|
||||
|
||||
# Prepare GUI
|
||||
self.tb_acls.setColumnWidth(0, 300)
|
||||
self.tb_acls.setColumnWidth(1, 50)
|
||||
self.btn_edit.setEnabled(False)
|
||||
self.btn_remove.setEnabled(False)
|
||||
self.btn_add.setEnabled(False)
|
||||
|
||||
# Move to next focus when enter a "."
|
||||
self.mrk_txt_ip_a_keyPressEvent = self.txt_ip_a.keyPressEvent
|
||||
self.mrk_txt_ip_b_keyPressEvent = self.txt_ip_b.keyPressEvent
|
||||
self.mrk_txt_ip_c_keyPressEvent = self.txt_ip_c.keyPressEvent
|
||||
self.mrk_txt_ip_d_keyPressEvent = self.txt_ip_d.keyPressEvent
|
||||
self.txt_ip_a.keyPressEvent = self.txt_ip_a_keyPressEvent
|
||||
self.txt_ip_b.keyPressEvent = self.txt_ip_b_keyPressEvent
|
||||
self.txt_ip_c.keyPressEvent = self.txt_ip_c_keyPressEvent
|
||||
self.txt_ip_d.keyPressEvent = self.txt_ip_d_keyPressEvent
|
||||
|
||||
def __check_load_error(self):
|
||||
"""
|
||||
Check load errors and shows a message one time.
|
||||
:return: True, if message was shown
|
||||
"""
|
||||
if bool(self.__mrk_message_shown & 1):
|
||||
return False
|
||||
|
||||
for row in range(self.tb_acls.rowCount()):
|
||||
if self.tb_acls.item(row, 0).data(WidgetData.has_error):
|
||||
self.__mrk_message_shown += 1
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"There are errors in the ACL list!\nCheck the ALC levels of the "
|
||||
"red lines in the table. The ACL levels or ip addresses are "
|
||||
"invalid. If you save this dialog again, we will remove the "
|
||||
"wrong entries automatically."
|
||||
)
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _changes_done(self):
|
||||
"""
|
||||
Check for unsaved changes in dialog.
|
||||
|
||||
:return: True, if unsaved changes was found
|
||||
"""
|
||||
return self.__table_to_acl() != self.__oldacl
|
||||
|
||||
def accept(self) -> None:
|
||||
"""Save settings."""
|
||||
if self.btn_add.isEnabled():
|
||||
# Entry is ready to save, did the user forgot to click the button?
|
||||
ask = QtWidgets.QMessageBox.question(
|
||||
self, self.tr("Unsaved entry"), self.tr(
|
||||
"You worked on a new ACL entry. Do you want to save "
|
||||
"that entry, too?"
|
||||
)
|
||||
) == QtWidgets.QMessageBox.Yes
|
||||
if ask:
|
||||
self.on_btn_add_pressed()
|
||||
|
||||
if self.__check_load_error():
|
||||
return
|
||||
|
||||
self.__oldacl = self.__table_to_acl()
|
||||
super(AclManager, self).accept()
|
||||
|
||||
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||
if self._changes_done():
|
||||
ask = QtWidgets.QMessageBox.question(
|
||||
self, self.tr("Question"), self.tr(
|
||||
"Do you really want to quit? \nUnsaved changes will be lost"
|
||||
)
|
||||
) == QtWidgets.QMessageBox.Yes
|
||||
|
||||
if ask:
|
||||
self.reject()
|
||||
else:
|
||||
a0.ignore()
|
||||
|
||||
def exec(self) -> int:
|
||||
return super(AclManager, self).exec()
|
||||
|
||||
def reject(self) -> None:
|
||||
"""Restore old settings."""
|
||||
self.setup_acl_manager(self.__oldacl, self.__dict_acltext)
|
||||
super(AclManager, self).reject()
|
||||
|
||||
def setup_acl_manager(self, acl_string: str, acl_texts: dict):
|
||||
if type(acl_string) != str:
|
||||
raise TypeError("acl_string must be <class 'str'>")
|
||||
if type(acl_texts) != dict:
|
||||
raise TypeError("acl_texts must be <class 'dict'>")
|
||||
|
||||
self.__dict_acltext = acl_texts.copy()
|
||||
|
||||
# Clean up widgets
|
||||
while self.tb_acls.rowCount() > 0:
|
||||
self.tb_acls.removeRow(0)
|
||||
self.cbb_level.clear()
|
||||
self.cbb_level.addItem(self.tr("Select..."), -1)
|
||||
self.lbl_level_info.clear()
|
||||
|
||||
self.__re_ipacl = compile(
|
||||
r"([\d*]{1,3}\.){3}[\d*]{1,3},[" +
|
||||
str(min(self.__dict_acltext.keys(), default=0)) + r"-" +
|
||||
str(max(self.__dict_acltext.keys(), default=0)) + r"]"
|
||||
)
|
||||
|
||||
for ip_level in acl_string.split(" "):
|
||||
self.__table_add_acl(ip_level)
|
||||
|
||||
lst_level_text = []
|
||||
for level in sorted(self.__dict_acltext.keys()):
|
||||
level_text = self.tr("Level") + " {0}: {1}".format(level, self.__dict_acltext[level])
|
||||
lst_level_text.append(level_text)
|
||||
self.cbb_level.addItem(level_text, level)
|
||||
|
||||
self.lbl_level_info.setText("\n".join(lst_level_text))
|
||||
self.__oldacl = self.__table_to_acl()
|
||||
|
||||
def get_acl(self):
|
||||
"""Get current ACL string."""
|
||||
return self.__oldacl
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# region # REGION: ACL list
|
||||
|
||||
def __load_selected_entry(self):
|
||||
row = self.tb_acls.currentRow()
|
||||
if row < 0:
|
||||
return
|
||||
|
||||
item = self.tb_acls.item(row, 0)
|
||||
ip_blocks = item.text().split(".")
|
||||
self.txt_ip_a.setText(ip_blocks[0])
|
||||
self.txt_ip_b.setText(ip_blocks[1])
|
||||
self.txt_ip_c.setText(ip_blocks[2])
|
||||
self.txt_ip_d.setText(ip_blocks[3])
|
||||
|
||||
for index in range(self.cbb_level.count()):
|
||||
if self.cbb_level.itemData(index, QtCore.Qt.UserRole) == item.data(WidgetData.acl_level):
|
||||
self.__cbb_level_loaded_index = index
|
||||
self.cbb_level.setCurrentIndex(index)
|
||||
break
|
||||
|
||||
self._check_all_filled()
|
||||
|
||||
def __table_add_acl(self, ip_level: str):
|
||||
"""Add ACL entry to table."""
|
||||
# Empty acl_string create empty ip_level
|
||||
if not ip_level:
|
||||
return
|
||||
|
||||
ip, level = ip_level.split(",")
|
||||
if self.__re_ipacl.match(ip_level):
|
||||
brush = QtGui.QBrush()
|
||||
has_error = False
|
||||
tool_tip = ""
|
||||
else:
|
||||
brush = QtGui.QBrush(QtGui.QColor("red"))
|
||||
has_error = True
|
||||
tool_tip = self.tr("This entry has an invalid ACL level or wrong IP format!")
|
||||
|
||||
for row in range(self.tb_acls.rowCount()):
|
||||
item_0 = self.tb_acls.item(row, 0)
|
||||
if item_0.text() == ip:
|
||||
item_1 = self.tb_acls.item(row, 1)
|
||||
|
||||
# Update existing entry
|
||||
item_0.setData(WidgetData.acl_level, int(level))
|
||||
item_0.setData(WidgetData.has_error, has_error)
|
||||
item_0.setBackground(brush)
|
||||
item_0.setToolTip(tool_tip)
|
||||
item_1.setText(level)
|
||||
item_1.setBackground(brush)
|
||||
item_1.setToolTip(tool_tip)
|
||||
return
|
||||
|
||||
row_count = self.tb_acls.rowCount()
|
||||
self.tb_acls.insertRow(row_count)
|
||||
|
||||
item = QtWidgets.QTableWidgetItem(ip)
|
||||
item.setData(WidgetData.acl_level, int(level))
|
||||
item.setData(WidgetData.has_error, has_error)
|
||||
item.setBackground(brush)
|
||||
item.setToolTip(tool_tip)
|
||||
self.tb_acls.setItem(row_count, 0, item)
|
||||
|
||||
item = QtWidgets.QTableWidgetItem(level)
|
||||
item.setBackground(brush)
|
||||
item.setToolTip(tool_tip)
|
||||
self.tb_acls.setItem(row_count, 1, item)
|
||||
|
||||
def __table_to_acl(self, force=False, row_indexes=None):
|
||||
"""
|
||||
Create acl string with valid entries only.
|
||||
|
||||
:param force: If True, return all entries
|
||||
:param row_indexes: Only from indexes ist <class 'list'>
|
||||
:return: ACL string
|
||||
"""
|
||||
if row_indexes is None:
|
||||
row_indexes = range(self.tb_acls.rowCount())
|
||||
|
||||
buff_acl = ""
|
||||
for i in row_indexes:
|
||||
item = self.tb_acls.item(i, 0)
|
||||
ip_level = "{0},{1} ".format(item.text(), item.data(WidgetData.acl_level))
|
||||
if not (force or self.__re_ipacl.match(ip_level)):
|
||||
continue
|
||||
buff_acl += ip_level
|
||||
return buff_acl.strip()
|
||||
|
||||
@QtCore.pyqtSlot(QtWidgets.QTableWidgetItem)
|
||||
def on_tb_acls_itemDoubleClicked(self, item: QtWidgets.QTableWidgetItem):
|
||||
if not self.__read_only:
|
||||
self.__load_selected_entry()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_tb_acls_itemSelectionChanged(self):
|
||||
selected_rows = int(len(self.tb_acls.selectedItems()) / self.tb_acls.columnCount())
|
||||
self.btn_edit.setEnabled(not self.__read_only and selected_rows == 1)
|
||||
self.btn_remove.setEnabled(not self.__read_only and selected_rows > 0)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_edit_pressed(self):
|
||||
self.__load_selected_entry()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_remove_pressed(self):
|
||||
lst_selected_row_indexes = [mi.row() for mi in self.tb_acls.selectionModel().selectedRows(0)]
|
||||
if len(lst_selected_row_indexes) == 0:
|
||||
return
|
||||
|
||||
str_remove = ""
|
||||
for ip_level in self.__table_to_acl(True, lst_selected_row_indexes).split(" "):
|
||||
if not ip_level:
|
||||
# Empty string will return empty field
|
||||
continue
|
||||
ip, level = ip_level.split(",")
|
||||
str_remove += "\nIP: {0:>15}\tLevel: {1}".format(ip, level)
|
||||
|
||||
ask = QtWidgets.QMessageBox.question(
|
||||
self, self.tr("Question"), self.tr(
|
||||
"Do you really want to delete the following items?\n{0}"
|
||||
).format(str_remove)
|
||||
) == QtWidgets.QMessageBox.Yes
|
||||
|
||||
if ask:
|
||||
# Turn order to start deleting from the button to preserve right indexes
|
||||
lst_selected_row_indexes.sort(reverse=True)
|
||||
for row in lst_selected_row_indexes:
|
||||
self.tb_acls.removeRow(row)
|
||||
|
||||
# endregion # # # # #
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# region # REGION: Group box IP
|
||||
|
||||
def _check_all_filled(self):
|
||||
all_filled = \
|
||||
len(self.txt_ip_a.text()) > 0 and \
|
||||
len(self.txt_ip_b.text()) > 0 and \
|
||||
len(self.txt_ip_c.text()) > 0 and \
|
||||
len(self.txt_ip_d.text()) > 0 and \
|
||||
self.cbb_level.currentIndex() != 0 and (
|
||||
self.txt_ip_a.isModified() or self.txt_ip_b.isModified() or
|
||||
self.txt_ip_c.isModified() or self.txt_ip_d.isModified() or
|
||||
self.cbb_level.currentIndex() != self.__cbb_level_loaded_index
|
||||
)
|
||||
|
||||
self.btn_add.setEnabled(not self.__read_only and all_filled)
|
||||
|
||||
def _move_ip_cursor(self, key: int, sender_widget: QtWidgets.QLineEdit,
|
||||
last_widget: QtWidgets.QLineEdit, next_widget: QtWidgets.QLineEdit):
|
||||
"""
|
||||
Move cursor between ip enter widgets.
|
||||
|
||||
:param key: Pressed key to check
|
||||
:param sender_widget: Sender widget of this key
|
||||
:param last_widget: Set focus to this widget on backspace key
|
||||
:param next_widget: Set focus to this widget on period key
|
||||
:return: True, if the key should not be processed further
|
||||
"""
|
||||
if last_widget and key == QtCore.Qt.Key_Backspace and len(sender_widget.text()) == 0:
|
||||
last_widget.setFocus()
|
||||
elif next_widget and key == QtCore.Qt.Key_Period:
|
||||
next_widget.setFocus()
|
||||
return True
|
||||
return False
|
||||
|
||||
def txt_ip_a_keyPressEvent(self, a0: QtGui.QKeyEvent) -> None:
|
||||
if not self._move_ip_cursor(a0.key(), self.txt_ip_a, self.txt_ip_a, self.txt_ip_b):
|
||||
self.mrk_txt_ip_a_keyPressEvent(a0)
|
||||
|
||||
def txt_ip_b_keyPressEvent(self, a0: QtGui.QKeyEvent) -> None:
|
||||
if not self._move_ip_cursor(a0.key(), self.txt_ip_b, self.txt_ip_a, self.txt_ip_c):
|
||||
self.mrk_txt_ip_b_keyPressEvent(a0)
|
||||
|
||||
def txt_ip_c_keyPressEvent(self, a0: QtGui.QKeyEvent) -> None:
|
||||
if not self._move_ip_cursor(a0.key(), self.txt_ip_c, self.txt_ip_b, self.txt_ip_d):
|
||||
self.mrk_txt_ip_c_keyPressEvent(a0)
|
||||
|
||||
def txt_ip_d_keyPressEvent(self, a0: QtGui.QKeyEvent) -> None:
|
||||
if not self._move_ip_cursor(a0.key(), self.txt_ip_d, self.txt_ip_c, self.txt_ip_d):
|
||||
self.mrk_txt_ip_d_keyPressEvent(a0)
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def on_txt_ip_a_textChanged(self, text: str):
|
||||
self._check_all_filled()
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def on_txt_ip_b_textChanged(self, text: str):
|
||||
self._check_all_filled()
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def on_txt_ip_c_textChanged(self, text: str):
|
||||
self._check_all_filled()
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def on_txt_ip_d_textChanged(self, text: str):
|
||||
self._check_all_filled()
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def on_cbb_level_currentIndexChanged(self, index: int):
|
||||
self._check_all_filled()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_add_pressed(self):
|
||||
"""Add a new entry to acl table."""
|
||||
ip_level = "{0}.{1}.{2}.{3},{4}".format(
|
||||
self.txt_ip_a.text(),
|
||||
self.txt_ip_b.text(),
|
||||
self.txt_ip_c.text(),
|
||||
self.txt_ip_d.text(),
|
||||
self.cbb_level.currentData(QtCore.Qt.UserRole)
|
||||
)
|
||||
if self.__re_ipacl.match(ip_level):
|
||||
self.__table_add_acl(ip_level)
|
||||
self.on_btn_clear_pressed()
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Can not save new ACL entry! Check format of ip address "
|
||||
"and acl level is in value list."
|
||||
)
|
||||
)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_clear_pressed(self):
|
||||
"""Clear entry widgets."""
|
||||
self.txt_ip_a.clear()
|
||||
self.txt_ip_b.clear()
|
||||
self.txt_ip_c.clear()
|
||||
self.txt_ip_d.clear()
|
||||
self.cbb_level.setCurrentIndex(0)
|
||||
self.__cbb_level_loaded_index = 0
|
||||
|
||||
# endregion # # # # #
|
||||
|
||||
@property
|
||||
def read_only(self):
|
||||
"""Getter for read_only value."""
|
||||
return self.__read_only
|
||||
|
||||
@read_only.setter
|
||||
def read_only(self, value):
|
||||
"""Setter for read_only window."""
|
||||
self.__read_only = value
|
||||
self.txt_ip_a.setEnabled(not value)
|
||||
self.txt_ip_b.setEnabled(not value)
|
||||
self.txt_ip_c.setEnabled(not value)
|
||||
self.txt_ip_d.setEnabled(not value)
|
||||
self.cbb_level.setEnabled(not value)
|
||||
self.btn_clear.setEnabled(not value)
|
||||
if value:
|
||||
self.btn_box.setStandardButtons(
|
||||
QtWidgets.QDialogButtonBox.Cancel
|
||||
)
|
||||
else:
|
||||
self.btn_box.setStandardButtons(
|
||||
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
|
||||
)
|
||||
|
||||
|
||||
# Debugging
|
||||
if __name__ == '__main__':
|
||||
app = QtWidgets.QApplication([])
|
||||
win = AclManager(None)
|
||||
win.setup_acl_manager(
|
||||
"127.0.1.*,0 127.0.0.1,1 127.0.0.2,2", {
|
||||
0: "Just have a look",
|
||||
1: "Do more things",
|
||||
}
|
||||
)
|
||||
win.read_only = False
|
||||
rc = win.exec()
|
||||
print(
|
||||
"return code:", rc,
|
||||
"acl:", win.get_acl()
|
||||
)
|
||||
333
src/revpicommander/avahisearch.py
Normal file
333
src/revpicommander/avahisearch.py
Normal file
@@ -0,0 +1,333 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Revolution Pi search with zeroconf."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import webbrowser
|
||||
from os import name as osname
|
||||
from re import compile
|
||||
from sys import platform
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
from zeroconf import IPVersion, ServiceBrowser, Zeroconf
|
||||
|
||||
from . import proginit as pi
|
||||
from .helper import WidgetData, settings
|
||||
from .ui.avahisearch_ui import Ui_diag_search
|
||||
|
||||
|
||||
class AvahiSearchThread(QtCore.QThread):
|
||||
"""Search thread for Revolution Pi with installed RevPiPyLoad."""
|
||||
added = QtCore.pyqtSignal(str, str, int, str, str)
|
||||
removed = QtCore.pyqtSignal(str, str)
|
||||
updated = QtCore.pyqtSignal(str, str, int, str, str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(AvahiSearchThread, self).__init__(parent)
|
||||
self._cycle_wait_ms = 1000
|
||||
|
||||
self.__dict_arp = {}
|
||||
self.re_posix = compile(
|
||||
r"(?P<ip>(\d{1,3}\.){3}\d{1,3}).*"
|
||||
r"(?P<mac>([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})"
|
||||
)
|
||||
|
||||
def _update_arp(self) -> None:
|
||||
"""Find mac address in arp table."""
|
||||
if osname == "posix":
|
||||
with open("/proc/net/arp") as fh:
|
||||
for line in fh.readlines():
|
||||
ip_mac = self.re_posix.search(line)
|
||||
if ip_mac:
|
||||
self.__dict_arp[ip_mac.group("ip")] = ip_mac.group("mac")
|
||||
|
||||
def get_mac(self, ip: str) -> dict:
|
||||
"""
|
||||
Get mac address of ip, if known.
|
||||
|
||||
:param ip: IP address to find mac address
|
||||
:return: MAC address as string or empty string, if unknown
|
||||
"""
|
||||
return self.__dict_arp.get(ip, "")
|
||||
|
||||
def remove_service(self, zeroconf: Zeroconf, conf_type: str, name: str) -> None:
|
||||
"""Revolution Pi disappeared."""
|
||||
pi.logger.debug("AvahiSearchThread.remove_service")
|
||||
self.removed.emit(name, conf_type)
|
||||
|
||||
def add_service(self, zeroconf: Zeroconf, conf_type: str, name: str) -> None:
|
||||
"""New Revolution Pi found."""
|
||||
pi.logger.debug("AvahiSearchThread.add_service")
|
||||
info = zeroconf.get_service_info(conf_type, name)
|
||||
if not info:
|
||||
return
|
||||
|
||||
for ip in info.parsed_addresses(IPVersion.V4Only):
|
||||
self.added.emit(name, info.server, info.port, conf_type, ip)
|
||||
|
||||
def update_service(self, zeroconf: Zeroconf, conf_type: str, name: str) -> None:
|
||||
"""New data of revolution pi"""
|
||||
pi.logger.debug("AvahiSearchThread.add_service")
|
||||
info = zeroconf.get_service_info(conf_type, name)
|
||||
if not info:
|
||||
return
|
||||
|
||||
for ip in info.parsed_addresses(IPVersion.V4Only):
|
||||
self.updated.emit(name, info.server, info.port, conf_type, ip)
|
||||
|
||||
def run(self) -> None:
|
||||
pi.logger.debug("Started zero conf discovery.")
|
||||
zeroconf = Zeroconf()
|
||||
revpi_browser = ServiceBrowser(zeroconf, "_revpipyload._tcp.local.", self)
|
||||
while not self.isInterruptionRequested():
|
||||
# Just hanging around :)
|
||||
self.msleep(self._cycle_wait_ms)
|
||||
zeroconf.close()
|
||||
pi.logger.debug("Stopped zero conf discovery.")
|
||||
|
||||
|
||||
class AvahiSearch(QtWidgets.QDialog, Ui_diag_search):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(AvahiSearch, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.clipboard = QtGui.QGuiApplication.clipboard()
|
||||
self.connect_index = -1
|
||||
self.known_hosts = {}
|
||||
self.th_zero_conf = AvahiSearchThread(self)
|
||||
|
||||
self.tb_revpi.setColumnWidth(0, 250)
|
||||
self.btn_connect.setEnabled(False)
|
||||
self.btn_save.setEnabled(False)
|
||||
|
||||
self.restoreGeometry(settings.value("avahisearch/geo", b''))
|
||||
column_sizes = settings.value("avahisearch/column_sizes", [], type=list)
|
||||
if len(column_sizes) == self.tb_revpi.columnCount():
|
||||
for i in range(self.tb_revpi.columnCount()):
|
||||
self.tb_revpi.setColumnWidth(i, int(column_sizes[i]))
|
||||
|
||||
# Global context menus
|
||||
self.cm_quick_actions = QtWidgets.QMenu(self)
|
||||
self.cm_quick_actions.addAction(self.act_open_pictory)
|
||||
self.cm_quick_actions.addSeparator()
|
||||
self.cm_quick_actions.addAction(self.act_copy_ip)
|
||||
self.cm_quick_actions.addAction(self.act_copy_host)
|
||||
|
||||
self.tb_revpi.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
self.tb_revpi.customContextMenuRequested.connect(self._context_menu)
|
||||
|
||||
@QtCore.pyqtSlot(QtCore.QPoint)
|
||||
def _context_menu(self, position: QtCore.QPoint) -> None:
|
||||
sender = self.sender()
|
||||
action = self.cm_quick_actions.exec(sender.mapToGlobal(position))
|
||||
if action:
|
||||
action.trigger()
|
||||
|
||||
def _load_known_hosts(self) -> None:
|
||||
"""Load existing connections to show hostname of existing ip addresses"""
|
||||
self.known_hosts.clear()
|
||||
|
||||
for i in range(settings.beginReadArray("connections")):
|
||||
settings.setArrayIndex(i)
|
||||
|
||||
name = settings.value("name", type=str)
|
||||
folder = settings.value("folder", type=str)
|
||||
address = settings.value("address", type=str)
|
||||
self.known_hosts[address] = "{0}/{1}".format(folder, name) if folder else name
|
||||
|
||||
settings.endArray()
|
||||
|
||||
def _restart_search(self) -> None:
|
||||
"""Clean up and restart search thread."""
|
||||
while self.tb_revpi.rowCount() > 0:
|
||||
self.tb_revpi.removeRow(0)
|
||||
self.th_zero_conf.requestInterruption()
|
||||
|
||||
self.th_zero_conf = AvahiSearchThread(self)
|
||||
self.th_zero_conf.added.connect(self.on_avahi_added)
|
||||
self.th_zero_conf.updated.connect(self.on_avahi_added)
|
||||
self.th_zero_conf.removed.connect(self.on_avahi_removed)
|
||||
self.th_zero_conf.start()
|
||||
|
||||
def _save_connection(self, row: int, 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) or -1
|
||||
"""
|
||||
item = self.tb_revpi.item(row, 0)
|
||||
if not item:
|
||||
return -1
|
||||
|
||||
folder_name = self.tr("Auto discovered")
|
||||
selected_name = item.text()
|
||||
selected_address = item.data(WidgetData.address)
|
||||
selected_port = item.data(WidgetData.port)
|
||||
i = 0
|
||||
for i in range(settings.beginReadArray("connections")):
|
||||
settings.setArrayIndex(i)
|
||||
|
||||
name = settings.value("name", type=str)
|
||||
address = settings.value("address", type=str)
|
||||
port = settings.value("port", type=int)
|
||||
if address.lower() == selected_address.lower() and port == selected_port:
|
||||
if not no_warn:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, self.tr("Already in list..."), self.tr(
|
||||
"The selected Revolution Pi is already saved in your "
|
||||
"connection list as '{0}'."
|
||||
).format(name)
|
||||
)
|
||||
settings.endArray()
|
||||
return i
|
||||
|
||||
settings.endArray()
|
||||
settings.beginWriteArray("connections")
|
||||
|
||||
settings.setArrayIndex(i + 1)
|
||||
settings.setValue("address", selected_address)
|
||||
settings.setValue("folder", folder_name)
|
||||
settings.setValue("name", selected_name)
|
||||
settings.setValue("port", selected_port)
|
||||
|
||||
settings.endArray()
|
||||
|
||||
if not no_warn:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, self.tr("Success"), self.tr(
|
||||
"The connection with the name '{0}' was successfully saved "
|
||||
"to folder '{1}' in your connections."
|
||||
).format(selected_name, folder_name)
|
||||
)
|
||||
|
||||
return i + 1
|
||||
|
||||
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||
settings.setValue("avahisearch/geo", self.saveGeometry())
|
||||
settings.setValue("avahisearch/column_sizes", [
|
||||
self.tb_revpi.columnWidth(i)
|
||||
for i in range(self.tb_revpi.columnCount())
|
||||
])
|
||||
|
||||
def exec(self) -> int:
|
||||
self._load_known_hosts()
|
||||
self._restart_search()
|
||||
rc = super(AvahiSearch, self).exec()
|
||||
self.th_zero_conf.requestInterruption()
|
||||
return rc
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_copy_host_triggered(self) -> None:
|
||||
"""Copy ip address of selected item to clipboard."""
|
||||
selected_items = self.tb_revpi.selectedItems()
|
||||
if not selected_items:
|
||||
return
|
||||
item = selected_items[0]
|
||||
host_name = item.data(WidgetData.host_name)
|
||||
if platform == "win32":
|
||||
# Strip hostname on Windows systems, it can not resolve .local addresses
|
||||
host_name = host_name[:host_name.find(".")]
|
||||
self.clipboard.setText(host_name)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_copy_ip_triggered(self) -> None:
|
||||
"""Copy ip address of selected item to clipboard."""
|
||||
selected_items = self.tb_revpi.selectedItems()
|
||||
if not selected_items:
|
||||
return
|
||||
item = selected_items[0]
|
||||
self.clipboard.setText(item.data(WidgetData.address))
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_open_pictory_triggered(self) -> None:
|
||||
"""Open piCtory in default browser of operating system."""
|
||||
selected_items = self.tb_revpi.selectedItems()
|
||||
if not selected_items:
|
||||
return
|
||||
item = selected_items[0]
|
||||
if platform == "win32":
|
||||
webbrowser.open("http://{0}/".format(item.data(WidgetData.address)))
|
||||
else:
|
||||
webbrowser.open("http://{0}/".format(item.data(WidgetData.host_name)))
|
||||
|
||||
@QtCore.pyqtSlot(str, str, int, str, str)
|
||||
def on_avahi_added(self, name: str, server: str, port: int, conf_type: str, ip: str) -> None:
|
||||
"""New Revolution Pi found."""
|
||||
index = -1
|
||||
for i in range(self.tb_revpi.rowCount()):
|
||||
if self.tb_revpi.item(i, 0).data(WidgetData.object_name) == name:
|
||||
index = i
|
||||
break
|
||||
|
||||
if index == -1:
|
||||
# New Row
|
||||
item_name = QtWidgets.QTableWidgetItem()
|
||||
item_ip = QtWidgets.QTableWidgetItem()
|
||||
|
||||
index = self.tb_revpi.rowCount()
|
||||
self.tb_revpi.insertRow(index)
|
||||
self.tb_revpi.setItem(index, 0, item_name)
|
||||
self.tb_revpi.setItem(index, 1, item_ip)
|
||||
else:
|
||||
# Update row
|
||||
item_name = self.tb_revpi.item(index, 0)
|
||||
item_ip = self.tb_revpi.item(index, 1)
|
||||
|
||||
host_name = server[:-1]
|
||||
item_name.setIcon(QtGui.QIcon(":/main/ico/cpu.ico"))
|
||||
if ip in self.known_hosts:
|
||||
item_name.setText("{0} ({1})".format(host_name, self.known_hosts[ip]))
|
||||
else:
|
||||
item_name.setText(host_name)
|
||||
item_name.setData(WidgetData.object_name, name)
|
||||
item_name.setData(WidgetData.address, ip)
|
||||
item_name.setData(WidgetData.port, port)
|
||||
item_name.setData(WidgetData.host_name, host_name)
|
||||
item_ip.setText(ip)
|
||||
|
||||
@QtCore.pyqtSlot(str, str)
|
||||
def on_avahi_removed(self, name: str, conf_type: str) -> None:
|
||||
"""Revolution Pi disappeared."""
|
||||
for i in range(self.tb_revpi.rowCount()):
|
||||
if self.tb_revpi.item(i, 0).data(WidgetData.object_name) == name:
|
||||
self.tb_revpi.removeRow(i)
|
||||
break
|
||||
|
||||
@QtCore.pyqtSlot(int, int)
|
||||
def on_tb_revpi_cellDoubleClicked(self, row: int, column: int) -> None:
|
||||
"""Connect to double clicked Revolution Pi."""
|
||||
pi.logger.debug("AvahiSearch.on_tb_revpi_cellDoubleClicked")
|
||||
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) -> 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) -> None:
|
||||
"""Connect to selected Revolution Pi."""
|
||||
pi.logger.debug("AvahiSearch.on_btn_connect_pressed")
|
||||
if self.tb_revpi.currentRow() == -1:
|
||||
return
|
||||
self.connect_index = self._save_connection(self.tb_revpi.currentRow(), no_warn=True)
|
||||
self.accept()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_save_pressed(self) -> None:
|
||||
"""Save selected Revolution Pi."""
|
||||
pi.logger.debug("AvahiSearch.on_btn_save_pressed")
|
||||
if self.tb_revpi.currentRow() == -1:
|
||||
return
|
||||
self.connect_index = self._save_connection(self.tb_revpi.currentRow())
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_restart_pressed(self) -> None:
|
||||
"""Clean up an restart search thread."""
|
||||
self._restart_search()
|
||||
103
src/revpicommander/backgroundworker.py
Normal file
103
src/revpicommander/backgroundworker.py
Normal file
@@ -0,0 +1,103 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""File transfer system to handle QThreads."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from .ui.backgroundworker_ui import Ui_diag_backgroundworker
|
||||
|
||||
log = getLogger()
|
||||
|
||||
|
||||
class BackgroundWorker(QtCore.QThread):
|
||||
steps_todo = QtCore.pyqtSignal(int)
|
||||
steps_done = QtCore.pyqtSignal(int)
|
||||
status_message = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(BackgroundWorker, self).__init__(parent)
|
||||
|
||||
def check_cancel(self) -> bool:
|
||||
"""
|
||||
Check for interruption of thread and show message
|
||||
|
||||
:return: True, if interruption was requested
|
||||
"""
|
||||
if self.isInterruptionRequested():
|
||||
self.status_message.emit(self.tr("User requested cancellation..."))
|
||||
self.msleep(750)
|
||||
return True
|
||||
return False
|
||||
|
||||
def exec_dialog(self) -> int:
|
||||
diag = WorkerDialog(self, self.parent())
|
||||
rc = diag.exec()
|
||||
diag.deleteLater()
|
||||
return rc
|
||||
|
||||
def wait_interruptable(self, seconds=-1) -> None:
|
||||
"""Save function to wait and get the cancel buttons."""
|
||||
counter = seconds * 4
|
||||
while counter != 0:
|
||||
counter -= 1
|
||||
self.msleep(250)
|
||||
if self._check_cancel():
|
||||
break
|
||||
|
||||
def run(self) -> None:
|
||||
"""Worker thread to import pictures from camera."""
|
||||
log.debug("BackgroundWorker.run")
|
||||
self.status_message.emit("Started dummy thread...")
|
||||
self.wait_interruptable(5)
|
||||
self.status_message.emit("Completed dummy thread.")
|
||||
self._save_wait(3)
|
||||
|
||||
|
||||
class WorkerDialog(QtWidgets.QDialog, Ui_diag_backgroundworker):
|
||||
|
||||
def __init__(self, worker_thread: BackgroundWorker, parent=None):
|
||||
"""
|
||||
Base of dialog to show progress from a background thread.
|
||||
|
||||
:param worker_thread: Thread with the logic work to do
|
||||
:param parent: QtWidget
|
||||
"""
|
||||
super(WorkerDialog, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self._canceled = False
|
||||
|
||||
self._th = worker_thread
|
||||
self._th.finished.connect(self.on_th_finished)
|
||||
self._th.steps_todo.connect(self.pgb_status.setMaximum)
|
||||
self._th.steps_done.connect(self.pgb_status.setValue)
|
||||
self._th.status_message.connect(self.lbl_status.setText)
|
||||
|
||||
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||
a0.ignore()
|
||||
|
||||
def exec(self) -> int:
|
||||
self._th.start()
|
||||
return super(WorkerDialog, self).exec()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_th_finished(self) -> None:
|
||||
"""Check the result of import thread."""
|
||||
if self._canceled:
|
||||
self.reject()
|
||||
else:
|
||||
self.accept()
|
||||
|
||||
@QtCore.pyqtSlot(QtWidgets.QAbstractButton)
|
||||
def on_btn_box_clicked(self, button: QtWidgets.QAbstractButton) -> None:
|
||||
"""Control buttons for dialog."""
|
||||
role = self.btn_box.buttonRole(button)
|
||||
log.debug("WorkerDialog.on_btn_box_clicked({0})".format(role))
|
||||
|
||||
if role == QtWidgets.QDialogButtonBox.RejectRole:
|
||||
self._th.requestInterruption()
|
||||
self._canceled = True
|
||||
432
src/revpicommander/debugcontrol.py
Normal file
432
src/revpicommander/debugcontrol.py
Normal file
@@ -0,0 +1,432 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Debug control widget to append to main window."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import pickle
|
||||
from xmlrpc.client import Binary, Fault, MultiCall, MultiCallIterator
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from .import helper
|
||||
from . import proginit as pi
|
||||
from .debugios import DebugIos
|
||||
from .ui.debugcontrol_ui import Ui_wid_debugcontrol
|
||||
|
||||
|
||||
class PsValues(QtCore.QThread):
|
||||
"""
|
||||
Get process image from Revolution Pi.
|
||||
|
||||
If this thread detects a driver reset, it will finish the work.
|
||||
"""
|
||||
|
||||
driver_reset_detected = QtCore.pyqtSignal()
|
||||
process_image_received = QtCore.pyqtSignal(Binary)
|
||||
|
||||
def __init__(self):
|
||||
super(PsValues, self).__init__()
|
||||
self._cycle_time = 200
|
||||
|
||||
def run(self):
|
||||
"""Read IO values of Revolution Pi."""
|
||||
pi.logger.debug("PsValues.run enter")
|
||||
|
||||
while not self.isInterruptionRequested():
|
||||
try:
|
||||
self.process_image_received.emit(
|
||||
helper.cm.call_remote_function("ps_values", raise_exception=True)
|
||||
)
|
||||
except Fault:
|
||||
pi.logger.warning("Detected piCtory reset.")
|
||||
self.requestInterruption()
|
||||
self.driver_reset_detected.emit()
|
||||
except Exception as e:
|
||||
pi.logger.error(e)
|
||||
self.process_image_received.emit(Binary())
|
||||
|
||||
self.msleep(self._cycle_time)
|
||||
|
||||
pi.logger.debug("PsValues.run exit")
|
||||
|
||||
|
||||
class DebugControl(QtWidgets.QWidget, Ui_wid_debugcontrol):
|
||||
"""Debug controller for main window."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(DebugControl, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.dict_devices = {}
|
||||
"""Key=position, value=device name."""
|
||||
self.dict_ios = {"inp": {}, "out": {}}
|
||||
"""IO types "inp" "out" which include key=device position, value=list with ios."""
|
||||
self.dict_windows = {}
|
||||
"""Debug IO windows with key=device position, value=DebugIos."""
|
||||
|
||||
self.driver_reset_detected = False
|
||||
self.err_workvalues = 0
|
||||
self.max_errors = 10
|
||||
self.th_worker = PsValues()
|
||||
|
||||
self.vl_devices.addItem(
|
||||
QtWidgets.QSpacerItem(20, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
)
|
||||
self.cbx_write.setEnabled(False)
|
||||
self.cbx_stay_on_top.setChecked(helper.settings.value("stay_on_top", False, bool))
|
||||
|
||||
def __del__(self):
|
||||
pi.logger.debug("DebugControl.__del__")
|
||||
|
||||
def _set_gui_control_states(self):
|
||||
"""Set states depending on acl level."""
|
||||
pi.logger.debug("DebugControl._set_gui_control_states")
|
||||
# xml_mode view >= 1
|
||||
# xml_mode write >= 3
|
||||
self.btn_read_io.setEnabled(not self.cbx_write.isChecked())
|
||||
self.btn_refresh_io.setEnabled(not self.cbx_refresh.isChecked())
|
||||
self.btn_write_o.setEnabled(
|
||||
not self.cbx_write.isChecked() and (helper.cm.xml_mode >= 3 or helper.cm.simulating)
|
||||
)
|
||||
self.cbx_write.setEnabled(
|
||||
self.cbx_refresh.isChecked() and (helper.cm.xml_mode >= 3 or helper.cm.simulating)
|
||||
)
|
||||
|
||||
def _destroy_io_view(self, device_position=-1):
|
||||
"""
|
||||
Destroy IO view including the button and dict entry.
|
||||
|
||||
:param device_position: Only device position or -1 for all
|
||||
"""
|
||||
pi.logger.debug("DebugControl._destroy_io_view")
|
||||
for position in sorted(self.dict_devices) if device_position == -1 else [device_position]:
|
||||
if position in self.dict_windows:
|
||||
# Remove singe window and button
|
||||
win = self.dict_windows[position] # type: DebugIos
|
||||
win.close()
|
||||
win.deleteLater()
|
||||
win.disconnect()
|
||||
del self.dict_windows[position]
|
||||
|
||||
btn = self.gb_devices.findChild((QtWidgets.QPushButton,), str(position)) # type: QtWidgets.QPushButton
|
||||
if btn:
|
||||
self.vl_devices.removeWidget(btn)
|
||||
btn.deleteLater()
|
||||
btn.disconnect()
|
||||
|
||||
def _driver_reset_detected(self):
|
||||
"""Things to do after driver reset."""
|
||||
self.driver_reset_detected = True
|
||||
self.cbx_write.setChecked(False)
|
||||
self.cbx_refresh.setChecked(False)
|
||||
for win in self.dict_windows.values(): # type: DebugIos
|
||||
win.stat_bar.showMessage(
|
||||
self.tr("Driver reset for piControl detected."),
|
||||
10000
|
||||
)
|
||||
self.reload_devices()
|
||||
|
||||
def _work_values(self, refresh=False, write_out=False, process_image=None):
|
||||
"""
|
||||
Read input and output values.
|
||||
|
||||
:param refresh: Refresh unchanged ios from process image
|
||||
:param write_out: Write changed outputs to process image
|
||||
:param process_image: Use this <class 'Binary'> for work and do not fetch
|
||||
"""
|
||||
if process_image is not None:
|
||||
ba_values = process_image
|
||||
else:
|
||||
try:
|
||||
ba_values = helper.cm.call_remote_function("ps_values", raise_exception=True)
|
||||
except Fault:
|
||||
pi.logger.warning("Detected piCtory reset.")
|
||||
self._driver_reset_detected()
|
||||
return
|
||||
except Exception as e:
|
||||
pi.logger.error(e)
|
||||
ba_values = Binary()
|
||||
|
||||
# From now on use bytes instead of Binary
|
||||
ba_values = bytearray(ba_values.data)
|
||||
|
||||
if not ba_values:
|
||||
if self.cbx_refresh.isChecked():
|
||||
self.err_workvalues += 1
|
||||
else:
|
||||
# Raise error on button press
|
||||
self.err_workvalues = self.max_errors
|
||||
|
||||
if self.err_workvalues >= self.max_errors:
|
||||
for win in self.dict_windows.values(): # type: DebugIos
|
||||
win.stat_bar.setStyleSheet("background-color: red;")
|
||||
win.stat_bar.showMessage(self.tr(
|
||||
"Error while getting values from Revolution Pi."
|
||||
), 5000)
|
||||
|
||||
return
|
||||
|
||||
if self.err_workvalues > 0:
|
||||
self.err_workvalues = 0
|
||||
for win in self.dict_windows.values(): # type: DebugIos
|
||||
win.stat_bar.setStyleSheet("")
|
||||
|
||||
# Use multicall to set all changed values
|
||||
if write_out and helper.cm.connected:
|
||||
cli = helper.cm.get_cli()
|
||||
xmlmc = MultiCall(cli)
|
||||
else:
|
||||
xmlmc = []
|
||||
|
||||
for io_type in self.dict_ios:
|
||||
for position in self.dict_ios[io_type]:
|
||||
if position not in self.dict_windows:
|
||||
continue
|
||||
|
||||
win = self.dict_windows[position]
|
||||
for io in self.dict_ios[io_type][position]: # type: list
|
||||
# ['name', bytelen, byte_address, 'bmk', bitaddress, 'byteorder', signed]
|
||||
value_procimg = bytes(ba_values[io[2]:io[2] + io[1]])
|
||||
if io[4] >= 0:
|
||||
# Bit-IO
|
||||
value_procimg = bool(
|
||||
int.from_bytes(value_procimg, byteorder=io[5], signed=io[6]) & 1 << io[4]
|
||||
)
|
||||
|
||||
if (refresh or write_out) and io_type == "out":
|
||||
widget_value, last_value = win.get_value(io[0])
|
||||
if widget_value != last_value:
|
||||
# User changed value
|
||||
|
||||
if not write_out:
|
||||
# Do not write output after change to save this state
|
||||
continue
|
||||
|
||||
value_procimg = widget_value
|
||||
if type(xmlmc) == MultiCall:
|
||||
xmlmc.ps_setvalue(position, io[0], widget_value)
|
||||
else:
|
||||
# Simulate multicall an collect result to list
|
||||
xmlmc.append(
|
||||
helper.cm.call_remote_function("ps_setvalue", position, io[0], widget_value)
|
||||
)
|
||||
|
||||
win.set_value(io[0], value_procimg)
|
||||
|
||||
if self.cbx_refresh.isChecked():
|
||||
win.stat_bar.showMessage(self.tr("Auto update values..."), 1000)
|
||||
else:
|
||||
win.stat_bar.showMessage(self.tr("Values updated..."), 2000)
|
||||
|
||||
if self.driver_reset_detected:
|
||||
# Show values, which we can recover to empty process image
|
||||
win.reset_change_value_colors()
|
||||
|
||||
self.driver_reset_detected = False
|
||||
|
||||
# Set values by multi call
|
||||
if write_out:
|
||||
if isinstance(xmlmc, list):
|
||||
self._validate_multicall(xmlmc)
|
||||
else:
|
||||
self._validate_multicall(xmlmc())
|
||||
|
||||
def _validate_multicall(self, return_list):
|
||||
"""
|
||||
Check xml rpc multi call return values.
|
||||
|
||||
:param return_list: Return values of multi call
|
||||
"""
|
||||
if isinstance(return_list, MultiCallIterator):
|
||||
return_list = return_list.results
|
||||
if len(return_list) == 0:
|
||||
return
|
||||
elif not isinstance(return_list, list):
|
||||
return
|
||||
pi.logger.debug("DebugControl._validate_multicall")
|
||||
|
||||
str_errmsg = ""
|
||||
for lst_result in return_list: # type: list
|
||||
# [[device, io, status, msg]] - Yes, double list list :D
|
||||
if type(lst_result[0]) == list:
|
||||
lst_result = lst_result.pop()
|
||||
if not lst_result[2]:
|
||||
# Create error message
|
||||
device_name = self.dict_devices[lst_result[0]]
|
||||
str_errmsg += self.tr(
|
||||
"Error set value of device '{0}' Output '{1}': {2}\n"
|
||||
).format(device_name, lst_result[1], lst_result[3])
|
||||
else:
|
||||
self.dict_windows[lst_result[0]].reset_change_value_colors(lst_result[1])
|
||||
|
||||
if str_errmsg != "":
|
||||
pi.logger.error(str_errmsg)
|
||||
if not self.cbx_refresh.isChecked():
|
||||
QtWidgets.QMessageBox.critical(self, self.tr("Error"), str_errmsg)
|
||||
|
||||
def deleteLater(self):
|
||||
"""Clean up all sub windows."""
|
||||
pi.logger.debug("DebugControl.deleteLater")
|
||||
|
||||
self.cbx_write.setChecked(False)
|
||||
self.cbx_refresh.setChecked(False)
|
||||
self._destroy_io_view()
|
||||
|
||||
super(DebugControl, self).deleteLater()
|
||||
|
||||
def reload_devices(self):
|
||||
"""Rebuild GUI depending on devices and ios of Revolution Pi."""
|
||||
pi.logger.debug("DebugControl.reload_devices")
|
||||
|
||||
if not helper.cm.call_remote_function("psstart", default_value=False):
|
||||
# RevPiPyLoad does not support psstart (too old)
|
||||
return False
|
||||
|
||||
# ps_devices format: [[0, 'picore01'], [32, 'di01'], ...
|
||||
dict_devices = {v[0]: v[1] for v in helper.cm.call_remote_function("ps_devices", default_value=[])}
|
||||
if len(dict_devices) == 0:
|
||||
# There is no piCtory configuration on the Revolution Pi
|
||||
return False
|
||||
|
||||
# Remove not existing or renamed devices
|
||||
for position in self.dict_devices:
|
||||
if position not in dict_devices or self.dict_devices[position] != dict_devices[position]:
|
||||
self._destroy_io_view(position)
|
||||
|
||||
self.dict_devices = dict_devices
|
||||
|
||||
# Format: {position: [['name', bitlength, byte_address, 'bmk', bitaddress, 'byteorder', signed], ...
|
||||
inps_data = helper.cm.call_remote_function("ps_inps", default_value=Binary()).data
|
||||
outs_data = helper.cm.call_remote_function("ps_outs", default_value=Binary()).data
|
||||
if inps_data == b'' or outs_data == b'':
|
||||
return False
|
||||
|
||||
dict_inps = pickle.loads(inps_data)
|
||||
dict_outs = pickle.loads(outs_data)
|
||||
|
||||
# Take spacer at last position and reinsert it after buttons
|
||||
spacer = self.vl_devices.takeAt(self.vl_devices.count() - 1)
|
||||
|
||||
for position in sorted(self.dict_devices):
|
||||
if position in self.dict_windows:
|
||||
# DebugIos already exists
|
||||
if self.dict_windows[position].update_ios(dict_inps[position], dict_outs[position]):
|
||||
# All IOs match the old ones
|
||||
continue
|
||||
else:
|
||||
# Destroy old window to build a new one
|
||||
self._destroy_io_view(position)
|
||||
|
||||
win = DebugIos(
|
||||
position, self.dict_devices[position],
|
||||
dict_inps[position], dict_outs[position]
|
||||
)
|
||||
win.device_closed.connect(self.on_device_closed)
|
||||
win.do_read.connect(self.btn_refresh_io.pressed)
|
||||
win.do_write.connect(self.btn_write_o.pressed)
|
||||
self.dict_windows[position] = win
|
||||
|
||||
btn = QtWidgets.QPushButton(self.gb_devices)
|
||||
btn.setCheckable(True)
|
||||
btn.setObjectName(str(position))
|
||||
btn.setText("{0} | {1}".format(position, self.dict_devices[position]))
|
||||
btn.clicked.connect(self.on_btn_device_clicked)
|
||||
self.vl_devices.addWidget(btn)
|
||||
|
||||
self.vl_devices.addItem(spacer)
|
||||
|
||||
self.dict_ios["inp"] = dict_inps
|
||||
self.dict_ios["out"] = dict_outs
|
||||
|
||||
self._work_values(refresh=True)
|
||||
self._set_gui_control_states()
|
||||
|
||||
self.cbx_refresh.setChecked(helper.settings.value("auto_refresh", False, bool))
|
||||
|
||||
return True
|
||||
|
||||
@QtCore.pyqtSlot(bool)
|
||||
def on_btn_device_clicked(self, checked: bool):
|
||||
"""Open or close IO window."""
|
||||
pi.logger.debug("DebugControl.on_btn_device_clicked")
|
||||
|
||||
position = int(self.sender().objectName())
|
||||
if position in self.dict_windows:
|
||||
win = self.dict_windows[position] # type: QtWidgets.QMainWindow
|
||||
win.setWindowFlag(QtCore.Qt.WindowStaysOnTopHint, self.cbx_stay_on_top.isChecked())
|
||||
win.setVisible(checked)
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def on_device_closed(self, position: int):
|
||||
"""Change the check state of button, if window was closed."""
|
||||
pi.logger.debug("DebugControl.on_device_closed")
|
||||
btn = self.gb_devices.findChild(QtWidgets.QPushButton, str(position)) # type: QtWidgets.QPushButton
|
||||
btn.setChecked(False)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_read_io_pressed(self):
|
||||
"""Read all IO values and replace changed ones."""
|
||||
pi.logger.debug("DebugControl.on_btn_read_io_pressed")
|
||||
for win in self.dict_windows.values(): # type: DebugIos
|
||||
win.reset_label_colors()
|
||||
self._work_values()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_refresh_io_pressed(self):
|
||||
"""Read all IO values but do not touch changed ones."""
|
||||
pi.logger.debug("DebugControl.on_btn_refresh_io_pressed")
|
||||
if not self.cbx_refresh.isChecked():
|
||||
self._work_values(refresh=True)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_write_o_pressed(self):
|
||||
"""Write outputs."""
|
||||
pi.logger.debug("DebugControl.on_btn_write_o_pressed")
|
||||
if not self.cbx_write.isChecked() and (helper.cm.xml_mode >= 3 or helper.cm.simulating):
|
||||
for win in self.dict_windows.values(): # type: DebugIos
|
||||
win.reset_label_colors()
|
||||
self._work_values(write_out=True)
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def on_cbx_refresh_stateChanged(self, state: int):
|
||||
"""Start or stop the auto refresh thread."""
|
||||
pi.logger.debug("DebugControl.cbx_refresh_stateChanged")
|
||||
|
||||
# Start / stop worker thread
|
||||
if state == QtCore.Qt.Checked and (helper.cm.connected or helper.cm.simulating):
|
||||
self.th_worker = PsValues()
|
||||
self.th_worker.driver_reset_detected.connect(self._driver_reset_detected)
|
||||
self.th_worker.process_image_received.connect(lambda process_image: self._work_values(
|
||||
refresh=True,
|
||||
write_out=self.cbx_write.isChecked(),
|
||||
process_image=process_image
|
||||
))
|
||||
self.th_worker.start()
|
||||
|
||||
else:
|
||||
self.th_worker.requestInterruption()
|
||||
self.th_worker.wait()
|
||||
self.th_worker.deleteLater()
|
||||
self.cbx_write.setChecked(False)
|
||||
|
||||
self._set_gui_control_states()
|
||||
|
||||
@QtCore.pyqtSlot(bool)
|
||||
def on_cbx_refresh_clicked(self, state: bool):
|
||||
"""Save the state on user action."""
|
||||
helper.settings.setValue("auto_refresh", state)
|
||||
|
||||
@QtCore.pyqtSlot(bool)
|
||||
def on_cbx_stay_on_top_clicked(self, state: bool):
|
||||
"""Save the state on user action."""
|
||||
helper.settings.setValue("stay_on_top", state)
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def on_cbx_write_stateChanged(self, state: int):
|
||||
pi.logger.debug("DebugControl.cbx_write_stateChanged")
|
||||
checked = state == QtCore.Qt.Checked
|
||||
for win in self.dict_windows.values(): # type: DebugIos
|
||||
win.write_values = checked
|
||||
|
||||
self._set_gui_control_states()
|
||||
366
src/revpicommander/debugios.py
Normal file
366
src/revpicommander/debugios.py
Normal file
@@ -0,0 +1,366 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""One device of the Revolution Pi."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import struct
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from . import helper
|
||||
from . import proginit as pi
|
||||
from .ui.debugios_ui import Ui_win_debugios
|
||||
|
||||
|
||||
class DebugIos(QtWidgets.QMainWindow, Ui_win_debugios):
|
||||
"""IO window of one device."""
|
||||
|
||||
device_closed = QtCore.pyqtSignal(int)
|
||||
"""This window was closed."""
|
||||
do_read = QtCore.pyqtSignal()
|
||||
do_write = QtCore.pyqtSignal()
|
||||
search_class = (QtWidgets.QLineEdit, QtWidgets.QDoubleSpinBox, QtWidgets.QCheckBox)
|
||||
|
||||
def __init__(self, position: int, name: str, inputs: list, outputs: list, parent=None):
|
||||
super(DebugIos, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.restoreGeometry(helper.cm.debug_geos.get(position, b''))
|
||||
self.setWindowTitle("{0} - {1}".format(position, name))
|
||||
self.gb_io.setTitle(self.gb_io.title().format(name))
|
||||
|
||||
self.__qwa = {}
|
||||
"""Quick widget access."""
|
||||
|
||||
self.position = position
|
||||
self.name = name
|
||||
self.inputs = inputs.copy()
|
||||
self.outputs = outputs.copy()
|
||||
self.write_values = False
|
||||
|
||||
min_input = min(inputs, key=lambda k: k[2])
|
||||
max_output = max(outputs, key=lambda k: k[2])
|
||||
self.length = max_output[2] + max_output[1] - min_input[2]
|
||||
|
||||
self.style_sheet = ""
|
||||
self._create_io(self.inputs, self.saw_inp, True)
|
||||
self._create_io(self.outputs, self.saw_out, False)
|
||||
self.style_sheet = "background-color: red;"
|
||||
|
||||
shc_read = QtWidgets.QShortcut(QtGui.QKeySequence("F5"), self)
|
||||
shc_read.activated.connect(self.do_read)
|
||||
shc_write = QtWidgets.QShortcut(QtGui.QKeySequence("F6"), self)
|
||||
shc_write.activated.connect(self.do_write)
|
||||
|
||||
def __del__(self):
|
||||
pi.logger.debug("DebugIos.__del__")
|
||||
|
||||
def closeEvent(self, a0: QtGui.QCloseEvent):
|
||||
pi.logger.debug("DebugIos.closeEvent")
|
||||
helper.cm.debug_geos[self.position] = self.saveGeometry()
|
||||
self.device_closed.emit(self.position)
|
||||
|
||||
@staticmethod
|
||||
def _calc_min_max(byte_length: int, signed: bool):
|
||||
"""Calculate min an max value which fits to bytes."""
|
||||
max_int_value = 256 ** byte_length
|
||||
return max_int_value / 2 * -1 if signed else 0.0, \
|
||||
max_int_value / 2 - 1 if signed else max_int_value - 1
|
||||
|
||||
def _create_io(self, lst_ios: list, container: QtWidgets.QWidget, read_only: bool):
|
||||
lst_names = list(lst[0] for lst in lst_ios)
|
||||
layout = container.layout() # type: QtWidgets.QFormLayout
|
||||
|
||||
for val in container.findChildren(self.search_class, options=QtCore.Qt.FindDirectChildrenOnly):
|
||||
name = val.objectName()
|
||||
if name not in lst_names:
|
||||
# Remove old io from layout
|
||||
del self.__qwa[name]
|
||||
layout.removeRow(layout.getWidgetPosition(val)[0])
|
||||
|
||||
counter = -1
|
||||
for io in lst_ios:
|
||||
counter += 1
|
||||
|
||||
name = io[0]
|
||||
byte_length = io[1]
|
||||
bit_address = io[4]
|
||||
byteorder = io[5]
|
||||
signed = io[6]
|
||||
|
||||
val = container.findChild(self.search_class, name)
|
||||
if val is not None:
|
||||
# Destroy IO if the properties was changed
|
||||
if byte_length != val.property("byte_length") or \
|
||||
bit_address != val.property("bit_address") or \
|
||||
byteorder != ("big" if val.property("big_endian") else "little") or \
|
||||
signed != val.property("signed"):
|
||||
del self.__qwa[name]
|
||||
layout.removeRow(layout.getWidgetPosition(val)[0])
|
||||
pi.logger.debug("Destroy property changed IO '{0}'".format(name))
|
||||
else:
|
||||
continue
|
||||
|
||||
lbl = QtWidgets.QLabel(name, container)
|
||||
lbl.setObjectName("lbl_".format(name))
|
||||
lbl.setStyleSheet(self.style_sheet)
|
||||
|
||||
val = self._create_widget(name, byte_length, bit_address, byteorder, signed, read_only)
|
||||
val.setParent(container)
|
||||
layout.insertRow(counter, val, lbl)
|
||||
|
||||
self.splitter.setSizes([1, 1])
|
||||
|
||||
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()
|
||||
val.setEnabled(not read_only)
|
||||
|
||||
# Set alias to use the same function name on all widget types
|
||||
val.setValue = val.setChecked
|
||||
if not read_only:
|
||||
val.stateChanged.connect(self._change_cbx_value)
|
||||
val.value = val.isChecked
|
||||
|
||||
elif byte_length > 4:
|
||||
# Bytes or string
|
||||
val = QtWidgets.QLineEdit()
|
||||
val.setReadOnly(read_only)
|
||||
val.setProperty("struct_type", "text")
|
||||
|
||||
val.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
val.customContextMenuRequested.connect(self.on_context_menu)
|
||||
|
||||
# Set alias to use the same function name on all widget types
|
||||
val.setValue = val.setText
|
||||
if not read_only:
|
||||
val.textChanged.connect(self._change_txt_value)
|
||||
val.value = val.text
|
||||
|
||||
else:
|
||||
struct_type = "B" if byte_length == 1 else "H" if byte_length == 2 else "I"
|
||||
|
||||
val = QtWidgets.QDoubleSpinBox()
|
||||
val.setReadOnly(read_only)
|
||||
val.setProperty("struct_type", struct_type)
|
||||
val.setProperty("frm", "{0}{1}".format(
|
||||
">" if byteorder == "big" else "<",
|
||||
struct_type.lower() if signed else struct_type
|
||||
))
|
||||
|
||||
val.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
val.customContextMenuRequested.connect(self.on_context_menu)
|
||||
|
||||
val.setDecimals(0)
|
||||
min_value, max_value = self._calc_min_max(byte_length, signed)
|
||||
val.setMinimum(min_value)
|
||||
val.setMaximum(max_value)
|
||||
if not read_only:
|
||||
val.valueChanged.connect(self._change_sbx_dvalue)
|
||||
|
||||
val.setObjectName(name)
|
||||
val.setProperty("big_endian", byteorder == "big")
|
||||
val.setProperty("bit_address", bit_address)
|
||||
val.setProperty("byte_length", byte_length)
|
||||
val.setProperty("signed", signed)
|
||||
|
||||
self.__qwa[name] = val
|
||||
return val
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def _change_cbx_value(self, value: int):
|
||||
"""Change value of a check box."""
|
||||
if self.sender().property("last_value") == (value == 2):
|
||||
self.sender().setStyleSheet("")
|
||||
else:
|
||||
self.sender().setStyleSheet("background-color: yellow;")
|
||||
|
||||
@QtCore.pyqtSlot(float)
|
||||
def _change_sbx_dvalue(self, value: float):
|
||||
"""Change value of a spin box."""
|
||||
if self.sender().property("last_value") == int(value):
|
||||
self.sender().setStyleSheet("")
|
||||
else:
|
||||
self.sender().setStyleSheet("background-color: yellow;")
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def _change_txt_value(self, value: str):
|
||||
"""Change value of a text box."""
|
||||
if self.sender().property("last_value") == value:
|
||||
self.sender().setStyleSheet("")
|
||||
else:
|
||||
self.sender().setStyleSheet("background-color: yellow;")
|
||||
|
||||
@QtCore.pyqtSlot(QtCore.QPoint)
|
||||
def on_context_menu(self, point: QtCore.QPoint):
|
||||
"""Generate menu for data format changes."""
|
||||
pi.logger.debug("DebugIos.on_context_menu")
|
||||
|
||||
sender = self.sender()
|
||||
men = QtWidgets.QMenu(sender)
|
||||
|
||||
if sender.property("byte_length") > 4:
|
||||
# Textbox needs format buttons
|
||||
act_as_text = QtWidgets.QAction(self.tr("as text"))
|
||||
men.addAction(act_as_text)
|
||||
act_as_number = QtWidgets.QAction(self.tr("as number"))
|
||||
men.addAction(act_as_number)
|
||||
men.addSeparator()
|
||||
|
||||
act_signed = QtWidgets.QAction(self.tr("signed"), men)
|
||||
act_signed.setCheckable(True)
|
||||
act_signed.setChecked(sender.property("signed") or False)
|
||||
men.addAction(act_signed)
|
||||
|
||||
act_byteorder = QtWidgets.QAction(self.tr("big_endian"), men)
|
||||
act_byteorder.setCheckable(True)
|
||||
act_byteorder.setChecked(sender.property("big_endian") or False)
|
||||
men.addAction(act_byteorder)
|
||||
|
||||
rc = men.exec(sender.mapToGlobal(point))
|
||||
if not rc:
|
||||
men.deleteLater()
|
||||
return
|
||||
|
||||
# Get actual value to reformat it
|
||||
actual_value, last_value = self.get_value(sender.objectName())
|
||||
if rc == act_signed:
|
||||
sender.setProperty("signed", act_signed.isChecked())
|
||||
if type(sender) == QtWidgets.QDoubleSpinBox:
|
||||
# Recalculate min / max for spinbox
|
||||
min_value, max_value = self._calc_min_max(
|
||||
sender.property("byte_length"), sender.property("signed")
|
||||
)
|
||||
sender.setMinimum(min_value)
|
||||
sender.setMaximum(max_value)
|
||||
elif rc == act_byteorder:
|
||||
sender.setProperty("big_endian", act_byteorder.isChecked())
|
||||
|
||||
if sender.property("frm"):
|
||||
sender.setProperty("frm", "{0}{1}".format(
|
||||
">" if act_byteorder.isChecked() else "<",
|
||||
sender.property("struct_type").lower() if act_signed.isChecked()
|
||||
else sender.property("struct_type").upper()
|
||||
))
|
||||
elif sender.property("byte_length") > 4:
|
||||
if rc == act_as_text:
|
||||
sender.setProperty("struct_type", "text")
|
||||
elif rc == act_as_number:
|
||||
sender.setProperty("struct_type", "number")
|
||||
|
||||
self.set_value(sender.objectName(), actual_value)
|
||||
men.deleteLater()
|
||||
|
||||
def reset_label_colors(self):
|
||||
"""Clean up color from labels."""
|
||||
for wid in self.findChildren(QtWidgets.QLabel): # type: QtWidgets.QLabel
|
||||
wid.setStyleSheet("")
|
||||
|
||||
def reset_change_value_colors(self, io_name=None):
|
||||
"""
|
||||
Clean up color from changed outputs.
|
||||
|
||||
:param io_name: Clean up only this IO
|
||||
"""
|
||||
pi.logger.debug("DebugIos.reset_change_value_colors")
|
||||
if io_name is None:
|
||||
lst_wid = self.saw_out.findChildren(
|
||||
self.search_class, options=QtCore.Qt.FindDirectChildrenOnly)
|
||||
else:
|
||||
lst_wid = self.saw_out.findChildren(
|
||||
self.search_class, name=io_name, options=QtCore.Qt.FindDirectChildrenOnly)
|
||||
|
||||
for wid in lst_wid:
|
||||
value, last_value = self.get_value(wid.objectName())
|
||||
if value == last_value:
|
||||
wid.setStyleSheet("")
|
||||
else:
|
||||
wid.setStyleSheet("background-color: yellow;")
|
||||
|
||||
def update_ios(self, inputs: list, outputs: list):
|
||||
"""Update IOs after driver reset of piCtory."""
|
||||
|
||||
# Check device length, this has to match to reuse this device
|
||||
min_input = min(inputs, key=lambda k: k[2])
|
||||
max_output = max(outputs, key=lambda k: k[2])
|
||||
new_length = max_output[2] + max_output[1] - min_input[2]
|
||||
if self.length != new_length:
|
||||
return False
|
||||
|
||||
# Remove IOs, which was remove or renamed
|
||||
self._create_io(inputs, self.saw_inp, True)
|
||||
self._create_io(outputs, self.saw_out, False)
|
||||
|
||||
return True
|
||||
|
||||
def get_value(self, io_name: str):
|
||||
"""
|
||||
Standard get function for a value of different widgets and last value.
|
||||
|
||||
:param io_name: Name of IO
|
||||
:return: (actual value, last value) <class 'tuple'>
|
||||
"""
|
||||
# child = self.findChild(self.search_class, io_name)
|
||||
child = self.__qwa[io_name]
|
||||
actual_value = child.value()
|
||||
last_value = child.value() if child.property("last_value") is None else child.property("last_value")
|
||||
if child.property("frm"):
|
||||
return struct.pack(child.property("frm"), int(actual_value)), \
|
||||
struct.pack(child.property("frm"), int(last_value))
|
||||
elif type(actual_value) == str:
|
||||
if child.property("struct_type") == "number":
|
||||
try:
|
||||
actual_value = int(actual_value).to_bytes(
|
||||
child.property("byte_length"),
|
||||
byteorder="big" if child.property("big_endian") else "little",
|
||||
signed=child.property("signed") or False
|
||||
)
|
||||
last_value = int(last_value).to_bytes(
|
||||
child.property("byte_length"),
|
||||
byteorder="big" if child.property("big_endian") else "little",
|
||||
signed=child.property("signed") or False
|
||||
)
|
||||
return actual_value, last_value
|
||||
except Exception:
|
||||
pi.logger.error("Could not convert '{0}' to bytes".format(actual_value))
|
||||
pass
|
||||
|
||||
return actual_value.encode(), last_value.encode()
|
||||
else:
|
||||
return actual_value, last_value
|
||||
|
||||
def set_value(self, io_name: str, value, just_last_value=False):
|
||||
"""
|
||||
Standard set function for a value of different widgets.
|
||||
|
||||
:param io_name: Name of IO
|
||||
:param value: New value as bytes or bool for widget
|
||||
:param just_last_value: Just set last value property
|
||||
"""
|
||||
child = self.__qwa[io_name]
|
||||
if child.property("frm"):
|
||||
value = struct.unpack(child.property("frm"), value)[0]
|
||||
elif type(value) == bytes:
|
||||
if child.property("struct_type") == "text":
|
||||
try:
|
||||
value = value.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
child.setProperty("struct_type", "number")
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self, self.tr("Can not use format text"), self.tr(
|
||||
"Can not convert bytes {0} to a text for IO '{1}'. Switch to number format instead!"
|
||||
).format(value, io_name)
|
||||
)
|
||||
if child.property("struct_type") == "number":
|
||||
value = str(int.from_bytes(
|
||||
value,
|
||||
byteorder="big" if child.property("big_endian") else "little",
|
||||
signed=child.property("signed") or False
|
||||
))
|
||||
|
||||
child.setProperty("last_value", value)
|
||||
if not just_last_value:
|
||||
child.setValue(value)
|
||||
487
src/revpicommander/helper.py
Normal file
487
src/revpicommander/helper.py
Normal file
@@ -0,0 +1,487 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Helper functions for this application."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import pickle
|
||||
import socket
|
||||
from enum import IntEnum
|
||||
from http.client import CannotSendRequest
|
||||
from os import environ, remove
|
||||
from os.path import exists
|
||||
from queue import Queue
|
||||
from threading import Lock
|
||||
from xmlrpc.client import Binary, ServerProxy
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from . import proginit as pi
|
||||
|
||||
|
||||
class WidgetData(IntEnum):
|
||||
address = 260
|
||||
replace_ios_config = 261
|
||||
acl_level = 262
|
||||
has_error = 263
|
||||
port = 264
|
||||
object_name = 265
|
||||
timeout = 266
|
||||
host_name = 267
|
||||
last_dir_upload = 301
|
||||
last_file_upload = 302
|
||||
last_dir_pictory = 303
|
||||
last_dir_picontrol = 304
|
||||
last_dir_selected = 305
|
||||
last_pictory_file = 306
|
||||
last_tar_file = 307
|
||||
last_zip_file = 308
|
||||
file_name = 309
|
||||
watch_files = 310
|
||||
watch_path = 311
|
||||
debug_geos = 312
|
||||
|
||||
|
||||
class ConnectionManager(QtCore.QThread):
|
||||
"""Check connection and status for PLC program on Revolution Pi."""
|
||||
|
||||
connection_established = QtCore.pyqtSignal()
|
||||
"""New connection established successfully with <class 'ServerProxy'>."""
|
||||
connection_disconnected = QtCore.pyqtSignal()
|
||||
"""Connection disconnected."""
|
||||
connection_disconnecting = QtCore.pyqtSignal()
|
||||
"""Signal emitted before closing connection."""
|
||||
connection_error_observed = QtCore.pyqtSignal(str)
|
||||
"""This will be triggered, if a connection error was detected."""
|
||||
status_changed = QtCore.pyqtSignal(str, str)
|
||||
"""Status message and color suggestion."""
|
||||
|
||||
def __init__(self, parent=None, cycle_time_ms=1000):
|
||||
super(ConnectionManager, self).__init__(parent)
|
||||
|
||||
self._cli = None
|
||||
self._cli_connect = Queue()
|
||||
self._cycle_time = cycle_time_ms
|
||||
self._lck_cli = Lock()
|
||||
self._ps_started = False
|
||||
self._revpi = None
|
||||
self._revpi_output = None
|
||||
|
||||
self.address = ""
|
||||
self.name = ""
|
||||
self.port = 55123
|
||||
|
||||
# Sync this with revpiplclist to preserve settings
|
||||
self.program_last_dir_upload = ""
|
||||
self.program_last_file_upload = ""
|
||||
self.program_last_dir_pictory = ""
|
||||
self.program_last_dir_picontrol = ""
|
||||
self.program_last_dir_selected = ""
|
||||
self.program_last_pictory_file = ""
|
||||
self.program_last_tar_file = ""
|
||||
self.program_last_zip_file = ""
|
||||
self.develop_watch_files = []
|
||||
self.develop_watch_path = ""
|
||||
self.debug_geos = {}
|
||||
|
||||
self.pyload_version = (0, 0, 0)
|
||||
"""Version number of RevPiPyLoad 0.0.0 with <class 'int'> values."""
|
||||
self.xml_funcs = []
|
||||
"""Name list of all supported functions of RevPiPyLoad."""
|
||||
self.xml_mode = -1
|
||||
"""ACL level for this connection (-1 on connection denied)."""
|
||||
self._xml_mode_refresh = False
|
||||
|
||||
def __call_simulator(self, function_name: str, *args, default_value=None, **kwargs):
|
||||
pi.logger.debug("ConnectionManager.__call_simulator({0})".format(function_name))
|
||||
if function_name == "ps_values":
|
||||
if self._revpi.readprocimg():
|
||||
bytebuff = bytearray()
|
||||
for dev in self._revpi.device:
|
||||
bytebuff += bytes(dev)
|
||||
return Binary(bytes(bytebuff))
|
||||
|
||||
elif function_name == "ps_setvalue":
|
||||
# args: 0=device, 1=io, 2=value
|
||||
device = args[0]
|
||||
io = args[1]
|
||||
if type(args[2]) == Binary:
|
||||
value = args[2].data
|
||||
else:
|
||||
value = args[2]
|
||||
|
||||
try:
|
||||
# Write new value to IO
|
||||
self._revpi.io[io].set_value(value)
|
||||
except Exception as e:
|
||||
return [device, io, False, str(e)]
|
||||
|
||||
return [device, io, True, ""]
|
||||
|
||||
elif function_name == "psstart":
|
||||
self._revpi.autorefresh_all()
|
||||
return True
|
||||
|
||||
elif function_name == "psstop":
|
||||
self._revpi.exit(full=False)
|
||||
return True
|
||||
|
||||
elif function_name == "ps_devices":
|
||||
return [(dev.position, dev.name) for dev in self._revpi.device]
|
||||
|
||||
elif function_name == "ps_inps":
|
||||
return self.__simulator_ios("inp")
|
||||
|
||||
elif function_name == "ps_outs":
|
||||
return self.__simulator_ios("out")
|
||||
|
||||
else:
|
||||
return default_value
|
||||
|
||||
def __simulator_ios(self, iotype: str):
|
||||
dict_ios = {}
|
||||
for dev in self._revpi.device:
|
||||
dict_ios[dev.position] = []
|
||||
|
||||
if iotype == "inp":
|
||||
lst_io = dev.get_inputs()
|
||||
elif iotype == "out":
|
||||
lst_io = dev.get_outputs()
|
||||
else:
|
||||
lst_io = []
|
||||
|
||||
for io in lst_io:
|
||||
dict_ios[dev.position].append([
|
||||
io.name,
|
||||
1 if io._bitlength == 1 else int(io._bitlength / 8),
|
||||
io._slc_address.start + dev.offset,
|
||||
io.bmk,
|
||||
io._bitaddress,
|
||||
io._byteorder,
|
||||
io._signed,
|
||||
])
|
||||
return Binary(pickle.dumps(dict_ios))
|
||||
|
||||
def _clear_settings(self):
|
||||
"""Clear connection settings."""
|
||||
self.address = ""
|
||||
self.name = ""
|
||||
self.port = 55123
|
||||
self.pyload_version = (0, 0, 0)
|
||||
self.xml_funcs.clear()
|
||||
self.xml_mode = -1
|
||||
|
||||
self.program_last_dir_upload = ""
|
||||
self.program_last_file_upload = ""
|
||||
self.program_last_dir_pictory = ""
|
||||
self.program_last_dir_picontrol = ""
|
||||
self.program_last_dir_selected = ""
|
||||
self.program_last_pictory_file = ""
|
||||
self.program_last_tar_file = ""
|
||||
self.program_last_zip_file = ""
|
||||
self.develop_watch_files = []
|
||||
self.develop_watch_path = ""
|
||||
self.debug_geos = {}
|
||||
|
||||
def _save_settings(self):
|
||||
"""Save settings to named Revolution Pi."""
|
||||
for i in range(settings.beginReadArray("connections")):
|
||||
settings.setArrayIndex(i)
|
||||
if settings.value("address") != self.address:
|
||||
# Search used connection, because connection manager could reorganize array
|
||||
continue
|
||||
|
||||
settings.setValue("last_dir_upload", self.program_last_dir_upload)
|
||||
settings.setValue("last_file_upload", self.program_last_file_upload)
|
||||
settings.setValue("last_dir_pictory", self.program_last_dir_pictory)
|
||||
settings.setValue("last_dir_picontrol", self.program_last_dir_picontrol)
|
||||
settings.setValue("last_dir_selected", self.program_last_dir_selected)
|
||||
settings.setValue("last_pictory_file", self.program_last_pictory_file)
|
||||
settings.setValue("last_tar_file", self.program_last_tar_file)
|
||||
settings.setValue("last_zip_file", self.program_last_zip_file)
|
||||
settings.setValue("watch_files", self.develop_watch_files)
|
||||
settings.setValue("watch_path", self.develop_watch_path)
|
||||
settings.setValue("debug_geos", self.debug_geos)
|
||||
|
||||
break
|
||||
|
||||
settings.endArray()
|
||||
|
||||
def pyload_connect(self, settings_index: int):
|
||||
"""
|
||||
Create a new connection from settings object.
|
||||
|
||||
:param settings_index: Index of settings array 'connections'
|
||||
:return: True, if the connection was successfully established
|
||||
"""
|
||||
|
||||
# First disconnect to send signal and clean up values
|
||||
self.pyload_disconnect()
|
||||
|
||||
settings.beginReadArray("connections")
|
||||
settings.setArrayIndex(settings_index)
|
||||
|
||||
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)
|
||||
self.program_last_dir_pictory = settings.value("last_dir_pictory", ".", str)
|
||||
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), 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)
|
||||
|
||||
settings.endArray()
|
||||
|
||||
socket.setdefaulttimeout(2)
|
||||
sp = ServerProxy("http://{0}:{1}".format(address, port))
|
||||
|
||||
# Load values and test connection to Revolution Pi
|
||||
try:
|
||||
pyload_version = tuple(map(int, sp.version().split(".")))
|
||||
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
|
||||
|
||||
self.address = address
|
||||
self.name = name
|
||||
self.port = port
|
||||
self.pyload_version = pyload_version
|
||||
self.xml_funcs = xml_funcs
|
||||
self.xml_mode = xml_mode
|
||||
|
||||
with self._lck_cli:
|
||||
socket.setdefaulttimeout(timeout)
|
||||
self._cli = sp
|
||||
self._cli_connect.put_nowait((address, port))
|
||||
|
||||
self.connection_established.emit()
|
||||
|
||||
return True
|
||||
|
||||
def pyload_disconnect(self):
|
||||
"""Disconnect from Revolution Pi."""
|
||||
if self._revpi is not None:
|
||||
self.connection_disconnecting.emit()
|
||||
|
||||
self._revpi.cleanup()
|
||||
self._revpi_output.cleanup()
|
||||
if settings.value("simulator/stop_remove", False, bool):
|
||||
remove(self._revpi.procimg)
|
||||
self._revpi = None
|
||||
self._revpi_output = None
|
||||
|
||||
pi.logger.debug("Simulator destroyed.")
|
||||
self.connection_disconnected.emit()
|
||||
|
||||
elif self._cli is not None:
|
||||
|
||||
# Tell all widget, that we want do disconnect, to save the settings
|
||||
self.connection_disconnecting.emit()
|
||||
self._save_settings()
|
||||
|
||||
with self._lck_cli:
|
||||
if self._ps_started:
|
||||
try:
|
||||
self._cli.psstop()
|
||||
except Exception:
|
||||
pass
|
||||
self._clear_settings()
|
||||
self._cli = None
|
||||
|
||||
self.connection_disconnected.emit()
|
||||
|
||||
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")
|
||||
|
||||
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 for outputs
|
||||
self._revpi_output = revpimodio2.RevPiModIO(configrsc=configrsc, procimg=procimg)
|
||||
self._revpi_output.setdefaultvalues()
|
||||
self._revpi_output.writeprocimg()
|
||||
|
||||
# This is our simulator to work with
|
||||
self._revpi = revpimodio2.RevPiModIO(simulator=True, configrsc=configrsc, procimg=procimg)
|
||||
self._revpi.setdefaultvalues()
|
||||
self._revpi.writeprocimg()
|
||||
|
||||
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
|
||||
if settings.value("simulator/stop_remove", False, bool):
|
||||
remove(procimg)
|
||||
|
||||
return self._revpi is not None
|
||||
|
||||
def refresh_xml_mode(self):
|
||||
"""Refresh XML ACL level after some change could be done."""
|
||||
self._xml_mode_refresh = True
|
||||
|
||||
def reset_simulator(self):
|
||||
"""Reset all io to piCtory defaults."""
|
||||
pi.logger.debug("ConnectionManager.reset_simulator")
|
||||
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."""
|
||||
self.setPriority(QtCore.QThread.NormalPriority)
|
||||
|
||||
sp = None
|
||||
while not self.isInterruptionRequested():
|
||||
|
||||
if self._revpi is not None:
|
||||
sp = None
|
||||
self.status_changed.emit(self.tr("SIMULATING"), "yellow")
|
||||
elif self._cli is None:
|
||||
sp = None
|
||||
self.status_changed.emit(self.tr("NOT CONNECTED"), "lightblue")
|
||||
elif not self._cli_connect.empty():
|
||||
# Get new connection information to create object in this thread
|
||||
item = self._cli_connect.get()
|
||||
sp = ServerProxy("http://{0}:{1}".format(*item))
|
||||
self._cli_connect.task_done()
|
||||
|
||||
if sp:
|
||||
try:
|
||||
plc_exit_code = sp.plcexitcode()
|
||||
if self._xml_mode_refresh:
|
||||
self.xml_mode = sp.xmlmodus()
|
||||
self._xml_mode_refresh = False
|
||||
except CannotSendRequest as e:
|
||||
pi.logger.warning(e)
|
||||
except Exception as e:
|
||||
pi.logger.warning(e)
|
||||
self.status_changed.emit(self.tr("SERVER ERROR"), "red")
|
||||
self.connection_error_observed.emit("{0} | {1}".format(e, type(e)))
|
||||
else:
|
||||
if plc_exit_code == -1:
|
||||
self.status_changed.emit(self.tr("RUNNING"), "green")
|
||||
elif plc_exit_code == -2:
|
||||
self.status_changed.emit(self.tr("PLC FILE NOT FOUND"), "red")
|
||||
elif plc_exit_code == -3:
|
||||
self.status_changed.emit(self.tr("NOT RUNNING (NO STATUS)"), "yellow")
|
||||
elif plc_exit_code == -9:
|
||||
self.status_changed.emit(self.tr("PROGRAM KILLED"), "red")
|
||||
elif plc_exit_code == -15:
|
||||
self.status_changed.emit(self.tr("PROGRAM TERMED"), "red")
|
||||
elif plc_exit_code == 0:
|
||||
self.status_changed.emit(self.tr("NOT RUNNING"), "yellow")
|
||||
else:
|
||||
self.status_changed.emit(self.tr("FINISHED WITH CODE {0}").format(plc_exit_code), "yellow")
|
||||
|
||||
self.msleep(self._cycle_time)
|
||||
|
||||
def call_remote_function(self, function_name: str, *args, default_value=None, raise_exception=False, **kwargs):
|
||||
"""
|
||||
Save call of a remote function with given name and parameters on Revolution Pi.
|
||||
|
||||
:param function_name: Function to call on RevPiPyLoad
|
||||
:param args: Functions arguments
|
||||
:param default_value: Default value will be returned on error
|
||||
:param raise_exception: Will raise the exception returned from server
|
||||
:param kwargs: Functions key word arguments
|
||||
:return: Return value of remote function or default_value
|
||||
"""
|
||||
if self._cli is None and self._revpi is None:
|
||||
pi.logger.error("Not connected while calling {0}".format(function_name))
|
||||
if raise_exception:
|
||||
raise ConnectionError("Connection manager not connected")
|
||||
return default_value
|
||||
|
||||
reload_funcs = False
|
||||
if function_name == "psstart":
|
||||
self._ps_started = True
|
||||
reload_funcs = True
|
||||
elif function_name == "psstop":
|
||||
self._ps_started = False
|
||||
reload_funcs = True
|
||||
|
||||
# On connection problems do not freeze
|
||||
if self._lck_cli.acquire(timeout=1.0):
|
||||
if self._revpi is not None:
|
||||
# Redirect call to simulator
|
||||
return_value = self.__call_simulator(function_name, *args, default_value=default_value, **kwargs)
|
||||
else:
|
||||
try:
|
||||
return_value = getattr(self._cli, function_name)(*args, **kwargs)
|
||||
if reload_funcs:
|
||||
self.xml_funcs = self._cli.system.listMethods()
|
||||
except Exception as e:
|
||||
pi.logger.error(e)
|
||||
if raise_exception:
|
||||
self._lck_cli.release()
|
||||
raise
|
||||
return_value = default_value
|
||||
|
||||
self._lck_cli.release()
|
||||
return return_value
|
||||
|
||||
elif raise_exception:
|
||||
raise ConnectionError("Can not get lock of connection")
|
||||
|
||||
return default_value
|
||||
|
||||
def get_cli(self):
|
||||
"""Connection proxy of actual connection."""
|
||||
if self.address and self.port:
|
||||
return ServerProxy("http://{0}:{1}".format(self.address, self.port))
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def connected(self) -> bool:
|
||||
"""True if we have an active connection."""
|
||||
return self._cli is not None
|
||||
|
||||
@property
|
||||
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."""
|
||||
|
||||
settings = QtCore.QSettings("revpipyplc", "revpipyload")
|
||||
"""Global application settings."""
|
||||
|
||||
homedir = environ.get("HOME", "") or environ.get("APPDATA", "")
|
||||
"""Home dir of user."""
|
||||
BIN
src/revpicommander/locale/revpicommander_de.qm
Normal file
BIN
src/revpicommander/locale/revpicommander_de.qm
Normal file
Binary file not shown.
1874
src/revpicommander/locale/revpicommander_de.ts
Normal file
1874
src/revpicommander/locale/revpicommander_de.ts
Normal file
File diff suppressed because it is too large
Load Diff
130
src/revpicommander/mqttmanager.py
Normal file
130
src/revpicommander/mqttmanager.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Options for MQTT system."""
|
||||
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
from PyQt5 import QtGui, QtWidgets
|
||||
|
||||
from . import proginit as pi
|
||||
from .ui.mqttmanager_ui import Ui_diag_mqtt
|
||||
|
||||
|
||||
class MqttManager(QtWidgets.QDialog, Ui_diag_mqtt):
|
||||
"""MQTT settings for option window."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(MqttManager, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
self.setFixedSize(self.size())
|
||||
|
||||
self.dc = {}
|
||||
|
||||
def _changesdone(self):
|
||||
"""
|
||||
Check for unsaved changes in dialog.
|
||||
|
||||
:return: True, if unsaved changes was found
|
||||
"""
|
||||
return (
|
||||
not self.dc or
|
||||
self.dc["mqttbasetopic"] != self.txt_basetopic.text() or
|
||||
self.dc["mqttsendinterval"] != self.sbx_sendinterval.value() or
|
||||
self.dc["mqttsend_on_event"] != int(self.cbx_send_on_event.isChecked()) or
|
||||
self.dc["mqttwrite_outputs"] != int(self.cbx_write_outputs.isChecked()) or
|
||||
self.dc["mqttbroker_address"] != self.txt_broker_address.text() or
|
||||
self.dc["mqtttls_set"] != int(self.cbx_tls_set.isChecked()) or
|
||||
self.dc["mqttport"] != self.sbx_port.value() or
|
||||
self.dc["mqttusername"] != self.txt_username.text() or
|
||||
self.dc["mqttpassword"] != self.txt_password.text() or
|
||||
self.dc["mqttclient_id"] != self.txt_client_id.text()
|
||||
)
|
||||
|
||||
def _load_settings(self):
|
||||
"""Load values to GUI widgets."""
|
||||
try:
|
||||
self.txt_basetopic.setText(self.dc["mqttbasetopic"])
|
||||
self.sbx_sendinterval.setValue(self.dc["mqttsendinterval"])
|
||||
self.dc["mqttsend_on_event"] = int(self.cbx_send_on_event.isChecked())
|
||||
self.dc["mqttwrite_outputs"] = int(self.cbx_write_outputs.isChecked())
|
||||
self.txt_broker_address.setText(self.dc["mqttbroker_address"])
|
||||
self.cbx_tls_set.setChecked(bool(self.dc["mqtttls_set"]))
|
||||
self.sbx_port.setValue(self.dc["mqttport"])
|
||||
self.txt_username.setText(self.dc["mqttusername"])
|
||||
self.txt_password.setText(self.dc["mqttpassword"])
|
||||
self.txt_client_id.setText(self.dc["mqttclient_id"])
|
||||
except Exception as e:
|
||||
pi.logger.exception(e)
|
||||
self.dc = {}
|
||||
return False
|
||||
return True
|
||||
|
||||
def accept(self) -> None:
|
||||
"""Save values to master dict."""
|
||||
self.dc["mqttbasetopic"] = self.txt_basetopic.text()
|
||||
self.dc["mqttsendinterval"] = self.sbx_sendinterval.value()
|
||||
self.dc["mqttsend_on_event"] = int(self.cbx_send_on_event.isChecked())
|
||||
self.dc["mqttwrite_outputs"] = int(self.cbx_write_outputs.isChecked())
|
||||
self.dc["mqttbroker_address"] = self.txt_broker_address.text()
|
||||
self.dc["mqtttls_set"] = int(self.cbx_tls_set.isChecked())
|
||||
self.dc["mqttport"] = self.sbx_port.value()
|
||||
self.dc["mqttusername"] = self.txt_username.text()
|
||||
self.dc["mqttpassword"] = self.txt_password.text()
|
||||
self.dc["mqttclient_id"] = self.txt_client_id.text()
|
||||
super(MqttManager, self).accept()
|
||||
|
||||
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||
if self._changesdone():
|
||||
ask = QtWidgets.QMessageBox.question(
|
||||
self, self.tr("Question"), self.tr(
|
||||
"Do you really want to quit? \nUnsaved changes will be lost."
|
||||
)
|
||||
) == QtWidgets.QMessageBox.Yes
|
||||
if ask:
|
||||
self.reject()
|
||||
else:
|
||||
a0.ignore()
|
||||
|
||||
def exec(self) -> int:
|
||||
"""Load settings from dc and show dialog."""
|
||||
if not self._load_settings():
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Can not load the MQTT settings dialog. Missing values!"
|
||||
)
|
||||
)
|
||||
return QtWidgets.QDialog.Rejected
|
||||
return super(MqttManager, self).exec()
|
||||
|
||||
def reject(self) -> None:
|
||||
"""Reject settings."""
|
||||
self._load_settings()
|
||||
super(MqttManager, self).reject()
|
||||
|
||||
@property
|
||||
def read_only(self):
|
||||
"""Getter for read_only value."""
|
||||
return not self.txt_basetopic.isEnabled()
|
||||
|
||||
@read_only.setter
|
||||
def read_only(self, value: bool):
|
||||
"""Setter for read_only window."""
|
||||
self.txt_basetopic.setEnabled(not value)
|
||||
self.sbx_sendinterval.setEnabled(not value)
|
||||
self.cbx_send_on_event.setEnabled(not value)
|
||||
self.cbx_write_outputs.setEnabled(not value)
|
||||
self.txt_broker_address.setEnabled(not value)
|
||||
self.sbx_port.setEnabled(not value)
|
||||
self.cbx_tls_set.setEnabled(not value)
|
||||
self.txt_username.setEnabled(not value)
|
||||
self.txt_password.setEnabled(not value)
|
||||
self.txt_client_id.setEnabled(not value)
|
||||
if value:
|
||||
self.btn_box.setStandardButtons(
|
||||
QtWidgets.QDialogButtonBox.Cancel
|
||||
)
|
||||
else:
|
||||
self.btn_box.setStandardButtons(
|
||||
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
|
||||
)
|
||||
117
src/revpicommander/proginit.py
Normal file
117
src/revpicommander/proginit.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Global program initialization."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "LGPLv3"
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
from configparser import ConfigParser
|
||||
from os import R_OK, W_OK, access
|
||||
from os.path import abspath, dirname, join
|
||||
|
||||
# Program name
|
||||
programname = "revpicommander"
|
||||
|
||||
# Set to True, if you want to save config file
|
||||
conf_rw = False
|
||||
|
||||
conf = ConfigParser()
|
||||
logger = logging.getLogger()
|
||||
pidfile = "/var/run/{0}.pid".format(programname)
|
||||
|
||||
|
||||
def cleanup():
|
||||
"""Clean up program."""
|
||||
# Shutdown logging system
|
||||
logging.shutdown()
|
||||
|
||||
|
||||
def reconfigure_logger():
|
||||
"""Configure logging module of program."""
|
||||
# Clear all log handler
|
||||
for lhandler in logger.handlers.copy():
|
||||
lhandler.close()
|
||||
logger.removeHandler(lhandler)
|
||||
|
||||
# Create new log handler
|
||||
logformat = logging.Formatter(
|
||||
"{asctime} [{levelname:8}] {message}",
|
||||
datefmt="%Y-%m-%d %H:%M:%S", style="{"
|
||||
)
|
||||
lhandler = logging.StreamHandler(sys.stdout)
|
||||
lhandler.setFormatter(logformat)
|
||||
logger.addHandler(lhandler)
|
||||
|
||||
if "logfile" in pargs and pargs.logfile is not None:
|
||||
# Write logs to a logfile
|
||||
lhandler = logging.FileHandler(filename=pargs.logfile)
|
||||
lhandler.setFormatter(logformat)
|
||||
logger.addHandler(lhandler)
|
||||
|
||||
# Loglevel auswerten
|
||||
if pargs.verbose == 1:
|
||||
loglevel = logging.INFO
|
||||
elif pargs.verbose > 1:
|
||||
loglevel = logging.DEBUG
|
||||
else:
|
||||
loglevel = logging.WARNING
|
||||
logger.setLevel(loglevel)
|
||||
|
||||
|
||||
def reload_conf():
|
||||
"""Reload config file."""
|
||||
if "conffile" in pargs:
|
||||
|
||||
# Check config file
|
||||
if not access(pargs.conffile, R_OK):
|
||||
raise RuntimeError(
|
||||
"can not access config file '{0}'".format(pargs.conffile)
|
||||
)
|
||||
if conf_rw and not access(pargs.conffile, W_OK):
|
||||
raise RuntimeError(
|
||||
"can not write to config file '{0}'".format(pargs.conffile)
|
||||
)
|
||||
|
||||
# Create global config
|
||||
global conf
|
||||
logger.info("loading config file: {0}".format(pargs.conffile))
|
||||
conf.read(pargs.conffile)
|
||||
|
||||
|
||||
# Generate command arguments of the program
|
||||
parser = ArgumentParser(
|
||||
prog=programname,
|
||||
description="Program description"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f", "--logfile", dest="logfile",
|
||||
help="Save log entries to this file"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", action="count", dest="verbose", default=0,
|
||||
help="Switch on verbose logging"
|
||||
)
|
||||
pargs = parser.parse_args()
|
||||
|
||||
# Check important objects and set to default if they do not exists
|
||||
if "verbose" not in pargs:
|
||||
pargs.verbose = 0
|
||||
|
||||
# Get absolute paths
|
||||
pwd = abspath(".")
|
||||
|
||||
# Configure logger
|
||||
if "logfile" in pargs and pargs.logfile is not None \
|
||||
and dirname(pargs.logfile) == "":
|
||||
pargs.logfile = join(pwd, pargs.logfile)
|
||||
reconfigure_logger()
|
||||
|
||||
# Initialize configparser of globalconfig
|
||||
if "conffile" in pargs and dirname(pargs.conffile) == "":
|
||||
pargs.conffile = join(pwd, pargs.conffile)
|
||||
|
||||
# Load configuration - Comment out, if you do that in your own program
|
||||
reload_conf()
|
||||
514
src/revpicommander/revpicommander.py
Normal file
514
src/revpicommander/revpicommander.py
Normal file
@@ -0,0 +1,514 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""RevPiCommander main program."""
|
||||
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
__version__ = "0.9.3"
|
||||
|
||||
import webbrowser
|
||||
from os.path import basename, dirname, join
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from . import helper
|
||||
from . import proginit as pi
|
||||
from . import revpilogfile
|
||||
from .avahisearch import AvahiSearch
|
||||
from .debugcontrol import DebugControl
|
||||
from .revpifiles import RevPiFiles
|
||||
from .revpiinfo import RevPiInfo
|
||||
from .revpioption import RevPiOption
|
||||
from .revpiplclist import RevPiPlcList
|
||||
from .revpiprogram import RevPiProgram
|
||||
from .simulator import Simulator
|
||||
from .ui.revpicommander_ui import Ui_win_revpicommander
|
||||
|
||||
|
||||
class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
|
||||
"""Main application of RevPiCommander."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Init main program."""
|
||||
super(RevPiCommander, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.wid_debugcontrol = None # type: DebugControl
|
||||
"""Holds the widget of DebugControl."""
|
||||
self.simulating = False
|
||||
"""True, if simulation is running."""
|
||||
self.dict_men_connections_subfolder = {}
|
||||
"""Submenus for folder entries."""
|
||||
|
||||
self._set_gui_control_states()
|
||||
self._load_men_connections()
|
||||
|
||||
# Load sub windows
|
||||
self.diag_connections = RevPiPlcList(self)
|
||||
self.diag_search = AvahiSearch(self)
|
||||
self.diag_info = RevPiInfo(__version__, self)
|
||||
self.diag_options = RevPiOption(self)
|
||||
self.diag_program = RevPiProgram(self)
|
||||
self.win_files = RevPiFiles(self)
|
||||
self.win_log = revpilogfile.RevPiLogfile(self)
|
||||
|
||||
self.btn_plc_logs.pressed.connect(self.on_act_logs_triggered)
|
||||
|
||||
helper.cm.connection_disconnected.connect(self.on_cm_connection_disconnected)
|
||||
helper.cm.connection_disconnecting.connect(self.on_cm_connection_disconnecting)
|
||||
helper.cm.connection_established.connect(self.on_cm_connection_established)
|
||||
helper.cm.connection_error_observed.connect(self.on_cm_connection_error_observed)
|
||||
helper.cm.status_changed.connect(self.on_cm_status_changed)
|
||||
|
||||
self.restoreGeometry(helper.settings.value("geo", b''))
|
||||
|
||||
self.setWindowFlag(QtCore.Qt.WindowMaximizeButtonHint, False)
|
||||
|
||||
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||
pi.logger.debug("RevPiCommander.closeEvent")
|
||||
helper.cm.pyload_disconnect()
|
||||
helper.settings.setValue("geo", self.saveGeometry())
|
||||
|
||||
def _set_gui_control_states(self):
|
||||
"""Setup states of actions and buttons."""
|
||||
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):
|
||||
"""
|
||||
Connection error occurred in connection manager.
|
||||
|
||||
:param message: Error message
|
||||
"""
|
||||
self.statusbar.showMessage(message, 15000)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_cm_connection_disconnected(self):
|
||||
"""Connection of connection manager was disconnected."""
|
||||
pi.logger.debug("RevPiCommander.on_cm_connection_disconnected")
|
||||
|
||||
self._set_gui_control_states()
|
||||
self.txt_host.setVisible(True)
|
||||
self.txt_host.clear()
|
||||
self.txt_connection.clear()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_cm_connection_disconnecting(self):
|
||||
"""Connection of connection manager will now disconnect."""
|
||||
pi.logger.debug("RevPiCommander.on_cm_connection_disconnecting")
|
||||
|
||||
# This will remove the widgets in the button functions
|
||||
self.btn_plc_debug.setChecked(False)
|
||||
|
||||
self.diag_info.reject()
|
||||
self.diag_options.reject()
|
||||
self.diag_program.reject()
|
||||
self.win_files.close()
|
||||
self.win_files.deleteLater()
|
||||
|
||||
self.centralwidget.adjustSize()
|
||||
self.adjustSize()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_cm_connection_established(self):
|
||||
"""Connection manager established a new connection and loaded values."""
|
||||
pi.logger.debug("RevPiCommander.on_cm_connection_established")
|
||||
|
||||
self._set_gui_control_states()
|
||||
if helper.cm.simulating:
|
||||
self.txt_host.setVisible(False)
|
||||
self.txt_connection.setText("configrsc=\"{0}\", procimg=\"{1}\"".format(
|
||||
helper.cm.simulating_configrsc,
|
||||
helper.cm.simulating_procimg,
|
||||
))
|
||||
else:
|
||||
self.txt_host.setText(helper.cm.name)
|
||||
self.txt_connection.setText(helper.cm.address)
|
||||
self.win_files = RevPiFiles(self)
|
||||
|
||||
@QtCore.pyqtSlot(str, str)
|
||||
def on_cm_status_changed(self, text: str, color: str):
|
||||
"""PLC program status from Revolution Pi."""
|
||||
self.txt_status.setText(text)
|
||||
self.txt_status.setStyleSheet("background: {0}".format(color))
|
||||
|
||||
# endregion # # # # #
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# region # REGION: Menu entries
|
||||
|
||||
def _load_men_connections(self):
|
||||
"""Build up connections menu."""
|
||||
|
||||
self.men_connections.clear()
|
||||
self.dict_men_connections_subfolder.clear()
|
||||
|
||||
for i in range(helper.settings.beginReadArray("connections")):
|
||||
helper.settings.setArrayIndex(i)
|
||||
|
||||
if helper.settings.value("folder"):
|
||||
if helper.settings.value("folder") not in self.dict_men_connections_subfolder:
|
||||
men_sub = QtWidgets.QMenu(self.men_connections)
|
||||
men_sub.setTitle(helper.settings.value("folder"))
|
||||
self.dict_men_connections_subfolder[helper.settings.value("folder")] = men_sub
|
||||
self.men_connections.addMenu(men_sub)
|
||||
parent_menu = self.dict_men_connections_subfolder[helper.settings.value("folder")]
|
||||
else:
|
||||
parent_menu = self.men_connections
|
||||
|
||||
act = QtWidgets.QAction(parent_menu)
|
||||
act.setText(helper.settings.value("name"))
|
||||
act.setData(i)
|
||||
act.setToolTip("{0}:{1}".format(
|
||||
helper.settings.value("address"),
|
||||
helper.settings.value("port"),
|
||||
))
|
||||
parent_menu.addAction(act)
|
||||
|
||||
helper.settings.endArray()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_connections_triggered(self):
|
||||
"""Edit saved connections to Revolution Pi devices."""
|
||||
if self.diag_connections.exec() == QtWidgets.QDialog.Accepted:
|
||||
self._load_men_connections()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_search_triggered(self):
|
||||
"""Search for Revolution Pi with zero conf."""
|
||||
if self.diag_search.exec() == QtWidgets.QDialog.Accepted:
|
||||
if self.diag_search.connect_index >= 0:
|
||||
self._pyload_connect(self.diag_search.connect_index)
|
||||
|
||||
self._load_men_connections()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_simulator_triggered(self):
|
||||
"""Start the simulator function."""
|
||||
diag = Simulator(self)
|
||||
if diag.exec() != QtWidgets.QDialog.Accepted:
|
||||
diag.deleteLater()
|
||||
return
|
||||
|
||||
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, 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={0}\nconfigrsc={1}\n\n"
|
||||
"You can copy that from header textbox."
|
||||
).format(procimg_file, configrsc_file)
|
||||
)
|
||||
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 have no write permissions for '{0}'."
|
||||
).format(procimg_file)
|
||||
)
|
||||
|
||||
diag.deleteLater()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_logs_triggered(self):
|
||||
"""Show log window."""
|
||||
if not helper.cm.connected:
|
||||
return
|
||||
|
||||
if "load_plclog" not in helper.cm.xml_funcs:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self, self.tr("Warning"), self.tr(
|
||||
"This version of Logviewer ist not supported in version {0} "
|
||||
"of RevPiPyLoad on your RevPi! You need at least version 0.4.1."
|
||||
).format(helper.cm.call_remote_function("version", default_value="-"))
|
||||
)
|
||||
return None
|
||||
|
||||
if self.win_log.isHidden():
|
||||
self.win_log.show()
|
||||
else:
|
||||
self.win_log.activateWindow()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_options_triggered(self):
|
||||
"""Show option dialog."""
|
||||
if not helper.cm.connected:
|
||||
return
|
||||
|
||||
if helper.cm.xml_mode < 2:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self, self.tr("Warning"), self.tr(
|
||||
"XML-RPC access mode in the RevPiPyLoad "
|
||||
"configuration is too small to access this dialog!"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
# Check version of RevPiPyLoad, must be greater than 0.5!
|
||||
if helper.cm.pyload_version < (0, 6, 0):
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"The Version of RevPiPyLoad on your Revolution Pi ({0}) is to old. "
|
||||
"This Version of RevPiCommander require at least version 0.6.0 "
|
||||
"of RevPiPyLoad. Please update your Revolution Pi!"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
rc = self.diag_options.exec()
|
||||
if rc == QtWidgets.QDialog.Accepted:
|
||||
helper.cm.refresh_xml_mode()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_program_triggered(self):
|
||||
"""Show program dialog."""
|
||||
if not helper.cm.connected:
|
||||
return
|
||||
|
||||
if helper.cm.xml_mode < 2:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self, self.tr("Warning"), self.tr(
|
||||
"XML-RPC access mode in the RevPiPyLoad "
|
||||
"configuration is too small to access this dialog!"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
self.diag_program.exec()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_developer_triggered(self):
|
||||
"""Extent developer mode to main window."""
|
||||
if not helper.cm.connected:
|
||||
return
|
||||
|
||||
if self.win_files.isHidden():
|
||||
self.win_files.show()
|
||||
else:
|
||||
self.win_files.activateWindow()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_pictory_triggered(self):
|
||||
"""Open piCtory in default browser of operating system."""
|
||||
if helper.cm.address:
|
||||
webbrowser.open("http://{0}/".format(helper.cm.address))
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_reset_triggered(self):
|
||||
"""Reset piControl driver on revolution pi."""
|
||||
if not (helper.cm.connected and helper.cm.xml_mode >= 3):
|
||||
return
|
||||
|
||||
ask = QtWidgets.QMessageBox.question(
|
||||
self, self.tr("Question"), self.tr(
|
||||
"Are you sure to reset piControl?\n"
|
||||
"The pictory configuration will be reloaded. During that time "
|
||||
"the process image will be interrupted and could rise errors "
|
||||
"on running control programs!"
|
||||
)
|
||||
)
|
||||
if ask != QtWidgets.QMessageBox.Yes:
|
||||
return
|
||||
|
||||
ec = helper.cm.call_remote_function("resetpicontrol", default_value=-1)
|
||||
if ec == 0:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, self.tr("Success"), self.tr(
|
||||
"piControl reset executed successfully"
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"piControl reset could not be executed successfully"
|
||||
)
|
||||
)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_disconnect_triggered(self):
|
||||
"""Close actual connection."""
|
||||
helper.cm.pyload_disconnect()
|
||||
|
||||
@QtCore.pyqtSlot(QtWidgets.QAction)
|
||||
def on_men_connections_triggered(self, action: QtWidgets.QAction):
|
||||
"""A connection is selected in the men_connections menu."""
|
||||
self._pyload_connect(action.data())
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_webpage_triggered(self):
|
||||
"""Open project page in default browser of operating system."""
|
||||
webbrowser.open("https://revpimodio.org")
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_act_info_triggered(self):
|
||||
"""Open info window of application."""
|
||||
self.diag_info.exec()
|
||||
|
||||
# endregion # # # # #
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# region # REGION: Button events
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_plc_start_clicked(self):
|
||||
"""Start plc program on revolution pi."""
|
||||
helper.cm.call_remote_function("plcstart")
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_plc_stop_clicked(self):
|
||||
"""Start plc program on revolution pi."""
|
||||
if helper.cm.simulating:
|
||||
helper.cm.pyload_disconnect()
|
||||
else:
|
||||
helper.cm.call_remote_function("plcstop")
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_plc_restart_clicked(self):
|
||||
"""Restart plc program on revolution pi."""
|
||||
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 {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
|
||||
helper.cm.reset_simulator()
|
||||
else:
|
||||
helper.cm.call_remote_function("plcstop")
|
||||
helper.cm.call_remote_function("plcstart")
|
||||
|
||||
@QtCore.pyqtSlot(bool)
|
||||
def on_btn_plc_debug_toggled(self, state: bool):
|
||||
"""Start plc watch mode."""
|
||||
if not (state or self.wid_debugcontrol is None):
|
||||
# Remove widget
|
||||
self.gl.removeWidget(self.wid_debugcontrol)
|
||||
self.wid_debugcontrol.deleteLater()
|
||||
self.wid_debugcontrol = None
|
||||
|
||||
elif "psstart" not in helper.cm.xml_funcs:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self, self.tr("Warning"), self.tr(
|
||||
"The watch mode ist not supported in version {0} "
|
||||
"of RevPiPyLoad on your RevPi! You need at least version "
|
||||
"0.5.3! Maybe the python3-revpimodio2 module is not "
|
||||
"installed on your RevPi at least version 2.0.0."
|
||||
).format(helper.cm.call_remote_function("version", "-"))
|
||||
)
|
||||
self.btn_plc_debug.setChecked(False)
|
||||
self.btn_plc_debug.setEnabled(False)
|
||||
|
||||
elif helper.cm.xml_mode < 1 and not helper.cm.simulating:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Can not load this function, because your ACL level is to low!\n"
|
||||
"You need at least level 1 to read or level 3 to write."
|
||||
)
|
||||
)
|
||||
self.btn_plc_debug.setChecked(False)
|
||||
|
||||
elif helper.cm.connected or helper.cm.simulating:
|
||||
debugcontrol = DebugControl(self.centralwidget)
|
||||
if debugcontrol.reload_devices():
|
||||
self.wid_debugcontrol = debugcontrol
|
||||
self.gl.addWidget(self.wid_debugcontrol)
|
||||
else:
|
||||
debugcontrol.deleteLater()
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Can not load piCtory configuration. \n"
|
||||
"Did you create a hardware configuration? "
|
||||
"Please check this in piCtory!"
|
||||
)
|
||||
)
|
||||
self.btn_plc_debug.setChecked(False)
|
||||
|
||||
self.centralwidget.adjustSize()
|
||||
self.adjustSize()
|
||||
|
||||
# endregion # # # # #
|
||||
|
||||
|
||||
def main() -> int:
|
||||
from sys import argv
|
||||
|
||||
if hasattr(QtCore.Qt, 'AA_EnableHighDpiScaling'):
|
||||
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
|
||||
if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'):
|
||||
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
|
||||
|
||||
app = QtWidgets.QApplication(argv)
|
||||
|
||||
try:
|
||||
# Setup translation from file with system language
|
||||
locale = QtCore.QLocale.system().name()
|
||||
translator = QtCore.QTranslator()
|
||||
locale_file_name = "revpicommander_{0}".format(locale)
|
||||
translator.load(join(dirname(__file__), "locale", locale_file_name), suffix=".qm")
|
||||
app.installTranslator(translator)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Prepare workers
|
||||
helper.cm.start()
|
||||
|
||||
win = RevPiCommander()
|
||||
win.show()
|
||||
exit_code = app.exec()
|
||||
|
||||
# Clean up workers
|
||||
helper.cm.requestInterruption()
|
||||
helper.cm.wait()
|
||||
|
||||
return exit_code
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
sys.exit(main())
|
||||
565
src/revpicommander/revpifiles.py
Normal file
565
src/revpicommander/revpifiles.py
Normal file
@@ -0,0 +1,565 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""File manager for up und download PLC program."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import gzip
|
||||
import os
|
||||
from enum import IntEnum
|
||||
from xmlrpc.client import Binary
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from . import helper
|
||||
from . import proginit as pi
|
||||
from .backgroundworker import BackgroundWorker
|
||||
from .helper import WidgetData
|
||||
from .ui.files_ui import Ui_win_files
|
||||
|
||||
|
||||
class NodeType(IntEnum):
|
||||
FILE = 1000
|
||||
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):
|
||||
super(RevPiFiles, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.dc_settings = {}
|
||||
self.tree_files_counter = 0
|
||||
self.tree_files_counter_max = 10000
|
||||
self.lbl_path_local.setText(helper.cm.develop_watch_path or self.tr("Please select..."))
|
||||
self.lbl_path_local.setToolTip(self.lbl_path_local.text())
|
||||
|
||||
self.btn_all.setEnabled(False)
|
||||
self.btn_to_left.setEnabled(False)
|
||||
self.btn_to_right.setEnabled(False)
|
||||
self.btn_delete_revpi.setEnabled(False)
|
||||
|
||||
if helper.cm.develop_watch_path:
|
||||
self._load_files_local(True)
|
||||
if helper.cm.connected:
|
||||
self._load_files_revpi(True)
|
||||
|
||||
self.restoreGeometry(helper.settings.value("files/geo", b''))
|
||||
self.splitter.setSizes(list(map(int, helper.settings.value("files/splitter", [0, 0]))))
|
||||
|
||||
def __del__(self):
|
||||
pi.logger.debug("RevPiFiles.__del__")
|
||||
|
||||
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||
pi.logger.debug("RevPiFiles.closeEvent")
|
||||
helper.settings.setValue("files/geo", self.saveGeometry())
|
||||
helper.settings.setValue("files/splitter", self.splitter.sizes())
|
||||
|
||||
def _do_my_job(self, stop_restart=True):
|
||||
"""
|
||||
Upload the selected files and do a optionally restart.
|
||||
|
||||
:param stop_restart: True will restart program
|
||||
"""
|
||||
if not helper.cm.connected:
|
||||
return
|
||||
|
||||
if stop_restart and helper.cm.call_remote_function("plcstop") is None:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Can not stop plc program on Revolution Pi."
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
uploader = UploadFiles(self.file_list_local(), self)
|
||||
if uploader.exec_dialog() == QtWidgets.QDialog.Rejected:
|
||||
return
|
||||
|
||||
if uploader.ec == 0:
|
||||
# Tell user, we did not find the auto start program in files
|
||||
if not uploader.plc_program_included:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, self.tr("Information"), self.tr(
|
||||
"A PLC program has been uploaded. Please check the "
|
||||
"PLC program settings to see if the correct program "
|
||||
"is specified as the start program."
|
||||
)
|
||||
)
|
||||
|
||||
elif uploader.ec == -1:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"The Revolution Pi could not process some parts of the "
|
||||
"transmission."
|
||||
)
|
||||
)
|
||||
|
||||
elif uploader.ec == -2:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"),
|
||||
self.tr("Errors occurred during transmission")
|
||||
)
|
||||
|
||||
if stop_restart and helper.cm.call_remote_function("plcstart", default_value=1) != 0:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self, self.tr("Warning"), self.tr(
|
||||
"Could not start the plc program on Revolution Pi."
|
||||
)
|
||||
)
|
||||
|
||||
def _set_gui_control_states(self):
|
||||
"""Setup states of actions and buttons."""
|
||||
state_local = len(self.tree_files_local.selectedItems()) > 0
|
||||
state_revpi = len(self.tree_files_revpi.selectedItems()) > 0
|
||||
|
||||
self.btn_all.setEnabled(state_local)
|
||||
self.btn_to_right.setEnabled(state_local)
|
||||
|
||||
if "plcdeletefile" not in helper.cm.xml_funcs:
|
||||
self.btn_delete_revpi.setEnabled(False)
|
||||
self.btn_delete_revpi.setToolTip(self.tr("The RevPiPyLoad version on the Revolution Pi is to old."))
|
||||
else:
|
||||
self.btn_delete_revpi.setEnabled(state_revpi)
|
||||
if "plcdownload_file" not in helper.cm.xml_funcs:
|
||||
self.btn_to_left.setEnabled(False)
|
||||
self.btn_to_left.setToolTip(self.tr("The RevPiPyLoad version on the Revolution Pi is to old."))
|
||||
elif not helper.cm.develop_watch_path:
|
||||
self.btn_to_left.setEnabled(False)
|
||||
self.btn_to_left.setToolTip(self.tr("Choose a local directory first."))
|
||||
else:
|
||||
self.btn_to_left.setEnabled(state_revpi)
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# region # REGION: Tree management
|
||||
|
||||
@staticmethod
|
||||
def _parent_selection_state(item: QtWidgets.QTreeWidgetItem):
|
||||
"""Set selected, if all children are selected."""
|
||||
if item.parent():
|
||||
all_selected = True
|
||||
for i in range(item.parent().childCount()):
|
||||
if not item.parent().child(i).isSelected():
|
||||
all_selected = False
|
||||
break
|
||||
item.parent().setSelected(all_selected)
|
||||
|
||||
def _select_children(self, top_item: QtWidgets.QTreeWidgetItem, value: bool):
|
||||
"""Recursive select children from parent."""
|
||||
pi.logger.debug("RevPiFiles._select_children")
|
||||
|
||||
for i in range(top_item.childCount()):
|
||||
item = top_item.child(i)
|
||||
if item.type() == NodeType.DIR:
|
||||
item.setSelected(value)
|
||||
self._select_children(item, value)
|
||||
elif item.type() == NodeType.FILE:
|
||||
item.setSelected(value)
|
||||
|
||||
def __item_selection_changed(self, tree_view: QtWidgets.QTreeView):
|
||||
"""Manager vor item selection of three views."""
|
||||
item = tree_view.currentItem()
|
||||
if item is None:
|
||||
return
|
||||
|
||||
pi.logger.debug("RevPiFiles.__itemSelectionChanged")
|
||||
|
||||
# Block while preselect other entries
|
||||
tree_view.blockSignals(True)
|
||||
|
||||
if item.type() == NodeType.DIR:
|
||||
self._select_children(item, item.isSelected())
|
||||
elif item.type() == NodeType.FILE:
|
||||
self._parent_selection_state(item)
|
||||
|
||||
tree_view.blockSignals(False)
|
||||
|
||||
self._set_gui_control_states()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_tree_files_local_itemSelectionChanged(self):
|
||||
self.__item_selection_changed(self.tree_files_local)
|
||||
helper.cm.develop_watch_files = self.file_list_local()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_tree_files_revpi_itemSelectionChanged(self):
|
||||
self.__item_selection_changed(self.tree_files_revpi)
|
||||
|
||||
# endregion # # # # #
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# region # REGION: Local file lists
|
||||
|
||||
def __insert_files_local(self, base_dir: str, child=None):
|
||||
"""
|
||||
Recursively add files to tree view.
|
||||
|
||||
:param base_dir: Directory to scan for files
|
||||
:param child: Child widget to add new widgets
|
||||
"""
|
||||
if not os.path.exists(base_dir):
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Can not open last directory '{0}'."
|
||||
).format(base_dir)
|
||||
)
|
||||
return
|
||||
|
||||
for de in os.scandir(base_dir): # type: os.DirEntry
|
||||
|
||||
if self.tree_files_counter > self.tree_files_counter_max:
|
||||
return
|
||||
|
||||
if de.is_dir(follow_symlinks=False):
|
||||
item = QtWidgets.QTreeWidgetItem(NodeType.DIR)
|
||||
item.setText(0, de.name)
|
||||
item.setIcon(0, QtGui.QIcon(":/main/ico/folder.ico"))
|
||||
if child:
|
||||
child.addChild(item)
|
||||
else:
|
||||
self.tree_files_local.addTopLevelItem(item)
|
||||
|
||||
self.__insert_files_local(de.path, item)
|
||||
|
||||
elif de.is_file(follow_symlinks=False):
|
||||
self.tree_files_counter += 1
|
||||
|
||||
item = QtWidgets.QTreeWidgetItem(NodeType.FILE)
|
||||
item.setText(0, de.name)
|
||||
item.setData(0, WidgetData.file_name, de.path)
|
||||
item.setIcon(0, QtGui.QIcon(
|
||||
":/file/ico/file-else.ico" if de.name.find(".py") == -1 else
|
||||
":/file/ico/file-python.ico"
|
||||
))
|
||||
if child:
|
||||
child.addChild(item)
|
||||
else:
|
||||
self.tree_files_local.addTopLevelItem(item)
|
||||
|
||||
item.setSelected(de.path in helper.cm.develop_watch_files)
|
||||
self._parent_selection_state(item)
|
||||
|
||||
def _load_files_local(self, silent=False):
|
||||
"""
|
||||
Refresh the file list.
|
||||
|
||||
:param silent: Do not show message boxes
|
||||
"""
|
||||
pi.logger.debug("RevPiFiles._load_files_local")
|
||||
|
||||
self.tree_files_counter = 0
|
||||
self.tree_files_local.blockSignals(True)
|
||||
self.tree_files_local.clear()
|
||||
self.__insert_files_local(helper.cm.develop_watch_path)
|
||||
self.tree_files_local.sortItems(0, QtCore.Qt.AscendingOrder)
|
||||
self.tree_files_local.blockSignals(False)
|
||||
|
||||
if not silent and self.tree_files_counter > self.tree_files_counter_max:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Stop scanning for files, because we found more than {0} files."
|
||||
).format(self.tree_files_counter_max)
|
||||
)
|
||||
|
||||
self._set_gui_control_states()
|
||||
|
||||
def file_list_local(self):
|
||||
"""Generate a file list with full path of selected entries."""
|
||||
pi.logger.debug("RevPiFiles.file_list_local")
|
||||
lst = []
|
||||
for item in self.tree_files_local.selectedItems():
|
||||
if item.type() == NodeType.DIR:
|
||||
# We just want files
|
||||
continue
|
||||
lst.append(item.data(0, WidgetData.file_name))
|
||||
|
||||
return lst
|
||||
|
||||
# endregion # # # # #
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# region # REGION: RevPi file lists
|
||||
|
||||
def _load_files_revpi(self, silent=False):
|
||||
"""
|
||||
Refresh the file list of revolution pi.
|
||||
|
||||
:param silent: Do not show message boxes
|
||||
"""
|
||||
pi.logger.debug("RevPiFiles._load_files_revpi")
|
||||
|
||||
self.tree_files_revpi.blockSignals(True)
|
||||
self.tree_files_revpi.clear()
|
||||
self.tree_files_revpi.blockSignals(False)
|
||||
|
||||
if not helper.cm.connected:
|
||||
lst_revpi = None
|
||||
else:
|
||||
lst_revpi = helper.cm.call_remote_function("get_filelist")
|
||||
# Just load settings once
|
||||
if not self.dc_settings:
|
||||
self.dc_settings = helper.cm.call_remote_function("get_config", default_value={})
|
||||
self.lbl_path_revpi.setText(
|
||||
self.dc_settings.get("plcworkdir", self.tr("Could not load path of working dir"))
|
||||
)
|
||||
self.lbl_path_revpi.setToolTip(self.lbl_path_revpi.text())
|
||||
|
||||
if lst_revpi is not None:
|
||||
lst_revpi.sort()
|
||||
|
||||
for path_file in lst_revpi:
|
||||
lst_path_file = path_file.split("/")
|
||||
dir_node = None # type: QtWidgets.QTreeWidgetItem
|
||||
|
||||
for folder in lst_path_file[:-1]:
|
||||
new_dir_node = QtWidgets.QTreeWidgetItem(NodeType.DIR)
|
||||
new_dir_node.setText(0, folder)
|
||||
new_dir_node.setIcon(0, QtGui.QIcon(":/main/ico/folder.ico"))
|
||||
|
||||
if dir_node:
|
||||
# Subfolder of top level
|
||||
for i in range(dir_node.childCount()):
|
||||
item = dir_node.child(i)
|
||||
if item.type() != NodeType.DIR:
|
||||
continue
|
||||
if item.text(0) == new_dir_node.text(0):
|
||||
dir_node = item
|
||||
new_dir_node = None
|
||||
break
|
||||
if new_dir_node:
|
||||
dir_node.addChild(new_dir_node)
|
||||
dir_node = new_dir_node
|
||||
else:
|
||||
# Search in top level
|
||||
for i in range(self.tree_files_revpi.topLevelItemCount()):
|
||||
item = self.tree_files_revpi.topLevelItem(i)
|
||||
if item.type() != NodeType.DIR:
|
||||
continue
|
||||
if item.text(0) == new_dir_node.text(0):
|
||||
dir_node = item
|
||||
new_dir_node = None
|
||||
break
|
||||
if new_dir_node:
|
||||
self.tree_files_revpi.addTopLevelItem(new_dir_node)
|
||||
dir_node = new_dir_node
|
||||
|
||||
# This is the file name
|
||||
object_name = lst_path_file[-1]
|
||||
item = QtWidgets.QTreeWidgetItem(NodeType.FILE)
|
||||
item.setText(0, object_name)
|
||||
item.setData(0, WidgetData.file_name, path_file)
|
||||
item.setIcon(0, QtGui.QIcon(
|
||||
":/file/ico/file-else.ico" if object_name.find(".py") == -1 else
|
||||
":/file/ico/file-python.ico"
|
||||
))
|
||||
if dir_node:
|
||||
dir_node.addChild(item)
|
||||
else:
|
||||
self.tree_files_revpi.addTopLevelItem(item)
|
||||
|
||||
self.tree_files_revpi.sortItems(0, QtCore.Qt.AscendingOrder)
|
||||
|
||||
elif not silent:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Can not load file list from Revolution Pi."
|
||||
)
|
||||
)
|
||||
|
||||
self._set_gui_control_states()
|
||||
|
||||
def file_list_revpi(self):
|
||||
"""Generate a file list with full path of selected entries."""
|
||||
pi.logger.debug("RevPiFiles.file_list_revpi")
|
||||
lst = []
|
||||
for item in self.tree_files_revpi.selectedItems():
|
||||
if item.type() == NodeType.DIR:
|
||||
# We just want files
|
||||
continue
|
||||
lst.append(item.data(0, WidgetData.file_name))
|
||||
|
||||
return lst
|
||||
|
||||
# endregion # # # # #
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_all_pressed(self):
|
||||
pi.logger.debug("RevPiFiles.on_btn_all_pressed")
|
||||
self._do_my_job(True)
|
||||
self.file_list_revpi()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_select_local_pressed(self):
|
||||
pi.logger.debug("RevPiFiles.on_btn_select_pressed")
|
||||
|
||||
diag_folder = QtWidgets.QFileDialog(
|
||||
self, self.tr("Select folder..."),
|
||||
helper.cm.develop_watch_path,
|
||||
)
|
||||
diag_folder.setFileMode(QtWidgets.QFileDialog.DirectoryOnly)
|
||||
if diag_folder.exec() != QtWidgets.QFileDialog.Accepted:
|
||||
return
|
||||
|
||||
selected_dir = diag_folder.selectedFiles()[0]
|
||||
|
||||
if not os.access(selected_dir, os.R_OK):
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Can not access the folder '{0}' to read files."
|
||||
)
|
||||
)
|
||||
helper.cm.develop_watch_files = []
|
||||
helper.cm.develop_watch_path = ""
|
||||
return
|
||||
|
||||
self.lbl_path_local.setText(selected_dir)
|
||||
self.lbl_path_local.setToolTip(self.lbl_path_local.text())
|
||||
helper.cm.develop_watch_path = selected_dir
|
||||
helper.cm.develop_watch_files = []
|
||||
|
||||
self._load_files_local(False)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_refresh_local_pressed(self):
|
||||
pi.logger.debug("RevPiFiles.on_btn_refresh_pressed")
|
||||
self._load_files_local(False)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_refresh_revpi_pressed(self):
|
||||
pi.logger.debug("RevPiFiles.on_btn_refresh_revpi_pressed")
|
||||
self._load_files_revpi(False)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_to_right_pressed(self):
|
||||
"""Upload selected files to revolution pi."""
|
||||
pi.logger.debug("RevPiFiles.on_btn_to_right_pressed")
|
||||
self._do_my_job(False)
|
||||
self._load_files_revpi(True)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_to_left_pressed(self):
|
||||
"""Download selected file."""
|
||||
pi.logger.debug("RevPiFiles.on_btn_to_left_pressed")
|
||||
|
||||
override = None
|
||||
for item in self.tree_files_revpi.selectedItems():
|
||||
if item.type() != NodeType.FILE:
|
||||
continue
|
||||
|
||||
file_name = item.data(0, WidgetData.file_name)
|
||||
rc = helper.cm.call_remote_function(
|
||||
"plcdownload_file", file_name,
|
||||
default_value=Binary()
|
||||
)
|
||||
rc = rc.data
|
||||
if not rc:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error..."), self.tr(
|
||||
"Error while download file '{0}'."
|
||||
).format(file_name)
|
||||
)
|
||||
else:
|
||||
file_name = os.path.join(helper.cm.develop_watch_path, file_name)
|
||||
if override is None and os.path.exists(file_name):
|
||||
rc_diag = QtWidgets.QMessageBox.question(
|
||||
self, self.tr("Override files..."), self.tr(
|
||||
"One or more files does exist on your computer! Do you want to override the existing"
|
||||
"files?\n\nSelect 'Yes' to override, 'No' to download only missing files."
|
||||
),
|
||||
buttons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel
|
||||
)
|
||||
if rc_diag == QtWidgets.QMessageBox.Cancel:
|
||||
return
|
||||
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))
|
||||
continue
|
||||
|
||||
os.makedirs(os.path.dirname(file_name), exist_ok=True)
|
||||
file_data = gzip.decompress(rc)
|
||||
with open(os.path.join(helper.cm.develop_watch_path, file_name), "wb") as fh:
|
||||
fh.write(file_data)
|
||||
|
||||
self._load_files_local()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_delete_revpi_pressed(self):
|
||||
"""Remove selected files from working directory on revolution pi."""
|
||||
pi.logger.debug("RevPiFiles.btn_delete_revpi_pressed")
|
||||
|
||||
lst_delete = []
|
||||
for item in self.tree_files_revpi.selectedItems():
|
||||
if item.type() == NodeType.FILE:
|
||||
lst_delete.append(item.data(0, WidgetData.file_name))
|
||||
|
||||
rc = QtWidgets.QMessageBox.question(
|
||||
self, self.tr("Delete files from Revolution Pi..."), self.tr(
|
||||
"Do you want to delete {0} files from revolution pi?"
|
||||
).format(len(lst_delete))
|
||||
)
|
||||
if rc != QtWidgets.QMessageBox.Yes:
|
||||
return
|
||||
|
||||
for file_name in lst_delete:
|
||||
rc = helper.cm.call_remote_function("plcdeletefile", file_name, default_value=False)
|
||||
if not rc:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error..."), self.tr(
|
||||
"Error while delete file '{0}'."
|
||||
).format(file_name)
|
||||
)
|
||||
|
||||
self._load_files_revpi()
|
||||
53
src/revpicommander/revpiinfo.py
Normal file
53
src/revpicommander/revpiinfo.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Program information of local an remote system."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from . import helper
|
||||
from .ui.revpiinfo_ui import Ui_diag_revpiinfo
|
||||
|
||||
|
||||
class RevPiInfo(QtWidgets.QDialog, Ui_diag_revpiinfo):
|
||||
"""Version information window."""
|
||||
|
||||
def __init__(self, version: str, parent=None):
|
||||
super(RevPiInfo, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self._debug_load = False
|
||||
|
||||
self.lbl_version_control.setText(version)
|
||||
self.lbl_version_control.mousePressEvent = self.lbl_version_mousePressEvent
|
||||
|
||||
def exec(self) -> int:
|
||||
self.lbl_version_pyload.setText(
|
||||
"{0}.{1}.{2}".format(*helper.cm.pyload_version)
|
||||
if helper.cm.connected else "-"
|
||||
)
|
||||
self._load_lst_files()
|
||||
return super(RevPiInfo, self).exec()
|
||||
|
||||
def lbl_version_mousePressEvent(self, a0: QtGui.QMouseEvent):
|
||||
if a0.button() == QtCore.Qt.MidButton:
|
||||
self._debug_load = not self._debug_load
|
||||
self._load_lst_files()
|
||||
|
||||
def _load_lst_files(self):
|
||||
"""Load files from working dir on Revolution Pi."""
|
||||
self.lst_files.clear()
|
||||
|
||||
if self._debug_load:
|
||||
lst = helper.cm.xml_funcs
|
||||
elif helper.cm.connected:
|
||||
lst = helper.cm.call_remote_function(
|
||||
"get_filelist",
|
||||
default_value=[self.tr("Can not load file list")]
|
||||
)
|
||||
else:
|
||||
lst = [self.tr("Not connected")]
|
||||
|
||||
lst.sort()
|
||||
self.lst_files.insertItems(0, lst)
|
||||
209
src/revpicommander/revpilogfile.py
Normal file
209
src/revpicommander/revpilogfile.py
Normal file
@@ -0,0 +1,209 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""View log files from Revolution Pi."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from . import helper
|
||||
from . import proginit as pi
|
||||
from .ui.revpilogfile_ui import Ui_win_revpilogfile
|
||||
|
||||
|
||||
class LogType(IntEnum):
|
||||
NONE = 0
|
||||
APP = 1
|
||||
DAEMON = 2
|
||||
|
||||
|
||||
class DataThread(QtCore.QThread):
|
||||
error_detected = QtCore.pyqtSignal(str)
|
||||
line_logged = QtCore.pyqtSignal(LogType, bool, str)
|
||||
"""log_type, success, text"""
|
||||
|
||||
def __init__(self, parent=None, cycle_time=1000):
|
||||
super(DataThread, self).__init__(parent)
|
||||
|
||||
self._cli = helper.cm.get_cli()
|
||||
self._cycle_time = cycle_time
|
||||
self._paused = True
|
||||
self.error_count = 0
|
||||
self.max_block = 16384 # 16 kByte
|
||||
self.mrk_app = 0
|
||||
self.mrk_daemon = 0
|
||||
|
||||
def _load_log(self, log_type: LogType, xmlcall, start_position: int):
|
||||
"""
|
||||
Get log text and put it to log viewer.
|
||||
|
||||
:param log_type: Type of log file <class 'LogType'>
|
||||
:param xmlcall: XML-RPC call to get log text
|
||||
:param start_position: Start position in log file to read from
|
||||
:return: tuple(position: int, EOF: bool)
|
||||
"""
|
||||
# Load max data from start position
|
||||
buff_log = xmlcall(start_position, self.max_block).data # type: bytes
|
||||
|
||||
eof = True
|
||||
if buff_log == b'\x16': # 'ESC'
|
||||
# RevPiPyLoad could not access log file on Revolution Pi
|
||||
self.line_logged.emit(log_type, False, "")
|
||||
|
||||
elif buff_log == b'\x19': # 'EndOfMedia'
|
||||
# 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", 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:
|
||||
pi.logger.debug("DataThread.run")
|
||||
|
||||
while not self.isInterruptionRequested():
|
||||
eof_app = False
|
||||
eof_daemon = False
|
||||
if not self._paused:
|
||||
try:
|
||||
while not (eof_app or self.isInterruptionRequested()):
|
||||
self.mrk_app, eof_app = self._load_log(
|
||||
LogType.APP,
|
||||
self._cli.load_applog,
|
||||
self.mrk_app,
|
||||
)
|
||||
while not (eof_daemon or self.isInterruptionRequested()):
|
||||
self.mrk_daemon, eof_daemon = self._load_log(
|
||||
LogType.DAEMON,
|
||||
self._cli.load_plclog,
|
||||
self.mrk_daemon,
|
||||
)
|
||||
self.error_count = 0
|
||||
except Exception as e:
|
||||
if self.error_count == 0:
|
||||
self.error_detected.emit(str(e))
|
||||
self.error_count += 1
|
||||
|
||||
self.msleep(self._cycle_time)
|
||||
|
||||
|
||||
class RevPiLogfile(QtWidgets.QMainWindow, Ui_win_revpilogfile):
|
||||
"""Log file viewer for daemon and plc program log."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
u"""Init RevPiLogfile-Class."""
|
||||
super(RevPiLogfile, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.th_data = DataThread(self)
|
||||
self.err_daemon = 0
|
||||
|
||||
helper.cm.connection_established.connect(self.on_cm_connection_established)
|
||||
helper.cm.connection_disconnecting.connect(self.on_cm_connection_disconnecting)
|
||||
|
||||
self._load_gui_settings()
|
||||
|
||||
def _create_data_thread(self):
|
||||
self.th_data.deleteLater()
|
||||
|
||||
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()
|
||||
|
||||
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||
helper.settings.setValue("logfile/geo", self.saveGeometry())
|
||||
helper.settings.setValue("logfile/stay_on_top", self.cbx_stay_on_top.isChecked())
|
||||
|
||||
def hideEvent(self, a0: QtGui.QHideEvent) -> None:
|
||||
self.th_data.pause()
|
||||
super(RevPiLogfile, self).hideEvent(a0)
|
||||
|
||||
def showEvent(self, a0: QtGui.QShowEvent) -> None:
|
||||
self.th_data.resume()
|
||||
super(RevPiLogfile, self).showEvent(a0)
|
||||
|
||||
def _load_gui_settings(self):
|
||||
self.restoreGeometry(helper.settings.value("logfile/geo", b''))
|
||||
self.cbx_stay_on_top.setChecked(helper.settings.value("logfile/stay_on_top", False, bool))
|
||||
self.cbx_wrap.setChecked(helper.settings.value("logfile/wrap", False, bool))
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_cm_connection_disconnecting(self):
|
||||
"""Disconnecting form Revolution Pi."""
|
||||
self.th_data.requestInterruption()
|
||||
self.th_data.wait()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_cm_connection_established(self):
|
||||
"""New connection established."""
|
||||
self.txt_app.clear()
|
||||
self.txt_daemon.clear()
|
||||
|
||||
self._create_data_thread()
|
||||
if self.isVisible():
|
||||
self.th_data.resume()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_daemon_pressed(self):
|
||||
"""Clear the daemon log view."""
|
||||
self.txt_daemon.clear()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_app_pressed(self):
|
||||
"""Clear the app log view."""
|
||||
self.txt_app.clear()
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def on_cbx_stay_on_top_stateChanged(self, state: int):
|
||||
"""Set flag to stay on top of all windows."""
|
||||
self.setWindowFlag(QtCore.Qt.WindowStaysOnTopHint, state == QtCore.Qt.Checked)
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def on_cbx_wrap_stateChanged(self, state: int):
|
||||
"""Line wrap mode."""
|
||||
wrap_mode = QtWidgets.QPlainTextEdit.WidgetWidth if state == QtCore.Qt.Checked else \
|
||||
QtWidgets.QPlainTextEdit.NoWrap
|
||||
self.txt_daemon.setLineWrapMode(wrap_mode)
|
||||
self.txt_app.setLineWrapMode(wrap_mode)
|
||||
helper.settings.setValue("logfile/wrap", self.cbx_wrap.isChecked())
|
||||
|
||||
@QtCore.pyqtSlot(LogType, bool, str)
|
||||
def on_line_logged(self, log_type: LogType, success: bool, text: str):
|
||||
pi.logger.debug("RevPiLogfile.on_line_logged")
|
||||
|
||||
if log_type == LogType.APP:
|
||||
textwidget = self.txt_app
|
||||
elif log_type == LogType.DAEMON:
|
||||
textwidget = self.txt_daemon
|
||||
else:
|
||||
return
|
||||
|
||||
bar = textwidget.verticalScrollBar()
|
||||
auto_scroll = bar.value() == bar.maximum()
|
||||
|
||||
if not success:
|
||||
textwidget.clear()
|
||||
textwidget.setPlainText(self.tr("Can not access log file on the RevPi"))
|
||||
elif text != "":
|
||||
# Function will add \n automatically
|
||||
textwidget.appendPlainText(text.strip("\n"))
|
||||
if auto_scroll:
|
||||
bar.setValue(bar.maximum())
|
||||
321
src/revpicommander/revpioption.py
Normal file
321
src/revpicommander/revpioption.py
Normal file
@@ -0,0 +1,321 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""RevPiPyLoad options window."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from . import helper
|
||||
from . import proginit as pi
|
||||
from .aclmanager import AclManager
|
||||
from .mqttmanager import MqttManager
|
||||
from .ui.revpioption_ui import Ui_diag_options
|
||||
|
||||
|
||||
class RevPiOption(QtWidgets.QDialog, Ui_diag_options):
|
||||
"""Set options of RevPiPyLoad."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(RevPiOption, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
self.setFixedSize(self.size())
|
||||
|
||||
self.dc = {}
|
||||
self.acl_plcslave = ""
|
||||
self.acl_xmlrpc = ""
|
||||
self.mrk_xml_ask = True
|
||||
|
||||
self._dict_mqttsettings = {
|
||||
"mqttbasetopic": "revpi01",
|
||||
"mqttclient_id": "",
|
||||
"mqttbroker_address": "127.0.0.1",
|
||||
"mqttpassword": "",
|
||||
"mqttport": 1883,
|
||||
"mqttsend_on_event": 0,
|
||||
"mqttsendinterval": 30,
|
||||
"mqtttls_set": 0,
|
||||
"mqttusername": "",
|
||||
"mqttwrite_outputs": 0,
|
||||
}
|
||||
|
||||
self.diag_aclmanager = AclManager(self)
|
||||
self.rejected.connect(self.diag_aclmanager.reject)
|
||||
self.diag_mqttmanager = MqttManager(self)
|
||||
self.rejected.connect(self.diag_mqttmanager.reject)
|
||||
self.diag_mqttmanager.dc = self._dict_mqttsettings
|
||||
|
||||
def _apply_acl(self):
|
||||
"""Set availability of controls depending on ACL level."""
|
||||
allow = helper.cm.xml_mode >= 4
|
||||
|
||||
self.cbx_autostart.setEnabled(allow)
|
||||
self.cbx_autoreload.setEnabled(allow)
|
||||
self.sbx_autoreloaddelay.setEnabled(allow)
|
||||
self.cbx_zeroonexit.setEnabled(allow)
|
||||
self.cbx_zeroonerror.setEnabled(allow)
|
||||
self.cbb_replace_io.setEnabled(allow)
|
||||
self.txt_replace_io.setEnabled(allow and self.cbb_replace_io.currentIndex() == 3)
|
||||
self.cbx_plcslave.setEnabled(allow)
|
||||
self.cbx_mqtt.setEnabled(allow)
|
||||
self.cbx_xmlrpc.setEnabled(allow)
|
||||
|
||||
if allow:
|
||||
self.btn_box.setStandardButtons(
|
||||
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
|
||||
)
|
||||
else:
|
||||
self.btn_box.setStandardButtons(
|
||||
QtWidgets.QDialogButtonBox.Cancel
|
||||
)
|
||||
|
||||
def _changesdone(self):
|
||||
"""
|
||||
Check for unsaved changes in dialog.
|
||||
|
||||
:return: True, if unsaved changes was found
|
||||
"""
|
||||
return (
|
||||
int(self.cbx_autostart.isChecked()) != self.dc.get("autostart", 0) or
|
||||
int(self.cbx_autoreload.isChecked()) != self.dc.get("autoreload", 1) or
|
||||
self.sbx_autoreloaddelay.value() != self.dc.get("autoreloaddelay", 5) or
|
||||
int(self.cbx_zeroonexit.isChecked()) != self.dc.get("zeroonexit", 0) or
|
||||
int(self.cbx_zeroonerror.isChecked()) != self.dc.get("zeroonerror", 0) or
|
||||
self.txt_replace_io.text() != self.dc.get("replace_ios", "") or
|
||||
self.cbb_reset_driver_action.currentIndex() != self.dc.get("reset_driver_action", 2) or
|
||||
# todo: self.dc.get("rtlevel", 2)
|
||||
|
||||
int(self.cbx_plcslave.isChecked()) != self.dc.get("plcslave", 0) or
|
||||
self.acl_plcslave != self.dc.get("plcslaveacl", "") or
|
||||
|
||||
int(self.cbx_mqtt.isChecked()) != self.dc.get("mqtt", 0) or
|
||||
self._changesdone_mqtt() or
|
||||
|
||||
int(self.cbx_xmlrpc.isChecked()) != self.dc.get("xmlrpc", 0) or
|
||||
self.acl_xmlrpc != self.dc.get("xmlrpcacl", "")
|
||||
)
|
||||
|
||||
def _changesdone_mqtt(self):
|
||||
"""
|
||||
Check for unsaved changes in mqtt settings.
|
||||
|
||||
:return: True, if unsaved changes was found
|
||||
"""
|
||||
for key in self._dict_mqttsettings:
|
||||
if key in self.dc:
|
||||
if self._dict_mqttsettings[key] != self.dc[key]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _load_settings(self):
|
||||
"""Load values to GUI widgets."""
|
||||
pi.logger.debug("RevPiOption._load_settings")
|
||||
|
||||
self.mrk_xml_ask = True
|
||||
|
||||
self.cbx_autostart.setChecked(bool(self.dc.get("autostart", 0)))
|
||||
self.cbx_autoreload.setChecked(bool(self.dc.get("autoreload", 1)))
|
||||
self.sbx_autoreloaddelay.setValue(self.dc.get("autoreloaddelay", 5))
|
||||
self.cbx_zeroonexit.setChecked(bool(self.dc.get("zeroonexit", 0)))
|
||||
self.cbx_zeroonerror.setChecked(bool(self.dc.get("zeroonerror", 0)))
|
||||
self.txt_replace_io.setText(self.dc.get("replace_ios", ""))
|
||||
self.cbb_reset_driver_action.setCurrentIndex(self.dc.get("reset_driver_action", 2))
|
||||
self.cbx_plcslave.setChecked(bool(self.dc.get("plcslave", 0)))
|
||||
self.acl_plcslave = self.dc.get("plcslaveacl", "")
|
||||
self.cbx_mqtt.setChecked(bool(self.dc.get("mqtt", 0)))
|
||||
self.cbx_xmlrpc.setChecked(bool(self.dc.get("xmlrpc", 0)))
|
||||
self.acl_xmlrpc = self.dc.get("xmlrpcacl", "")
|
||||
|
||||
# Find the right index of combo box
|
||||
if self.txt_replace_io.text() == "":
|
||||
self.cbb_replace_io.setCurrentIndex(0)
|
||||
elif self.txt_replace_io.text() == "/etc/revpipyload/replace_ios.conf":
|
||||
self.cbb_replace_io.setCurrentIndex(1)
|
||||
elif self.txt_replace_io.text() == "replace_ios.conf":
|
||||
self.cbb_replace_io.setCurrentIndex(2)
|
||||
else:
|
||||
self.cbb_replace_io.setCurrentIndex(3)
|
||||
|
||||
# Update directory with mqtt values to compare with dc values
|
||||
for key in self._dict_mqttsettings:
|
||||
if key in self.dc:
|
||||
self._dict_mqttsettings[key] = self.dc[key]
|
||||
|
||||
def accept(self) -> None:
|
||||
if not self._changesdone():
|
||||
super(RevPiOption, self).accept()
|
||||
return
|
||||
|
||||
ask = QtWidgets.QMessageBox.question(
|
||||
self, self.tr("Question"), self.tr(
|
||||
"The settings will be set on the Revolution Pi now.\n\n"
|
||||
"ACL changes and service settings are applied immediately."
|
||||
)
|
||||
) == QtWidgets.QMessageBox.Yes
|
||||
|
||||
if not ask:
|
||||
return
|
||||
|
||||
self.dc["autostart"] = int(self.cbx_autostart.isChecked())
|
||||
self.dc["autoreload"] = int(self.cbx_autoreload.isChecked())
|
||||
self.dc["autoreloaddelay"] = self.sbx_autoreloaddelay.value()
|
||||
self.dc["reset_driver_action"] = self.cbb_reset_driver_action.currentIndex()
|
||||
self.dc["zeroonexit"] = int(self.cbx_zeroonexit.isChecked())
|
||||
self.dc["zeroonerror"] = int(self.cbx_zeroonerror.isChecked())
|
||||
self.dc["replace_ios"] = self.txt_replace_io.text()
|
||||
|
||||
# PLCSlave Settings
|
||||
self.dc["plcslave"] = int(self.cbx_plcslave.isChecked())
|
||||
self.dc["plcslaveacl"] = self.acl_plcslave
|
||||
|
||||
# MQTT Settings
|
||||
self.dc["mqtt"] = int(self.cbx_mqtt.isChecked())
|
||||
self.dc["mqttbasetopic"] = self._dict_mqttsettings["mqttbasetopic"]
|
||||
self.dc["mqttsendinterval"] = int(self._dict_mqttsettings["mqttsendinterval"])
|
||||
self.dc["mqttsend_on_event"] = int(self._dict_mqttsettings["mqttsend_on_event"])
|
||||
self.dc["mqttwrite_outputs"] = int(self._dict_mqttsettings["mqttwrite_outputs"])
|
||||
self.dc["mqttbroker_address"] = self._dict_mqttsettings["mqttbroker_address"]
|
||||
self.dc["mqttport"] = int(self._dict_mqttsettings["mqttport"])
|
||||
self.dc["mqtttls_set"] = int(self._dict_mqttsettings["mqtttls_set"])
|
||||
self.dc["mqttusername"] = self._dict_mqttsettings["mqttusername"]
|
||||
self.dc["mqttpassword"] = self._dict_mqttsettings["mqttpassword"]
|
||||
self.dc["mqttclient_id"] = self._dict_mqttsettings["mqttclient_id"]
|
||||
|
||||
# XML Settings
|
||||
self.dc["xmlrpc"] = int(self.cbx_xmlrpc.isChecked())
|
||||
self.dc["xmlrpcacl"] = self.acl_xmlrpc
|
||||
|
||||
saved = helper.cm.call_remote_function(
|
||||
"set_config", self.dc, ask,
|
||||
default_value=False
|
||||
)
|
||||
|
||||
if saved:
|
||||
super(RevPiOption, self).accept()
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"The settings could not be saved on the Revolution Pi!\n"
|
||||
"Try to save the values one mor time and check the log "
|
||||
"files of RevPiPyLoad if the error rises again."
|
||||
)
|
||||
)
|
||||
|
||||
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||
if self._changesdone():
|
||||
ask = QtWidgets.QMessageBox.question(
|
||||
self, self.tr("Question"), self.tr(
|
||||
"Do you really want to quit? \nUnsaved changes will be lost."
|
||||
)
|
||||
) == QtWidgets.QMessageBox.Yes
|
||||
|
||||
if ask:
|
||||
self.reject()
|
||||
else:
|
||||
a0.ignore()
|
||||
|
||||
def exec(self) -> int:
|
||||
# Reset class values
|
||||
if not helper.cm.connected:
|
||||
return QtWidgets.QDialog.Rejected
|
||||
|
||||
self.dc = helper.cm.call_remote_function("get_config", default_value={})
|
||||
if len(self.dc) == 0:
|
||||
return QtWidgets.QDialog.Rejected
|
||||
|
||||
self._load_settings()
|
||||
self._apply_acl()
|
||||
|
||||
running = helper.cm.call_remote_function("plcslaverunning", default_value=False)
|
||||
self.lbl_slave_status.setText(
|
||||
self.tr("running") if running else self.tr("stopped")
|
||||
)
|
||||
self.lbl_slave_status.setStyleSheet(
|
||||
"color: green" if running else "color: red"
|
||||
)
|
||||
|
||||
running = helper.cm.call_remote_function("mqttrunning")
|
||||
if running is None:
|
||||
# On older versions of RevPiPyLoad MQTT is not implemented
|
||||
self.cbx_mqtt.setToolTip(self.tr(
|
||||
"The MQTT service is not available on your RevPiPyLoad version."
|
||||
))
|
||||
self.btn_mqtt.setVisible(False)
|
||||
self.lbl_mqtt_status.setText("N/A")
|
||||
self.lbl_mqtt_status.setStyleSheet("")
|
||||
else:
|
||||
self.cbx_mqtt.setToolTip("")
|
||||
self.btn_mqtt.setVisible(True)
|
||||
self.lbl_mqtt_status.setText(
|
||||
self.tr("running") if running else self.tr("stopped")
|
||||
)
|
||||
self.lbl_mqtt_status.setStyleSheet(
|
||||
"color: green" if running else "color: red"
|
||||
)
|
||||
|
||||
return super(RevPiOption, self).exec()
|
||||
|
||||
def reject(self) -> None:
|
||||
"""Reject all sub windows and reload settings."""
|
||||
self._load_settings()
|
||||
super(RevPiOption, self).reject()
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def on_cbb_replace_io_currentIndexChanged(self, index: int):
|
||||
"""Update replace_io path in text field to compare values."""
|
||||
if index == 0:
|
||||
self.txt_replace_io.setText("")
|
||||
elif index == 1:
|
||||
self.txt_replace_io.setText("/etc/revpipyload/replace_ios.conf")
|
||||
elif index == 2:
|
||||
self.txt_replace_io.setText("replace_ios.conf")
|
||||
else:
|
||||
self.txt_replace_io.setText(self.dc.get("replace_ios", ""))
|
||||
|
||||
self.txt_replace_io.setEnabled(index == 3)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_aclplcslave_clicked(self):
|
||||
"""Start ACL manager to edit ACL entries."""
|
||||
self.diag_aclmanager.setup_acl_manager(self.acl_plcslave, {
|
||||
0: self.tr("read only"),
|
||||
1: self.tr("read and write"),
|
||||
})
|
||||
self.diag_aclmanager.read_only = helper.cm.xml_mode < 4
|
||||
if self.diag_aclmanager.exec() == QtWidgets.QDialog.Accepted:
|
||||
self.acl_plcslave = self.diag_aclmanager.get_acl()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_mqtt_clicked(self):
|
||||
"""Open MQTT settings."""
|
||||
if not helper.cm.connected:
|
||||
return
|
||||
|
||||
self.diag_mqttmanager.read_only = helper.cm.xml_mode < 4
|
||||
self.diag_mqttmanager.exec()
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def on_cbx_xmlrpc_stateChanged(self, state: int):
|
||||
if state == QtCore.Qt.Unchecked and self.mrk_xml_ask:
|
||||
self.mrk_xml_ask = QtWidgets.QMessageBox.question(
|
||||
self, self.tr("Question"), self.tr(
|
||||
"Are you sure you want to deactivate the XML-RPC server? "
|
||||
"You will NOT be able to access the Revolution Pi with "
|
||||
"this program after saving the options!"
|
||||
)
|
||||
) == QtWidgets.QMessageBox.No
|
||||
if self.mrk_xml_ask:
|
||||
self.cbx_xmlrpc.setCheckState(QtCore.Qt.Checked)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_aclxmlrpc_clicked(self):
|
||||
self.diag_aclmanager.setup_acl_manager(self.acl_xmlrpc, {
|
||||
0: self.tr("Start/Stop PLC program and read logs"),
|
||||
1: self.tr("+ read IOs in watch mode"),
|
||||
2: self.tr("+ read properties and download PLC program"),
|
||||
3: self.tr("+ upload PLC program"),
|
||||
4: self.tr("+ set properties")
|
||||
})
|
||||
self.diag_aclmanager.read_only = helper.cm.xml_mode < 4
|
||||
if self.diag_aclmanager.exec() == QtWidgets.QDialog.Accepted:
|
||||
self.acl_xmlrpc = self.diag_aclmanager.get_acl()
|
||||
376
src/revpicommander/revpiplclist.py
Normal file
376
src/revpicommander/revpiplclist.py
Normal file
@@ -0,0 +1,376 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Saved connections of Revolution Pi devices."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from . import proginit as pi
|
||||
from .helper import WidgetData, settings
|
||||
from .ui.revpiplclist_ui import Ui_diag_connections
|
||||
|
||||
|
||||
class NodeType(IntEnum):
|
||||
CON = 1000
|
||||
DIR = 1001
|
||||
|
||||
|
||||
class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections):
|
||||
"""Manage your saved connections."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(RevPiPlcList, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
self.__default_name = self.tr("New connection")
|
||||
self.__default_port = 55123
|
||||
|
||||
self.__current_item = QtWidgets.QTreeWidgetItem() # type: QtWidgets.QTreeWidgetItem
|
||||
self.changes = True
|
||||
|
||||
self.tre_connections.setColumnWidth(0, 250)
|
||||
self.lbl_port.setText(self.lbl_port.text().format(self.__default_port))
|
||||
self.sbx_port.setValue(self.__default_port)
|
||||
|
||||
def _load_settings(self):
|
||||
"""Load values to GUI widgets."""
|
||||
pi.logger.debug("RevPiPlcList._load_settings")
|
||||
|
||||
self.tre_connections.clear()
|
||||
self.cbb_folder.clear()
|
||||
self.cbb_folder.addItem("")
|
||||
for i in range(settings.beginReadArray("connections")):
|
||||
settings.setArrayIndex(i)
|
||||
|
||||
con_item = QtWidgets.QTreeWidgetItem(NodeType.CON)
|
||||
con_item.setIcon(0, QtGui.QIcon(":/main/ico/cpu.ico"))
|
||||
con_item.setText(0, settings.value("name", "Revolution Pi", str))
|
||||
con_item.setText(1, settings.value("address", "127.0.0.1", str))
|
||||
con_item.setData(0, WidgetData.port, settings.value("port", self.__default_port, int))
|
||||
con_item.setData(0, WidgetData.timeout, settings.value("timeout", 5, int))
|
||||
|
||||
con_item.setData(0, WidgetData.last_dir_upload, settings.value("last_dir_upload"))
|
||||
con_item.setData(0, WidgetData.last_file_upload, settings.value("last_file_upload"))
|
||||
con_item.setData(0, WidgetData.last_dir_pictory, settings.value("last_dir_pictory"))
|
||||
con_item.setData(0, WidgetData.last_dir_picontrol, settings.value("last_dir_picontrol"))
|
||||
con_item.setData(0, WidgetData.last_dir_selected, settings.value("last_dir_selected"))
|
||||
con_item.setData(0, WidgetData.last_pictory_file, settings.value("last_pictory_file"))
|
||||
con_item.setData(0, WidgetData.last_tar_file, settings.value("last_tar_file"))
|
||||
con_item.setData(0, WidgetData.last_zip_file, settings.value("last_zip_file"))
|
||||
con_item.setData(0, WidgetData.watch_files, settings.value("watch_files"))
|
||||
con_item.setData(0, WidgetData.watch_path, settings.value("watch_path"))
|
||||
con_item.setData(0, WidgetData.debug_geos, settings.value("debug_geos"))
|
||||
|
||||
folder = settings.value("folder", "", str)
|
||||
if folder:
|
||||
sub_folder = self._get_folder_item(folder)
|
||||
if sub_folder is None:
|
||||
sub_folder = QtWidgets.QTreeWidgetItem(NodeType.DIR)
|
||||
sub_folder.setIcon(0, QtGui.QIcon(":/main/ico/folder.ico"))
|
||||
sub_folder.setText(0, folder)
|
||||
self.tre_connections.addTopLevelItem(sub_folder)
|
||||
self.cbb_folder.addItem(folder)
|
||||
|
||||
sub_folder.addChild(con_item)
|
||||
else:
|
||||
self.tre_connections.addTopLevelItem(con_item)
|
||||
|
||||
settings.endArray()
|
||||
|
||||
self.tre_connections.expandAll()
|
||||
self.changes = True
|
||||
|
||||
if self.tre_connections.topLevelItemCount() == 0:
|
||||
self._edit_state()
|
||||
|
||||
def accept(self) -> None:
|
||||
pi.logger.debug("RevPiPlcList.accept")
|
||||
|
||||
def set_settings(node: QtWidgets.QTreeWidgetItem):
|
||||
parent = node.parent()
|
||||
settings.setValue("address", node.text(1))
|
||||
settings.setValue("folder", parent.text(0) if parent else "")
|
||||
settings.setValue("name", node.text(0))
|
||||
settings.setValue("port", node.data(0, WidgetData.port))
|
||||
settings.setValue("timeout", node.data(0, WidgetData.timeout))
|
||||
|
||||
if node.data(0, WidgetData.last_dir_upload):
|
||||
settings.setValue("last_dir_upload", node.data(0, WidgetData.last_dir_upload))
|
||||
if node.data(0, WidgetData.last_file_upload):
|
||||
settings.setValue("last_file_upload", node.data(0, WidgetData.last_file_upload))
|
||||
if node.data(0, WidgetData.last_dir_pictory):
|
||||
settings.setValue("last_dir_pictory", node.data(0, WidgetData.last_dir_pictory))
|
||||
if node.data(0, WidgetData.last_dir_picontrol):
|
||||
settings.setValue("last_dir_picontrol", node.data(0, WidgetData.last_dir_picontrol))
|
||||
if node.data(0, WidgetData.last_dir_selected):
|
||||
settings.setValue("last_dir_selected", node.data(0, WidgetData.last_dir_selected))
|
||||
if node.data(0, WidgetData.last_pictory_file):
|
||||
settings.setValue("last_pictory_file", node.data(0, WidgetData.last_pictory_file))
|
||||
if node.data(0, WidgetData.last_tar_file):
|
||||
settings.setValue("last_tar_file", node.data(0, WidgetData.last_tar_file))
|
||||
if node.data(0, WidgetData.last_zip_file):
|
||||
settings.setValue("last_zip_file", node.data(0, WidgetData.last_zip_file))
|
||||
if node.data(0, WidgetData.watch_files):
|
||||
settings.setValue("watch_files", node.data(0, WidgetData.watch_files))
|
||||
if node.data(0, WidgetData.watch_path):
|
||||
settings.setValue("watch_path", node.data(0, WidgetData.watch_path))
|
||||
if node.data(0, WidgetData.debug_geos):
|
||||
settings.setValue("debug_geos", node.data(0, WidgetData.debug_geos))
|
||||
|
||||
settings.remove("connections")
|
||||
settings.beginWriteArray("connections")
|
||||
|
||||
counter_index = 0
|
||||
for i in range(self.tre_connections.topLevelItemCount()):
|
||||
root_item = self.tre_connections.topLevelItem(i)
|
||||
if root_item.type() == NodeType.DIR:
|
||||
for k in range(root_item.childCount()):
|
||||
settings.setArrayIndex(counter_index)
|
||||
set_settings(root_item.child(k))
|
||||
counter_index += 1
|
||||
elif root_item.type() == NodeType.CON:
|
||||
settings.setArrayIndex(counter_index)
|
||||
set_settings(root_item)
|
||||
counter_index += 1
|
||||
|
||||
settings.endArray()
|
||||
|
||||
self.changes = False
|
||||
super(RevPiPlcList, self).accept()
|
||||
|
||||
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||
pi.logger.debug("RevPiPlcList.closeEvent")
|
||||
if self.changes:
|
||||
ask = QtWidgets.QMessageBox.question(
|
||||
self, self.tr("Question"), self.tr(
|
||||
"Do you really want to quit? \nUnsaved changes will be lost."
|
||||
)
|
||||
) == QtWidgets.QMessageBox.Yes
|
||||
|
||||
if ask:
|
||||
self.reject()
|
||||
else:
|
||||
a0.ignore()
|
||||
|
||||
def exec(self) -> int:
|
||||
self._load_settings()
|
||||
return super(RevPiPlcList, self).exec()
|
||||
|
||||
@QtCore.pyqtSlot(QtWidgets.QAbstractButton)
|
||||
def on_btn_box_clicked(self, button: QtWidgets.QAbstractButton):
|
||||
if self.btn_box.buttonRole(button) == QtWidgets.QDialogButtonBox.DestructiveRole:
|
||||
self.reject()
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# region # REGION: Connection management
|
||||
|
||||
def _edit_state(self):
|
||||
item = self.tre_connections.currentItem()
|
||||
if item is None:
|
||||
up_ok = False
|
||||
down_ok = False
|
||||
con_item = False
|
||||
dir_item = False
|
||||
else:
|
||||
con_item = item.type() == NodeType.CON
|
||||
dir_item = item.type() == NodeType.DIR
|
||||
|
||||
if item.parent():
|
||||
index = item.parent().indexOfChild(item)
|
||||
up_ok = index > 0
|
||||
down_ok = index < item.parent().childCount() - 1
|
||||
else:
|
||||
index = self.tre_connections.indexOfTopLevelItem(item)
|
||||
up_ok = index > 0
|
||||
down_ok = index < self.tre_connections.topLevelItemCount() - 1
|
||||
|
||||
self.btn_up.setEnabled(up_ok)
|
||||
self.btn_down.setEnabled(down_ok)
|
||||
self.btn_delete.setEnabled(con_item)
|
||||
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):
|
||||
"""Find the folder entry by name."""
|
||||
for i in range(self.tre_connections.topLevelItemCount()):
|
||||
tli = self.tre_connections.topLevelItem(i)
|
||||
if tli.type() == NodeType.DIR and tli.text(0) == name:
|
||||
return tli
|
||||
return None
|
||||
|
||||
def _move_item(self, count: int):
|
||||
"""Move connection item up or down"""
|
||||
item = self.tre_connections.currentItem()
|
||||
if not item:
|
||||
return
|
||||
|
||||
if item.parent():
|
||||
dir_item = item.parent()
|
||||
index = dir_item.indexOfChild(item)
|
||||
new_index = index + count
|
||||
if 0 <= new_index < dir_item.childCount():
|
||||
item = dir_item.takeChild(index)
|
||||
dir_item.insertChild(new_index, item)
|
||||
else:
|
||||
index = self.tre_connections.indexOfTopLevelItem(item)
|
||||
new_index = index + count
|
||||
if 0 <= index < self.tre_connections.topLevelItemCount():
|
||||
item = self.tre_connections.takeTopLevelItem(index)
|
||||
self.tre_connections.insertTopLevelItem(new_index, item)
|
||||
|
||||
self.tre_connections.setCurrentItem(item)
|
||||
self._edit_state()
|
||||
|
||||
@QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem, QtWidgets.QTreeWidgetItem)
|
||||
def on_tre_connections_currentItemChanged(
|
||||
self, current: QtWidgets.QTreeWidgetItem, previous: QtWidgets.QTreeWidgetItem):
|
||||
|
||||
self._edit_state()
|
||||
if current and current.type() == NodeType.CON:
|
||||
self.__current_item = current
|
||||
self.txt_name.setText(current.text(0))
|
||||
self.txt_address.setText(current.text(1))
|
||||
self.sbx_port.setValue(current.data(0, WidgetData.port))
|
||||
self.sbx_timeout.setValue(current.data(0, WidgetData.timeout))
|
||||
if current.parent() is None:
|
||||
self.cbb_folder.setCurrentIndex(0)
|
||||
else:
|
||||
self.cbb_folder.setCurrentText(current.parent().text(0))
|
||||
elif current and current.type() == NodeType.DIR:
|
||||
self.__current_item = current
|
||||
self.cbb_folder.setCurrentText(current.text(0))
|
||||
else:
|
||||
self.__current_item = QtWidgets.QTreeWidgetItem()
|
||||
self.cbb_folder.setCurrentText(current.text(0) if current else "")
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_up_pressed(self):
|
||||
self._move_item(-1)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_down_pressed(self):
|
||||
self._move_item(1)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_delete_pressed(self):
|
||||
"""Remove selected entry."""
|
||||
item = self.tre_connections.currentItem()
|
||||
if item and item.type() == NodeType.CON:
|
||||
dir_node = item.parent()
|
||||
if dir_node:
|
||||
dir_node.removeChild(item)
|
||||
else:
|
||||
index = self.tre_connections.indexOfTopLevelItem(item)
|
||||
self.tre_connections.takeTopLevelItem(index)
|
||||
|
||||
self._edit_state()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_add_pressed(self):
|
||||
"""Create new element."""
|
||||
self.__current_item = QtWidgets.QTreeWidgetItem(NodeType.CON)
|
||||
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)
|
||||
else:
|
||||
self.tre_connections.addTopLevelItem(self.__current_item)
|
||||
|
||||
self.tre_connections.setCurrentItem(self.__current_item)
|
||||
self.txt_name.setFocus()
|
||||
self.txt_name.selectAll()
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def on_txt_name_textEdited(self, text):
|
||||
if self.__current_item.type() != NodeType.CON:
|
||||
return
|
||||
self.__current_item.setText(0, text)
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def on_txt_address_textEdited(self, text):
|
||||
if self.__current_item.type() != NodeType.CON:
|
||||
return
|
||||
self.__current_item.setText(1, text)
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def on_sbx_port_valueChanged(self, value: int):
|
||||
if self.__current_item.type() != NodeType.CON:
|
||||
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))
|
||||
|
||||
if self.__current_item.type() == NodeType.DIR:
|
||||
# We just have to rename the dir node
|
||||
self.__current_item.setText(0, text)
|
||||
|
||||
elif self.__current_item.type() == NodeType.CON:
|
||||
sub_folder = self._get_folder_item(text)
|
||||
dir_node = self.__current_item.parent()
|
||||
if dir_node:
|
||||
if dir_node.text(0) == text:
|
||||
# It is the same folder
|
||||
return
|
||||
|
||||
if text != "" and dir_node.childCount() == 1 and not sub_folder:
|
||||
# The folder hold just one item, so we can rename that
|
||||
for i in range(self.cbb_folder.count()):
|
||||
if self.cbb_folder.itemText(i) == dir_node.text(0):
|
||||
self.cbb_folder.setItemText(i, text)
|
||||
break
|
||||
dir_node.setText(0, text)
|
||||
return
|
||||
|
||||
index = dir_node.indexOfChild(self.__current_item)
|
||||
self.__current_item = dir_node.takeChild(index)
|
||||
|
||||
elif text != "":
|
||||
# Move root to folder
|
||||
index = self.tre_connections.indexOfTopLevelItem(self.__current_item)
|
||||
self.__current_item = self.tre_connections.takeTopLevelItem(index)
|
||||
|
||||
else:
|
||||
# Root stays root
|
||||
return
|
||||
|
||||
if text == "":
|
||||
self.tre_connections.addTopLevelItem(self.__current_item)
|
||||
|
||||
else:
|
||||
if sub_folder is None:
|
||||
sub_folder = QtWidgets.QTreeWidgetItem(NodeType.DIR)
|
||||
sub_folder.setIcon(0, QtGui.QIcon(":/main/ico/folder.ico"))
|
||||
sub_folder.setText(0, text)
|
||||
self.tre_connections.addTopLevelItem(sub_folder)
|
||||
self.cbb_folder.addItem(text)
|
||||
sub_folder.addChild(self.__current_item)
|
||||
|
||||
if dir_node and dir_node.childCount() == 0:
|
||||
# Remove empty folders
|
||||
for i in range(self.cbb_folder.count()):
|
||||
if self.cbb_folder.itemText(i) == dir_node.text(0):
|
||||
self.cbb_folder.removeItem(i)
|
||||
break
|
||||
index = self.tre_connections.indexOfTopLevelItem(dir_node)
|
||||
self.tre_connections.takeTopLevelItem(index)
|
||||
|
||||
self.tre_connections.setCurrentItem(self.__current_item)
|
||||
self.cbb_folder.setFocus()
|
||||
|
||||
# endregion # # # # #
|
||||
704
src/revpicommander/revpiprogram.py
Normal file
704
src/revpicommander/revpiprogram.py
Normal file
@@ -0,0 +1,704 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Revolution Pi PLC program configuration."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import gzip
|
||||
import os
|
||||
import tarfile
|
||||
import zipfile
|
||||
from shutil import rmtree
|
||||
from tempfile import mkdtemp
|
||||
from xmlrpc.client import Binary
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from . import helper
|
||||
from . import proginit as pi
|
||||
from .ui.revpiprogram_ui import Ui_diag_program
|
||||
|
||||
|
||||
class RevPiProgram(QtWidgets.QDialog, Ui_diag_program):
|
||||
"""Program options of RevPiPyLoad."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(RevPiProgram, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
self.setFixedSize(self.size())
|
||||
|
||||
self.dc = {}
|
||||
self.lst_files = []
|
||||
|
||||
self.cbx_pictory.setEnabled(False)
|
||||
self.recover_gui()
|
||||
|
||||
def _apply_acl(self):
|
||||
"""Set availability of controls depending on ACL level."""
|
||||
|
||||
# Setting properties require level 4
|
||||
self.cbb_plcprogram.setEnabled(helper.cm.xml_mode >= 4)
|
||||
self.txt_plcarguments.setEnabled(helper.cm.xml_mode >= 4)
|
||||
self.rbn_pythonversion_2.setEnabled(helper.cm.xml_mode >= 4)
|
||||
self.rbn_pythonversion_3.setEnabled(helper.cm.xml_mode >= 4)
|
||||
self.cbx_plcworkdir_set_uid.setEnabled(helper.cm.xml_mode >= 4)
|
||||
|
||||
# Downloads require level 2
|
||||
self.btn_program_download.setEnabled(helper.cm.xml_mode >= 2)
|
||||
self.btn_pictory_download.setEnabled(helper.cm.xml_mode >= 2)
|
||||
self.btn_procimg_download.setEnabled(helper.cm.xml_mode >= 2)
|
||||
|
||||
# Uploads require level 3
|
||||
self.btn_program_upload.setEnabled(helper.cm.xml_mode >= 3)
|
||||
self.btn_pictory_upload.setEnabled(helper.cm.xml_mode >= 3)
|
||||
|
||||
def _changesdone(self):
|
||||
"""
|
||||
Check for unsaved changes in dialog.
|
||||
|
||||
:return: True, if unsaved changes was found
|
||||
"""
|
||||
return \
|
||||
self.cbb_plcprogram.currentText() != self.dc.get("plcprogram", "") or \
|
||||
self.txt_plcarguments.text() != self.dc.get("plcarguments", "") or \
|
||||
self.rbn_pythonversion_2.isChecked() != (self.dc.get("pythonversion", 3) == 2) or \
|
||||
self.rbn_pythonversion_3.isChecked() != (self.dc.get("pythonversion", 3) == 3) or \
|
||||
int(self.cbx_plcworkdir_set_uid.isChecked()) != self.dc.get("plcworkdir_set_uid", 0) or \
|
||||
self.sbx_plcprogram_watchdog.value() != self.dc.get("plcprogram_watchdog", 0)
|
||||
|
||||
def _load_settings(self, files_only=False):
|
||||
"""Load values to GUI widgets."""
|
||||
pi.logger.debug("RevPiProgram._load_settings")
|
||||
|
||||
if files_only:
|
||||
mrk_program = self.cbb_plcprogram.currentText()
|
||||
else:
|
||||
mrk_program = ""
|
||||
|
||||
self.cbb_plcprogram.clear()
|
||||
self.cbb_plcprogram.addItem("")
|
||||
|
||||
self.lst_files.sort()
|
||||
is_in_list = False
|
||||
for file in self.lst_files:
|
||||
if file == ".placeholder":
|
||||
continue
|
||||
self.cbb_plcprogram.addItem(file)
|
||||
check = mrk_program or self.dc.get("plcprogram", "")
|
||||
if file == check:
|
||||
is_in_list = True
|
||||
|
||||
if not is_in_list:
|
||||
pi.logger.warning("File {0} is not in list".format(mrk_program or self.dc.get("plcprogram", "")))
|
||||
|
||||
if files_only:
|
||||
self.cbb_plcprogram.setCurrentText(mrk_program)
|
||||
else:
|
||||
self.cbb_plcprogram.setCurrentText(self.dc.get("plcprogram", ""))
|
||||
self.txt_plcarguments.setText(self.dc.get("plcarguments", ""))
|
||||
self.rbn_pythonversion_2.setChecked(self.dc.get("pythonversion", 3) == 2)
|
||||
self.rbn_pythonversion_3.setChecked(self.dc.get("pythonversion", 3) == 3)
|
||||
self.cbx_plcworkdir_set_uid.setChecked(bool(self.dc.get("plcworkdir_set_uid", 0)))
|
||||
self.sbx_plcprogram_watchdog.setValue(self.dc.get("plcprogram_watchdog", 0))
|
||||
|
||||
def accept(self) -> None:
|
||||
# todo: After upload ask for restart pcl program?
|
||||
if not self._changesdone():
|
||||
super(RevPiProgram, self).accept()
|
||||
return
|
||||
|
||||
if self.cbb_plcprogram.currentText() == "":
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"You have to select a start program, before uploading the "
|
||||
"settings."
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
ask = QtWidgets.QMessageBox.question(
|
||||
self, self.tr("Question"), self.tr(
|
||||
"The settings will be set on the Revolution Pi now.\n\n"
|
||||
"If you made changes on the 'PCL Program' section, your plc "
|
||||
"program will restart now!"
|
||||
)
|
||||
) == QtWidgets.QMessageBox.Yes
|
||||
|
||||
if not ask:
|
||||
return
|
||||
|
||||
self.dc["plcprogram"] = self.cbb_plcprogram.currentText()
|
||||
self.dc["plcarguments"] = self.txt_plcarguments.text()
|
||||
self.dc["pythonversion"] = 2 if self.rbn_pythonversion_2.isChecked() else 3
|
||||
self.dc["plcworkdir_set_uid"] = int(self.cbx_plcworkdir_set_uid.isChecked())
|
||||
self.dc["plcprogram_watchdog"] = self.sbx_plcprogram_watchdog.value()
|
||||
|
||||
saved = helper.cm.call_remote_function(
|
||||
"set_config", self.dc, ask,
|
||||
default_value=False
|
||||
)
|
||||
|
||||
if saved:
|
||||
super(RevPiProgram, self).accept()
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"The settings could not be saved on the Revolution Pi!\n"
|
||||
"Try to save the values one mor time and check the log "
|
||||
"files of RevPiPyLoad if the error rises again."
|
||||
)
|
||||
)
|
||||
|
||||
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||
if self._changesdone():
|
||||
ask = QtWidgets.QMessageBox.question(
|
||||
self, self.tr("Question"), self.tr(
|
||||
"Do you really want to quit? \nUnsaved changes will be lost."
|
||||
)
|
||||
) == QtWidgets.QMessageBox.Yes
|
||||
|
||||
if ask:
|
||||
self.reject()
|
||||
else:
|
||||
a0.ignore()
|
||||
|
||||
def exec(self) -> int:
|
||||
# Reset class values
|
||||
if not helper.cm.connected:
|
||||
return QtWidgets.QDialog.Rejected
|
||||
|
||||
self.dc = helper.cm.call_remote_function("get_config", default_value={})
|
||||
self.lst_files = helper.cm.call_remote_function("get_filelist", default_value=[])
|
||||
if len(self.dc) == 0 or len(self.lst_files) == 0:
|
||||
return QtWidgets.QDialog.Rejected
|
||||
|
||||
self._load_settings()
|
||||
self._apply_acl()
|
||||
|
||||
return super(RevPiProgram, self).exec()
|
||||
|
||||
def reject(self) -> None:
|
||||
"""Reject all sub windows and reload settings."""
|
||||
self._load_settings()
|
||||
super(RevPiProgram, self).reject()
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# region # REGION: PLC program
|
||||
|
||||
def _upload_pictory(self, filename: str):
|
||||
"""Upload piCtory configuration."""
|
||||
fh = open(filename, "rb")
|
||||
file_binary = Binary(fh.read())
|
||||
fh.close()
|
||||
|
||||
ask = QtWidgets.QMessageBox.question(
|
||||
self, self.tr("Reset driver..."), self.tr(
|
||||
"Reset piControl driver after successful uploading new piCtory "
|
||||
"configuration?\nThe process image will be interrupted for a "
|
||||
"short time!"
|
||||
), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel
|
||||
)
|
||||
if ask == QtWidgets.QMessageBox.Cancel:
|
||||
return
|
||||
|
||||
ec = helper.cm.call_remote_function(
|
||||
"set_pictoryrsc", file_binary, ask == QtWidgets.QMessageBox.Yes
|
||||
)
|
||||
|
||||
if ec is None:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Got an network error while send data to Revolution Pi.\n"
|
||||
"Please try again."
|
||||
)
|
||||
)
|
||||
elif ec == 0:
|
||||
helper.cm.program_last_pictory_file = filename
|
||||
if ask == QtWidgets.QMessageBox.Yes:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, self.tr("Success"), self.tr(
|
||||
"The transfer of the piCtory configuration "
|
||||
"and the reset of piControl have been "
|
||||
"successfully executed."
|
||||
),
|
||||
)
|
||||
else:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, self.tr("Success"), self.tr(
|
||||
"The piCtory configuration was successfully transferred."
|
||||
)
|
||||
)
|
||||
|
||||
elif ec == -1:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Can not process the transferred file."
|
||||
)
|
||||
)
|
||||
elif ec == -2:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Can not find main elements in piCtory file."
|
||||
)
|
||||
)
|
||||
elif ec == -4:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Contained devices could not be found on Revolution "
|
||||
"Pi. The configuration may be from a newer piCtory version!"
|
||||
)
|
||||
)
|
||||
elif ec == -5:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Could not load RAP catalog on Revolution Pi."
|
||||
)
|
||||
)
|
||||
elif ec < 0:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"The piCtory configuration could not be "
|
||||
"written on the Revolution Pi."
|
||||
)
|
||||
)
|
||||
elif ec > 0:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self, self.tr("Warning"), self.tr(
|
||||
"The piCtroy configuration has been saved successfully.\n"
|
||||
"An error occurred on piControl reset!"
|
||||
)
|
||||
)
|
||||
|
||||
def check_replacedir(self, rootdir: str):
|
||||
"""
|
||||
Return root dir of a extracted archive an piCtory configuration.
|
||||
|
||||
This function checks the root dir of a extracted directory. It
|
||||
will check the existence of a piCtory configuration will will
|
||||
return that information in the return tuple.
|
||||
|
||||
:param rootdir: Dir to check
|
||||
:return: Tuple with corrected root dir and piCtory configuration
|
||||
"""
|
||||
lst_dir = os.listdir(rootdir)
|
||||
if len(lst_dir) == 1 and \
|
||||
os.path.isdir(os.path.join(rootdir, lst_dir[0])):
|
||||
return os.path.join(rootdir, lst_dir[0]), None
|
||||
|
||||
if len(lst_dir) == 2:
|
||||
rscfile = None
|
||||
for fname in lst_dir:
|
||||
if fname.find(".rsc"):
|
||||
rscfile = os.path.join(rootdir, fname)
|
||||
return os.path.join(rootdir, lst_dir[0]), rscfile
|
||||
|
||||
else:
|
||||
return rootdir, None
|
||||
|
||||
def create_filelist(self, rootdir):
|
||||
"""Create a file list of a directory.
|
||||
|
||||
:param rootdir: Root dir to crate file list
|
||||
:return: File list with <class 'str'>
|
||||
"""
|
||||
filelist = []
|
||||
for tup_dir in os.walk(rootdir):
|
||||
for fname in tup_dir[2]:
|
||||
filelist.append(os.path.join(tup_dir[0], fname))
|
||||
return filelist
|
||||
|
||||
def recover_gui(self):
|
||||
"""Load saved GUI states."""
|
||||
self.cbb_format.setCurrentIndex(helper.settings.value("program/cbb_format_index", 0, int))
|
||||
self.cbx_pictory.setChecked(helper.settings.value("program/cbx_pictory_checked", False, bool))
|
||||
self.cbx_clear.setChecked(helper.settings.value("program/cbx_clear_checked", False, bool))
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def on_cbb_format_currentIndexChanged(self, index: int):
|
||||
helper.settings.setValue("program/cbb_format_index", index)
|
||||
self.cbx_pictory.setEnabled(index >= 1)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_program_download_pressed(self):
|
||||
"""Download plc program from Revolution Pi."""
|
||||
if not helper.cm.connected:
|
||||
return
|
||||
|
||||
selected_dir = ""
|
||||
|
||||
if self.cbb_format.currentIndex() == 0:
|
||||
# Save files as zip archive
|
||||
diag_save = QtWidgets.QFileDialog(
|
||||
self, self.tr("Save ZIP archive..."),
|
||||
os.path.join(
|
||||
helper.cm.program_last_zip_file,
|
||||
"{0}.zip".format(helper.cm.name)
|
||||
),
|
||||
self.tr("ZIP archive (*.zip);;All files (*.*)")
|
||||
)
|
||||
diag_save.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
|
||||
diag_save.setDefaultSuffix("zip")
|
||||
self.rejected.connect(diag_save.reject)
|
||||
|
||||
if diag_save.exec() != QtWidgets.QFileDialog.AcceptSave or len(diag_save.selectedFiles()) != 1:
|
||||
return
|
||||
filename = diag_save.selectedFiles()[0]
|
||||
fh = open(filename, "wb")
|
||||
|
||||
helper.cm.program_last_zip_file = filename
|
||||
|
||||
elif self.cbb_format.currentIndex() == 1:
|
||||
# Save files as TarGz archive
|
||||
diag_save = QtWidgets.QFileDialog(
|
||||
self, self.tr("Save TGZ archive..."),
|
||||
os.path.join(
|
||||
helper.cm.program_last_tar_file,
|
||||
"{0}.tgz".format(helper.cm.name)
|
||||
),
|
||||
self.tr("TGZ archive (*.tgz);;All files (*.*)")
|
||||
)
|
||||
diag_save.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
|
||||
diag_save.setDefaultSuffix("tgz")
|
||||
self.rejected.connect(diag_save.reject)
|
||||
|
||||
if diag_save.exec() != QtWidgets.QFileDialog.AcceptSave or len(diag_save.selectedFiles()) != 1:
|
||||
return
|
||||
filename = diag_save.selectedFiles()[0]
|
||||
fh = open(filename, "wb")
|
||||
|
||||
helper.cm.program_last_tar_file = filename
|
||||
|
||||
else:
|
||||
# Other indexes are not allowed for download
|
||||
return
|
||||
|
||||
plcfile = helper.cm.call_remote_function(
|
||||
"plcdownload",
|
||||
"zip" if self.cbb_format.currentIndex() == 0 else "tar",
|
||||
self.cbx_pictory.isChecked()
|
||||
)
|
||||
|
||||
if plcfile is None:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Could not load PLC program from Revolution Pi."
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
try:
|
||||
fh.write(plcfile.data)
|
||||
fh.close()
|
||||
|
||||
except Exception as e:
|
||||
pi.logger.error(e)
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Coud not save the archive or extract the files!\n"
|
||||
"Please retry.")
|
||||
)
|
||||
else:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, self.tr("Success"), self.tr(
|
||||
"Transfer successfully completed."
|
||||
)
|
||||
)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_program_upload_pressed(self):
|
||||
"""Upload plc program to Revolution Pi."""
|
||||
if not helper.cm.connected:
|
||||
return
|
||||
|
||||
dirtmp = None
|
||||
|
||||
def remove_temp():
|
||||
# Remove temp dir
|
||||
if dirtmp is not None:
|
||||
rmtree(dirtmp)
|
||||
|
||||
dirselect = ""
|
||||
lst_files = []
|
||||
folder_name = ""
|
||||
rscfile = None
|
||||
|
||||
if self.cbb_format.currentIndex() == 0:
|
||||
# Upload zip archive content
|
||||
diag_open = QtWidgets.QFileDialog(
|
||||
self, self.tr("Upload content of ZIP archive..."),
|
||||
helper.cm.program_last_file_upload,
|
||||
self.tr("ZIP archive (*.zip);;All files (*.*)")
|
||||
)
|
||||
diag_open.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
|
||||
diag_open.setFileMode(QtWidgets.QFileDialog.ExistingFile)
|
||||
diag_open.setDefaultSuffix("zip")
|
||||
self.rejected.connect(diag_open.reject)
|
||||
|
||||
if diag_open.exec() != QtWidgets.QFileDialog.AcceptSave or len(diag_open.selectedFiles()) != 1:
|
||||
return
|
||||
|
||||
filename = diag_open.selectedFiles()[0]
|
||||
helper.cm.program_last_file_upload = filename
|
||||
if zipfile.is_zipfile(filename):
|
||||
dirtmp = mkdtemp()
|
||||
fhz = zipfile.ZipFile(filename)
|
||||
fhz.extractall(dirtmp)
|
||||
fhz.close()
|
||||
|
||||
lst_files = self.create_filelist(dirtmp)
|
||||
dirselect, rscfile = self.check_replacedir(dirtmp)
|
||||
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"The selected file ist not a ZIP archive."
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
elif self.cbb_format.currentIndex() == 1:
|
||||
# Upload TarGz content
|
||||
diag_open = QtWidgets.QFileDialog(
|
||||
self, self.tr("Upload content of TAR archive..."),
|
||||
helper.cm.program_last_file_upload,
|
||||
self.tr("TAR archive (*.tgz);;All files (*.*)")
|
||||
)
|
||||
diag_open.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
|
||||
diag_open.setFileMode(QtWidgets.QFileDialog.ExistingFile)
|
||||
diag_open.setDefaultSuffix("tgz")
|
||||
self.rejected.connect(diag_open.reject)
|
||||
|
||||
if diag_open.exec() != QtWidgets.QFileDialog.AcceptSave or len(diag_open.selectedFiles()) != 1:
|
||||
return
|
||||
|
||||
filename = diag_open.selectedFiles()[0]
|
||||
helper.cm.program_last_file_upload = filename
|
||||
if tarfile.is_tarfile(filename):
|
||||
dirtmp = mkdtemp()
|
||||
fht = tarfile.open(filename)
|
||||
fht.extractall(dirtmp)
|
||||
fht.close()
|
||||
|
||||
lst_files = self.create_filelist(dirtmp)
|
||||
dirselect, rscfile = self.check_replacedir(dirtmp)
|
||||
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"The selected file ist not a TAR archive."
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
# No files selected
|
||||
if len(lst_files) == 0:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self, self.tr("No files to upload..."), self.tr(
|
||||
"Found no files to upload in given location or archive."
|
||||
)
|
||||
)
|
||||
remove_temp()
|
||||
return
|
||||
|
||||
# Clean up directory before upload
|
||||
clean_revpi = helper.cm.call_remote_function("plcuploadclean", default_value=False)
|
||||
if self.cbx_clear.isChecked() and not clean_revpi:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"There was an error deleting the files on the Revolution Pi.\n"
|
||||
"Upload aborted! Please try again."
|
||||
)
|
||||
)
|
||||
remove_temp()
|
||||
return
|
||||
|
||||
plc_program_not_in_upload = True
|
||||
ec = 0
|
||||
|
||||
for file_name in lst_files:
|
||||
|
||||
if file_name == rscfile:
|
||||
# Do not send piCtory configuration
|
||||
continue
|
||||
|
||||
# fixme: Fehlerabfang bei Dateilesen
|
||||
with open(file_name, "rb") as fh:
|
||||
|
||||
# Generate relative file name for transfer
|
||||
if dirselect == "":
|
||||
sendname = os.path.basename(file_name)
|
||||
else:
|
||||
# Append folder name on complete folder transfer to crate it on Revolution Pi
|
||||
sendname = os.path.join(
|
||||
folder_name,
|
||||
file_name.replace(dirselect, "")[1:]
|
||||
)
|
||||
|
||||
# Try to find given plc start program in upload files
|
||||
if sendname == self.dc.get("plcprogram", ""):
|
||||
plc_program_not_in_upload = False
|
||||
|
||||
ustatus = helper.cm.call_remote_function(
|
||||
"plcupload", Binary(gzip.compress(fh.read())), sendname
|
||||
)
|
||||
if ustatus is None:
|
||||
ec = -2
|
||||
break
|
||||
elif not ustatus:
|
||||
ec = -1
|
||||
break
|
||||
|
||||
if ec == 0:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, self.tr("Success"), self.tr(
|
||||
"The PLC program was transferred successfully."
|
||||
)
|
||||
)
|
||||
self.lst_files = helper.cm.call_remote_function("get_filelist", default_value=[])
|
||||
self._load_settings(files_only=True)
|
||||
if plc_program_not_in_upload:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self, self.tr("Information"), self.tr(
|
||||
"Could not find the selected PLC start program in "
|
||||
"uploaded files.\nThis is not an error, if the file "
|
||||
"was already on the Revolution Pi. Check PLC start "
|
||||
"program field"
|
||||
)
|
||||
)
|
||||
|
||||
if self.cbx_pictory.isChecked():
|
||||
if rscfile is not None:
|
||||
self._upload_pictory(rscfile)
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"There is no piCtory configuration in this archive."
|
||||
)
|
||||
)
|
||||
|
||||
elif ec == -1:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"The Revolution Pi could not process some parts of the transmission."
|
||||
)
|
||||
)
|
||||
|
||||
elif ec == -2:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Errors occurred during transmission."
|
||||
)
|
||||
)
|
||||
|
||||
remove_temp()
|
||||
|
||||
# endregion # # # # #
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# region # REGION: Control files
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_pictory_download_pressed(self):
|
||||
"""Download piCtory configuration."""
|
||||
if not helper.cm.connected:
|
||||
return
|
||||
|
||||
diag_save = QtWidgets.QFileDialog(
|
||||
self, self.tr("Save piCtory file..."),
|
||||
os.path.join(
|
||||
helper.cm.program_last_dir_pictory,
|
||||
"{0}.rsc".format(helper.cm.name)
|
||||
),
|
||||
self.tr("piCtory file (*.rsc);;All files (*.*)")
|
||||
)
|
||||
diag_save.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
|
||||
diag_save.setDefaultSuffix("rsc")
|
||||
self.rejected.connect(diag_save.reject)
|
||||
|
||||
if diag_save.exec() != QtWidgets.QFileDialog.AcceptSave or len(diag_save.selectedFiles()) != 1:
|
||||
return
|
||||
|
||||
filename = diag_save.selectedFiles()[0]
|
||||
helper.cm.program_last_dir_pictory = os.path.dirname(filename)
|
||||
bin_buffer = helper.cm.call_remote_function("get_pictoryrsc") # type: Binary
|
||||
if bin_buffer is None:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Could not load piCtory file from Revolution Pi."
|
||||
)
|
||||
)
|
||||
else:
|
||||
fh = open(filename, "wb")
|
||||
fh.write(bin_buffer.data)
|
||||
fh.close()
|
||||
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, self.tr("Success"), self.tr(
|
||||
"piCtory configuration successfully loaded and saved to:\n{0}."
|
||||
).format(filename)
|
||||
)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_pictory_upload_pressed(self):
|
||||
if not helper.cm.connected:
|
||||
return
|
||||
|
||||
diag_open = QtWidgets.QFileDialog(
|
||||
self, self.tr("Upload piCtory file..."),
|
||||
helper.cm.program_last_pictory_file,
|
||||
self.tr("piCtory file (*.rsc);;All files (*.*)")
|
||||
)
|
||||
diag_open.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
|
||||
diag_open.setFileMode(QtWidgets.QFileDialog.ExistingFile)
|
||||
diag_open.setDefaultSuffix("rsc")
|
||||
self.rejected.connect(diag_open.reject)
|
||||
|
||||
if diag_open.exec() != QtWidgets.QFileDialog.AcceptSave or len(diag_open.selectedFiles()) != 1:
|
||||
return
|
||||
|
||||
self._upload_pictory(diag_open.selectedFiles()[0])
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_procimg_download_pressed(self):
|
||||
"""Download process image."""
|
||||
if not helper.cm.connected:
|
||||
return
|
||||
|
||||
diag_save = QtWidgets.QFileDialog(
|
||||
self,
|
||||
self.tr("Save piControl file..."),
|
||||
os.path.join(
|
||||
helper.cm.program_last_dir_picontrol,
|
||||
"{0}.img".format(helper.cm.name)
|
||||
),
|
||||
self.tr("Process image file (*.img);;All files (*.*)")
|
||||
)
|
||||
diag_save.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
|
||||
diag_save.setDefaultSuffix("img")
|
||||
self.rejected.connect(diag_save.reject)
|
||||
|
||||
if diag_save.exec() != QtWidgets.QFileDialog.AcceptSave or len(diag_save.selectedFiles()) != 1:
|
||||
return
|
||||
|
||||
filename = diag_save.selectedFiles()[0]
|
||||
helper.cm.program_last_dir_picontrol = os.path.dirname(filename)
|
||||
bin_buffer = helper.cm.call_remote_function("get_procimg") # type: Binary
|
||||
|
||||
if bin_buffer is None:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, self.tr("Error"), self.tr(
|
||||
"Could not load process image from Revolution Pi."
|
||||
)
|
||||
)
|
||||
else:
|
||||
fh = open(filename, "wb")
|
||||
fh.write(bin_buffer.data)
|
||||
fh.close()
|
||||
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, self.tr("Success"), self.tr(
|
||||
"Process image successfully loaded and saved to:\n{0}."
|
||||
).format(filename)
|
||||
)
|
||||
|
||||
# endregion # # # # #
|
||||
122
src/revpicommander/simulator.py
Normal file
122
src/revpicommander/simulator.py
Normal file
@@ -0,0 +1,122 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Simulator for piControl."""
|
||||
__author__ = "Sven Sager"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
from os import W_OK, access
|
||||
from os.path import basename, dirname, exists, join
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from . import helper
|
||||
from .ui.simulator_ui import Ui_diag_simulator
|
||||
|
||||
|
||||
class Simulator(QtWidgets.QDialog, Ui_diag_simulator):
|
||||
"""
|
||||
This is a configuration dialog for the simulator of piControl. The
|
||||
selected values will be saved in QSettings section 'simulator' and can be
|
||||
accessed by simulator starting classes.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(Simulator, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
self.clean_procimg = False
|
||||
self.max_items = 5
|
||||
|
||||
self.cbb_history.addItem("", "")
|
||||
lst_configrsc = helper.settings.value("simulator/history_configrsc", [], list)
|
||||
lst_procimg = helper.settings.value("simulator/history_procimg", [], list)
|
||||
for i in range(len(lst_configrsc)):
|
||||
self.cbb_history.addItem(lst_configrsc[i], lst_procimg[i])
|
||||
|
||||
self.cbx_stop_remove.setChecked(helper.settings.value("simulator/stop_remove", False, bool))
|
||||
self.rb_restart_pictory.setChecked(helper.settings.value("simulator/restart_pictory", False, bool))
|
||||
self.rb_restart_zero.setChecked(helper.settings.value("simulator/restart_zero", False, bool))
|
||||
|
||||
self.btn_start_pictory.setEnabled(False)
|
||||
self.btn_start_empty.setEnabled(False)
|
||||
self.btn_start_nochange.setEnabled(False)
|
||||
|
||||
self.txt_configrsc.textChanged.connect(self.on_txt_textChanged)
|
||||
self.txt_procimg.textChanged.connect(self.on_txt_textChanged)
|
||||
|
||||
def _save_gui(self) -> None:
|
||||
helper.settings.setValue("simulator/stop_remove", self.cbx_stop_remove.isChecked())
|
||||
helper.settings.setValue("simulator/restart_pictory", self.rb_restart_pictory.isChecked())
|
||||
helper.settings.setValue("simulator/restart_zero", self.rb_restart_zero.isChecked())
|
||||
|
||||
def accept(self) -> None:
|
||||
self.cbb_history.removeItem(0)
|
||||
if self.cbb_history.findText(self.txt_configrsc.text()) == -1:
|
||||
self.cbb_history.addItem(self.txt_configrsc.text(), self.txt_procimg.text())
|
||||
if self.cbb_history.count() > self.max_items:
|
||||
self.cbb_history.removeItem(self.max_items)
|
||||
|
||||
helper.settings.setValue("simulator/configrsc", self.txt_configrsc.text())
|
||||
helper.settings.setValue("simulator/procimg", self.txt_procimg.text())
|
||||
self._save_gui()
|
||||
|
||||
lst_configrsc = []
|
||||
lst_procimg = []
|
||||
for i in range(self.cbb_history.count()):
|
||||
lst_configrsc.append(self.cbb_history.itemText(i))
|
||||
lst_procimg.append(self.cbb_history.itemData(i))
|
||||
helper.settings.setValue("simulator/history_configrsc", lst_configrsc)
|
||||
helper.settings.setValue("simulator/history_procimg", lst_procimg)
|
||||
|
||||
self.clean_procimg = self.sender() is self.btn_start_empty
|
||||
|
||||
super(Simulator, self).accept()
|
||||
|
||||
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||
self._save_gui()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def on_btn_configrsc_clicked(self) -> None:
|
||||
diag_open = QtWidgets.QFileDialog(
|
||||
self, self.tr("Select downloaded piCtory file..."),
|
||||
helper.settings.value("simulator/last_dir", ".", str),
|
||||
self.tr("piCtory file (*.rsc);;All files (*.*)")
|
||||
)
|
||||
diag_open.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
|
||||
diag_open.setFileMode(QtWidgets.QFileDialog.ExistingFile)
|
||||
diag_open.setDefaultSuffix("rsc")
|
||||
|
||||
if diag_open.exec() != QtWidgets.QFileDialog.AcceptSave or len(diag_open.selectedFiles()) != 1:
|
||||
diag_open.deleteLater()
|
||||
return
|
||||
|
||||
configrsc_file = diag_open.selectedFiles()[0]
|
||||
dir_name = dirname(configrsc_file)
|
||||
procimg_file = join(dir_name, "{0}.img".format(basename(configrsc_file).rsplit(".", maxsplit=1)[0]))
|
||||
self.txt_configrsc.setText(configrsc_file)
|
||||
self.txt_procimg.setText(procimg_file)
|
||||
|
||||
helper.settings.setValue("simulator/last_dir", dir_name)
|
||||
diag_open.deleteLater()
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def on_cbb_history_currentIndexChanged(self, index: int) -> None:
|
||||
if index == 0:
|
||||
return
|
||||
self.txt_configrsc.setText(self.cbb_history.itemText(index))
|
||||
self.txt_procimg.setText(self.cbb_history.itemData(index))
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def on_txt_textChanged(self, text: str) -> None:
|
||||
configrsc_file = self.txt_configrsc.text()
|
||||
procimg_file = self.txt_procimg.text()
|
||||
if configrsc_file and procimg_file:
|
||||
file_access = access(procimg_file, W_OK) if exists(procimg_file) else access(dirname(procimg_file), W_OK)
|
||||
self.txt_info.setPlainText("configrsc=\"{0}\", procimg=\"{1}\"".format(configrsc_file, procimg_file))
|
||||
self.btn_start_pictory.setEnabled(file_access)
|
||||
self.btn_start_empty.setEnabled(file_access)
|
||||
self.btn_start_nochange.setEnabled(file_access and exists(procimg_file))
|
||||
else:
|
||||
self.txt_info.clear()
|
||||
self.btn_start_pictory.setEnabled(False)
|
||||
self.btn_start_empty.setEnabled(False)
|
||||
self.btn_start_nochange.setEnabled(False)
|
||||
162
src/revpicommander/ui/aclmanager_ui.py
Normal file
162
src/revpicommander/ui/aclmanager_ui.py
Normal file
@@ -0,0 +1,162 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'aclmanager.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_diag_aclmanager(object):
|
||||
def setupUi(self, diag_aclmanager):
|
||||
diag_aclmanager.setObjectName("diag_aclmanager")
|
||||
diag_aclmanager.resize(454, 572)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(diag_aclmanager)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.gb_acls = QtWidgets.QGroupBox(diag_aclmanager)
|
||||
self.gb_acls.setObjectName("gb_acls")
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.gb_acls)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.tb_acls = QtWidgets.QTableWidget(self.gb_acls)
|
||||
self.tb_acls.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
||||
self.tb_acls.setTabKeyNavigation(False)
|
||||
self.tb_acls.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
self.tb_acls.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.tb_acls.setWordWrap(False)
|
||||
self.tb_acls.setCornerButtonEnabled(False)
|
||||
self.tb_acls.setObjectName("tb_acls")
|
||||
self.tb_acls.setColumnCount(2)
|
||||
self.tb_acls.setRowCount(0)
|
||||
item = QtWidgets.QTableWidgetItem()
|
||||
self.tb_acls.setHorizontalHeaderItem(0, item)
|
||||
item = QtWidgets.QTableWidgetItem()
|
||||
self.tb_acls.setHorizontalHeaderItem(1, item)
|
||||
self.tb_acls.horizontalHeader().setHighlightSections(False)
|
||||
self.tb_acls.horizontalHeader().setStretchLastSection(True)
|
||||
self.tb_acls.verticalHeader().setVisible(False)
|
||||
self.verticalLayout_2.addWidget(self.tb_acls)
|
||||
self.hl_acls = QtWidgets.QHBoxLayout()
|
||||
self.hl_acls.setObjectName("hl_acls")
|
||||
self.btn_edit = QtWidgets.QPushButton(self.gb_acls)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.btn_edit.sizePolicy().hasHeightForWidth())
|
||||
self.btn_edit.setSizePolicy(sizePolicy)
|
||||
self.btn_edit.setObjectName("btn_edit")
|
||||
self.hl_acls.addWidget(self.btn_edit)
|
||||
self.btn_remove = QtWidgets.QPushButton(self.gb_acls)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.btn_remove.sizePolicy().hasHeightForWidth())
|
||||
self.btn_remove.setSizePolicy(sizePolicy)
|
||||
self.btn_remove.setObjectName("btn_remove")
|
||||
self.hl_acls.addWidget(self.btn_remove)
|
||||
self.verticalLayout_2.addLayout(self.hl_acls)
|
||||
self.verticalLayout.addWidget(self.gb_acls)
|
||||
self.gb_edit = QtWidgets.QGroupBox(diag_aclmanager)
|
||||
self.gb_edit.setObjectName("gb_edit")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.gb_edit)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.btn_clear = QtWidgets.QPushButton(self.gb_edit)
|
||||
self.btn_clear.setObjectName("btn_clear")
|
||||
self.gridLayout_2.addWidget(self.btn_clear, 1, 0, 1, 1)
|
||||
self.btn_add = QtWidgets.QPushButton(self.gb_edit)
|
||||
self.btn_add.setObjectName("btn_add")
|
||||
self.gridLayout_2.addWidget(self.btn_add, 1, 1, 1, 1)
|
||||
self.fl_edit = QtWidgets.QFormLayout()
|
||||
self.fl_edit.setObjectName("fl_edit")
|
||||
self.lbl_ip = QtWidgets.QLabel(self.gb_edit)
|
||||
self.lbl_ip.setObjectName("lbl_ip")
|
||||
self.fl_edit.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lbl_ip)
|
||||
self.lbl_level = QtWidgets.QLabel(self.gb_edit)
|
||||
self.lbl_level.setObjectName("lbl_level")
|
||||
self.fl_edit.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lbl_level)
|
||||
self.cbb_level = QtWidgets.QComboBox(self.gb_edit)
|
||||
self.cbb_level.setObjectName("cbb_level")
|
||||
self.fl_edit.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.cbb_level)
|
||||
self.hl_ip = QtWidgets.QHBoxLayout()
|
||||
self.hl_ip.setObjectName("hl_ip")
|
||||
self.txt_ip_a = QtWidgets.QLineEdit(self.gb_edit)
|
||||
self.txt_ip_a.setMaxLength(3)
|
||||
self.txt_ip_a.setObjectName("txt_ip_a")
|
||||
self.hl_ip.addWidget(self.txt_ip_a)
|
||||
self.lbl_ip_a = QtWidgets.QLabel(self.gb_edit)
|
||||
self.lbl_ip_a.setObjectName("lbl_ip_a")
|
||||
self.hl_ip.addWidget(self.lbl_ip_a)
|
||||
self.txt_ip_b = QtWidgets.QLineEdit(self.gb_edit)
|
||||
self.txt_ip_b.setMaxLength(3)
|
||||
self.txt_ip_b.setObjectName("txt_ip_b")
|
||||
self.hl_ip.addWidget(self.txt_ip_b)
|
||||
self.lbl_ip_b = QtWidgets.QLabel(self.gb_edit)
|
||||
self.lbl_ip_b.setObjectName("lbl_ip_b")
|
||||
self.hl_ip.addWidget(self.lbl_ip_b)
|
||||
self.txt_ip_c = QtWidgets.QLineEdit(self.gb_edit)
|
||||
self.txt_ip_c.setMaxLength(3)
|
||||
self.txt_ip_c.setObjectName("txt_ip_c")
|
||||
self.hl_ip.addWidget(self.txt_ip_c)
|
||||
self.lbl_ip_c = QtWidgets.QLabel(self.gb_edit)
|
||||
self.lbl_ip_c.setObjectName("lbl_ip_c")
|
||||
self.hl_ip.addWidget(self.lbl_ip_c)
|
||||
self.txt_ip_d = QtWidgets.QLineEdit(self.gb_edit)
|
||||
self.txt_ip_d.setMaxLength(3)
|
||||
self.txt_ip_d.setObjectName("txt_ip_d")
|
||||
self.hl_ip.addWidget(self.txt_ip_d)
|
||||
self.fl_edit.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.hl_ip)
|
||||
self.gridLayout_2.addLayout(self.fl_edit, 0, 0, 1, 2)
|
||||
self.verticalLayout.addWidget(self.gb_edit)
|
||||
self.lbl_level_info = QtWidgets.QLabel(diag_aclmanager)
|
||||
self.lbl_level_info.setText("")
|
||||
self.lbl_level_info.setObjectName("lbl_level_info")
|
||||
self.verticalLayout.addWidget(self.lbl_level_info)
|
||||
self.btn_box = QtWidgets.QDialogButtonBox(diag_aclmanager)
|
||||
self.btn_box.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
|
||||
self.btn_box.setObjectName("btn_box")
|
||||
self.verticalLayout.addWidget(self.btn_box)
|
||||
|
||||
self.retranslateUi(diag_aclmanager)
|
||||
self.btn_box.accepted.connect(diag_aclmanager.accept) # type: ignore
|
||||
self.btn_box.rejected.connect(diag_aclmanager.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(diag_aclmanager)
|
||||
diag_aclmanager.setTabOrder(self.tb_acls, self.btn_edit)
|
||||
diag_aclmanager.setTabOrder(self.btn_edit, self.btn_remove)
|
||||
diag_aclmanager.setTabOrder(self.btn_remove, self.txt_ip_a)
|
||||
diag_aclmanager.setTabOrder(self.txt_ip_a, self.txt_ip_b)
|
||||
diag_aclmanager.setTabOrder(self.txt_ip_b, self.txt_ip_c)
|
||||
diag_aclmanager.setTabOrder(self.txt_ip_c, self.txt_ip_d)
|
||||
diag_aclmanager.setTabOrder(self.txt_ip_d, self.cbb_level)
|
||||
|
||||
def retranslateUi(self, diag_aclmanager):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
diag_aclmanager.setWindowTitle(_translate("diag_aclmanager", "IP access control list"))
|
||||
self.gb_acls.setTitle(_translate("diag_aclmanager", "Existing ACLs"))
|
||||
item = self.tb_acls.horizontalHeaderItem(0)
|
||||
item.setText(_translate("diag_aclmanager", "IP Address"))
|
||||
item = self.tb_acls.horizontalHeaderItem(1)
|
||||
item.setText(_translate("diag_aclmanager", "Access Level"))
|
||||
self.btn_edit.setText(_translate("diag_aclmanager", "&Edit"))
|
||||
self.btn_remove.setText(_translate("diag_aclmanager", "&Remove"))
|
||||
self.gb_edit.setTitle(_translate("diag_aclmanager", "Add / Edit access entry"))
|
||||
self.btn_clear.setText(_translate("diag_aclmanager", "Clear fields"))
|
||||
self.btn_add.setText(_translate("diag_aclmanager", "&Save entry"))
|
||||
self.lbl_ip.setText(_translate("diag_aclmanager", "IP address:"))
|
||||
self.lbl_level.setText(_translate("diag_aclmanager", "Access level:"))
|
||||
self.lbl_ip_a.setText(_translate("diag_aclmanager", "."))
|
||||
self.lbl_ip_b.setText(_translate("diag_aclmanager", "."))
|
||||
self.lbl_ip_c.setText(_translate("diag_aclmanager", "."))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
diag_aclmanager = QtWidgets.QDialog()
|
||||
ui = Ui_diag_aclmanager()
|
||||
ui.setupUi(diag_aclmanager)
|
||||
diag_aclmanager.show()
|
||||
sys.exit(app.exec_())
|
||||
104
src/revpicommander/ui/avahisearch_ui.py
Normal file
104
src/revpicommander/ui/avahisearch_ui.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'avahisearch.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_diag_search(object):
|
||||
def setupUi(self, diag_search):
|
||||
diag_search.setObjectName("diag_search")
|
||||
diag_search.resize(480, 360)
|
||||
self.gridLayout = QtWidgets.QGridLayout(diag_search)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.hl_header = QtWidgets.QHBoxLayout()
|
||||
self.hl_header.setObjectName("hl_header")
|
||||
self.lbl_search = QtWidgets.QLabel(diag_search)
|
||||
self.lbl_search.setObjectName("lbl_search")
|
||||
self.hl_header.addWidget(self.lbl_search)
|
||||
self.btn_restart = QtWidgets.QPushButton(diag_search)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.btn_restart.sizePolicy().hasHeightForWidth())
|
||||
self.btn_restart.setSizePolicy(sizePolicy)
|
||||
self.btn_restart.setText("")
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(":/action/ico/reload.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.btn_restart.setIcon(icon)
|
||||
self.btn_restart.setIconSize(QtCore.QSize(24, 24))
|
||||
self.btn_restart.setObjectName("btn_restart")
|
||||
self.hl_header.addWidget(self.btn_restart)
|
||||
self.gridLayout.addLayout(self.hl_header, 0, 0, 1, 2)
|
||||
self.tb_revpi = QtWidgets.QTableWidget(diag_search)
|
||||
self.tb_revpi.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
||||
self.tb_revpi.setTabKeyNavigation(False)
|
||||
self.tb_revpi.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
||||
self.tb_revpi.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.tb_revpi.setWordWrap(False)
|
||||
self.tb_revpi.setCornerButtonEnabled(False)
|
||||
self.tb_revpi.setObjectName("tb_revpi")
|
||||
self.tb_revpi.setColumnCount(2)
|
||||
self.tb_revpi.setRowCount(0)
|
||||
item = QtWidgets.QTableWidgetItem()
|
||||
self.tb_revpi.setHorizontalHeaderItem(0, item)
|
||||
item = QtWidgets.QTableWidgetItem()
|
||||
self.tb_revpi.setHorizontalHeaderItem(1, item)
|
||||
self.tb_revpi.horizontalHeader().setHighlightSections(False)
|
||||
self.tb_revpi.horizontalHeader().setStretchLastSection(True)
|
||||
self.tb_revpi.verticalHeader().setVisible(False)
|
||||
self.gridLayout.addWidget(self.tb_revpi, 1, 0, 1, 2)
|
||||
self.btn_connect = QtWidgets.QPushButton(diag_search)
|
||||
self.btn_connect.setObjectName("btn_connect")
|
||||
self.gridLayout.addWidget(self.btn_connect, 2, 0, 1, 1)
|
||||
self.btn_save = QtWidgets.QPushButton(diag_search)
|
||||
self.btn_save.setObjectName("btn_save")
|
||||
self.gridLayout.addWidget(self.btn_save, 2, 1, 1, 1)
|
||||
self.btn_box = QtWidgets.QDialogButtonBox(diag_search)
|
||||
self.btn_box.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Close)
|
||||
self.btn_box.setObjectName("btn_box")
|
||||
self.gridLayout.addWidget(self.btn_box, 3, 0, 1, 2)
|
||||
self.act_copy_host = QtWidgets.QAction(diag_search)
|
||||
self.act_copy_host.setObjectName("act_copy_host")
|
||||
self.act_copy_ip = QtWidgets.QAction(diag_search)
|
||||
self.act_copy_ip.setObjectName("act_copy_ip")
|
||||
self.act_open_pictory = QtWidgets.QAction(diag_search)
|
||||
self.act_open_pictory.setObjectName("act_open_pictory")
|
||||
|
||||
self.retranslateUi(diag_search)
|
||||
self.btn_box.rejected.connect(diag_search.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(diag_search)
|
||||
|
||||
def retranslateUi(self, diag_search):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
diag_search.setWindowTitle(_translate("diag_search", "Search Revolution Pi devices"))
|
||||
self.lbl_search.setText(_translate("diag_search", "Searching for Revolution Pi devices in your network..."))
|
||||
self.btn_restart.setToolTip(_translate("diag_search", "Restart search"))
|
||||
self.tb_revpi.setSortingEnabled(True)
|
||||
item = self.tb_revpi.horizontalHeaderItem(0)
|
||||
item.setText(_translate("diag_search", "Zero-conf name"))
|
||||
item = self.tb_revpi.horizontalHeaderItem(1)
|
||||
item.setText(_translate("diag_search", "IP address"))
|
||||
self.btn_connect.setText(_translate("diag_search", "&Connect to Revolution Pi"))
|
||||
self.btn_save.setText(_translate("diag_search", "&Save connection"))
|
||||
self.act_copy_host.setText(_translate("diag_search", "Copy host name"))
|
||||
self.act_copy_ip.setText(_translate("diag_search", "Copy IP address"))
|
||||
self.act_open_pictory.setText(_translate("diag_search", "Open piCtory"))
|
||||
from . import ressources_rc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
diag_search = QtWidgets.QDialog()
|
||||
ui = Ui_diag_search()
|
||||
ui.setupUi(diag_search)
|
||||
diag_search.show()
|
||||
sys.exit(app.exec_())
|
||||
51
src/revpicommander/ui/backgroundworker_ui.py
Normal file
51
src/revpicommander/ui/backgroundworker_ui.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'backgroundworker.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_diag_backgroundworker(object):
|
||||
def setupUi(self, diag_backgroundworker):
|
||||
diag_backgroundworker.setObjectName("diag_backgroundworker")
|
||||
diag_backgroundworker.resize(418, 97)
|
||||
diag_backgroundworker.setModal(True)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(diag_backgroundworker)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.lbl_status = QtWidgets.QLabel(diag_backgroundworker)
|
||||
self.lbl_status.setText("Status message...")
|
||||
self.lbl_status.setObjectName("lbl_status")
|
||||
self.verticalLayout.addWidget(self.lbl_status)
|
||||
self.pgb_status = QtWidgets.QProgressBar(diag_backgroundworker)
|
||||
self.pgb_status.setMinimumSize(QtCore.QSize(400, 0))
|
||||
self.pgb_status.setObjectName("pgb_status")
|
||||
self.verticalLayout.addWidget(self.pgb_status)
|
||||
self.btn_box = QtWidgets.QDialogButtonBox(diag_backgroundworker)
|
||||
self.btn_box.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel)
|
||||
self.btn_box.setCenterButtons(True)
|
||||
self.btn_box.setObjectName("btn_box")
|
||||
self.verticalLayout.addWidget(self.btn_box)
|
||||
|
||||
self.retranslateUi(diag_backgroundworker)
|
||||
QtCore.QMetaObject.connectSlotsByName(diag_backgroundworker)
|
||||
|
||||
def retranslateUi(self, diag_backgroundworker):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
diag_backgroundworker.setWindowTitle(_translate("diag_backgroundworker", "File transfer..."))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
diag_backgroundworker = QtWidgets.QDialog()
|
||||
ui = Ui_diag_backgroundworker()
|
||||
ui.setupUi(diag_backgroundworker)
|
||||
diag_backgroundworker.show()
|
||||
sys.exit(app.exec_())
|
||||
85
src/revpicommander/ui/debugcontrol_ui.py
Normal file
85
src/revpicommander/ui/debugcontrol_ui.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'debugcontrol.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_wid_debugcontrol(object):
|
||||
def setupUi(self, wid_debugcontrol):
|
||||
wid_debugcontrol.setObjectName("wid_debugcontrol")
|
||||
wid_debugcontrol.resize(402, 214)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(wid_debugcontrol.sizePolicy().hasHeightForWidth())
|
||||
wid_debugcontrol.setSizePolicy(sizePolicy)
|
||||
self.gridLayout = QtWidgets.QGridLayout(wid_debugcontrol)
|
||||
self.gridLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.gb_devices = QtWidgets.QGroupBox(wid_debugcontrol)
|
||||
self.gb_devices.setObjectName("gb_devices")
|
||||
self.vl_devices = QtWidgets.QVBoxLayout(self.gb_devices)
|
||||
self.vl_devices.setObjectName("vl_devices")
|
||||
self.gridLayout.addWidget(self.gb_devices, 0, 0, 1, 1)
|
||||
self.cbx_stay_on_top = QtWidgets.QCheckBox(wid_debugcontrol)
|
||||
self.cbx_stay_on_top.setObjectName("cbx_stay_on_top")
|
||||
self.gridLayout.addWidget(self.cbx_stay_on_top, 1, 0, 1, 1)
|
||||
self.gb_control = QtWidgets.QGroupBox(wid_debugcontrol)
|
||||
self.gb_control.setObjectName("gb_control")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.gb_control)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.btn_read_io = QtWidgets.QPushButton(self.gb_control)
|
||||
self.btn_read_io.setObjectName("btn_read_io")
|
||||
self.verticalLayout.addWidget(self.btn_read_io)
|
||||
self.btn_refresh_io = QtWidgets.QPushButton(self.gb_control)
|
||||
self.btn_refresh_io.setObjectName("btn_refresh_io")
|
||||
self.verticalLayout.addWidget(self.btn_refresh_io)
|
||||
self.btn_write_o = QtWidgets.QPushButton(self.gb_control)
|
||||
self.btn_write_o.setObjectName("btn_write_o")
|
||||
self.verticalLayout.addWidget(self.btn_write_o)
|
||||
self.cbx_refresh = QtWidgets.QCheckBox(self.gb_control)
|
||||
self.cbx_refresh.setObjectName("cbx_refresh")
|
||||
self.verticalLayout.addWidget(self.cbx_refresh)
|
||||
self.cbx_write = QtWidgets.QCheckBox(self.gb_control)
|
||||
self.cbx_write.setObjectName("cbx_write")
|
||||
self.verticalLayout.addWidget(self.cbx_write)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout.addItem(spacerItem)
|
||||
self.gridLayout.addWidget(self.gb_control, 0, 1, 2, 1)
|
||||
|
||||
self.retranslateUi(wid_debugcontrol)
|
||||
QtCore.QMetaObject.connectSlotsByName(wid_debugcontrol)
|
||||
|
||||
def retranslateUi(self, wid_debugcontrol):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
self.gb_devices.setTitle(_translate("wid_debugcontrol", "Revolution Pi devices"))
|
||||
self.cbx_stay_on_top.setText(_translate("wid_debugcontrol", "Open to stay on top"))
|
||||
self.gb_control.setTitle(_translate("wid_debugcontrol", "IO Control"))
|
||||
self.btn_read_io.setToolTip(_translate("wid_debugcontrol", "Read all IO values and discard local changes (F4)"))
|
||||
self.btn_read_io.setText(_translate("wid_debugcontrol", "Read &all IO values"))
|
||||
self.btn_read_io.setShortcut(_translate("wid_debugcontrol", "F4"))
|
||||
self.btn_refresh_io.setToolTip(_translate("wid_debugcontrol", "Refresh all IO values which are locally not changed (F5)"))
|
||||
self.btn_refresh_io.setText(_translate("wid_debugcontrol", "&Refresh unchanged IOs"))
|
||||
self.btn_refresh_io.setShortcut(_translate("wid_debugcontrol", "F5"))
|
||||
self.btn_write_o.setToolTip(_translate("wid_debugcontrol", "Write locally changed output values to process image (F6)"))
|
||||
self.btn_write_o.setText(_translate("wid_debugcontrol", "&Write changed outputs"))
|
||||
self.btn_write_o.setShortcut(_translate("wid_debugcontrol", "F6"))
|
||||
self.cbx_refresh.setText(_translate("wid_debugcontrol", "&Auto refresh values"))
|
||||
self.cbx_write.setText(_translate("wid_debugcontrol", "and write outputs"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
wid_debugcontrol = QtWidgets.QWidget()
|
||||
ui = Ui_wid_debugcontrol()
|
||||
ui.setupUi(wid_debugcontrol)
|
||||
wid_debugcontrol.show()
|
||||
sys.exit(app.exec_())
|
||||
77
src/revpicommander/ui/debugios_ui.py
Normal file
77
src/revpicommander/ui/debugios_ui.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'debugios.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_win_debugios(object):
|
||||
def setupUi(self, win_debugios):
|
||||
win_debugios.setObjectName("win_debugios")
|
||||
win_debugios.resize(434, 343)
|
||||
self.centralwidget = QtWidgets.QWidget(win_debugios)
|
||||
self.centralwidget.setObjectName("centralwidget")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.gb_io = QtWidgets.QGroupBox(self.centralwidget)
|
||||
self.gb_io.setObjectName("gb_io")
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.gb_io)
|
||||
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.splitter = QtWidgets.QSplitter(self.gb_io)
|
||||
self.splitter.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.splitter.setChildrenCollapsible(False)
|
||||
self.splitter.setObjectName("splitter")
|
||||
self.sca_inp = QtWidgets.QScrollArea(self.splitter)
|
||||
self.sca_inp.setLineWidth(0)
|
||||
self.sca_inp.setWidgetResizable(True)
|
||||
self.sca_inp.setObjectName("sca_inp")
|
||||
self.saw_inp = QtWidgets.QWidget()
|
||||
self.saw_inp.setGeometry(QtCore.QRect(0, 0, 201, 275))
|
||||
self.saw_inp.setObjectName("saw_inp")
|
||||
self.form_inp = QtWidgets.QFormLayout(self.saw_inp)
|
||||
self.form_inp.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.form_inp.setContentsMargins(-1, 6, -1, 6)
|
||||
self.form_inp.setObjectName("form_inp")
|
||||
self.sca_inp.setWidget(self.saw_inp)
|
||||
self.sca_out = QtWidgets.QScrollArea(self.splitter)
|
||||
self.sca_out.setLineWidth(0)
|
||||
self.sca_out.setWidgetResizable(True)
|
||||
self.sca_out.setObjectName("sca_out")
|
||||
self.saw_out = QtWidgets.QWidget()
|
||||
self.saw_out.setGeometry(QtCore.QRect(0, 0, 201, 275))
|
||||
self.saw_out.setObjectName("saw_out")
|
||||
self.form_out = QtWidgets.QFormLayout(self.saw_out)
|
||||
self.form_out.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.form_out.setContentsMargins(-1, 6, -1, 6)
|
||||
self.form_out.setObjectName("form_out")
|
||||
self.sca_out.setWidget(self.saw_out)
|
||||
self.verticalLayout_2.addWidget(self.splitter)
|
||||
self.verticalLayout.addWidget(self.gb_io)
|
||||
win_debugios.setCentralWidget(self.centralwidget)
|
||||
self.stat_bar = QtWidgets.QStatusBar(win_debugios)
|
||||
self.stat_bar.setObjectName("stat_bar")
|
||||
win_debugios.setStatusBar(self.stat_bar)
|
||||
|
||||
self.retranslateUi(win_debugios)
|
||||
QtCore.QMetaObject.connectSlotsByName(win_debugios)
|
||||
|
||||
def retranslateUi(self, win_debugios):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
self.gb_io.setTitle(_translate("win_debugios", "{0}: Inputs | Outputs"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
win_debugios = QtWidgets.QMainWindow()
|
||||
ui = Ui_win_debugios()
|
||||
ui.setupUi(win_debugios)
|
||||
win_debugios.show()
|
||||
sys.exit(app.exec_())
|
||||
184
src/revpicommander/ui/files_ui.py
Normal file
184
src/revpicommander/ui/files_ui.py
Normal file
@@ -0,0 +1,184 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'files.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_win_files(object):
|
||||
def setupUi(self, win_files):
|
||||
win_files.setObjectName("win_files")
|
||||
win_files.resize(725, 519)
|
||||
self.centralwidget = QtWidgets.QWidget(win_files)
|
||||
self.centralwidget.setObjectName("centralwidget")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.splitter = QtWidgets.QSplitter(self.centralwidget)
|
||||
self.splitter.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.splitter.setChildrenCollapsible(False)
|
||||
self.splitter.setObjectName("splitter")
|
||||
self.verticalLayoutWidget = QtWidgets.QWidget(self.splitter)
|
||||
self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
|
||||
self.vl_local = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
|
||||
self.vl_local.setContentsMargins(0, 0, 0, 0)
|
||||
self.vl_local.setObjectName("vl_local")
|
||||
self.gb_select_local = QtWidgets.QGroupBox(self.verticalLayoutWidget)
|
||||
self.gb_select_local.setObjectName("gb_select_local")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.gb_select_local)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.lbl_select_local = QtWidgets.QLabel(self.gb_select_local)
|
||||
self.lbl_select_local.setObjectName("lbl_select_local")
|
||||
self.gridLayout_2.addWidget(self.lbl_select_local, 0, 0, 1, 1)
|
||||
self.btn_select_local = QtWidgets.QPushButton(self.gb_select_local)
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(":/action/ico/folder-open.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.btn_select_local.setIcon(icon)
|
||||
self.btn_select_local.setIconSize(QtCore.QSize(24, 24))
|
||||
self.btn_select_local.setAutoDefault(False)
|
||||
self.btn_select_local.setObjectName("btn_select_local")
|
||||
self.gridLayout_2.addWidget(self.btn_select_local, 0, 1, 1, 1)
|
||||
self.btn_refresh_local = QtWidgets.QPushButton(self.gb_select_local)
|
||||
icon1 = QtGui.QIcon()
|
||||
icon1.addPixmap(QtGui.QPixmap(":/action/ico/refresh.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.btn_refresh_local.setIcon(icon1)
|
||||
self.btn_refresh_local.setIconSize(QtCore.QSize(24, 24))
|
||||
self.btn_refresh_local.setObjectName("btn_refresh_local")
|
||||
self.gridLayout_2.addWidget(self.btn_refresh_local, 0, 2, 1, 1)
|
||||
self.lbl_path_local = QtWidgets.QLabel(self.gb_select_local)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
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)
|
||||
self.vl_local.addWidget(self.gb_select_local)
|
||||
self.hl_revpi_2 = QtWidgets.QHBoxLayout()
|
||||
self.hl_revpi_2.setObjectName("hl_revpi_2")
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.hl_revpi_2.addItem(spacerItem)
|
||||
self.btn_to_right = QtWidgets.QPushButton(self.verticalLayoutWidget)
|
||||
self.btn_to_right.setText("")
|
||||
icon2 = QtGui.QIcon()
|
||||
icon2.addPixmap(QtGui.QPixmap(":/action/ico/arrow-right.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.btn_to_right.setIcon(icon2)
|
||||
self.btn_to_right.setIconSize(QtCore.QSize(24, 24))
|
||||
self.btn_to_right.setAutoDefault(False)
|
||||
self.btn_to_right.setObjectName("btn_to_right")
|
||||
self.hl_revpi_2.addWidget(self.btn_to_right)
|
||||
self.vl_local.addLayout(self.hl_revpi_2)
|
||||
self.tree_files_local = QtWidgets.QTreeWidget(self.verticalLayoutWidget)
|
||||
self.tree_files_local.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
||||
self.tree_files_local.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
||||
self.tree_files_local.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.tree_files_local.setIconSize(QtCore.QSize(24, 24))
|
||||
self.tree_files_local.setObjectName("tree_files_local")
|
||||
self.tree_files_local.headerItem().setText(0, "1")
|
||||
self.tree_files_local.header().setVisible(False)
|
||||
self.vl_local.addWidget(self.tree_files_local)
|
||||
self.gridLayoutWidget_2 = QtWidgets.QWidget(self.splitter)
|
||||
self.gridLayoutWidget_2.setObjectName("gridLayoutWidget_2")
|
||||
self.vl_revpi = QtWidgets.QVBoxLayout(self.gridLayoutWidget_2)
|
||||
self.vl_revpi.setContentsMargins(0, 0, 0, 0)
|
||||
self.vl_revpi.setObjectName("vl_revpi")
|
||||
self.gb_select_revpi = QtWidgets.QGroupBox(self.gridLayoutWidget_2)
|
||||
self.gb_select_revpi.setObjectName("gb_select_revpi")
|
||||
self.gridLayout_3 = QtWidgets.QGridLayout(self.gb_select_revpi)
|
||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
||||
self.lbl_path_revpi = QtWidgets.QLabel(self.gb_select_revpi)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
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)
|
||||
self.lbl_select_revpi.setObjectName("lbl_select_revpi")
|
||||
self.gridLayout_3.addWidget(self.lbl_select_revpi, 0, 0, 1, 1)
|
||||
self.btn_refresh_revpi = QtWidgets.QPushButton(self.gb_select_revpi)
|
||||
self.btn_refresh_revpi.setIcon(icon1)
|
||||
self.btn_refresh_revpi.setIconSize(QtCore.QSize(24, 24))
|
||||
self.btn_refresh_revpi.setObjectName("btn_refresh_revpi")
|
||||
self.gridLayout_3.addWidget(self.btn_refresh_revpi, 0, 1, 1, 1)
|
||||
self.gridLayout_3.setColumnStretch(0, 1)
|
||||
self.vl_revpi.addWidget(self.gb_select_revpi)
|
||||
self.hl_revpi = QtWidgets.QHBoxLayout()
|
||||
self.hl_revpi.setObjectName("hl_revpi")
|
||||
self.btn_to_left = QtWidgets.QPushButton(self.gridLayoutWidget_2)
|
||||
self.btn_to_left.setText("")
|
||||
icon3 = QtGui.QIcon()
|
||||
icon3.addPixmap(QtGui.QPixmap(":/action/ico/arrow-left.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.btn_to_left.setIcon(icon3)
|
||||
self.btn_to_left.setIconSize(QtCore.QSize(24, 24))
|
||||
self.btn_to_left.setAutoDefault(False)
|
||||
self.btn_to_left.setObjectName("btn_to_left")
|
||||
self.hl_revpi.addWidget(self.btn_to_left)
|
||||
self.btn_delete_revpi = QtWidgets.QPushButton(self.gridLayoutWidget_2)
|
||||
self.btn_delete_revpi.setText("")
|
||||
icon4 = QtGui.QIcon()
|
||||
icon4.addPixmap(QtGui.QPixmap(":/action/ico/edit-delete.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.btn_delete_revpi.setIcon(icon4)
|
||||
self.btn_delete_revpi.setIconSize(QtCore.QSize(24, 24))
|
||||
self.btn_delete_revpi.setAutoDefault(False)
|
||||
self.btn_delete_revpi.setObjectName("btn_delete_revpi")
|
||||
self.hl_revpi.addWidget(self.btn_delete_revpi)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.hl_revpi.addItem(spacerItem1)
|
||||
self.vl_revpi.addLayout(self.hl_revpi)
|
||||
self.tree_files_revpi = QtWidgets.QTreeWidget(self.gridLayoutWidget_2)
|
||||
self.tree_files_revpi.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
||||
self.tree_files_revpi.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
||||
self.tree_files_revpi.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.tree_files_revpi.setIconSize(QtCore.QSize(24, 24))
|
||||
self.tree_files_revpi.setObjectName("tree_files_revpi")
|
||||
self.tree_files_revpi.headerItem().setText(0, "1")
|
||||
self.tree_files_revpi.header().setVisible(False)
|
||||
self.vl_revpi.addWidget(self.tree_files_revpi)
|
||||
self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1)
|
||||
self.btn_all = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.btn_all.setObjectName("btn_all")
|
||||
self.gridLayout.addWidget(self.btn_all, 1, 0, 1, 1)
|
||||
win_files.setCentralWidget(self.centralwidget)
|
||||
self.statusbar = QtWidgets.QStatusBar(win_files)
|
||||
self.statusbar.setObjectName("statusbar")
|
||||
win_files.setStatusBar(self.statusbar)
|
||||
|
||||
self.retranslateUi(win_files)
|
||||
QtCore.QMetaObject.connectSlotsByName(win_files)
|
||||
|
||||
def retranslateUi(self, win_files):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
win_files.setWindowTitle(_translate("win_files", "File manager"))
|
||||
self.gb_select_local.setTitle(_translate("win_files", "Local computer"))
|
||||
self.lbl_select_local.setText(_translate("win_files", "Path to development root:"))
|
||||
self.btn_select_local.setToolTip(_translate("win_files", "Open developer root directory"))
|
||||
self.btn_refresh_local.setToolTip(_translate("win_files", "Reload file list"))
|
||||
self.lbl_path_local.setToolTip(_translate("win_files", "/"))
|
||||
self.tree_files_local.setSortingEnabled(True)
|
||||
self.gb_select_revpi.setTitle(_translate("win_files", "Revolution Pi"))
|
||||
self.lbl_path_revpi.setToolTip(_translate("win_files", "/"))
|
||||
self.lbl_select_revpi.setText(_translate("win_files", "RevPiPyLoad working directory:"))
|
||||
self.btn_refresh_revpi.setToolTip(_translate("win_files", "Reload file list"))
|
||||
self.tree_files_revpi.setSortingEnabled(True)
|
||||
self.btn_all.setText(_translate("win_files", "Stop - Upload - Start"))
|
||||
from . import ressources_rc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
win_files = QtWidgets.QMainWindow()
|
||||
ui = Ui_win_files()
|
||||
ui.setupUi(win_files)
|
||||
win_files.show()
|
||||
sys.exit(app.exec_())
|
||||
159
src/revpicommander/ui/mqttmanager_ui.py
Normal file
159
src/revpicommander/ui/mqttmanager_ui.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'mqttmanager.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_diag_mqtt(object):
|
||||
def setupUi(self, diag_mqtt):
|
||||
diag_mqtt.setObjectName("diag_mqtt")
|
||||
diag_mqtt.resize(489, 709)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(diag_mqtt)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.gb_basetopic = QtWidgets.QGroupBox(diag_mqtt)
|
||||
self.gb_basetopic.setObjectName("gb_basetopic")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.gb_basetopic)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.lbl_basetopic_description = QtWidgets.QLabel(self.gb_basetopic)
|
||||
self.lbl_basetopic_description.setMaximumSize(QtCore.QSize(450, 16777215))
|
||||
self.lbl_basetopic_description.setWordWrap(True)
|
||||
self.lbl_basetopic_description.setObjectName("lbl_basetopic_description")
|
||||
self.gridLayout.addWidget(self.lbl_basetopic_description, 1, 0, 1, 4)
|
||||
self.lbl_basetopic = QtWidgets.QLabel(self.gb_basetopic)
|
||||
self.lbl_basetopic.setObjectName("lbl_basetopic")
|
||||
self.gridLayout.addWidget(self.lbl_basetopic, 0, 0, 1, 1)
|
||||
self.txt_basetopic = QtWidgets.QLineEdit(self.gb_basetopic)
|
||||
self.txt_basetopic.setObjectName("txt_basetopic")
|
||||
self.gridLayout.addWidget(self.txt_basetopic, 0, 1, 1, 1)
|
||||
self.verticalLayout.addWidget(self.gb_basetopic)
|
||||
self.gb_send_on_event = QtWidgets.QGroupBox(diag_mqtt)
|
||||
self.gb_send_on_event.setObjectName("gb_send_on_event")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.gb_send_on_event)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.lbl_sendinterval = QtWidgets.QLabel(self.gb_send_on_event)
|
||||
self.lbl_sendinterval.setObjectName("lbl_sendinterval")
|
||||
self.gridLayout_2.addWidget(self.lbl_sendinterval, 0, 0, 1, 1)
|
||||
self.sbx_sendinterval = QtWidgets.QSpinBox(self.gb_send_on_event)
|
||||
self.sbx_sendinterval.setMinimum(1)
|
||||
self.sbx_sendinterval.setMaximum(21600)
|
||||
self.sbx_sendinterval.setObjectName("sbx_sendinterval")
|
||||
self.gridLayout_2.addWidget(self.sbx_sendinterval, 0, 1, 1, 1)
|
||||
self.lbl_topic_io = QtWidgets.QLabel(self.gb_send_on_event)
|
||||
self.lbl_topic_io.setObjectName("lbl_topic_io")
|
||||
self.gridLayout_2.addWidget(self.lbl_topic_io, 1, 0, 1, 2)
|
||||
self.cbx_send_on_event = QtWidgets.QCheckBox(self.gb_send_on_event)
|
||||
self.cbx_send_on_event.setObjectName("cbx_send_on_event")
|
||||
self.gridLayout_2.addWidget(self.cbx_send_on_event, 2, 0, 1, 2)
|
||||
self.lbl_topic_event = QtWidgets.QLabel(self.gb_send_on_event)
|
||||
self.lbl_topic_event.setObjectName("lbl_topic_event")
|
||||
self.gridLayout_2.addWidget(self.lbl_topic_event, 3, 0, 1, 2)
|
||||
self.verticalLayout.addWidget(self.gb_send_on_event)
|
||||
self.gb_write_outputs = QtWidgets.QGroupBox(diag_mqtt)
|
||||
self.gb_write_outputs.setObjectName("gb_write_outputs")
|
||||
self.gridLayout_3 = QtWidgets.QGridLayout(self.gb_write_outputs)
|
||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
||||
self.lbl_write_outputs = QtWidgets.QLabel(self.gb_write_outputs)
|
||||
self.lbl_write_outputs.setMaximumSize(QtCore.QSize(450, 16777215))
|
||||
self.lbl_write_outputs.setWordWrap(True)
|
||||
self.lbl_write_outputs.setObjectName("lbl_write_outputs")
|
||||
self.gridLayout_3.addWidget(self.lbl_write_outputs, 1, 0, 1, 1)
|
||||
self.cbx_write_outputs = QtWidgets.QCheckBox(self.gb_write_outputs)
|
||||
self.cbx_write_outputs.setObjectName("cbx_write_outputs")
|
||||
self.gridLayout_3.addWidget(self.cbx_write_outputs, 0, 0, 1, 1)
|
||||
self.verticalLayout.addWidget(self.gb_write_outputs)
|
||||
self.gb_broker = QtWidgets.QGroupBox(diag_mqtt)
|
||||
self.gb_broker.setObjectName("gb_broker")
|
||||
self.formLayout = QtWidgets.QFormLayout(self.gb_broker)
|
||||
self.formLayout.setObjectName("formLayout")
|
||||
self.lbl_broker_address = QtWidgets.QLabel(self.gb_broker)
|
||||
self.lbl_broker_address.setObjectName("lbl_broker_address")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lbl_broker_address)
|
||||
self.txt_broker_address = QtWidgets.QLineEdit(self.gb_broker)
|
||||
self.txt_broker_address.setObjectName("txt_broker_address")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.txt_broker_address)
|
||||
self.lbl_port = QtWidgets.QLabel(self.gb_broker)
|
||||
self.lbl_port.setObjectName("lbl_port")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lbl_port)
|
||||
self.lbl_username = QtWidgets.QLabel(self.gb_broker)
|
||||
self.lbl_username.setObjectName("lbl_username")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.lbl_username)
|
||||
self.lbl_password = QtWidgets.QLabel(self.gb_broker)
|
||||
self.lbl_password.setObjectName("lbl_password")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.lbl_password)
|
||||
self.lbl_client_id = QtWidgets.QLabel(self.gb_broker)
|
||||
self.lbl_client_id.setObjectName("lbl_client_id")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.lbl_client_id)
|
||||
self.txt_username = QtWidgets.QLineEdit(self.gb_broker)
|
||||
self.txt_username.setObjectName("txt_username")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.txt_username)
|
||||
self.txt_password = QtWidgets.QLineEdit(self.gb_broker)
|
||||
self.txt_password.setObjectName("txt_password")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.txt_password)
|
||||
self.txt_client_id = QtWidgets.QLineEdit(self.gb_broker)
|
||||
self.txt_client_id.setObjectName("txt_client_id")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.txt_client_id)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.sbx_port = QtWidgets.QSpinBox(self.gb_broker)
|
||||
self.sbx_port.setMinimum(1)
|
||||
self.sbx_port.setMaximum(65535)
|
||||
self.sbx_port.setObjectName("sbx_port")
|
||||
self.horizontalLayout.addWidget(self.sbx_port)
|
||||
self.cbx_tls_set = QtWidgets.QCheckBox(self.gb_broker)
|
||||
self.cbx_tls_set.setObjectName("cbx_tls_set")
|
||||
self.horizontalLayout.addWidget(self.cbx_tls_set)
|
||||
self.formLayout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout)
|
||||
self.verticalLayout.addWidget(self.gb_broker)
|
||||
self.btn_box = QtWidgets.QDialogButtonBox(diag_mqtt)
|
||||
self.btn_box.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
|
||||
self.btn_box.setObjectName("btn_box")
|
||||
self.verticalLayout.addWidget(self.btn_box)
|
||||
|
||||
self.retranslateUi(diag_mqtt)
|
||||
self.btn_box.accepted.connect(diag_mqtt.accept) # type: ignore
|
||||
self.btn_box.rejected.connect(diag_mqtt.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(diag_mqtt)
|
||||
|
||||
def retranslateUi(self, diag_mqtt):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
diag_mqtt.setWindowTitle(_translate("diag_mqtt", "MQTT settings"))
|
||||
self.gb_basetopic.setTitle(_translate("diag_mqtt", "Base topic"))
|
||||
self.lbl_basetopic_description.setText(_translate("diag_mqtt", "The base topic is the first part of any mqtt topic, the Revolution Pi will publish. You can use any character includig \'/\' to structure the messages on your broker.\n"
|
||||
"\n"
|
||||
"For example: revpi0000/data"))
|
||||
self.lbl_basetopic.setText(_translate("diag_mqtt", "Base topic:"))
|
||||
self.gb_send_on_event.setTitle(_translate("diag_mqtt", "Publish settings"))
|
||||
self.lbl_sendinterval.setText(_translate("diag_mqtt", "Publish all exported values every n seconds:"))
|
||||
self.lbl_topic_io.setText(_translate("diag_mqtt", "Topic: [basetopic]/io/[ioname]"))
|
||||
self.cbx_send_on_event.setText(_translate("diag_mqtt", "Send exported values immediately on value change"))
|
||||
self.lbl_topic_event.setText(_translate("diag_mqtt", "Topic: [basetopic]/event/[ioname]"))
|
||||
self.gb_write_outputs.setTitle(_translate("diag_mqtt", "Set outputs"))
|
||||
self.lbl_write_outputs.setText(_translate("diag_mqtt", "The Revolution Pi will subscribe a topic on which your mqtt client can publish messages with the new io value as payload.\n"
|
||||
"\n"
|
||||
"Publish values with topic: [basetopic]/set/[outputname]"))
|
||||
self.cbx_write_outputs.setText(_translate("diag_mqtt", "Allow MQTT to to set outputs on Revolution Pi"))
|
||||
self.gb_broker.setTitle(_translate("diag_mqtt", "Broker settings"))
|
||||
self.lbl_broker_address.setText(_translate("diag_mqtt", "Broker address:"))
|
||||
self.lbl_port.setText(_translate("diag_mqtt", "Broker port:"))
|
||||
self.lbl_username.setText(_translate("diag_mqtt", "User name:"))
|
||||
self.lbl_password.setText(_translate("diag_mqtt", "Password:"))
|
||||
self.lbl_client_id.setText(_translate("diag_mqtt", "Client ID:"))
|
||||
self.cbx_tls_set.setText(_translate("diag_mqtt", "Use TLS"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
diag_mqtt = QtWidgets.QDialog()
|
||||
ui = Ui_diag_mqtt()
|
||||
ui.setupUi(diag_mqtt)
|
||||
diag_mqtt.show()
|
||||
sys.exit(app.exec_())
|
||||
3315
src/revpicommander/ui/ressources_rc.py
Normal file
3315
src/revpicommander/ui/ressources_rc.py
Normal file
File diff suppressed because it is too large
Load Diff
187
src/revpicommander/ui/revpicommander_ui.py
Normal file
187
src/revpicommander/ui/revpicommander_ui.py
Normal file
@@ -0,0 +1,187 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'revpicommander.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_win_revpicommander(object):
|
||||
def setupUi(self, win_revpicommander):
|
||||
win_revpicommander.setObjectName("win_revpicommander")
|
||||
win_revpicommander.resize(353, 299)
|
||||
win_revpicommander.setWindowTitle("RevPi Commander")
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(":/main/ico/revpipycontrol.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
win_revpicommander.setWindowIcon(icon)
|
||||
self.centralwidget = QtWidgets.QWidget(win_revpicommander)
|
||||
self.centralwidget.setObjectName("centralwidget")
|
||||
self.gl = QtWidgets.QVBoxLayout(self.centralwidget)
|
||||
self.gl.setObjectName("gl")
|
||||
self.hzl_connection = QtWidgets.QHBoxLayout()
|
||||
self.hzl_connection.setObjectName("hzl_connection")
|
||||
self.txt_host = QtWidgets.QLineEdit(self.centralwidget)
|
||||
self.txt_host.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.txt_host.setText("")
|
||||
self.txt_host.setReadOnly(True)
|
||||
self.txt_host.setObjectName("txt_host")
|
||||
self.hzl_connection.addWidget(self.txt_host)
|
||||
self.txt_connection = QtWidgets.QLineEdit(self.centralwidget)
|
||||
self.txt_connection.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.txt_connection.setText("")
|
||||
self.txt_connection.setReadOnly(True)
|
||||
self.txt_connection.setObjectName("txt_connection")
|
||||
self.hzl_connection.addWidget(self.txt_connection)
|
||||
self.gl.addLayout(self.hzl_connection)
|
||||
self.btn_plc_start = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.btn_plc_start.setObjectName("btn_plc_start")
|
||||
self.gl.addWidget(self.btn_plc_start)
|
||||
self.btn_plc_stop = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.btn_plc_stop.setObjectName("btn_plc_stop")
|
||||
self.gl.addWidget(self.btn_plc_stop)
|
||||
self.btn_plc_restart = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.btn_plc_restart.setObjectName("btn_plc_restart")
|
||||
self.gl.addWidget(self.btn_plc_restart)
|
||||
self.btn_plc_logs = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.btn_plc_logs.setObjectName("btn_plc_logs")
|
||||
self.gl.addWidget(self.btn_plc_logs)
|
||||
self.hzl_status = QtWidgets.QHBoxLayout()
|
||||
self.hzl_status.setObjectName("hzl_status")
|
||||
self.lbl_status = QtWidgets.QLabel(self.centralwidget)
|
||||
self.lbl_status.setObjectName("lbl_status")
|
||||
self.hzl_status.addWidget(self.lbl_status)
|
||||
self.txt_status = QtWidgets.QLineEdit(self.centralwidget)
|
||||
self.txt_status.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.txt_status.setText("")
|
||||
self.txt_status.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.txt_status.setReadOnly(True)
|
||||
self.txt_status.setObjectName("txt_status")
|
||||
self.hzl_status.addWidget(self.txt_status)
|
||||
self.gl.addLayout(self.hzl_status)
|
||||
self.btn_plc_debug = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.btn_plc_debug.setMinimumSize(QtCore.QSize(300, 0))
|
||||
self.btn_plc_debug.setCheckable(True)
|
||||
self.btn_plc_debug.setObjectName("btn_plc_debug")
|
||||
self.gl.addWidget(self.btn_plc_debug)
|
||||
win_revpicommander.setCentralWidget(self.centralwidget)
|
||||
self.menubar = QtWidgets.QMenuBar(win_revpicommander)
|
||||
self.menubar.setGeometry(QtCore.QRect(0, 0, 353, 24))
|
||||
self.menubar.setObjectName("menubar")
|
||||
self.men_file = QtWidgets.QMenu(self.menubar)
|
||||
self.men_file.setObjectName("men_file")
|
||||
self.men_help = QtWidgets.QMenu(self.menubar)
|
||||
self.men_help.setObjectName("men_help")
|
||||
self.men_plc = QtWidgets.QMenu(self.menubar)
|
||||
self.men_plc.setObjectName("men_plc")
|
||||
self.men_connections = QtWidgets.QMenu(self.menubar)
|
||||
self.men_connections.setObjectName("men_connections")
|
||||
win_revpicommander.setMenuBar(self.menubar)
|
||||
self.statusbar = QtWidgets.QStatusBar(win_revpicommander)
|
||||
self.statusbar.setSizeGripEnabled(False)
|
||||
self.statusbar.setObjectName("statusbar")
|
||||
win_revpicommander.setStatusBar(self.statusbar)
|
||||
self.act_connections = QtWidgets.QAction(win_revpicommander)
|
||||
self.act_connections.setObjectName("act_connections")
|
||||
self.act_search = QtWidgets.QAction(win_revpicommander)
|
||||
self.act_search.setObjectName("act_search")
|
||||
self.act_quit = QtWidgets.QAction(win_revpicommander)
|
||||
self.act_quit.setObjectName("act_quit")
|
||||
self.act_webpage = QtWidgets.QAction(win_revpicommander)
|
||||
self.act_webpage.setObjectName("act_webpage")
|
||||
self.act_info = QtWidgets.QAction(win_revpicommander)
|
||||
self.act_info.setObjectName("act_info")
|
||||
self.act_logs = QtWidgets.QAction(win_revpicommander)
|
||||
self.act_logs.setObjectName("act_logs")
|
||||
self.act_options = QtWidgets.QAction(win_revpicommander)
|
||||
self.act_options.setObjectName("act_options")
|
||||
self.act_program = QtWidgets.QAction(win_revpicommander)
|
||||
self.act_program.setObjectName("act_program")
|
||||
self.act_developer = QtWidgets.QAction(win_revpicommander)
|
||||
self.act_developer.setObjectName("act_developer")
|
||||
self.act_pictory = QtWidgets.QAction(win_revpicommander)
|
||||
self.act_pictory.setObjectName("act_pictory")
|
||||
self.act_disconnect = QtWidgets.QAction(win_revpicommander)
|
||||
self.act_disconnect.setObjectName("act_disconnect")
|
||||
self.act_reset = QtWidgets.QAction(win_revpicommander)
|
||||
self.act_reset.setObjectName("act_reset")
|
||||
self.act_simulator = QtWidgets.QAction(win_revpicommander)
|
||||
self.act_simulator.setObjectName("act_simulator")
|
||||
self.men_file.addAction(self.act_connections)
|
||||
self.men_file.addAction(self.act_search)
|
||||
self.men_file.addSeparator()
|
||||
self.men_file.addAction(self.act_simulator)
|
||||
self.men_file.addSeparator()
|
||||
self.men_file.addAction(self.act_quit)
|
||||
self.men_help.addAction(self.act_webpage)
|
||||
self.men_help.addSeparator()
|
||||
self.men_help.addAction(self.act_info)
|
||||
self.men_plc.addAction(self.act_logs)
|
||||
self.men_plc.addAction(self.act_options)
|
||||
self.men_plc.addAction(self.act_program)
|
||||
self.men_plc.addAction(self.act_developer)
|
||||
self.men_plc.addSeparator()
|
||||
self.men_plc.addAction(self.act_pictory)
|
||||
self.men_plc.addAction(self.act_reset)
|
||||
self.men_plc.addSeparator()
|
||||
self.men_plc.addAction(self.act_disconnect)
|
||||
self.menubar.addAction(self.men_file.menuAction())
|
||||
self.menubar.addAction(self.men_plc.menuAction())
|
||||
self.menubar.addAction(self.men_connections.menuAction())
|
||||
self.menubar.addAction(self.men_help.menuAction())
|
||||
|
||||
self.retranslateUi(win_revpicommander)
|
||||
self.act_quit.triggered.connect(win_revpicommander.close) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(win_revpicommander)
|
||||
win_revpicommander.setTabOrder(self.btn_plc_start, self.btn_plc_stop)
|
||||
win_revpicommander.setTabOrder(self.btn_plc_stop, self.btn_plc_restart)
|
||||
win_revpicommander.setTabOrder(self.btn_plc_restart, self.btn_plc_logs)
|
||||
win_revpicommander.setTabOrder(self.btn_plc_logs, self.btn_plc_debug)
|
||||
|
||||
def retranslateUi(self, win_revpicommander):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
self.btn_plc_start.setText(_translate("win_revpicommander", "PLC &start"))
|
||||
self.btn_plc_stop.setText(_translate("win_revpicommander", "PLC s&top"))
|
||||
self.btn_plc_restart.setText(_translate("win_revpicommander", "PLC restart"))
|
||||
self.btn_plc_logs.setText(_translate("win_revpicommander", "PLC &logs"))
|
||||
self.lbl_status.setText(_translate("win_revpicommander", "Status:"))
|
||||
self.btn_plc_debug.setText(_translate("win_revpicommander", "PLC watch &mode"))
|
||||
self.men_file.setTitle(_translate("win_revpicommander", "&File"))
|
||||
self.men_help.setTitle(_translate("win_revpicommander", "&Help"))
|
||||
self.men_plc.setTitle(_translate("win_revpicommander", "&PLC"))
|
||||
self.men_connections.setTitle(_translate("win_revpicommander", "&Connections"))
|
||||
self.act_connections.setText(_translate("win_revpicommander", "&Connections..."))
|
||||
self.act_connections.setShortcut(_translate("win_revpicommander", "Ctrl+N"))
|
||||
self.act_search.setText(_translate("win_revpicommander", "&Search Revolution Pi..."))
|
||||
self.act_search.setShortcut(_translate("win_revpicommander", "Ctrl+F"))
|
||||
self.act_quit.setText(_translate("win_revpicommander", "&Quit"))
|
||||
self.act_webpage.setText(_translate("win_revpicommander", "Visit &webpage..."))
|
||||
self.act_info.setText(_translate("win_revpicommander", "&Info..."))
|
||||
self.act_logs.setText(_translate("win_revpicommander", "PLC &logs..."))
|
||||
self.act_logs.setShortcut(_translate("win_revpicommander", "Ctrl+L"))
|
||||
self.act_options.setText(_translate("win_revpicommander", "PLC &options..."))
|
||||
self.act_options.setShortcut(_translate("win_revpicommander", "Ctrl+O"))
|
||||
self.act_program.setText(_translate("win_revpicommander", "PLC progra&m..."))
|
||||
self.act_program.setShortcut(_translate("win_revpicommander", "Ctrl+P"))
|
||||
self.act_developer.setText(_translate("win_revpicommander", "PLC de&veloper..."))
|
||||
self.act_developer.setShortcut(_translate("win_revpicommander", "Ctrl+D"))
|
||||
self.act_pictory.setText(_translate("win_revpicommander", "piCtory configuraiton..."))
|
||||
self.act_disconnect.setText(_translate("win_revpicommander", "&Disconnect"))
|
||||
self.act_disconnect.setShortcut(_translate("win_revpicommander", "Ctrl+X"))
|
||||
self.act_reset.setText(_translate("win_revpicommander", "Reset driver..."))
|
||||
self.act_simulator.setText(_translate("win_revpicommander", "RevPi si&mulator..."))
|
||||
from . import ressources_rc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
win_revpicommander = QtWidgets.QMainWindow()
|
||||
ui = Ui_win_revpicommander()
|
||||
ui.setupUi(win_revpicommander)
|
||||
win_revpicommander.show()
|
||||
sys.exit(app.exec_())
|
||||
108
src/revpicommander/ui/revpiinfo_ui.py
Normal file
108
src/revpicommander/ui/revpiinfo_ui.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'revpiinfo.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_diag_revpiinfo(object):
|
||||
def setupUi(self, diag_revpiinfo):
|
||||
diag_revpiinfo.setObjectName("diag_revpiinfo")
|
||||
diag_revpiinfo.resize(627, 398)
|
||||
self.gridLayout = QtWidgets.QGridLayout(diag_revpiinfo)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.lbl_head = QtWidgets.QLabel(diag_revpiinfo)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(20)
|
||||
font.setBold(True)
|
||||
font.setWeight(75)
|
||||
self.lbl_head.setFont(font)
|
||||
self.lbl_head.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.lbl_head.setObjectName("lbl_head")
|
||||
self.gridLayout.addWidget(self.lbl_head, 0, 0, 1, 3)
|
||||
self.lbl_lbl_version_pyload = QtWidgets.QLabel(diag_revpiinfo)
|
||||
font = QtGui.QFont()
|
||||
font.setBold(True)
|
||||
font.setWeight(75)
|
||||
self.lbl_lbl_version_pyload.setFont(font)
|
||||
self.lbl_lbl_version_pyload.setObjectName("lbl_lbl_version_pyload")
|
||||
self.gridLayout.addWidget(self.lbl_lbl_version_pyload, 3, 0, 1, 1)
|
||||
self.lst_files = QtWidgets.QListWidget(diag_revpiinfo)
|
||||
self.lst_files.setObjectName("lst_files")
|
||||
self.gridLayout.addWidget(self.lst_files, 3, 2, 4, 1)
|
||||
self.lbl_version_pyload = QtWidgets.QLabel(diag_revpiinfo)
|
||||
font = QtGui.QFont()
|
||||
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)
|
||||
self.lbl_link = QtWidgets.QLabel(diag_revpiinfo)
|
||||
self.lbl_link.setOpenExternalLinks(True)
|
||||
self.lbl_link.setObjectName("lbl_link")
|
||||
self.gridLayout.addWidget(self.lbl_link, 6, 0, 1, 1)
|
||||
self.btn_box = QtWidgets.QDialogButtonBox(diag_revpiinfo)
|
||||
self.btn_box.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Ok)
|
||||
self.btn_box.setObjectName("btn_box")
|
||||
self.gridLayout.addWidget(self.btn_box, 7, 0, 1, 3)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.lbl_lbl_version_control = QtWidgets.QLabel(diag_revpiinfo)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(14)
|
||||
self.lbl_lbl_version_control.setFont(font)
|
||||
self.lbl_lbl_version_control.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.lbl_lbl_version_control.setObjectName("lbl_lbl_version_control")
|
||||
self.horizontalLayout.addWidget(self.lbl_lbl_version_control)
|
||||
self.lbl_version_control = QtWidgets.QLabel(diag_revpiinfo)
|
||||
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)
|
||||
self.lbl_info = QtWidgets.QLabel(diag_revpiinfo)
|
||||
self.lbl_info.setMinimumSize(QtCore.QSize(300, 0))
|
||||
self.lbl_info.setWordWrap(True)
|
||||
self.lbl_info.setObjectName("lbl_info")
|
||||
self.gridLayout.addWidget(self.lbl_info, 4, 0, 1, 2)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout.addItem(spacerItem, 5, 0, 1, 1)
|
||||
|
||||
self.retranslateUi(diag_revpiinfo)
|
||||
self.btn_box.accepted.connect(diag_revpiinfo.accept) # type: ignore
|
||||
self.btn_box.rejected.connect(diag_revpiinfo.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(diag_revpiinfo)
|
||||
|
||||
def retranslateUi(self, diag_revpiinfo):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
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_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_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"
|
||||
"\n"
|
||||
"(c) Sven Sager, License: GPLv3"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
diag_revpiinfo = QtWidgets.QDialog()
|
||||
ui = Ui_diag_revpiinfo()
|
||||
ui.setupUi(diag_revpiinfo)
|
||||
diag_revpiinfo.show()
|
||||
sys.exit(app.exec_())
|
||||
98
src/revpicommander/ui/revpilogfile_ui.py
Normal file
98
src/revpicommander/ui/revpilogfile_ui.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'revpilogfile.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_win_revpilogfile(object):
|
||||
def setupUi(self, win_revpilogfile):
|
||||
win_revpilogfile.setObjectName("win_revpilogfile")
|
||||
win_revpilogfile.resize(796, 347)
|
||||
self.centralwidget = QtWidgets.QWidget(win_revpilogfile)
|
||||
self.centralwidget.setObjectName("centralwidget")
|
||||
self.gridLayout_3 = QtWidgets.QGridLayout(self.centralwidget)
|
||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
||||
self.cbx_stay_on_top = QtWidgets.QCheckBox(self.centralwidget)
|
||||
self.cbx_stay_on_top.setObjectName("cbx_stay_on_top")
|
||||
self.gridLayout_3.addWidget(self.cbx_stay_on_top, 1, 0, 1, 1)
|
||||
self.cbx_wrap = QtWidgets.QCheckBox(self.centralwidget)
|
||||
self.cbx_wrap.setObjectName("cbx_wrap")
|
||||
self.gridLayout_3.addWidget(self.cbx_wrap, 1, 1, 1, 1)
|
||||
self.splitter = QtWidgets.QSplitter(self.centralwidget)
|
||||
self.splitter.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.splitter.setChildrenCollapsible(False)
|
||||
self.splitter.setObjectName("splitter")
|
||||
self.gridLayoutWidget = QtWidgets.QWidget(self.splitter)
|
||||
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
|
||||
self.grid_daemon = QtWidgets.QGridLayout(self.gridLayoutWidget)
|
||||
self.grid_daemon.setContentsMargins(0, 0, 0, 0)
|
||||
self.grid_daemon.setObjectName("grid_daemon")
|
||||
self.lbl_daemon = QtWidgets.QLabel(self.gridLayoutWidget)
|
||||
self.lbl_daemon.setObjectName("lbl_daemon")
|
||||
self.grid_daemon.addWidget(self.lbl_daemon, 0, 0, 1, 1)
|
||||
self.btn_daemon = QtWidgets.QPushButton(self.gridLayoutWidget)
|
||||
self.btn_daemon.setObjectName("btn_daemon")
|
||||
self.grid_daemon.addWidget(self.btn_daemon, 0, 1, 1, 1)
|
||||
self.txt_daemon = QtWidgets.QPlainTextEdit(self.gridLayoutWidget)
|
||||
self.txt_daemon.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
self.txt_daemon.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
self.txt_daemon.setTabChangesFocus(True)
|
||||
self.txt_daemon.setUndoRedoEnabled(False)
|
||||
self.txt_daemon.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap)
|
||||
self.txt_daemon.setReadOnly(True)
|
||||
self.txt_daemon.setObjectName("txt_daemon")
|
||||
self.grid_daemon.addWidget(self.txt_daemon, 1, 0, 1, 2)
|
||||
self.grid_daemon.setColumnStretch(0, 1)
|
||||
self.gridLayoutWidget_2 = QtWidgets.QWidget(self.splitter)
|
||||
self.gridLayoutWidget_2.setObjectName("gridLayoutWidget_2")
|
||||
self.grid_app = QtWidgets.QGridLayout(self.gridLayoutWidget_2)
|
||||
self.grid_app.setContentsMargins(0, 0, 0, 0)
|
||||
self.grid_app.setObjectName("grid_app")
|
||||
self.btn_app = QtWidgets.QPushButton(self.gridLayoutWidget_2)
|
||||
self.btn_app.setObjectName("btn_app")
|
||||
self.grid_app.addWidget(self.btn_app, 0, 1, 1, 1)
|
||||
self.lbl_app = QtWidgets.QLabel(self.gridLayoutWidget_2)
|
||||
self.lbl_app.setObjectName("lbl_app")
|
||||
self.grid_app.addWidget(self.lbl_app, 0, 0, 1, 1)
|
||||
self.txt_app = QtWidgets.QPlainTextEdit(self.gridLayoutWidget_2)
|
||||
self.txt_app.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
self.txt_app.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
self.txt_app.setTabChangesFocus(True)
|
||||
self.txt_app.setUndoRedoEnabled(False)
|
||||
self.txt_app.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap)
|
||||
self.txt_app.setReadOnly(True)
|
||||
self.txt_app.setObjectName("txt_app")
|
||||
self.grid_app.addWidget(self.txt_app, 1, 0, 1, 2)
|
||||
self.grid_app.setColumnStretch(0, 1)
|
||||
self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 2)
|
||||
win_revpilogfile.setCentralWidget(self.centralwidget)
|
||||
|
||||
self.retranslateUi(win_revpilogfile)
|
||||
QtCore.QMetaObject.connectSlotsByName(win_revpilogfile)
|
||||
|
||||
def retranslateUi(self, win_revpilogfile):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
win_revpilogfile.setWindowTitle(_translate("win_revpilogfile", "RevPi Python PLC Logfiles"))
|
||||
self.cbx_stay_on_top.setText(_translate("win_revpilogfile", "Stay on top of all windows"))
|
||||
self.cbx_wrap.setText(_translate("win_revpilogfile", "Linewrap"))
|
||||
self.lbl_daemon.setText(_translate("win_revpilogfile", "RevPiPyLoad - Logfile"))
|
||||
self.btn_daemon.setText(_translate("win_revpilogfile", "Clear view"))
|
||||
self.btn_app.setText(_translate("win_revpilogfile", "Clear view"))
|
||||
self.lbl_app.setText(_translate("win_revpilogfile", "Python PLC program - Logfile"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
win_revpilogfile = QtWidgets.QMainWindow()
|
||||
ui = Ui_win_revpilogfile()
|
||||
ui.setupUi(win_revpilogfile)
|
||||
win_revpilogfile.show()
|
||||
sys.exit(app.exec_())
|
||||
163
src/revpicommander/ui/revpioption_ui.py
Normal file
163
src/revpicommander/ui/revpioption_ui.py
Normal file
@@ -0,0 +1,163 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'revpioption.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_diag_options(object):
|
||||
def setupUi(self, diag_options):
|
||||
diag_options.setObjectName("diag_options")
|
||||
diag_options.resize(458, 557)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(diag_options)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.gb_plc = QtWidgets.QGroupBox(diag_options)
|
||||
self.gb_plc.setObjectName("gb_plc")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.gb_plc)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.lbl_replace_io = QtWidgets.QLabel(self.gb_plc)
|
||||
self.lbl_replace_io.setObjectName("lbl_replace_io")
|
||||
self.gridLayout.addWidget(self.lbl_replace_io, 6, 0, 1, 1)
|
||||
self.cbx_zeroonerror = QtWidgets.QCheckBox(self.gb_plc)
|
||||
self.cbx_zeroonerror.setObjectName("cbx_zeroonerror")
|
||||
self.gridLayout.addWidget(self.cbx_zeroonerror, 5, 1, 1, 2)
|
||||
self.cbx_autostart = QtWidgets.QCheckBox(self.gb_plc)
|
||||
self.cbx_autostart.setObjectName("cbx_autostart")
|
||||
self.gridLayout.addWidget(self.cbx_autostart, 0, 0, 1, 3)
|
||||
self.cbb_reset_driver_action = QtWidgets.QComboBox(self.gb_plc)
|
||||
self.cbb_reset_driver_action.setObjectName("cbb_reset_driver_action")
|
||||
self.cbb_reset_driver_action.addItem("")
|
||||
self.cbb_reset_driver_action.addItem("")
|
||||
self.cbb_reset_driver_action.addItem("")
|
||||
self.gridLayout.addWidget(self.cbb_reset_driver_action, 8, 1, 1, 2)
|
||||
self.lbl_reset_driver_action = QtWidgets.QLabel(self.gb_plc)
|
||||
self.lbl_reset_driver_action.setObjectName("lbl_reset_driver_action")
|
||||
self.gridLayout.addWidget(self.lbl_reset_driver_action, 8, 0, 1, 1)
|
||||
self.lbl_plc_zero = QtWidgets.QLabel(self.gb_plc)
|
||||
self.lbl_plc_zero.setObjectName("lbl_plc_zero")
|
||||
self.gridLayout.addWidget(self.lbl_plc_zero, 3, 0, 1, 3)
|
||||
self.cbb_replace_io = QtWidgets.QComboBox(self.gb_plc)
|
||||
self.cbb_replace_io.setObjectName("cbb_replace_io")
|
||||
self.cbb_replace_io.addItem("")
|
||||
self.cbb_replace_io.addItem("")
|
||||
self.cbb_replace_io.addItem("")
|
||||
self.cbb_replace_io.addItem("")
|
||||
self.gridLayout.addWidget(self.cbb_replace_io, 6, 1, 1, 2)
|
||||
self.cbx_zeroonexit = QtWidgets.QCheckBox(self.gb_plc)
|
||||
self.cbx_zeroonexit.setObjectName("cbx_zeroonexit")
|
||||
self.gridLayout.addWidget(self.cbx_zeroonexit, 4, 1, 1, 2)
|
||||
self.lbl_plc_delay = QtWidgets.QLabel(self.gb_plc)
|
||||
self.lbl_plc_delay.setObjectName("lbl_plc_delay")
|
||||
self.gridLayout.addWidget(self.lbl_plc_delay, 2, 0, 1, 2)
|
||||
self.sbx_autoreloaddelay = QtWidgets.QSpinBox(self.gb_plc)
|
||||
self.sbx_autoreloaddelay.setMinimum(5)
|
||||
self.sbx_autoreloaddelay.setMaximum(120)
|
||||
self.sbx_autoreloaddelay.setObjectName("sbx_autoreloaddelay")
|
||||
self.gridLayout.addWidget(self.sbx_autoreloaddelay, 2, 2, 1, 1)
|
||||
self.cbx_autoreload = QtWidgets.QCheckBox(self.gb_plc)
|
||||
self.cbx_autoreload.setObjectName("cbx_autoreload")
|
||||
self.gridLayout.addWidget(self.cbx_autoreload, 1, 0, 1, 3)
|
||||
self.txt_replace_io = QtWidgets.QLineEdit(self.gb_plc)
|
||||
self.txt_replace_io.setObjectName("txt_replace_io")
|
||||
self.gridLayout.addWidget(self.txt_replace_io, 7, 1, 1, 2)
|
||||
self.lbl_lbl_reset_driver_action = QtWidgets.QLabel(self.gb_plc)
|
||||
self.lbl_lbl_reset_driver_action.setObjectName("lbl_lbl_reset_driver_action")
|
||||
self.gridLayout.addWidget(self.lbl_lbl_reset_driver_action, 9, 0, 1, 3)
|
||||
self.verticalLayout.addWidget(self.gb_plc)
|
||||
self.gb_server = QtWidgets.QGroupBox(diag_options)
|
||||
self.gb_server.setObjectName("gb_server")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.gb_server)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.btn_aclplcslave = QtWidgets.QPushButton(self.gb_server)
|
||||
self.btn_aclplcslave.setObjectName("btn_aclplcslave")
|
||||
self.gridLayout_2.addWidget(self.btn_aclplcslave, 0, 1, 1, 1)
|
||||
self.cbx_mqtt = QtWidgets.QCheckBox(self.gb_server)
|
||||
self.cbx_mqtt.setObjectName("cbx_mqtt")
|
||||
self.gridLayout_2.addWidget(self.cbx_mqtt, 2, 0, 1, 1)
|
||||
self.cbx_plcslave = QtWidgets.QCheckBox(self.gb_server)
|
||||
self.cbx_plcslave.setObjectName("cbx_plcslave")
|
||||
self.gridLayout_2.addWidget(self.cbx_plcslave, 0, 0, 1, 1)
|
||||
self.lbl_slave_status = QtWidgets.QLabel(self.gb_server)
|
||||
self.lbl_slave_status.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.lbl_slave_status.setObjectName("lbl_slave_status")
|
||||
self.gridLayout_2.addWidget(self.lbl_slave_status, 1, 1, 1, 1)
|
||||
self.lbl_lbl_slave_status = QtWidgets.QLabel(self.gb_server)
|
||||
self.lbl_lbl_slave_status.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.lbl_lbl_slave_status.setObjectName("lbl_lbl_slave_status")
|
||||
self.gridLayout_2.addWidget(self.lbl_lbl_slave_status, 1, 0, 1, 1)
|
||||
self.lbl_mqtt_status = QtWidgets.QLabel(self.gb_server)
|
||||
self.lbl_mqtt_status.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.lbl_mqtt_status.setObjectName("lbl_mqtt_status")
|
||||
self.gridLayout_2.addWidget(self.lbl_mqtt_status, 3, 1, 1, 1)
|
||||
self.lbl_lbl_mqtt_status = QtWidgets.QLabel(self.gb_server)
|
||||
self.lbl_lbl_mqtt_status.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
self.lbl_lbl_mqtt_status.setObjectName("lbl_lbl_mqtt_status")
|
||||
self.gridLayout_2.addWidget(self.lbl_lbl_mqtt_status, 3, 0, 1, 1)
|
||||
self.btn_mqtt = QtWidgets.QPushButton(self.gb_server)
|
||||
self.btn_mqtt.setObjectName("btn_mqtt")
|
||||
self.gridLayout_2.addWidget(self.btn_mqtt, 2, 1, 1, 1)
|
||||
self.btn_aclxmlrpc = QtWidgets.QPushButton(self.gb_server)
|
||||
self.btn_aclxmlrpc.setObjectName("btn_aclxmlrpc")
|
||||
self.gridLayout_2.addWidget(self.btn_aclxmlrpc, 4, 1, 1, 1)
|
||||
self.cbx_xmlrpc = QtWidgets.QCheckBox(self.gb_server)
|
||||
self.cbx_xmlrpc.setObjectName("cbx_xmlrpc")
|
||||
self.gridLayout_2.addWidget(self.cbx_xmlrpc, 4, 0, 1, 1)
|
||||
self.verticalLayout.addWidget(self.gb_server)
|
||||
self.btn_box = QtWidgets.QDialogButtonBox(diag_options)
|
||||
self.btn_box.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
|
||||
self.btn_box.setObjectName("btn_box")
|
||||
self.verticalLayout.addWidget(self.btn_box)
|
||||
|
||||
self.retranslateUi(diag_options)
|
||||
self.btn_box.accepted.connect(diag_options.accept) # type: ignore
|
||||
self.btn_box.rejected.connect(diag_options.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(diag_options)
|
||||
|
||||
def retranslateUi(self, diag_options):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
diag_options.setWindowTitle(_translate("diag_options", "RevPi Python PLC Options"))
|
||||
self.gb_plc.setTitle(_translate("diag_options", "Start / Stop behavior of PLC program"))
|
||||
self.lbl_replace_io.setText(_translate("diag_options", "Replace IO file:"))
|
||||
self.cbx_zeroonerror.setText(_translate("diag_options", "... after exception and errors"))
|
||||
self.cbx_autostart.setText(_translate("diag_options", "Start PLC program automatically"))
|
||||
self.cbb_reset_driver_action.setItemText(0, _translate("diag_options", "Do nothing"))
|
||||
self.cbb_reset_driver_action.setItemText(1, _translate("diag_options", "Restart after piCtory changed"))
|
||||
self.cbb_reset_driver_action.setItemText(2, _translate("diag_options", "Always restart the PLC program"))
|
||||
self.lbl_reset_driver_action.setText(_translate("diag_options", "Driver reset action:"))
|
||||
self.lbl_plc_zero.setText(_translate("diag_options", "Set process image to NULL if program terminates..."))
|
||||
self.cbb_replace_io.setItemText(0, _translate("diag_options", "Do not use replace io file"))
|
||||
self.cbb_replace_io.setItemText(1, _translate("diag_options", "Use static file from RevPiPyLoad"))
|
||||
self.cbb_replace_io.setItemText(2, _translate("diag_options", "Use dynamic file from work directory"))
|
||||
self.cbb_replace_io.setItemText(3, _translate("diag_options", "Give own path and filename"))
|
||||
self.cbx_zeroonexit.setText(_translate("diag_options", "... sucessfully without error"))
|
||||
self.lbl_plc_delay.setText(_translate("diag_options", "Restart delay in seconds:"))
|
||||
self.cbx_autoreload.setText(_translate("diag_options", "Restart PLC program after exit or crash"))
|
||||
self.lbl_lbl_reset_driver_action.setText(_translate("diag_options", "PLC program behavior after piCtory driver reset clicked"))
|
||||
self.gb_server.setTitle(_translate("diag_options", "RevPiPyLoad server services"))
|
||||
self.btn_aclplcslave.setText(_translate("diag_options", "Edit ACL"))
|
||||
self.cbx_mqtt.setText(_translate("diag_options", "MQTT process image publisher"))
|
||||
self.cbx_plcslave.setText(_translate("diag_options", "Start RevPi piControl server"))
|
||||
self.lbl_slave_status.setText(_translate("diag_options", "status"))
|
||||
self.lbl_lbl_slave_status.setText(_translate("diag_options", "piControl server is:"))
|
||||
self.lbl_mqtt_status.setText(_translate("diag_options", "status"))
|
||||
self.lbl_lbl_mqtt_status.setText(_translate("diag_options", "MQTT publish service is:"))
|
||||
self.btn_mqtt.setText(_translate("diag_options", "Settings"))
|
||||
self.btn_aclxmlrpc.setText(_translate("diag_options", "Edit ACL"))
|
||||
self.cbx_xmlrpc.setText(_translate("diag_options", "Activate XML-RPC for RevPiCommander"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
diag_options = QtWidgets.QDialog()
|
||||
ui = Ui_diag_options()
|
||||
ui.setupUi(diag_options)
|
||||
diag_options.show()
|
||||
sys.exit(app.exec_())
|
||||
143
src/revpicommander/ui/revpiplclist_ui.py
Normal file
143
src/revpicommander/ui/revpiplclist_ui.py
Normal file
@@ -0,0 +1,143 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'revpiplclist.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_diag_connections(object):
|
||||
def setupUi(self, diag_connections):
|
||||
diag_connections.setObjectName("diag_connections")
|
||||
diag_connections.resize(520, 508)
|
||||
self.gridLayout = QtWidgets.QGridLayout(diag_connections)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.tre_connections = QtWidgets.QTreeWidget(diag_connections)
|
||||
self.tre_connections.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
||||
self.tre_connections.setObjectName("tre_connections")
|
||||
self.gridLayout.addWidget(self.tre_connections, 0, 0, 1, 1)
|
||||
self.vl_edit = QtWidgets.QVBoxLayout()
|
||||
self.vl_edit.setObjectName("vl_edit")
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.vl_edit.addItem(spacerItem)
|
||||
self.btn_up = QtWidgets.QPushButton(diag_connections)
|
||||
self.btn_up.setText("")
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(":/action/ico/arrow-up.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.btn_up.setIcon(icon)
|
||||
self.btn_up.setObjectName("btn_up")
|
||||
self.vl_edit.addWidget(self.btn_up)
|
||||
self.btn_down = QtWidgets.QPushButton(diag_connections)
|
||||
self.btn_down.setText("")
|
||||
icon1 = QtGui.QIcon()
|
||||
icon1.addPixmap(QtGui.QPixmap(":/action/ico/arrow-down.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.btn_down.setIcon(icon1)
|
||||
self.btn_down.setObjectName("btn_down")
|
||||
self.vl_edit.addWidget(self.btn_down)
|
||||
self.btn_delete = QtWidgets.QPushButton(diag_connections)
|
||||
self.btn_delete.setText("")
|
||||
icon2 = QtGui.QIcon()
|
||||
icon2.addPixmap(QtGui.QPixmap(":/action/ico/edit-delete.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.btn_delete.setIcon(icon2)
|
||||
self.btn_delete.setObjectName("btn_delete")
|
||||
self.vl_edit.addWidget(self.btn_delete)
|
||||
self.btn_add = QtWidgets.QPushButton(diag_connections)
|
||||
self.btn_add.setText("")
|
||||
icon3 = QtGui.QIcon()
|
||||
icon3.addPixmap(QtGui.QPixmap(":/action/ico/edit-add.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.btn_add.setIcon(icon3)
|
||||
self.btn_add.setObjectName("btn_add")
|
||||
self.vl_edit.addWidget(self.btn_add)
|
||||
self.gridLayout.addLayout(self.vl_edit, 0, 1, 1, 1)
|
||||
self.gb_properties = QtWidgets.QGroupBox(diag_connections)
|
||||
self.gb_properties.setObjectName("gb_properties")
|
||||
self.formLayout = QtWidgets.QFormLayout(self.gb_properties)
|
||||
self.formLayout.setObjectName("formLayout")
|
||||
self.lbl_name = QtWidgets.QLabel(self.gb_properties)
|
||||
self.lbl_name.setObjectName("lbl_name")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lbl_name)
|
||||
self.lbl_folder = QtWidgets.QLabel(self.gb_properties)
|
||||
self.lbl_folder.setObjectName("lbl_folder")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.lbl_folder)
|
||||
self.lbl_address = QtWidgets.QLabel(self.gb_properties)
|
||||
self.lbl_address.setObjectName("lbl_address")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lbl_address)
|
||||
self.lbl_port = QtWidgets.QLabel(self.gb_properties)
|
||||
self.lbl_port.setObjectName("lbl_port")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.lbl_port)
|
||||
self.txt_name = QtWidgets.QLineEdit(self.gb_properties)
|
||||
self.txt_name.setObjectName("txt_name")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.txt_name)
|
||||
self.txt_address = QtWidgets.QLineEdit(self.gb_properties)
|
||||
self.txt_address.setObjectName("txt_address")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.txt_address)
|
||||
self.sbx_port = QtWidgets.QSpinBox(self.gb_properties)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.sbx_port.sizePolicy().hasHeightForWidth())
|
||||
self.sbx_port.setSizePolicy(sizePolicy)
|
||||
self.sbx_port.setMinimum(1)
|
||||
self.sbx_port.setMaximum(65535)
|
||||
self.sbx_port.setProperty("value", 55123)
|
||||
self.sbx_port.setObjectName("sbx_port")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.sbx_port)
|
||||
self.cbb_folder = QtWidgets.QComboBox(self.gb_properties)
|
||||
self.cbb_folder.setEditable(True)
|
||||
self.cbb_folder.setObjectName("cbb_folder")
|
||||
self.cbb_folder.addItem("")
|
||||
self.cbb_folder.setItemText(0, "")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.cbb_folder)
|
||||
self.lbl_timeout = QtWidgets.QLabel(self.gb_properties)
|
||||
self.lbl_timeout.setObjectName("lbl_timeout")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.lbl_timeout)
|
||||
self.sbx_timeout = QtWidgets.QSpinBox(self.gb_properties)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.sbx_timeout.sizePolicy().hasHeightForWidth())
|
||||
self.sbx_timeout.setSizePolicy(sizePolicy)
|
||||
self.sbx_timeout.setMinimum(5)
|
||||
self.sbx_timeout.setMaximum(30)
|
||||
self.sbx_timeout.setObjectName("sbx_timeout")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.sbx_timeout)
|
||||
self.gridLayout.addWidget(self.gb_properties, 1, 0, 1, 2)
|
||||
self.btn_box = QtWidgets.QDialogButtonBox(diag_connections)
|
||||
self.btn_box.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Discard|QtWidgets.QDialogButtonBox.Save)
|
||||
self.btn_box.setObjectName("btn_box")
|
||||
self.gridLayout.addWidget(self.btn_box, 2, 0, 1, 2)
|
||||
|
||||
self.retranslateUi(diag_connections)
|
||||
self.btn_box.accepted.connect(diag_connections.accept) # type: ignore
|
||||
self.btn_box.rejected.connect(diag_connections.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(diag_connections)
|
||||
|
||||
def retranslateUi(self, diag_connections):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
diag_connections.setWindowTitle(_translate("diag_connections", "Revolution Pi connections"))
|
||||
self.tre_connections.headerItem().setText(0, _translate("diag_connections", "Connection name"))
|
||||
self.tre_connections.headerItem().setText(1, _translate("diag_connections", "Address"))
|
||||
self.gb_properties.setTitle(_translate("diag_connections", "Connection properties"))
|
||||
self.lbl_name.setText(_translate("diag_connections", "Display name:"))
|
||||
self.lbl_folder.setText(_translate("diag_connections", "Sub folder:"))
|
||||
self.lbl_address.setText(_translate("diag_connections", "Address (DNS/IP):"))
|
||||
self.lbl_port.setText(_translate("diag_connections", "Port (Default {0}):"))
|
||||
self.lbl_timeout.setText(_translate("diag_connections", "Connection timeout:"))
|
||||
self.sbx_timeout.setSuffix(_translate("diag_connections", " sec."))
|
||||
from . import ressources_rc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
diag_connections = QtWidgets.QDialog()
|
||||
ui = Ui_diag_connections()
|
||||
ui.setupUi(diag_connections)
|
||||
diag_connections.show()
|
||||
sys.exit(app.exec_())
|
||||
159
src/revpicommander/ui/revpiprogram_ui.py
Normal file
159
src/revpicommander/ui/revpiprogram_ui.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'revpiprogram.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_diag_program(object):
|
||||
def setupUi(self, diag_program):
|
||||
diag_program.setObjectName("diag_program")
|
||||
diag_program.resize(400, 501)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(diag_program)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.gb_plc = QtWidgets.QGroupBox(diag_program)
|
||||
self.gb_plc.setObjectName("gb_plc")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.gb_plc)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.cbb_plcprogram = QtWidgets.QComboBox(self.gb_plc)
|
||||
self.cbb_plcprogram.setObjectName("cbb_plcprogram")
|
||||
self.gridLayout.addWidget(self.cbb_plcprogram, 1, 0, 1, 3)
|
||||
self.rbn_pythonversion_3 = QtWidgets.QRadioButton(self.gb_plc)
|
||||
self.rbn_pythonversion_3.setObjectName("rbn_pythonversion_3")
|
||||
self.gridLayout.addWidget(self.rbn_pythonversion_3, 3, 2, 1, 1)
|
||||
self.cbx_plcworkdir_set_uid = QtWidgets.QCheckBox(self.gb_plc)
|
||||
self.cbx_plcworkdir_set_uid.setObjectName("cbx_plcworkdir_set_uid")
|
||||
self.gridLayout.addWidget(self.cbx_plcworkdir_set_uid, 4, 0, 1, 3)
|
||||
self.lbl_plcprogram = QtWidgets.QLabel(self.gb_plc)
|
||||
self.lbl_plcprogram.setObjectName("lbl_plcprogram")
|
||||
self.gridLayout.addWidget(self.lbl_plcprogram, 0, 0, 1, 3)
|
||||
self.lbl_pythonversion = QtWidgets.QLabel(self.gb_plc)
|
||||
self.lbl_pythonversion.setObjectName("lbl_pythonversion")
|
||||
self.gridLayout.addWidget(self.lbl_pythonversion, 3, 0, 1, 1)
|
||||
self.rbn_pythonversion_2 = QtWidgets.QRadioButton(self.gb_plc)
|
||||
self.rbn_pythonversion_2.setObjectName("rbn_pythonversion_2")
|
||||
self.gridLayout.addWidget(self.rbn_pythonversion_2, 3, 1, 1, 1)
|
||||
self.lbl_plcarguments = QtWidgets.QLabel(self.gb_plc)
|
||||
self.lbl_plcarguments.setObjectName("lbl_plcarguments")
|
||||
self.gridLayout.addWidget(self.lbl_plcarguments, 2, 0, 1, 1)
|
||||
self.txt_plcarguments = QtWidgets.QLineEdit(self.gb_plc)
|
||||
self.txt_plcarguments.setObjectName("txt_plcarguments")
|
||||
self.gridLayout.addWidget(self.txt_plcarguments, 2, 1, 1, 2)
|
||||
self.sbx_plcprogram_watchdog = QtWidgets.QSpinBox(self.gb_plc)
|
||||
self.sbx_plcprogram_watchdog.setMaximum(600)
|
||||
self.sbx_plcprogram_watchdog.setObjectName("sbx_plcprogram_watchdog")
|
||||
self.gridLayout.addWidget(self.sbx_plcprogram_watchdog, 5, 2, 1, 1)
|
||||
self.lbl_plcprogram_watchdog = QtWidgets.QLabel(self.gb_plc)
|
||||
self.lbl_plcprogram_watchdog.setObjectName("lbl_plcprogram_watchdog")
|
||||
self.gridLayout.addWidget(self.lbl_plcprogram_watchdog, 5, 0, 1, 2)
|
||||
self.verticalLayout.addWidget(self.gb_plc)
|
||||
self.cb_transfair = QtWidgets.QGroupBox(diag_program)
|
||||
self.cb_transfair.setObjectName("cb_transfair")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.cb_transfair)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.cbb_format = QtWidgets.QComboBox(self.cb_transfair)
|
||||
self.cbb_format.setObjectName("cbb_format")
|
||||
self.cbb_format.addItem("")
|
||||
self.cbb_format.addItem("")
|
||||
self.gridLayout_2.addWidget(self.cbb_format, 0, 1, 1, 1)
|
||||
self.btn_program_upload = QtWidgets.QPushButton(self.cb_transfair)
|
||||
self.btn_program_upload.setObjectName("btn_program_upload")
|
||||
self.gridLayout_2.addWidget(self.btn_program_upload, 3, 1, 1, 1)
|
||||
self.btn_program_download = QtWidgets.QPushButton(self.cb_transfair)
|
||||
self.btn_program_download.setObjectName("btn_program_download")
|
||||
self.gridLayout_2.addWidget(self.btn_program_download, 3, 0, 1, 1)
|
||||
self.lbl_format = QtWidgets.QLabel(self.cb_transfair)
|
||||
self.lbl_format.setObjectName("lbl_format")
|
||||
self.gridLayout_2.addWidget(self.lbl_format, 0, 0, 1, 1)
|
||||
self.cbx_pictory = QtWidgets.QCheckBox(self.cb_transfair)
|
||||
self.cbx_pictory.setObjectName("cbx_pictory")
|
||||
self.gridLayout_2.addWidget(self.cbx_pictory, 1, 0, 1, 2)
|
||||
self.cbx_clear = QtWidgets.QCheckBox(self.cb_transfair)
|
||||
self.cbx_clear.setObjectName("cbx_clear")
|
||||
self.gridLayout_2.addWidget(self.cbx_clear, 2, 0, 1, 2)
|
||||
self.verticalLayout.addWidget(self.cb_transfair)
|
||||
self.gb_control = QtWidgets.QGroupBox(diag_program)
|
||||
self.gb_control.setObjectName("gb_control")
|
||||
self.gridLayout_3 = QtWidgets.QGridLayout(self.gb_control)
|
||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
||||
self.btn_procimg_download = QtWidgets.QPushButton(self.gb_control)
|
||||
self.btn_procimg_download.setObjectName("btn_procimg_download")
|
||||
self.gridLayout_3.addWidget(self.btn_procimg_download, 1, 1, 1, 1)
|
||||
self.btn_pictory_download = QtWidgets.QPushButton(self.gb_control)
|
||||
self.btn_pictory_download.setObjectName("btn_pictory_download")
|
||||
self.gridLayout_3.addWidget(self.btn_pictory_download, 0, 1, 1, 1)
|
||||
self.btn_pictory_upload = QtWidgets.QPushButton(self.gb_control)
|
||||
self.btn_pictory_upload.setObjectName("btn_pictory_upload")
|
||||
self.gridLayout_3.addWidget(self.btn_pictory_upload, 0, 2, 1, 1)
|
||||
self.lbl_pictory = QtWidgets.QLabel(self.gb_control)
|
||||
self.lbl_pictory.setObjectName("lbl_pictory")
|
||||
self.gridLayout_3.addWidget(self.lbl_pictory, 0, 0, 1, 1)
|
||||
self.lbl_procimg = QtWidgets.QLabel(self.gb_control)
|
||||
self.lbl_procimg.setObjectName("lbl_procimg")
|
||||
self.gridLayout_3.addWidget(self.lbl_procimg, 1, 0, 1, 1)
|
||||
self.verticalLayout.addWidget(self.gb_control)
|
||||
self.btn_box = QtWidgets.QDialogButtonBox(diag_program)
|
||||
self.btn_box.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
|
||||
self.btn_box.setObjectName("btn_box")
|
||||
self.verticalLayout.addWidget(self.btn_box)
|
||||
|
||||
self.retranslateUi(diag_program)
|
||||
self.btn_box.accepted.connect(diag_program.accept) # type: ignore
|
||||
self.btn_box.rejected.connect(diag_program.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(diag_program)
|
||||
diag_program.setTabOrder(self.cbb_plcprogram, self.txt_plcarguments)
|
||||
diag_program.setTabOrder(self.txt_plcarguments, self.rbn_pythonversion_2)
|
||||
diag_program.setTabOrder(self.rbn_pythonversion_2, self.rbn_pythonversion_3)
|
||||
diag_program.setTabOrder(self.rbn_pythonversion_3, self.cbx_plcworkdir_set_uid)
|
||||
diag_program.setTabOrder(self.cbx_plcworkdir_set_uid, self.cbb_format)
|
||||
diag_program.setTabOrder(self.cbb_format, self.cbx_pictory)
|
||||
diag_program.setTabOrder(self.cbx_pictory, self.cbx_clear)
|
||||
diag_program.setTabOrder(self.cbx_clear, self.btn_program_download)
|
||||
diag_program.setTabOrder(self.btn_program_download, self.btn_program_upload)
|
||||
diag_program.setTabOrder(self.btn_program_upload, self.btn_pictory_download)
|
||||
diag_program.setTabOrder(self.btn_pictory_download, self.btn_pictory_upload)
|
||||
diag_program.setTabOrder(self.btn_pictory_upload, self.btn_procimg_download)
|
||||
|
||||
def retranslateUi(self, diag_program):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
diag_program.setWindowTitle(_translate("diag_program", "PLC program"))
|
||||
self.gb_plc.setTitle(_translate("diag_program", "PLC program"))
|
||||
self.rbn_pythonversion_3.setText(_translate("diag_program", "Python 3"))
|
||||
self.cbx_plcworkdir_set_uid.setText(_translate("diag_program", "Set write permissions for plc program to workdirectory"))
|
||||
self.lbl_plcprogram.setText(_translate("diag_program", "Python PLC start program:"))
|
||||
self.lbl_pythonversion.setText(_translate("diag_program", "Python version:"))
|
||||
self.rbn_pythonversion_2.setText(_translate("diag_program", "Python 2"))
|
||||
self.lbl_plcarguments.setText(_translate("diag_program", "Program arguments:"))
|
||||
self.sbx_plcprogram_watchdog.setSuffix(_translate("diag_program", " sec."))
|
||||
self.lbl_plcprogram_watchdog.setText(_translate("diag_program", "Software watchdog (0=disabled):"))
|
||||
self.cb_transfair.setTitle(_translate("diag_program", "Transfair PLC program"))
|
||||
self.cbb_format.setItemText(0, _translate("diag_program", "ZIP archive"))
|
||||
self.cbb_format.setItemText(1, _translate("diag_program", "TGZ archive"))
|
||||
self.btn_program_upload.setText(_translate("diag_program", "Upload"))
|
||||
self.btn_program_download.setText(_translate("diag_program", "Download"))
|
||||
self.lbl_format.setText(_translate("diag_program", "Transfair format:"))
|
||||
self.cbx_pictory.setText(_translate("diag_program", "Including piCtory configuration"))
|
||||
self.cbx_clear.setText(_translate("diag_program", "Remove all files on Revolution Pi before upload"))
|
||||
self.gb_control.setTitle(_translate("diag_program", "Control files"))
|
||||
self.btn_procimg_download.setText(_translate("diag_program", "Download"))
|
||||
self.btn_pictory_download.setText(_translate("diag_program", "Download"))
|
||||
self.btn_pictory_upload.setText(_translate("diag_program", "Upload"))
|
||||
self.lbl_pictory.setText(_translate("diag_program", "piCtory configuraiton"))
|
||||
self.lbl_procimg.setText(_translate("diag_program", "Process image from piControl0"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
diag_program = QtWidgets.QDialog()
|
||||
ui = Ui_diag_program()
|
||||
ui.setupUi(diag_program)
|
||||
diag_program.show()
|
||||
sys.exit(app.exec_())
|
||||
136
src/revpicommander/ui/simulator_ui.py
Normal file
136
src/revpicommander/ui/simulator_ui.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'simulator.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_diag_simulator(object):
|
||||
def setupUi(self, diag_simulator):
|
||||
diag_simulator.setObjectName("diag_simulator")
|
||||
diag_simulator.resize(522, 500)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(diag_simulator)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.gb_settings = QtWidgets.QGroupBox(diag_simulator)
|
||||
self.gb_settings.setObjectName("gb_settings")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.gb_settings)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.lbl_history = QtWidgets.QLabel(self.gb_settings)
|
||||
self.lbl_history.setObjectName("lbl_history")
|
||||
self.gridLayout.addWidget(self.lbl_history, 0, 0, 1, 1)
|
||||
self.lbl_configrsc = QtWidgets.QLabel(self.gb_settings)
|
||||
self.lbl_configrsc.setObjectName("lbl_configrsc")
|
||||
self.gridLayout.addWidget(self.lbl_configrsc, 1, 0, 1, 1)
|
||||
self.txt_configrsc = QtWidgets.QLineEdit(self.gb_settings)
|
||||
self.txt_configrsc.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.txt_configrsc.setText("")
|
||||
self.txt_configrsc.setObjectName("txt_configrsc")
|
||||
self.gridLayout.addWidget(self.txt_configrsc, 1, 1, 1, 1)
|
||||
self.btn_configrsc = QtWidgets.QPushButton(self.gb_settings)
|
||||
self.btn_configrsc.setObjectName("btn_configrsc")
|
||||
self.gridLayout.addWidget(self.btn_configrsc, 1, 2, 1, 1)
|
||||
self.lbl_procimg = QtWidgets.QLabel(self.gb_settings)
|
||||
self.lbl_procimg.setObjectName("lbl_procimg")
|
||||
self.gridLayout.addWidget(self.lbl_procimg, 2, 0, 1, 1)
|
||||
self.txt_procimg = QtWidgets.QLineEdit(self.gb_settings)
|
||||
self.txt_procimg.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.txt_procimg.setText("")
|
||||
self.txt_procimg.setObjectName("txt_procimg")
|
||||
self.gridLayout.addWidget(self.txt_procimg, 2, 1, 1, 1)
|
||||
self.lbl_stop = QtWidgets.QLabel(self.gb_settings)
|
||||
self.lbl_stop.setObjectName("lbl_stop")
|
||||
self.gridLayout.addWidget(self.lbl_stop, 3, 0, 1, 1)
|
||||
self.lbl_restart = QtWidgets.QLabel(self.gb_settings)
|
||||
self.lbl_restart.setObjectName("lbl_restart")
|
||||
self.gridLayout.addWidget(self.lbl_restart, 4, 0, 1, 1)
|
||||
self.cbb_history = QtWidgets.QComboBox(self.gb_settings)
|
||||
self.cbb_history.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
|
||||
self.cbb_history.setObjectName("cbb_history")
|
||||
self.gridLayout.addWidget(self.cbb_history, 0, 1, 1, 2)
|
||||
self.cbx_stop_remove = QtWidgets.QCheckBox(self.gb_settings)
|
||||
self.cbx_stop_remove.setObjectName("cbx_stop_remove")
|
||||
self.gridLayout.addWidget(self.cbx_stop_remove, 3, 1, 1, 2)
|
||||
self.rb_restart_pictory = QtWidgets.QRadioButton(self.gb_settings)
|
||||
self.rb_restart_pictory.setChecked(True)
|
||||
self.rb_restart_pictory.setObjectName("rb_restart_pictory")
|
||||
self.gridLayout.addWidget(self.rb_restart_pictory, 4, 1, 1, 2)
|
||||
self.rb_restart_zero = QtWidgets.QRadioButton(self.gb_settings)
|
||||
self.rb_restart_zero.setObjectName("rb_restart_zero")
|
||||
self.gridLayout.addWidget(self.rb_restart_zero, 5, 1, 1, 2)
|
||||
self.verticalLayout.addWidget(self.gb_settings)
|
||||
self.gb_info = QtWidgets.QGroupBox(diag_simulator)
|
||||
self.gb_info.setObjectName("gb_info")
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.gb_info)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.lbl_info = QtWidgets.QLabel(self.gb_info)
|
||||
self.lbl_info.setWordWrap(True)
|
||||
self.lbl_info.setObjectName("lbl_info")
|
||||
self.verticalLayout_2.addWidget(self.lbl_info)
|
||||
self.txt_info = QtWidgets.QPlainTextEdit(self.gb_info)
|
||||
self.txt_info.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
|
||||
self.txt_info.setReadOnly(True)
|
||||
self.txt_info.setPlainText("")
|
||||
self.txt_info.setObjectName("txt_info")
|
||||
self.verticalLayout_2.addWidget(self.txt_info)
|
||||
self.verticalLayout.addWidget(self.gb_info)
|
||||
self.btn_start_pictory = QtWidgets.QPushButton(diag_simulator)
|
||||
self.btn_start_pictory.setShortcut("Ctrl+1")
|
||||
self.btn_start_pictory.setObjectName("btn_start_pictory")
|
||||
self.verticalLayout.addWidget(self.btn_start_pictory)
|
||||
self.btn_start_empty = QtWidgets.QPushButton(diag_simulator)
|
||||
self.btn_start_empty.setShortcut("Ctrl+2")
|
||||
self.btn_start_empty.setObjectName("btn_start_empty")
|
||||
self.verticalLayout.addWidget(self.btn_start_empty)
|
||||
self.btn_start_nochange = QtWidgets.QPushButton(diag_simulator)
|
||||
self.btn_start_nochange.setShortcut("Ctrl+3")
|
||||
self.btn_start_nochange.setObjectName("btn_start_nochange")
|
||||
self.verticalLayout.addWidget(self.btn_start_nochange)
|
||||
|
||||
self.retranslateUi(diag_simulator)
|
||||
self.btn_start_empty.clicked.connect(diag_simulator.accept) # type: ignore
|
||||
self.btn_start_nochange.clicked.connect(diag_simulator.accept) # type: ignore
|
||||
self.btn_start_pictory.clicked.connect(diag_simulator.accept) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(diag_simulator)
|
||||
diag_simulator.setTabOrder(self.cbb_history, self.btn_configrsc)
|
||||
diag_simulator.setTabOrder(self.btn_configrsc, self.cbx_stop_remove)
|
||||
diag_simulator.setTabOrder(self.cbx_stop_remove, self.rb_restart_pictory)
|
||||
diag_simulator.setTabOrder(self.rb_restart_pictory, self.rb_restart_zero)
|
||||
diag_simulator.setTabOrder(self.rb_restart_zero, self.txt_info)
|
||||
diag_simulator.setTabOrder(self.txt_info, self.btn_start_pictory)
|
||||
diag_simulator.setTabOrder(self.btn_start_pictory, self.btn_start_empty)
|
||||
diag_simulator.setTabOrder(self.btn_start_empty, self.btn_start_nochange)
|
||||
|
||||
def retranslateUi(self, diag_simulator):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
diag_simulator.setWindowTitle(_translate("diag_simulator", "piControl simulator"))
|
||||
self.gb_settings.setTitle(_translate("diag_simulator", "Simulator settings"))
|
||||
self.lbl_history.setText(_translate("diag_simulator", "Last used:"))
|
||||
self.lbl_configrsc.setText(_translate("diag_simulator", "piCtory file:"))
|
||||
self.btn_configrsc.setText(_translate("diag_simulator", "select..."))
|
||||
self.lbl_procimg.setText(_translate("diag_simulator", "Process image:"))
|
||||
self.lbl_stop.setText(_translate("diag_simulator", "Stop action:"))
|
||||
self.lbl_restart.setText(_translate("diag_simulator", "Restart action:"))
|
||||
self.cbx_stop_remove.setText(_translate("diag_simulator", "Remove process image file"))
|
||||
self.rb_restart_pictory.setText(_translate("diag_simulator", "Restore piCtory default values"))
|
||||
self.rb_restart_zero.setText(_translate("diag_simulator", "Reset everything to ZERO"))
|
||||
self.gb_info.setTitle(_translate("diag_simulator", "RevPiModIO integration"))
|
||||
self.lbl_info.setText(_translate("diag_simulator", "You can work with this simulator if you call RevPiModIO with this additional parameters:"))
|
||||
self.btn_start_pictory.setText(_translate("diag_simulator", "Start with piCtory default values"))
|
||||
self.btn_start_empty.setText(_translate("diag_simulator", "Start with empty process image"))
|
||||
self.btn_start_nochange.setText(_translate("diag_simulator", "Start without changing actual process image"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
diag_simulator = QtWidgets.QDialog()
|
||||
ui = Ui_diag_simulator()
|
||||
ui.setupUi(diag_simulator)
|
||||
diag_simulator.show()
|
||||
sys.exit(app.exec_())
|
||||
Reference in New Issue
Block a user