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:
2023-01-04 18:12:59 +01:00
parent 2400bd6951
commit 3f2f3e0478
73 changed files with 2606 additions and 2584 deletions

View File

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

View File

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

View File

@@ -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()
)

View 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()

View File

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

View File

@@ -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()

View 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)

View 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."""

Binary file not shown.

File diff suppressed because it is too large Load Diff

View 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
)

View 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()

View 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())

View 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()

View 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)

View 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())

View 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()

View 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 # # # # #

View 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 # # # # #

View File

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

View File

@@ -0,0 +1,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_())

View 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_())

View File

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'backgroundworker.ui'
#
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_diag_backgroundworker(object):
def setupUi(self, diag_backgroundworker):
diag_backgroundworker.setObjectName("diag_backgroundworker")
diag_backgroundworker.resize(418, 97)
diag_backgroundworker.setModal(True)
self.verticalLayout = QtWidgets.QVBoxLayout(diag_backgroundworker)
self.verticalLayout.setObjectName("verticalLayout")
self.lbl_status = QtWidgets.QLabel(diag_backgroundworker)
self.lbl_status.setText("Status message...")
self.lbl_status.setObjectName("lbl_status")
self.verticalLayout.addWidget(self.lbl_status)
self.pgb_status = QtWidgets.QProgressBar(diag_backgroundworker)
self.pgb_status.setMinimumSize(QtCore.QSize(400, 0))
self.pgb_status.setObjectName("pgb_status")
self.verticalLayout.addWidget(self.pgb_status)
self.btn_box = QtWidgets.QDialogButtonBox(diag_backgroundworker)
self.btn_box.setOrientation(QtCore.Qt.Horizontal)
self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel)
self.btn_box.setCenterButtons(True)
self.btn_box.setObjectName("btn_box")
self.verticalLayout.addWidget(self.btn_box)
self.retranslateUi(diag_backgroundworker)
QtCore.QMetaObject.connectSlotsByName(diag_backgroundworker)
def retranslateUi(self, diag_backgroundworker):
_translate = QtCore.QCoreApplication.translate
diag_backgroundworker.setWindowTitle(_translate("diag_backgroundworker", "File transfer..."))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
diag_backgroundworker = QtWidgets.QDialog()
ui = Ui_diag_backgroundworker()
ui.setupUi(diag_backgroundworker)
diag_backgroundworker.show()
sys.exit(app.exec_())

View File

@@ -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_())

View 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_())

View 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_())

View 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_())

File diff suppressed because it is too large Load Diff

View 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_())

View 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_())

View 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_())

View 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_())

View 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_())

View 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_())

View File

@@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'simulator.ui'
#
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_diag_simulator(object):
def setupUi(self, diag_simulator):
diag_simulator.setObjectName("diag_simulator")
diag_simulator.resize(522, 500)
self.verticalLayout = QtWidgets.QVBoxLayout(diag_simulator)
self.verticalLayout.setObjectName("verticalLayout")
self.gb_settings = QtWidgets.QGroupBox(diag_simulator)
self.gb_settings.setObjectName("gb_settings")
self.gridLayout = QtWidgets.QGridLayout(self.gb_settings)
self.gridLayout.setObjectName("gridLayout")
self.lbl_history = QtWidgets.QLabel(self.gb_settings)
self.lbl_history.setObjectName("lbl_history")
self.gridLayout.addWidget(self.lbl_history, 0, 0, 1, 1)
self.lbl_configrsc = QtWidgets.QLabel(self.gb_settings)
self.lbl_configrsc.setObjectName("lbl_configrsc")
self.gridLayout.addWidget(self.lbl_configrsc, 1, 0, 1, 1)
self.txt_configrsc = QtWidgets.QLineEdit(self.gb_settings)
self.txt_configrsc.setFocusPolicy(QtCore.Qt.NoFocus)
self.txt_configrsc.setText("")
self.txt_configrsc.setObjectName("txt_configrsc")
self.gridLayout.addWidget(self.txt_configrsc, 1, 1, 1, 1)
self.btn_configrsc = QtWidgets.QPushButton(self.gb_settings)
self.btn_configrsc.setObjectName("btn_configrsc")
self.gridLayout.addWidget(self.btn_configrsc, 1, 2, 1, 1)
self.lbl_procimg = QtWidgets.QLabel(self.gb_settings)
self.lbl_procimg.setObjectName("lbl_procimg")
self.gridLayout.addWidget(self.lbl_procimg, 2, 0, 1, 1)
self.txt_procimg = QtWidgets.QLineEdit(self.gb_settings)
self.txt_procimg.setFocusPolicy(QtCore.Qt.NoFocus)
self.txt_procimg.setText("")
self.txt_procimg.setObjectName("txt_procimg")
self.gridLayout.addWidget(self.txt_procimg, 2, 1, 1, 1)
self.lbl_stop = QtWidgets.QLabel(self.gb_settings)
self.lbl_stop.setObjectName("lbl_stop")
self.gridLayout.addWidget(self.lbl_stop, 3, 0, 1, 1)
self.lbl_restart = QtWidgets.QLabel(self.gb_settings)
self.lbl_restart.setObjectName("lbl_restart")
self.gridLayout.addWidget(self.lbl_restart, 4, 0, 1, 1)
self.cbb_history = QtWidgets.QComboBox(self.gb_settings)
self.cbb_history.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
self.cbb_history.setObjectName("cbb_history")
self.gridLayout.addWidget(self.cbb_history, 0, 1, 1, 2)
self.cbx_stop_remove = QtWidgets.QCheckBox(self.gb_settings)
self.cbx_stop_remove.setObjectName("cbx_stop_remove")
self.gridLayout.addWidget(self.cbx_stop_remove, 3, 1, 1, 2)
self.rb_restart_pictory = QtWidgets.QRadioButton(self.gb_settings)
self.rb_restart_pictory.setChecked(True)
self.rb_restart_pictory.setObjectName("rb_restart_pictory")
self.gridLayout.addWidget(self.rb_restart_pictory, 4, 1, 1, 2)
self.rb_restart_zero = QtWidgets.QRadioButton(self.gb_settings)
self.rb_restart_zero.setObjectName("rb_restart_zero")
self.gridLayout.addWidget(self.rb_restart_zero, 5, 1, 1, 2)
self.verticalLayout.addWidget(self.gb_settings)
self.gb_info = QtWidgets.QGroupBox(diag_simulator)
self.gb_info.setObjectName("gb_info")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.gb_info)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.lbl_info = QtWidgets.QLabel(self.gb_info)
self.lbl_info.setWordWrap(True)
self.lbl_info.setObjectName("lbl_info")
self.verticalLayout_2.addWidget(self.lbl_info)
self.txt_info = QtWidgets.QPlainTextEdit(self.gb_info)
self.txt_info.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
self.txt_info.setReadOnly(True)
self.txt_info.setPlainText("")
self.txt_info.setObjectName("txt_info")
self.verticalLayout_2.addWidget(self.txt_info)
self.verticalLayout.addWidget(self.gb_info)
self.btn_start_pictory = QtWidgets.QPushButton(diag_simulator)
self.btn_start_pictory.setShortcut("Ctrl+1")
self.btn_start_pictory.setObjectName("btn_start_pictory")
self.verticalLayout.addWidget(self.btn_start_pictory)
self.btn_start_empty = QtWidgets.QPushButton(diag_simulator)
self.btn_start_empty.setShortcut("Ctrl+2")
self.btn_start_empty.setObjectName("btn_start_empty")
self.verticalLayout.addWidget(self.btn_start_empty)
self.btn_start_nochange = QtWidgets.QPushButton(diag_simulator)
self.btn_start_nochange.setShortcut("Ctrl+3")
self.btn_start_nochange.setObjectName("btn_start_nochange")
self.verticalLayout.addWidget(self.btn_start_nochange)
self.retranslateUi(diag_simulator)
self.btn_start_empty.clicked.connect(diag_simulator.accept) # type: ignore
self.btn_start_nochange.clicked.connect(diag_simulator.accept) # type: ignore
self.btn_start_pictory.clicked.connect(diag_simulator.accept) # type: ignore
QtCore.QMetaObject.connectSlotsByName(diag_simulator)
diag_simulator.setTabOrder(self.cbb_history, self.btn_configrsc)
diag_simulator.setTabOrder(self.btn_configrsc, self.cbx_stop_remove)
diag_simulator.setTabOrder(self.cbx_stop_remove, self.rb_restart_pictory)
diag_simulator.setTabOrder(self.rb_restart_pictory, self.rb_restart_zero)
diag_simulator.setTabOrder(self.rb_restart_zero, self.txt_info)
diag_simulator.setTabOrder(self.txt_info, self.btn_start_pictory)
diag_simulator.setTabOrder(self.btn_start_pictory, self.btn_start_empty)
diag_simulator.setTabOrder(self.btn_start_empty, self.btn_start_nochange)
def retranslateUi(self, diag_simulator):
_translate = QtCore.QCoreApplication.translate
diag_simulator.setWindowTitle(_translate("diag_simulator", "piControl simulator"))
self.gb_settings.setTitle(_translate("diag_simulator", "Simulator settings"))
self.lbl_history.setText(_translate("diag_simulator", "Last used:"))
self.lbl_configrsc.setText(_translate("diag_simulator", "piCtory file:"))
self.btn_configrsc.setText(_translate("diag_simulator", "select..."))
self.lbl_procimg.setText(_translate("diag_simulator", "Process image:"))
self.lbl_stop.setText(_translate("diag_simulator", "Stop action:"))
self.lbl_restart.setText(_translate("diag_simulator", "Restart action:"))
self.cbx_stop_remove.setText(_translate("diag_simulator", "Remove process image file"))
self.rb_restart_pictory.setText(_translate("diag_simulator", "Restore piCtory default values"))
self.rb_restart_zero.setText(_translate("diag_simulator", "Reset everything to ZERO"))
self.gb_info.setTitle(_translate("diag_simulator", "RevPiModIO integration"))
self.lbl_info.setText(_translate("diag_simulator", "You can work with this simulator if you call RevPiModIO with this additional parameters:"))
self.btn_start_pictory.setText(_translate("diag_simulator", "Start with piCtory default values"))
self.btn_start_empty.setText(_translate("diag_simulator", "Start with empty process image"))
self.btn_start_nochange.setText(_translate("diag_simulator", "Start without changing actual process image"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
diag_simulator = QtWidgets.QDialog()
ui = Ui_diag_simulator()
ui.setupUi(diag_simulator)
diag_simulator.show()
sys.exit(app.exec_())