diff --git a/.gitignore b/.gitignore index b6e4761..0bd544e 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,4 @@ dmypy.json # Pyre type checker .pyre/ +/make.conf diff --git a/.idea/$CACHE_FILE$ b/.idea/$CACHE_FILE$ new file mode 100644 index 0000000..3a1e22c --- /dev/null +++ b/.idea/$CACHE_FILE$ @@ -0,0 +1,17 @@ + + + + + + + + + + + Angular + + + + + + \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..4d48d65 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,31 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..3999087 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f7bb2b0 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/revpicommander.iml b/.idea/revpicommander.iml new file mode 100644 index 0000000..56db104 --- /dev/null +++ b/.idea/revpicommander.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..6ea99c5 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..71a43b9 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include MANIFEST.in +include stdeb.cfg +recursive-include data * +recursive-include include *.py +recursive-include lib *.py +recursive-include revpicommander *.py *.qm +global-exclude *.pyc +include LICENSE.txt diff --git a/data/revpicommander b/data/revpicommander new file mode 100644 index 0000000..924d6c9 --- /dev/null +++ b/data/revpicommander @@ -0,0 +1,3 @@ +#!/bin/sh + +exec "/usr/share/revpicommander/revpicommander.py" "$@" diff --git a/data/revpicommander.desktop b/data/revpicommander.desktop new file mode 100644 index 0000000..cc075fd --- /dev/null +++ b/data/revpicommander.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=RevPi PLC Commander +Comment=Controls the Python PLC program on your Revolution PI +Name[de]=RevPi PLC Steuerung +Comment[de]=Kontrolliert das Python PLC Programm auf dem Revolution PI +Exec=/usr/bin/revpicommander +Icon=revpicommander +Terminal=false +Type=Application +Categories=Application; +#StartupNotify=true diff --git a/data/revpicommander.ico b/data/revpicommander.ico new file mode 100644 index 0000000..4602e2f Binary files /dev/null and b/data/revpicommander.ico differ diff --git a/data/revpicommander.png b/data/revpicommander.png new file mode 100644 index 0000000..18efbe8 Binary files /dev/null and b/data/revpicommander.png differ diff --git a/include/ui/aclmanager_ui.py b/include/ui/aclmanager_ui.py new file mode 100644 index 0000000..6763acc --- /dev/null +++ b/include/ui/aclmanager_ui.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'aclmanager.ui' +# +# Created by: PyQt5 UI code generator 5.10.1 +# +# WARNING! All changes made in this file will be lost! + +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) + self.btn_box.rejected.connect(diag_aclmanager.reject) + 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_()) + diff --git a/include/ui/avahisearch_ui.py b/include/ui/avahisearch_ui.py new file mode 100644 index 0000000..2f2df3f --- /dev/null +++ b/include/ui/avahisearch_ui.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'avahisearch.ui' +# +# Created by: PyQt5 UI code generator 5.10.1 +# +# WARNING! All changes made in this file will be lost! + +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.retranslateUi(diag_search) + self.btn_box.rejected.connect(diag_search.reject) + 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")) + +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_()) + diff --git a/include/ui/debugcontrol_ui.py b/include/ui/debugcontrol_ui.py new file mode 100644 index 0000000..86441fc --- /dev/null +++ b/include/ui/debugcontrol_ui.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'debugcontrol.ui' +# +# Created by: PyQt5 UI code generator 5.10.1 +# +# WARNING! All changes made in this file will be lost! + +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_()) + diff --git a/include/ui/debugios_ui.py b/include/ui/debugios_ui.py new file mode 100644 index 0000000..8fa2ff8 --- /dev/null +++ b/include/ui/debugios_ui.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'debugios.ui' +# +# Created by: PyQt5 UI code generator 5.10.1 +# +# WARNING! All changes made in this file will be lost! + +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_()) + diff --git a/include/ui/mqttmanager_ui.py b/include/ui/mqttmanager_ui.py new file mode 100644 index 0000000..f09a043 --- /dev/null +++ b/include/ui/mqttmanager_ui.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'mqttmanager.ui' +# +# Created by: PyQt5 UI code generator 5.10.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_diag_mqtt(object): + def setupUi(self, diag_mqtt): + diag_mqtt.setObjectName("diag_mqtt") + diag_mqtt.resize(492, 704) + 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) + self.btn_box.rejected.connect(diag_mqtt.reject) + 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.

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", "Brocker 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_()) + diff --git a/include/ui/ressources_rc.py b/include/ui/ressources_rc.py new file mode 100644 index 0000000..85472ad --- /dev/null +++ b/include/ui/ressources_rc.py @@ -0,0 +1,2761 @@ +# -*- coding: utf-8 -*- + +# Resource object code +# +# Created by: The Resource Compiler for PyQt5 (Qt v5.9.5) +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore + +qt_resource_data = b"\ +\x00\x00\x03\xca\ +\x00\ +\x00\x10\xbe\x78\x9c\xed\xd7\x7f\x4c\x1b\x65\x1c\xc7\xf1\xa2\xf1\ +\x2f\x93\xb6\x90\x65\xc2\xc8\x58\xbb\x29\x16\xa3\x2e\x41\xb7\xcc\ +\x4d\x03\xd1\x64\x59\x50\xa7\xc6\x28\x4b\x16\x61\x10\x47\x83\x2e\ +\x2e\x96\x49\x57\xd8\xea\x36\x74\x64\x53\x16\x64\x14\x28\x65\x2b\ +\x63\xcc\x95\x01\x2d\x51\x9c\xc0\xc8\x40\xc8\x00\x65\x9d\x1d\x3f\ +\x96\x2c\x46\x98\xc1\x80\x94\xeb\xaf\xb5\xd0\xbb\x52\xf8\xf8\x3c\ +\xa4\x6d\x18\xfe\x48\x4c\xeb\x69\xa2\xdf\xe6\x9d\x4b\x9e\x27\x77\ +\xaf\x5e\xfa\x47\xef\x04\x82\x28\xf2\x91\x48\x04\x4b\x63\x10\x0b\ +\x04\xab\xc9\x51\x46\xa2\x4b\xa9\xa4\x28\x41\x60\x53\x2c\xf8\x37\ +\x4f\x54\xa0\xfb\x22\xd5\xba\xba\xb4\xa2\xe8\xd2\x38\xf6\x41\x4d\ +\x02\xfb\x40\x6d\x72\x51\x60\x3d\xe8\x04\xe7\x7e\x92\x90\x14\x47\ +\x5a\x4b\x4a\x88\x54\x31\xc5\xf1\xdc\xfa\xaa\x44\x48\xb5\x32\x88\ +\x4f\x49\xb9\xc0\xfa\xda\x80\x25\x5c\x66\x6f\x21\xa5\x93\x32\x48\ +\x99\x91\x4a\xa8\x12\xe1\xe1\xc6\x2d\xd8\xf8\xf5\xcb\x58\x7d\x2c\ +\x01\xcb\xf6\xd2\x03\xa6\x50\x9d\xf3\xda\x1a\xef\xcd\x16\x87\xff\ +\xd6\x65\x44\x3a\xd1\xfb\xd1\xc4\xde\x89\x6d\xd7\xde\xc6\x43\x2a\ +\xc9\x3d\x7b\xd4\xa4\xb6\xb5\x49\xfd\x88\xb7\xad\xd8\xef\xe9\x29\ +\x9d\xbb\xdb\x55\x82\x70\xfa\x40\xf7\x02\xc4\x6a\x21\x44\x07\x45\ +\xc4\x16\x23\x41\x21\xc1\x13\x5f\xa5\xe1\xe9\xce\x37\xb1\x41\xf9\ +\x38\xe2\xd5\x49\x90\x94\x24\x43\xa6\x7f\x0e\x9f\xb4\xca\x41\x6d\ +\x4f\x53\xfe\xa6\x59\xa3\x6a\x7e\xfe\x66\x83\x9b\x33\x9f\x47\x38\ +\x45\x7f\x2c\x42\x6e\x6b\x2e\xf6\xb6\xe5\x60\xff\x37\x79\xc8\xeb\ +\x3b\x0c\x79\xbf\x12\x6f\xf5\x29\xf0\x7a\x97\x1c\x49\x0d\xcf\x20\ +\xa6\x22\x16\x71\x9f\xae\x41\xf2\x17\xaf\x80\xda\xae\x0b\xef\xa5\ +\xb8\x8d\x05\xdc\xc2\x48\xb3\xcb\xf7\xfd\x45\x84\xd3\x86\x52\x29\ +\x6a\xcc\x35\x38\x6d\x3e\x8d\x72\x4b\x05\x2a\x86\x74\x28\x1f\xd1\ +\x41\x33\x7a\x06\x55\x23\x7a\x68\x87\xce\x22\xef\xea\x01\x6c\xad\ +\xde\x86\xed\x57\xb2\x41\x6d\xc7\xf9\x77\x77\xde\x35\x15\xb0\x0b\ +\xa3\x46\xa7\xcf\x42\xae\x13\x46\xcd\xbd\xc5\x78\xac\x2c\x09\x31\ +\xea\x55\x58\xa5\x8c\xc5\xfa\x83\x32\x18\x6e\x37\x92\x9a\x20\x53\ +\x6f\x86\xf4\xf8\x26\x3c\x5a\x99\x8a\x1d\x2d\x59\xb8\xd4\xa6\x5a\ +\xa4\x36\x53\x2b\xdf\xed\x32\x15\x7a\x17\x46\x4d\x0e\x9f\xc5\x80\ +\x48\x16\x7b\x48\x8a\xf6\xf1\x0e\x74\x90\xa4\xc7\x9f\x0a\xad\x93\ +\xdf\xda\x4f\x4d\x6a\x4f\xeb\xf6\xe4\x38\x5b\x0e\xcd\x92\xfb\xb7\ +\x87\x7b\xff\x2b\x8b\x3f\x91\x08\xf3\xa4\x79\xa9\x44\xcd\xb3\xa1\ +\x75\xe2\xcf\x53\x93\xda\x53\xda\x4c\xa5\xc3\x54\xe8\xf1\x5b\x2e\ +\xda\xb9\xc1\x5a\x44\xae\x73\x48\xad\x4f\xc3\x1d\xdb\x1d\x8c\xcf\ +\x8c\xe3\x25\x53\x66\x68\xcf\x77\xbd\xce\x47\x4d\x6a\x4f\x94\xef\ +\x3a\x62\x6f\x56\xb9\xe7\xbf\x3d\x6b\x63\xaf\x69\x11\xc9\x86\x87\ +\x2e\x61\xd7\x15\x39\xd2\x3b\x73\xd1\x7f\x43\x1f\x5a\xe7\xfa\xaa\ +\x7d\xd4\xa4\xf6\x4f\x65\x6f\x94\xda\x1b\xf3\x5d\x5c\xaf\xc6\x36\ +\xd7\xf5\x19\xf8\xc8\xdb\x5d\xc6\x51\x93\xda\x63\xa7\x5e\x3d\x63\ +\x33\x28\x9c\x6c\x67\x09\x33\xd7\x7e\x02\x7c\xe4\xed\x38\xc9\x51\ +\x93\xda\x3f\x9c\x7c\xd1\x60\xbb\xb0\xdf\xe1\xbd\xfc\x11\xe3\xf9\ +\xf2\x28\xf8\x68\xb6\xf5\x18\x47\x4d\x6a\xdf\x2e\xde\xd1\x6e\xab\ +\x7b\xc7\x3e\x67\x2a\x64\xdc\x46\x15\xf8\xc8\x63\x2c\x60\xa9\x49\ +\xed\x5b\x45\xcf\xf7\x30\xfa\xbd\x8c\xa7\x41\xc1\xb8\x0d\x0a\xf0\ +\x14\x4b\x4d\x6a\x0f\x1f\x4d\x19\xb6\xd6\x64\xcf\x38\xeb\xf7\x31\ +\x8e\xfa\x7d\xe0\x29\x96\x9a\xd4\xb6\x1c\xde\xfa\xe3\x94\x36\xc3\ +\x3a\xa3\xcf\x61\xac\xb5\xe4\x3f\x89\x8f\xf4\x72\x96\x9a\xd4\xbe\ +\x51\xb0\x99\x99\xa8\xd8\xfd\xcb\x64\xf5\x1e\x66\xb2\x26\x1b\xbc\ +\xa4\xcb\x62\xa9\x49\xed\x41\x65\xb2\xcf\xf9\x5d\x93\xdb\x65\x6e\ +\x99\x75\x0e\x9a\xc0\x4b\xd7\x4d\x7e\x6a\x52\x7b\xe0\xc0\xc6\xa9\ +\x81\xbc\x27\xf1\x8f\x44\xec\xe0\x43\xe8\x84\x26\x63\x71\xec\xf3\ +\x7c\xf0\x11\xb1\x68\xcb\x9f\x81\x97\xfc\xe9\xab\xd5\xe0\xa3\x80\ +\x2f\x58\xe9\xcf\xf4\x9e\xc3\xef\x35\xdd\x56\x86\x89\xca\xac\xe0\ +\x79\x91\x68\x6c\xe5\xcb\x07\xf5\x99\x7e\x03\x98\x81\x7b\xb3\x76\ +\x56\xe2\xe7\xaa\x6c\x2f\xd9\xdf\xbe\xf2\x9c\x48\x0e\xb9\x7e\xcf\ +\x1f\x7c\x57\xeb\xdf\x6d\xff\x3f\xff\xdd\xc1\xd2\x2c\xae\x0b\x76\ +\x84\xbc\x1b\xff\x59\x7f\xe5\xda\xdd\xe4\xfd\x36\x54\x0a\xd0\xfd\ +\x21\x7e\x33\xbf\x02\xb6\x23\xb4\x4f\ +\x00\x00\x10\xbe\ +\x00\ +\x00\x01\x00\x01\x00\x20\x20\x00\x00\x00\x00\x00\x00\xa8\x10\x00\ +\x00\x16\x00\x00\x00\x28\x00\x00\x00\x20\x00\x00\x00\x40\x00\x00\ +\x00\x01\x00\x20\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x01\x54\x0d\ +\x18\x01\x67\x2d\x3b\x01\x78\x47\x57\x01\x81\x57\x64\x01\x81\x56\ +\x64\x01\x77\x46\x56\x01\x66\x2c\x39\x01\x54\x0c\x15\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x01\x47\x0b\x15\x01\x76\x55\x67\x00\xad\xa8\xb7\x00\xd5\xde\ +\xea\x00\xea\xfa\xff\x00\xf5\xff\xff\x00\xf9\xff\xff\x00\xf9\xff\ +\xff\x00\xf4\xff\xff\x00\xe8\xf9\xff\x00\xd2\xdd\xe8\x00\xaa\xa4\ +\xb5\x01\x74\x51\x63\x01\x47\x09\x13\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x01\x3a\x02\x0b\x01\x7b\x64\ +\x78\x00\xcd\xd5\xe3\x00\xf2\xff\xff\x00\xf5\xff\xff\x00\xf1\xff\ +\xff\x00\xf0\xff\xff\x00\xf2\xff\xff\x00\xf5\xff\xff\x00\xf5\xff\ +\xff\x00\xf2\xff\xff\x00\xef\xff\xff\x00\xf0\xff\xff\x00\xf2\xff\ +\xff\x00\xed\xff\xff\x00\xc6\xd1\xe0\x01\x76\x5e\x74\x01\x3a\x00\ +\x09\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x01\x4b\x25\x37\x00\xb6\xb9\xcc\x00\xee\xff\ +\xff\x00\xe4\xff\xff\x00\xd9\xff\xff\x00\xd4\xff\xff\x00\xd7\xff\ +\xff\x00\xdd\xff\xff\x00\xe3\xff\xff\x00\xe6\xff\xff\x00\xe6\xff\ +\xff\x00\xe2\xff\xff\x00\xdd\xff\xff\x00\xd7\xff\xff\x00\xd3\xff\ +\xff\x00\xd6\xff\xff\x00\xde\xff\xff\x00\xe5\xff\xff\x00\xad\xb3\ +\xc8\x01\x48\x21\x32\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x01\x59\x3f\x55\x00\xd6\xe4\xf1\x00\xe6\xff\xff\x00\xc3\xff\ +\xff\x00\xb2\xff\xff\x00\xbc\xff\xff\x00\xc7\xff\xff\x00\xcf\xff\ +\xff\x00\xd5\xff\xff\x00\xda\xff\xff\x00\xdc\xff\xff\x00\xdc\xff\ +\xff\x00\xda\xff\xff\x00\xd5\xff\xff\x00\xcf\xff\xff\x00\xc8\xff\ +\xff\x00\xbc\xff\xff\x00\xaf\xff\xff\x00\xbd\xff\xff\x00\xdd\xff\ +\xff\x00\xcb\xde\xee\x01\x54\x38\x4f\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x56\x3e\ +\x56\x00\xdd\xf0\xfa\x00\xd9\xff\xff\x00\xb0\xff\xff\x00\x9e\xff\ +\xff\x00\xa0\xff\xff\x00\xae\xff\xff\x00\xc0\xff\xff\x00\xc9\xff\ +\xff\x00\xcd\xff\xff\x00\xd0\xff\xff\x00\xd2\xff\xff\x00\xd2\xff\ +\xff\x00\xd0\xff\xff\x00\xcd\xff\xff\x00\xc9\xff\xff\x00\xc0\xff\ +\xff\x00\xae\xff\xff\x00\xa1\xff\xff\x00\x9d\xff\xff\x00\xa9\xff\ +\xff\x00\xce\xff\xff\x00\xd1\xea\xf8\x00\x51\x37\x4f\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x41\x23\x36\x00\xd4\xe5\ +\xf4\x00\xd3\xff\xff\x00\xa2\xfd\xff\x00\x91\xff\xff\x00\x98\xff\ +\xff\x00\x9f\xff\xff\x00\xa3\xff\xff\x00\xae\xff\xff\x00\xba\xff\ +\xff\x00\xc0\xff\xff\x00\xc5\xff\xff\x02\xc9\xff\xff\x02\xc9\xff\ +\xff\x00\xc5\xff\xff\x00\xc0\xff\xff\x00\xba\xff\xff\x00\xad\xff\ +\xff\x00\xa3\xff\xff\x00\x9f\xff\xff\x00\x98\xff\xff\x00\x91\xff\ +\xff\x00\x9b\xfd\xff\x00\xc6\xff\xff\x00\xc6\xde\xef\x00\x3d\x1d\ +\x2f\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x27\x04\x0c\x00\xb1\xba\xd1\x00\xd9\xff\ +\xff\x00\x9d\xf2\xff\x00\x84\xf7\xff\x00\x8d\xfe\xff\x00\x94\xff\ +\xff\x00\x9a\xff\xff\x00\x9d\xff\xff\x00\x9e\xff\xff\x2d\xb7\xff\ +\xff\x7f\xdb\xff\xff\xb9\xee\xff\xff\xd1\xf4\xff\xff\xd1\xf4\xff\ +\xff\xb8\xed\xff\xff\x7d\xda\xff\xff\x2b\xb6\xff\xff\x00\x9d\xff\ +\xff\x00\x9d\xff\xff\x00\x9a\xff\xff\x00\x94\xff\xff\x00\x8d\xfe\ +\xff\x00\x85\xf7\xff\x00\x92\xf1\xff\x00\xca\xff\xff\x00\xa3\xaf\ +\xca\x00\x26\x02\x09\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x6c\x62\x7c\x00\xe2\xff\xff\x00\xa2\xec\ +\xff\x00\x7b\xe9\xff\x00\x80\xf1\xff\x00\x88\xf9\xff\x00\x8c\xfe\ +\xff\x00\x8e\xff\xff\x11\x9d\xff\xff\x97\xd7\xff\xff\xf8\xfc\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf7\xfb\xff\xff\x95\xd6\xff\ +\xff\x10\x9d\xff\xff\x00\x90\xff\xff\x00\x8e\xfe\xff\x00\x88\xf9\ +\xff\x00\x80\xf1\xff\x00\x79\xe8\xff\x00\x93\xe8\xff\x00\xd1\xfd\ +\xff\x00\x63\x57\x71\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x27\x0b\x16\x00\xc2\xd5\xea\x00\xbd\xf2\xff\x00\x7a\xdd\ +\xff\x00\x71\xe1\xff\x00\x7b\xea\xff\xff\xff\xff\xff\x51\xad\xfa\ +\xff\xa2\xd6\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xcd\xea\xff\xff\x1e\x99\xfe\xff\x00\x83\xf8\xff\x00\x82\xf2\ +\xff\x00\x7b\xea\xff\x00\x72\xe2\xff\x00\x72\xdb\xff\x00\xa9\xed\ +\xff\x00\xb3\xc9\xe3\x00\x26\x07\x11\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x5e\x54\x6c\x00\xda\xfc\xff\x00\x8d\xdb\xff\x00\x65\xd2\ +\xff\x00\x6d\xdb\xff\x00\x75\xe3\xff\xff\xff\xff\xff\xb3\xd9\xfa\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xb3\xdf\xff\xff\x59\xb8\xff\xff\x31\xa9\xff\xff\x32\xaa\xff\ +\xff\x5a\xb9\xff\xff\xb4\xdf\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xcd\xe8\xfd\xff\x0d\x85\xf1\xff\x00\x79\xea\ +\xff\x00\x75\xe3\xff\x00\x6e\xdb\xff\x00\x64\xd2\xff\x00\x7a\xd4\ +\xff\x00\xc6\xf4\xff\x00\x56\x4a\x62\xff\xff\xff\x00\x00\x15\x00\ +\x01\x00\x9d\xa6\xbf\x00\xc0\xf1\xff\x00\x6b\xca\xff\x00\x5d\xcb\ +\xff\x00\x67\xd4\xff\x00\x6a\xdb\xff\xff\xff\xff\xff\xf4\xf9\xfe\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfd\xfe\xff\xff\x6a\xba\xfc\ +\xff\x00\x86\xfd\xff\x00\x88\xfe\xff\x00\x8b\xff\xff\x00\x8b\xff\ +\xff\x00\x88\xfe\xff\x00\x87\xfd\xff\x61\xb6\xfc\xff\xf9\xfc\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\x8b\xc3\xf4\xff\x00\x6d\xe1\ +\xff\x00\x6e\xdb\xff\x00\x67\xd4\xff\x00\x5e\xcb\xff\x00\x60\xc6\ +\xff\x00\xa9\xe8\xff\x00\x8e\x9a\xb6\xff\xff\xff\x00\x00\x23\x0d\ +\x1a\x00\xc2\xd8\xf0\x00\x9b\xda\xff\x00\x56\xbd\xff\x00\x58\xc3\ +\xff\x00\x60\xcc\xff\x00\x5d\xd1\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x64\xb4\xf8\ +\xff\x00\x7c\xf3\xff\x00\x85\xf6\xff\x00\x86\xf7\xff\x00\x86\xf7\ +\xff\x00\x85\xf6\xff\x00\x83\xf4\xff\x00\x77\xf1\xff\x6c\xb5\xf6\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf3\xf9\xfe\xff\x1d\x7d\xde\ +\xff\x00\x63\xd2\xff\x00\x60\xcc\xff\x00\x58\xc4\xff\x00\x51\xbb\ +\xff\x00\x83\xd0\xff\x00\xb0\xcc\xea\x00\x21\x08\x14\x00\x3a\x2d\ +\x40\x00\xce\xed\xff\x00\x7f\xc9\xff\x00\x49\xb3\xff\x00\x51\xbc\ +\xff\x00\x57\xc3\xff\x04\x5e\xca\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\x57\xa1\xeb\xff\x15\x81\xe8\ +\xff\x00\x79\xe9\xff\x00\x7c\xeb\xff\x00\x7d\xec\xff\x00\x7d\xec\ +\xff\x00\x7c\xeb\xff\x00\x7b\xea\xff\x00\x76\xe7\xff\x01\x74\xe4\ +\xff\xce\xe3\xf9\xff\xff\xff\xff\xff\xff\xff\xff\xff\x69\xa3\xe3\ +\xff\x00\x54\xc6\xff\x00\x57\xc2\xff\x00\x51\xbb\xff\x00\x47\xb2\ +\xff\x00\x68\xc0\xff\x00\xba\xe1\xfe\x00\x34\x26\x38\x00\x4e\x48\ +\x5e\x00\xcd\xf2\xff\x00\x6f\xc2\xff\x00\x42\xad\xff\x00\x48\xb1\ +\xff\x00\x4d\xb8\xff\x06\x5a\xc2\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\x00\x7d\xec\xff\x00\x7f\xee\xff\x00\x80\xef\xff\x00\x80\xef\ +\xff\x00\x7f\xee\xff\x00\x7e\xed\xff\x00\x7c\xea\xff\x00\x6f\xe5\ +\xff\x82\xbc\xf2\xff\xff\xff\xff\xff\xff\xff\xff\xff\xa8\xc9\xee\ +\xff\x00\x56\xc7\xff\x00\x55\xbf\xff\x00\x4d\xb6\xff\x00\x44\xae\ +\xff\x00\x59\xb8\xff\x00\xb6\xe6\xff\x00\x46\x3e\x54\x00\x58\x56\ +\x6d\x00\xc7\xf0\xff\x00\x66\xbd\xff\x00\x47\xb2\xff\x00\x53\xbc\ +\xff\x00\x5c\xc6\xff\x00\x65\xd0\xff\x00\x61\xd7\xff\x00\x65\xdf\ +\xff\x00\x6f\xe5\xff\x00\x7a\xeb\xff\x00\x80\xee\xff\x00\x82\xf0\ +\xff\x00\x83\xf2\xff\x00\x85\xf4\xff\x00\x85\xf4\xff\x00\x85\xf4\ +\xff\x00\x84\xf4\xff\x00\x83\xf2\xff\x00\x81\xf0\xff\x00\x79\xec\ +\xff\x49\xa2\xf0\xff\xcc\xe4\xfa\xff\xcc\xe3\xf9\xff\x92\xc2\xf0\ +\xff\x00\x67\xd5\xff\x00\x65\xcf\xff\x00\x64\xcf\xff\x00\x60\xcb\ +\xff\x00\x6a\xcd\xff\x00\xb8\xee\xff\x00\x4e\x4c\x65\x00\x55\x55\ +\x6d\x00\xd0\xf8\xff\x00\x89\xe5\xff\x00\x76\xe7\xff\x00\x7b\xea\ +\xff\x00\x7b\xea\xff\x00\x79\xea\xff\x59\xa8\xef\xff\x74\xb7\xf3\ +\xff\x74\xb9\xf5\xff\x22\x97\xf6\xff\x00\x87\xf8\xff\x00\x8b\xfa\ +\xff\x00\x8d\xfb\xff\x00\x8e\xfb\xff\x00\x8e\xfc\xff\x00\x8e\xfc\ +\xff\x00\x8e\xfb\xff\x00\x8c\xfb\xff\x00\x8b\xfa\xff\x00\x89\xf8\ +\xff\x00\x86\xf6\xff\x00\x83\xf3\xff\x00\x7e\xf0\xff\x00\x79\xec\ +\xff\x00\x78\xe6\xff\x00\x76\xe2\xff\x00\x78\xe6\xff\x00\x78\xea\ +\xff\x00\x85\xed\xff\x00\xc3\xfb\xff\x00\x4a\x4a\x65\x00\x45\x45\ +\x5e\x00\xdf\xff\xff\x00\xb3\xff\xff\x00\x9d\xff\xff\x00\x9e\xff\ +\xff\x00\x9e\xfe\xff\x00\x98\xfc\xff\xc1\xe8\xfd\xff\xfe\xfe\xfe\ +\xff\xff\xff\xff\xff\x65\xc8\xfe\xff\x00\x9d\xfd\xff\x00\xa4\xfb\ +\xff\x00\xa6\xf9\xff\x00\xa9\xfb\xff\x00\xab\xfa\xff\x00\xad\xfb\ +\xff\x00\xac\xfc\xff\x00\xaa\xfc\xff\xfd\xfd\xfd\xff\xfe\xfe\xfe\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xfe\xfe\ +\xff\x01\x9b\xf9\xff\x00\x97\xf3\xff\x00\x9c\xfb\xff\x00\x9d\xfe\ +\xff\x00\xa8\xff\xff\x00\xce\xfe\xff\x00\x3c\x3a\x54\x00\x2c\x2a\ +\x40\x00\xdd\xf8\xff\x00\xd1\xff\xff\x00\xbd\xff\xff\x00\xbe\xff\ +\xff\x00\xbd\xfd\xff\x00\xb4\xf7\xff\x89\xdb\xf7\xff\xfc\xfc\xfc\ +\xff\xfe\xfe\xfe\xff\xa2\xe7\xfe\xff\x00\xb7\xf9\xff\x00\xb7\xef\ +\xff\x00\xb9\xee\xff\x00\xbd\xf1\xff\x00\xbf\xf2\xff\x00\xc0\xf3\ +\xff\x00\xbe\xf2\xff\x00\xbc\xf1\xff\x00\xbd\xf3\xff\x12\xc4\xfb\ +\xff\xfe\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xfe\xfe\ +\xff\x04\xb9\xf8\xff\x00\xb4\xf2\xff\x00\xbb\xfb\xff\x00\xbd\xfe\ +\xff\x00\xca\xff\xff\x00\xcd\xf0\xfe\x00\x26\x22\x38\x00\x10\x0b\ +\x1b\x00\xc4\xdb\xf0\x00\xe4\xff\xff\x00\xcc\xff\xff\x00\xcb\xff\ +\xff\x00\xca\xfe\xff\x00\xc3\xf8\xff\x3f\xcf\xf3\xff\xf9\xf9\xf9\ +\xff\xfe\xfe\xfe\xff\xf3\xfd\xfe\xff\x28\xd3\xf9\xff\x00\xbf\xea\ +\xff\x00\xc2\xe9\xff\x00\xc9\xf1\xff\x00\xca\xf1\xff\x00\xca\xf1\ +\xff\x00\xc6\xec\xff\x00\xbf\xe7\xff\x5b\xd2\xeb\xff\xde\xf4\xf9\ +\xff\xfe\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfd\xfd\xfd\ +\xff\x00\xc2\xf7\xff\x00\xc3\xf4\xff\x00\xc9\xfc\xff\x00\xcb\xff\ +\xff\x00\xdd\xff\xff\x00\xb5\xd0\xea\x00\x0d\x06\x14\x00\x00\x00\ +\x01\x00\x91\xa3\xbf\x00\xf3\xff\xff\x00\xd8\xff\xff\x00\xd4\xff\ +\xff\x00\xd4\xfe\xff\x00\xd1\xfb\xff\x04\xcb\xf4\xff\xc1\xed\xf6\ +\xff\xfc\xfc\xfc\xff\xfe\xfe\xfe\xff\xca\xf6\xfc\xff\x18\xd4\xf1\ +\xff\x00\xc9\xe8\xff\x07\xcb\xe7\xff\x0c\xcc\xe8\xff\x0d\xcc\xe7\ +\xff\x0a\xcd\xe7\xff\x00\xcb\xe9\xff\x5a\xe0\xf3\xff\xfc\xfc\xfc\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfd\xfd\xfd\ +\xff\x00\xcc\xf7\xff\x00\xcb\xf4\xff\x00\xd2\xfc\xff\x00\xd5\xff\ +\xff\x00\xea\xff\xff\x00\x81\x96\xb6\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x48\x50\x6d\x00\xf0\xff\xff\x00\xe6\xff\xff\x00\xdd\xff\ +\xff\x00\xdd\xff\xff\x00\xdc\xfe\xff\x00\xd7\xf8\xff\x35\xdd\xf3\ +\xff\xf0\xf6\xf6\xff\xfd\xfd\xfd\xff\xff\xff\xff\xff\xd8\xf8\xfb\ +\xff\x69\xe7\xf6\xff\x39\xde\xf0\xff\x34\xdc\xf0\xff\x36\xdc\xf0\ +\xff\x42\xde\xf2\xff\x79\xeb\xf8\xff\xde\xfa\xfc\xff\xfe\xfe\xfe\ +\xff\xff\xff\xff\xff\xfe\xfe\xfe\xff\xc4\xf8\xfe\xff\xfc\xfc\xfc\ +\xff\x00\xd7\xf6\xff\x00\xd5\xf5\xff\x00\xda\xfc\xff\x00\xe3\xff\ +\xff\x00\xe4\xfe\xff\x00\x3d\x45\x63\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x0c\x09\x17\x00\xbb\xd2\xea\x00\xf7\xff\xff\x00\xe6\xff\ +\xff\x00\xe5\xff\xff\x00\xe6\xff\xff\x01\xe7\xfd\xff\x01\xe1\xf7\ +\xff\x6f\xe8\xf3\xff\xf6\xf7\xf7\xff\xfd\xfd\xfd\xff\xfe\xfe\xfe\ +\xff\xfd\xfd\xfd\xff\xeb\xfb\xfc\xff\xd4\xf8\xfc\xff\xd5\xf8\xfc\ +\xff\xed\xfc\xfd\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\ +\xff\xf5\xfb\xfc\xff\xea\xf8\xf8\xff\x7e\xee\xf9\xff\xfb\xfb\xfb\ +\xff\x03\xe1\xf6\xff\x00\xdd\xf5\xff\x00\xe2\xfb\xff\x00\xf3\xff\ +\xff\x00\xab\xc7\xe3\x00\x08\x06\x12\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x52\x5d\x7d\x00\xf4\xff\xff\x00\xf0\xff\ +\xff\x00\xea\xff\xff\x0a\xeb\xff\xff\x1b\xed\xfe\xff\x2d\xeb\xfb\ +\xff\x34\xe7\xf6\xff\x85\xeb\xf3\xff\xe9\xf8\xf9\xff\xfd\xfd\xfd\ +\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xfe\xfe\xfe\xff\xff\xff\xff\xff\xfd\xfd\xfd\xff\xe5\xf6\xf7\ +\xff\x87\xea\xf2\xff\x63\xe5\xf0\xff\x4d\xe8\xf6\xff\xfa\xfa\xfa\ +\xff\x21\xe5\xf6\xff\x0e\xe4\xf7\xff\x03\xee\xfd\xff\x00\xea\xff\ +\xff\x00\x46\x51\x72\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x05\x03\x0d\x00\x9e\xb5\xd3\x07\xfd\xff\ +\xff\x16\xef\xff\xff\x29\xed\xff\xff\x3d\xef\xff\xff\x4f\xf1\xff\ +\xff\x5f\xf0\xfc\xff\x65\xed\xf9\xff\x88\xf1\xfa\xff\xc4\xf5\xf9\ +\xff\xe9\xf8\xf9\xff\xf8\xfb\xfb\xff\xfe\xfe\xfe\xff\xfb\xfb\xfb\ +\xff\xf8\xfb\xfb\xff\xeb\xf9\xfa\xff\xc6\xf1\xf4\xff\x8d\xe5\xec\ +\xff\x74\xe2\xec\xff\x6c\xea\xf5\xff\x60\xea\xf6\xff\x54\xec\xf9\ +\xff\x41\xeb\xfb\xff\x31\xed\xfc\xff\x16\xf8\xfe\xff\x00\x90\xa9\ +\xcc\x00\x03\x01\x0a\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x1d\x20\x37\x07\xc9\xe0\ +\xf4\x33\xfd\xff\xff\x4b\xf1\xff\xff\x59\xf1\xff\xff\x6a\xf3\xff\ +\xff\x7c\xf4\xfe\xff\x8b\xf5\xfe\xff\x95\xf4\xfc\xff\x9c\xf0\xf7\ +\xff\xac\xed\xf3\xff\xbf\xf0\xf4\xff\xcf\xf5\xf8\xff\xce\xf3\xf5\ +\xff\xc5\xef\xf3\xff\xb5\xed\xf2\xff\xa7\xec\xf2\xff\xa3\xee\xf4\ +\xff\x9b\xf1\xf8\xff\x8f\xf5\xfe\xff\x7f\xf4\xfd\xff\x6f\xf2\xfd\ +\xff\x63\xf3\xfe\xff\x48\xfb\xfe\xff\x0b\xbc\xd8\xf0\x00\x18\x1b\ +\x31\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x32\x3a\ +\x56\x15\xd5\xea\xfa\x5e\xfe\xff\xff\x79\xf6\xff\xff\x83\xf4\xff\ +\xff\x93\xf6\xff\xff\xa3\xf8\xff\xff\xb1\xf9\xfe\xff\xbd\xf8\xfc\ +\xff\xc6\xf6\xfa\xff\xce\xf8\xfa\xff\xd7\xfa\xfc\xff\xd8\xf7\xfa\ +\xff\xd5\xf6\xf8\xff\xd1\xf7\xf9\xff\xcb\xf7\xfb\xff\xc2\xf9\xfd\ +\xff\xb5\xf8\xfe\xff\xa7\xf8\xfe\xff\x98\xf7\xff\xff\x90\xf7\xff\ +\xff\x70\xfd\xff\xff\x18\xc9\xe4\xf8\x00\x2a\x33\x50\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x32\x3b\x56\x18\xc8\xde\xf3\x73\xff\xff\xff\xa4\xfb\xff\ +\xff\xaa\xf8\xff\xff\xb5\xf9\xff\xff\xc3\xfa\xff\xff\xd0\xfb\xff\ +\xff\xdd\xfc\xff\xff\xe8\xfd\xff\xff\xed\xfe\xff\xff\xee\xfd\xfd\ +\xff\xed\xfd\xfe\xff\xea\xfe\xff\xff\xe0\xfd\xfe\xff\xd4\xfc\xff\ +\xff\xc7\xfa\xff\xff\xbe\xfa\xff\xff\xb8\xfd\xff\xff\x80\xfe\xff\ +\xff\x19\xbc\xd8\xef\x00\x2a\x34\x51\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x1c\x21\x38\x0a\x9b\xb3\xce\x5d\xf4\xff\ +\xff\xb3\xff\xff\xff\xd2\xfe\xff\xff\xd8\xfd\xff\xff\xdf\xfd\xff\ +\xff\xea\xfe\xff\xff\xf2\xfe\xff\xff\xf2\xfe\xff\xff\xf2\xfe\xff\ +\xff\xf2\xfe\xff\xff\xf2\xfe\xff\xff\xef\xfe\xff\xff\xe9\xfe\xff\ +\xff\xe4\xff\xff\xff\xc2\xff\xff\xff\x63\xee\xff\xff\x09\x90\xab\ +\xc8\x00\x18\x1d\x34\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x04\x01\x0c\x00\x4e\x5d\ +\x7a\x20\xb6\xcf\xe4\x74\xf2\xff\xff\xbf\xff\xff\xff\xeb\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf6\xff\xff\xff\xcb\xff\xff\ +\xff\x78\xee\xff\xff\x1f\xae\xca\xe1\x00\x48\x58\x75\x00\x03\x00\ +\x09\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x0b\x08\x16\x00\x43\x4f\x69\x0a\x89\x9f\xba\x36\xbe\xd6\ +\xeb\x66\xdd\xf2\xff\x86\xed\xff\xff\x95\xf2\xff\xff\x94\xf1\xff\ +\xff\x85\xec\xfe\xff\x64\xdb\xf1\xff\x36\xba\xd4\xea\x0a\x84\x9c\ +\xb6\x00\x3e\x4b\x65\x00\x09\x06\x14\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x0e\x0a\ +\x18\x00\x24\x27\x3c\x00\x38\x40\x58\x00\x43\x4d\x68\x00\x42\x4d\ +\x67\x00\x37\x3f\x57\x00\x23\x26\x3b\x00\x0d\x09\x17\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\xff\xff\xe0\x07\xff\xff\x80\x01\xff\xfe\x00\x00\x7f\xfc\x00\x00\ +\x3f\xf8\x00\x00\x1f\xf0\x00\x00\x0f\xe0\x00\x00\x07\xe0\x00\x00\ +\x07\xc0\x00\x00\x03\xc0\x00\x00\x03\x80\x00\x00\x01\x80\x00\x00\ +\x01\x80\x00\x00\x01\x80\x00\x00\x01\x80\x00\x00\x01\x80\x00\x00\ +\x01\x80\x00\x00\x01\x80\x00\x00\x01\x80\x00\x00\x01\x80\x00\x00\ +\x01\xc0\x00\x00\x03\xc0\x00\x00\x03\xe0\x00\x00\x07\xe0\x00\x00\ +\x07\xf0\x00\x00\x0f\xf8\x00\x00\x1f\xfc\x00\x00\x3f\xfe\x00\x00\ +\x7f\xff\x80\x01\xff\xff\xe0\x07\xff\xff\xff\xff\xff\ +\x00\x00\x10\xbe\ +\x00\ +\x00\x01\x00\x01\x00\x20\x20\x00\x00\x00\x00\x00\x00\xa8\x10\x00\ +\x00\x16\x00\x00\x00\x28\x00\x00\x00\x20\x00\x00\x00\x40\x00\x00\ +\x00\x01\x00\x20\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\ +\x04\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00\ +\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\ +\x07\x00\x00\x00\x0c\x00\x00\x00\x10\x00\x00\x00\x12\x00\x00\x00\ +\x15\x00\x00\x00\x17\x00\x00\x00\x18\x00\x00\x00\x19\x00\x00\x00\ +\x19\x00\x00\x00\x19\x00\x00\x00\x18\x00\x00\x00\x17\x00\x00\x00\ +\x15\x00\x00\x00\x13\x00\x00\x00\x0f\x00\x00\x00\x0a\x00\x00\x00\ +\x06\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x02\x00\x00\x00\x08\x00\x00\x00\x0d\x00\x00\x00\x12\x00\x00\x00\ +\x17\x00\x00\x00\x1b\x00\x00\x00\x1f\x00\x00\x00\x22\x00\x00\x00\ +\x25\x00\x00\x00\x27\x00\x00\x00\x29\x00\x00\x00\x2a\x00\x00\x00\ +\x2b\x00\x00\x00\x2b\x00\x00\x00\x2a\x00\x00\x00\x28\x00\x00\x00\ +\x26\x00\x00\x00\x23\x00\x00\x00\x20\x00\x00\x00\x1c\x00\x00\x00\ +\x18\x00\x00\x00\x13\x00\x00\x00\x0f\x00\x00\x00\x07\x00\x00\x00\ +\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00\ +\x0c\x00\x00\x00\x12\x00\x00\x00\x18\x00\x00\x00\x1d\x00\x00\x00\ +\x22\x00\x00\x00\x27\x00\x00\x00\x2c\x00\x00\x00\x30\x00\x00\x00\ +\x34\x00\x00\x00\x37\x00\x00\x00\x3a\x00\x00\x00\x3c\x00\x00\x00\ +\x3c\x00\x00\x00\x3c\x00\x00\x00\x3b\x00\x00\x00\x38\x00\x00\x00\ +\x35\x00\x00\x00\x31\x00\x00\x00\x2d\x00\x00\x00\x28\x00\x00\x00\ +\x24\x00\x00\x00\x1e\x00\x00\x00\x19\x00\x00\x00\x14\x00\x00\x00\ +\x0e\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x0c\x00\x00\x00\ +\x12\x00\x00\x00\x19\x00\x00\x00\x1f\x00\x00\x00\x25\x00\x00\x00\ +\x2b\x00\x00\x00\x30\x00\x00\x00\x36\x00\x00\x00\x3c\x00\x00\x00\ +\x41\x00\x00\x00\x46\x00\x00\x00\x4a\x00\x00\x00\x4d\x00\x00\x00\ +\x4e\x00\x00\x00\x4d\x00\x00\x00\x4b\x00\x00\x00\x47\x00\x00\x00\ +\x42\x00\x00\x00\x3d\x00\x00\x00\x38\x00\x00\x00\x32\x00\x00\x00\ +\x2c\x00\x00\x00\x26\x00\x00\x00\x20\x00\x00\x00\x1a\x00\x00\x00\ +\x14\x00\x00\x00\x0e\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x02\x00\x00\x00\x08\x00\x00\x00\x0f\x94\x59\x2e\ +\x75\x00\x00\x00\x1b\x00\x00\x00\x22\x00\x00\x00\x28\x00\x00\x00\ +\x2e\x00\x00\x00\x34\x00\x00\x00\x3b\x00\x00\x00\x41\x00\x00\x00\ +\x47\x0f\x09\x06\x54\x58\x35\x1b\x83\x85\x51\x2b\xba\x9a\x5e\x32\ +\xe0\xa1\x64\x34\xf5\xa3\x63\x34\xf9\xa4\x64\x33\xfc\xa2\x64\x34\ +\xf0\x9b\x60\x32\xd7\x83\x4f\x2a\x98\x43\x29\x16\x50\x00\x00\x00\ +\x30\x00\x00\x00\x2a\x00\x00\x00\x23\x00\x00\x00\x1d\x00\x00\x00\ +\x17\x00\x00\x00\x10\x00\x00\x00\x0a\x00\x00\x00\x02\x00\x00\x00\ +\x00\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00\x0d\xa4\x64\x33\ +\xff\x86\x54\x2a\x61\x00\x00\x00\x20\x00\x00\x00\x26\x00\x00\x00\ +\x2c\x00\x00\x00\x32\x00\x00\x00\x38\x44\x2b\x14\x5a\x8e\x57\x2e\ +\xb3\xa2\x66\x36\xf7\xad\x74\x46\xf5\xbe\x88\x5a\xfe\xc6\x92\x65\ +\xff\xcb\x98\x6b\xff\xcc\x99\x6b\xff\xcc\x97\x67\xff\xc7\x90\x60\ +\xff\xc1\x88\x55\xff\xb6\x79\x46\xfc\xa6\x67\x37\xf3\x9c\x60\x32\ +\xc7\x4d\x2e\x17\x42\x00\x00\x00\x21\x00\x00\x00\x1b\x00\x00\x00\ +\x15\x00\x00\x00\x0f\x00\x00\x00\x09\x00\x00\x00\x02\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x08\xa4\x65\x34\ +\xff\xa8\x6a\x3b\xf4\x77\x4b\x25\x4b\x00\x00\x00\x1f\x00\x00\x00\ +\x25\x43\x2b\x14\x41\x98\x5f\x31\xb7\xa6\x69\x3a\xf7\xc0\x8e\x64\ +\xfb\xd2\xa5\x7e\xff\xd2\xa4\x7a\xff\xce\x9d\x6f\xff\xcb\x97\x67\ +\xff\xca\x93\x61\xff\xc8\x91\x5f\xff\xc8\x90\x5d\xff\xc7\x90\x5c\ +\xff\xc8\x90\x5c\xff\xc7\x8f\x5b\xff\xc6\x8e\x58\xff\xb7\x7a\x47\ +\xfd\xa5\x65\x34\xf4\x82\x50\x2a\x5c\x00\x00\x00\x16\x00\x00\x00\ +\x10\x00\x00\x00\x0a\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xa4\x64\x33\ +\xff\xda\xbc\x9f\xff\xa6\x68\x38\xf0\x6f\x46\x20\x37\x8f\x56\x2e\ +\x79\xa5\x68\x37\xf6\xc1\x90\x69\xfb\xd7\xb0\x8b\xff\xd2\xa5\x7c\ +\xff\xcc\x9a\x6a\xff\xcb\x96\x65\xff\xca\x95\x63\xff\xca\x94\x62\ +\xff\xc9\x93\x60\xff\xc9\x93\x60\xff\xc9\x93\x60\xff\xca\x93\x60\ +\xff\xc8\x91\x5d\xff\xc8\x8f\x5b\xff\xc6\x8d\x58\xff\xc7\x8c\x58\ +\xff\xbe\x82\x4d\xff\xa5\x66\x35\xf6\x8b\x56\x2d\x44\x00\x00\x00\ +\x07\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x64\x33\ +\xff\xea\xd5\xc1\xff\xd7\xb3\x94\xfd\xa4\x64\x33\xff\xb7\x82\x58\ +\xf6\xdc\xbe\xa1\xff\xd7\xae\x88\xff\xcd\x9c\x6e\xff\xcc\x99\x69\ +\xff\xcb\x97\x67\xff\xcb\x96\x66\xff\xcc\x99\x69\xff\xcf\x9e\x72\ +\xff\xd0\xa2\x77\xff\xd0\xa0\x76\xff\xc8\x95\x69\xff\xc3\x8d\x5e\ +\xff\xc2\x8a\x5a\xff\xc3\x8c\x5c\xff\xc9\x90\x5e\xff\xc7\x8f\x5b\ +\xff\xc7\x8c\x57\xff\xbc\x7e\x4a\xff\xa5\x66\x35\xe6\xaa\x55\x2b\ +\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x64\x33\ +\xff\xea\xd6\xc2\xff\xe7\xd0\xb9\xff\xde\xc2\xa9\xff\xe7\xce\xb7\ +\xff\xde\xbe\x9f\xff\xd5\xab\x84\xff\xce\x9b\x6d\xff\xcc\x99\x6a\ +\xff\xcf\x9e\x71\xff\xd4\xa9\x82\xff\xd3\xa7\x80\xff\xc1\x8d\x61\ +\xfc\xaf\x73\x45\xf1\xa5\x66\x34\xf8\xa9\x69\x37\xff\xad\x6b\x38\ +\xff\xae\x6c\x37\xff\xad\x6b\x38\xfe\xa9\x67\x36\xfe\xb0\x72\x40\ +\xfb\xbe\x83\x51\xff\xc7\x8d\x58\xff\xad\x6e\x3b\xf2\xa6\x67\x32\ +\x5c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x64\x33\ +\xff\xeb\xd7\xc4\xff\xde\xbc\x9d\xff\xe6\xcd\xb5\xff\xdd\xbb\x9b\ +\xff\xdb\xb8\x96\xff\xdb\xb7\x95\xff\xdb\xb7\x95\xff\xda\xb6\x95\ +\xff\xd2\xaa\x85\xff\xb9\x82\x56\xf5\xa5\x67\x36\xf4\xa6\x67\x36\ +\xa1\xa4\x63\x31\x43\xa4\x5b\x37\x0e\x00\x00\x00\x00\xbb\x77\x33\ +\x0f\xc3\x7a\x40\x7b\xc6\x82\x49\xf2\xe5\xcd\xb3\xe4\xcc\x9c\x76\ +\xe0\xab\x6c\x3a\xfb\xb3\x76\x44\xfa\xbc\x81\x4d\xff\xa6\x67\x35\ +\xc6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x64\x33\ +\xff\xec\xd8\xc6\xff\xdd\xbb\x9b\xff\xdc\xb9\x98\xff\xdc\xb9\x98\ +\xff\xdb\xb9\x98\xff\xe1\xc4\xa7\xff\xe4\xcb\xb2\xff\xc5\x99\x73\ +\xfa\xa6\x68\x36\xf1\xa3\x65\x35\x6f\xa4\x5b\x37\x0e\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xca\x8d\x55\x57\xd9\xad\x87\xc8\xe9\xd6\xc4\ +\x97\xe2\xca\xb1\x82\xad\x70\x3f\xea\xb4\x77\x45\xfa\xa6\x67\x36\ +\xf2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x64\x33\ +\xff\xec\xd9\xc8\xff\xdd\xbb\x9b\xff\xdc\xb9\x98\xff\xdc\xba\x99\ +\xff\xe8\xd2\xbd\xff\xdf\xc2\xa9\xff\xa9\x6e\x41\xff\xa7\x67\x37\ +\xa8\xa6\x66\x33\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xcf\x8f\x60\x10\xd8\xa9\x82\x9d\xea\xd5\xc2\ +\x6d\xe8\xd5\xc1\x4e\xd6\xae\x94\x45\xa7\x69\x38\xf7\xa4\x65\x34\ +\xff\xa5\x69\x2d\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x64\x33\ +\xff\xec\xda\xc8\xff\xdd\xbb\x9b\xff\xdc\xb9\x98\xff\xdc\xb9\x98\ +\xff\xe2\xc5\xaa\xff\xe4\xcb\xb3\xff\xaa\x6e\x41\xfd\xa5\x67\x34\ +\x4f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xff\x80\x80\x02\xdb\xb4\x93\x63\xe8\xd5\xc2\ +\x43\xe9\xd3\xbd\x23\xbf\xbf\xbf\x04\xa9\x6a\x39\x9d\xa4\x64\x33\ +\xff\xa2\x66\x33\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x64\x33\ +\xff\xec\xda\xc8\xff\xdd\xbb\x9b\xff\xdc\xb9\x98\xff\xdc\xb9\x98\ +\xff\xdc\xb9\x98\xff\xe3\xc8\xae\xff\xe0\xc4\xab\xff\xa8\x6c\x3c\ +\xf4\xa2\x66\x33\x37\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xff\xff\x00\x01\xe4\xd0\xbc\x26\xea\xd5\xbf\ +\x18\x00\x00\x00\x00\x00\x00\x00\x00\xa3\x65\x34\x4e\xa4\x64\x33\ +\xff\xaa\x66\x33\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x64\x33\ +\xff\xed\xdb\xca\xff\xed\xdb\xca\xff\xed\xda\xc9\xff\xec\xd9\xc7\ +\xff\xea\xd6\xc2\xff\xea\xd4\xc0\xff\xea\xd5\xc1\xff\xdb\xbc\xa0\ +\xff\xa6\x69\x37\xf1\xa3\x63\x32\x24\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdf\xbf\xbf\x08\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x65\x32\x51\xa4\x64\x33\ +\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x64\x33\ +\xff\xa5\x66\x36\xf6\xa5\x66\x36\xf6\xa5\x66\x36\xf6\xa4\x65\x34\ +\xfa\xa4\x64\x33\xff\xa4\x64\x33\xff\xa4\x64\x33\xff\xa4\x64\x33\ +\xff\xa4\x64\x33\xff\xa4\x64\x33\xff\xaa\x61\x31\x15\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa\x55\x55\x03\xa2\x64\x36\ +\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa7\x61\x35\ +\x1d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\xa2\x64\x32\x42\xa4\x64\x33\xff\xa4\x64\x33\xff\xa4\x64\x33\ +\xff\xa4\x64\x33\xff\xa4\x64\x33\xff\xa4\x64\x33\xff\xa4\x64\x33\ +\xff\xa4\x64\x33\xff\xa4\x64\x33\xff\xa4\x64\x33\xff\xa4\x64\x33\ +\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa2\x5d\x2e\x0b\xa4\x64\x33\ +\xff\xb2\x79\x4b\x63\xb7\x7c\x58\x40\xba\x80\x53\x34\xaa\x77\x44\ +\x2d\xa2\x64\x36\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xa5\x67\x37\x4f\xab\x6e\x3f\xf6\xdd\xbe\xa3\ +\xff\xe2\xc5\xaa\xff\xe2\xc4\xa9\xff\xe1\xc4\xa9\xff\xe1\xc5\xa9\ +\xff\xe2\xc5\xaa\xff\xe4\xc8\xaf\xff\xe4\xca\xb2\xff\xa4\x65\x35\ +\xf7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x61\x37\x2a\xa4\x64\x33\ +\xff\xbb\x83\x57\xa2\xca\x97\x6f\x78\xcc\x99\x70\x69\xbf\x89\x5d\ +\x73\xa7\x67\x39\x74\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x65\x33\x68\xac\x71\x45\ +\xf5\xdc\xbc\x9e\xff\xd7\xb0\x8b\xff\xd0\xa2\x77\xff\xd0\xa1\x74\ +\xff\xcf\x9f\x72\xff\xd0\xa2\x76\xff\xe4\xc9\xaf\xff\xa5\x65\x35\ +\xf7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x66\x33\x2d\xa4\x64\x33\ +\xff\xaf\x72\x41\xeb\xc3\x8d\x64\xba\xc5\x8f\x67\xab\xb4\x7a\x4d\ +\xc3\xa3\x65\x35\xa4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa5\x68\x37\ +\x7d\xac\x72\x44\xf9\xdf\xbf\xa2\xff\xd6\xad\x87\xff\xd0\xa1\x76\ +\xff\xcf\x9f\x73\xff\xd0\xa2\x76\xff\xe3\xc7\xad\xff\xa5\x65\x35\ +\xf7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x65\x35\x2b\xa8\x6a\x3b\ +\xff\xa7\x6a\x3b\xfe\xbb\x84\x58\xfc\xbe\x88\x5d\xed\xb8\x7e\x51\ +\xe8\xa6\x66\x36\xc3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa7\x62\x31\x1a\xa6\x68\x37\ +\xb5\xaa\x6e\x40\xff\xdb\xbb\x9e\xff\xdc\xb9\x98\xff\xd0\xa2\x77\ +\xff\xcf\xa0\x74\xff\xd0\xa1\x75\xff\xe2\xc6\xab\xff\xa5\x65\x35\ +\xf7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x99\x66\x33\x0a\xa9\x6a\x3c\ +\xf5\xc0\x91\x6b\xfb\xa8\x69\x39\xfb\xb3\x77\x4a\xff\xb5\x7b\x4e\ +\xff\xa5\x67\x36\xee\xaa\x63\x39\x12\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\xa5\x69\x2d\x11\xa7\x66\x36\x7a\xa8\x68\x39\xf3\xc5\x97\x71\ +\xfb\xde\xbe\xa0\xff\xd7\xaf\x8b\xff\xd2\xa5\x7a\xff\xd1\xa3\x78\ +\xff\xd0\xa1\x75\xff\xd0\xa1\x75\xff\xe1\xc4\xa9\xff\xa5\x64\x33\ +\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa8\x6b\x3c\ +\xde\xdc\xbf\xa5\xff\xb8\x86\x5d\xfc\xa8\x6a\x3b\xfc\xa7\x68\x37\ +\xff\xa9\x6a\x3a\xf8\xa5\x64\x34\xdd\xa2\x66\x33\x37\x00\x00\x00\ +\x00\x00\x00\x00\x00\xaa\x66\x33\x0f\xa4\x62\x33\x46\xa7\x69\x39\ +\xaa\xa5\x69\x39\xf5\xc0\x90\x6b\xf6\xd9\xb7\x98\xff\xdc\xb9\x98\ +\xff\xd5\xab\x84\xff\xd3\xa7\x7e\xff\xd2\xa5\x7b\xff\xd4\xa9\x80\ +\xff\xdb\xb8\x97\xff\xd1\xa4\x79\xff\xdf\xc0\xa3\xff\xa4\x64\x33\ +\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x68\x35\ +\x78\xc1\x94\x6e\xf7\xea\xd4\xbf\xff\xd4\xb1\x93\xfe\xb2\x79\x4e\ +\xfc\xa8\x69\x39\xfe\xa8\x6a\x3a\xff\xa7\x69\x39\xff\xa7\x69\x39\ +\xff\xa6\x68\x38\xfd\xa7\x6a\x3b\xf8\xb8\x84\x5c\xf1\xd2\xac\x8c\ +\xfd\xe5\xcc\xb3\xff\xe6\xcd\xb6\xff\xe3\xc6\xab\xff\xde\xbd\x9e\ +\xff\xd8\xb3\x8f\xff\xd3\xa9\x81\xff\xd7\xb0\x8c\xff\xdd\xbd\x9f\ +\xff\xd8\xb5\x96\xff\xdd\xbc\x9c\xff\xdf\xc0\xa2\xff\xa4\x64\x33\ +\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x5e\x36\ +\x13\xa7\x68\x38\xf0\xdd\xbf\xa6\xff\xe9\xd4\xc0\xff\xe8\xd3\xbd\ +\xff\xe4\xcc\xb4\xff\xdb\xbc\xa2\xff\xd7\xb6\x9a\xff\xda\xb9\x9e\ +\xff\xe0\xc4\xab\xff\xe7\xd0\xba\xff\xe7\xcf\xb9\xff\xe5\xcc\xb4\ +\xff\xe3\xc8\xae\xff\xe2\xc5\xaa\xff\xe1\xc4\xa8\xff\xe0\xc2\xa6\ +\xff\xe1\xc4\xa9\xff\xe6\xcb\xb3\xff\xdb\xba\x9d\xff\xad\x74\x45\ +\xf5\xa4\x65\x34\xff\xd1\xaa\x89\xfe\xe0\xc2\xa5\xff\xa4\x64\x33\ +\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\xa5\x67\x35\x52\xaa\x6e\x3f\xf5\xe1\xc5\xae\xff\xe9\xd3\xbe\ +\xff\xe8\xd2\xbc\xff\xe8\xd0\xba\xff\xe7\xd0\xb9\xff\xe7\xce\xb8\ +\xff\xe6\xcd\xb6\xff\xe5\xcb\xb2\xff\xe4\xca\xb1\xff\xe3\xc8\xaf\ +\xff\xe3\xc7\xad\xff\xe3\xc6\xab\xff\xe3\xc7\xad\xff\xe5\xcc\xb4\ +\xff\xe4\xca\xb1\xff\xc6\x9a\x76\xfa\xa6\x67\x38\xf2\xa6\x67\x34\ +\x59\xa5\x65\x35\x30\xa7\x68\x38\xf3\xd2\xaa\x87\xff\xa4\x64\x33\ +\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xa5\x65\x35\x60\xa9\x6c\x3e\xf3\xd4\xb1\x94\ +\xfe\xe8\xd2\xbc\xff\xe8\xd1\xbb\xff\xe7\xd0\xb9\xff\xe6\xce\xb8\ +\xff\xe5\xcd\xb5\xff\xe5\xcb\xb3\xff\xe5\xcb\xb3\xff\xe5\xcb\xb2\ +\xff\xe5\xcc\xb4\xff\xe6\xce\xb8\xff\xe1\xc4\xaa\xff\xc9\x9e\x79\ +\xfa\xa9\x69\x3b\xf5\xa6\x68\x37\x9a\xa6\x66\x33\x14\x00\x00\x00\ +\x00\x00\x00\x00\x00\xa4\x63\x34\x3b\xa7\x67\x38\xf5\xa4\x64\x33\ +\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x65\x35\x2b\xa8\x6b\x3c\ +\xc8\xb0\x77\x4b\xf0\xce\xa6\x87\xfc\xde\xbf\xa5\xff\xe4\xca\xb3\ +\xff\xe8\xd0\xba\xff\xe6\xce\xb6\xff\xe1\xc6\xaf\xff\xd9\xb8\x9c\ +\xff\xcb\xa3\x82\xfc\xb5\x7e\x53\xf1\xa7\x67\x37\xf0\xa6\x69\x38\ +\x8d\xa5\x63\x31\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x62\x33\x46\xa4\x64\x33\ +\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\xa4\x61\x31\x2a\xa9\x6a\x39\x82\xa9\x6c\x3d\xcd\xa8\x68\x39\ +\xed\xa5\x65\x35\xfa\xa6\x67\x37\xf6\xa7\x67\x38\xed\xa8\x6a\x3a\ +\xca\xa6\x67\x35\x8f\xa2\x64\x32\x42\xaa\x55\x2b\x06\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa3\x65\x34\ +\x53\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xfc\x01\xff\xf7\xf0\x00\x7f\xf3\xc0\x00\x3f\xf1\x80\x00\ +\x1f\xf0\x00\x00\x0f\xf0\x00\x00\x0f\xf0\x00\x7c\x07\xf0\x03\xfe\ +\x07\xf0\x07\xfe\xe7\xf0\x0f\xff\xe7\xf0\x07\xff\xf7\xf0\x03\xff\ +\xf7\xf0\x01\xff\xff\xff\xff\xc0\x07\xf7\xff\xe0\x07\xf3\xff\xf0\ +\x07\xf0\x3f\xf8\x07\xf0\x3f\xf0\x07\xf0\x3f\xe0\x07\xf0\x1f\x00\ +\x07\xf8\x00\x00\x07\xf8\x00\x00\x07\xfc\x00\x00\xc7\xfe\x00\x01\ +\xe7\xff\x00\x07\xf7\xff\xc0\x3f\xff\xff\xff\xff\xff\ +\x00\x00\x10\xbe\ +\x00\ +\x00\x01\x00\x01\x00\x20\x20\x00\x00\x00\x00\x00\x00\xa8\x10\x00\ +\x00\x16\x00\x00\x00\x28\x00\x00\x00\x20\x00\x00\x00\x40\x00\x00\ +\x00\x01\x00\x20\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x80\x0c\x16\x1e\x7c\ +\xc9\x0c\x0f\x77\xd9\x00\x00\x78\x22\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x00\x78\x22\x29\x29\xa5\xde\x2d\x2d\xab\xc8\x00\x00\x80\ +\x0c\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x80\x0c\x00\x00\x81\xc3\x20\x2a\x81\ +\xff\x24\x2c\x86\xff\x00\x00\x61\xe7\x00\x00\x72\x23\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x72\ +\x23\x00\x00\x63\xe6\x36\x38\xa8\xff\x2d\x2e\xa8\xff\x00\x00\x81\ +\xc3\x00\x00\x80\x0c\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x00\x80\x0c\x00\x00\x81\xc3\x0a\x0b\x86\xff\x66\x6e\xdb\ +\xff\x56\x5c\xe5\xff\x1d\x1f\x79\xff\x00\x00\x56\xea\x00\x00\x71\ +\x25\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x71\x25\x00\x00\x56\ +\xea\x1d\x1f\x7e\xff\x55\x5b\xe5\xff\x66\x6e\xdb\xff\x0a\x0b\x86\ +\xff\x00\x00\x81\xc3\x00\x00\x80\x0c\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x80\ +\x0c\x00\x00\x81\xc3\x0a\x0a\x86\xff\x64\x6d\xdb\xff\x06\x07\xf5\ +\xff\x00\x00\xf5\xff\x52\x58\xe2\xff\x1c\x1e\x68\xff\x00\x00\x4e\ +\xec\x00\x00\x6f\x26\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x6f\x26\x00\x00\x4d\xec\x1c\x1e\x6a\ +\xff\x52\x58\xe2\xff\x00\x00\xf5\xff\x06\x07\xf5\xff\x65\x6d\xdb\ +\xff\x0a\x0b\x86\xff\x00\x00\x81\xc3\x00\x00\x80\x0c\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x0e\x15\x7e\ +\xc2\x1f\x28\x82\xff\x62\x6c\xdb\xff\x06\x07\xf5\xff\x00\x00\xf5\ +\xff\x00\x00\xf5\xff\x00\x00\xf5\xff\x4e\x56\xdf\xff\x1b\x1d\x56\ +\xff\x00\x00\x4a\xed\x00\x00\x6f\x26\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x00\x6f\x26\x00\x00\x47\xee\x1c\x1d\x59\xff\x50\x57\xdf\ +\xff\x00\x00\xf5\xff\x00\x00\xf5\xff\x00\x00\xf5\xff\x06\x07\xf5\ +\xff\x66\x6e\xdb\xff\x26\x27\x94\xff\x11\x11\x8a\xb8\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x0f\x15\x7e\ +\xda\x2b\x36\x8c\xff\x4e\x59\xe3\xff\x00\x00\xf0\xff\x00\x00\xf0\ +\xff\x00\x00\xf0\xff\x00\x00\xf0\xff\x00\x00\xef\xff\x4b\x54\xd7\ +\xff\x1a\x1d\x47\xff\x00\x00\x49\xee\x00\x00\x6f\x28\x00\x00\x6f\ +\x28\x00\x00\x44\xf0\x1a\x1d\x48\xff\x4c\x54\xd9\xff\x00\x00\xef\ +\xff\x00\x00\xf0\xff\x00\x00\xf0\xff\x00\x00\xf0\xff\x00\x00\xf0\ +\xff\x55\x5c\xe5\xff\x56\x58\xb0\xff\x26\x26\x94\xd6\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x07\x0e\x79\ +\x25\x00\x00\x81\xe0\x17\x1d\x93\xff\x42\x51\xde\xff\x00\x00\xe7\ +\xff\x00\x00\xe7\xff\x00\x00\xe7\xff\x00\x00\xe7\xff\x00\x00\xe6\ +\xff\x45\x51\xce\xff\x18\x1c\x40\xff\x00\x00\x40\xf0\x00\x00\x3b\ +\xf1\x18\x1c\x3f\xff\x48\x52\xcf\xff\x00\x00\xe6\xff\x00\x00\xe7\ +\xff\x00\x00\xe7\xff\x00\x00\xe7\xff\x00\x00\xe7\xff\x4c\x55\xde\ +\xff\x1b\x1e\x93\xff\x0c\x0c\x87\xe1\x2f\x2f\x97\x26\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x00\x80\x20\x00\x00\x81\xe1\x13\x1b\x93\xff\x38\x4c\xda\ +\xff\x00\x00\xde\xff\x00\x00\xde\xff\x00\x00\xde\xff\x00\x00\xde\ +\xff\x00\x00\xdb\xff\x3b\x4c\xc5\xff\x14\x1a\x31\xff\x14\x1a\x30\ +\xff\x3e\x4d\xc6\xff\x00\x00\xdc\xff\x00\x00\xde\xff\x00\x00\xde\ +\xff\x00\x00\xde\xff\x00\x00\xde\xff\x42\x51\xda\xff\x17\x1d\x93\ +\xff\x00\x00\x81\xe1\x00\x00\x80\x20\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x83\x21\x00\x00\x83\xe1\x12\x1a\x95\ +\xff\x32\x4a\xd5\xff\x00\x00\xd5\xff\x00\x00\xd5\xff\x00\x00\xd5\ +\xff\x00\x00\xd4\xff\x00\x00\xd1\xff\x31\x48\xbc\xff\x32\x48\xbd\ +\xff\x00\x00\xd1\xff\x00\x00\xd5\xff\x00\x00\xd5\xff\x00\x00\xd5\ +\xff\x00\x00\xd5\xff\x37\x4c\xd5\xff\x13\x1b\x95\xff\x00\x00\x83\ +\xe1\x00\x00\x83\x21\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x87\x22\x00\x00\x86\ +\xe2\x10\x19\x96\xff\x2f\x48\xd1\xff\x00\x00\xcd\xff\x00\x00\xcd\ +\xff\x00\x00\xcc\xff\x00\x00\xcb\xff\x00\x00\xc7\xff\x00\x00\xc7\ +\xff\x00\x00\xcb\xff\x00\x00\xcc\xff\x00\x00\xcd\xff\x00\x00\xcd\ +\xff\x31\x4a\xd1\xff\x11\x19\x96\xff\x00\x00\x86\xe2\x00\x00\x87\ +\x22\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x87\ +\x22\x00\x00\x89\xe3\x0f\x18\x99\xff\x2b\x46\xcd\xff\x00\x00\xc2\ +\xff\x00\x00\xc2\xff\x00\x00\xc1\xff\x00\x00\xc0\xff\x00\x00\xc0\ +\xff\x00\x00\xc1\xff\x00\x00\xc2\xff\x00\x00\xc2\xff\x2d\x47\xcd\ +\xff\x10\x19\x99\xff\x00\x00\x89\xe3\x00\x00\x87\x22\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x00\x8a\x23\x00\x00\x8c\xe3\x0e\x17\x9b\xff\x28\x44\xc8\ +\xff\x00\x00\xb9\xff\x00\x00\xb9\xff\x00\x00\xb9\xff\x00\x00\xb9\ +\xff\x00\x00\xb9\xff\x00\x00\xb9\xff\x29\x44\xc8\xff\x0e\x17\x9b\ +\xff\x00\x00\x8c\xe3\x00\x00\x8a\x23\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x00\x92\x23\x00\x00\x8f\xe3\x0e\x18\x9f\xff\x48\x5e\xd1\ +\xff\x39\x39\xc4\xff\x3b\x3b\xc4\xff\x1d\x1d\xba\xff\x01\x01\xb0\ +\xff\x00\x00\xb0\xff\x00\x00\xb0\xff\x27\x43\xc5\xff\x0d\x17\x9e\ +\xff\x00\x00\x8f\xe3\x00\x00\x92\x23\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x8f\ +\x22\x00\x00\x92\xe3\x0e\x18\xa1\xff\x3d\x54\xd1\xff\x2e\x2e\xc5\ +\xff\x2e\x2e\xc5\xff\x32\x32\xc5\xff\x4e\x4e\xce\xff\x68\x67\xd5\ +\xff\x6c\x6b\xd7\xff\x69\x69\xd1\xff\x3f\x3f\xbf\xff\x25\x44\xc2\ +\xff\x0d\x17\xa0\xff\x00\x00\x92\xe3\x00\x00\x8f\x22\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x96\x22\x00\x00\x95\ +\xe2\x0f\x19\xa3\xff\x43\x59\xd3\xff\x39\x39\xc8\xff\x39\x39\xc8\ +\xff\x39\x39\xc8\xff\x39\x39\xc7\xff\x3b\x3b\xc2\xff\x3b\x3b\xc1\ +\xff\x39\x39\xc5\xff\x39\x39\xc8\xff\x58\x58\xd2\xff\x7b\x7a\xda\ +\xff\x86\x93\xe0\xff\x16\x20\xa6\xff\x00\x00\x95\xe2\x00\x00\x96\ +\x22\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x9b\x21\x00\x00\x98\xe1\x0f\x1a\xa6\ +\xff\x49\x5d\xd4\xff\x44\x44\xcb\xff\x44\x44\xcb\xff\x44\x44\xcb\ +\xff\x44\x44\xca\xff\x46\x46\xc6\xff\x71\x80\xd2\xff\x6f\x7e\xcf\ +\xff\x45\x45\xc3\xff\x44\x44\xca\xff\x44\x44\xcb\xff\x44\x44\xcb\ +\xff\x47\x47\xcc\xff\x5a\x6c\xd9\xff\x14\x1f\xa8\xff\x00\x00\x98\ +\xe1\x00\x00\x9b\x21\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x00\x97\x20\x00\x00\x9b\xe1\x10\x1b\xa9\xff\x50\x62\xd6\ +\xff\x50\x50\xd0\xff\x50\x50\xd0\xff\x50\x50\xd0\xff\x50\x50\xd0\ +\xff\x51\x51\xce\xff\x80\x8c\xdb\xff\x16\x20\x7d\xff\x15\x1f\x73\ +\xff\x7b\x88\xd5\xff\x51\x51\xcc\xff\x50\x50\xd0\xff\x50\x50\xd0\ +\xff\x50\x50\xd0\xff\x50\x50\xd0\xff\x70\x7e\xdf\xff\x18\x22\xac\ +\xff\x00\x00\x9b\xe1\x00\x00\x97\x20\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x9f\ +\x20\x00\x00\x9e\xe0\x11\x1c\xac\xff\x56\x66\xd8\xff\x5b\x5b\xd6\ +\xff\x5b\x5b\xd6\xff\x5b\x5b\xd6\xff\x5b\x5b\xd6\xff\x5d\x5d\xd6\ +\xff\x8b\x96\xe5\xff\x1b\x23\x95\xff\x00\x00\x74\xea\x00\x00\x66\ +\xec\x19\x21\x7c\xff\x83\x8f\xde\xff\x5c\x5c\xd5\xff\x5b\x5b\xd6\ +\xff\x5b\x5b\xd6\xff\x5b\x5b\xd6\xff\x5b\x5b\xd6\xff\x91\x9a\xe9\ +\xff\x19\x23\xaf\xff\x00\x00\x9e\xe0\x00\x00\x9f\x20\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\xa1\ +\xd0\x0d\x16\xab\xff\x5c\x6b\xde\xff\x67\x67\xe1\xff\x67\x67\xe1\ +\xff\x67\x67\xe1\xff\x67\x67\xe1\xff\x68\x68\xe1\xff\x95\x9e\xea\ +\xff\x1b\x23\xaf\xff\x00\x00\x91\xe6\x00\x00\x93\x26\x00\x00\x8c\ +\x27\x00\x00\x77\xea\x1b\x23\x99\xff\x89\x93\xe7\xff\x67\x67\xe1\ +\xff\x67\x67\xe1\xff\x67\x67\xe1\xff\x67\x67\xe1\xff\x6a\x6a\xe2\ +\xff\xac\xb2\xf1\xff\x0b\x14\xaa\xff\x00\x00\xa1\xd0\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\xa3\ +\xb5\x05\x08\xa8\xff\x5f\x75\xdf\xff\x72\x73\xec\xff\x72\x72\xeb\ +\xff\x72\x72\xeb\xff\x73\x73\xeb\xff\x9f\xa5\xef\xff\x1d\x27\xb4\ +\xff\x00\x00\xa3\xe2\x00\x00\xa0\x23\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x00\x97\x25\x00\x00\x94\xe5\x1e\x28\xb2\xff\x8f\x97\xeb\ +\xff\x72\x72\xeb\xff\x72\x72\xeb\xff\x72\x72\xeb\xff\x7b\x7b\xee\ +\xff\x9b\xa7\xed\xff\x05\x08\xa8\xff\x00\x00\xa3\xb5\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\xaa\ +\x0c\x00\x00\xa7\xc3\x05\x08\xab\xff\x74\x86\xe6\xff\x7e\x7e\xf6\ +\xff\x7e\x7e\xf6\xff\xa6\xab\xf3\xff\x1e\x28\xb7\xff\x00\x00\xa7\ +\xe1\x00\x00\xa5\x22\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\xa0\x23\x00\x00\xa6\xe1\x1f\x29\xb7\ +\xff\x93\x9a\xf0\xff\x7d\x7d\xf6\xff\x85\x85\xf7\xff\x9e\xa9\xef\ +\xff\x05\x08\xab\xff\x00\x00\xa7\xc3\x00\x00\xaa\x0c\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x00\xaa\x0c\x00\x00\xaa\xc3\x05\x08\xae\xff\x86\x94\xea\ +\xff\xab\xaf\xf6\xff\x20\x2a\xba\xff\x00\x00\xaa\xe1\x00\x00\xaa\ +\x21\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\xaa\x21\x00\x00\xaa\ +\xe1\x22\x2c\xbb\xff\x9c\xa1\xf3\xff\xa4\xae\xf1\xff\x05\x08\xae\ +\xff\x00\x00\xaa\xc3\x00\x00\xaa\x0c\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\xaa\x0c\x00\x00\xad\xc3\x15\x18\xb2\ +\xff\x45\x4e\xc1\xff\x00\x00\xad\xe0\x00\x00\xaf\x20\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\xaf\ +\x20\x00\x00\xad\xe0\x37\x40\xc0\xff\x28\x2b\xb5\xff\x00\x00\xad\ +\xc3\x00\x00\xaa\x0c\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\xaa\x0c\x17\x17\xb1\ +\xc0\x30\x30\xb9\xdc\x08\x08\xac\x22\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x00\x00\xb1\x21\x24\x24\xb6\xda\x24\x24\xb5\xc4\x00\x00\xb1\ +\x0d\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3f\xfc\ +\xff\xfe\x1f\xf8\x7f\xfc\x0f\xf0\x3f\xf8\x07\xe0\x1f\xf0\x03\xc0\ +\x0f\xf0\x01\x80\x0f\xf8\x00\x00\x1f\xfc\x00\x00\x3f\xfe\x00\x00\ +\x7f\xff\x00\x00\xff\xff\x80\x01\xff\xff\xc0\x03\xff\xff\xc0\x03\ +\xff\xff\x80\x01\xff\xff\x00\x00\xff\xfe\x00\x00\x7f\xfc\x00\x00\ +\x3f\xf8\x00\x00\x1f\xf0\x01\x80\x0f\xf0\x03\xc0\x0f\xf8\x07\xe0\ +\x1f\xfc\x0f\xf0\x3f\xfe\x1f\xf8\x7f\xff\x3f\xfc\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\x00\x00\x10\xbe\ +\x00\ +\x00\x01\x00\x01\x00\x20\x20\x00\x00\x00\x00\x00\x00\xa8\x10\x00\ +\x00\x16\x00\x00\x00\x28\x00\x00\x00\x20\x00\x00\x00\x40\x00\x00\ +\x00\x01\x00\x20\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xe7\x5f\x00\x00\xea\x61\x00\x00\xed\x62\x00\ +\x00\xee\x63\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\xe0\x5c\x00\x00\xe7\x5f\x00\x07\xea\x61\x00\x3a\xed\x62\x00\ +\x40\xee\x63\x00\x0b\xee\x64\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\xff\x71\x00\x00\xe5\x5e\x00\x52\xe8\x5f\x00\xe0\xeb\x60\x00\ +\xe9\xed\x63\x00\x66\xf0\x65\x00\x03\xee\x64\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd0\x52\x00\x00\xd7\x56\x00\ +\x00\xde\x5b\x00\x34\xe0\x5a\x00\xd1\xe9\x6b\x11\xff\xee\x73\x17\ +\xff\xeb\x61\x01\xe0\xed\x63\x00\x47\xf7\x6d\x00\x00\xed\x63\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\x55\x00\x00\xd9\x57\x00\ +\x1e\xda\x57\x00\xb3\xe1\x63\x0c\xfe\xf6\x9e\x50\xff\xfa\xa8\x58\ +\xff\xec\x6c\x10\xff\xea\x60\x00\xc5\xec\x62\x00\x2a\xeb\x61\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xc9\x4f\x00\x00\xd3\x55\x00\x0e\xd5\x54\x00\ +\x93\xd8\x59\x05\xfb\xef\x92\x46\xff\xfe\xbf\x7a\xff\xff\xa1\x4d\ +\xff\xf6\x6f\x0b\xff\xe9\x60\x00\xfe\xea\x61\x00\xa9\xec\x62\x00\ +\x18\xea\x61\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\xc2\x4a\x00\x00\xca\x52\x00\x02\xcf\x50\x00\x6e\xd1\x52\x02\ +\xf3\xe5\x83\x3a\xff\xfd\xc2\x83\xff\xff\xb3\x6a\xff\xff\x80\x1d\ +\xff\xfe\x70\x05\xff\xf2\x67\x01\xff\xe8\x60\x00\xf9\xea\x61\x00\ +\x85\xec\x62\x00\x08\xed\x63\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbd\x49\x00\ +\x00\xc3\x4b\x00\x01\xc8\x4e\x00\x4c\xca\x4c\x00\xe1\xdb\x73\x2c\ +\xff\xf9\xc0\x86\xff\xff\xc2\x84\xff\xff\x95\x3d\xff\xff\x7d\x19\ +\xff\xff\x78\x10\xff\xfd\x70\x06\xff\xef\x64\x01\xff\xe8\x60\x00\ +\xeb\xea\x61\x00\x61\xee\x64\x00\x03\xea\x61\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbb\x46\x00\ +\x00\xc2\x4b\x00\x2f\xc4\x49\x00\xce\xd1\x63\x1e\xff\xf4\xb9\x83\ +\xff\xff\xce\x99\xff\xff\xa9\x5d\xff\xff\x8c\x2f\xff\xff\x85\x25\ +\xff\xff\x7e\x1a\xff\xff\x78\x10\xff\xfb\x6f\x06\xff\xeb\x62\x00\ +\xff\xe7\x60\x00\xdc\xea\x61\x00\x3f\xcb\x47\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb5\x43\x00\x00\xbd\x48\x00\ +\x19\xbe\x45\x00\xab\xc6\x54\x10\xff\xec\xab\x78\xff\xff\xd7\xaa\ +\xff\xff\xba\x7a\xff\xff\x9b\x46\xff\xff\x95\x3c\xff\xff\x8e\x31\ +\xff\xff\x85\x25\xff\xff\x7f\x1b\xff\xff\x78\x10\xff\xf8\x6d\x05\ +\xff\xe9\x60\x00\xff\xe8\x5f\x00\xc0\xea\x61\x00\x27\xe9\x60\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xaf\x3f\x00\x00\xb8\x45\x00\x0a\xb8\x43\x00\ +\x8c\xbd\x49\x08\xf8\xe1\x9a\x67\xff\xfd\xdb\xb5\xff\xff\xcb\x97\ +\xff\xff\xab\x5f\xff\xff\xa1\x4e\xff\xf2\x88\x34\xff\xf1\x7f\x28\ +\xff\xfe\x8b\x2e\xff\xff\x85\x24\xff\xff\x7e\x1a\xff\xff\x77\x0f\ +\xff\xf4\x6a\x05\xff\xe7\x5f\x00\xfc\xe8\x5f\x00\xa2\xea\x61\x00\ +\x11\xe9\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\xa7\x3d\x00\x00\xb0\x40\x00\x04\xb2\x40\x00\x65\xb5\x41\x04\ +\xf1\xd4\x84\x52\xff\xfc\xde\xbe\xff\xff\xd8\xaf\xff\xff\xbb\x79\ +\xff\xff\xb1\x66\xff\xf0\x91\x43\xff\xd1\x58\x0b\xff\xd2\x55\x06\ +\xff\xef\x7f\x29\xff\xff\x91\x36\xff\xff\x85\x24\xff\xff\x7e\x1a\ +\xff\xfe\x76\x0f\xff\xf0\x67\x04\xff\xe6\x5e\x00\xf7\xe8\x5f\x00\ +\x7b\xea\x60\x00\x09\xe9\x61\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x9d\x30\x00\x00\xac\x3e\x00\x48\xad\x3b\x00\xde\xc6\x6e\x3c\ +\xff\xf5\xd6\xb9\xff\xff\xe4\xc6\xff\xff\xcc\x94\xff\xff\xbe\x7a\ +\xff\xf6\xa8\x61\xff\xd2\x62\x18\xff\xc8\x4c\x00\xde\xcb\x4f\x00\ +\xd8\xd6\x61\x13\xff\xf5\x99\x4d\xff\xff\x91\x37\xff\xff\x83\x22\ +\xff\xff\x7e\x19\xff\xfc\x75\x0e\xff\xec\x64\x03\xff\xe5\x5e\x00\ +\xea\xe7\x5f\x00\x5c\xf4\x67\x00\x01\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9e\x36\x00\ +\x00\xa5\x3b\x00\x28\xa7\x38\x00\xc6\xb8\x58\x25\xff\xee\xce\xb4\ +\xff\xff\xee\xd8\xff\xff\xda\xad\xff\xff\xcc\x8f\xff\xfa\xbc\x7c\ +\xff\xd4\x70\x2b\xff\xc1\x48\x00\xec\xc6\x4c\x00\x5b\xcb\x50\x00\ +\x48\xcb\x4e\x00\xe1\xdb\x6f\x26\xff\xf9\xa3\x5a\xff\xff\x8e\x32\ +\xff\xff\x84\x23\xff\xff\x7d\x19\xff\xfa\x73\x0d\xff\xe8\x62\x02\ +\xff\xe5\x5e\x00\xd6\xe7\x60\x00\x37\xe7\x5f\x00\x00\xe9\x5f\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x35\x00\x00\xa0\x38\x00\ +\x18\xa1\x34\x00\xa7\xac\x48\x18\xfd\xe1\xb7\x9d\xff\xfe\xf4\xe6\ +\xff\xff\xe8\xc7\xff\xff\xd9\xa5\xff\xfd\xd1\x98\xff\xdb\x88\x48\ +\xff\xbd\x48\x05\xf9\xbf\x47\x00\x85\xc3\x4b\x00\x09\xcb\x50\x00\ +\x04\xca\x4e\x00\x6f\xcc\x4f\x04\xf3\xe2\x83\x42\xff\xfd\xa8\x5f\ +\xff\xff\x8b\x2d\xff\xff\x84\x22\xff\xff\x7d\x18\xff\xf6\x70\x0b\ +\xff\xe6\x60\x01\xfe\xe5\x5e\x00\xb9\xe7\x5f\x00\x23\xe5\x5f\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x35\x00\x09\x9b\x32\x00\ +\x84\xa0\x39\x09\xf9\xd4\xa2\x89\xff\xfd\xf7\xf1\xff\xff\xf3\xdd\ +\xff\xff\xe7\xbc\xff\xfe\xe2\xb0\xff\xe2\xa1\x68\xff\xb9\x49\x0a\ +\xfc\xb8\x43\x00\xa5\xbd\x48\x00\x14\xc3\x4b\x00\x00\xcb\x50\x00\ +\x00\xc9\x4f\x00\x0c\xc9\x4d\x00\x90\xcd\x53\x09\xf9\xe8\x95\x5a\ +\xff\xfe\xa6\x5b\xff\xff\x8a\x2b\xff\xff\x84\x23\xff\xff\x7d\x18\ +\xff\xf3\x6d\x0a\xff\xe4\x5e\x00\xfd\xe5\x5e\x00\x9a\xe7\x5f\x00\ +\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98\x31\x00\x49\x9a\x33\x05\ +\xe9\xc2\x81\x63\xff\xf9\xf1\xee\xff\xff\xfc\xf4\xff\xff\xf5\xd3\ +\xff\xff\xf1\xc7\xff\xeb\xc1\x90\xff\xba\x55\x1b\xff\xb1\x3e\x00\ +\xc8\xb6\x44\x00\x2b\xbe\x48\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\xca\x4f\x00\x00\xc9\x4f\x00\x1d\xc9\x4c\x00\xb5\xd0\x5e\x18\ +\xff\xef\xab\x77\xff\xff\xa0\x50\xff\xff\x8a\x2b\xff\xff\x84\x22\ +\xff\xfd\x7b\x17\xff\xed\x68\x08\xff\xe3\x5d\x00\xf2\xe5\x5e\x00\ +\x5b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\x2c\x00\x8b\xad\x5b\x35\ +\xff\xf0\xe1\xd9\xff\xff\xff\xfb\xff\xfe\xfe\xe6\xff\xff\xfd\xda\ +\xff\xf2\xdc\xb3\xff\xbd\x63\x30\xff\xab\x3a\x00\xdd\xb0\x40\x00\ +\x45\xa9\x42\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xce\x52\x00\x00\xc8\x4f\x00\x33\xc9\x4c\x00\ +\xd0\xd4\x66\x22\xff\xf5\x95\x48\xff\xff\x92\x37\xff\xff\x8a\x2d\ +\xff\xff\x84\x23\xff\xfb\x7a\x16\xff\xe8\x63\x05\xff\xe1\x5c\x00\ +\x92\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x97\x30\x00\x85\xa5\x4b\x1d\ +\xff\xc0\x81\x61\xff\xc3\x86\x62\xff\xc4\x86\x5b\xff\xc6\x88\x5c\ +\xff\xba\x68\x36\xff\xa6\x3a\x02\xf0\xa9\x3c\x00\x67\xac\x40\x00\ +\x03\xb5\x44\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xc8\x4e\x00\x00\xc6\x4e\x00\x01\xc8\x4e\x00\ +\x53\xc9\x4d\x00\xe7\xd7\x5f\x0d\xff\xe3\x6e\x1a\xff\xe4\x6b\x15\ +\xff\xe7\x6a\x11\xff\xe8\x68\x0c\xff\xe2\x5f\x03\xff\xdf\x5b\x00\ +\x8e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x99\x33\x00\x3a\x98\x31\x00\ +\xd5\x97\x2f\x00\xfb\x97\x2f\x00\xfc\x97\x2f\x00\xfc\x9b\x31\x00\ +\xfc\x9f\x34\x00\xef\xa3\x39\x00\x85\xa6\x3b\x00\x0c\xae\x40\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc7\x4e\x00\x00\xc7\x4d\x00\ +\x05\xc8\x4e\x00\x70\xc9\x4e\x00\xe9\xcc\x4f\x00\xfc\xcf\x51\x00\ +\xfc\xd2\x54\x00\xfc\xd6\x55\x00\xfc\xda\x58\x00\xdf\xdd\x5a\x00\ +\x4b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x99\x33\x00\x03\x99\x33\x00\ +\x34\x99\x33\x00\x53\x99\x33\x00\x53\x99\x33\x00\x53\x9b\x34\x00\ +\x54\x9e\x35\x00\x46\xa1\x37\x00\x10\xa8\x3c\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc8\x4e\x00\ +\x00\xc7\x4c\x00\x0a\xc9\x4e\x00\x41\xcb\x50\x00\x54\xce\x51\x00\ +\x53\xd1\x54\x00\x53\xd5\x55\x00\x53\xd7\x57\x00\x3b\xdb\x59\x00\ +\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x99\x33\x00\x00\x99\x33\x00\ +\x00\x99\x33\x00\x00\x99\x33\x00\x00\x99\x33\x00\x00\x9b\x34\x00\ +\x00\x9e\x36\x00\x00\xa1\x37\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\xc7\x4d\x00\x00\xc9\x4e\x00\x00\xcb\x50\x00\x00\xcf\x52\x00\ +\x00\xd2\x54\x00\x00\xd5\x55\x00\x00\xd8\x57\x00\x00\xdb\x59\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\x7f\xff\xff\xfc\x3f\ +\xff\xff\xf8\x1f\xff\xff\xf0\x0f\xff\xff\xf0\x07\xff\xff\xe0\x07\ +\xff\xff\xc0\x03\xff\xff\x80\x01\xff\xff\x00\x00\xff\xff\x00\x00\ +\xff\xfe\x00\x00\x7f\xfc\x01\x80\x3f\xf8\x01\xc0\x1f\xf0\x03\xc0\ +\x0f\xf0\x07\xe0\x0f\xe0\x0f\xf0\x07\xe0\x1f\xf8\x07\xf0\x1f\xfc\ +\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\x00\x00\x10\xbe\ +\x00\ +\x00\x01\x00\x01\x00\x20\x20\x00\x00\x00\x00\x00\x00\xa8\x10\x00\ +\x00\x16\x00\x00\x00\x28\x00\x00\x00\x20\x00\x00\x00\x40\x00\x00\ +\x00\x01\x00\x20\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98\x33\x00\x00\x9a\x33\x00\ +\x00\x9d\x35\x00\x00\xa2\x38\x00\x00\xa6\x3a\x00\x00\xaa\x3d\x00\ +\x00\xae\x3f\x00\x00\xb1\x41\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\xe1\x5c\x00\x00\xe4\x5e\x00\x00\xe8\x60\x00\x00\xec\x62\x00\ +\x00\xf0\x64\x00\x00\xf5\x68\x00\x00\xf8\x6a\x00\x00\xfa\x6a\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98\x33\x00\x05\x9a\x33\x00\ +\x39\x9d\x35\x00\x53\xa2\x38\x00\x53\xa6\x3a\x00\x53\xaa\x3c\x00\ +\x54\xae\x3f\x00\x49\xb1\x41\x00\x15\xb0\x40\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd9\x57\x00\ +\x00\xe1\x5d\x00\x0e\xe4\x5e\x00\x45\xe8\x60\x00\x54\xec\x62\x00\ +\x53\xf0\x64\x00\x53\xf5\x67\x00\x53\xf8\x6a\x00\x40\xfa\x6a\x00\ +\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x99\x33\x00\x40\x99\x32\x00\ +\xdb\x99\x31\x00\xfc\x9e\x35\x00\xfc\xa2\x37\x00\xfc\xa6\x39\x00\ +\xfc\xab\x3c\x00\xf2\xaf\x40\x00\x91\xb1\x41\x00\x0f\xb0\x41\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd2\x53\x00\x00\xdc\x58\x00\ +\x07\xde\x5a\x00\x7a\xe1\x5c\x00\xee\xe6\x5d\x00\xfc\xea\x60\x00\ +\xfc\xee\x62\x00\xfc\xf3\x65\x00\xfc\xf7\x68\x00\xe5\xf9\x6a\x00\ +\x51\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x97\x30\x00\x88\xa5\x47\x15\ +\xff\xc3\x72\x3e\xff\xc7\x6d\x30\xff\xc9\x6a\x2a\xff\xcc\x67\x22\ +\xff\xc0\x55\x12\xff\xac\x3e\x01\xf4\xae\x3f\x00\x70\xb2\x42\x00\ +\x04\xb0\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xc8\x4d\x00\x00\xd6\x56\x00\x02\xd6\x55\x00\ +\x5a\xd9\x56\x00\xeb\xe6\x6a\x10\xff\xef\x7d\x23\xff\xf2\x80\x25\ +\xff\xf4\x82\x26\xff\xf6\x83\x25\xff\xf6\x71\x0d\xff\xf6\x67\x00\ +\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\x2d\x00\x89\xac\x52\x26\ +\xff\xef\xc1\x96\xff\xff\xc7\x87\xff\xff\xb4\x6a\xff\xff\xa9\x5b\ +\xff\xf4\x93\x41\xff\xbf\x54\x11\xff\xaa\x3c\x00\xe2\xae\x40\x00\ +\x4c\xb5\x44\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xf7\x6e\x00\x00\xce\x51\x00\x39\xd1\x52\x01\ +\xd6\xdf\x68\x16\xff\xf9\x8f\x34\xff\xff\xa1\x46\xff\xff\xab\x54\ +\xff\xff\xae\x59\xff\xfc\xa5\x4e\xff\xf3\x75\x14\xff\xf2\x64\x00\ +\x91\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98\x32\x00\x44\x98\x32\x03\ +\xe5\xbc\x72\x4a\xff\xf7\xcf\xa6\xff\xff\xc5\x86\xff\xff\xb4\x6b\ +\xff\xff\xaa\x5b\xff\xed\x8b\x3b\xff\xb7\x4a\x0c\xff\xaa\x3c\x00\ +\xce\xae\x40\x00\x32\xac\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\xbb\x47\x00\x00\xc7\x4c\x00\x22\xc8\x4c\x00\xbd\xd5\x62\x17\ +\xff\xf5\x92\x3f\xff\xff\x8f\x2d\xff\xff\x94\x34\xff\xff\xa2\x47\ +\xff\xfd\xa7\x50\xff\xf0\x7e\x23\xff\xeb\x61\x01\xef\xee\x63\x00\ +\x56\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x35\x00\x07\x98\x31\x00\ +\x7a\x9a\x34\x06\xf7\xcc\x8f\x6b\xff\xfd\xd8\xaf\xff\xff\xc2\x83\ +\xff\xff\xb4\x6b\xff\xff\xa9\x59\xff\xe1\x7d\x32\xff\xae\x41\x05\ +\xfe\xaa\x3c\x00\xae\xae\x40\x00\x18\xad\x3e\x00\x00\xb6\x44\x00\ +\x00\xbe\x4a\x00\x0e\xc1\x47\x00\x98\xc8\x54\x0e\xfa\xeb\x8c\x42\ +\xff\xff\x86\x24\xff\xff\x81\x1a\xff\xff\x8a\x27\xff\xff\x95\x36\ +\xff\xf1\x81\x27\xff\xe3\x5f\x04\xfd\xe7\x5f\x00\x91\xed\x63\x00\ +\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x34\x00\x00\x99\x34\x00\ +\x13\x97\x2f\x00\x9e\x9f\x3e\x13\xfc\xd9\xa6\x87\xff\xfe\xda\xb1\ +\xff\xff\xc2\x84\xff\xff\xb4\x6b\xff\xfd\xa7\x58\xff\xd5\x71\x29\ +\xff\xaa\x3d\x03\xfa\xaa\x3d\x00\x8c\xae\x3f\x00\x0b\xb8\x46\x00\ +\x06\xb9\x43\x00\x78\xbe\x4b\x0a\xf5\xe0\x87\x49\xff\xfd\x8a\x2e\ +\xff\xff\x75\x09\xff\xff\x7b\x13\xff\xff\x82\x1d\xff\xf2\x79\x1b\ +\xff\xde\x5d\x05\xfe\xde\x5a\x00\xb3\xe3\x5e\x00\x1e\xe9\x61\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x99\x33\x00\ +\x00\x9a\x34\x00\x23\x97\x2f\x00\xc0\xa6\x4b\x21\xff\xe7\xc3\xa7\ +\xff\xff\xdb\xb1\xff\xff\xc1\x82\xff\xff\xb4\x6b\xff\xf9\xa2\x54\ +\xff\xc7\x60\x1e\xff\xa8\x3a\x00\xef\xab\x3d\x00\x64\xb2\x41\x00\ +\x52\xb4\x42\x06\xe5\xd1\x78\x43\xff\xfa\x92\x43\xff\xff\x70\x05\ +\xff\xff\x72\x06\xff\xff\x77\x0d\xff\xf6\x74\x10\xff\xd9\x5b\x04\ +\xff\xd6\x55\x00\xd0\xdb\x59\x00\x31\xe5\x5e\x00\x00\xfe\x6b\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x99\x32\x00\x00\x99\x33\x00\x3f\x97\x2e\x00\xd8\xb1\x61\x3c\ +\xff\xef\xd3\xbb\xff\xff\xd9\xae\xff\xff\xc1\x81\xff\xff\xb4\x6a\ +\xff\xf3\x9c\x4f\xff\xbc\x54\x15\xff\xa7\x3a\x00\xe5\xac\x3c\x00\ +\xe0\xc2\x62\x2b\xff\xf4\x9b\x59\xff\xff\x72\x0c\xff\xff\x6d\x00\ +\xff\xff\x70\x04\xff\xf8\x6f\x06\xff\xd9\x5a\x03\xff\xcd\x50\x00\ +\xe6\xd3\x53\x00\x53\xff\xdd\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x99\x34\x00\x00\x9a\x35\x00\x03\x98\x32\x00\x5c\x97\x30\x03\ +\xee\xbd\x7a\x5b\xff\xf9\xe5\xd0\xff\xff\xd6\xaa\xff\xff\xbf\x7e\ +\xff\xff\xb5\x6a\xff\xe8\x8f\x45\xff\xb2\x48\x0d\xff\xb1\x42\x06\ +\xff\xe6\x75\x23\xff\xff\x7a\x15\xff\xff\x6c\x00\xff\xff\x6e\x00\ +\xff\xfc\x6e\x02\xff\xdc\x5a\x02\xff\xc6\x4c\x00\xf5\xca\x4f\x00\ +\x73\xcf\x52\x00\x07\xd9\x57\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x99\x34\x00\x00\x9a\x36\x00\x08\x98\x31\x00\ +\x84\x9a\x35\x09\xf6\xcc\x97\x7e\xff\xfb\xea\xd5\xff\xff\xd4\xa6\ +\xff\xff\xbf\x7d\xff\xfe\xb2\x68\xff\xec\x92\x47\xff\xe9\x83\x33\ +\xff\xfd\x8a\x2c\xff\xff\x7e\x19\xff\xff\x73\x09\xff\xfd\x6d\x01\ +\xff\xe1\x5d\x00\xff\xc0\x49\x00\xfa\xc2\x4a\x00\x9a\xc7\x4e\x00\ +\x0d\xd0\x52\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x34\x00\x00\x99\x34\x00\ +\x14\x97\x2f\x00\xa3\x9f\x3e\x14\xfe\xda\xb4\xa1\xff\xff\xef\xdb\ +\xff\xff\xd2\xa1\xff\xff\xbe\x7b\xff\xff\xb3\x68\xff\xff\xa6\x55\ +\xff\xff\x98\x41\xff\xff\x8b\x2d\xff\xff\x7f\x1a\xff\xe8\x65\x06\ +\xff\xbd\x47\x00\xff\xb9\x45\x00\xb7\xbf\x49\x00\x21\xc9\x4f\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x99\x32\x00\ +\x00\x99\x34\x00\x29\x97\x2e\x00\xc8\xa6\x4d\x27\xff\xe7\xcd\xbf\ +\xff\xff\xed\xd8\xff\xff\xd0\x9d\xff\xff\xbd\x79\xff\xff\xb0\x66\ +\xff\xff\xa4\x53\xff\xff\x98\x41\xff\xf0\x7e\x26\xff\xbd\x4b\x06\ +\xff\xb1\x40\x00\xd7\xb7\x44\x00\x39\xc6\x4e\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x35\x00\ +\x00\x9a\x35\x00\x01\x99\x33\x00\x46\x96\x2d\x00\xdc\xb1\x63\x42\ +\xff\xf1\xe0\xd6\xff\xff\xeb\xd3\xff\xff\xcd\x97\xff\xff\xbc\x78\ +\xff\xff\xb1\x67\xff\xf6\x9c\x4c\xff\xc1\x57\x15\xff\xa8\x3b\x00\ +\xe7\xae\x3f\x00\x58\xb3\x41\x00\x02\xbd\x48\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x99\x33\x00\x00\x9a\x35\x00\x02\x98\x32\x00\x65\x97\x30\x02\ +\xef\xbe\x7d\x60\xff\xf7\xec\xe5\xff\xff\xe7\xcd\xff\xff\xcc\x92\ +\xff\xfb\xba\x76\xff\xca\x6e\x30\xff\xa2\x38\x02\xf7\xa5\x39\x00\ +\x7b\xaa\x3d\x00\x05\xb5\x42\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x99\x33\x00\x00\x9a\x35\x00\x0a\x98\x31\x00\ +\x89\x9a\x34\x08\xf9\xcd\x9b\x84\xff\xfd\xf7\xf2\xff\xff\xe6\xc7\ +\xff\xd7\x91\x5a\xff\x9e\x38\x07\xfd\x9c\x34\x00\xa0\xa3\x39\x00\ +\x13\xac\x3e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x34\x00\x00\x99\x34\x00\ +\x1a\x97\x2f\x00\xac\xa0\x40\x17\xfd\xdd\xbb\xac\xff\xe8\xce\xbf\ +\xff\xa7\x4d\x22\xff\x96\x2f\x00\xbe\x9a\x34\x00\x25\xa5\x3a\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x99\x33\x00\x00\x99\x33\x00\ +\x00\x99\x34\x00\x2d\x97\x2f\x00\xc9\xa7\x4f\x25\xff\xac\x59\x32\ +\xff\x98\x31\x01\xda\x99\x32\x00\x3e\x9d\x31\x00\x00\xa2\x39\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x96\x30\x00\x00\x99\x33\x00\x49\x98\x30\x00\xd9\x97\x30\x00\ +\xe4\x99\x33\x00\x5c\x9c\x3a\x00\x01\x9b\x36\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x99\x34\x00\x00\x99\x33\x00\x05\x99\x33\x00\x35\x99\x33\x00\ +\x3c\x99\x33\x00\x08\x99\x34\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x99\x33\x00\x00\x99\x33\x00\x00\x99\x33\x00\ +\x00\x99\x33\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x1f\xfc\x0f\xe0\x1f\xf8\ +\x07\xe0\x0f\xf0\x07\xf0\x07\xe0\x0f\xf8\x03\xc0\x0f\xf8\x01\xc0\ +\x1f\xfc\x01\x80\x3f\xfe\x00\x00\x7f\xff\x00\x00\xff\xff\x00\x00\ +\xff\xff\x80\x01\xff\xff\xc0\x03\xff\xff\xe0\x07\xff\xff\xf0\x0f\ +\xff\xff\xf0\x0f\xff\xff\xf8\x1f\xff\xff\xfc\x3f\xff\xff\xfe\x7f\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\x00\x00\x10\xbe\ +\x00\ +\x00\x01\x00\x01\x00\x20\x20\x00\x00\x00\x00\x00\x00\xa8\x10\x00\ +\x00\x16\x00\x00\x00\x28\x00\x00\x00\x20\x00\x00\x00\x40\x00\x00\ +\x00\x01\x00\x20\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x05\x2f\x05\x38\x04\x2c\x04\ +\x69\x00\x2a\x00\x65\x00\x2a\x00\x65\x00\x2b\x00\x65\x00\x2d\x00\ +\x65\x00\x2e\x00\x65\x00\x2f\x00\x65\x05\x35\x06\x69\x07\x34\x07\ +\x39\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x27\x43\x27\x88\x2b\x4f\x2b\ +\xff\x1b\x38\x1e\xff\x09\x32\x0d\xff\x08\x3c\x0d\xff\x08\x46\x0e\ +\xff\x08\x4d\x0e\xff\x14\x5d\x1d\xff\x2d\x67\x30\xff\x28\x5d\x2a\ +\x90\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x02\x33\x03\xc3\x4f\x7d\x4c\ +\xff\x47\xb4\x45\xff\x10\x88\x1c\xff\x13\x9c\x20\xff\x1f\xaa\x2c\ +\xff\x2a\xb6\x37\xff\x49\xd2\x58\xff\x48\xaa\x52\xff\x04\x42\x05\ +\xc8\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x30\x00\xc3\x38\x74\x35\ +\xff\x3f\xc9\x3d\xff\x00\x95\x0b\xff\x07\xab\x16\xff\x18\xba\x27\ +\xff\x26\xc9\x35\xff\x4e\xe9\x5f\xff\x32\xaa\x3f\xff\x00\x41\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x30\x00\xc3\x35\x74\x32\ +\xff\x40\xc9\x3e\xff\x00\x9a\x0f\xff\x0e\xae\x1d\xff\x1f\xbe\x2e\ +\xff\x2d\xcd\x3c\xff\x56\xec\x66\xff\x2f\xae\x3d\xff\x00\x42\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x31\x00\xc3\x36\x77\x33\ +\xff\x40\xcc\x3e\xff\x01\x9e\x10\xff\x11\xb1\x20\xff\x22\xc1\x31\ +\xff\x31\xd1\x40\xff\x5a\xef\x6a\xff\x32\xb5\x40\xff\x00\x44\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x33\x00\xc3\x36\x7d\x34\ +\xff\x40\xcc\x3e\xff\x02\x9f\x11\xff\x13\xb2\x22\xff\x24\xc3\x33\ +\xff\x33\xd3\x42\xff\x5c\xf0\x6c\xff\x32\xba\x41\xff\x00\x45\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x35\x00\xc3\x36\x85\x35\ +\xff\x40\xcb\x3e\xff\x02\x9f\x11\xff\x12\xb2\x21\xff\x23\xc2\x32\ +\xff\x32\xd2\x41\xff\x5b\xef\x6b\xff\x32\xbc\x41\xff\x00\x46\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x36\x00\xc3\x36\x8b\x35\ +\xff\x40\xc9\x3e\xff\x00\x9c\x10\xff\x0f\xaf\x1f\xff\x21\xc0\x30\ +\xff\x2f\xcf\x3e\xff\x58\xec\x68\xff\x31\xbc\x40\xff\x00\x46\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x38\x00\xc3\x35\x90\x35\ +\xff\x40\xc5\x3e\xff\x00\x97\x0e\xff\x0b\xab\x1a\xff\x1b\xba\x2a\ +\xff\x2a\xca\x39\xff\x52\xe7\x62\xff\x2e\xbc\x3d\xff\x00\x47\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x39\x00\xc0\x38\x97\x38\ +\xff\x40\xc0\x3d\xff\x00\x8e\x0c\xff\x05\xa5\x15\xff\x15\xb4\x24\ +\xff\x22\xc2\x31\xff\x4a\xe0\x5a\xff\x2d\xbb\x3c\xff\x00\x46\x01\ +\xc5\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x08\x33\x08\ +\x39\x11\x36\x12\x6b\x01\x25\x02\x69\x00\x24\x00\x69\x00\x26\x00\ +\x69\x00\x27\x00\x69\x00\x28\x00\x69\x00\x29\x00\x69\x00\x2a\x00\ +\x69\x00\x2b\x00\x69\x01\x2d\x02\x62\x08\x41\x0a\xda\x4e\xa7\x4e\ +\xff\x3e\xba\x3b\xff\x00\x82\x0c\xff\x01\x9c\x0f\xff\x0c\xac\x1b\ +\xff\x19\xb9\x28\xff\x3d\xd6\x4d\xff\x38\xbc\x47\xff\x06\x50\x0a\ +\xdd\x00\x3b\x00\x62\x00\x3f\x00\x69\x00\x3f\x00\x69\x00\x3e\x00\ +\x69\x00\x3d\x00\x69\x00\x3b\x00\x69\x00\x39\x00\x69\x00\x37\x00\ +\x69\x00\x35\x00\x69\x0d\x40\x0f\x6b\x07\x35\x07\x3a\x16\x37\x16\ +\x80\x5f\x74\x61\xff\x10\x4e\x14\xff\x06\x48\x0a\xff\x06\x4b\x0a\ +\xff\x06\x4d\x0a\xff\x06\x4f\x0a\xff\x06\x50\x0a\xff\x06\x51\x0b\ +\xff\x06\x53\x0a\xff\x0b\x5a\x0f\xff\x18\x65\x1d\xff\x66\xca\x66\ +\xff\x3d\xb5\x39\xff\x00\x75\x0b\xff\x00\x8d\x0d\xff\x02\xa1\x11\ +\xff\x0b\xad\x1a\xff\x32\xcd\x43\xff\x59\xc6\x63\xff\x20\x74\x25\ +\xff\x15\x72\x1b\xff\x13\x71\x18\xff\x13\x6f\x19\xff\x13\x6e\x19\ +\xff\x13\x6d\x19\xff\x13\x6a\x18\xff\x13\x66\x18\xff\x13\x62\x17\ +\xff\x1b\x63\x1f\xff\x62\x9a\x68\xff\x15\x48\x18\x87\x06\x28\x06\ +\x7b\x28\x59\x2d\xff\x0f\xb7\x1c\xff\x08\xb4\x15\xff\x08\xb2\x15\ +\xff\x08\xb4\x15\xff\x07\xba\x16\xff\x07\xc0\x17\xff\x07\xc4\x18\ +\xff\x07\xc7\x18\xff\x07\xc6\x17\xff\x05\xc1\x14\xff\x0a\xc0\x17\ +\xff\x1e\xa6\x23\xff\x00\x6d\x0a\xff\x00\x7b\x0b\xff\x09\x96\x16\ +\xff\x1d\xac\x2b\xff\x4c\xc9\x57\xff\x71\xd7\x74\xff\x79\xdd\x7c\ +\xff\x81\xe2\x84\xff\x86\xe5\x89\xff\x89\xe7\x8c\xff\x8b\xe7\x8e\ +\xff\x8c\xe6\x8f\xff\x8c\xe5\x8e\xff\x8a\xe2\x8d\xff\x88\xdf\x8a\ +\xff\x8a\xe0\x8d\xff\x3c\x82\x44\xff\x03\x36\x05\x82\x00\x26\x00\ +\x7b\x12\x42\x17\xff\x08\x75\x13\xff\x00\x6e\x09\xff\x00\x76\x0b\ +\xff\x00\x88\x0c\xff\x00\x98\x0e\xff\x02\xa2\x12\xff\x06\xa8\x16\ +\xff\x08\xab\x18\xff\x08\xaa\x17\xff\x04\xa7\x13\xff\x00\x9f\x0e\ +\xff\x02\x96\x10\xff\x11\x88\x1d\xff\x22\x85\x2c\xff\x30\x96\x3a\ +\xff\x36\xa8\x41\xff\x3d\xb6\x47\xff\x44\xbf\x4f\xff\x4d\xc5\x58\ +\xff\x54\xcb\x5f\xff\x5a\xce\x65\xff\x5f\xd0\x69\xff\x61\xd1\x6b\ +\xff\x63\xd1\x6c\xff\x63\xcf\x6d\xff\x61\xcc\x6b\xff\x5f\xc8\x68\ +\xff\x66\xc8\x6f\xff\x22\x69\x2a\xff\x00\x2e\x00\x82\x00\x28\x00\ +\x7b\x0e\x43\x14\xff\x09\x70\x13\xff\x00\x74\x0a\xff\x00\x8c\x0e\ +\xff\x03\xa0\x12\xff\x0d\xac\x1c\xff\x16\xb4\x25\xff\x1b\xba\x2a\ +\xff\x1d\xbc\x2c\xff\x1e\xbd\x2d\xff\x21\xbb\x30\xff\x29\xb9\x37\ +\xff\x30\xb7\x3c\xff\x32\xb0\x3e\xff\x33\x9f\x3e\xff\x36\x90\x3f\ +\xff\x39\x98\x42\xff\x3b\xa6\x46\xff\x3f\xb1\x4a\xff\x44\xba\x50\ +\xff\x4a\xbf\x56\xff\x50\xc3\x5b\xff\x55\xc5\x5f\xff\x58\xc6\x62\ +\xff\x59\xc6\x64\xff\x5a\xc4\x64\xff\x5b\xc1\x65\xff\x5c\xbc\x65\ +\xff\x68\xbb\x71\xff\x21\x5e\x27\xff\x00\x2b\x00\x82\x00\x2a\x00\ +\x7b\x0d\x46\x14\xff\x08\x78\x13\xff\x00\x8c\x0c\xff\x05\xa3\x14\ +\xff\x12\xb2\x21\xff\x1e\xbe\x2d\xff\x27\xc8\x36\xff\x32\xcf\x41\ +\xff\x3e\xd5\x4c\xff\x4b\xd7\x58\xff\x51\xd6\x5e\xff\x4f\xd1\x5b\ +\xff\x48\xc8\x54\xff\x3f\xbf\x4b\xff\x39\xb3\x44\xff\x3a\xa0\x45\ +\xff\x3e\x91\x46\xff\x42\x97\x4a\xff\x45\xa1\x4e\xff\x48\xab\x51\ +\xff\x4b\xb2\x55\xff\x4f\xb7\x59\xff\x52\xba\x5c\xff\x55\xbb\x5f\ +\xff\x58\xbb\x62\xff\x5c\xb8\x65\xff\x5f\xb4\x67\xff\x62\xaf\x6a\ +\xff\x6e\xb1\x75\xff\x23\x56\x28\xff\x00\x28\x00\x82\x00\x2b\x00\ +\x7b\x0f\x4c\x15\xff\x07\x88\x14\xff\x00\x9e\x0f\xff\x0f\xb0\x1e\ +\xff\x20\xc0\x2f\xff\x34\xd0\x42\xff\x4a\xdd\x58\xff\x5c\xe7\x6a\ +\xff\x66\xeb\x73\xff\x68\xeb\x74\xff\x65\xe7\x71\xff\x5f\xe0\x6b\ +\xff\x58\xd6\x64\xff\x50\xcc\x5c\xff\x46\xc1\x52\xff\x41\xb2\x4c\ +\xff\x43\x9d\x4c\xff\x46\x92\x4e\xff\x48\x95\x50\xff\x4c\x9b\x54\ +\xff\x4f\xa0\x57\xff\x53\xa5\x5a\xff\x56\xa8\x5e\xff\x59\xaa\x61\ +\xff\x5c\xaa\x64\xff\x60\xa9\x67\xff\x63\xa7\x6a\xff\x66\xa6\x6d\ +\xff\x72\xb0\x78\xff\x22\x4d\x27\xff\x00\x23\x00\x82\x00\x2c\x01\ +\x7b\x14\x57\x1c\xff\x1c\xa7\x28\xff\x21\xbb\x2e\xff\x3c\xcc\x48\ +\xff\x57\xdd\x63\xff\x6d\xec\x78\xff\x7d\xf7\x87\xff\x87\xfe\x91\ +\xff\x8e\xff\x98\xff\x8f\xff\x99\xff\x89\xfc\x93\xff\x7f\xf2\x8a\ +\xff\x73\xe5\x7c\xff\x5f\xd7\x6a\xff\x54\xca\x5f\xff\x4a\xbe\x55\ +\xff\x48\xab\x52\xff\x54\xa0\x5b\xff\x5b\xa4\x62\xff\x61\xa8\x67\ +\xff\x66\xab\x6c\xff\x69\xae\x70\xff\x6d\xb0\x73\xff\x70\xb2\x76\ +\xff\x73\xb4\x7a\xff\x76\xb7\x7c\xff\x7a\xb9\x7f\xff\x7c\xbb\x82\ +\xff\x86\xc3\x8c\xff\x34\x53\x38\xff\x02\x22\x02\x82\x0e\x3a\x10\ +\x81\x27\x5f\x2a\xff\x40\xab\x49\xff\x48\xb6\x51\xff\x57\xc2\x60\ +\xff\x62\xce\x6c\xff\x6c\xd9\x75\xff\x74\xe3\x7f\xff\x81\xe7\x8b\ +\xff\x8c\xea\x94\xff\x8c\xec\x95\xff\x89\xec\x92\xff\x96\xf0\x9c\ +\xff\x8b\xf1\x91\xff\x6a\xdf\x75\xff\x5f\xd2\x6a\xff\x54\xc6\x5f\ +\xff\x4c\xb3\x56\xff\x71\xbe\x74\xff\x93\xcf\x95\xff\x82\xc2\x87\ +\xff\x79\xbb\x7e\xff\x78\xb7\x7c\xff\x7a\xb6\x7e\xff\x7b\xb3\x7f\ +\xff\x7d\xb1\x81\xff\x7e\xaf\x81\xff\x80\xad\x82\xff\x81\xad\x84\ +\xff\x91\xba\x95\xff\x3c\x60\x3d\xff\x11\x30\x12\x88\x1a\x44\x1a\ +\x54\x41\x6b\x44\xbe\x07\x43\x0a\xdf\x00\x42\x03\xdf\x00\x45\x03\ +\xdf\x00\x48\x03\xdf\x00\x4b\x03\xdf\x00\x4e\x03\xdf\x00\x51\x03\ +\xdf\x00\x54\x03\xdf\x00\x56\x05\xde\x0d\x64\x13\xf0\x62\xc9\x6b\ +\xff\x98\xf9\x9e\xff\x73\xe5\x7e\xff\x68\xd7\x72\xff\x5c\xca\x67\ +\xff\x52\xba\x5d\xff\x77\xc7\x7a\xff\x72\xb0\x76\xff\x1c\x58\x1f\ +\xf1\x03\x42\x06\xde\x00\x3d\x01\xdf\x00\x3b\x01\xdf\x00\x38\x01\ +\xdf\x00\x34\x00\xdf\x00\x30\x00\xdf\x00\x2e\x00\xdf\x00\x2d\x00\ +\xdf\x06\x31\x07\xdf\x61\x6d\x62\xbe\x23\x44\x23\x5b\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x47\x01\xc1\x4e\xca\x59\ +\xff\xa0\xfc\xa6\xff\x79\xe7\x84\xff\x6e\xda\x78\xff\x62\xcd\x6c\ +\xff\x58\xbd\x62\xff\x80\xcd\x83\xff\x4c\x93\x52\xff\x01\x3c\x02\ +\xc5\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x46\x00\xc3\x47\xc4\x52\ +\xff\xa2\xfb\xa8\xff\x7c\xe7\x86\xff\x71\xda\x7b\xff\x67\xce\x70\ +\xff\x5d\xbe\x67\xff\x86\xcf\x88\xff\x43\x86\x47\xff\x00\x38\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x45\x00\xc3\x47\xbd\x51\ +\xff\xa1\xf8\xa7\xff\x7c\xe5\x86\xff\x73\xd8\x7c\xff\x69\xcd\x73\ +\xff\x62\xbd\x6b\xff\x8b\xcf\x8d\xff\x44\x86\x49\xff\x00\x37\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x43\x00\xc3\x46\xb5\x4f\ +\xff\x9e\xf3\xa3\xff\x7b\xdf\x84\xff\x72\xd5\x7c\xff\x6a\xca\x73\ +\xff\x67\xba\x6f\xff\x8f\xce\x91\xff\x46\x85\x4a\xff\x00\x37\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x41\x00\xc3\x44\xac\x4c\ +\xff\x9a\xed\x9e\xff\x78\xd9\x81\xff\x71\xd0\x7a\xff\x6c\xc6\x74\ +\xff\x6c\xb4\x74\xff\x94\xce\x95\xff\x47\x83\x4b\xff\x00\x36\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x3f\x00\xc3\x42\xa3\x4a\ +\xff\x95\xe6\x99\xff\x74\xd2\x7c\xff\x6f\xca\x78\xff\x70\xbd\x78\ +\xff\x72\xaf\x79\xff\x99\xd1\x9a\xff\x49\x80\x4c\xff\x00\x35\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x3e\x00\xc3\x41\x9a\x48\ +\xff\x90\xe0\x94\xff\x71\xcb\x79\xff\x72\xc0\x79\xff\x76\xb5\x7c\ +\xff\x78\xb0\x7e\xff\x9d\xd4\x9f\xff\x4a\x7d\x4d\xff\x00\x34\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x3c\x00\xc3\x3e\x91\x45\ +\xff\x8f\xd8\x92\xff\x74\xbe\x7b\xff\x78\xb6\x7e\xff\x7b\xb2\x81\ +\xff\x7d\xb3\x83\xff\xa1\xd6\xa2\xff\x49\x78\x4c\xff\x00\x33\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x3b\x00\xc3\x4c\x8f\x52\ +\xff\x9c\xd4\x9d\xff\x83\xbc\x89\xff\x87\xbc\x8d\xff\x8a\xbf\x8f\ +\xff\x8b\xc0\x91\xff\xae\xe2\xaf\xff\x5a\x81\x5d\xff\x00\x33\x00\ +\xc7\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x09\x42\x0a\xc6\x6b\x99\x71\ +\xff\x5c\x8f\x61\xff\x4a\x80\x4f\xff\x4a\x7e\x4f\xff\x4a\x7b\x4f\ +\xff\x4c\x79\x50\xff\x67\x8d\x6b\xff\x8d\x9f\x8f\xff\x10\x42\x11\ +\xcb\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x1a\x45\x1b\x58\x0f\x46\x11\ +\xd5\x00\x39\x02\xd5\x00\x36\x00\xd5\x00\x35\x00\xd5\x00\x33\x00\ +\xd5\x00\x32\x00\xd5\x02\x34\x03\xd5\x18\x46\x18\xd5\x2e\x4f\x2e\ +\x5e\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\xff\xff\xe0\x07\xff\xff\xe0\x07\xff\xff\xe0\x07\xff\xff\xe0\x07\ +\xff\xff\xe0\x07\xff\xff\xe0\x07\xff\xff\xe0\x07\xff\xff\xe0\x07\ +\xff\xff\xe0\x07\xff\xff\xe0\x07\xff\xff\xe0\x07\xff\x00\x00\x00\ +\x00\x80\x00\x00\x00\x80\x00\x00\x00\x80\x00\x00\x00\x80\x00\x00\ +\x00\x80\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\ +\x01\xff\xe0\x07\xff\xff\xe0\x07\xff\xff\xe0\x07\xff\xff\xe0\x07\ +\xff\xff\xe0\x07\xff\xff\xe0\x07\xff\xff\xe0\x07\xff\xff\xe0\x07\ +\xff\xff\xe0\x07\xff\xff\xe0\x07\xff\xff\xf0\x0f\xff\ +\x00\x00\x10\xbe\ +\x00\ +\x00\x01\x00\x01\x00\x20\x20\x00\x00\x00\x00\x00\x00\xa8\x10\x00\ +\x00\x16\x00\x00\x00\x28\x00\x00\x00\x20\x00\x00\x00\x40\x00\x00\ +\x00\x01\x00\x20\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\ +\x00\x00\x00\xcb\x00\x00\x00\xb7\x00\x00\x00\xa4\x00\x00\x00\x2c\ +\x00\x00\x00\xc4\x00\x00\x00\xb3\x17\x00\x0f\xb0\xa8\x00\x01\x3d\ +\x74\x00\x00\x00\x03\x05\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x7b\x00\x00\x00\x8d\x11\x00\x0f\xa7\xa2\x00\x04\x5a\ +\x82\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x6e\x00\x00\x00\x00\ +\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x07\x00\x00\x00\x8d\x00\x00\x00\x4f\ +\x00\x00\x00\xcb\x00\x00\x00\xb7\x00\x00\x00\xa3\x00\x00\x00\x2a\ +\x00\x00\x02\xcd\x37\x15\x4c\xd7\xda\x38\xbd\xfb\xff\x0e\x70\xe1\ +\xff\x00\x02\x30\x68\x05\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x7f\x01\x01\x1a\x92\xb0\x22\xa7\xeb\xff\x34\xa2\xf7\ +\xff\x09\x26\x95\xb3\x00\x00\x08\x10\x00\x00\x6b\x00\x00\x00\x00\ +\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x38\x00\x00\x00\x07\ +\x00\x00\x00\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\xaa\x00\x00\x00\xbb\x00\x00\x00\x5c\ +\x00\x00\x00\xcb\x00\x00\x00\xb6\x00\x00\x00\xa6\x20\x00\x00\x2e\ +\x29\x04\x07\xae\x66\x6e\xb0\xf7\xff\x87\xf2\xff\xff\x2e\xb5\xff\ +\xff\x00\x09\x51\x96\x02\x00\x00\x00\x03\x03\x03\x15\x00\x00\x00\ +\x1b\x00\x00\x51\x00\x03\x3e\xa9\xe0\x50\xdd\xff\xff\x92\xef\xff\ +\xff\x3a\x5f\xd5\xf7\x00\x00\x2f\x1f\x00\x00\x7a\x3d\x00\x00\x01\ +\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x42\x00\x00\x00\x11\ +\x00\x00\x00\x01\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\xc5\x00\x00\x00\xb4\x00\x00\x00\x5a\ +\x00\x00\x00\xce\x00\x00\x00\xbb\x50\x0b\x50\xd8\xe8\x08\x4d\xbb\ +\xf2\x00\x00\x3f\x54\x28\x2b\xad\xab\x98\xed\xff\xff\x25\xab\xff\ +\xff\x00\x05\x45\x8d\x18\x15\x0f\x4c\x47\x47\x47\xe1\x35\x35\x33\ +\xce\x0a\x07\x09\x4e\x00\x35\x91\xd8\x53\xdf\xff\xff\x85\xb6\xf2\ +\xff\x00\x00\x52\x61\x00\x10\x83\x8a\x11\x70\xdc\xff\x02\x2b\xa0\ +\xc3\x00\x00\x22\x2c\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x10\ +\x00\x00\x00\x01\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\xc0\x00\x00\x00\xb4\x00\x00\x00\x54\ +\x00\x00\x00\xc2\x2a\x39\x76\xed\xff\x72\xed\xff\xff\x36\xcc\xff\ +\xff\x00\x18\x81\xca\x3d\x41\x9b\xc0\x90\xf0\xff\xff\x1f\xab\xfd\ +\xff\x06\x19\x71\xe9\x51\x4e\x4e\xff\x62\x62\x61\xff\x54\x53\x51\ +\xff\x34\x2d\x43\xf4\x04\x43\x96\xf5\x44\xd6\xff\xff\x90\xcd\xfa\ +\xff\x08\x01\x6a\x99\x09\x61\xb8\xf8\x52\xe6\xff\xff\x70\xdd\xff\ +\xff\x16\x31\xb2\xd7\x00\x00\x00\x00\x00\x00\x39\x00\x00\x00\x0c\ +\x00\x00\x00\x01\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\xbf\x00\x00\x00\xb4\x3e\x00\x17\x9c\ +\xb4\x00\x00\x52\x5a\x28\x34\xb4\xb1\x97\xdc\xfc\xff\x4d\xd5\xff\ +\xff\x00\x1c\x97\xdb\x60\x72\xc3\xe7\x86\xf5\xff\xff\x96\xfa\xff\ +\xff\x52\x77\xc9\xff\x38\x36\x58\xff\x62\x62\x5d\xff\x46\x45\x41\ +\xff\x21\x27\x67\xff\x8c\xca\xe9\xff\x7f\xff\xff\xff\x93\xec\xff\ +\xff\x21\x1d\x90\xd0\x07\x65\xb7\xf4\x77\xf0\xff\xff\x76\xa1\xe3\ +\xf8\x0c\x0f\x6a\x71\x00\x0a\x79\x7e\x00\x17\x8f\xb2\x00\x00\x1b\ +\x1e\x00\x00\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x03\x0f\xca\x7e\x28\x79\xe5\xf9\x37\xc7\xff\ +\xff\x04\x47\xad\xea\x06\x00\x68\x95\x91\xcf\xfd\xff\x3a\xce\xff\ +\xff\x00\x16\x9b\xee\x2d\x63\xc7\xf8\x52\xe6\xfe\xff\x5c\x8e\xc8\ +\xff\x50\x4e\x7c\xff\x6d\x6c\x6f\xff\x73\x73\x73\xff\x5e\x5e\x5c\ +\xff\x3d\x3b\x49\xff\x42\x49\x85\xff\x64\xc8\xe8\xff\x49\xea\xff\ +\xff\x13\x1d\x99\xf5\x00\x58\xb2\xfb\x6c\xef\xff\xff\x66\x82\xcd\ +\xe9\x00\x00\x5e\x92\x15\x8a\xe0\xff\x41\xc7\xff\xff\x1a\x52\xca\ +\xe6\x00\x01\x30\x3f\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x17\x27\xd1\xc7\x85\xd0\xf8\xff\x74\xed\xff\ +\xff\x10\x7e\xdf\xfc\x22\x1f\x9d\xe3\x9b\xe5\xff\xff\x5b\xde\xff\ +\xff\x3e\x7a\xd1\xff\x12\x17\x75\xff\x38\x50\xa4\xff\x68\x65\x88\ +\xff\x86\x85\x7f\xff\x79\x79\x78\xff\x56\x56\x56\xff\x65\x65\x65\ +\xff\x73\x73\x6f\xff\x4e\x4a\x4c\xff\x2a\x32\x69\xff\x1f\x34\x88\ +\xff\x18\x24\x78\xff\x5a\xb9\xe0\xff\x6b\xec\xff\xff\x88\xae\xea\ +\xf9\x00\x0c\x8b\xe6\x34\xbe\xf2\xff\x8c\xf1\xff\xff\x5d\x91\xee\ +\xfd\x01\x02\x4b\x62\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\xb4\x14\x52\x64\xd3\xdf\x85\xf6\xff\ +\xff\x05\x6d\xd7\xfe\x2f\x3e\xb9\xfc\x77\xfc\xff\xff\x87\xed\xfc\ +\xff\x70\x8a\xc3\xff\x6d\x6c\x8f\xff\xa7\xa2\x9b\xff\x8b\x8b\x87\ +\xff\x6d\x6e\x6e\xff\x3d\x3d\x3d\xff\x18\x18\x18\xff\x20\x20\x20\ +\xff\x53\x53\x53\xff\x79\x79\x78\xff\x68\x65\x61\xff\x4d\x47\x46\ +\xff\x39\x3d\x71\xff\x91\xc3\xe3\xff\x7a\xff\xff\xff\x6e\xdd\xfd\ +\xff\x06\x14\x9c\xfd\x29\xb3\xeb\xff\x95\xe3\xff\xff\x18\x18\x77\ +\xa1\x00\x00\x03\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\xb2\x1b\x6a\x86\xdd\xec\x75\xef\xff\ +\xff\x12\x81\xe2\xff\x04\x15\x96\xff\x1f\x7c\xce\xff\x4d\x70\xb9\ +\xff\x96\x91\xad\xff\xa9\xa8\xa4\xff\x7f\x7f\x7f\xff\x59\x59\x59\ +\xff\x2b\x2b\x2b\xff\x1e\x1e\x1e\xff\x25\x25\x25\xff\x22\x22\x22\ +\xff\x1e\x1e\x1e\xff\x3b\x3b\x3b\xff\x6f\x6f\x6f\xff\x78\x78\x76\ +\xff\x59\x57\x56\xff\x32\x30\x60\xff\x35\x7d\xbf\xff\x13\x5f\xbb\ +\xff\x05\x1d\x8b\xff\x2b\xba\xf1\xff\x91\xec\xff\xff\x32\x3a\x8f\ +\xb6\x00\x00\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\xbc\x42\x82\xb3\xec\xfe\x79\xf4\xff\ +\xff\x9b\xec\xff\xff\x38\x4a\x9d\xff\x94\x89\xb7\xff\xc2\xbe\xc7\ +\xff\x9d\x9e\x98\xff\x6f\x6f\x6f\xff\x42\x42\x42\xff\x27\x27\x27\ +\xff\x28\x28\x28\xff\x2c\x2c\x2c\xff\x2c\x2c\x2c\xff\x2c\x2c\x2c\ +\xff\x2c\x2c\x2c\xff\x25\x25\x25\xff\x2b\x2b\x2b\xff\x58\x58\x59\ +\xff\x7d\x7d\x7c\xff\x6f\x6e\x67\xff\x4b\x43\x58\xff\x26\x1c\x58\ +\xff\x68\x8f\xc0\xff\x99\xff\xff\xff\x86\xfc\xff\xff\x52\x68\xbc\ +\xde\x00\x00\x02\x0a\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x10\x0a\x8a\x8c\x30\x92\xe2\xff\x4e\xcd\xee\ +\xff\x66\x7e\xbf\xff\xb4\xb0\xcd\xff\xc7\xc7\xc0\xff\x84\x84\x82\ +\xff\x5a\x5a\x5a\xff\x31\x31\x31\xff\x35\x35\x35\xff\x4a\x4a\x4a\ +\xff\x42\x42\x42\xff\x36\x36\x36\xff\x33\x33\x33\xff\x34\x34\x34\ +\xff\x33\x33\x33\xff\x32\x32\x32\xff\x2d\x2d\x2d\xff\x27\x27\x27\ +\xff\x40\x40\x40\xff\x71\x71\x71\xff\x7c\x7c\x77\xff\x60\x5f\x5e\ +\xff\x40\x3e\x6d\xff\x57\x92\xc7\xff\x4c\xe7\xfc\xff\x23\x5b\xba\ +\xfb\x06\x05\x0f\x5e\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x5d\x5b\x55\xee\x95\x9e\xdc\xff\x6e\x7d\xcc\ +\xff\xc7\xc3\xd1\xff\xa7\xa6\xa0\xff\x6c\x6c\x6c\xff\x45\x45\x45\ +\xff\x2b\x2b\x2b\xff\x49\x49\x49\xff\x51\x51\x51\xff\x51\x51\x51\ +\xff\x56\x56\x56\xff\x52\x52\x52\xff\x47\x47\x47\xff\x3b\x3b\x3b\ +\xff\x3b\x3b\x3b\xff\x39\x39\x39\xff\x37\x37\x37\xff\x34\x34\x34\ +\xff\x2c\x2c\x2c\xff\x2f\x2f\x2f\xff\x59\x59\x59\xff\x7d\x7d\x7c\ +\xff\x71\x70\x6a\xff\x45\x43\x63\xff\x2c\x3e\x8e\xff\x48\x48\x66\ +\xff\x17\x17\x14\xa5\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x62\x62\x61\xf0\xff\xff\xff\xff\xc4\xc1\xbd\ +\xff\x7f\x80\x7d\xff\x57\x57\x57\xff\x33\x33\x33\xff\x2c\x2c\x2c\ +\xff\x36\x36\x36\xff\x4b\x4b\x4b\xff\x53\x53\x53\xff\x57\x57\x57\ +\xff\x52\x52\x52\xff\x59\x59\x59\xff\x58\x58\x58\xff\x56\x56\x56\ +\xff\x45\x45\x45\xff\x40\x40\x40\xff\x3e\x3e\x3e\xff\x3b\x3b\x3b\ +\xff\x38\x38\x38\xff\x31\x31\x31\xff\x29\x29\x29\xff\x3f\x3f\x3f\ +\xff\x6e\x6e\x6e\xff\x78\x78\x74\xff\x6c\x68\x63\xff\x5f\x5f\x5a\ +\xff\x12\x12\x13\xa6\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x44\x44\x44\xdf\x8e\x8e\x8e\xff\x77\x77\x77\ +\xff\x4c\x4c\x4c\xff\x24\x24\x24\xff\x2f\x2f\x2f\xff\x38\x38\x38\ +\xff\x3c\x3c\x3c\xff\x3e\x3e\x3e\xff\x47\x47\x47\xff\x51\x51\x51\ +\xff\x5b\x5b\x5b\xff\x59\x59\x59\xff\x58\x58\x58\xff\x5c\x5c\x5c\ +\xff\x58\x58\x58\xff\x54\x54\x54\xff\x47\x47\x47\xff\x41\x41\x41\ +\xff\x3e\x3e\x3e\xff\x39\x39\x39\xff\x34\x34\x34\xff\x28\x28\x28\ +\xff\x28\x28\x28\xff\x67\x67\x67\xff\x81\x81\x80\xff\x57\x57\x57\ +\xff\x14\x14\x14\x83\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x16\x16\x16\x23\x25\x25\x25\xa5\x65\x65\x65\ +\xff\x7c\x7c\x7c\xff\x53\x53\x53\xff\x35\x35\x35\xff\x3b\x3b\x3b\ +\xff\x43\x43\x43\xff\x47\x47\x47\xff\x4b\x4b\x4b\xff\x4f\x4f\x4f\ +\xff\x54\x54\x54\xff\x59\x59\x59\xff\x5e\x5e\x5e\xff\x5b\x5b\x5b\ +\xff\x5c\x5c\x5c\xff\x5d\x5d\x5d\xff\x59\x59\x59\xff\x52\x52\x52\ +\xff\x44\x44\x44\xff\x3f\x3f\x3f\xff\x35\x35\x35\xff\x39\x39\x39\ +\xff\x64\x64\x64\xff\x7b\x7b\x7b\xff\x4e\x4e\x4e\xf2\x1f\x1f\x1f\ +\x70\x1b\x1b\x1b\x06\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x1a\x1a\x1a\x00\x15\x15\x15\x00\x17\x17\x17\ +\x5b\x48\x48\x48\xe5\x86\x86\x86\xff\x78\x78\x78\xff\x4c\x4c\x4c\ +\xff\x44\x44\x44\xff\x4d\x4d\x4d\xff\x52\x52\x52\xff\x56\x56\x56\ +\xff\x59\x59\x59\xff\x5b\x5b\x5b\xff\x5e\x5e\x5e\xff\x5f\x5f\x5f\ +\xff\x5e\x5e\x5e\xff\x59\x59\x59\xff\x5a\x5a\x5a\xff\x5d\x5d\x5d\ +\xff\x53\x53\x53\xff\x43\x43\x43\xff\x57\x57\x57\xff\x85\x85\x85\ +\xff\x74\x74\x74\xff\x30\x30\x30\xbb\x13\x13\x13\x2b\x16\x16\x16\ +\x00\x1b\x1b\x1b\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x1a\x1a\x1a\x00\x18\x18\x18\x00\x16\x16\x16\ +\x00\x0e\x0e\x0e\x1c\x26\x26\x26\xa7\x70\x70\x70\xff\x92\x92\x92\ +\xff\x6c\x6c\x6c\xff\x52\x52\x52\xff\x57\x57\x57\xff\x5d\x5d\x5d\ +\xff\x61\x61\x61\xff\x63\x63\x63\xff\x63\x63\x63\xff\x63\x63\x63\ +\xff\x61\x61\x61\xff\x60\x60\x60\xff\x5f\x5f\x5f\xff\x59\x59\x59\ +\xff\x57\x57\x57\xff\x7b\x7b\x7b\xff\x8f\x8f\x8f\xff\x53\x53\x53\ +\xf1\x16\x16\x16\x70\x10\x10\x10\x01\x17\x17\x17\x00\x18\x18\x18\ +\x00\x1b\x1b\x1b\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x1c\x1c\x1c\x00\x18\x18\x18\x00\x17\x17\x17\ +\x00\x13\x13\x13\x00\x0c\x0c\x0c\x00\x0f\x0f\x0f\x5a\x49\x49\x49\ +\xe4\x93\x93\x93\xff\x8d\x8d\x8d\xff\x67\x67\x67\xff\x60\x60\x60\ +\xff\x67\x67\x67\xff\x6a\x6a\x6a\xff\x6b\x6b\x6b\xff\x6b\x6b\x6b\ +\xff\x69\x69\x69\xff\x64\x64\x64\xff\x5e\x5e\x5e\xff\x70\x70\x70\ +\xff\x97\x97\x97\xff\x7e\x7e\x7e\xff\x2d\x2d\x2d\xba\x0a\x0a\x0a\ +\x2a\x0f\x0f\x0f\x00\x14\x14\x14\x00\x17\x17\x17\x00\x19\x19\x19\ +\x00\x1b\x1b\x1b\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x0b\x0b\x0b\x00\x17\x17\x17\x00\x18\x18\x18\ +\x00\x13\x13\x13\x00\x10\x10\x10\x00\x0d\x0d\x0d\x00\x05\x05\x05\ +\x1b\x21\x21\x21\xa5\x77\x77\x77\xff\xa3\xa3\xa3\xff\x84\x84\x84\ +\xff\x6d\x6d\x6d\xff\x70\x70\x70\xff\x73\x73\x73\xff\x73\x73\x73\ +\xff\x6d\x6d\x6d\xff\x70\x70\x70\xff\x92\x92\x92\xff\x9e\x9e\x9e\ +\xff\x56\x56\x56\xf0\x0f\x0f\x0f\x6e\x07\x07\x07\x00\x0f\x0f\x0f\ +\x00\x11\x11\x11\x00\x14\x14\x14\x00\x18\x18\x18\x00\x13\x13\x13\ +\x00\x06\x06\x06\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x00\x03\x03\x03\x00\x12\x12\x12\ +\x00\x14\x14\x14\x00\x10\x10\x10\x00\x0e\x0e\x0e\x00\x0b\x0b\x0b\ +\x00\x03\x03\x03\x00\x06\x06\x06\x59\x49\x49\x49\xe3\xa0\xa0\xa0\ +\xff\xa2\xa2\xa2\xff\x80\x80\x80\xff\x78\x78\x78\xff\x78\x78\x78\ +\xff\x89\x89\x89\xff\xaa\xaa\xaa\xff\x87\x87\x87\xff\x29\x29\x29\ +\xb8\x01\x01\x01\x29\x07\x07\x07\x00\x0c\x0c\x0c\x00\x0f\x0f\x0f\ +\x00\x12\x12\x12\x00\x14\x14\x14\x00\x0d\x0d\x0d\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x08\x08\x08\x00\x0f\x0f\x0f\x00\x0f\x0f\x0f\x00\x0b\x0b\x0b\ +\x00\x08\x08\x08\x00\x06\x06\x06\x00\x00\x00\x00\x1a\x1c\x1c\x1c\ +\xa4\x7e\x7e\x7e\xff\xb4\xb4\xb4\xff\x99\x99\x99\xff\xa4\xa4\xa4\ +\xff\xac\xac\xac\xff\x58\x58\x58\xf0\x07\x07\x07\x6d\x00\x00\x00\ +\x00\x08\x08\x08\x00\x09\x09\x09\x00\x0c\x0c\x0c\x00\x10\x10\x10\ +\x00\x0d\x0d\x0d\x00\x04\x04\x04\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x02\x02\x00\x0b\x0b\x0b\x00\x0b\x0b\x0b\ +\x00\x08\x08\x08\x00\x07\x07\x07\x00\x04\x04\x04\x00\x00\x00\x00\ +\x00\x00\x00\x00\x55\x48\x48\x48\xdd\xa8\xa8\xa8\xff\x8d\x8d\x8d\ +\xff\x25\x25\x25\xb2\x00\x00\x00\x27\x00\x00\x00\x00\x05\x05\x05\ +\x00\x08\x08\x08\x00\x09\x09\x09\x00\x0c\x0c\x0c\x00\x08\x08\x08\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x05\x05\ +\x00\x08\x08\x08\x00\x07\x07\x07\x00\x04\x04\x04\x00\x01\x01\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\x1d\x17\x17\x17\xb4\x08\x08\x08\ +\x80\x00\x00\x00\x00\x01\x01\x01\x00\x02\x02\x02\x00\x05\x05\x05\ +\x00\x08\x08\x08\x00\x08\x08\x08\x00\x03\x03\x03\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xef\xe7\ +\xff\xff\xc7\xc3\xff\xff\xc3\xc3\xff\xfe\x42\x44\x7f\xfc\x00\x00\ +\x3f\xf4\x00\x00\x6f\xe0\x00\x00\x07\xc0\x00\x00\x07\xe0\x00\x00\ +\x07\xe0\x00\x00\x07\xe0\x00\x00\x07\xc0\x00\x00\x07\xc0\x00\x00\ +\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xe0\x00\x00\x0f\xf8\x00\x00\ +\x1f\xfc\x00\x00\x7f\xff\x00\x00\xff\xff\x80\x03\xff\xff\xe0\x07\ +\xff\xff\xf0\x1f\xff\xff\xfc\x3f\xff\xff\xfe\x7f\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\x00\x00\x03\x01\ +\x00\ +\x00\x10\xbe\x78\x9c\xc5\x97\x3d\x4c\x13\x61\x18\xc7\x8f\x38\x98\ +\x30\x21\x26\x24\xa2\x31\x6c\x9a\x18\x13\x41\xa3\x03\x26\x16\x26\ +\xe5\xc3\xd1\xd5\x09\x23\x8e\xc4\x81\x01\x4d\xc1\xa0\x52\x13\x1c\ +\x58\x18\x18\xba\x10\x4d\xa3\x1d\x6c\x0b\x81\x01\x63\x95\x96\x36\ +\x48\x88\x21\x86\x18\x04\x41\x8a\xe5\xab\x3d\x28\x3d\x0b\x01\x1e\ +\x9f\xff\xb5\xef\xa5\x87\xa4\xf4\x0a\x57\x1e\xf2\xcb\xf5\x3d\x9e\ +\xff\xfb\xbb\xbb\xf6\xbd\x6b\x25\xa9\x80\xff\xca\xca\x24\xb5\xde\ +\x16\x49\x52\x09\x6f\x2f\x32\xd8\x65\x61\x0a\xa4\xd4\x3f\x8b\xa4\ +\xa3\xaa\x42\xe6\x12\x53\xc9\xdc\x34\x40\x65\x2a\x57\x78\x08\xf7\ +\x09\xe6\x72\x38\x1c\x5e\x97\x65\x99\x8c\x82\x1c\xf2\xa9\x79\x50\ +\xc5\x4c\x05\x73\x35\x4b\x6e\x30\x0f\x62\xb1\x18\xed\xc7\x4f\xef\ +\x1b\xfa\xf2\xfc\x0e\x73\x3b\xc9\x8b\x1a\x9a\xf1\xbd\xd7\xf5\x20\ +\xcf\x5c\x67\x4e\x33\xe5\x94\x65\xed\xee\xee\x52\x22\x91\xa0\xc1\ +\xc1\x41\x52\x14\x65\x5f\x3e\x59\x2d\xb4\xd2\x74\x96\xe4\xc7\xe7\ +\x34\x86\xf9\x18\xd2\x7b\x90\xc7\x3c\xe2\xbc\x8d\xf8\x91\xef\xeb\ +\xeb\xa3\xad\xad\x2d\x15\xab\xd5\x4a\xcd\xcd\xcd\x1a\xf6\xfb\xe7\ +\x69\xa8\xe1\x14\xf9\x1b\x4b\x68\xee\xe9\x15\x5a\xb3\x55\x91\xef\ +\x65\x8d\xd6\x0f\x90\xc7\x3c\xec\xbe\x66\xd4\x1f\x8f\xc7\xc9\xe3\ +\xf1\xa8\xaf\x41\x4b\x4b\x8b\xf6\x1a\x78\x5b\x6f\xd1\x52\x78\x81\ +\xa6\xfd\x1f\xc8\xd7\x56\x45\x43\x8d\x67\xc8\x6f\xab\xd3\xf5\x20\ +\x8f\x79\x72\xf5\xbb\xdd\x6e\x6d\x1f\xce\x3f\xbd\x3e\xb7\x55\x53\ +\x7c\x2d\x42\x09\x65\x83\x94\x78\x8c\xd6\xe5\x28\x05\x5e\xdf\xd3\ +\xf5\x20\x7f\x54\xfe\xf6\xf6\x76\x5d\x4f\x28\xf0\x8e\xaf\x77\x2d\ +\x0d\xf3\x35\x07\xbe\x8e\x5a\xfa\xf3\xd5\x65\x9a\xdf\x66\xb3\x65\ +\x1b\x37\xc5\xdf\xd9\xd9\x79\xac\xfe\xae\xae\x2e\x5d\x4f\x28\xe8\ +\xa4\x11\x5b\x3d\x53\xa7\x12\x78\x75\x97\xc2\x63\x1e\xd3\xfc\xdd\ +\xdd\xdd\xba\x1e\x7c\xfe\x96\x9b\x4a\x75\xeb\x1f\xc7\x61\x96\xbf\ +\xa7\xa7\x47\x87\xe3\xe1\x05\xfa\xd8\x50\x9c\x5c\xff\x4f\xb0\xfe\ +\x2d\x34\xd2\x61\x9e\xdf\x6e\xb7\xeb\x7a\xbc\xad\x96\xe4\xfa\x1f\ +\x71\x91\xff\x59\x35\x0d\x3d\x2a\xe5\xf5\x5f\x6f\x9a\xbf\xb7\xb7\ +\x57\xd7\xa3\xae\x7f\x79\x95\xfe\xf2\xda\x57\x36\xd6\x79\xfd\x47\ +\x4c\x5d\xff\x0e\x87\x43\xd7\x13\x0a\x38\xff\x5f\xff\x63\x6e\x5d\ +\xcf\x61\xfd\xb8\x7f\x8a\x72\x3a\x9d\xd9\xc6\xb5\x3a\xcc\xfd\x17\ +\xcf\x8d\xfe\xfe\x7e\x6d\x9f\xcb\xe5\xca\x90\xd8\xbf\x90\xcf\xf5\ +\xf9\xb3\xb9\xb9\x49\xc1\x60\x50\xcd\xa3\xbc\x5e\xaf\x21\x37\x72\ +\xc8\x63\x1e\xa3\x7e\xd4\xf6\xf6\x36\x45\xa3\x51\x75\x0e\x9c\x07\ +\xae\x25\xde\xcf\x83\x40\x1f\xfa\x91\x43\x1e\xf3\x88\xef\x34\x46\ +\xfc\xb8\x06\xc8\xe2\xf8\xc5\xf7\x09\xbc\x97\x07\x21\x7a\x91\x43\ +\x1e\xf3\xe4\xe2\x4f\x3f\x8e\x5c\x11\x25\xfc\xbf\x66\x66\x68\x6c\ +\x74\x34\xaf\xc0\x29\xfc\xbf\x67\x67\xe9\xdb\xf8\x78\x5e\x81\x53\ +\xf8\x43\xf3\xf3\xf4\x7d\x62\x22\xaf\xc0\x29\xfc\xe1\x85\x05\xfa\ +\x31\x39\x49\xc1\x40\x80\x06\x06\x06\xd4\x2d\xc6\x46\xd9\x9b\xcf\ +\x34\x86\x53\xf8\x97\x16\x17\x69\x7a\x6a\x8a\xa6\xd2\x98\xce\x81\ +\xbd\xf9\x4c\x63\x38\x85\x7f\x65\x79\x99\x66\xf9\xf3\x10\x59\x5d\ +\x55\xd7\x07\xb6\x18\x1b\x65\x6f\x3e\xd3\x18\x4e\xe1\x97\x23\x11\ +\x9a\x9f\x9b\xcb\x2b\x70\xa6\xfc\xe5\xfc\x5b\x44\xd9\xd9\xd9\xa1\ +\x7c\x02\x27\xdc\x39\xfc\xfe\x3b\x2a\x2a\x52\xee\x63\xad\xe4\x43\ +\x45\x3a\x09\xac\x92\x54\x90\x2f\xc4\x3d\xf8\x1f\x81\x4d\x4a\x92\ +\ +\x00\x00\x10\xbe\ +\x00\ +\x00\x01\x00\x01\x00\x20\x20\x00\x00\x00\x00\x00\x00\xa8\x10\x00\ +\x00\x16\x00\x00\x00\x28\x00\x00\x00\x20\x00\x00\x00\x40\x00\x00\ +\x00\x01\x00\x20\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xd7\xd7\xd7\x00\xd5\xd5\xd5\x00\xd5\xd5\xd5\x00\xd5\xd5\xd5\ +\x00\xd5\xd5\xd5\x00\xd5\xd5\xd5\x00\xd5\xd5\xd5\x00\xd5\xd5\xd5\ +\x00\xd6\xd6\xd6\x00\xdf\xdf\xdf\x00\xfe\xfe\xfe\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xd7\xd7\xd7\x00\xd5\xd5\xd5\x00\xd5\xd5\xd5\x00\xd5\xd5\xd5\ +\x00\xd5\xd5\xd5\x00\xd5\xd5\xd5\x00\xd5\xd5\xd5\x00\xd5\xd5\xd5\ +\x00\xd5\xd5\xd5\x00\xe9\xe9\xe9\x00\x24\x25\x26\x02\x83\x5d\x33\ +\x5c\x63\x41\x22\x32\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x25\x25\x28\ +\x08\x23\x23\x26\x1d\x17\x17\x1a\x0d\x3b\x3b\x3e\x3a\xc4\xc4\xc4\ +\x9f\xd6\xd6\xd6\x6c\xb8\xb8\xb8\x31\xb8\xb8\xb8\x07\xd5\xd5\xd5\ +\x00\xd5\xd5\xd5\x00\xd5\xd5\xd5\x00\xd5\xd5\xd5\x00\xd5\xd5\xd5\ +\x00\xd7\xd7\xd7\x00\x1f\x20\x24\x03\x27\x26\x25\x0e\xa3\x62\x2f\ +\x94\xc7\x6e\x18\x94\x78\x4a\x1e\x36\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x25\x25\x28\x10\x23\x23\x26\ +\x3b\x2b\x2b\x2e\x61\x9d\x9d\x9e\xb8\xd7\xd7\xd8\xe7\xf9\xf9\xf9\ +\xff\xf0\xf0\xf0\xff\xe1\xe1\xe1\xff\xd3\xd3\xd3\xda\xc9\xc9\xc9\ +\xa5\xbc\xbc\xbc\x65\xb8\xb8\xb8\x2c\xce\xce\xce\x06\xd5\xd5\xd5\ +\x00\xe9\xe9\xe9\x00\x1b\x1c\x20\x07\x23\x22\x21\x12\x9d\x5f\x2e\ +\x8c\xcd\x87\x41\xa0\xc6\x6f\x19\x91\x7f\x4f\x20\x3f\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x25\x25\x28\x02\x25\x25\x28\x1b\x25\x25\x28\x45\x1f\x1f\x23\ +\x50\x40\x40\x42\x7c\xf2\xf2\xf2\xff\xf9\xf9\xf9\xff\xf0\xf0\xf0\ +\xff\xf0\xf0\xf0\xff\xf0\xf0\xf0\xff\xf0\xf0\xf0\xff\xf0\xf0\xf0\ +\xff\xf0\xf0\xf0\xff\xea\xea\xea\xfc\xe2\xe2\xe2\xd7\xbf\xbf\xbf\ +\x9e\x7b\x7b\x7c\x65\x4c\x4d\x50\x37\x2f\x2e\x2c\x15\x9d\x5d\x2c\ +\x86\xcf\x8a\x44\x98\xc0\x6e\x1c\x8b\xc5\x72\x1d\x92\xb6\x6b\x20\ +\x48\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x25\x25\x28\x0d\x25\x25\x28\x3d\x25\x25\x28\x4f\x1f\x1f\x22\ +\x47\x40\x40\x42\x75\xea\xea\xe9\xff\xf4\xf4\xf4\xff\xf1\xf1\xf1\ +\xff\xf1\xf1\xf1\xff\xf1\xf1\xf1\xff\xf0\xf0\xf0\xff\xf0\xf0\xf0\ +\xff\xf0\xf0\xf0\xff\xf0\xf0\xf0\xff\xef\xef\xef\xff\xf2\xf2\xf2\ +\xff\xf0\xf0\xf0\xff\xe1\xe2\xe2\xf9\xc9\xc9\xc9\xd5\xbf\x91\x6c\ +\xd2\xd3\x99\x5e\xbd\xc6\x7d\x31\x9b\xc4\x75\x24\x8d\xc5\x73\x1d\ +\x8e\xc3\x71\x1e\x4c\xc4\x75\x22\x01\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x25\x25\x28\x08\x25\x25\x28\x2e\x25\x25\x28\x44\x1f\x1f\x22\ +\x3e\x41\x41\x43\x6e\xea\xea\xea\xff\xf4\xf4\xf4\xff\xf1\xf1\xf1\ +\xff\xf1\xf1\xf1\xff\xf1\xf1\xf1\xff\xf1\xf1\xf1\xff\xf1\xf1\xf1\ +\xff\xf1\xf1\xf1\xff\xf1\xf1\xf1\xff\xf0\xf0\xf0\xff\xef\xef\xef\ +\xff\xe4\xe4\xe4\xff\xe0\xe0\xe0\xff\xe3\xe3\xe3\xff\xcf\xae\x92\ +\xff\xdf\xb8\x91\xff\xd8\xad\x80\xfb\xd6\xa5\x72\xe8\xd1\x98\x5d\ +\xce\xcc\x8a\x45\xba\xc9\x80\x33\x51\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x25\x25\x28\ +\x06\x25\x25\x28\x22\x25\x25\x28\x35\x25\x25\x28\x39\x1f\x1f\x22\ +\x34\x42\x42\x44\x67\xeb\xeb\xeb\xff\xf4\xf4\xf4\xff\xf1\xf1\xf1\ +\xff\xf1\xf1\xf1\xff\xf1\xf1\xf1\xff\xf1\xf1\xf1\xff\xf1\xf1\xf1\ +\xff\xf1\xf1\xf1\xff\xf1\xf1\xf1\xff\xf1\xf1\xf1\xff\xf0\xf0\xf0\ +\xff\xe4\xe4\xe4\xff\xdf\xdf\xdf\xff\xde\xde\xde\xff\xcf\xac\x8f\ +\xff\xe0\xb8\x8f\xff\xdb\xb1\x85\xff\xdb\xb3\x87\xff\xdc\xb4\x89\ +\xff\xdc\xb3\x88\xff\xb9\x94\x6d\xa9\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x25\x25\x28\x07\x25\x25\x28\ +\x1b\x25\x25\x28\x29\x25\x25\x28\x2b\x25\x25\x28\x2d\x1f\x1f\x22\ +\x29\x43\x43\x45\x5e\xec\xec\xec\xff\xf5\xf5\xf5\xff\xf2\xf2\xf2\ +\xff\xf2\xf2\xf2\xff\xf2\xf2\xf2\xff\xf2\xf2\xf2\xff\xf1\xf1\xf1\ +\xff\xf1\xf1\xf1\xff\xf1\xf1\xf1\xff\xf1\xf1\xf1\xff\xf0\xf0\xf0\ +\xff\xe5\xe5\xe5\xff\xe0\xe0\xe0\xff\xde\xde\xde\xff\xcf\xac\x90\ +\xff\xe1\xbb\x91\xff\xdd\xb4\x86\xff\xdd\xb4\x86\xff\xdd\xb4\x86\ +\xff\xdc\xb3\x86\xff\xba\x98\x72\xa9\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x25\x25\x28\x10\x25\x25\x28\ +\x19\x25\x25\x28\x1c\x25\x25\x28\x1f\x25\x25\x28\x22\x1f\x1f\x22\ +\x1d\x43\x43\x46\x55\xed\xed\xed\xff\xf6\xf6\xf6\xff\xf2\xf2\xf2\ +\xff\xf2\xf2\xf2\xff\xf2\xf2\xf2\xff\xf2\xf2\xf2\xff\xf2\xf2\xf2\ +\xff\xf2\xf2\xf2\xff\xf2\xf2\xf2\xff\xf1\xf1\xf1\xff\xf0\xf0\xf0\ +\xff\xe5\xe5\xe5\xff\xe0\xe0\xe0\xff\xde\xdf\xdf\xff\xd1\xad\x91\ +\xff\xe3\xbd\x93\xff\xdf\xb6\x89\xff\xdf\xb6\x89\xff\xdf\xb6\x88\ +\xff\xdf\xb5\x88\xff\xbc\x99\x72\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x25\x25\x28\x05\x25\x25\x28\ +\x08\x25\x25\x28\x0c\x25\x25\x28\x11\x25\x25\x28\x15\x1f\x1f\x22\ +\x10\x44\x44\x46\x4c\xf2\xf2\xf2\xff\xfc\xfc\xfc\xff\xf8\xf8\xf8\ +\xff\xf7\xf7\xf7\xff\xf5\xf5\xf5\xff\xf4\xf4\xf4\xff\xf2\xf2\xf2\ +\xff\xf2\xf2\xf2\xff\xf2\xf2\xf2\xff\xf2\xf2\xf2\xff\xf1\xf1\xf1\ +\xff\xe5\xe5\xe5\xff\xe0\xe0\xe0\xff\xde\xdf\xdf\xff\xd2\xaf\x92\ +\xff\xe5\xc0\x95\xff\xe1\xb9\x8c\xff\xe1\xb9\x8b\xff\xe1\xb9\x8b\ +\xff\xe1\xb8\x8b\xff\xbf\x9c\x75\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x25\x25\x28\x01\x25\x25\x28\x02\x25\x25\x28\x06\x1e\x1e\x22\ +\x02\x44\x44\x47\x41\xf3\xf3\xf3\xff\xfc\xfc\xfc\xff\xf9\xf9\xf9\ +\xff\xfa\xfa\xfa\xff\xfa\xfa\xfa\xff\xfa\xfa\xfa\xff\xf8\xf8\xf8\ +\xff\xf5\xf5\xf5\xff\xf3\xf3\xf3\xff\xf1\xf1\xf1\xff\xeb\xeb\xeb\ +\xff\xe3\xe3\xe3\xff\xe1\xe1\xe1\xff\xde\xdf\xe0\xff\xd3\xb2\x97\ +\xff\xe8\xc4\x9a\xff\xe3\xbb\x8d\xff\xe3\xbb\x8e\xff\xe3\xbb\x8e\ +\xff\xe2\xbb\x8d\xff\xbf\x9e\x77\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x49\x49\x4b\x37\xf5\xf5\xf5\xff\xff\xff\xff\xff\xfa\xfa\xfa\ +\xff\xfa\xfa\xfa\xff\xfa\xfa\xfa\xff\xfa\xfa\xfa\xff\xfa\xfa\xfa\ +\xff\xfa\xfa\xfa\xff\xfa\xfa\xfa\xff\xf3\xf3\xf3\xff\xe3\xe3\xe3\ +\xff\xe0\xe0\xe0\xff\xe1\xe1\xe3\xff\xe1\xdf\xdb\xff\xdb\xb9\x9f\ +\xff\xec\xd1\xb2\xff\xe7\xc4\x9b\xff\xe5\xbd\x8f\xff\xe5\xbe\x90\ +\xff\xe4\xbd\x8f\xff\xc1\xa0\x79\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\x6b\x6b\x6c\x0a\xb2\xb2\xb2\x6f\xe7\xe7\xe7\xec\xfd\xfd\xfd\ +\xff\xfb\xfb\xfb\xff\xfb\xfb\xfb\xff\xfb\xfb\xfb\xff\xfa\xfa\xfa\ +\xff\xfb\xfb\xfb\xff\xfc\xfc\xfc\xff\xf8\xf8\xf8\xff\xe9\xe9\xe9\ +\xff\xe2\xe3\xe4\xff\xde\xdd\xdc\xff\xdb\xbe\xa9\xff\xee\xd1\xad\ +\xff\xec\xd1\xb0\xff\xed\xd2\xb2\xff\xe9\xc8\x9f\xff\xe7\xc0\x90\ +\xff\xe7\xc1\x92\xff\xc3\xa3\x7c\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xb8\xb8\xb8\x9a\xff\xff\xff\ +\xff\xfb\xfb\xfb\xff\xfb\xfb\xfb\xff\xfb\xfb\xfb\xff\xfb\xfb\xfb\ +\xff\xfb\xfb\xfb\xff\xfc\xfc\xfc\xff\xf8\xf8\xf8\xff\xeb\xeb\xeb\ +\xff\xe9\xea\xeb\xff\xe1\xdf\xdc\xff\xdc\xbd\xa6\xff\xf2\xdb\xc0\ +\xff\xee\xd4\xb4\xff\xee\xd5\xb5\xff\xee\xd5\xb6\xff\xea\xc7\x9c\ +\xff\xe8\xc2\x93\xff\xc5\xa5\x7d\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xbb\xbb\xbb\x8d\xff\xff\xff\ +\xff\xfb\xfb\xfb\xff\xfb\xfb\xfb\xff\xfb\xfb\xfb\xff\xfc\xfc\xfc\ +\xff\xfc\xfc\xfc\xff\xfd\xfd\xfd\xff\xf9\xf9\xf9\xff\xec\xec\xec\ +\xff\xea\xeb\xec\xff\xe7\xe4\xe1\xff\xdf\xc1\xab\xff\xf3\xdf\xc4\ +\xff\xf0\xd7\xb7\xff\xf0\xd8\xb8\xff\xf0\xd9\xbb\xff\xef\xd4\xb3\ +\xff\xea\xc6\x97\xff\xc6\xa7\x7f\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xbe\xbe\xbe\x8f\xff\xff\xff\ +\xff\xfc\xfc\xfc\xff\xfc\xfc\xfc\xff\xfc\xfc\xfc\xff\xfc\xfc\xfc\ +\xff\xfc\xfc\xfc\xff\xfd\xfd\xfd\xff\xf9\xf9\xf9\xff\xec\xec\xec\ +\xff\xea\xeb\xec\xff\xe7\xe4\xe1\xff\xe2\xc5\xae\xff\xf6\xe3\xc9\ +\xff\xf1\xda\xbb\xff\xf1\xda\xbd\xff\xf1\xdb\xbd\xff\xf1\xdc\xc0\ +\xff\xef\xcf\xa6\xff\xc8\xa8\x80\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xc0\xc0\xc0\x8f\xff\xff\xff\ +\xff\xfc\xfc\xfc\xff\xfc\xfc\xfc\xff\xfc\xfc\xfc\xff\xfc\xfc\xfc\ +\xff\xfd\xfd\xfd\xff\xfd\xfd\xfd\xff\xf9\xf9\xf9\xff\xec\xec\xec\ +\xff\xeb\xec\xed\xff\xe7\xe5\xe1\xff\xe3\xc6\xb0\xff\xfb\xe8\xcf\ +\xff\xf5\xde\xc0\xff\xf3\xdd\xc0\xff\xf3\xde\xc1\xff\xf3\xdf\xc3\ +\xff\xf2\xd9\xb9\xff\xca\xab\x82\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xc2\xc2\xc2\x8f\xff\xff\xff\ +\xff\xfd\xfd\xfd\xff\xfd\xfd\xfd\xff\xfd\xfd\xfd\xff\xfd\xfd\xfd\ +\xff\xfd\xfd\xfd\xff\xfe\xfe\xfe\xff\xfa\xfa\xfa\xff\xed\xed\xed\ +\xff\xeb\xec\xed\xff\xe7\xe4\xe1\xff\xe4\xc7\xb2\xff\xfb\xea\xd2\ +\xff\xf9\xe3\xc6\xff\xf5\xe0\xc4\xff\xf5\xe0\xc5\xff\xf5\xe1\xc6\ +\xff\xf4\xe0\xc5\xff\xcb\xb0\x8b\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xc4\xc4\xc4\x8f\xff\xff\xff\ +\xff\xfd\xfd\xfd\xff\xfd\xfd\xfd\xff\xfd\xfd\xfd\xff\xfd\xfd\xfd\ +\xff\xfd\xfd\xfd\xff\xfe\xfe\xfe\xff\xfa\xfa\xfa\xff\xed\xed\xed\ +\xff\xec\xec\xed\xff\xe8\xe4\xe1\xff\xe4\xc8\xb4\xff\xfb\xeb\xd4\ +\xff\xfb\xe7\xcb\xff\xf9\xe5\xca\xff\xf6\xe3\xc8\xff\xf6\xe3\xc9\ +\xff\xf6\xe4\xcc\xff\xce\xb6\x96\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xc7\xc7\xc7\x8f\xff\xff\xff\ +\xff\xfd\xfd\xfd\xff\xfd\xfd\xfd\xff\xfd\xfd\xfd\xff\xfd\xfd\xfd\ +\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfa\xfa\xfa\xff\xee\xee\xee\ +\xff\xec\xed\xee\xff\xe8\xe5\xe2\xff\xe5\xca\xb5\xff\xfd\xee\xd7\ +\xff\xfc\xea\xce\xff\xfc\xea\xd0\xff\xf8\xe6\xcd\xff\xf7\xe5\xcc\ +\xff\xf7\xe6\xd0\xff\xd0\xba\x9c\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xc9\xc9\xc9\x8f\xff\xff\xff\ +\xff\xfd\xfd\xfd\xff\xfd\xfd\xfd\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\ +\xff\xfe\xfe\xfe\xff\xff\xff\xff\xff\xfb\xfb\xfb\xff\xee\xee\xee\ +\xff\xec\xed\xee\xff\xe8\xe5\xe2\xff\xe7\xcc\xb7\xff\xff\xf0\xdb\ +\xff\xfd\xeb\xd2\xff\xfd\xec\xd3\xff\xfb\xeb\xd2\xff\xf8\xe7\xcf\ +\xff\xf8\xe9\xd3\xff\xd1\xbc\x9e\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xcc\xcc\xcc\x8f\xff\xff\xff\ +\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\ +\xff\xfe\xfe\xfe\xff\xff\xff\xff\xff\xfb\xfb\xfb\xff\xee\xee\xee\ +\xff\xec\xed\xee\xff\xe8\xe5\xe1\xff\xe7\xcd\xb8\xff\xff\xf2\xdd\ +\xff\xfe\xed\xd5\xff\xfe\xee\xd6\xff\xfe\xee\xd7\xff\xfa\xea\xd3\ +\xff\xf8\xeb\xd6\xff\xd2\xbe\xa0\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xce\xce\xce\x8f\xff\xff\xff\ +\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\ +\xff\xfe\xfe\xfe\xff\xff\xff\xff\xff\xfb\xfb\xfb\xff\xee\xee\xee\ +\xff\xec\xed\xee\xff\xe8\xe5\xe1\xff\xe8\xce\xba\xff\xff\xf3\xe0\ +\xff\xff\xf0\xd8\xff\xff\xf0\xd9\xff\xff\xf0\xdb\xff\xfc\xee\xd9\ +\xff\xfa\xec\xd9\xff\xd4\xbf\xa3\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xd0\xd0\xd0\x8f\xff\xff\xff\ +\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\ +\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfc\xfc\xfc\xff\xef\xef\xef\ +\xff\xec\xed\xed\xff\xe9\xe5\xe1\xff\xe8\xcf\xbc\xff\xff\xf5\xe3\ +\xff\xff\xf1\xdd\xff\xff\xf1\xde\xff\xff\xf2\xde\xff\xfe\xf1\xdd\ +\xff\xfa\xef\xdd\xff\xd4\xc2\xa6\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xd2\xd2\xd2\x8f\xff\xff\xff\ +\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\ +\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xf7\xf7\xf7\ +\xff\xef\xef\xf0\xff\xe8\xe4\xe0\xff\xe8\xcf\xbd\xff\xff\xf6\xe7\ +\xff\xff\xf3\xe1\xff\xff\xf4\xe2\xff\xff\xf4\xe2\xff\xfe\xf4\xe2\ +\xff\xfa\xf1\xe1\xff\xd4\xc4\xa9\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xd4\xd4\xd4\x8f\xff\xff\xff\ +\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\ +\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\ +\xff\xfb\xfb\xfc\xff\xef\xeb\xe6\xff\xe8\xd0\xbf\xff\xff\xf7\xe5\ +\xff\xff\xf5\xe5\xff\xff\xf6\xe5\xff\xff\xf6\xe6\xff\xff\xf6\xe6\ +\xff\xfb\xf3\xe5\xff\xd4\xc5\xad\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xd6\xd6\xd6\x91\xff\xff\xff\ +\xff\xfe\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xfe\xfe\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xfb\xf8\xf4\xff\xf4\xe6\xd3\xff\xff\xf8\xea\ +\xff\xff\xf8\xe9\xff\xff\xf7\xe8\xff\xff\xf7\xe9\xff\xff\xf7\xe9\ +\xff\xfb\xf4\xe8\xff\xd4\xc7\xb0\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xd3\xd3\xd3\x8a\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf9\xf3\xec\xff\xf7\xed\xe0\ +\xff\xfb\xf2\xe5\xff\xff\xf9\xed\xff\xff\xfa\xed\xff\xff\xf8\xec\ +\xff\xfb\xf5\xeb\xff\xd4\xc8\xb3\xa8\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xc0\xc0\xc0\x1b\xd8\xd8\xd8\ +\x79\xde\xde\xde\x9b\xe8\xe8\xe8\xb9\xec\xec\xec\xd2\xf4\xf4\xf4\ +\xea\xf8\xf8\xf8\xf9\xfc\xfc\xfc\xff\xff\xff\xff\xff\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\ +\xff\xfa\xf4\xed\xff\xf8\xee\xe3\xff\xfb\xf4\xe7\xff\xff\xfa\xf0\ +\xff\xfc\xf8\xef\xff\xd4\xca\xb6\xa9\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xbe\xbe\xbe\x03\xcc\xcc\xcc\ +\x11\xc4\xc4\xc4\x27\xd2\xd2\xd2\x3f\xce\xce\xce\x5c\xdc\xdc\xdc\ +\x7c\xdd\xdd\xdd\x9a\xe8\xe8\xe8\xb9\xec\xec\xec\xd2\xf3\xf4\xf4\ +\xea\xf9\xfa\xfb\xf9\xfb\xfa\xf9\xff\xfa\xf4\xed\xff\xf9\xf0\xe5\ +\xff\xfa\xf5\xec\xff\xd5\xca\xb6\xab\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xbe\xbe\xbe\x03\xcb\xcb\xcb\ +\x11\xc3\xc3\xc3\x27\xd2\xd2\xd3\x3f\xcf\xd0\xd1\x5e\xdb\xdb\xda\ +\x7e\xd6\xd0\xcb\x9f\xf6\xef\xe0\x5a\xff\xff\xff\x00\xff\xff\xff\ +\xff\xff\xff\xff\xff\xff\xf7\xff\x3f\xff\xc0\x7f\x1f\xff\xc0\x07\ +\x0f\xff\xc0\x00\x07\xff\xc0\x00\x03\xff\xc0\x00\x01\xff\xc0\x00\ +\x01\xff\xc0\x00\x01\xff\xc0\x00\x01\xff\xc0\x00\x01\xff\xc0\x00\ +\x01\xff\xe0\x00\x01\xff\xe0\x00\x01\xff\xe0\x00\x01\xff\xe0\x00\ +\x01\xff\xe0\x00\x01\xff\xe0\x00\x01\xff\xe0\x00\x01\xff\xe0\x00\ +\x01\xff\xe0\x00\x01\xff\xe0\x00\x01\xff\xe0\x00\x01\xff\xe0\x00\ +\x01\xff\xe0\x00\x01\xff\xe0\x00\x01\xff\xe0\x00\x01\xff\xe0\x00\ +\x01\xff\xf8\x00\x01\xff\xff\xf8\x01\xff\xff\xff\xfb\ +\x00\x00\x10\xbe\ +\x00\ +\x00\x01\x00\x01\x00\x20\x20\x00\x00\x00\x00\x00\x00\xa8\x10\x00\ +\x00\x16\x00\x00\x00\x28\x00\x00\x00\x20\x00\x00\x00\x40\x00\x00\ +\x00\x01\x00\x20\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x00\x04\x00\x00\x00\ +\x07\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\ +\x08\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\ +\x08\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\ +\x08\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x07\x00\x00\x00\ +\x02\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x04\x00\x00\x00\x1f\x00\x00\x00\ +\x3b\x00\x00\x00\x3f\x00\x00\x00\x3f\x00\x00\x00\x3f\x00\x00\x00\ +\x3f\x00\x00\x00\x3f\x00\x00\x00\x3f\x00\x00\x00\x3f\x00\x00\x00\ +\x3f\x00\x00\x00\x3f\x00\x00\x00\x3f\x00\x00\x00\x3f\x00\x00\x00\ +\x3f\x00\x00\x00\x3f\x00\x00\x00\x3f\x00\x00\x00\x39\x00\x00\x00\ +\x1c\x00\x00\x00\x05\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x07\x00\x00\x00\x3a\xfa\xfa\xfa\ +\xff\xf9\xf9\xf9\xff\xfa\xfa\xfa\xff\xfb\xfb\xfb\xff\xf7\xf7\xf7\ +\xff\xfc\xfc\xfc\xff\xfc\xfc\xfc\xff\xfd\xfd\xfd\xff\xfd\xfd\xfd\ +\xff\xfd\xfd\xfd\xff\xf5\xf5\xf5\xff\xfd\xfd\xfd\xff\xfc\xfc\xfc\ +\xff\xf9\xf9\xf9\xff\xf4\xf4\xf4\xff\xee\xee\xee\xfe\xb5\xb5\xb5\ +\xc5\x00\x00\x00\x27\x00\x00\x00\x07\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x07\x00\x00\x00\x3e\xf8\xf8\xf7\ +\xff\xf7\xf7\xf7\xff\xf8\xf8\xf8\xff\xfa\xfa\xfa\xff\xcf\xcf\xcf\ +\xff\xdc\xdc\xdc\xff\xde\xde\xde\xff\xe1\xe1\xe1\xff\xf7\xf7\xf7\ +\xff\xec\xec\xec\xff\xe6\xe6\xe6\xff\xf0\xf0\xf0\xff\xfb\xfb\xfb\ +\xff\xf7\xf7\xf7\xff\xf1\xf1\xf1\xff\xeb\xeb\xeb\xff\xd9\xd9\xd9\ +\xff\xb5\xb5\xb5\xd9\x00\x00\x00\x2f\x00\x00\x00\x0a\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf8\xf8\xf7\ +\xff\xf7\xf7\xf7\xff\xf8\xf8\xf8\xff\xfa\xfa\xfa\xff\xfa\xfa\xfa\ +\xff\xfb\xfb\xfb\xff\xfb\xfb\xfb\xff\xfc\xfc\xfc\xff\xfd\xfd\xfd\ +\xff\xfd\xfd\xfd\xff\xfe\xfe\xfe\xff\xfe\xfe\xfe\xff\xfb\xfb\xfb\ +\xff\xf7\xf7\xf7\xff\xf1\xf1\xf1\xff\xec\xec\xec\xff\xd9\xd9\xd9\ +\xff\xd7\xd7\xd7\xff\xb5\xb5\xb5\xe3\x0d\x0d\x0d\x3a\x00\x00\x00\ +\x0d\x00\x00\x00\x01\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf8\xf8\xf7\ +\xff\xf7\xf7\xf7\xff\xd4\xd4\xd4\xff\xd3\xd3\xd3\xff\xd5\xd5\xd5\ +\xff\xf3\xf3\xf3\xff\xfb\xfb\xfb\xff\xfc\xfc\xfc\xff\xfd\xfd\xfd\ +\xff\xfb\xfb\xfb\xff\xfb\xfb\xfb\xff\xfb\xfb\xfb\xff\xf9\xf9\xf9\ +\xff\xf5\xf5\xf5\xff\xf0\xf0\xf0\xff\xed\xed\xed\xff\xdf\xdf\xdf\ +\xff\xcc\xcc\xcc\xff\xdc\xdc\xdc\xff\xb8\xb8\xb8\xeb\x19\x19\x19\ +\x46\x00\x00\x00\x0d\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf7\xf7\xf7\ +\xff\xf7\xf7\xf7\xff\xf8\xf8\xf8\xff\xf9\xf9\xf9\xff\xfa\xfa\xfa\ +\xff\xf7\xf7\xf7\xff\xfb\xfb\xfb\xff\xfc\xfc\xfc\xff\xe9\xf0\xf2\ +\xff\x87\xcc\xdf\xff\x5f\xc8\xe5\xff\x48\xca\xee\xff\x4c\xca\xed\ +\xff\x5d\xc6\xe3\xff\x81\xc8\xdc\xff\xdb\xe4\xe6\xff\xe4\xe4\xe4\ +\xff\xc1\xc0\xc0\xff\xf5\xf5\xf5\xff\xdb\xdb\xdb\xff\xb8\xb8\xb8\ +\xeb\x0d\x0d\x0d\x3c\x00\x00\x00\x0b\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf7\xf7\xf7\ +\xff\xf6\xf6\xf6\xff\xf8\xf8\xf8\xff\xf9\xf9\xf9\xff\xfa\xfa\xfa\ +\xff\xce\xce\xce\xff\xdc\xdc\xdc\xff\xbe\xd0\xd4\xff\x54\xd4\xf5\ +\xff\x4b\xd9\xff\xff\x46\xd8\xff\xff\x41\xd6\xff\xff\x3c\xd4\xff\ +\xff\x3b\xd4\xff\xff\x6d\xd0\xec\xff\x4a\xce\xf2\xff\xc7\xd8\xdd\ +\xff\xc2\xc2\xc2\xff\xfe\xfe\xfe\xff\xf4\xf4\xf4\xff\xda\xda\xda\ +\xff\xb3\xb3\xb3\xe3\x00\x00\x00\x31\x00\x00\x00\x08\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf7\xf7\xf7\ +\xff\xf6\xf6\xf6\xff\xf7\xf7\xf7\xff\xf9\xf9\xf9\xff\xfa\xfa\xfa\ +\xff\xfa\xfa\xfa\xff\xf9\xf9\xf9\xff\x80\xcd\xe0\xff\x56\xdd\xff\ +\xff\x51\xdc\xff\xff\x4d\xda\xff\xff\x48\xd8\xff\xff\x43\xd7\xff\ +\xff\x49\xcc\xf0\xff\xfa\xfa\xfa\xff\x9a\xd7\xe7\xff\x71\xc5\xdd\ +\xff\xdb\xdb\xdb\xff\xc6\xc6\xc6\xff\xc7\xc7\xc7\xff\xd1\xd1\xd1\ +\xff\xd6\xd6\xd6\xff\xb3\xb3\xb3\xd9\x00\x00\x00\x2b\x00\x00\x00\ +\x06\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf7\xf7\xf7\ +\xff\xf6\xf6\xf6\xff\xf7\xf7\xf7\xff\xf4\xf4\xf4\xff\xd1\xd1\xd1\ +\xff\xd7\xd7\xd7\xff\xd8\xd8\xd8\xff\x70\xd0\xe6\xff\x5d\xe0\xff\ +\xff\x58\xde\xff\xff\x53\xdc\xff\xff\x4f\xdb\xff\xff\x4a\xd9\xff\ +\xff\x45\xd7\xff\xff\x86\xd4\xe9\xff\x4b\xd0\xf6\xff\x58\xc6\xe5\ +\xff\xee\xee\xee\xff\xea\xea\xea\xff\xe6\xe6\xe6\xff\xe4\xe4\xe4\ +\xff\xe3\xe2\xe2\xff\xe2\xe1\xe1\xff\xb3\xb3\xb3\xc4\x00\x00\x00\ +\x1f\x00\x00\x00\x02\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf7\xf7\xf7\ +\xff\xf5\xf5\xf5\xff\xf7\xf7\xf7\xff\xf8\xf8\xf8\xff\xf7\xf7\xf7\ +\xff\xf3\xf3\xf3\xff\xf5\xf5\xf5\xff\x76\xd3\xe7\xff\x64\xe2\xff\ +\xff\x5f\xe1\xff\xff\x5a\xdf\xff\xff\x55\xdd\xff\xff\x6e\xc9\xe1\ +\xff\x6c\xca\xe2\xff\x67\xc6\xe0\xff\x64\xc6\xe1\xff\x81\xc8\xdc\ +\xff\xf1\xf1\xf1\xff\xf0\xf0\xf0\xff\xef\xef\xef\xff\xea\xea\xea\ +\xff\xed\xed\xed\xff\xeb\xeb\xeb\xff\xf1\xf1\xf1\xfe\x00\x00\x00\ +\x3a\x00\x00\x00\x07\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf7\xf7\xf6\ +\xff\xf5\xf5\xf5\xff\xf6\xf6\xf6\xff\xdd\xd7\xd0\xff\xae\x95\x75\ +\xff\x9a\x76\x4a\xff\xa3\x8b\x6e\xff\x77\xd0\xe3\xff\x6b\xe5\xff\ +\xff\x66\xe3\xff\xff\x61\xe1\xff\xff\x5c\xdf\xff\xff\x62\xc7\xe0\ +\xff\x62\xca\xe4\xff\x5e\xc7\xe3\xff\x5d\xc9\xe6\xff\x59\xc8\xe6\ +\xff\x51\xc2\xe2\xff\x4e\xc2\xe2\xff\x66\xbd\xd6\xff\xaa\xbe\xc4\ +\xff\xe0\xe0\xe0\xff\xec\xec\xec\xff\xf7\xf7\xf7\xff\x00\x00\x00\ +\x3e\x00\x00\x00\x07\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf6\xf6\xf6\ +\xff\xf5\xf5\xf4\xff\xed\xec\xea\xff\xa8\x7d\x47\xff\xa8\x77\x3b\ +\xff\xa5\x74\x39\xff\xa3\x83\x5d\xff\x7d\xd5\xe7\xff\x71\xe7\xff\ +\xff\x6d\xe5\xff\xff\x68\xe4\xff\xff\x63\xe2\xff\xff\x5e\xe0\xff\ +\xff\x59\xde\xff\xff\x54\xdd\xff\xff\x50\xdb\xff\xff\x4b\xd9\xff\ +\xff\x46\xd8\xff\xff\x41\xd6\xff\xff\x3c\xd4\xff\xff\x47\xcc\xf1\ +\xff\xf2\xf3\xf4\xff\xfa\xfa\xfa\xff\xfb\xfb\xfb\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf6\xf6\xf5\ +\xff\xf3\xf3\xf3\xff\xbf\xac\x95\xff\xae\x7c\x3f\xff\xaa\x7a\x3d\ +\xff\xa7\x77\x3b\xff\x9c\x79\x4d\xff\x7b\xc8\xd8\xff\x72\xe7\xff\ +\xff\x72\xe7\xff\xff\x6e\xe6\xff\xff\x6a\xe4\xff\xff\x65\xe3\xff\ +\xff\x60\xe1\xff\xff\x5b\xdf\xff\xff\x56\xdd\xff\xff\x51\xdc\xff\ +\xff\x4d\xda\xff\xff\x48\xd8\xff\xff\x43\xd7\xff\xff\x3e\xd5\xff\ +\xff\x9e\xd2\xe1\xff\xfc\xfc\xfc\xff\xfd\xfd\xfd\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf5\xf5\xf5\ +\xff\xf1\xf1\xf1\xff\xb2\x94\x6f\xff\xb0\x7f\x41\xff\xad\x7c\x3f\ +\xff\xaa\x79\x3c\xff\xa7\x76\x3b\xff\xaa\xc5\xc7\xff\x72\xe7\xff\ +\xff\x72\xe7\xff\xff\x72\xe7\xff\xff\x70\xe7\xff\xff\x6c\xe5\xff\ +\xff\x67\xe3\xff\xff\x62\xe2\xff\xff\x5d\xe0\xff\xff\x58\xde\xff\ +\xff\x53\xdc\xff\xff\x4f\xdb\xff\xff\x4a\xd9\xff\xff\x45\xd7\xff\ +\xff\x72\xc8\xe0\xff\xfb\xfb\xfb\xff\xfe\xfe\xfe\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf5\xf5\xf4\ +\xff\xf1\xf1\xf0\xff\xb0\x8b\x5d\xff\xb3\x81\x43\xff\xb0\x7e\x41\ +\xff\xad\x7c\x3e\xff\xa9\x79\x3c\xff\x9e\x7c\x53\xff\x9c\xb7\xb8\ +\xff\x7b\xc9\xd9\xff\x77\xcf\xe1\xff\x78\xd0\xe2\xff\x78\xd0\xe2\ +\xff\x77\xd1\xe4\xff\x6d\xd4\xeb\xff\x64\xe2\xff\xff\x5f\xe1\xff\ +\xff\x5a\xdf\xff\xff\x55\xdd\xff\xff\x51\xdb\xff\xff\x4c\xda\xff\ +\xff\x5a\xc6\xe3\xff\xf3\xf3\xf3\xff\xfd\xfd\xfd\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf4\xf4\xf4\ +\xff\xef\xef\xef\xff\xb1\x8c\x5b\xff\xb6\x84\x45\xff\xb3\x81\x42\ +\xff\xaf\x7e\x40\xff\xac\x7b\x3e\xff\xa9\x78\x3c\xff\xa6\x75\x3b\ +\xff\xa1\x7e\x52\xff\xa2\x83\x5c\xff\xa0\x81\x5b\xff\x9e\x7f\x59\ +\xff\x9d\x7e\x59\xff\xa4\x89\x69\xff\xa9\xc2\xc2\xff\x71\xd5\xeb\ +\xff\x61\xe1\xff\xff\x5c\xe0\xff\xff\x57\xde\xff\xff\x52\xdc\xff\ +\xff\x66\xcb\xe6\xff\xfb\xfb\xfb\xff\xfd\xfd\xfd\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf4\xf4\xf4\ +\xff\xef\xef\xef\xff\xb5\x96\x6f\xff\xb8\x86\x47\xff\xb5\x83\x44\ +\xff\xb2\x80\x42\xff\xaf\x7e\x40\xff\xac\x7b\x3e\xff\xa9\x78\x3b\ +\xff\xa5\x75\x39\xff\xa2\x72\x37\xff\x9f\x6f\x35\xff\x9c\x6c\x33\ +\xff\x99\x6a\x30\xff\x98\x69\x30\xff\x98\x69\x30\xff\x95\xa8\xa5\ +\xff\x68\xe4\xff\xff\x63\xe2\xff\xff\x5e\xe0\xff\xff\x59\xde\xff\ +\xff\x75\xc4\xd8\xff\xeb\xeb\xeb\xff\xfd\xfd\xfd\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf4\xf4\xf3\ +\xff\xf0\xf0\xef\xff\xc1\xad\x93\xff\xbb\x89\x48\xff\xb8\x86\x46\ +\xff\xb5\x83\x44\xff\xb2\x80\x42\xff\xae\x7d\x40\xff\xab\x7a\x3d\ +\xff\xa8\x77\x3b\xff\xa5\x75\x39\xff\xa2\x72\x37\xff\x9e\x6f\x34\ +\xff\x9b\x6c\x32\xff\x98\x69\x30\xff\x98\x69\x30\xff\xa1\x84\x62\ +\xff\x75\xd7\xeb\xff\x6a\xe4\xff\xff\x65\xe3\xff\xff\x60\xe1\xff\ +\xff\xa0\xd4\xe0\xff\xfa\xfa\xfa\xff\xfd\xfd\xfd\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf3\xf3\xf2\ +\xff\xf0\xf0\xef\xff\xe9\xe8\xe6\xff\xb3\x89\x54\xff\xbb\x88\x48\ +\xff\xb8\x85\x46\xff\xb4\x82\x44\xff\xb1\x80\x41\xff\xae\x7d\x3f\ +\xff\xab\x7a\x3d\xff\xa8\x77\x3b\xff\xa4\x74\x39\xff\xa1\x71\x36\ +\xff\x9e\x6e\x34\xff\x9b\x6c\x32\xff\x98\x69\x30\xff\x93\x71\x48\ +\xff\x79\xc9\xda\xff\x70\xe7\xff\xff\x6c\xe5\xff\xff\x69\xdc\xf6\ +\xff\xd9\xde\xdf\xff\xf5\xf5\xf5\xff\xfc\xfc\xfc\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf2\xf2\xf1\ +\xff\xef\xef\xee\xff\xf0\xf0\xef\xff\xda\xd3\xc9\xff\xb8\x9d\x79\ +\xff\xb3\x89\x53\xff\xb2\x86\x4e\xff\xaf\x83\x4c\xff\xac\x81\x4a\ +\xff\xaa\x7e\x48\xff\xa6\x7b\x47\xff\xa4\x7a\x43\xff\xa4\x74\x38\ +\xff\xa1\x71\x36\xff\x9e\x6e\x34\xff\x9a\x6b\x32\xff\x9b\x79\x50\ +\xff\x8a\xd1\xdf\xff\x76\xdd\xf2\xff\x81\xd2\xe3\xff\xc1\xdb\xe1\ +\xff\xf9\xf9\xf9\xff\xfa\xfa\xfa\xff\xfb\xfb\xfb\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf1\xf1\xf1\ +\xff\xee\xee\xed\xff\xef\xef\xee\xff\xcb\xcb\xca\xff\xc8\xc8\xc7\ +\xff\xd0\xd0\xcf\xff\xc9\xc9\xc8\xff\xa7\x93\x79\xff\xa5\x8b\x6a\ +\xff\xad\x93\x72\xff\xab\x91\x71\xff\xa2\x88\x67\xff\xa7\x76\x3a\ +\xff\xa3\x73\x38\xff\xa0\x70\x36\xff\x9d\x6e\x33\xff\x95\x73\x49\ +\xff\xcd\xcd\xcd\xff\xc7\xc7\xc7\xff\xd8\xd8\xd8\xff\xe6\xe6\xe6\ +\xff\xfa\xfa\xfa\xff\xfa\xfa\xfa\xff\xfb\xfb\xfb\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf1\xf1\xf1\ +\xff\xed\xed\xed\xff\xee\xee\xee\xff\xef\xef\xef\xff\xf0\xf0\xef\ +\xff\xf1\xf1\xf0\xff\xef\xef\xee\xff\xb2\x8d\x5c\xff\xb6\x87\x4c\ +\xff\xb9\x98\x6e\xff\xb0\x7e\x40\xff\xad\x7b\x3e\xff\xa9\x79\x3c\ +\xff\xa6\x76\x3a\xff\xa3\x73\x38\xff\xa0\x70\x35\xff\x9e\x7c\x52\ +\xff\xf6\xf6\xf6\xff\xf8\xf8\xf8\xff\xf9\xf9\xf9\xff\xf9\xf9\xf9\ +\xff\xf9\xf9\xf9\xff\xf9\xf9\xf9\xff\xfa\xfa\xfa\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xf1\xf1\xf0\ +\xff\xed\xed\xec\xff\xc2\xc2\xc2\xff\xca\xca\xc9\xff\xed\xed\xec\ +\xff\xc8\xc8\xc7\xff\xc8\xc8\xc7\xff\xad\x8a\x5f\xff\xc7\xb5\x9e\ +\xff\xec\xec\xeb\xff\xac\x80\x48\xff\xaf\x7e\x40\xff\xac\x7b\x3e\ +\xff\xa9\x78\x3c\xff\xa6\x75\x39\xff\xa3\x72\x37\xff\xa5\x89\x65\ +\xff\xf5\xf5\xf5\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\ +\xff\xf8\xf8\xf7\xff\xf8\xf8\xf7\xff\xf9\xf9\xf9\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xef\xef\xee\ +\xff\xec\xec\xeb\xff\xed\xed\xec\xff\xed\xed\xec\xff\xee\xee\xed\ +\xff\xef\xef\xee\xff\xef\xef\xee\xff\xc0\xac\x92\xff\xbc\x91\x5b\ +\xff\xc4\xab\x8c\xff\xb5\x83\x44\xff\xb2\x80\x42\xff\xaf\x7d\x40\ +\xff\xac\x7b\x3e\xff\xa8\x78\x3b\xff\xa5\x75\x3a\xff\xd1\xc9\xbe\ +\xff\xf6\xf6\xf5\xff\xf6\xf6\xf6\xff\xf6\xf6\xf6\xff\xf6\xf6\xf6\ +\xff\xf6\xf6\xf6\xff\xf6\xf6\xf6\xff\xf8\xf8\xf8\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xee\xee\xee\ +\xff\xea\xea\xe9\xff\xec\xec\xeb\xff\xec\xec\xeb\xff\xed\xed\xec\ +\xff\xee\xee\xed\xff\xef\xef\xee\xff\xed\xed\xed\xff\xce\xc3\xb3\ +\xff\xb5\x95\x6b\xff\xb1\x89\x56\xff\xb0\x85\x4d\xff\xad\x82\x4d\ +\xff\xa9\x83\x52\xff\xab\x8d\x67\xff\xd4\xcc\xc2\xff\xf3\xf3\xf3\ +\xff\xf4\xf4\xf4\xff\xf5\xf5\xf4\xff\xf5\xf5\xf4\xff\xf5\xf5\xf5\ +\xff\xf5\xf5\xf5\xff\xf5\xf5\xf5\xff\xf7\xf7\xf7\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xee\xee\xed\ +\xff\xe9\xe9\xe8\xff\xea\xea\xe9\xff\xeb\xeb\xea\xff\xcb\xcb\xca\ +\xff\xc9\xc9\xc8\xff\xd5\xd5\xd4\xff\xd1\xd1\xd1\xff\xe2\xe2\xe1\ +\xff\xd5\xd5\xd4\xff\xd0\xd0\xd0\xff\xdf\xdf\xde\xff\xed\xed\xed\ +\xff\xef\xef\xee\xff\xf0\xf0\xf0\xff\xf3\xf3\xf2\xff\xf3\xf3\xf2\ +\xff\xf3\xf3\xf3\xff\xf3\xf3\xf3\xff\xf4\xf4\xf3\xff\xf4\xf4\xf3\ +\xff\xf4\xf4\xf3\xff\xf4\xf4\xf3\xff\xf7\xf7\xf6\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x08\x00\x00\x00\x3f\xed\xec\xeb\ +\xff\xe8\xe8\xe7\xff\xe9\xe9\xe8\xff\xea\xea\xe9\xff\xeb\xeb\xea\ +\xff\xec\xec\xeb\xff\xed\xed\xec\xff\xed\xed\xec\xff\xea\xea\xe9\ +\xff\xeb\xeb\xea\xff\xef\xef\xee\xff\xf0\xf0\xef\xff\xf0\xf0\xef\ +\xff\xf1\xf1\xf0\xff\xf1\xf1\xf0\xff\xf1\xf1\xf1\xff\xf2\xf2\xf1\ +\xff\xf2\xf2\xf1\xff\xf2\xf2\xf2\xff\xf2\xf2\xf2\xff\xf2\xf2\xf2\ +\xff\xf2\xf2\xf2\xff\xf2\xf2\xf2\xff\xf5\xf5\xf5\xff\x00\x00\x00\ +\x3f\x00\x00\x00\x08\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x07\x00\x00\x00\x3e\xec\xeb\xeb\ +\xff\xe7\xe6\xe5\xff\xc1\xc0\xbf\xff\xc4\xc4\xc3\xff\xe6\xe5\xe4\ +\xff\xc4\xc3\xc3\xff\xc8\xc8\xc7\xff\xdf\xdf\xde\xff\xcf\xcf\xce\ +\xff\xcb\xcb\xcb\xff\xe7\xe7\xe6\xff\xee\xee\xee\xff\xef\xef\xee\ +\xff\xef\xef\xef\xff\xf0\xf0\xef\xff\xf0\xf0\xef\xff\xf0\xf0\xf0\ +\xff\xf1\xf1\xf0\xff\xf1\xf1\xf0\xff\xf1\xf1\xf0\xff\xf1\xf1\xf0\ +\xff\xf1\xf1\xf1\xff\xf1\xf1\xf1\xff\xf4\xf4\xf4\xff\x00\x00\x00\ +\x3e\x00\x00\x00\x07\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x07\x00\x00\x00\x38\xf1\xf1\xf0\ +\xff\xeb\xeb\xeb\xff\xec\xec\xeb\xff\xed\xed\xec\xff\xee\xee\xed\ +\xff\xee\xee\xee\xff\xef\xef\xee\xff\xf0\xf0\xef\xff\xf1\xf1\xf0\ +\xff\xf1\xf1\xf0\xff\xf1\xf1\xf1\xff\xf1\xf1\xf1\xff\xf2\xf2\xf1\ +\xff\xf2\xf2\xf1\xff\xf3\xf3\xf2\xff\xf3\xf3\xf2\xff\xf3\xf3\xf2\ +\xff\xf3\xf3\xf3\xff\xf4\xf4\xf3\xff\xf4\xf4\xf3\xff\xf4\xf4\xf3\ +\xff\xf4\xf4\xf3\xff\xf4\xf4\xf3\xff\xf7\xf7\xf6\xff\x00\x00\x00\ +\x38\x00\x00\x00\x07\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\x00\x00\x00\x02\x00\x00\x00\x13\x00\x00\x00\ +\x24\x00\x00\x00\x27\x00\x00\x00\x27\x00\x00\x00\x28\x00\x00\x00\ +\x28\x00\x00\x00\x28\x00\x00\x00\x28\x00\x00\x00\x28\x00\x00\x00\ +\x28\x00\x00\x00\x28\x00\x00\x00\x28\x00\x00\x00\x28\x00\x00\x00\ +\x28\x00\x00\x00\x28\x00\x00\x00\x28\x00\x00\x00\x28\x00\x00\x00\ +\x28\x00\x00\x00\x28\x00\x00\x00\x28\x00\x00\x00\x28\x00\x00\x00\ +\x28\x00\x00\x00\x28\x00\x00\x00\x27\x00\x00\x00\x24\x00\x00\x00\ +\x13\x00\x00\x00\x02\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x00\x01\x00\x00\x00\ +\x03\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\ +\x04\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\ +\x04\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\ +\x04\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\ +\x04\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\ +\x04\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\ +\x01\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\ +\xff\xff\xff\xff\xff\xf0\x00\x07\xff\xf0\x00\x03\xff\xf0\x00\x01\ +\xff\xf0\x00\x00\xff\xf0\x00\x00\x7f\xf0\x00\x00\x3f\xf0\x00\x00\ +\x1f\xf0\x00\x00\x0f\xf0\x00\x00\x0f\xf0\x00\x00\x0f\xf0\x00\x00\ +\x0f\xf0\x00\x00\x0f\xf0\x00\x00\x0f\xf0\x00\x00\x0f\xf0\x00\x00\ +\x0f\xf0\x00\x00\x0f\xf0\x00\x00\x0f\xf0\x00\x00\x0f\xf0\x00\x00\ +\x0f\xf0\x00\x00\x0f\xf0\x00\x00\x0f\xf0\x00\x00\x0f\xf0\x00\x00\ +\x0f\xf0\x00\x00\x0f\xf0\x00\x00\x0f\xf0\x00\x00\x0f\xf0\x00\x00\ +\x0f\xf0\x00\x00\x0f\xff\xff\xff\xff\xff\xff\xff\xff\ +\x00\x00\x03\x76\ +\x00\ +\x00\x10\xbe\x78\x9c\xb5\x97\x4b\x4f\xdb\x40\x14\x85\x87\xd2\x97\ +\x5a\x45\x45\xaa\x54\x09\xa9\x12\x2c\x2a\x81\xd4\x45\xd5\x1d\xa5\ +\x55\xe9\xaa\x7f\xab\x7f\xa7\x7b\x84\xc4\x92\xa2\x16\x36\x6c\xd8\ +\x80\xc2\x43\x88\x47\x43\x62\x3b\x89\xf3\x20\x09\xaf\xdc\xde\x33\ +\xe3\xc1\xe3\x89\xed\x31\x11\x35\x3a\x32\x71\xc6\xf7\x7c\x39\x77\ +\x3c\xc9\x08\x31\xc1\x7f\xb3\xb3\x42\x1e\x3f\xa7\x84\x78\xc3\xe7\ +\x79\x16\x2e\x7d\x63\x4d\x88\xe8\xcd\x29\x31\x72\x10\xd1\x9d\xf8\ +\x78\xcc\x7a\xc6\x7a\x3e\xa6\x70\xef\x23\xb3\x66\x51\x45\xde\x33\ +\xac\xcf\xac\xa5\x31\xf5\x89\xf5\x96\xf5\x64\x0c\x7f\xb0\x2f\x5e\ +\x5d\x5d\xd1\xe5\xe5\x25\xe1\x7c\x7d\x7d\x2d\x75\x73\x73\x23\x75\ +\x7b\x7b\x7b\xa7\xe1\x70\x28\x85\xeb\x18\xdf\xed\x76\xa9\xd1\x68\ +\x0c\x57\x56\x56\xfe\x70\x9d\x39\xd4\x1b\xc3\xff\xeb\x60\xd0\xa7\ +\x7e\xbf\x4f\x83\xc1\x40\x32\xd8\x1c\xa6\x3f\x0e\x5c\xc7\xf8\x56\ +\xab\x45\xbe\xef\x53\xb9\x5c\x26\x66\x28\x73\xad\x0f\xac\x17\xf7\ +\xf0\x47\xff\x96\xee\xe3\x0f\x99\xfe\x41\x10\x48\xff\xdd\xdd\x5d\ +\x30\x9c\x96\x4a\xa5\x45\xae\x59\xc2\x14\x1c\xc7\x3f\xad\x0f\x69\ +\xf9\x63\x6c\xbb\xdd\xa6\x7a\xbd\x4e\x47\x47\x47\xb4\xb5\xb5\x45\ +\x07\x07\x07\xb4\xba\xba\xea\x4f\x4f\x4f\x7f\x07\x43\x51\x7f\x78\ +\xe7\xf9\x6b\x06\xed\x8f\xff\x31\x06\xfd\x6f\x36\x9b\x74\x76\x76\ +\x46\xbf\xd6\xd6\xe8\xe2\xe2\x82\xf6\xf7\xf7\x25\x03\xe7\xf0\x85\ +\x6b\xbf\x2c\xea\xdf\xeb\xf5\x12\xfe\x9a\xc1\x9e\x87\x76\x0f\x90\ +\x81\xe7\x79\xb4\xbe\xbe\x2e\xaf\x83\x69\x6f\x6f\x8f\x96\x97\x97\ +\x4f\xb9\xf6\x47\x78\x14\xf5\xc7\xd9\xf6\xcf\xeb\x01\xc6\xe0\x33\ +\x87\x61\x48\x87\x87\x87\xb4\xb1\xb1\x41\x9b\x9b\x9b\xb4\xbd\xbd\ +\x4d\x3b\x3b\x3b\x60\xc0\x9c\x7c\xcf\x7a\x5a\xd4\x3f\x6b\x0e\xda\ +\x3d\x30\x9f\x43\xcd\x80\x1c\x2a\x95\x8a\xec\xc7\xe9\xc9\x09\x9d\ +\x1c\x1f\x83\xe1\x77\xb4\xc6\x8c\xac\x51\xa6\x3f\x6a\xd8\x73\xc0\ +\xd5\x03\xdd\x07\x8c\x07\x7f\xa7\xd3\x91\xcf\x04\xe6\x04\xaf\x0b\ +\x52\xfc\x7a\x88\x35\x26\x6d\x6d\x88\xfd\x7b\xd2\xdf\xd5\x83\x2c\ +\x06\xdd\x0b\xb0\xa3\x06\x6a\x61\x1e\x40\xa8\x87\x35\x26\xcf\x5f\ +\xdd\xd3\xe5\x7b\xb3\x33\x70\x31\xe0\x35\xde\xc3\x38\x7d\x9f\xbe\ +\x37\x5a\xa7\x47\xe6\x61\xec\xaf\x78\xcd\x0c\xf2\xe6\x41\x16\x83\ +\xc9\x62\xca\xe5\x0f\x5f\xf4\xee\x3e\x19\x64\x7d\x37\xa4\xc9\xed\ +\xdf\x65\xff\x36\x67\xd0\x19\xc9\x20\x8d\x21\xeb\xfb\x29\x8d\xa7\ +\xc8\xe7\x47\xf6\x58\x47\x90\x41\x5a\x1f\x5c\x0c\x2e\x96\x22\xfe\ +\x78\x6e\x8a\x32\xb8\x38\x6c\x26\xb7\x3f\x9e\xdb\x90\xfd\x5b\x46\ +\x1f\xd4\x5c\x28\xca\x90\x97\x89\xcb\x1f\x9e\x61\xd8\x2c\xcc\x60\ +\x73\x64\xb1\x68\x1e\x97\x3f\x3c\x9b\xcd\x46\x26\x43\xb2\x17\xf9\ +\x1c\x69\x5c\x2e\x7f\xf4\xbe\xd1\xa8\x67\x32\x98\xf3\xc1\xcc\xc2\ +\xe4\xc8\x62\x29\xea\x8f\xdf\x11\x58\xaf\xb1\x76\xdb\x0c\xf1\x9c\ +\x34\xb3\x50\x1c\x26\x8b\xcd\xa3\x99\xdc\xfe\x21\xfb\x07\x11\xc3\ +\x68\x0e\xea\xb9\x48\x66\x91\xcc\x23\x66\x89\x99\x94\xc0\xe0\xf2\ +\x87\x5f\x10\xf8\x11\x43\x30\xc2\xa0\x9e\x4d\x33\x8b\x64\x1e\x8a\ +\x25\xe6\x31\x85\x4c\x5c\xfe\xc8\xdd\xf3\x6a\x92\x41\x73\x68\x06\ +\xb3\x1f\x9a\x43\xe7\x61\xb2\xc4\x3c\x31\x93\xfe\x4e\x77\xfb\xd7\ +\xa9\x56\xab\x4a\x06\xdf\xf7\x12\x0c\x6a\x4e\xa8\x2c\xf0\x1b\x23\ +\x9d\x43\xb1\xc4\x3c\xb1\xd0\x1b\x97\x7f\x9d\xfd\xaa\xd5\xf3\x54\ +\x86\x64\x16\x9a\x43\xe7\x61\xb2\x28\x1e\x5b\xc8\x20\xc7\x5f\xee\ +\x3f\x02\xde\x43\x9c\x57\xfe\x52\xf5\xbc\x42\x35\xe6\xf0\x98\xc3\ +\xb7\xfa\xa1\xe7\xa6\xce\x43\xf7\x25\x99\x4b\x98\xe0\x82\xd0\x97\ +\x9c\xdf\x1f\xf0\x5f\xc0\x78\xec\x63\xb2\x72\xb7\x6b\xea\xcf\x1b\ +\x67\xdf\x4e\xcd\xde\xc8\x7f\x21\xc3\xff\x11\xeb\x35\xeb\x9d\x50\ +\xfb\x37\x68\xfe\x01\x35\x17\xd5\x86\x47\xea\x1e\x59\x60\xab\x2e\ +\xc4\xa4\x50\x7b\xe1\xff\x21\xd4\xb6\xf7\x62\xf2\x08\x39\x92\x50\ +\x4c\xb2\x26\x58\x02\xfa\xc1\x5a\x62\xcd\xb0\x5e\x3d\xb4\xb4\xef\ +\x3f\xa6\xa9\x0d\x9d\ +" + +qt_resource_name = b"\ +\x00\x04\ +\x00\x06\xd0\x25\ +\x00\x66\ +\x00\x69\x00\x6c\x00\x65\ +\x00\x04\ +\x00\x07\x37\xfe\ +\x00\x6d\ +\x00\x61\x00\x69\x00\x6e\ +\x00\x06\ +\x06\x7a\xb0\x5e\ +\x00\x61\ +\x00\x63\x00\x74\x00\x69\x00\x6f\x00\x6e\ +\x00\x03\ +\x00\x00\x6f\x9f\ +\x00\x69\ +\x00\x63\x00\x6f\ +\x00\x0f\ +\x04\x18\x5a\xdf\ +\x00\x66\ +\x00\x6f\x00\x6c\x00\x64\x00\x65\x00\x72\x00\x2d\x00\x6f\x00\x70\x00\x65\x00\x6e\x00\x2e\x00\x69\x00\x63\x00\x6f\ +\x00\x0a\ +\x05\x78\x57\xff\ +\x00\x72\ +\x00\x65\x00\x6c\x00\x6f\x00\x61\x00\x64\x00\x2e\x00\x69\x00\x63\x00\x6f\ +\x00\x0b\ +\x0c\x6a\x34\x9f\ +\x00\x72\ +\x00\x65\x00\x66\x00\x72\x00\x65\x00\x73\x00\x68\x00\x2e\x00\x69\x00\x63\x00\x6f\ +\x00\x0f\ +\x0c\x42\xb1\x1f\ +\x00\x65\ +\x00\x64\x00\x69\x00\x74\x00\x2d\x00\x64\x00\x65\x00\x6c\x00\x65\x00\x74\x00\x65\x00\x2e\x00\x69\x00\x63\x00\x6f\ +\x00\x0e\ +\x06\x0c\x12\xdf\ +\x00\x61\ +\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2d\x00\x64\x00\x6f\x00\x77\x00\x6e\x00\x2e\x00\x69\x00\x63\x00\x6f\ +\x00\x0c\ +\x09\xd0\x62\xdf\ +\x00\x61\ +\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2d\x00\x75\x00\x70\x00\x2e\x00\x69\x00\x63\x00\x6f\ +\x00\x0c\ +\x0a\xd1\x41\xff\ +\x00\x65\ +\x00\x64\x00\x69\x00\x74\x00\x2d\x00\x61\x00\x64\x00\x64\x00\x2e\x00\x69\x00\x63\x00\x6f\ +\x00\x07\ +\x0a\x78\x4f\x5f\ +\x00\x63\ +\x00\x70\x00\x75\x00\x2e\x00\x69\x00\x63\x00\x6f\ +\x00\x12\ +\x08\x97\x58\xdf\ +\x00\x72\ +\x00\x65\x00\x76\x00\x70\x00\x69\x00\x70\x00\x79\x00\x63\x00\x6f\x00\x6e\x00\x74\x00\x72\x00\x6f\x00\x6c\x00\x2e\x00\x69\x00\x63\ +\x00\x6f\ +\x00\x0a\ +\x0a\xc8\xe3\xdf\ +\x00\x66\ +\x00\x6f\x00\x6c\x00\x64\x00\x65\x00\x72\x00\x2e\x00\x69\x00\x63\x00\x6f\ +\x00\x0f\ +\x0a\x68\xfa\xdf\ +\x00\x66\ +\x00\x69\x00\x6c\x00\x65\x00\x2d\x00\x70\x00\x79\x00\x74\x00\x68\x00\x6f\x00\x6e\x00\x2e\x00\x69\x00\x63\x00\x6f\ +\x00\x0d\ +\x09\x95\x48\xbf\ +\x00\x66\ +\x00\x69\x00\x6c\x00\x65\x00\x2d\x00\x65\x00\x6c\x00\x73\x00\x65\x00\x2e\x00\x69\x00\x63\x00\x6f\ +" + +qt_resource_struct_v1 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x10\ +\x00\x00\x00\x0e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0c\ +\x00\x00\x00\x1c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x04\ +\x00\x00\x00\x2e\x00\x02\x00\x00\x00\x07\x00\x00\x00\x05\ +\x00\x00\x00\x3a\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x00\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x03\xce\ +\x00\x00\x00\xb8\x00\x00\x00\x00\x00\x01\x00\x00\x36\x14\ +\x00\x00\x00\xda\x00\x00\x00\x00\x00\x01\x00\x00\x46\xd6\ +\x00\x00\x00\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x57\x98\ +\x00\x00\x00\x94\x00\x00\x00\x00\x00\x01\x00\x00\x25\x52\ +\x00\x00\x00\x78\x00\x00\x00\x00\x00\x01\x00\x00\x14\x90\ +\x00\x00\x00\x2e\x00\x02\x00\x00\x00\x03\x00\x00\x00\x0d\ +\x00\x00\x01\x2a\x00\x01\x00\x00\x00\x01\x00\x00\x79\x1c\ +\x00\x00\x01\x16\x00\x00\x00\x00\x00\x01\x00\x00\x68\x5a\ +\x00\x00\x01\x54\x00\x00\x00\x00\x00\x01\x00\x00\x7c\x21\ +\x00\x00\x00\x2e\x00\x02\x00\x00\x00\x02\x00\x00\x00\x11\ +\x00\x00\x01\x92\x00\x01\x00\x00\x00\x01\x00\x00\x9d\xa5\ +\x00\x00\x01\x6e\x00\x00\x00\x00\x00\x01\x00\x00\x8c\xe3\ +" + +qt_resource_struct_v2 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x10\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x0e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0c\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x1c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x04\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x2e\x00\x02\x00\x00\x00\x07\x00\x00\x00\x05\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x3a\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x2c\x48\x86\xc6\x88\ +\x00\x00\x00\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x03\xce\ +\x00\x00\x01\x2c\x48\x86\x93\xc0\ +\x00\x00\x00\xb8\x00\x00\x00\x00\x00\x01\x00\x00\x36\x14\ +\x00\x00\x01\x72\xe6\x95\x29\xb5\ +\x00\x00\x00\xda\x00\x00\x00\x00\x00\x01\x00\x00\x46\xd6\ +\x00\x00\x01\x72\xe6\x95\x29\xb5\ +\x00\x00\x00\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x57\x98\ +\x00\x00\x01\x72\xe6\x95\x29\xb5\ +\x00\x00\x00\x94\x00\x00\x00\x00\x00\x01\x00\x00\x25\x52\ +\x00\x00\x01\x72\xe6\x95\x29\xb5\ +\x00\x00\x00\x78\x00\x00\x00\x00\x00\x01\x00\x00\x14\x90\ +\x00\x00\x01\x2c\x48\x86\xdd\xf8\ +\x00\x00\x00\x2e\x00\x02\x00\x00\x00\x03\x00\x00\x00\x0d\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x01\x2a\x00\x01\x00\x00\x00\x01\x00\x00\x79\x1c\ +\x00\x00\x01\x6d\xe9\x44\xee\x85\ +\x00\x00\x01\x16\x00\x00\x00\x00\x00\x01\x00\x00\x68\x5a\ +\x00\x00\x01\x2c\x48\x86\xbe\xb8\ +\x00\x00\x01\x54\x00\x00\x00\x00\x00\x01\x00\x00\x7c\x21\ +\x00\x00\x01\x2c\x48\x86\x93\xc0\ +\x00\x00\x00\x2e\x00\x02\x00\x00\x00\x02\x00\x00\x00\x11\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x01\x92\x00\x01\x00\x00\x00\x01\x00\x00\x9d\xa5\ +\x00\x00\x01\x2c\x48\x86\xce\x58\ +\x00\x00\x01\x6e\x00\x00\x00\x00\x00\x01\x00\x00\x8c\xe3\ +\x00\x00\x01\x2c\x48\x86\xd6\x28\ +" + +qt_version = QtCore.qVersion().split('.') +if qt_version < ['5', '8', '0']: + rcc_version = 1 + qt_resource_struct = qt_resource_struct_v1 +else: + rcc_version = 2 + qt_resource_struct = qt_resource_struct_v2 + +def qInitResources(): + QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/include/ui/revpicommander_ui.py b/include/ui/revpicommander_ui.py new file mode 100644 index 0000000..3564b7c --- /dev/null +++ b/include/ui/revpicommander_ui.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'revpicommander.ui' +# +# Created by: PyQt5 UI code generator 5.10.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_win_revpicommander(object): + def setupUi(self, win_revpicommander): + win_revpicommander.setObjectName("win_revpicommander") + win_revpicommander.resize(318, 273) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(":/main/ico/revpipycontrol.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + win_revpicommander.setWindowIcon(icon) + self.centralwidget = QtWidgets.QWidget(win_revpicommander) + self.centralwidget.setObjectName("centralwidget") + self.gl = QtWidgets.QGridLayout(self.centralwidget) + self.gl.setObjectName("gl") + self.txt_connection = QtWidgets.QLineEdit(self.centralwidget) + self.txt_connection.setFocusPolicy(QtCore.Qt.NoFocus) + self.txt_connection.setReadOnly(True) + self.txt_connection.setObjectName("txt_connection") + self.gl.addWidget(self.txt_connection, 0, 0, 1, 1) + self.btn_plc_start = QtWidgets.QPushButton(self.centralwidget) + self.btn_plc_start.setObjectName("btn_plc_start") + self.gl.addWidget(self.btn_plc_start, 1, 0, 1, 1) + self.btn_plc_stop = QtWidgets.QPushButton(self.centralwidget) + self.btn_plc_stop.setObjectName("btn_plc_stop") + self.gl.addWidget(self.btn_plc_stop, 2, 0, 1, 1) + self.btn_plc_restart = QtWidgets.QPushButton(self.centralwidget) + self.btn_plc_restart.setObjectName("btn_plc_restart") + self.gl.addWidget(self.btn_plc_restart, 3, 0, 1, 1) + self.btn_plc_logs = QtWidgets.QPushButton(self.centralwidget) + self.btn_plc_logs.setObjectName("btn_plc_logs") + self.gl.addWidget(self.btn_plc_logs, 4, 0, 1, 1) + self.txt_status = QtWidgets.QLineEdit(self.centralwidget) + self.txt_status.setFocusPolicy(QtCore.Qt.NoFocus) + self.txt_status.setAlignment(QtCore.Qt.AlignCenter) + self.txt_status.setReadOnly(True) + self.txt_status.setObjectName("txt_status") + self.gl.addWidget(self.txt_status, 5, 0, 1, 1) + self.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, 6, 0, 1, 1) + win_revpicommander.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(win_revpicommander) + self.menubar.setGeometry(QtCore.QRect(0, 0, 318, 22)) + 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.setCheckable(True) + 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) + 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 + win_revpicommander.setWindowTitle(_translate("win_revpicommander", "RevPi Python PLC Commander")) + self.btn_plc_start.setText(_translate("win_revpicommander", "PLC &start")) + self.btn_plc_stop.setText(_translate("win_revpicommander", "PLC s&top")) + self.btn_plc_restart.setText(_translate("win_revpicommander", "PLC restart")) + self.btn_plc_logs.setText(_translate("win_revpicommander", "PLC &logs")) + self.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_search.setText(_translate("win_revpicommander", "&Search Revolution Pi...")) + self.act_search.setShortcut(_translate("win_revpicommander", "Ctrl+F")) + self.act_quit.setText(_translate("win_revpicommander", "&Quit")) + self.act_webpage.setText(_translate("win_revpicommander", "Visit &webpage...")) + self.act_info.setText(_translate("win_revpicommander", "&Info...")) + self.act_logs.setText(_translate("win_revpicommander", "PLC &logs...")) + self.act_logs.setShortcut(_translate("win_revpicommander", "F3")) + self.act_options.setText(_translate("win_revpicommander", "PLC &options...")) + self.act_options.setShortcut(_translate("win_revpicommander", "Ctrl+O")) + self.act_program.setText(_translate("win_revpicommander", "PLC progra&m...")) + self.act_program.setShortcut(_translate("win_revpicommander", "Ctrl+P")) + self.act_developer.setText(_translate("win_revpicommander", "PLC de&veloper...")) + self.act_developer.setShortcut(_translate("win_revpicommander", "F9")) + self.act_pictory.setText(_translate("win_revpicommander", "piCtory configuraiton...")) + self.act_disconnect.setText(_translate("win_revpicommander", "&Disconnect")) + self.act_disconnect.setShortcut(_translate("win_revpicommander", "Ctrl+X")) + self.act_reset.setText(_translate("win_revpicommander", "Reset driver...")) + self.act_simulator.setText(_translate("win_revpicommander", "Start local si&mulator...")) + +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_()) + diff --git a/include/ui/revpidevelop_ui.py b/include/ui/revpidevelop_ui.py new file mode 100644 index 0000000..59da5e8 --- /dev/null +++ b/include/ui/revpidevelop_ui.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'revpidevelop.ui' +# +# Created by: PyQt5 UI code generator 5.10.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_wid_develop(object): + def setupUi(self, wid_develop): + wid_develop.setObjectName("wid_develop") + wid_develop.resize(374, 444) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(wid_develop.sizePolicy().hasHeightForWidth()) + wid_develop.setSizePolicy(sizePolicy) + self.gridLayout = QtWidgets.QGridLayout(wid_develop) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setObjectName("gridLayout") + self.btn_all = QtWidgets.QPushButton(wid_develop) + self.btn_all.setObjectName("btn_all") + self.gridLayout.addWidget(self.btn_all, 2, 0, 1, 1) + self.btn_upload = QtWidgets.QPushButton(wid_develop) + self.btn_upload.setObjectName("btn_upload") + self.gridLayout.addWidget(self.btn_upload, 2, 1, 1, 1) + self.tree_files = QtWidgets.QTreeWidget(wid_develop) + self.tree_files.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.tree_files.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) + self.tree_files.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.tree_files.setIconSize(QtCore.QSize(24, 24)) + self.tree_files.setObjectName("tree_files") + self.tree_files.headerItem().setText(0, "1") + self.tree_files.header().setVisible(False) + self.gridLayout.addWidget(self.tree_files, 1, 0, 1, 2) + self.gb_select = QtWidgets.QGroupBox(wid_develop) + self.gb_select.setObjectName("gb_select") + self.gridLayout_2 = QtWidgets.QGridLayout(self.gb_select) + self.gridLayout_2.setObjectName("gridLayout_2") + self.lbl_select = QtWidgets.QLabel(self.gb_select) + self.lbl_select.setObjectName("lbl_select") + self.gridLayout_2.addWidget(self.lbl_select, 0, 0, 1, 1) + self.btn_select = QtWidgets.QPushButton(self.gb_select) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(":/action/ico/folder-open.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.btn_select.setIcon(icon) + self.btn_select.setIconSize(QtCore.QSize(24, 24)) + self.btn_select.setObjectName("btn_select") + self.gridLayout_2.addWidget(self.btn_select, 0, 1, 1, 1) + self.lbl_path = QtWidgets.QLabel(self.gb_select) + self.lbl_path.setObjectName("lbl_path") + self.gridLayout_2.addWidget(self.lbl_path, 1, 0, 1, 2) + self.btn_refresh = QtWidgets.QPushButton(self.gb_select) + icon1 = QtGui.QIcon() + icon1.addPixmap(QtGui.QPixmap(":/action/ico/refresh.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.btn_refresh.setIcon(icon1) + self.btn_refresh.setIconSize(QtCore.QSize(24, 24)) + self.btn_refresh.setObjectName("btn_refresh") + self.gridLayout_2.addWidget(self.btn_refresh, 0, 2, 1, 1) + self.gridLayout_2.setColumnStretch(0, 1) + self.gridLayout.addWidget(self.gb_select, 0, 0, 1, 2) + + self.retranslateUi(wid_develop) + QtCore.QMetaObject.connectSlotsByName(wid_develop) + + def retranslateUi(self, wid_develop): + _translate = QtCore.QCoreApplication.translate + self.btn_all.setText(_translate("wid_develop", "Stop / Upload / Start")) + self.btn_upload.setText(_translate("wid_develop", "Just upload")) + self.gb_select.setTitle(_translate("wid_develop", "File watcher for PLC development")) + self.lbl_select.setText(_translate("wid_develop", "Path to development root:")) + self.btn_select.setToolTip(_translate("wid_develop", "Open developer root directory")) + self.lbl_path.setText(_translate("wid_develop", "/")) + self.btn_refresh.setToolTip(_translate("wid_develop", "Reload file list")) + +from . import ressources_rc + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + wid_develop = QtWidgets.QWidget() + ui = Ui_wid_develop() + ui.setupUi(wid_develop) + wid_develop.show() + sys.exit(app.exec_()) + diff --git a/include/ui/revpiinfo_ui.py b/include/ui/revpiinfo_ui.py new file mode 100644 index 0000000..3d94c14 --- /dev/null +++ b/include/ui/revpiinfo_ui.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'revpiinfo.ui' +# +# Created by: PyQt5 UI code generator 5.10.1 +# +# WARNING! All changes made in this file will be lost! + +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.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.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) + self.btn_box.rejected.connect(diag_revpiinfo.reject) + QtCore.QMetaObject.connectSlotsByName(diag_revpiinfo) + + def retranslateUi(self, diag_revpiinfo): + _translate = QtCore.QCoreApplication.translate + diag_revpiinfo.setWindowTitle(_translate("diag_revpiinfo", "Dialog")) + self.lbl_head.setText(_translate("diag_revpiinfo", "RevPi Python PLC - Control")) + self.lbl_lbl_version_pyload.setText(_translate("diag_revpiinfo", "RevPiPyLoad version on RevPi:")) + self.lbl_version_pyload.setText(_translate("diag_revpiinfo", "0.0.0")) + self.lbl_link.setText(_translate("diag_revpiinfo", "

https://revpimodio.org/

")) + self.lbl_lbl_version_control.setText(_translate("diag_revpiinfo", "Version:")) + self.lbl_version_control.setText(_translate("diag_revpiinfo", "0.0.0")) + self.lbl_info.setText(_translate("diag_revpiinfo", "RevPiModIO, RevPiPyLoad and RevPiPyControl are community driven projects. They are all free and open source software.\n" +"All of them comes with ABSOLUTELY NO WARRANTY, to the extent permitted by\n" +"applicable law.\n" +"\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_()) + diff --git a/include/ui/revpilogfile_ui.py b/include/ui/revpilogfile_ui.py new file mode 100644 index 0000000..8a69de8 --- /dev/null +++ b/include/ui/revpilogfile_ui.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'revpilogfile.ui' +# +# Created by: PyQt5 UI code generator 5.10.1 +# +# WARNING! All changes made in this file will be lost! + +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_()) + diff --git a/include/ui/revpioption_ui.py b/include/ui/revpioption_ui.py new file mode 100644 index 0000000..ff141c0 --- /dev/null +++ b/include/ui/revpioption_ui.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'revpioption.ui' +# +# Created by: PyQt5 UI code generator 5.10.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_diag_options(object): + def setupUi(self, diag_options): + diag_options.setObjectName("diag_options") + diag_options.resize(452, 503) + 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.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_zeroonexit = QtWidgets.QCheckBox(self.gb_plc) + self.cbx_zeroonexit.setObjectName("cbx_zeroonexit") + self.gridLayout.addWidget(self.cbx_zeroonexit, 4, 1, 1, 2) + 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.cbx_autoreload = QtWidgets.QCheckBox(self.gb_plc) + self.cbx_autoreload.setObjectName("cbx_autoreload") + self.gridLayout.addWidget(self.cbx_autoreload, 1, 0, 1, 3) + 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.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.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.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.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) + self.btn_box.rejected.connect(diag_options.reject) + 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_zeroonexit.setText(_translate("diag_options", "... sucessfully without error")) + self.cbx_zeroonerror.setText(_translate("diag_options", "... after exception and errors")) + self.cbx_autostart.setText(_translate("diag_options", "Start PLC program automatically")) + self.cbx_autoreload.setText(_translate("diag_options", "Restart PLC program after exit or crash")) + 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.lbl_plc_delay.setText(_translate("diag_options", "Restart delay in seconds:")) + 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 RevPiPyControl")) + + +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_()) + diff --git a/include/ui/revpiplclist_ui.py b/include/ui/revpiplclist_ui.py new file mode 100644 index 0000000..044d7eb --- /dev/null +++ b/include/ui/revpiplclist_ui.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'revpiplclist.ui' +# +# Created by: PyQt5 UI code generator 5.10.1 +# +# WARNING! All changes made in this file will be lost! + +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(3, QtWidgets.QFormLayout.LabelRole, self.lbl_folder) + self.lbl_address = QtWidgets.QLabel(self.gb_properties) + self.lbl_address.setObjectName("lbl_address") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lbl_address) + self.lbl_port = QtWidgets.QLabel(self.gb_properties) + self.lbl_port.setObjectName("lbl_port") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.lbl_port) + self.txt_name = QtWidgets.QLineEdit(self.gb_properties) + self.txt_name.setObjectName("txt_name") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.txt_name) + self.txt_address = QtWidgets.QLineEdit(self.gb_properties) + self.txt_address.setObjectName("txt_address") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.txt_address) + self.sbx_port = QtWidgets.QSpinBox(self.gb_properties) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.sbx_port.sizePolicy().hasHeightForWidth()) + self.sbx_port.setSizePolicy(sizePolicy) + self.sbx_port.setMinimum(1) + self.sbx_port.setMaximum(65535) + self.sbx_port.setProperty("value", 55123) + self.sbx_port.setObjectName("sbx_port") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.sbx_port) + self.cbb_folder = QtWidgets.QComboBox(self.gb_properties) + self.cbb_folder.setEditable(True) + self.cbb_folder.setObjectName("cbb_folder") + self.cbb_folder.addItem("") + self.cbb_folder.setItemText(0, "") + self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.cbb_folder) + self.gridLayout.addWidget(self.gb_properties, 1, 0, 1, 2) + self.btn_box = QtWidgets.QDialogButtonBox(diag_connections) + self.btn_box.setOrientation(QtCore.Qt.Horizontal) + self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Discard|QtWidgets.QDialogButtonBox.Save) + self.btn_box.setObjectName("btn_box") + self.gridLayout.addWidget(self.btn_box, 2, 0, 1, 2) + + self.retranslateUi(diag_connections) + self.btn_box.accepted.connect(diag_connections.accept) + self.btn_box.rejected.connect(diag_connections.reject) + 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}):")) + +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_()) + diff --git a/include/ui/revpiprogram_ui.py b/include/ui/revpiprogram_ui.py new file mode 100644 index 0000000..e753aa1 --- /dev/null +++ b/include/ui/revpiprogram_ui.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'revpiprogram.ui' +# +# Created by: PyQt5 UI code generator 5.10.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_diag_program(object): + def setupUi(self, diag_program): + diag_program.setObjectName("diag_program") + diag_program.resize(488, 500) + 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.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.txt_plcarguments = QtWidgets.QLineEdit(self.gb_plc) + self.txt_plcarguments.setObjectName("txt_plcarguments") + self.gridLayout.addWidget(self.txt_plcarguments, 2, 1, 1, 2) + 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.lbl_plcprogram = QtWidgets.QLabel(self.gb_plc) + self.lbl_plcprogram.setObjectName("lbl_plcprogram") + self.gridLayout.addWidget(self.lbl_plcprogram, 0, 0, 1, 3) + 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.cbb_plcprogram = QtWidgets.QComboBox(self.gb_plc) + self.cbb_plcprogram.setObjectName("cbb_plcprogram") + self.gridLayout.addWidget(self.cbb_plcprogram, 1, 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.lbl_plcarguments = QtWidgets.QLabel(self.gb_plc) + self.lbl_plcarguments.setObjectName("lbl_plcarguments") + self.gridLayout.addWidget(self.lbl_plcarguments, 2, 0, 1, 1) + 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.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) + self.btn_box.rejected.connect(diag_program.reject) + 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_2.setText(_translate("diag_program", "Python 2")) + self.rbn_pythonversion_3.setText(_translate("diag_program", "Python 3")) + self.lbl_plcprogram.setText(_translate("diag_program", "Python PLC start program:")) + self.cbx_plcworkdir_set_uid.setText(_translate("diag_program", "Set write permissions for plc program to workdirectory")) + self.lbl_pythonversion.setText(_translate("diag_program", "Python version:")) + self.lbl_plcarguments.setText(_translate("diag_program", "Program arguments:")) + self.cb_transfair.setTitle(_translate("diag_program", "Transfair PLC program")) + self.cbb_format.setItemText(0, _translate("diag_program", "Files")) + self.cbb_format.setItemText(1, _translate("diag_program", "Folder")) + self.cbb_format.setItemText(2, _translate("diag_program", "ZIP archive")) + self.cbb_format.setItemText(3, _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_()) + diff --git a/include/ui_dev/aclmanager.ui b/include/ui_dev/aclmanager.ui new file mode 100644 index 0000000..41132da --- /dev/null +++ b/include/ui_dev/aclmanager.ui @@ -0,0 +1,258 @@ + + + diag_aclmanager + + + + 0 + 0 + 454 + 572 + + + + IP access control list + + + + + + Existing ACLs + + + + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + false + + + false + + + false + + + true + + + false + + + + IP Address + + + + + Access Level + + + + + + + + + + + 0 + 0 + + + + &Edit + + + + + + + + 0 + 0 + + + + &Remove + + + + + + + + + + + + Add / Edit access entry + + + + + + Clear fields + + + + + + + &Save entry + + + + + + + + + IP address: + + + + + + + Access level: + + + + + + + + + + + + 3 + + + + + + + . + + + + + + + 3 + + + + + + + . + + + + + + + 3 + + + + + + + . + + + + + + + 3 + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + tb_acls + btn_edit + btn_remove + txt_ip_a + txt_ip_b + txt_ip_c + txt_ip_d + cbb_level + + + + + btn_box + accepted() + diag_aclmanager + accept() + + + 247 + 538 + + + 157 + 274 + + + + + btn_box + rejected() + diag_aclmanager + reject() + + + 315 + 538 + + + 286 + 274 + + + + + diff --git a/include/ui_dev/avahisearch.ui b/include/ui_dev/avahisearch.ui new file mode 100644 index 0000000..8fe72a0 --- /dev/null +++ b/include/ui_dev/avahisearch.ui @@ -0,0 +1,145 @@ + + + diag_search + + + + 0 + 0 + 480 + 360 + + + + Search Revolution Pi devices + + + + + + + + Searching for Revolution Pi devices in your network... + + + + + + + + 0 + 0 + + + + Restart search + + + + + + + :/action/ico/reload.ico:/action/ico/reload.ico + + + + 24 + 24 + + + + + + + + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + false + + + false + + + true + + + false + + + + Zero conf Name + + + + + IP address + + + + + + + + &Connect to Revolution Pi + + + + + + + &Save connection + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + + + btn_box + rejected() + diag_search + reject() + + + 239 + 338 + + + 239 + 179 + + + + + diff --git a/include/ui_dev/debugcontrol.ui b/include/ui_dev/debugcontrol.ui new file mode 100644 index 0000000..8afa1ea --- /dev/null +++ b/include/ui_dev/debugcontrol.ui @@ -0,0 +1,126 @@ + + + wid_debugcontrol + + + + 0 + 0 + 402 + 214 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Revolution Pi devices + + + + + + + + Open to stay on top + + + + + + + IO Control + + + + + + Read all IO values and discard local changes (F4) + + + Read &all IO values + + + F4 + + + + + + + Refresh all IO values which are locally not changed (F5) + + + &Refresh unchanged IOs + + + F5 + + + + + + + Write locally changed output values to process image (F6) + + + &Write changed outputs + + + F6 + + + + + + + &Auto refresh values + + + + + + + and write outputs + + + + + + + Qt::Vertical + + + + 20 + 1 + + + + + + + + + + + + diff --git a/include/ui_dev/debugios.ui b/include/ui_dev/debugios.ui new file mode 100644 index 0000000..f9aa452 --- /dev/null +++ b/include/ui_dev/debugios.ui @@ -0,0 +1,110 @@ + + + win_debugios + + + + 0 + 0 + 434 + 343 + + + + + + + + {0}: Inputs | Outputs + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + false + + + + 0 + + + true + + + + + 0 + 0 + 201 + 275 + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 6 + + + 6 + + + + + + + 0 + + + true + + + + + 0 + 0 + 201 + 275 + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 6 + + + 6 + + + + + + + + + + + + + + + + diff --git a/include/ui_dev/ico/arrow-down.ico b/include/ui_dev/ico/arrow-down.ico new file mode 100644 index 0000000..17c2865 Binary files /dev/null and b/include/ui_dev/ico/arrow-down.ico differ diff --git a/include/ui_dev/ico/arrow-up.ico b/include/ui_dev/ico/arrow-up.ico new file mode 100644 index 0000000..21fc142 Binary files /dev/null and b/include/ui_dev/ico/arrow-up.ico differ diff --git a/include/ui_dev/ico/cpu.ico b/include/ui_dev/ico/cpu.ico new file mode 100644 index 0000000..04c1685 Binary files /dev/null and b/include/ui_dev/ico/cpu.ico differ diff --git a/include/ui_dev/ico/edit-add.ico b/include/ui_dev/ico/edit-add.ico new file mode 100644 index 0000000..4b66ec7 Binary files /dev/null and b/include/ui_dev/ico/edit-add.ico differ diff --git a/include/ui_dev/ico/edit-delete.ico b/include/ui_dev/ico/edit-delete.ico new file mode 100644 index 0000000..4845038 Binary files /dev/null and b/include/ui_dev/ico/edit-delete.ico differ diff --git a/include/ui_dev/ico/file-else.ico b/include/ui_dev/ico/file-else.ico new file mode 100644 index 0000000..7133f6c Binary files /dev/null and b/include/ui_dev/ico/file-else.ico differ diff --git a/include/ui_dev/ico/file-python.ico b/include/ui_dev/ico/file-python.ico new file mode 100644 index 0000000..4730d07 Binary files /dev/null and b/include/ui_dev/ico/file-python.ico differ diff --git a/include/ui_dev/ico/folder-open.ico b/include/ui_dev/ico/folder-open.ico new file mode 100644 index 0000000..1fde0f8 Binary files /dev/null and b/include/ui_dev/ico/folder-open.ico differ diff --git a/include/ui_dev/ico/folder.ico b/include/ui_dev/ico/folder.ico new file mode 100644 index 0000000..e9264a2 Binary files /dev/null and b/include/ui_dev/ico/folder.ico differ diff --git a/include/ui_dev/ico/refresh.ico b/include/ui_dev/ico/refresh.ico new file mode 100644 index 0000000..d995ebe Binary files /dev/null and b/include/ui_dev/ico/refresh.ico differ diff --git a/include/ui_dev/ico/reload.ico b/include/ui_dev/ico/reload.ico new file mode 100644 index 0000000..eebede6 Binary files /dev/null and b/include/ui_dev/ico/reload.ico differ diff --git a/include/ui_dev/ico/revpipycontrol.ico b/include/ui_dev/ico/revpipycontrol.ico new file mode 100644 index 0000000..4602e2f Binary files /dev/null and b/include/ui_dev/ico/revpipycontrol.ico differ diff --git a/include/ui_dev/mqttmanager.ui b/include/ui_dev/mqttmanager.ui new file mode 100644 index 0000000..3a3881e --- /dev/null +++ b/include/ui_dev/mqttmanager.ui @@ -0,0 +1,257 @@ + + + diag_mqtt + + + + 0 + 0 + 492 + 704 + + + + MQTT settings + + + + + + Base topic + + + + + + + 450 + 16777215 + + + + <html><head/><body><p>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.</p><p>For example: revpi0000/data</p></body></html> + + + true + + + + + + + Base topic: + + + + + + + + + + + + + Publish settings + + + + + + Publish all exported values every n seconds: + + + + + + + 1 + + + 21600 + + + + + + + Topic: [basetopic]/io/[ioname] + + + + + + + Send exported values immediately on value change + + + + + + + Topic: [basetopic]/event/[ioname] + + + + + + + + + + Set outputs + + + + + + + 450 + 16777215 + + + + The Revolution Pi will subscribe a topic on which your mqtt client can publish messages with the new io value as payload. + +Publish values with topic: [basetopic]/set/[outputname] + + + true + + + + + + + Allow MQTT to to set outputs on Revolution Pi + + + + + + + + + + Brocker settings + + + + + + Broker address: + + + + + + + + + + Broker port: + + + + + + + User name: + + + + + + + Password: + + + + + + + Client ID: + + + + + + + + + + + + + + + + + + 1 + + + 65535 + + + + + + + Use TLS + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + btn_box + accepted() + diag_mqtt + accept() + + + 248 + 254 + + + 157 + 274 + + + + + btn_box + rejected() + diag_mqtt + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/include/ui_dev/ressources.qrc b/include/ui_dev/ressources.qrc new file mode 100644 index 0000000..759beb6 --- /dev/null +++ b/include/ui_dev/ressources.qrc @@ -0,0 +1,20 @@ + + + ico/cpu.ico + ico/folder.ico + ico/revpipycontrol.ico + + + ico/file-else.ico + ico/file-python.ico + + + ico/folder-open.ico + ico/refresh.ico + ico/reload.ico + ico/arrow-down.ico + ico/arrow-up.ico + ico/edit-add.ico + ico/edit-delete.ico + + diff --git a/include/ui_dev/revpicommander.ui b/include/ui_dev/revpicommander.ui new file mode 100644 index 0000000..7e3b8cc --- /dev/null +++ b/include/ui_dev/revpicommander.ui @@ -0,0 +1,264 @@ + + + Sven Sager + win_revpicommander + + + + 0 + 0 + 318 + 273 + + + + RevPi Python PLC Commander + + + + :/main/ico/revpipycontrol.ico:/main/ico/revpipycontrol.ico + + + + + + + Qt::NoFocus + + + true + + + + + + + PLC &start + + + + + + + PLC s&top + + + + + + + PLC restart + + + + + + + PLC &logs + + + + + + + Qt::NoFocus + + + Qt::AlignCenter + + + true + + + + + + + + 300 + 0 + + + + PLC watch &mode + + + true + + + + + + + + + 0 + 0 + 318 + 22 + + + + + &File + + + + + + + + + + + &Help + + + + + + + + &PLC + + + + + + + + + + + + + + &Connections + + + + + + + + + + false + + + + + &Connections... + + + + + &Search Revolution Pi... + + + Ctrl+F + + + + + &Quit + + + + + Visit &webpage... + + + + + &Info... + + + + + PLC &logs... + + + F3 + + + + + PLC &options... + + + Ctrl+O + + + + + PLC progra&m... + + + Ctrl+P + + + + + true + + + PLC de&veloper... + + + F9 + + + + + piCtory configuraiton... + + + + + &Disconnect + + + Ctrl+X + + + + + Reset driver... + + + + + Start local si&mulator... + + + + + btn_plc_start + btn_plc_stop + btn_plc_restart + btn_plc_logs + btn_plc_debug + + + + + + + act_quit + triggered() + win_revpicommander + close() + + + -1 + -1 + + + 159 + 136 + + + + + diff --git a/include/ui_dev/revpidevelop.ui b/include/ui_dev/revpidevelop.ui new file mode 100644 index 0000000..e767f88 --- /dev/null +++ b/include/ui_dev/revpidevelop.ui @@ -0,0 +1,136 @@ + + + wid_develop + + + + 0 + 0 + 374 + 444 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Stop / Upload / Start + + + + + + + Just upload + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::MultiSelection + + + QAbstractItemView::SelectRows + + + + 24 + 24 + + + + false + + + + 1 + + + + + + + + File watcher for PLC development + + + + + + Path to development root: + + + + + + + Open developer root directory + + + + :/action/ico/folder-open.ico:/action/ico/folder-open.ico + + + + 24 + 24 + + + + + + + + / + + + + + + + Reload file list + + + + :/action/ico/refresh.ico:/action/ico/refresh.ico + + + + 24 + 24 + + + + + + + + + + + + + + diff --git a/include/ui_dev/revpiinfo.ui b/include/ui_dev/revpiinfo.ui new file mode 100644 index 0000000..28a89fe --- /dev/null +++ b/include/ui_dev/revpiinfo.ui @@ -0,0 +1,187 @@ + + + diag_revpiinfo + + + + 0 + 0 + 627 + 398 + + + + Dialog + + + + + + + 20 + 75 + true + + + + RevPi Python PLC - Control + + + Qt::AlignCenter + + + + + + + + 75 + true + + + + RevPiPyLoad version on RevPi: + + + + + + + + + + + 75 + true + + + + 0.0.0 + + + Qt::AlignCenter + + + + + + + <html><head/><body><p><a href="https://revpimodio.org/"><span style=" text-decoration: underline; color:#0000ff;">https://revpimodio.org/</span></a></p></body></html> + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + + 14 + + + + Version: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 14 + + + + 0.0.0 + + + + + + + + + + 300 + 0 + + + + RevPiModIO, RevPiPyLoad and RevPiPyControl are community driven projects. They are all free and open source software. +All of them comes with ABSOLUTELY NO WARRANTY, to the extent permitted by +applicable law. + +(c) Sven Sager, License: GPLv3 + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + btn_box + accepted() + diag_revpiinfo + accept() + + + 248 + 254 + + + 157 + 274 + + + + + btn_box + rejected() + diag_revpiinfo + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/include/ui_dev/revpilogfile.ui b/include/ui_dev/revpilogfile.ui new file mode 100644 index 0000000..6d837f0 --- /dev/null +++ b/include/ui_dev/revpilogfile.ui @@ -0,0 +1,127 @@ + + + win_revpilogfile + + + + 0 + 0 + 796 + 347 + + + + RevPi Python PLC Logfiles + + + + + + + Stay on top of all windows + + + + + + + Linewrap + + + + + + + Qt::Horizontal + + + false + + + + + + + RevPiPyLoad - Logfile + + + + + + + Clear view + + + + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOn + + + true + + + false + + + QPlainTextEdit::NoWrap + + + true + + + + + + + + + + + Clear view + + + + + + + Python PLC program - Logfile + + + + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOn + + + true + + + false + + + QPlainTextEdit::NoWrap + + + true + + + + + + + + + + + + + diff --git a/include/ui_dev/revpioption.ui b/include/ui_dev/revpioption.ui new file mode 100644 index 0000000..88d2eb3 --- /dev/null +++ b/include/ui_dev/revpioption.ui @@ -0,0 +1,250 @@ + + + diag_options + + + + 0 + 0 + 452 + 503 + + + + RevPi Python PLC Options + + + + + + Start / Stop behavior of PLC program + + + + + + Replace IO file: + + + + + + + 5 + + + 120 + + + + + + + ... sucessfully without error + + + + + + + ... after exception and errors + + + + + + + Start PLC program automatically + + + + + + + Restart PLC program after exit or crash + + + + + + + Set process image to NULL if program terminates... + + + + + + + + + + + Do not use replace io file + + + + + Use static file from RevPiPyLoad + + + + + Use dynamic file from work directory + + + + + Give own path and filename + + + + + + + + Restart delay in seconds: + + + + + + + + + + RevPiPyLoad server services + + + + + + Edit ACL + + + + + + + MQTT process image publisher + + + + + + + Start RevPi piControl server + + + + + + + status + + + Qt::AlignCenter + + + + + + + piControl server is: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + status + + + Qt::AlignCenter + + + + + + + MQTT publish service is: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Settings + + + + + + + Edit ACL + + + + + + + Activate XML-RPC for RevPiPyControl + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + btn_box + accepted() + diag_options + accept() + + + 248 + 254 + + + 157 + 274 + + + + + btn_box + rejected() + diag_options + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/include/ui_dev/revpiplclist.ui b/include/ui_dev/revpiplclist.ui new file mode 100644 index 0000000..ec6e78b --- /dev/null +++ b/include/ui_dev/revpiplclist.ui @@ -0,0 +1,218 @@ + + + diag_connections + + + + 0 + 0 + 520 + 508 + + + + Revolution Pi connections + + + + + + QAbstractItemView::NoEditTriggers + + + + Connection name + + + + + Address + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + :/action/ico/arrow-up.ico:/action/ico/arrow-up.ico + + + + + + + + + + + :/action/ico/arrow-down.ico:/action/ico/arrow-down.ico + + + + + + + + + + + :/action/ico/edit-delete.ico:/action/ico/edit-delete.ico + + + + + + + + + + + :/action/ico/edit-add.ico:/action/ico/edit-add.ico + + + + + + + + + Connection properties + + + + + + Display name: + + + + + + + Sub folder: + + + + + + + Address (DNS/IP): + + + + + + + Port (Default {0}): + + + + + + + + + + + + + + 0 + 0 + + + + 1 + + + 65535 + + + 55123 + + + + + + + true + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Discard|QDialogButtonBox::Save + + + + + + + + + + + btn_box + accepted() + diag_connections + accept() + + + 248 + 254 + + + 157 + 274 + + + + + btn_box + rejected() + diag_connections + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/include/ui_dev/revpiprogram.ui b/include/ui_dev/revpiprogram.ui new file mode 100644 index 0000000..78ed5de --- /dev/null +++ b/include/ui_dev/revpiprogram.ui @@ -0,0 +1,248 @@ + + + diag_program + + + + 0 + 0 + 488 + 500 + + + + PLC program + + + + + + PLC program + + + + + + Python 2 + + + + + + + + + + Python 3 + + + + + + + Python PLC start program: + + + + + + + Set write permissions for plc program to workdirectory + + + + + + + + + + Python version: + + + + + + + Program arguments: + + + + + + + + + + Transfair PLC program + + + + + + + Files + + + + + Folder + + + + + ZIP archive + + + + + TGZ archive + + + + + + + + Upload + + + + + + + Download + + + + + + + Transfair format: + + + + + + + Including piCtory configuration + + + + + + + Remove all files on Revolution Pi before upload + + + + + + + + + + Control files + + + + + + Download + + + + + + + Download + + + + + + + Upload + + + + + + + piCtory configuraiton + + + + + + + Process image from piControl0 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + cbb_plcprogram + txt_plcarguments + rbn_pythonversion_2 + rbn_pythonversion_3 + cbx_plcworkdir_set_uid + cbb_format + cbx_pictory + cbx_clear + btn_program_download + btn_program_upload + btn_pictory_download + btn_pictory_upload + btn_procimg_download + + + + + btn_box + accepted() + diag_program + accept() + + + 257 + 490 + + + 157 + 274 + + + + + btn_box + rejected() + diag_program + reject() + + + 325 + 490 + + + 286 + 274 + + + + + diff --git a/make.bat b/make.bat new file mode 100644 index 0000000..948043c --- /dev/null +++ b/make.bat @@ -0,0 +1,5 @@ +@echo off +pyinstaller --clean -D --windowed ^ + --add-data="data\revpipycontrol.ico;." ^ + --icon=data\\revpipycontrol.ico ^ + revpipycontrol\revpipycontrol.py diff --git a/revpicommander/aclmanager.py b/revpicommander/aclmanager.py new file mode 100644 index 0000000..88a15f2 --- /dev/null +++ b/revpicommander/aclmanager.py @@ -0,0 +1,430 @@ +# -*- coding: utf-8 -*- +u"""Manager for ACL lists.""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2018 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): + super(AclManager, self).__init__(parent) + self.setupUi(self) + self.setFixedSize(self.size()) + + self.__re_ipacl = compile(r"([\d*]{1,3}\.){3}[\d*]{1,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 ") + if type(acl_texts) != dict: + raise TypeError("acl_texts must be ") + + 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 + :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() + ) diff --git a/revpicommander/avahisearch.py b/revpicommander/avahisearch.py new file mode 100644 index 0000000..b31d3f2 --- /dev/null +++ b/revpicommander/avahisearch.py @@ -0,0 +1,232 @@ +# -*- coding: utf-8 -*- +"""Revolution Pi search with zeroconf.""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2020 Sven Sager" +__license__ = "GPLv3" + +from os import name as osname +from re import compile +from socket import gethostbyname + +from PyQt5 import QtCore, QtGui, QtWidgets +from zeroconf import ServiceBrowser, Zeroconf + +import helper +import proginit as pi +from helper import WidgetData +from ui.avahisearch_ui import Ui_diag_search + + +class AvahiSearchThread(QtCore.QThread): + """Search thread for Revolution Pi with installed RevPiPyLoad.""" + added = QtCore.pyqtSignal(str, str, int, str, str) + removed = QtCore.pyqtSignal(str, str) + + def __init__(self, parent=None): + super(AvahiSearchThread, self).__init__(parent) + self._cycle_wait_ms = 1000 + + self.__dict_arp = {} + self.re_posix = compile( + r"(?P(\d{1,3}\.){3}\d{1,3}).*" + r"(?P([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})" + ) + + def _update_arp(self): + 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): + return self.__dict_arp.get(ip, None) + + def remove_service(self, zeroconf: Zeroconf, conf_type: str, name: str): + """Revolution Pi disappeared.""" + pi.logger.debug("AvahiSearchThread.remove_service") + self.removed.emit(name, conf_type) + + def add_service(self, zeroconf: Zeroconf, conf_type: str, name: str): + """New Revolution Pi found.""" + pi.logger.debug("AvahiSearchThread.add_service") + info = zeroconf.get_service_info(conf_type, name) + if not info: + return + + try: + ip = gethostbyname(info.server) + except Exception: + ip = self.tr("N/A") + self.added.emit(name, info.server, info.port, conf_type, ip) + + def run(self): + pi.logger.debug("Started zero conf discovery.") + zeroconf = Zeroconf() + revpi_browser = ServiceBrowser(zeroconf, "_revpipyload._tcp.local.", self) + while not self.isInterruptionRequested(): + # Just hanging around :) + self.msleep(self._cycle_wait_ms) + zeroconf.close() + pi.logger.debug("Stopped zero conf discovery.") + + +class AvahiSearch(QtWidgets.QDialog, Ui_diag_search): + + def __init__(self, parent=None): + super(AvahiSearch, self).__init__(parent) + self.setupUi(self) + self.setFixedSize(self.size()) + + self.connect_index = -1 + self.th_zero_conf = AvahiSearchThread(self) + + self.tb_revpi.setColumnWidth(0, 250) + self.btn_connect.setEnabled(False) + self.btn_save.setEnabled(False) + + def _restart_search(self): + """Clean up an restart search thread.""" + while self.tb_revpi.rowCount() > 0: + self.tb_revpi.removeRow(0) + self.th_zero_conf.requestInterruption() + + self.th_zero_conf = AvahiSearchThread(self) + self.th_zero_conf.added.connect(self.on_avahi_added) + self.th_zero_conf.removed.connect(self.on_avahi_removed) + self.th_zero_conf.start() + + def _save_connection(self, row: int, no_warn=False): + """ + Save the connection from given row to settings. + + :param row: Row with connection data + :param no_warn: If True, no message boxes will appear + :return: Array index of connection (found or saved) + """ + item = self.tb_revpi.item(row, 0) + if not item: + return + + folder_name = self.tr("Auto discovered") + selected_name = item.text() + selected_address = item.data(WidgetData.address) + selected_port = item.data(WidgetData.port) + i = 0 + for i in range(helper.settings.beginReadArray("connections")): + helper.settings.setArrayIndex(i) + + name = helper.settings.value("name", type=str) + address = helper.settings.value("address", type=str) + port = helper.settings.value("port", type=int) + if address.lower() == selected_address.lower() and port == selected_port: + if not no_warn: + QtWidgets.QMessageBox.information( + self, self.tr("Already in list..."), self.tr( + "The selected Revolution Pi is already saved in your " + "connection list as '{0}.".format(name) + ) + ) + helper.settings.endArray() + return i + + helper.settings.endArray() + helper.settings.beginWriteArray("connections") + + helper.settings.setArrayIndex(i + 1) + helper.settings.setValue("address", selected_address) + helper.settings.setValue("folder", folder_name) + helper.settings.setValue("name", selected_name) + helper.settings.setValue("port", selected_port) + + helper.settings.endArray() + + if not no_warn: + QtWidgets.QMessageBox.information( + self, self.tr("Success"), self.tr( + "The connection with the name '{0}' was successfully saved " + "to folder '{1}' in your connections.".format(selected_name, folder_name) + ) + ) + + return i + 1 + + def exec(self) -> int: + self._restart_search() + rc = super(AvahiSearch, self).exec() + self.th_zero_conf.requestInterruption() + return rc + + @QtCore.pyqtSlot(str, str, int, str, str) + def on_avahi_added(self, name: str, server: str, port: int, conf_type: str, ip: str): + """New Revolution Pi found.""" + index = -1 + for i in range(self.tb_revpi.rowCount()): + if self.tb_revpi.item(i, 0).data(WidgetData.object_name) == name: + index = i + break + + if index == -1: + # New Row + item_name = QtWidgets.QTableWidgetItem() + item_ip = QtWidgets.QTableWidgetItem() + + index = self.tb_revpi.rowCount() + self.tb_revpi.insertRow(index) + self.tb_revpi.setItem(index, 0, item_name) + self.tb_revpi.setItem(index, 1, item_ip) + else: + # Update row + item_name = self.tb_revpi.item(index, 0) + item_ip = self.tb_revpi.item(index, 1) + + item_name.setIcon(QtGui.QIcon(":/main/ico/cpu.ico")) + item_name.setText(server[:-1]) + item_name.setData(WidgetData.object_name, name) + item_name.setData(WidgetData.address, server[:-1]) + item_name.setData(WidgetData.port, port) + item_ip.setText(ip) + + @QtCore.pyqtSlot(str, str) + def on_avahi_removed(self, name: str, conf_type: str): + """Revolution Pi disappeared.""" + for i in range(self.tb_revpi.rowCount()): + if self.tb_revpi.item(i, 0).data(WidgetData.object_name) == name: + self.tb_revpi.removeRow(i) + break + + @QtCore.pyqtSlot(int, int) + def on_tb_revpi_cellDoubleClicked(self, row: int, column: int): + """Connect to double clicked Revolution Pi.""" + pi.logger.debug("AvahiSearch.on_tb_revpi_cellDoubleClicked") + self.connect_index = self._save_connection(row, no_warn=True) + self.accept() + + @QtCore.pyqtSlot(int, int, int, int) + def on_tb_revpi_currentCellChanged(self, row: int, column: int, last_row: int, last_column: int): + """Manage state of buttons.""" + self.btn_connect.setEnabled(row >= 0) + self.btn_save.setEnabled(row >= 0) + + @QtCore.pyqtSlot() + def on_btn_connect_pressed(self): + """Connect to selected Revolution Pi.""" + pi.logger.debug("AvahiSearch.on_btn_connect_pressed") + if self.tb_revpi.currentRow() == -1: + return + self.connect_index = self._save_connection(self.tb_revpi.currentRow(), no_warn=True) + self.accept() + + @QtCore.pyqtSlot() + def on_btn_save_pressed(self): + """Save selected Revolution Pi.""" + pi.logger.debug("AvahiSearch.on_btn_save_pressed") + if self.tb_revpi.currentRow() == -1: + return + self.connect_index = self._save_connection(self.tb_revpi.currentRow()) + + @QtCore.pyqtSlot() + def on_btn_restart_pressed(self): + """Clean up an restart search thread.""" + self._restart_search() diff --git a/revpicommander/debugcontrol.py b/revpicommander/debugcontrol.py new file mode 100644 index 0000000..c27845f --- /dev/null +++ b/revpicommander/debugcontrol.py @@ -0,0 +1,436 @@ +# -*- coding: utf-8 -*- +"""Debug control widget to append to main window.""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2020 Sven Sager" +__license__ = "GPLv3" + +import pickle +from xmlrpc.client import Binary, Fault, MultiCall, MultiCallIterator + +from PyQt5 import QtCore, QtWidgets + +import helper +import proginit as pi +from debugios import DebugIos +from ui.debugcontrol_ui import Ui_wid_debugcontrol + + +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 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', bitlength, byte_address, 'bmk', bitaddress, 'byteorder', signed] + value_procimg = 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 self.driver_reset_detected: + # Change last value to save the actual states of outputs + win.set_last_value(io[0], value_procimg) + + 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 not 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() diff --git a/revpicommander/debugios.py b/revpicommander/debugios.py new file mode 100644 index 0000000..0021afa --- /dev/null +++ b/revpicommander/debugios.py @@ -0,0 +1,310 @@ +# -*- coding: utf-8 -*- +"""One device of the Revolution Pi.""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2020 Sven Sager" +__license__ = "GPLv3" + +import struct + +from PyQt5 import QtCore, QtGui, QtWidgets + +import helper +import proginit as pi +from ui.debugios_ui import Ui_win_debugios + + +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() + + 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.position = position + self.name = name + self.inputs = inputs.copy() + self.outputs = outputs.copy() + self.search_class = (QtWidgets.QLineEdit, QtWidgets.QDoubleSpinBox, QtWidgets.QCheckBox) + 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) + + def _calc_min_max(self, 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 + 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] + + if container.findChild(self.search_class, name) is not None: + # Check properties of this IO + 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.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + + # 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("big_endian", byteorder == "big") + val.setProperty("byte_length", byte_length) + val.setProperty("signed", signed) + val.setProperty("struct_type", struct_type) + val.setProperty("frm", "{0}{1}".format( + ">" if val.property("big_endian") 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) + 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) + + 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()) + + 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() + )) + + 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) + """ + child = self.findChild(self.search_class, 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: + return actual_value.encode(), last_value.encode() + else: + return actual_value, last_value + + def set_last_value(self, io_name: str, value): + """ + Set last value for widget to sync after driver reset. + + :param io_name: Name of IO + :param value: Process value as or + """ + child = self.findChild(self.search_class, io_name) + if child.property("frm"): + value = struct.unpack(child.property("frm"), value)[0] + elif type(value) == bytearray: + value = value.decode() + + child.setProperty("last_value", value) + + def set_value(self, io_name: str, value): + """ + 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 + """ + child = self.findChild(self.search_class, io_name) + if child.property("frm"): + value = struct.unpack(child.property("frm"), value)[0] + elif type(value) == bytearray: + value = value.decode() + + child.setProperty("last_value", value) + child.setValue(value) diff --git a/revpicommander/helper.py b/revpicommander/helper.py new file mode 100644 index 0000000..b6977bb --- /dev/null +++ b/revpicommander/helper.py @@ -0,0 +1,462 @@ +# -*- coding: utf-8 -*- +"""Helper functions for this application.""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2020 Sven Sager" +__license__ = "GPLv3" + +import pickle +import socket +from enum import IntEnum +from http.client import CannotSendRequest +from os import environ, remove +from queue import Queue +from threading import Lock +from xmlrpc.client import Binary, ServerProxy + +from PyQt5 import QtCore + +import proginit as pi + + +class WidgetData(IntEnum): + address = 260 + replace_ios_config = 261 + acl_level = 262 + has_error = 263 + port = 264 + object_name = 265 + 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 .""" + 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 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") + name = settings.value("name") + port = settings.value("port", defaultValue=55123) + + 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)) + self.program_last_zip_file = settings.value("last_zip_file", "{0}.zip".format(name)) + 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 = list(map(int, sp.version().split("."))) + xml_funcs = sp.system.listMethods() + xml_mode = sp.xmlmodus() + except Exception as 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(5) + 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() + 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): + """Start the simulator for piControl on local computer.""" + pi.logger.debug("ConnectionManager.start_simulate") + + with open(procimg, "wb") as fh: + fh.write(b'\x00' * 4096) + + try: + import revpimodio2 + + # Prepare process image with default values + 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"] + + except Exception as e: + pi.logger.exception(e) + self._revpi_output = None + self._revpi = None + 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") + 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("SIMULATING", "yellow") + elif self._cli is None: + sp = None + self.status_changed.emit("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)) + + 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("SERVER ERROR", "red") + self.connection_error_observed.emit("{0} | {1}".format(e, type(e))) + else: + if plc_exit_code == -1: + self.status_changed.emit("RUNNING", "green") + elif plc_exit_code == -2: + self.status_changed.emit("FILE NOT FOUND", "red") + elif plc_exit_code == -3: + self.status_changed.emit("NOT RUNNING (NO STATUS)", "yellow") + elif plc_exit_code == -9: + self.status_changed.emit("PROGRAM KILLED", "red") + elif plc_exit_code == -15: + self.status_changed.emit("PROGRAM TERMED", "red") + elif plc_exit_code == 0: + self.status_changed.emit("NOT RUNNING", "yellow") + else: + self.status_changed.emit("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): + """True if we have an active connection.""" + return self._cli is not None + + @property + def simulating(self): + """True, if simulating mode is running.""" + return self._revpi is not None + + +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.""" diff --git a/revpicommander/locale/revpicommander_de.qm b/revpicommander/locale/revpicommander_de.qm new file mode 100644 index 0000000..4addce5 Binary files /dev/null and b/revpicommander/locale/revpicommander_de.qm differ diff --git a/revpicommander/locale/revpicommander_de.ts b/revpicommander/locale/revpicommander_de.ts new file mode 100644 index 0000000..f0e4183 --- /dev/null +++ b/revpicommander/locale/revpicommander_de.ts @@ -0,0 +1,1569 @@ + + + + + AclManager + + + Error + + + + + There are errors in the ACL list! +Check 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. + + + + + Unsaved entry + + + + + You worked on a new ACL entry. Do you want to save that entry, too? + + + + + Question + + + + + Do you really want to quit? +Unsaved changes will be lost + + + + + Select... + + + + + Level + + + + + This entry has an invalid ACL level or wrong IP format! + + + + + Do you really want to delete the following items? +{0} + + + + + Can not save new ACL entry! Check format of ip address and acl level is in value list. + + + + + AvahiSearch + + + Auto discovered + + + + + Already in list... + + + + + The selected Revolution Pi is already saved in your connection list as '{0}. + + + + + Success + + + + + The connection with the name '{0}' was successfully saved to folder '{1}' in your connections. + + + + + AvahiSearchThread + + + N/A + + + + + DebugControl + + + Driver reset for piControl detected. + + + + + Error while getting values from Revolution Pi. + + + + + Auto update values... + + + + + Values updated... + + + + + Error set value of device '{0}' Output '{1}': {2} + + + + + + Error + + + + + DebugIos + + + signed + + + + + big_endian + + + + + MqttManager + + + Question + + + + + Do you really want to quit? +Unsaved changes will be lost! + + + + + Error + + + + + Can not load the MQTT settings dialog. Missing values! + + + + + RevPiCommander + + + Select downloaded piCtory file... + + + + + piCtory file (*.rsc);;All files (*.*) + + + + + Simulator started... + + + + + The simulator is running! + +You can work with this simulator if your call RevPiModIO with this additional parameters: +procimg={procimg} +configrsc={config} + +You can copy that from header textbox. + + + + + Can not start... + + + + + Can not start the simulator! Maybe the piCtory file is corrupt or you can not write to the location '{0}'. + + + + + Warning + + + + + This version of Logviewer ist not supported in version {0} of RevPiPyLoad on your RevPi! You need at least version 0.4.1. + + + + + XML-RPC access mode in the RevPiPyLoad configuration is too small to access this dialog! + + + + + Error + + + + + 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! + + + + + Question + + + + + Are you sure to reset piControl? +The pictory configuration will be reloaded. During that time the process image will be interrupted and could rise errors on running control programs! + + + + + Success + + + + + piControl reset executed successfully + + + + + piControl reset could not be executed successfully + + + + + Can not connect to RevPi XML-RPC Service! + +This could have the following reasons: The RevPi is not online, the XML-RPC service is not running or the ACL permission is not set for your IP!!! + + + + + Reset to piCtory defaults... + + + + + Do you want to reset your process image to piCtory default values? +You have to stop other RevPiModIO programs before doing that, because they could reset the outputs. + + + + + 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. + + + + + Can not load this function, because your ACL level is to low! +You need at least level 1 to read or level 3 to write. + + + + + Can not load piCtory configuration. +Did you create a hardware configuration? Please check this in piCtory! + + + + + RevPiDevelop + + + Please select... + + + + + Error + + + + + Can not stop plc program on Revolution Pi. + + + + + Information... + + + + + A PLC program has been uploaded. Please check the PLC options to see if the correct program is specified as the start program. + + + + + The Revolution Pi could not process some parts of the transmission. + + + + + Errors occurred during transmission + + + + + Warning + + + + + Could not start the plc program on Revolution Pi. + + + + + Stop scanning for files, because we found more than {0} files. + + + + + Select folder... + + + + + Can not access the folder '{0}' to read files. + + + + + RevPiInfo + + + Can not load file list + + + + + Not connected + + + + + RevPiLogfile + + + Can not access log file on the RevPi + + + + + RevPiOption + + + Question + + + + + The settings will be set on the Revolution Pi now. + +ACL changes and service settings are applied immediately. + + + + + Error + + + + + The settings could not be saved on the Revolution Pi! +Try to save the values one mor time and check the log files of RevPiPyLoad if the error rises again. + + + + + Do you really want to quit? +Unsaved changes will be lost. + + + + + running + + + + + stopped + + + + + The MQTT service is not available on your RevPiPyLoad version. + + + + + read only + + + + + read and write + + + + + 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! + + + + + Start/Stop PLC program and read logs + + + + + + read IOs in watch mode + + + + + + read properties and download PLC program + + + + + + upload PLC program + + + + + + set properties + + + + + RevPiPlcList + + + New connection + + + + + Question + + + + + Do you really want to quit? +Unsaved changes will be lost. + + + + + RevPiProgram + + + Error + + + + + You have to select a start program, before uploading the settings. + + + + + Question + + + + + The settings will be set on the Revolution Pi now. + +If you made changes on the 'PCL Program' section, your plc program will restart now! + + + + + The settings could not be saved on the Revolution Pi! +Try to save the values one mor time and check the log files of RevPiPyLoad if the error rises again. + + + + + Do you really want to quit? +Unsaved changes will be lost. + + + + + Reset driver... + + + + + Reset piControl driver after successful uploading new piCtory configuration? +The process image will be interrupted for a short time! + + + + + Got an network error while send data to Revolution Pi. +Please try again. + + + + + Success + + + + + The transfer of the piCtory configuration and the reset of piControl have been successfully executed. + + + + + The piCtory configuration was successfully transferred. + + + + + Can not process the transferred file. + + + + + Can not find main elements in piCtory file. + + + + + Contained devices could not be found on Revolution Pi. The configuration may be from a newer piCtory version! + + + + + Could not load RAP catalog on Revolution Pi. + + + + + The piCtory configuration could not be written on the Revolution Pi. + + + + + Warning + + + + + The piCtroy configuration has been saved successfully. +An error occurred on piControl reset! + + + + + Select folder... + + + + + Save ZIP archive... + + + + + ZIP archive (*.zip);;All files (*.*) + + + + + Save TGZ archive... + + + + + TGZ archive (*.tgz);;All files (*.*) + + + + + Could not load PLC program from Revolution Pi. + + + + + Coud not save the archive or extract the files! +Please retry. + + + + + Transfer successfully completed. + + + + + Upload plc files... + + + + + Python file (*.py);;Config file (*.conf);;;JSON file (*.json);;All files (*.*) + + + + + Select folder to upload... + + + + + Upload content of ZIP archive... + + + + + The selected file ist not a ZIP archive. + + + + + Upload content of TAR archive... + + + + + TAR archive (*.tgz);;All files (*.*) + + + + + The selected file ist not a TAR archive. + + + + + No files to upload... + + + + + Found no files to upload in given location or archive. + + + + + There was an error deleting the files on the Revolution Pi. +Upload aborted! Please try again. + + + + + The PLC program was transferred successfully. + + + + + Information + + + + + Could not find the selected PLC start program in uploaded files. +This is not an error, if the file was already on the Revolution Pi. Check PLC start program field + + + + + There is no piCtory configuration in this archive. + + + + + The Revolution Pi could not process some parts of the transmission. + + + + + Errors occurred during transmission. + + + + + Save piCtory file... + + + + + piCtory file (*.rsc);;All files (*.*) + + + + + Could not load piCtory file from Revolution Pi. + + + + + piCtory configuration successfully loaded and saved to: +{0}. + + + + + Upload piCtory file... + + + + + Save piControl file... + + + + + Process image file (*.img);;All files (*.*) + + + + + Could not load process image from Revolution Pi. + + + + + Process image successfully loaded and saved to: +{0}. + + + + + diag_aclmanager + + + IP access control list + + + + + Existing ACLs + + + + + IP Address + + + + + Access Level + + + + + &Edit + + + + + &Remove + + + + + Add / Edit access entry + + + + + Clear fields + + + + + &Save entry + + + + + IP address: + + + + + Access level: + + + + + . + + + + + diag_connections + + + Revolution Pi connections + + + + + Connection name + + + + + Address + + + + + Connection properties + + + + + Display name: + + + + + Sub folder: + + + + + Address (DNS/IP): + + + + + Port (Default {0}): + + + + + diag_mqtt + + + MQTT settings + + + + + Base topic + + + + + <html><head/><body><p>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.</p><p>For example: revpi0000/data</p></body></html> + + + + + Base topic: + + + + + Publish settings + + + + + Publish all exported values every n seconds: + + + + + Topic: [basetopic]/io/[ioname] + + + + + Send exported values immediately on value change + + + + + Topic: [basetopic]/event/[ioname] + + + + + Set outputs + + + + + The Revolution Pi will subscribe a topic on which your mqtt client can publish messages with the new io value as payload. + +Publish values with topic: [basetopic]/set/[outputname] + + + + + Allow MQTT to to set outputs on Revolution Pi + + + + + Brocker settings + + + + + Broker address: + + + + + Broker port: + + + + + User name: + + + + + Password: + + + + + Client ID: + + + + + Use TLS + + + + + diag_options + + + RevPi Python PLC Options + + + + + Start / Stop behavior of PLC program + + + + + Replace IO file: + + + + + ... sucessfully without error + + + + + ... after exception and errors + + + + + Start PLC program automatically + + + + + Restart PLC program after exit or crash + + + + + Set process image to NULL if program terminates... + + + + + Do not use replace io file + + + + + Use static file from RevPiPyLoad + + + + + Use dynamic file from work directory + + + + + Give own path and filename + + + + + Restart delay in seconds: + + + + + RevPiPyLoad server services + + + + + Edit ACL + + + + + MQTT process image publisher + + + + + Start RevPi piControl server + + + + + status + + + + + piControl server is: + + + + + MQTT publish service is: + + + + + Settings + + + + + Activate XML-RPC for RevPiPyControl + + + + + diag_program + + + PLC program + + + + + Python 2 + + + + + Python 3 + + + + + Python PLC start program: + + + + + Set write permissions for plc program to workdirectory + + + + + Python version: + + + + + Program arguments: + + + + + Transfair PLC program + + + + + Files + + + + + Folder + + + + + ZIP archive + + + + + TGZ archive + + + + + Upload + + + + + Download + + + + + Transfair format: + + + + + Including piCtory configuration + + + + + Remove all files on Revolution Pi before upload + + + + + Control files + + + + + piCtory configuraiton + + + + + Process image from piControl0 + + + + + diag_revpiinfo + + + Dialog + + + + + RevPi Python PLC - Control + + + + + RevPiPyLoad version on RevPi: + + + + + 0.0.0 + + + + + <html><head/><body><p><a href="https://revpimodio.org/"><span style=" text-decoration: underline; color:#0000ff;">https://revpimodio.org/</span></a></p></body></html> + + + + + Version: + + + + + RevPiModIO, RevPiPyLoad and RevPiPyControl are community driven projects. They are all free and open source software. +All of them comes with ABSOLUTELY NO WARRANTY, to the extent permitted by +applicable law. + +(c) Sven Sager, License: GPLv3 + + + + + diag_search + + + Search Revolution Pi devices + + + + + Searching for Revolution Pi devices in your network... + + + + + Restart search + + + + + Zero conf Name + + + + + IP address + + + + + &Connect to Revolution Pi + + + + + &Save connection + + + + + wid_debugcontrol + + + Revolution Pi devices + + + + + Open to stay on top + + + + + IO Control + + + + + Read all IO values and discard local changes (F4) + + + + + Read &all IO values + + + + + F4 + + + + + Refresh all IO values which are locally not changed (F5) + + + + + &Refresh unchanged IOs + + + + + F5 + + + + + Write locally changed output values to process image (F6) + + + + + &Write changed outputs + + + + + F6 + + + + + &Auto refresh values + + + + + and write outputs + + + + + wid_develop + + + Stop / Upload / Start + + + + + Just upload + + + + + File watcher for PLC development + + + + + Path to development root: + + + + + Open developer root directory + + + + + / + + + + + Reload file list + + + + + win_debugios + + + {0}: Inputs | Outputs + + + + + win_revpicommander + + + RevPi Python PLC Commander + + + + + PLC &start + + + + + PLC s&top + + + + + PLC restart + + + + + PLC &logs + + + + + PLC watch &mode + + + + + &File + &Datei + + + + &Help + &Hilfe + + + + &PLC + + + + + &Connections + &Verbindungen + + + + &Connections... + &Verbindungen... + + + + &Search Revolution Pi... + &Suche Revolution Pi... + + + + Ctrl+F + + + + + &Quit + &Beenden + + + + Visit &webpage... + &Webseite besuchen... + + + + &Info... + + + + + PLC &logs... + + + + + F3 + + + + + PLC &options... + + + + + Ctrl+O + + + + + PLC progra&m... + + + + + Ctrl+P + + + + + PLC de&veloper... + + + + + F9 + + + + + piCtory configuraiton... + + + + + &Disconnect + + + + + Ctrl+X + + + + + Reset driver... + + + + + Start local si&mulator... + + + + + win_revpilogfile + + + RevPi Python PLC Logfiles + + + + + Stay on top of all windows + + + + + Linewrap + + + + + RevPiPyLoad - Logfile + + + + + Clear view + + + + + Python PLC program - Logfile + + + + diff --git a/revpicommander/mqttmanager.py b/revpicommander/mqttmanager.py new file mode 100644 index 0000000..0fb6525 --- /dev/null +++ b/revpicommander/mqttmanager.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +"""Options for MQTT system.""" + +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2018 Sven Sager" +__license__ = "GPLv3" + +from PyQt5 import QtGui, QtWidgets + +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): + 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 + ) diff --git a/revpicommander/proginit.py b/revpicommander/proginit.py new file mode 100644 index 0000000..1a71ec8 --- /dev/null +++ b/revpicommander/proginit.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +"""Global program initialization.""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2019 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( +# "-c", "--conffile", dest="conffile", +# default="{0}.conf".format(programname), +# help="Application configuration file" +# ) +parser.add_argument( + "-f", "--logfile", dest="logfile", + help="Save log entries to this file" +) + +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() diff --git a/revpicommander/revpicommander.py b/revpicommander/revpicommander.py new file mode 100755 index 0000000..e648d1e --- /dev/null +++ b/revpicommander/revpicommander.py @@ -0,0 +1,523 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +"""RevPiCommander main program.""" + +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2018 Sven Sager" +__license__ = "GPLv3" +__version__ = "0.9.0" + +import webbrowser +from os.path import basename, dirname, join + +from PyQt5 import QtCore, QtGui, QtWidgets + +import helper +import proginit as pi +import revpilogfile +from avahisearch import AvahiSearch +from debugcontrol import DebugControl +from revpidevelop import RevPiDevelop +from revpiinfo import RevPiInfo +from revpioption import RevPiOption +from revpiplclist import RevPiPlcList +from revpiprogram import RevPiProgram +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.wid_develop = None # type: RevPiDevelop + """Holds the widget of RevPiDevelop.""" + self.simulating = False + """True, if simulation is running.""" + self.dict_men_connections_subfolder = {} + """Submenus for folder entries.""" + + # fixme: Prepare gui + #self.__base_size = self.size() + #self.setFixedSize(self.__base_size) + + 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(self, __version__) + self.diag_options = RevPiOption(self) + self.diag_program = RevPiProgram(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'')) + + 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.""" + 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 + + @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_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.act_developer.setChecked(False) + + self.diag_info.reject() + self.diag_options.reject() + self.diag_program.reject() + + 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() + self.txt_connection.setText("{0} - {1}:{2}".format( + helper.cm.name, + helper.cm.address, + helper.cm.port + )) + + @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) + + act = QtWidgets.QAction(self) + act.setText(helper.settings.value("name")) + act.setData(i) + act.setToolTip("{0}:{1}".format( + helper.settings.value("address"), + helper.settings.value("port"), + )) + + if helper.settings.value("folder"): + if helper.settings.value("folder") not in self.dict_men_connections_subfolder: + men_sub = QtWidgets.QMenu(self) + men_sub.setTitle(helper.settings.value("folder")) + self.dict_men_connections_subfolder[helper.settings.value("folder")] = men_sub + self.men_connections.addMenu(men_sub) + self.dict_men_connections_subfolder[helper.settings.value("folder")].addAction(act) + + else: + self.men_connections.addAction(act) + + 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: + helper.cm.pyload_connect(self.diag_search.connect_index) + + self._load_men_connections() + + @QtCore.pyqtSlot() + def on_act_simulator_triggered(self): + """Start the simulator function.""" + helper.cm.pyload_disconnect() + + diag_open = QtWidgets.QFileDialog( + self, self.tr("Select downloaded piCtory file..."), + helper.settings.value("simulator_pictory", ".", str), + self.tr("piCtory file (*.rsc);;All files (*.*)") + ) + diag_open.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen) + diag_open.setFileMode(QtWidgets.QFileDialog.ExistingFile) + diag_open.setDefaultSuffix("rsc") + + if diag_open.exec() != QtWidgets.QFileDialog.AcceptSave or len(diag_open.selectedFiles()) != 1: + return + + configrsc_file = diag_open.selectedFiles()[0] + dir_name = dirname(configrsc_file) + procimg_file = join(dir_name, "{0}.img".format( + basename(configrsc_file).rsplit(".", maxsplit=1)[0] + )) + helper.settings.setValue("simulator_pictory", configrsc_file) + + if helper.cm.pyload_simulate(configrsc_file, procimg_file): + QtWidgets.QMessageBox.information( + self, self.tr("Simulator started..."), self.tr( + "The simulator is running!\n\nYou can work with this simulator if your call " + "RevPiModIO with this additional parameters:\nprocimg={procimg}\nconfigrsc={config}\n\n" + "You can copy that from header textbox." + ).format(procimg=procimg_file, config=configrsc_file) + ) + self.txt_connection.setText( + "configrsc=\"{0}\", procimg=\"{1}\"".format(configrsc_file, procimg_file) + ) + + # Stop button will disable simulating + self.btn_plc_stop.setEnabled(True) + self.btn_plc_restart.setEnabled(True) + self.btn_plc_debug.setEnabled(True) + else: + pi.logger.error("Can not start simulator") + QtWidgets.QMessageBox.critical( + self, self.tr("Can not start..."), self.tr( + "Can not start the simulator! Maybe the piCtory file is corrupt " + "or you can not write to the location '{0}'.".format(dir_name) + ) + ) + + @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] == 0 and helper.cm.pyload_version[1] < 6: + 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(bool) + def on_act_developer_toggled(self, state: bool): + """Extent developer mode to main window.""" + if not (state or self.wid_develop is None): + # Remove widget + self.gl.removeWidget(self.wid_develop) + self.wid_develop.deleteLater() + self.wid_develop = None + self.gl.setColumnStretch(1, 0) + + elif state and helper.cm.connected: + 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!" + ) + ) + self.act_developer.setChecked(False) + + else: + self.wid_develop = RevPiDevelop(self.centralwidget) + self.gl.addWidget(self.wid_develop, 0, 1, 8, 1) + self.gl.setColumnStretch(1, 1) + + elif state: + self.act_developer.setChecked(False) + + self.centralwidget.adjustSize() + self.adjustSize() + + @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.""" + if not helper.cm.pyload_connect(action.data()): + QtWidgets.QMessageBox.critical( + self, self.tr("Error"), self.tr( + "Can not connect to RevPi XML-RPC Service! \n\n" + "This could have the following reasons: The RevPi is not " + "online, the XML-RPC service is not running or the ACL " + "permission is not set for your IP!!!" + ) + ) + + @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 piCtory default values?\n" + "You have to stop other RevPiModIO programs before doing that, " + "because they could reset the outputs." + ) + ) == 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, 7, 0) + 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 # # # # # + + +if __name__ == "__main__": + import sys + + app = QtWidgets.QApplication(sys.argv) + + try: + # Setup translation from file with system language + locale = QtCore.QLocale.system().name() + translator = QtCore.QTranslator() + translator.load("locale/revpicommander_{0}".format(locale), 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() + + sys.exit(exit_code) diff --git a/revpicommander/revpidevelop.py b/revpicommander/revpidevelop.py new file mode 100644 index 0000000..3ae144f --- /dev/null +++ b/revpicommander/revpidevelop.py @@ -0,0 +1,299 @@ +# -*- coding: utf-8 -*- +"""PLC developer to upload your active development.""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2018 Sven Sager" +__license__ = "GPLv3" + +import gzip +import os +from enum import IntEnum +from os import DirEntry +from xmlrpc.client import Binary + +from PyQt5 import QtCore, QtGui, QtWidgets + +import helper +import proginit as pi +from helper import WidgetData +from ui.revpidevelop_ui import Ui_wid_develop + + +class NodeType(IntEnum): + FILE = 1000 + DIR = 1001 + + +class RevPiDevelop(QtWidgets.QWidget, Ui_wid_develop): + """Developer extension for RevPiCommander.""" + + def __init__(self, parent=None): + super(RevPiDevelop, self).__init__(parent) + self.setupUi(self) + + self.tree_files_counter = 0 + self.tree_files_counter_max = 10000 + self.lbl_path.setText(helper.cm.develop_watch_path or self.tr("Please select...")) + + self.btn_all.setEnabled(False) + self.btn_upload.setEnabled(False) + + if helper.cm.develop_watch_path: + self._load_path_files(True) + + def __del__(self): + pi.logger.debug("RevPiDevelop.__del__") + + 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 + + # Get config to find actual auto start program for warnings + opt_program = helper.cm.call_remote_function("get_config", default_value={}) + opt_program = opt_program.get("plcprogram", "none.py") + uploaded = True # Will be False, when opt_program was found in files + ec = 0 + + for file_name in self.file_list(): + # todo: Check exception of local file + with open(file_name, "rb") as fh: + # Remove base dir of file to set relative for PyLoad + send_name = file_name.replace(helper.cm.develop_watch_path, "")[1:] + + # Check whether this is the auto start program + if send_name == opt_program: + uploaded = False + + # Transfer file + try: + upload_status = helper.cm.call_remote_function( + "plcupload", Binary(gzip.compress(fh.read())), send_name, + default_value=False + ) + except Exception as e: + pi.logger.error(e) + ec = -2 + break + + if not upload_status: + ec = -1 + break + + if ec == 0: + # Tell user, we did not find the auto start program in files + if uploaded: + QtWidgets.QMessageBox.information( + self, self.tr("Information..."), self.tr( + "A PLC program has been uploaded. Please check the " + "PLC options to see if the correct program is " + "specified as the start program." + ) + ) + + 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") + ) + + 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." + ) + ) + + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + # region # REGION: Tree management + + def __insert_files(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 + """ + for de in os.scandir(base_dir): # type: 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.addTopLevelItem(item) + + self.__insert_files(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.addTopLevelItem(item) + + item.setSelected(de.path in helper.cm.develop_watch_files) + self._parent_selected(item) + + def __select_children(self, top_item: QtWidgets.QTreeWidgetItem, value: bool): + """Recursive select files from directory.""" + pi.logger.debug("RevPiDevelop.__select_children") + + for i in range(top_item.childCount()): + item = top_item.child(i) + if item.type() == NodeType.DIR: + self.__select_children(item, value) + elif item.type() == NodeType.FILE: + item.setSelected(value) + + def _load_path_files(self, silent=False): + """ + Refresh the file list. + + :param silent: Do not show message boxes + """ + pi.logger.debug("RevPiDevelop._load_path_files") + + self.tree_files_counter = 0 + self.tree_files.blockSignals(True) + self.tree_files.clear() + self.tree_files.blockSignals(False) + + self.__insert_files(helper.cm.develop_watch_path) + self.tree_files.sortItems(0, QtCore.Qt.AscendingOrder) + + 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) + ) + + state = len(self.tree_files.selectedItems()) > 0 + self.btn_all.setEnabled(state) + self.btn_upload.setEnabled(state) + + def _parent_selected(self, item: QtWidgets.QTreeWidgetItem): + """Check all children of a parent 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 file_list(self): + """Generate a file list with full path of selected entries.""" + pi.logger.debug("RevPiDevelop.file_list") + lst = [] + for item in self.tree_files.selectedItems(): + if item.type() == NodeType.DIR: + continue + lst.append(item.data(0, WidgetData.file_name)) + + return lst + + @QtCore.pyqtSlot() + def on_tree_files_itemSelectionChanged(self): + item = self.tree_files.currentItem() + if item is None: + return + + pi.logger.debug("RevPiDevelop.on_tree_files_itemSelectionChanged") + + # Block while preselect other entries + self.tree_files.blockSignals(True) + + if item.type() == NodeType.DIR: + self.__select_children(item, item.isSelected()) + + elif item.type() == NodeType.FILE: + self._parent_selected(item) + + self.tree_files.blockSignals(False) + + state = len(self.tree_files.selectedItems()) > 0 + self.btn_all.setEnabled(state) + self.btn_upload.setEnabled(state) + + helper.cm.develop_watch_files = self.file_list() + + # endregion # # # # # + + @QtCore.pyqtSlot() + def on_btn_all_pressed(self): + pi.logger.debug("RevPiDevelop.on_btn_all_pressed") + self._do_my_job(True) + + @QtCore.pyqtSlot() + def on_btn_select_pressed(self): + pi.logger.debug("RevPiDevelop.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.setText(selected_dir) + helper.cm.develop_watch_path = selected_dir + helper.cm.develop_watch_files = [] + + self._load_path_files(False) + + @QtCore.pyqtSlot() + def on_btn_refresh_pressed(self): + pi.logger.debug("RevPiDevelop.on_btn_refresh_pressed") + self._load_path_files(False) + + @QtCore.pyqtSlot() + def on_btn_upload_pressed(self): + pi.logger.debug("RevPiDevelop.on_btn_upload_pressed") + self._do_my_job(False) diff --git a/revpicommander/revpiinfo.py b/revpicommander/revpiinfo.py new file mode 100644 index 0000000..493c72a --- /dev/null +++ b/revpicommander/revpiinfo.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +"""Program information of local an remote system.""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2018 Sven Sager" +__license__ = "GPLv3" + +from PyQt5 import QtCore, QtGui, QtWidgets + +import helper +from ui.revpiinfo_ui import Ui_diag_revpiinfo + + +class RevPiInfo(QtWidgets.QDialog, Ui_diag_revpiinfo): + """Version information window.""" + + def __init__(self, parent, version: str): + 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) diff --git a/revpicommander/revpilogfile.py b/revpicommander/revpilogfile.py new file mode 100644 index 0000000..21a201b --- /dev/null +++ b/revpicommander/revpilogfile.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- +"""View log files from Revolution Pi.""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2018 Sven Sager" +__license__ = "GPLv3" + +from enum import IntEnum + +from PyQt5 import QtCore, QtGui, QtWidgets + +import helper +import proginit as pi +from ui.revpilogfile_ui import Ui_win_revpilogfile + + +class LogType(IntEnum): + NONE = 0 + APP = 1 + DAEMON = 2 + + +class DataThread(QtCore.QThread): + + error_detected = QtCore.pyqtSignal() + line_logged = QtCore.pyqtSignal(LogType, bool, str) + + 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 + 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 + :param xmlcall: XML-RPC call to get log text + :param start_position: Start position in log file to read from + :return: New position in log file + """ + buff_log = b'' + while True: + # Load max data from start position to see the end of logfile + bytebuff = xmlcall(start_position, self.max_block).data + buff_log += bytebuff + start_position += len(bytebuff) + + # If we get less than max_block, the log file is EOF for this time + if len(bytebuff) < self.max_block: + break + + if bytebuff == b'\x16': # 'ESC' + # RevPiPyLoad could not access log file on Revolution Pi + self.line_logged.emit(log_type, False, "") + + elif bytebuff == b'\x19': # 'EndOfMedia' + # The log file was rotated by log rotate on the Revolution Pi + start_position = 0 + + elif buff_log: + self.line_logged.emit(log_type, True, buff_log.decode("utf-8")) + + return start_position + + def pause(self): + """Stop checking new log lines, but leave thread alive.""" + self._paused = True + + def resume(self): + """Start checking for new log lines.""" + self._paused = False + + def run(self) -> None: + pi.logger.debug("DataThread.run") + + while not self.isInterruptionRequested(): + if not self._paused: + try: + self.mrk_app = self._load_log( + LogType.APP, + self._cli.load_applog, + self.mrk_app, + ) + self.mrk_daemon = self._load_log( + LogType.DAEMON, + self._cli.load_plclog, + self.mrk_daemon, + ) + self.error_count = 0 + except Exception: + if self.error_count == 0: + self.error_detected.emit() + 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): + u"""Init RevPiLogfile-Class.""" + super(RevPiLogfile, self).__init__(parent) + self.setupUi(self) + + self.th_data = DataThread() + 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 __del__(self): + pi.logger.debug("RevPiLogfile.__del__") + self.th_data.deleteLater() + + def _create_data_thread(self): + self.th_data.deleteLater() + + self.th_data = DataThread() + 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()) diff --git a/revpicommander/revpioption.py b/revpicommander/revpioption.py new file mode 100644 index 0000000..2d5770c --- /dev/null +++ b/revpicommander/revpioption.py @@ -0,0 +1,320 @@ +# -*- coding: utf-8 -*- +"""RevPiPyLoad options window.""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2018 Sven Sager" +__license__ = "GPLv3" + +from PyQt5 import QtCore, QtGui, QtWidgets + +import helper +import proginit as pi +from aclmanager import AclManager +from mqttmanager import MqttManager +from ui.revpioption_ui import Ui_diag_options + + +class RevPiOption(QtWidgets.QDialog, Ui_diag_options): + """Set options of RevPiPyLoad.""" + + def __init__(self, parent): + 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 + # todo: self.dc.get("plcprogram_watchdog", 0) + # todo: self.dc.get("reset_driver_action", 2) + # 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.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["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() diff --git a/revpicommander/revpiplclist.py b/revpicommander/revpiplclist.py new file mode 100644 index 0000000..d9e4c58 --- /dev/null +++ b/revpicommander/revpiplclist.py @@ -0,0 +1,367 @@ +# -*- coding: utf-8 -*- +"""Saved connections of Revolution Pi devices.""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2018 Sven Sager" +__license__ = "GPLv3" + +from enum import IntEnum + +from PyQt5 import QtCore, QtGui, QtWidgets + +import helper +import proginit as pi +from helper import WidgetData +from ui.revpiplclist_ui import Ui_diag_connections + + +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.setFixedSize(self.size()) + 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(helper.settings.beginReadArray("connections")): + helper.settings.setArrayIndex(i) + + con_item = QtWidgets.QTreeWidgetItem(NodeType.CON) + con_item.setIcon(0, QtGui.QIcon(":/main/ico/cpu.ico")) + con_item.setText(0, helper.settings.value("name", "Revolution Pi", str)) + con_item.setText(1, helper.settings.value("address", "127.0.0.1", str)) + con_item.setData(0, WidgetData.port, helper.settings.value("port", self.__default_port, int)) + + con_item.setData(0, WidgetData.last_dir_upload, helper.settings.value("last_dir_upload")) + con_item.setData(0, WidgetData.last_file_upload, helper.settings.value("last_file_upload")) + con_item.setData(0, WidgetData.last_dir_pictory, helper.settings.value("last_dir_pictory")) + con_item.setData(0, WidgetData.last_dir_picontrol, helper.settings.value("last_dir_picontrol")) + con_item.setData(0, WidgetData.last_dir_selected, helper.settings.value("last_dir_selected")) + con_item.setData(0, WidgetData.last_pictory_file, helper.settings.value("last_pictory_file")) + con_item.setData(0, WidgetData.last_tar_file, helper.settings.value("last_tar_file")) + con_item.setData(0, WidgetData.last_zip_file, helper.settings.value("last_zip_file")) + con_item.setData(0, WidgetData.watch_files, helper.settings.value("watch_files")) + con_item.setData(0, WidgetData.watch_path, helper.settings.value("watch_path")) + con_item.setData(0, WidgetData.debug_geos, helper.settings.value("debug_geos")) + + folder = helper.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) + + helper.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() + helper.settings.setValue("address", node.text(1)) + helper.settings.setValue("folder", parent.text(0) if parent else "") + helper.settings.setValue("name", node.text(0)) + helper.settings.setValue("port", node.data(0, WidgetData.port)) + + if node.data(0, WidgetData.last_dir_upload): + helper.settings.setValue("last_dir_upload", node.data(0, WidgetData.last_dir_upload)) + if node.data(0, WidgetData.last_file_upload): + helper.settings.setValue("last_file_upload", node.data(0, WidgetData.last_file_upload)) + if node.data(0, WidgetData.last_dir_pictory): + helper.settings.setValue("last_dir_pictory", node.data(0, WidgetData.last_dir_pictory)) + if node.data(0, WidgetData.last_dir_picontrol): + helper.settings.setValue("last_dir_picontrol", node.data(0, WidgetData.last_dir_picontrol)) + if node.data(0, WidgetData.last_dir_selected): + helper.settings.setValue("last_dir_selected", node.data(0, WidgetData.last_dir_selected)) + if node.data(0, WidgetData.last_pictory_file): + helper.settings.setValue("last_pictory_file", node.data(0, WidgetData.last_pictory_file)) + if node.data(0, WidgetData.last_tar_file): + helper.settings.setValue("last_tar_file", node.data(0, WidgetData.last_tar_file)) + if node.data(0, WidgetData.last_zip_file): + helper.settings.setValue("last_zip_file", node.data(0, WidgetData.last_zip_file)) + if node.data(0, WidgetData.watch_files): + helper.settings.setValue("watch_files", node.data(0, WidgetData.watch_files)) + if node.data(0, WidgetData.watch_path): + helper.settings.setValue("watch_path", node.data(0, WidgetData.watch_path)) + if node.data(0, WidgetData.debug_geos): + helper.settings.setValue("debug_geos", node.data(0, WidgetData.debug_geos)) + + helper.settings.remove("connections") + helper.settings.beginWriteArray("connections") + + counter_index = 0 + for i in range(self.tre_connections.topLevelItemCount()): + root_item = self.tre_connections.topLevelItem(i) + if root_item.type() == NodeType.DIR: + for k in range(root_item.childCount()): + helper.settings.setArrayIndex(counter_index) + set_settings(root_item.child(k)) + counter_index += 1 + elif root_item.type() == NodeType.CON: + helper.settings.setArrayIndex(counter_index) + set_settings(root_item) + counter_index += 1 + + helper.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.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)) + 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) + 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(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 # # # # # diff --git a/revpicommander/revpiprogram.py b/revpicommander/revpiprogram.py new file mode 100644 index 0000000..f9aa89c --- /dev/null +++ b/revpicommander/revpiprogram.py @@ -0,0 +1,772 @@ +# -*- coding: utf-8 -*- +"""Revolution Pi PLC program configuration.""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2018 Sven Sager" +__license__ = "GPLv3" + +import gzip +import os +import tarfile +import zipfile +from shutil import rmtree +from tempfile import mkdtemp, mkstemp +from xmlrpc.client import Binary + +from PyQt5 import QtCore, QtGui, QtWidgets + +import helper +import proginit as pi +from ui.revpiprogram_ui import Ui_diag_program + + +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) + + 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))) + + 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()) + + 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 + """ + 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 >= 2) + self.btn_program_download.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 to selected directory + diag_folder = QtWidgets.QFileDialog( + self, self.tr("Select folder..."), + helper.cm.program_last_dir_selected, + ) + diag_folder.setFileMode(QtWidgets.QFileDialog.DirectoryOnly) + self.rejected.connect(diag_folder.reject) + + if diag_folder.exec() != QtWidgets.QFileDialog.Accepted: + return + + selected_dir = diag_folder.selectedFiles()[0] + fh = open(mkstemp()[1], "wb") + + helper.cm.program_last_dir_selected = selected_dir + + elif self.cbb_format.currentIndex() == 2: + # 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() == 3: + # 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() == 2 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() + + if self.cbb_format.currentIndex() == 0: + # Extract archive to selected directory + os.makedirs(selected_dir, exist_ok=True) + fh_pack = tarfile.open(fh.name) + + for tar_item in fh_pack.getmembers(): + if tar_item.name == "revpipyload": + # Ignore root folder in Tar file + continue + tar_item.name = tar_item.name.replace("revpipyload/", "") + fh_pack.extract(tar_item, selected_dir) + + fh_pack.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 files + diag_open = QtWidgets.QFileDialog( + self, + self.tr("Upload plc files..."), + helper.cm.program_last_dir_upload, + self.tr("Python file (*.py);;Config file (*.conf);;;JSON file (*.json);;All files (*.*)") + ) + diag_open.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen) + diag_open.setFileMode(QtWidgets.QFileDialog.ExistingFiles) + self.rejected.connect(diag_open.reject) + + if diag_open.exec() != QtWidgets.QFileDialog.AcceptSave or len(diag_open.selectedFiles()) != 1: + return + + lst_files = diag_open.selectedFiles() + if len(lst_files) > 0: + helper.cm.program_last_dir_upload = os.path.dirname(lst_files[0]) + + elif self.cbb_format.currentIndex() == 1: + # Select folder to upload + diag_folder = QtWidgets.QFileDialog( + self, self.tr("Select folder to upload..."), + helper.cm.program_last_dir_upload, + ) + diag_folder.setFileMode(QtWidgets.QFileDialog.DirectoryOnly) + self.rejected.connect(diag_folder.reject) + + if diag_folder.exec() != QtWidgets.QFileDialog.Accepted: + return + + selected_dir = diag_folder.selectedFiles()[0] + helper.cm.program_last_dir_upload = selected_dir + + folder_name = os.path.basename(selected_dir) + lst_files = self.create_filelist(selected_dir) + + elif self.cbb_format.currentIndex() == 2: + # 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() == 3: + # 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 # # # # # diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..9f73c30 --- /dev/null +++ b/setup.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +"""Setupscript fuer RevPiPyLoad.""" +__author__ = "Sven Sager" +__copyright__ = "Copyright (C) 2018 Sven Sager" +__license__ = "LGPLv3" + +import distutils.command.install_egg_info +from distutils.core import setup +from glob import glob + + +class MyEggInfo(distutils.command.install_egg_info.install_egg_info): + + u"""Disable egg_info installation, seems pointless for a non-library.""" + + def run(self): + u"""just pass egg_info.""" + pass + + +setup( + version="0.9.0", + python_requires="~=3.4", + requires=["PyQt5", "zeroconf"], + + scripts=["data/revpicommander"], + data_files=[ + ("share/applications", ["data/revpicommander.desktop"]), + ("share/icons/hicolor/32x32/apps", ["data/revpicommander.png"]), + ("share/revpicommander", glob("revpicommander/*.py")), + ("share/revpicommander/revpimodio2", glob("lib/revpimodio2/revpimodio2/*.py")), + ("share/revpicommander/ui", glob("include/ui/*.py")), + ("share/revpicommander/locale/", glob("revpicommander/locale/*.qm")), + ], + + # Additional meta-data + name="revpicommander", + author="Sven Sager", + author_email="akira@narux.de", + maintainer="Sven Sager", + maintainer_email="akira@revpimodio.org", + url="https://revpimodio.org/revpipyplc/", + description="GUI for Revolution Pi to upload programs and do IO-Checks", + long_description="" + "Dieses Programm startet beim Systemstart ein angegebenes Python PLC\n" + "Programm. Es überwacht das Programm und startet es im Fehlerfall neu.\n" + "Bei Abstruz kann das gesamte /dev/piControl0 auf 0x00 gesettz werden.\n" + "Außerdem stellt es einen XML-RPC Server bereit, über den die Software\n" + "auf den RevPi geladen werden kann. Das Prozessabbild kann über ein Tool\n" + "zur Laufzeit überwacht werden.", + classifiers=[ + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Operating System :: POSIX :: Linux", + ], + license="GPLv3", + cmdclass={"install_egg_info": MyEggInfo}, +) diff --git a/stdeb.cfg b/stdeb.cfg new file mode 100644 index 0000000..68f943d --- /dev/null +++ b/stdeb.cfg @@ -0,0 +1,6 @@ +[DEFAULT] +Debian-Version=1 +Depends3=python3-pyqt5, python3-zeroconf +Section=universe/x11 +Suite=stable +X-Python3-Version: >=3.4 diff --git a/translate.pro b/translate.pro new file mode 100644 index 0000000..3c9a11c --- /dev/null +++ b/translate.pro @@ -0,0 +1,29 @@ +SOURCES = revpicommander/aclmanager.py \ + revpicommander/avahisearch.py \ + revpicommander/debugcontrol.py \ + revpicommander/debugios.py \ + revpicommander/mqttmanager.py \ + revpicommander/revpidevelop.py \ + revpicommander/revpiinfo.py \ + revpicommander/revpilogfile.py \ + revpicommander/revpioption.py \ + revpicommander/revpiplclist.py \ + revpicommander/revpiprogram.py \ + revpicommander/revpicommander.py + +FORMS = include/ui_dev/aclmanager.ui \ + include/ui_dev/avahisearch.ui \ + include/ui_dev/debugcontrol.ui \ + include/ui_dev/debugios.ui \ + include/ui_dev/mqttmanager.ui \ + include/ui_dev/revpidevelop.ui \ + include/ui_dev/revpiinfo.ui \ + include/ui_dev/revpilogfile.ui \ + include/ui_dev/revpioption.ui \ + include/ui_dev/revpiplclist.ui \ + include/ui_dev/revpiprogram.ui \ + include/ui_dev/revpicommander.ui + +TRANSLATIONS = revpicommander/locale/revpicommander_de.ts + +CODECRORTR = UTF-8