mirror of
https://github.com/naruxde/revpipycontrol.git
synced 2025-11-08 23:53:52 +01:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7939f193da | |||
| d7d1aa2194 | |||
| 5dde6e2705 | |||
| 8a6d1ebca8 | |||
| e828c1f55a | |||
| 84f52cb17a | |||
| 93e02df73e | |||
| 63f60f9e36 | |||
| 34f133ea6e | |||
| 472605bb38 | |||
| 79ea020903 | |||
| 7b61dee1c6 | |||
| f1163f2e3f | |||
| 5812dba69d | |||
| 7e9afd0944 | |||
| 79c737ea2f | |||
| 55c1349edf | |||
| df308600c2 | |||
| fb88271364 | |||
| fa8fc6a7ce | |||
| df4c82579d | |||
| 220de03774 | |||
| f2dd6416ea | |||
| 7d3957a9d1 | |||
| ef912fa907 | |||
| 0fc9af633c | |||
| 1d9c28d48b | |||
| 554f19d090 | |||
| e42cc1a6d2 | |||
| 6c2578f84c | |||
| 37e6dd6a75 | |||
| 348ef65716 | |||
| 6ac9466850 | |||
| 56cf5a7b09 | |||
| 3c05f9f024 | |||
| 2751a2bd35 | |||
| e32383048f | |||
| 7fefb1aece | |||
| a60d029f39 | |||
| e322b1d43b | |||
| a8442b5969 | |||
| c7d5e4432a | |||
| adc1158f5c | |||
| 364e6a169c | |||
| f761bca89d | |||
| efbde10c02 | |||
| c20dc479a2 | |||
| 587247f048 | |||
| 9e9751883d | |||
| 6103104d96 | |||
| 45e3414f94 | |||
| f52dc05be2 | |||
| f58be573c4 | |||
| 5488107c42 | |||
| 03773ff4b0 | |||
| cdaff8fe4e | |||
| 7fc879bbe1 | |||
| 15b59be6d8 | |||
| 145468d35b | |||
| 0653c6c8eb | |||
| 256a95aa8b | |||
| 1fec478e3f | |||
| a60431e456 | |||
| 1f668b153c | |||
| 624b6f6972 | |||
| 8c8da29915 | |||
| 673c338c6b | |||
| e9279e4a53 | |||
| 099266032c |
118
.gitignore
vendored
Normal file
118
.gitignore
vendored
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# ---> Python
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
/test/
|
||||||
|
/make.conf
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
syntax: glob
|
|
||||||
*.pyc
|
|
||||||
deb_dist/*
|
|
||||||
dist/*
|
|
||||||
revpipycontrol.egg-info/*
|
|
||||||
doc/*
|
|
||||||
deb/*
|
|
||||||
.eric6project/*
|
|
||||||
17
.idea/$CACHE_FILE$
generated
Normal file
17
.idea/$CACHE_FILE$
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectInspectionProfilesVisibleTreeState">
|
||||||
|
<entry key="Project Default">
|
||||||
|
<profile-state>
|
||||||
|
<expanded-state>
|
||||||
|
<State />
|
||||||
|
</expanded-state>
|
||||||
|
<selected-state>
|
||||||
|
<State>
|
||||||
|
<id>Angular</id>
|
||||||
|
</State>
|
||||||
|
</selected-state>
|
||||||
|
</profile-state>
|
||||||
|
</entry>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
2
.idea/.gitignore
generated
vendored
Normal file
2
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/workspace.xml
|
||||||
7
.idea/dictionaries/akira.xml
generated
Normal file
7
.idea/dictionaries/akira.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<component name="ProjectDictionaryState">
|
||||||
|
<dictionary name="akira">
|
||||||
|
<words>
|
||||||
|
<w>revpipycontrol</w>
|
||||||
|
</words>
|
||||||
|
</dictionary>
|
||||||
|
</component>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptSettings">
|
||||||
|
<option name="languageLevel" value="ES6" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/revpipycontrol.iml" filepath="$PROJECT_DIR$/.idea/revpipycontrol.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
12
.idea/revpipycontrol.iml
generated
Normal file
12
.idea/revpipycontrol.iml
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/revpipycontrol" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/downloads" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.6" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
165
LICENSE.txt
Normal file
165
LICENSE.txt
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
||||||
@@ -3,3 +3,4 @@ include stdeb.cfg
|
|||||||
recursive-include data *
|
recursive-include data *
|
||||||
recursive-include revpipycontrol *
|
recursive-include revpipycontrol *
|
||||||
global-exclude *.pyc
|
global-exclude *.pyc
|
||||||
|
include LICENSE.txt
|
||||||
|
|||||||
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Minimal makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build
|
||||||
|
SPHINXPROJ = RevPiPyControl
|
||||||
|
SOURCEDIR = .
|
||||||
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
# Put it first so that "make" without argument is like "make help".
|
||||||
|
help:
|
||||||
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
|
.PHONY: help Makefile
|
||||||
|
|
||||||
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
|
%: Makefile
|
||||||
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
7
docs/aclmanager.rst
Normal file
7
docs/aclmanager.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
aclmanager module
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. automodule:: aclmanager
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
171
docs/conf.py
Normal file
171
docs/conf.py
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# RevPiPyControl documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Sun Oct 20 17:05:21 2019.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its
|
||||||
|
# containing dir.
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.abspath('../revpipycontrol'))
|
||||||
|
|
||||||
|
|
||||||
|
# -- General configuration ------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#
|
||||||
|
# needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
|
# ones.
|
||||||
|
extensions = ['sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.todo',
|
||||||
|
'sphinx.ext.viewcode']
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix(es) of source filenames.
|
||||||
|
# You can specify multiple suffix as a list of string:
|
||||||
|
#
|
||||||
|
# source_suffix = ['.rst', '.md']
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = 'RevPiPyControl'
|
||||||
|
copyright = '2019, Sven Sager (NaruX)'
|
||||||
|
author = 'Sven Sager (NaruX)'
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
# built documents.
|
||||||
|
#
|
||||||
|
# The short X.Y version.
|
||||||
|
version = ''
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = ''
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#
|
||||||
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
|
# Usually you set "language" from the command line for these cases.
|
||||||
|
language = None
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
# This patterns also effect to html_static_path and html_extra_path
|
||||||
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||||
|
todo_include_todos = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output ----------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
#
|
||||||
|
html_theme = 'alabaster'
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
#
|
||||||
|
# html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
# Custom sidebar templates, must be a dictionary that maps document names
|
||||||
|
# to template names.
|
||||||
|
#
|
||||||
|
# This is required for the alabaster theme
|
||||||
|
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
|
||||||
|
html_sidebars = {
|
||||||
|
'**': [
|
||||||
|
'relations.html', # needs 'show_related': True theme option to display
|
||||||
|
'searchbox.html',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTMLHelp output ------------------------------------------
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'RevPiPyControldoc'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
|
|
||||||
|
latex_elements = {
|
||||||
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
#
|
||||||
|
# 'papersize': 'letterpaper',
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#
|
||||||
|
# 'pointsize': '10pt',
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#
|
||||||
|
# 'preamble': '',
|
||||||
|
|
||||||
|
# Latex figure (float) alignment
|
||||||
|
#
|
||||||
|
# 'figure_align': 'htbp',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title,
|
||||||
|
# author, documentclass [howto, manual, or own class]).
|
||||||
|
latex_documents = [
|
||||||
|
(master_doc, 'RevPiPyControl.tex', 'RevPiPyControl Documentation',
|
||||||
|
'Sven Sager (NaruX)', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output ---------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
(master_doc, 'revpipycontrol', 'RevPiPyControl Documentation',
|
||||||
|
[author], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Texinfo output -------------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
|
# (source start file, target name, title, author,
|
||||||
|
# dir menu entry, description, category)
|
||||||
|
texinfo_documents = [
|
||||||
|
(master_doc, 'RevPiPyControl', 'RevPiPyControl Documentation',
|
||||||
|
author, 'RevPiPyControl', 'One line description of project.',
|
||||||
|
'Miscellaneous'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
20
docs/index.rst
Normal file
20
docs/index.rst
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.. RevPiPyControl documentation master file, created by
|
||||||
|
sphinx-quickstart on Sun Oct 20 17:05:21 2019.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
|
Welcome to RevPiPyControl's documentation!
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:caption: Contents:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
==================
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
||||||
19
docs/modules.rst
Normal file
19
docs/modules.rst
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
revpipycontrol
|
||||||
|
==============
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 4
|
||||||
|
|
||||||
|
aclmanager
|
||||||
|
mqttmanager
|
||||||
|
mytools
|
||||||
|
revpicheckclient
|
||||||
|
revpidevelop
|
||||||
|
revpiinfo
|
||||||
|
revpilegacy
|
||||||
|
revpilogfile
|
||||||
|
revpioption
|
||||||
|
revpiplclist
|
||||||
|
revpiprogram
|
||||||
|
revpipycontrol
|
||||||
|
shared
|
||||||
7
docs/mqttmanager.rst
Normal file
7
docs/mqttmanager.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mqttmanager module
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. automodule:: mqttmanager
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
docs/mytools.rst
Normal file
7
docs/mytools.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mytools module
|
||||||
|
==============
|
||||||
|
|
||||||
|
.. automodule:: mytools
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
docs/revpicheckclient.rst
Normal file
7
docs/revpicheckclient.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
revpicheckclient module
|
||||||
|
=======================
|
||||||
|
|
||||||
|
.. automodule:: revpicheckclient
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
docs/revpidevelop.rst
Normal file
7
docs/revpidevelop.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
revpidevelop module
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. automodule:: revpidevelop
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
docs/revpiinfo.rst
Normal file
7
docs/revpiinfo.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
revpiinfo module
|
||||||
|
================
|
||||||
|
|
||||||
|
.. automodule:: revpiinfo
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
docs/revpilegacy.rst
Normal file
7
docs/revpilegacy.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
revpilegacy module
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. automodule:: revpilegacy
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
docs/revpilogfile.rst
Normal file
7
docs/revpilogfile.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
revpilogfile module
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. automodule:: revpilogfile
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
docs/revpioption.rst
Normal file
7
docs/revpioption.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
revpioption module
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. automodule:: revpioption
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
docs/revpiplclist.rst
Normal file
7
docs/revpiplclist.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
revpiplclist module
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. automodule:: revpiplclist
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
docs/revpiprogram.rst
Normal file
7
docs/revpiprogram.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
revpiprogram module
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. automodule:: revpiprogram
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
docs/revpipycontrol.rst
Normal file
7
docs/revpipycontrol.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
revpipycontrol module
|
||||||
|
=====================
|
||||||
|
|
||||||
|
.. automodule:: revpipycontrol
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
22
docs/shared.rst
Normal file
22
docs/shared.rst
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
shared package
|
||||||
|
==============
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
shared\.ipaclmanager module
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
.. automodule:: shared.ipaclmanager
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
||||||
|
Module contents
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. automodule:: shared
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
5
make.bat
Normal file
5
make.bat
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@echo off
|
||||||
|
pyinstaller --clean -D --windowed ^
|
||||||
|
--add-data="data\revpipycontrol.ico;." ^
|
||||||
|
--icon=data\\revpipycontrol.ico ^
|
||||||
|
revpipycontrol\revpipycontrol.py
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
revpicheckclient.RevPiCheckClient._autorw?5()
|
|
||||||
revpicheckclient.RevPiCheckClient._createiogroup?5(device, frame, iotype)
|
|
||||||
revpicheckclient.RevPiCheckClient._createwidgets?5()
|
|
||||||
revpicheckclient.RevPiCheckClient._readvaluesdev?5(device, iotype)
|
|
||||||
revpicheckclient.RevPiCheckClient._writevaluesdev?5(device)
|
|
||||||
revpicheckclient.RevPiCheckClient.myapp?7
|
|
||||||
revpicheckclient.RevPiCheckClient.onfrmconf?4(canvas)
|
|
||||||
revpicheckclient.RevPiCheckClient.readvalues?4()
|
|
||||||
revpicheckclient.RevPiCheckClient.root?7
|
|
||||||
revpicheckclient.RevPiCheckClient.toggleauto?4()
|
|
||||||
revpicheckclient.RevPiCheckClient.writevalues?4()
|
|
||||||
revpicheckclient.RevPiCheckClient?1(master, xmlcli)
|
|
||||||
revpilogfile.RevPiLogfile._createwidgets?5()
|
|
||||||
revpilogfile.RevPiLogfile.btn_clearapp?4()
|
|
||||||
revpilogfile.RevPiLogfile.btn_clearplc?4()
|
|
||||||
revpilogfile.RevPiLogfile.get_applines?4()
|
|
||||||
revpilogfile.RevPiLogfile.get_applog?4()
|
|
||||||
revpilogfile.RevPiLogfile.get_plclines?4()
|
|
||||||
revpilogfile.RevPiLogfile.get_plclog?4()
|
|
||||||
revpilogfile.RevPiLogfile?1(master, xmlcli)
|
|
||||||
revpioption.RevPiOption._createwidgets?5()
|
|
||||||
revpioption.RevPiOption._loadappdata?5()
|
|
||||||
revpioption.RevPiOption._setappdata?5()
|
|
||||||
revpioption.RevPiOption.askxmlon?4()
|
|
||||||
revpioption.RevPiOption.xmlmods?4()
|
|
||||||
revpioption.RevPiOption?1(master, xmlcli, xmlmode)
|
|
||||||
revpiplclist.RevPiPlcList._createwidgets?5()
|
|
||||||
revpiplclist.RevPiPlcList._loadappdata?5()
|
|
||||||
revpiplclist.RevPiPlcList._saveappdata?5()
|
|
||||||
revpiplclist.RevPiPlcList.build_listconn?4()
|
|
||||||
revpiplclist.RevPiPlcList.evt_btnadd?4()
|
|
||||||
revpiplclist.RevPiPlcList.evt_btnclose?4()
|
|
||||||
revpiplclist.RevPiPlcList.evt_btnnew?4()
|
|
||||||
revpiplclist.RevPiPlcList.evt_btnremove?4()
|
|
||||||
revpiplclist.RevPiPlcList.evt_btnsave?4()
|
|
||||||
revpiplclist.RevPiPlcList.evt_keypress?4(evt=None)
|
|
||||||
revpiplclist.RevPiPlcList.evt_listconn?4(evt=None)
|
|
||||||
revpiplclist.RevPiPlcList.myapp?7
|
|
||||||
revpiplclist.RevPiPlcList.root?7
|
|
||||||
revpiplclist.RevPiPlcList?1(master)
|
|
||||||
revpiplclist.get_connections?4()
|
|
||||||
revpiplclist.savefile?7
|
|
||||||
revpiprogram.RevPiProgram._createwidgets?5()
|
|
||||||
revpiprogram.RevPiProgram._evt_optdown?5(text="")
|
|
||||||
revpiprogram.RevPiProgram._evt_optup?5(text="")
|
|
||||||
revpiprogram.RevPiProgram._loaddefault?5(full=False)
|
|
||||||
revpiprogram.RevPiProgram._savedefaults?5()
|
|
||||||
revpiprogram.RevPiProgram.check_replacedir?4(rootdir)
|
|
||||||
revpiprogram.RevPiProgram.create_filelist?4(rootdir)
|
|
||||||
revpiprogram.RevPiProgram.getpictoryrsc?4()
|
|
||||||
revpiprogram.RevPiProgram.getprocimg?4()
|
|
||||||
revpiprogram.RevPiProgram.myapp?7
|
|
||||||
revpiprogram.RevPiProgram.picontrolreset?4()
|
|
||||||
revpiprogram.RevPiProgram.plcdownload?4()
|
|
||||||
revpiprogram.RevPiProgram.plcupload?4()
|
|
||||||
revpiprogram.RevPiProgram.root?7
|
|
||||||
revpiprogram.RevPiProgram.setpictoryrsc?4(filename=None)
|
|
||||||
revpiprogram.RevPiProgram?1(master, xmlcli, xmlmode, revpi)
|
|
||||||
revpiprogram.savefile?7
|
|
||||||
revpipycontrol.RevPiPyControl._btnstate?5()
|
|
||||||
revpipycontrol.RevPiPyControl._closeall?5()
|
|
||||||
revpipycontrol.RevPiPyControl._createwidgets?5()
|
|
||||||
revpipycontrol.RevPiPyControl._fillconnbar?5()
|
|
||||||
revpipycontrol.RevPiPyControl._fillmbar?5()
|
|
||||||
revpipycontrol.RevPiPyControl._opt_conn?5(text)
|
|
||||||
revpipycontrol.RevPiPyControl.myapp?7
|
|
||||||
revpipycontrol.RevPiPyControl.plclist?4()
|
|
||||||
revpipycontrol.RevPiPyControl.plclogs?4()
|
|
||||||
revpipycontrol.RevPiPyControl.plcmonitor?4()
|
|
||||||
revpipycontrol.RevPiPyControl.plcoptions?4()
|
|
||||||
revpipycontrol.RevPiPyControl.plcprogram?4()
|
|
||||||
revpipycontrol.RevPiPyControl.plcrestart?4()
|
|
||||||
revpipycontrol.RevPiPyControl.plcstart?4()
|
|
||||||
revpipycontrol.RevPiPyControl.plcstop?4()
|
|
||||||
revpipycontrol.RevPiPyControl.root?7
|
|
||||||
revpipycontrol.RevPiPyControl.servererror?4()
|
|
||||||
revpipycontrol.RevPiPyControl.tmr_plcrunning?4()
|
|
||||||
revpipycontrol.RevPiPyControl?1(master=None)
|
|
||||||
revpipycontrol.addroot?4(filename)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
RevPiCheckClient tkinter.Frame
|
|
||||||
RevPiLogfile tkinter.Frame
|
|
||||||
RevPiOption tkinter.Frame
|
|
||||||
RevPiPlcList tkinter.Frame
|
|
||||||
RevPiProgram tkinter.Frame
|
|
||||||
RevPiPyControl tkinter.Frame
|
|
||||||
@@ -1,343 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE Project SYSTEM "Project-5.1.dtd">
|
|
||||||
<!-- eric project file for project revpipycontrol -->
|
|
||||||
<!-- Saved: 2017-04-11, 12:38:06 -->
|
|
||||||
<!-- Copyright (C) 2017 Sven Sager, akira@narux.de -->
|
|
||||||
<Project version="5.1">
|
|
||||||
<Language>en_US</Language>
|
|
||||||
<Hash>66103e2eaf8a762f14d1fd51d8b1c9dcaf35a275</Hash>
|
|
||||||
<ProgLanguage mixed="0">Python3</ProgLanguage>
|
|
||||||
<ProjectType>Console</ProjectType>
|
|
||||||
<Description></Description>
|
|
||||||
<Version>0.2.12</Version>
|
|
||||||
<Author>Sven Sager</Author>
|
|
||||||
<Email>akira@narux.de</Email>
|
|
||||||
<Eol index="-1"/>
|
|
||||||
<Sources>
|
|
||||||
<Source>revpipycontrol/revpipycontrol.py</Source>
|
|
||||||
<Source>revpipycontrol/revpicheckclient.py</Source>
|
|
||||||
<Source>setup.py</Source>
|
|
||||||
<Source>revpipycontrol/revpiplclist.py</Source>
|
|
||||||
<Source>revpipycontrol/revpilogfile.py</Source>
|
|
||||||
<Source>revpipycontrol/revpioption.py</Source>
|
|
||||||
<Source>revpipycontrol/revpiprogram.py</Source>
|
|
||||||
</Sources>
|
|
||||||
<Forms/>
|
|
||||||
<Translations/>
|
|
||||||
<Resources/>
|
|
||||||
<Interfaces/>
|
|
||||||
<Others>
|
|
||||||
<Other>data</Other>
|
|
||||||
<Other>doc</Other>
|
|
||||||
<Other>revpipycontrol.api</Other>
|
|
||||||
</Others>
|
|
||||||
<MainScript>revpipycontrol/revpipycontrol.py</MainScript>
|
|
||||||
<Vcs>
|
|
||||||
<VcsType>Mercurial</VcsType>
|
|
||||||
<VcsOptions>
|
|
||||||
<dict>
|
|
||||||
<key>
|
|
||||||
<string>add</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string></string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>checkout</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string></string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>commit</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string></string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>diff</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string></string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>export</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string></string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>global</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string></string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>history</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string></string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>log</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string></string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>remove</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string></string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>status</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string></string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>tag</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string></string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>update</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string></string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
</dict>
|
|
||||||
</VcsOptions>
|
|
||||||
<VcsOtherData>
|
|
||||||
<dict/>
|
|
||||||
</VcsOtherData>
|
|
||||||
</Vcs>
|
|
||||||
<FiletypeAssociations>
|
|
||||||
<FiletypeAssociation pattern="*.idl" type="INTERFACES"/>
|
|
||||||
<FiletypeAssociation pattern="*.py" type="SOURCES"/>
|
|
||||||
<FiletypeAssociation pattern="*.py3" type="SOURCES"/>
|
|
||||||
<FiletypeAssociation pattern="*.pyw" type="SOURCES"/>
|
|
||||||
<FiletypeAssociation pattern="*.pyw3" type="SOURCES"/>
|
|
||||||
</FiletypeAssociations>
|
|
||||||
<Documentation>
|
|
||||||
<DocumentationParams>
|
|
||||||
<dict>
|
|
||||||
<key>
|
|
||||||
<string>ERIC4API</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<dict>
|
|
||||||
<key>
|
|
||||||
<string>ignoreDirectories</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string>data</string>
|
|
||||||
<string>deb</string>
|
|
||||||
<string>dist</string>
|
|
||||||
<string>doc</string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>ignoreFilePatterns</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string>setup.py</string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>includePrivate</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<bool>True</bool>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>languages</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string>Python3</string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>outputFile</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<string>revpipycontrol.api</string>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>useRecursion</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<bool>True</bool>
|
|
||||||
</value>
|
|
||||||
</dict>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>ERIC4DOC</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<dict>
|
|
||||||
<key>
|
|
||||||
<string>ignoreDirectories</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string>data</string>
|
|
||||||
<string>deb</string>
|
|
||||||
<string>dist</string>
|
|
||||||
<string>doc</string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>ignoreFilePatterns</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string>setup.py</string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>noindex</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<bool>True</bool>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>outputDirectory</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<string>doc</string>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>qtHelpEnabled</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<bool>False</bool>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>sourceExtensions</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<string></string>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>useRecursion</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<bool>True</bool>
|
|
||||||
</value>
|
|
||||||
</dict>
|
|
||||||
</value>
|
|
||||||
</dict>
|
|
||||||
</DocumentationParams>
|
|
||||||
</Documentation>
|
|
||||||
<Checkers>
|
|
||||||
<CheckersParams>
|
|
||||||
<dict>
|
|
||||||
<key>
|
|
||||||
<string>Pep8Checker</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<dict>
|
|
||||||
<key>
|
|
||||||
<string>DocstringType</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<string>pep257</string>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>ExcludeFiles</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<string></string>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>ExcludeMessages</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<string>E123,E226,E24</string>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>FixCodes</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<string></string>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>FixIssues</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<bool>False</bool>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>HangClosing</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<bool>False</bool>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>IncludeMessages</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<string></string>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>MaxLineLength</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<int>79</int>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>NoFixCodes</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<string>E501</string>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>RepeatMessages</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<bool>True</bool>
|
|
||||||
</value>
|
|
||||||
<key>
|
|
||||||
<string>ShowIgnored</string>
|
|
||||||
</key>
|
|
||||||
<value>
|
|
||||||
<bool>False</bool>
|
|
||||||
</value>
|
|
||||||
</dict>
|
|
||||||
</value>
|
|
||||||
</dict>
|
|
||||||
</CheckersParams>
|
|
||||||
</Checkers>
|
|
||||||
</Project>
|
|
||||||
392
revpipycontrol/aclmanager.py
Normal file
392
revpipycontrol/aclmanager.py
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
u"""Manager für ACL Einträge."""
|
||||||
|
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
|
import tkinter
|
||||||
|
import tkinter.messagebox as tkmsg
|
||||||
|
from mytools import gettrans
|
||||||
|
from shared.ipaclmanager import IpAclManager
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
# Übersetzung laden
|
||||||
|
_ = gettrans()
|
||||||
|
|
||||||
|
|
||||||
|
class AclManager(ttk.Frame):
|
||||||
|
|
||||||
|
u"""Hauptfenster des ACL-Managers."""
|
||||||
|
|
||||||
|
def __init__(self, master, minlevel, maxlevel, acl_str="", readonly=False):
|
||||||
|
u"""Init AclManger-Class.
|
||||||
|
@return None"""
|
||||||
|
super().__init__(master)
|
||||||
|
self.master.bind("<KeyPress-Escape>", self._checkclose)
|
||||||
|
self.master.protocol("WM_DELETE_WINDOW", self._checkclose)
|
||||||
|
self.pack(expand=True, fill="both")
|
||||||
|
|
||||||
|
# Daten laden
|
||||||
|
self.__acl = IpAclManager(minlevel, maxlevel, acl_str)
|
||||||
|
self.__dict_acltext = {}
|
||||||
|
self.__oldacl = self.__acl.acl
|
||||||
|
self.__ro = "disabled" if readonly else "normal"
|
||||||
|
self.maxlevel = maxlevel
|
||||||
|
self.minlevel = minlevel
|
||||||
|
|
||||||
|
# Fenster bauen
|
||||||
|
self._createwidgets()
|
||||||
|
|
||||||
|
def __get_acltext(self):
|
||||||
|
"""Getter fuer Leveltexte.
|
||||||
|
@return Leveltexte als <class 'dict'>"""
|
||||||
|
return self.__dict_acltext.copy()
|
||||||
|
|
||||||
|
def __set_acltext(self, value):
|
||||||
|
"""Setter fuer Leveltexte.
|
||||||
|
@param value Leveltexte als <class 'dict'>"""
|
||||||
|
if type(value) != dict:
|
||||||
|
raise ValueError("value must be <class 'dict'>")
|
||||||
|
self.__dict_acltext = value.copy()
|
||||||
|
|
||||||
|
# Infotexte aufbauen
|
||||||
|
self.aclinfo.destroy()
|
||||||
|
self.aclinfo = ttk.Frame(self)
|
||||||
|
for acltext in self.__dict_acltext:
|
||||||
|
lbl = ttk.Label(self.aclinfo)
|
||||||
|
lbl["text"] = _("Level") + " {id}: {text}".format(
|
||||||
|
id=acltext, text=self.__dict_acltext[acltext]
|
||||||
|
)
|
||||||
|
lbl.pack(anchor="w")
|
||||||
|
|
||||||
|
self.aclinfo.pack(anchor="w", padx=4, pady=4)
|
||||||
|
|
||||||
|
def _changesdone(self):
|
||||||
|
u"""Prüft ob sich die Einstellungen geändert haben.
|
||||||
|
@return True, wenn min. eine Einstellung geändert wurde"""
|
||||||
|
return not self.__acl.acl == self.__oldacl
|
||||||
|
|
||||||
|
def _checkclose(self, event=None):
|
||||||
|
u"""Prüft ob Fenster beendet werden soll.
|
||||||
|
@param event tkinter-Event"""
|
||||||
|
ask = True
|
||||||
|
if self._changesdone():
|
||||||
|
ask = tkmsg.askyesno(
|
||||||
|
_("Question"),
|
||||||
|
_("Do you really want to quit? \nUnsaved changes will "
|
||||||
|
"be lost"),
|
||||||
|
parent=self.master, default="no"
|
||||||
|
)
|
||||||
|
|
||||||
|
if ask:
|
||||||
|
self.master.destroy()
|
||||||
|
|
||||||
|
def _createwidgets(self):
|
||||||
|
u"""Erstellt Widgets."""
|
||||||
|
self.master.wm_title(_("IP access control list"))
|
||||||
|
self.master.wm_resizable(width=False, height=False)
|
||||||
|
|
||||||
|
cpadw = {"padx": 4, "pady": 2, "sticky": "w"}
|
||||||
|
|
||||||
|
# Gruppe Bestehende ACL ######################################
|
||||||
|
gb_acl = ttk.LabelFrame(self)
|
||||||
|
gb_acl["text"] = _("Existing ACLs")
|
||||||
|
gb_acl.columnconfigure(0, weight=1)
|
||||||
|
gb_acl.columnconfigure(1, weight=1)
|
||||||
|
gb_acl.pack(expand=True, fill="both", padx=4, pady=4)
|
||||||
|
|
||||||
|
row = 0
|
||||||
|
frame = ttk.Frame(gb_acl)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
scb_acl = ttk.Scrollbar(frame)
|
||||||
|
|
||||||
|
self.trv_acl = ttk.Treeview(frame)
|
||||||
|
self.trv_acl["columns"] = ("level")
|
||||||
|
self.trv_acl["yscrollcommand"] = scb_acl.set
|
||||||
|
self.trv_acl.heading("level", text=_("Access level"))
|
||||||
|
self.trv_acl.column("level", width=100)
|
||||||
|
self.trv_acl.bind("<<TreeviewSelect>>", self._status_editremove)
|
||||||
|
self._refreshacls()
|
||||||
|
self.trv_acl.grid(row=0, column=0, sticky="we")
|
||||||
|
|
||||||
|
scb_acl["command"] = self.trv_acl.yview
|
||||||
|
scb_acl.grid(row=0, column=1, sticky="ns")
|
||||||
|
|
||||||
|
frame.grid(row=row, columnspan=2, sticky="we")
|
||||||
|
|
||||||
|
row = 1
|
||||||
|
|
||||||
|
# Edit / Remove button
|
||||||
|
self.btn_edit = ttk.Button(gb_acl)
|
||||||
|
self.btn_edit["command"] = self._loadfields
|
||||||
|
self.btn_edit["text"] = _("load entry")
|
||||||
|
self.btn_edit["state"] = "disabled"
|
||||||
|
self.btn_edit.grid(row=row, column=0, pady=4)
|
||||||
|
|
||||||
|
self.btn_remove = ttk.Button(gb_acl)
|
||||||
|
self.btn_remove["command"] = self._ask_delete
|
||||||
|
self.btn_remove["text"] = _("remove entry")
|
||||||
|
self.btn_remove["state"] = "disabled"
|
||||||
|
self.btn_remove.grid(row=row, column=1, pady=4)
|
||||||
|
|
||||||
|
# ############################################################
|
||||||
|
|
||||||
|
# Gruppe Bearbeiten ##########################################
|
||||||
|
gb_edit = ttk.LabelFrame(self)
|
||||||
|
gb_edit["text"] = _("Edit acess control list")
|
||||||
|
gb_edit.pack(expand=True, fill="both", padx=4, pady=4)
|
||||||
|
|
||||||
|
frame = ttk.Frame(gb_edit)
|
||||||
|
frame.grid()
|
||||||
|
|
||||||
|
row = 0
|
||||||
|
lbl = ttk.Label(frame)
|
||||||
|
lbl["text"] = _("IP address") + ": "
|
||||||
|
lbl.grid(row=row, column=0, **cpadw)
|
||||||
|
|
||||||
|
# Variablen IP / Level
|
||||||
|
self.var_ip1 = tkinter.StringVar(frame)
|
||||||
|
self.var_ip2 = tkinter.StringVar(frame)
|
||||||
|
self.var_ip3 = tkinter.StringVar(frame)
|
||||||
|
self.var_ip4 = tkinter.StringVar(frame)
|
||||||
|
self.var_acl = tkinter.StringVar(frame, self.minlevel)
|
||||||
|
|
||||||
|
ip_block1 = ttk.Entry(frame, width=4)
|
||||||
|
ip_block2 = ttk.Entry(frame, width=4)
|
||||||
|
ip_block3 = ttk.Entry(frame, width=4)
|
||||||
|
ip_block4 = ttk.Entry(frame, width=4)
|
||||||
|
|
||||||
|
ip_block1.bind(
|
||||||
|
"<KeyRelease>",
|
||||||
|
lambda event, tkvar=self.var_ip1: self._checkdot(
|
||||||
|
event, tkvar, ip_block2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ip_block1["state"] = self.__ro
|
||||||
|
ip_block1["textvariable"] = self.var_ip1
|
||||||
|
ip_block1.grid(row=row, column=1)
|
||||||
|
|
||||||
|
ip_block2.bind(
|
||||||
|
"<KeyRelease>",
|
||||||
|
lambda event, tkvar=self.var_ip2: self._checkdot(
|
||||||
|
event, tkvar, ip_block3
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ip_block2.bind(
|
||||||
|
"<Key>",
|
||||||
|
lambda event, tkvar=self.var_ip2: self._checkback(
|
||||||
|
event, tkvar, ip_block1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ip_block2["state"] = self.__ro
|
||||||
|
ip_block2["textvariable"] = self.var_ip2
|
||||||
|
ip_block2.grid(row=row, column=3)
|
||||||
|
|
||||||
|
ip_block3.bind(
|
||||||
|
"<KeyRelease>",
|
||||||
|
lambda event, tkvar=self.var_ip3: self._checkdot(
|
||||||
|
event, tkvar, ip_block4
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ip_block3.bind(
|
||||||
|
"<Key>",
|
||||||
|
lambda event, tkvar=self.var_ip3: self._checkback(
|
||||||
|
event, tkvar, ip_block2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ip_block3["state"] = self.__ro
|
||||||
|
ip_block3["textvariable"] = self.var_ip3
|
||||||
|
ip_block3.grid(row=row, column=5)
|
||||||
|
|
||||||
|
ip_block4.bind(
|
||||||
|
"<KeyRelease>",
|
||||||
|
lambda event, tkvar=self.var_ip4: self._checkdot(
|
||||||
|
event, tkvar, None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ip_block4.bind(
|
||||||
|
"<Key>",
|
||||||
|
lambda event, tkvar=self.var_ip4: self._checkback(
|
||||||
|
event, tkvar, ip_block3
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ip_block4["state"] = self.__ro
|
||||||
|
ip_block4["textvariable"] = self.var_ip4
|
||||||
|
ip_block4.grid(row=row, column=7)
|
||||||
|
|
||||||
|
# Punkte zwischen IP-Feldern
|
||||||
|
for i in range(2, 7, 2):
|
||||||
|
lbl = ttk.Label(frame, text=".")
|
||||||
|
lbl.grid(row=row, column=i)
|
||||||
|
|
||||||
|
row = 1
|
||||||
|
lbl = ttk.Label(frame)
|
||||||
|
lbl["text"] = _("Access level") + ": "
|
||||||
|
lbl.grid(row=row, column=0, **cpadw)
|
||||||
|
|
||||||
|
self.sb_level = tkinter.Spinbox(frame, width=4)
|
||||||
|
self.sb_level["from_"] = self.minlevel
|
||||||
|
self.sb_level["to"] = self.maxlevel
|
||||||
|
self.sb_level["state"] = self.__ro
|
||||||
|
self.sb_level["textvariable"] = self.var_acl
|
||||||
|
self.sb_level.grid(row=row, column=1, columnspan=8, sticky="w")
|
||||||
|
|
||||||
|
# Buttons neben IP-Eintrag
|
||||||
|
self.btn_add = ttk.Button(gb_edit)
|
||||||
|
self.btn_add["command"] = self._savefields
|
||||||
|
self.btn_add["state"] = self.__ro
|
||||||
|
self.btn_add["text"] = _("add to list")
|
||||||
|
self.btn_add.grid(column=0, row=1, sticky="e", padx=4, pady=4)
|
||||||
|
self.btn_clear = ttk.Button(gb_edit)
|
||||||
|
self.btn_clear["command"] = self._clearfields
|
||||||
|
self.btn_clear["state"] = self.__ro
|
||||||
|
self.btn_clear["text"] = _("clear")
|
||||||
|
self.btn_clear.grid(column=1, row=1, padx=4, pady=4)
|
||||||
|
|
||||||
|
# ############################################################
|
||||||
|
|
||||||
|
frame = ttk.Frame(self)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.pack(expand=True, fill="both", pady=4)
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
btn_save = ttk.Button(frame)
|
||||||
|
btn_save["command"] = self._save
|
||||||
|
btn_save["state"] = self.__ro
|
||||||
|
btn_save["text"] = _("Save")
|
||||||
|
btn_save.grid(column=0, row=0)
|
||||||
|
|
||||||
|
btn_close = ttk.Button(frame)
|
||||||
|
btn_close["command"] = self._checkclose
|
||||||
|
btn_close["text"] = _("Close")
|
||||||
|
btn_close.grid(column=1, row=0)
|
||||||
|
|
||||||
|
# Infotexte vorbereiten
|
||||||
|
self.aclinfo = ttk.Frame(self)
|
||||||
|
|
||||||
|
def _ask_delete(self):
|
||||||
|
u"""Löscht ein Eintrag der Liste."""
|
||||||
|
str_acl = self.trv_acl.focus()
|
||||||
|
if str_acl != "":
|
||||||
|
lst_ipacl = str_acl.split()
|
||||||
|
ask = tkmsg.askyesno(
|
||||||
|
_("Question"),
|
||||||
|
_("Do you really want to delete the following item? \n"
|
||||||
|
"\nIP: {0} / Level: {1}").format(*lst_ipacl),
|
||||||
|
parent=self.master, default="no"
|
||||||
|
)
|
||||||
|
if ask:
|
||||||
|
new_acl = self.__acl.acl.replace(
|
||||||
|
"{0},{1}".format(*lst_ipacl), ""
|
||||||
|
).replace(" ", " ")
|
||||||
|
|
||||||
|
if self.__acl.loadacl(new_acl.strip()):
|
||||||
|
# Liste neu aufbauen
|
||||||
|
self._refreshacls()
|
||||||
|
else:
|
||||||
|
tkmsg.showerror(
|
||||||
|
_("Error"),
|
||||||
|
_("Can not delete ACL! Check format."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
|
def _checkback(self, event, tkvar, pretxt):
|
||||||
|
u"""Springt bei Backspace in vorheriges Feld.
|
||||||
|
|
||||||
|
@param event TK Event
|
||||||
|
@param tkvar TK Variable zum prüfen
|
||||||
|
@param nexttxt Vorheriges IP Feld für Fokus
|
||||||
|
|
||||||
|
"""
|
||||||
|
if pretxt is not None and event.keycode == 22 and tkvar.get() == "":
|
||||||
|
pretxt.focus_set()
|
||||||
|
|
||||||
|
def _checkdot(self, event, tkvar, nexttxt):
|
||||||
|
u"""Prüft auf . und geht weiter.
|
||||||
|
|
||||||
|
@param event TK Event
|
||||||
|
@param tkvar TK Variable zum prüfen
|
||||||
|
@param nexttxt Nächstes IP Feld für Fokus
|
||||||
|
|
||||||
|
"""
|
||||||
|
val = tkvar.get()
|
||||||
|
if val.find(".") >= 0:
|
||||||
|
tkvar.set(val[:-1])
|
||||||
|
if nexttxt is not None:
|
||||||
|
nexttxt.focus_set()
|
||||||
|
|
||||||
|
def _clearfields(self):
|
||||||
|
u"""Leert die Eingabefelder."""
|
||||||
|
self.var_ip1.set("")
|
||||||
|
self.var_ip2.set("")
|
||||||
|
self.var_ip3.set("")
|
||||||
|
self.var_ip4.set("")
|
||||||
|
self.var_acl.set(self.minlevel)
|
||||||
|
|
||||||
|
def _loadfields(self):
|
||||||
|
u"""Übernimmt Listeneintrag in Editfelder."""
|
||||||
|
str_acl = self.trv_acl.focus()
|
||||||
|
if str_acl != "":
|
||||||
|
lst_ip, acl = str_acl.split()
|
||||||
|
lst_ip = lst_ip.split(".")
|
||||||
|
self.var_ip1.set(lst_ip[0])
|
||||||
|
self.var_ip2.set(lst_ip[1])
|
||||||
|
self.var_ip3.set(lst_ip[2])
|
||||||
|
self.var_ip4.set(lst_ip[3])
|
||||||
|
self.var_acl.set(acl)
|
||||||
|
|
||||||
|
def _refreshacls(self):
|
||||||
|
u"""Leert die ACL Liste und füllt sie neu."""
|
||||||
|
self.trv_acl.delete(*self.trv_acl.get_children())
|
||||||
|
for tup_acl in self.__acl:
|
||||||
|
self.trv_acl.insert(
|
||||||
|
"", "end", tup_acl, text=tup_acl[0], values=tup_acl[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
def _save(self):
|
||||||
|
u"""Übernimt die Änderungen."""
|
||||||
|
self.__oldacl = self.__acl.acl
|
||||||
|
self._checkclose()
|
||||||
|
|
||||||
|
def _savefields(self):
|
||||||
|
u"""Übernimmt neuen ACL Eintrag."""
|
||||||
|
new_acl = "{0}.{1}.{2}.{3},{4}".format(
|
||||||
|
self.var_ip1.get(),
|
||||||
|
self.var_ip2.get(),
|
||||||
|
self.var_ip3.get(),
|
||||||
|
self.var_ip4.get(),
|
||||||
|
self.var_acl.get()
|
||||||
|
)
|
||||||
|
if self.__acl.loadacl((self.__acl.acl + " " + new_acl).strip()):
|
||||||
|
self._refreshacls()
|
||||||
|
else:
|
||||||
|
tkmsg.showerror(
|
||||||
|
_("Error"),
|
||||||
|
_("Can not load new ACL! Check format."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
|
def _status_editremove(self, tkevt):
|
||||||
|
u"""Setzt state der Buttons."""
|
||||||
|
if self.__ro == "normal":
|
||||||
|
status = "disabled" if self.trv_acl.focus() == "" else "normal"
|
||||||
|
self.btn_edit["state"] = status
|
||||||
|
self.btn_remove["state"] = status
|
||||||
|
|
||||||
|
def get_acl(self):
|
||||||
|
u"""Gibt die Konfigurierten ACL zurück.
|
||||||
|
@return ACL als <class 'str'>"""
|
||||||
|
return self.__oldacl
|
||||||
|
|
||||||
|
acl = property(get_acl)
|
||||||
|
acltext = property(__get_acltext, __set_acltext)
|
||||||
|
|
||||||
|
|
||||||
|
# Debugging
|
||||||
|
if __name__ == "__main__":
|
||||||
|
root = AclManager(tkinter.Tk(), 0, 9, " 192.168.50.100,2 127.0.0.*,1")
|
||||||
|
root.acltext = {0: "Keine Rechte", 1: "Hohe Rechte"}
|
||||||
|
root.mainloop()
|
||||||
1093
revpipycontrol/locale/de/LC_MESSAGES/revpipycontrol.po
Normal file
1093
revpipycontrol/locale/de/LC_MESSAGES/revpipycontrol.po
Normal file
File diff suppressed because it is too large
Load Diff
321
revpipycontrol/mqttmanager.py
Normal file
321
revpipycontrol/mqttmanager.py
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
u"""Optionen für das MQTT System."""
|
||||||
|
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
|
import tkinter
|
||||||
|
import tkinter.messagebox as tkmsg
|
||||||
|
from mytools import gettrans
|
||||||
|
|
||||||
|
# Übersetzung laden
|
||||||
|
_ = gettrans()
|
||||||
|
|
||||||
|
|
||||||
|
class MqttManager(tkinter.Frame):
|
||||||
|
|
||||||
|
u"""Hauptfenster der MQTT-Einstellungen."""
|
||||||
|
|
||||||
|
def __init__(self, master, settings, readonly=False):
|
||||||
|
u"""Init MqttManager-Class.
|
||||||
|
@return None"""
|
||||||
|
if not isinstance(settings, dict):
|
||||||
|
raise ValueError("parameter settings must be <class 'dict'>")
|
||||||
|
if not isinstance(readonly, bool):
|
||||||
|
raise ValueError("parameter readonly must be <class 'bool'>")
|
||||||
|
|
||||||
|
super().__init__(master)
|
||||||
|
self.master.bind("<KeyPress-Escape>", self._checkclose)
|
||||||
|
self.master.protocol("WM_DELETE_WINDOW", self._checkclose)
|
||||||
|
self.pack(expand=True, fill="both")
|
||||||
|
|
||||||
|
# Daten laden
|
||||||
|
self.__ro = "disabled" if readonly else "normal"
|
||||||
|
self.__settings = settings
|
||||||
|
|
||||||
|
# Fenster bauen
|
||||||
|
self._createwidgets()
|
||||||
|
|
||||||
|
def _changesdone(self):
|
||||||
|
u"""Prüft ob sich die Einstellungen geändert haben.
|
||||||
|
@return True, wenn min. eine Einstellung geändert wurde"""
|
||||||
|
return (
|
||||||
|
self.var_basetopic.get() != self.__settings["mqttbasetopic"] or
|
||||||
|
self.var_send_events.get() !=
|
||||||
|
self.__settings["mqttsend_on_event"] or
|
||||||
|
self.var_client_id.get() != self.__settings["mqttclient_id"] or
|
||||||
|
self.var_password.get() != self.__settings["mqttpassword"] or
|
||||||
|
self.var_port.get() != str(self.__settings["mqttport"]) or
|
||||||
|
self.var_tls_set.get() != self.__settings["mqtttls_set"] or
|
||||||
|
self.var_username.get() != self.__settings["mqttusername"] or
|
||||||
|
self.var_broker_address.get() !=
|
||||||
|
self.__settings["mqttbroker_address"] or
|
||||||
|
self.var_sendinterval.get() !=
|
||||||
|
str(self.__settings["mqttsendinterval"]) or
|
||||||
|
self.var_write_outputs.get() !=
|
||||||
|
self.__settings["mqttwrite_outputs"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def _checkclose(self, event=None):
|
||||||
|
u"""Prüft ob Fenster beendet werden soll.
|
||||||
|
@param event tkinter-Event"""
|
||||||
|
ask = True
|
||||||
|
if self._changesdone():
|
||||||
|
ask = tkmsg.askyesno(
|
||||||
|
_("Question"),
|
||||||
|
_("Do you really want to quit? \nUnsaved changes will "
|
||||||
|
"be lost"),
|
||||||
|
parent=self.master, default="no"
|
||||||
|
)
|
||||||
|
|
||||||
|
if ask:
|
||||||
|
self.master.destroy()
|
||||||
|
|
||||||
|
def _createwidgets(self):
|
||||||
|
u"""Erstellt Widgets."""
|
||||||
|
self.master.wm_title(_("MQTT Settings"))
|
||||||
|
self.master.wm_resizable(width=False, height=False)
|
||||||
|
|
||||||
|
# cpade = {"padx": 4, "pady": 2, "sticky": "e"}
|
||||||
|
cpadw = {"padx": 4, "pady": 2, "sticky": "w"}
|
||||||
|
cpadwe = {"padx": 4, "pady": 2, "sticky": "we"}
|
||||||
|
|
||||||
|
# Gruppe MQTT System ######################################
|
||||||
|
|
||||||
|
# Basetopic
|
||||||
|
gb = tkinter.LabelFrame(self)
|
||||||
|
gb["text"] = _("MQTT base topic")
|
||||||
|
gb.columnconfigure(0, weight=1)
|
||||||
|
gb.pack(expand=True, fill="both", padx=4, pady=4)
|
||||||
|
|
||||||
|
self.var_basetopic = tkinter.StringVar(
|
||||||
|
gb, self.__settings["mqttbasetopic"])
|
||||||
|
|
||||||
|
row = 0
|
||||||
|
lbl = tkinter.Label(gb)
|
||||||
|
lbl["text"] = _("Base topic") + ":"
|
||||||
|
lbl.grid(row=row, column=0, **cpadw)
|
||||||
|
|
||||||
|
txt = tkinter.Entry(gb)
|
||||||
|
txt["state"] = self.__ro
|
||||||
|
txt["textvariable"] = self.var_basetopic
|
||||||
|
txt["width"] = 34
|
||||||
|
txt.grid(row=row, column=1, **cpadwe)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
lbl = tkinter.Label(gb)
|
||||||
|
lbl["justify"] = "left"
|
||||||
|
lbl["text"] = _(
|
||||||
|
"""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"""
|
||||||
|
)
|
||||||
|
lbl.grid(row=row, column=0, columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
# Publish settings
|
||||||
|
gb = tkinter.LabelFrame(self)
|
||||||
|
gb["text"] = _("MQTT publish settings")
|
||||||
|
gb.columnconfigure(0, weight=1)
|
||||||
|
gb.pack(expand=True, fill="both", padx=4, pady=4)
|
||||||
|
|
||||||
|
self.var_send_events = tkinter.BooleanVar(
|
||||||
|
gb, self.__settings["mqttsend_on_event"])
|
||||||
|
self.var_sendinterval = tkinter.StringVar(
|
||||||
|
gb, self.__settings["mqttsendinterval"])
|
||||||
|
self.var_write_outputs = tkinter.BooleanVar(
|
||||||
|
gb, self.__settings["mqttwrite_outputs"])
|
||||||
|
|
||||||
|
row = 0
|
||||||
|
lbl = tkinter.Label(gb)
|
||||||
|
lbl["text"] = _("Publish all exported values every n seconds") + ":"
|
||||||
|
lbl.grid(row=row, column=0, **cpadw)
|
||||||
|
sb = tkinter.Spinbox(gb)
|
||||||
|
sb["state"] = self.__ro
|
||||||
|
sb["textvariable"] = self.var_sendinterval
|
||||||
|
sb["width"] = 5
|
||||||
|
sb.grid(row=row, column=1, **cpadw)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
lbl = tkinter.Label(gb)
|
||||||
|
lbl["justify"] = "left"
|
||||||
|
lbl["text"] = _("Topic: \t[basetopic]/io/[ioname]")
|
||||||
|
lbl.grid(row=row, columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
cb = tkinter.Checkbutton(gb)
|
||||||
|
cb["state"] = self.__ro
|
||||||
|
cb["text"] = _("Send exported values immediately on value change")
|
||||||
|
cb["variable"] = self.var_send_events
|
||||||
|
cb.grid(row=row, columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
lbl = tkinter.Label(gb)
|
||||||
|
lbl["justify"] = "left"
|
||||||
|
lbl["text"] = _("Topic: \t[basetopic]/event/[ioname]")
|
||||||
|
lbl.grid(row=row, columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
# Subscribe settings
|
||||||
|
gb = tkinter.LabelFrame(self)
|
||||||
|
gb["text"] = _("MQTT set outputs")
|
||||||
|
gb.columnconfigure(0, weight=1)
|
||||||
|
gb.pack(expand=True, fill="both", padx=4, pady=4)
|
||||||
|
|
||||||
|
row = 0
|
||||||
|
cb = tkinter.Checkbutton(gb)
|
||||||
|
cb["state"] = self.__ro
|
||||||
|
cb["text"] = _("Allow MQTT to to set outputs on Revolution Pi")
|
||||||
|
cb["variable"] = self.var_write_outputs
|
||||||
|
cb.grid(row=row, columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
lbl = tkinter.Label(gb)
|
||||||
|
lbl["justify"] = "left"
|
||||||
|
lbl["text"] = _(
|
||||||
|
"""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: \t[basetopic]/set/[outputname]"""
|
||||||
|
)
|
||||||
|
lbl.grid(row=row, columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
# ############################################################
|
||||||
|
|
||||||
|
# Gruppe Broker ##########################################
|
||||||
|
gb = tkinter.LabelFrame(self)
|
||||||
|
gb["text"] = _("MQTT broker settings")
|
||||||
|
gb.pack(expand=True, fill="both", padx=4, pady=4)
|
||||||
|
gb.columnconfigure(2, weight=1)
|
||||||
|
|
||||||
|
# Variablen
|
||||||
|
self.var_client_id = tkinter.StringVar(
|
||||||
|
gb, self.__settings["mqttclient_id"])
|
||||||
|
self.var_broker_address = tkinter.StringVar(
|
||||||
|
gb, self.__settings["mqttbroker_address"])
|
||||||
|
self.var_password = tkinter.StringVar(
|
||||||
|
gb, self.__settings["mqttpassword"])
|
||||||
|
self.var_port = tkinter.StringVar(
|
||||||
|
gb, self.__settings["mqttport"])
|
||||||
|
self.var_tls_set = tkinter.BooleanVar(
|
||||||
|
gb, self.__settings["mqtttls_set"])
|
||||||
|
self.var_username = tkinter.StringVar(
|
||||||
|
gb, self.__settings["mqttusername"])
|
||||||
|
|
||||||
|
row = 0
|
||||||
|
lbl = tkinter.Label(gb)
|
||||||
|
lbl["text"] = _("Broker address") + ":"
|
||||||
|
lbl.grid(row=row, column=0, **cpadw)
|
||||||
|
txt = tkinter.Entry(gb)
|
||||||
|
txt["state"] = self.__ro
|
||||||
|
txt["textvariable"] = self.var_broker_address
|
||||||
|
txt.grid(row=row, column=1, columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
lbl = tkinter.Label(gb)
|
||||||
|
lbl["text"] = _("Broker port") + ":"
|
||||||
|
lbl.grid(row=row, column=0, **cpadw)
|
||||||
|
sb = tkinter.Spinbox(gb)
|
||||||
|
sb["state"] = self.__ro
|
||||||
|
sb["textvariable"] = self.var_port
|
||||||
|
sb["width"] = 6
|
||||||
|
sb.grid(row=row, column=1, **cpadw)
|
||||||
|
|
||||||
|
ckb = tkinter.Checkbutton(gb)
|
||||||
|
ckb["state"] = self.__ro
|
||||||
|
ckb["text"] = _("Use TLS") + ":"
|
||||||
|
ckb["variable"] = self.var_tls_set
|
||||||
|
ckb.grid(row=row, column=2, **cpadw)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
lbl = tkinter.Label(gb)
|
||||||
|
lbl["text"] = _("Username") + ":"
|
||||||
|
lbl.grid(row=row, column=0, **cpadw)
|
||||||
|
txt = tkinter.Entry(gb)
|
||||||
|
txt["state"] = self.__ro
|
||||||
|
txt["textvariable"] = self.var_username
|
||||||
|
txt.grid(row=row, column=1, columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
lbl = tkinter.Label(gb)
|
||||||
|
lbl["text"] = _("Password") + ":"
|
||||||
|
lbl.grid(row=row, column=0, **cpadw)
|
||||||
|
txt = tkinter.Entry(gb)
|
||||||
|
txt["state"] = self.__ro
|
||||||
|
txt["textvariable"] = self.var_password
|
||||||
|
txt.grid(row=row, column=1, columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
lbl = tkinter.Label(gb)
|
||||||
|
lbl["text"] = _("Client ID") + ":"
|
||||||
|
lbl.grid(row=row, column=0, **cpadw)
|
||||||
|
txt = tkinter.Entry(gb)
|
||||||
|
txt["state"] = self.__ro
|
||||||
|
txt["textvariable"] = self.var_client_id
|
||||||
|
txt["width"] = 30
|
||||||
|
txt.grid(row=row, column=1, columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
# ############################################################
|
||||||
|
|
||||||
|
frame = tkinter.Frame(self)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.pack(expand=True, fill="both", pady=4)
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
btn_save = tkinter.Button(frame)
|
||||||
|
btn_save["command"] = self._save
|
||||||
|
btn_save["state"] = self.__ro
|
||||||
|
btn_save["text"] = _("Save")
|
||||||
|
btn_save.grid(column=0, row=0)
|
||||||
|
|
||||||
|
btn_close = tkinter.Button(frame)
|
||||||
|
btn_close["command"] = self._checkclose
|
||||||
|
btn_close["text"] = _("Close")
|
||||||
|
btn_close.grid(column=1, row=0)
|
||||||
|
|
||||||
|
def _save(self):
|
||||||
|
u"""Übernimt die Änderungen."""
|
||||||
|
|
||||||
|
# TODO: Wertprüfung
|
||||||
|
|
||||||
|
# Wertübernahme
|
||||||
|
self.__settings["mqttbasetopic"] = self.var_basetopic.get()
|
||||||
|
self.__settings["mqttsendinterval"] = int(self.var_sendinterval.get())
|
||||||
|
self.__settings["mqttsend_on_event"] = int(self.var_send_events.get())
|
||||||
|
self.__settings["mqttwrite_outputs"] = \
|
||||||
|
int(self.var_write_outputs.get())
|
||||||
|
self.__settings["mqttbroker_address"] = self.var_broker_address.get()
|
||||||
|
self.__settings["mqtttls_set"] = int(self.var_tls_set.get())
|
||||||
|
self.__settings["mqttport"] = int(self.var_port.get())
|
||||||
|
self.__settings["mqttusername"] = self.var_username.get()
|
||||||
|
self.__settings["mqttpassword"] = self.var_password.get()
|
||||||
|
self.__settings["mqttclient_id"] = self.var_client_id.get()
|
||||||
|
|
||||||
|
self._checkclose()
|
||||||
|
|
||||||
|
def get_settings(self):
|
||||||
|
u"""Gibt die MQTT Konfiguration zurück.
|
||||||
|
@return Settings als <class 'dict'>"""
|
||||||
|
return self.__settings
|
||||||
|
|
||||||
|
settings = property(get_settings)
|
||||||
|
|
||||||
|
|
||||||
|
# Debugging
|
||||||
|
if __name__ == "__main__":
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
root = MqttManager(tkinter.Tk(), dict_mqttsettings)
|
||||||
|
root.mainloop()
|
||||||
70
revpipycontrol/mytools.py
Normal file
70
revpipycontrol/mytools.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Tools-Sammlung."""
|
||||||
|
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
|
import gettext
|
||||||
|
import locale
|
||||||
|
import sys
|
||||||
|
from os import environ
|
||||||
|
from os.path import dirname
|
||||||
|
from os.path import join as pathjoin
|
||||||
|
from sys import platform
|
||||||
|
|
||||||
|
# Systemwerte und SaveFiles
|
||||||
|
# TODO: Mac einbauen
|
||||||
|
if platform == "linux":
|
||||||
|
homedir = environ["HOME"]
|
||||||
|
else:
|
||||||
|
homedir = environ["APPDATA"]
|
||||||
|
|
||||||
|
savefile_connections = pathjoin(
|
||||||
|
homedir, ".revpipyplc", "connections.dat")
|
||||||
|
savefile_developer = pathjoin(
|
||||||
|
homedir, ".revpipyplc", "developer.dat")
|
||||||
|
savefile_programpath = pathjoin(
|
||||||
|
homedir, ".revpipyplc", "programpath.dat")
|
||||||
|
|
||||||
|
|
||||||
|
def addroot(filename):
|
||||||
|
u"""Hängt root-dir der Anwendung vor Dateinamen.
|
||||||
|
|
||||||
|
Je nach Ausführungsart der Anwendung muss das root-dir über
|
||||||
|
andere Arten abgerufen werden.
|
||||||
|
|
||||||
|
@param filename Datei oder Ordnername
|
||||||
|
@return root dir
|
||||||
|
|
||||||
|
"""
|
||||||
|
if getattr(sys, "frozen", False):
|
||||||
|
return pathjoin(dirname(sys.executable), filename)
|
||||||
|
else:
|
||||||
|
return pathjoin(dirname(__file__), filename)
|
||||||
|
|
||||||
|
|
||||||
|
def gettrans(proglang=None):
|
||||||
|
u"""Wertet die Sprache des OS aus und gibt Übersetzung zurück.
|
||||||
|
|
||||||
|
@param proglang Bestimmte Sprache laden
|
||||||
|
@return gettext Übersetzung für Zuweisung an '_'
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Sprache auswählen
|
||||||
|
if proglang is None:
|
||||||
|
# Autodetect Language or switch to static
|
||||||
|
proglang = locale.getdefaultlocale()[0]
|
||||||
|
if proglang is not None and proglang.find("_") >= 0:
|
||||||
|
proglang = proglang.split('_')[0]
|
||||||
|
else:
|
||||||
|
proglang = "en"
|
||||||
|
|
||||||
|
# Übersetzungen laden
|
||||||
|
trans = gettext.translation(
|
||||||
|
"revpipycontrol",
|
||||||
|
addroot("locale"),
|
||||||
|
languages=[proglang],
|
||||||
|
fallback=True
|
||||||
|
)
|
||||||
|
return trans.gettext
|
||||||
@@ -1,224 +1,498 @@
|
|||||||
#
|
# -*- coding: utf-8 -*-
|
||||||
# RevPiPyControl
|
u"""Fenstererweiterung für den 'watch modus'.
|
||||||
#
|
|
||||||
# Webpage: https://revpimodio.org/revpipyplc/
|
Thranks to: http://stackoverflow.com/questions/3085696/adding-a-
|
||||||
# (c) Sven Sager, License: LGPLv3
|
scrollbar-to-a-group-of-widgets-in-tkinter
|
||||||
#
|
|
||||||
# Thranks to: http://stackoverflow.com/questions/3085696/adding-a-
|
"""
|
||||||
# scrollbar-to-a-group-of-widgets-in-tkinter
|
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
import pickle
|
import pickle
|
||||||
import tkinter
|
import tkinter
|
||||||
from argparse import ArgumentParser
|
import tkinter.messagebox as tkmsg
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from mytools import gettrans
|
||||||
from time import sleep
|
from threading import Lock
|
||||||
from xmlrpc.client import ServerProxy, Binary, MultiCall
|
from xmlrpc.client import MultiCall
|
||||||
|
|
||||||
|
# Übersetzung laden
|
||||||
|
_ = gettrans()
|
||||||
|
|
||||||
|
|
||||||
class RevPiCheckClient(tkinter.Frame):
|
class RevPiCheckClient(tkinter.Frame):
|
||||||
|
|
||||||
def __init__(self, master, xmlcli):
|
u"""Baut Fenstererweiterung für 'watch modus'."""
|
||||||
|
|
||||||
|
def __init__(self, master, xmlcli, xmlmode=0):
|
||||||
"""Instantiiert MyApp-Klasse."""
|
"""Instantiiert MyApp-Klasse."""
|
||||||
super().__init__(master)
|
super().__init__(master)
|
||||||
self.pack(fill="both", expand=True)
|
|
||||||
|
|
||||||
|
# XML-Daten abrufen
|
||||||
|
self.xmlmode = xmlmode
|
||||||
self.cli = xmlcli
|
self.cli = xmlcli
|
||||||
|
self.cli.psstart()
|
||||||
|
self.lst_devices = self.cli.ps_devices()
|
||||||
|
self.dict_devices = {v[0]: v[1] for v in self.lst_devices}
|
||||||
|
self.lst_devices = [d[0] for d in self.lst_devices]
|
||||||
|
self.dict_inps = pickle.loads(self.cli.ps_inps().data)
|
||||||
|
self.dict_outs = pickle.loads(self.cli.ps_outs().data)
|
||||||
|
self.err_workvalues = 0
|
||||||
|
self.max_errors = 25
|
||||||
|
|
||||||
self.lst_devices = self.cli.get_devicenames()
|
self.lk = Lock()
|
||||||
self.lst_group = []
|
self.dict_wins = {}
|
||||||
self.dict_inpvar = {}
|
self.__checkwrite = True
|
||||||
self.dict_outvar = {}
|
self.__lockedvar = None
|
||||||
|
self.__oldvalue = None
|
||||||
|
|
||||||
self.autorw = tkinter.BooleanVar()
|
self.autorw = tkinter.BooleanVar()
|
||||||
self.fut_autorw = None
|
self.dowrite = tkinter.BooleanVar()
|
||||||
|
|
||||||
# Fenster aufbauen
|
# Fenster aufbauen
|
||||||
self._createwidgets()
|
self._createwidgets()
|
||||||
|
|
||||||
# Aktuelle Werte einlesen
|
# Aktuelle Werte einlesen
|
||||||
self.readvalues()
|
self.refreshvalues()
|
||||||
|
|
||||||
def _autorw(self):
|
def __chval(self, device, io, event=None):
|
||||||
dict_inp = {}
|
u"""Schreibt neuen Output Wert auf den RevPi."""
|
||||||
dict_out = {}
|
if self.dowrite.get() and self._warnwrite():
|
||||||
|
with self.lk:
|
||||||
|
self.validatereturn(
|
||||||
|
self.cli.ps_setvalue(device, io[0], io[5].get())
|
||||||
|
)
|
||||||
|
|
||||||
while self.autorw.get():
|
# Alles neu einlesen wenn nicht AutoRW aktiv ist
|
||||||
for dev in self.lst_devices:
|
if not self.autorw.get():
|
||||||
try:
|
self.refreshvalues()
|
||||||
dict_out[dev] = [
|
|
||||||
value[8].get() for value in self.dict_outvar[dev]
|
|
||||||
]
|
|
||||||
except:
|
|
||||||
print("lasse {} aus".format(dev))
|
|
||||||
|
|
||||||
dict_inp = self.cli.refreshvalues(
|
self.__lockedvar = None
|
||||||
Binary(pickle.dumps(dict_out, 3))
|
|
||||||
|
def __hidewin(self, win, event=None):
|
||||||
|
u"""Verbergt übergebenes Fenster.
|
||||||
|
@param win Fenster zum verbergen
|
||||||
|
@param event Tkinter Event"""
|
||||||
|
win.withdraw()
|
||||||
|
|
||||||
|
def __saveoldvalue(self, event, tkvar):
|
||||||
|
u"""Speichert bei Keypress aktuellen Wert für wiederherstellung."""
|
||||||
|
if self.__lockedvar is None:
|
||||||
|
self.__lockedvar = tkvar
|
||||||
|
try:
|
||||||
|
self.__oldvalue = tkvar.get()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __showwin(self, win):
|
||||||
|
u"""Zeigt oder verbergt übergebenes Fenster.
|
||||||
|
@param win Fenster zum anzeigen/verbergen"""
|
||||||
|
if win.winfo_viewable():
|
||||||
|
win.withdraw()
|
||||||
|
else:
|
||||||
|
win.deiconify()
|
||||||
|
|
||||||
|
def __spinboxkey(self, device, io, event=None):
|
||||||
|
u"""Prüft die Eingabe auf plausibilität.
|
||||||
|
@param event tkinter Event
|
||||||
|
@param io IO Liste mit tkinter Variable"""
|
||||||
|
# io = [name,bytelen,byteaddr,bmk,bitaddress,(tkinter_var)]
|
||||||
|
try:
|
||||||
|
newvalue = io[5].get()
|
||||||
|
# Wertebereich prüfen
|
||||||
|
if not self.minint(io) <= newvalue <= self.maxint(io):
|
||||||
|
raise ValueError("value not valid")
|
||||||
|
|
||||||
|
self.__chval(device, io)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
io[5].set(self.__oldvalue)
|
||||||
|
tkmsg.showerror(
|
||||||
|
_("Error"),
|
||||||
|
_("Given value for Output '{0}' is not valid! \n"
|
||||||
|
"Reset to '{1}'").format(
|
||||||
|
self.dict_devices[device],
|
||||||
|
self.__oldvalue
|
||||||
|
),
|
||||||
|
parent=self.dict_wins[device]
|
||||||
)
|
)
|
||||||
dict_inp = pickle.loads(dict_inp.data)
|
|
||||||
|
|
||||||
for dev in dict_inp:
|
# Focus zurücksetzen
|
||||||
for io in self.dict_inpvar[dev]:
|
event.widget.focus_set()
|
||||||
try:
|
|
||||||
io[8].set(dict_inp[dev].pop(0))
|
|
||||||
except:
|
|
||||||
print("lasse {} aus".format(io[0]))
|
|
||||||
|
|
||||||
sleep(0.1)
|
|
||||||
|
|
||||||
def onfrmconf(self, canvas):
|
|
||||||
canvas.configure(scrollregion=canvas.bbox("all"))
|
|
||||||
|
|
||||||
def _createiogroup(self, device, frame, iotype):
|
def _createiogroup(self, device, frame, iotype):
|
||||||
"""Erstellt IO-Gruppen."""
|
u"""Erstellt IO-Gruppen.
|
||||||
# IOs generieren
|
|
||||||
canvas = tkinter.Canvas(frame, borderwidth=0, width=180, heigh=800)
|
@param device Deviceposition
|
||||||
|
@param frame tkinter Frame
|
||||||
|
@param iotype 'inp' oder 'out' als str()
|
||||||
|
|
||||||
|
"""
|
||||||
|
# IO-Typen festlegen
|
||||||
|
if iotype == "inp":
|
||||||
|
lst_io = self.dict_inps[device]
|
||||||
|
else:
|
||||||
|
lst_io = self.dict_outs[device]
|
||||||
|
|
||||||
|
# Fensterinhalt aufbauen
|
||||||
|
calc_heigh = len(lst_io) * 21
|
||||||
|
canvas = tkinter.Canvas(
|
||||||
|
frame,
|
||||||
|
borderwidth=0,
|
||||||
|
width=190,
|
||||||
|
heigh=calc_heigh if calc_heigh <= 600 else 600
|
||||||
|
)
|
||||||
s_frame = tkinter.Frame(canvas)
|
s_frame = tkinter.Frame(canvas)
|
||||||
|
s_frame.columnconfigure(1, weight=1)
|
||||||
vsb = tkinter.Scrollbar(frame, orient="vertical", command=canvas.yview)
|
vsb = tkinter.Scrollbar(frame, orient="vertical", command=canvas.yview)
|
||||||
canvas.configure(yscrollcommand=vsb.set)
|
canvas.configure(yscrollcommand=vsb.set)
|
||||||
|
|
||||||
|
# Scrollrad Linux
|
||||||
|
canvas.bind(
|
||||||
|
"<ButtonPress-4>",
|
||||||
|
lambda x: canvas.yview_scroll(-1, "units")
|
||||||
|
)
|
||||||
|
canvas.bind(
|
||||||
|
"<ButtonPress-5>",
|
||||||
|
lambda x: canvas.yview_scroll(1, "units")
|
||||||
|
)
|
||||||
|
|
||||||
vsb.pack(side="right", fill="y")
|
vsb.pack(side="right", fill="y")
|
||||||
canvas.pack(side="left", fill="both", expand=True)
|
canvas.pack(side="left", fill="both", expand=True)
|
||||||
|
|
||||||
canvas.create_window((4, 4), window=s_frame, anchor="nw")
|
canvas.create_window((4, 4), window=s_frame, anchor="nw")
|
||||||
s_frame.bind(
|
s_frame.bind(
|
||||||
"<Configure>", lambda event, canvas=canvas: self.onfrmconf(canvas)
|
"<Configure>", lambda event, canvas=canvas: self._onfrmconf(canvas)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# IOs generieren
|
||||||
rowcount = 0
|
rowcount = 0
|
||||||
for io in self.cli.get_iolist(device, iotype):
|
for io in lst_io:
|
||||||
# io = [name,default,anzbits,adressbyte,export,adressid,bmk,bitaddress,tkinter_var]
|
# io = [name,blen,baddr,bmk,bitaddr,(tkinter_var),border,signed]
|
||||||
|
|
||||||
tkinter.Label(s_frame, text=io[0]).grid(
|
tkinter.Label(s_frame, text=io[0]).grid(
|
||||||
column=0, row=rowcount, sticky="w"
|
column=1, row=rowcount, sticky="w"
|
||||||
)
|
)
|
||||||
|
|
||||||
if io[7] >= 0:
|
if io[4] >= 0:
|
||||||
var = tkinter.BooleanVar()
|
var = tkinter.BooleanVar()
|
||||||
check = tkinter.Checkbutton(s_frame)
|
check = tkinter.Checkbutton(s_frame)
|
||||||
|
check["command"] = \
|
||||||
|
lambda device=device, io=io: self.__chval(device, io)
|
||||||
check["state"] = "disabled" if iotype == "inp" else "normal"
|
check["state"] = "disabled" if iotype == "inp" else "normal"
|
||||||
check["text"] = ""
|
check["text"] = ""
|
||||||
check["variable"] = var
|
check["variable"] = var
|
||||||
check.grid(column=1, row=rowcount)
|
check.grid(column=0, row=rowcount)
|
||||||
else:
|
else:
|
||||||
var = tkinter.IntVar()
|
var = tkinter.IntVar()
|
||||||
txt = tkinter.Spinbox(s_frame, to=256)
|
txt = tkinter.Spinbox(
|
||||||
txt["state"] = "disabled" if iotype == "inp" else "normal"
|
s_frame,
|
||||||
txt["width"] = 4
|
from_=self.minint(io),
|
||||||
|
to=self.maxint(io),
|
||||||
|
)
|
||||||
|
txt.bind(
|
||||||
|
"<Key>",
|
||||||
|
lambda event, tkvar=var: self.__saveoldvalue(event, tkvar)
|
||||||
|
)
|
||||||
|
txt.bind(
|
||||||
|
"<FocusOut>",
|
||||||
|
lambda event, device=device, io=io:
|
||||||
|
self.__spinboxkey(device, io, event)
|
||||||
|
)
|
||||||
|
txt["command"] = \
|
||||||
|
lambda device=device, io=io: self.__chval(device, io)
|
||||||
|
txt["state"] = "disabled" if iotype == "inp" or \
|
||||||
|
self.maxint(io) == 0 else "normal"
|
||||||
|
width = len(str(self.maxint(io))) + 1
|
||||||
|
txt["width"] = 7 if width > 7 else width
|
||||||
txt["textvariable"] = var
|
txt["textvariable"] = var
|
||||||
txt.grid(column=1, row=rowcount)
|
txt.grid(column=0, row=rowcount)
|
||||||
|
|
||||||
# Steuerelementvariable in IO übernehmen
|
# Steuerelementvariable in IO übernehmen (mutabel)
|
||||||
io.append(var)
|
io.insert(5, var)
|
||||||
if iotype == "inp":
|
|
||||||
self.dict_inpvar[device].append(io)
|
|
||||||
elif iotype == "out":
|
|
||||||
self.dict_outvar[device].append(io)
|
|
||||||
|
|
||||||
rowcount += 1
|
rowcount += 1
|
||||||
|
s_frame.update()
|
||||||
|
|
||||||
def _createwidgets(self):
|
def _createwidgets(self):
|
||||||
"""Erstellt den Fensterinhalt."""
|
"""Erstellt den Fensterinhalt."""
|
||||||
# Hauptfenster
|
cfxpxy53 = {"fill": "x", "padx": 5, "pady": 3}
|
||||||
self.master.wm_title("RevPi Onlineview")
|
|
||||||
|
devgrp = tkinter.LabelFrame(self)
|
||||||
|
devgrp["text"] = _("Devices of RevPi")
|
||||||
|
devgrp.pack(expand=True, fill="both", side="left")
|
||||||
|
|
||||||
for dev in self.lst_devices:
|
for dev in self.lst_devices:
|
||||||
# Variablen vorbereiten
|
win = tkinter.Toplevel(self)
|
||||||
self.dict_inpvar[dev] = []
|
win.wm_title("{0} | {1}".format(dev, self.dict_devices[dev]))
|
||||||
self.dict_outvar[dev] = []
|
win.protocol(
|
||||||
|
"WM_DELETE_WINDOW",
|
||||||
|
lambda win=win: self.__hidewin(win)
|
||||||
|
)
|
||||||
|
win.withdraw()
|
||||||
|
self.dict_wins[dev] = win
|
||||||
|
|
||||||
# Devicegruppe erstellen
|
# Devicegruppe erstellen
|
||||||
group = tkinter.LabelFrame(self)
|
group = tkinter.LabelFrame(win)
|
||||||
group["text"] = dev
|
group["text"] = self.dict_devices[dev]
|
||||||
group.pack(side="left", fill="both", expand=True)
|
group.pack(side="left", fill="both", expand=True)
|
||||||
self.lst_group.append(group)
|
|
||||||
|
|
||||||
for iotype in ["inp", "out"]:
|
for iotype in ["inp", "out"]:
|
||||||
frame = tkinter.Frame(group)
|
frame = tkinter.Frame(group)
|
||||||
frame.pack(side="left", fill="both", expand=True)
|
frame.pack(side="left", fill="both", expand=True)
|
||||||
|
frame.update()
|
||||||
self._createiogroup(dev, frame, iotype)
|
self._createiogroup(dev, frame, iotype)
|
||||||
|
|
||||||
# self.btn_update = tkinter.Button(self)
|
# Button erstellen
|
||||||
# self.btn_update["text"] = "UPDATE"
|
btn = tkinter.Button(devgrp)
|
||||||
# self.btn_update["command"] = self._autorw
|
btn["command"] = lambda win=win: self.__showwin(win)
|
||||||
# self.btn_update.pack(anchor="s", side="bottom", fill="x")
|
btn["text"] = "{0} | {1}".format(dev, self.dict_devices[dev])
|
||||||
|
btn.pack(**cfxpxy53)
|
||||||
|
|
||||||
self.btn_write = tkinter.Button(self)
|
# Steuerungsfunktionen
|
||||||
self.btn_write["text"] = "SCHREIBEN"
|
cntgrp = tkinter.LabelFrame(self)
|
||||||
self.btn_write["command"] = self.writevalues
|
cntgrp["text"] = _("Control")
|
||||||
self.btn_write.pack(side="bottom", fill="x")
|
cntgrp.pack(expand=True, fill="both", side="right")
|
||||||
|
|
||||||
self.btn_read = tkinter.Button(self)
|
self.btn_refresh = tkinter.Button(cntgrp)
|
||||||
self.btn_read["text"] = "LESEN"
|
self.btn_refresh["text"] = _("Read all IOs")
|
||||||
|
self.btn_refresh["command"] = self.refreshvalues
|
||||||
|
self.btn_refresh.pack(**cfxpxy53)
|
||||||
|
|
||||||
|
self.btn_read = tkinter.Button(cntgrp)
|
||||||
|
self.btn_read["text"] = _("Read just Inputs")
|
||||||
self.btn_read["command"] = self.readvalues
|
self.btn_read["command"] = self.readvalues
|
||||||
self.btn_read.pack(side="bottom", fill="x")
|
self.btn_read.pack(**cfxpxy53)
|
||||||
|
|
||||||
check = tkinter.Checkbutton(self)
|
self.btn_write = tkinter.Button(cntgrp)
|
||||||
check["command"] = self.toggleauto
|
self.btn_write["state"] = "normal" if self.xmlmode >= 3 \
|
||||||
check["text"] = "autoupdate"
|
else "disabled"
|
||||||
check["variable"] = self.autorw
|
self.btn_write["text"] = _("Write Outputs")
|
||||||
check.pack(side="bottom")
|
self.btn_write["command"] = self.writevalues
|
||||||
|
self.btn_write.pack(**cfxpxy53)
|
||||||
|
|
||||||
def _readvaluesdev(self, device, iotype):
|
self.chk_auto = tkinter.Checkbutton(cntgrp)
|
||||||
"""Ruft alle aktuellen Werte fuer das Device ab."""
|
self.chk_auto["command"] = self.toggleauto
|
||||||
# Multicall vorbereiten
|
self.chk_auto["text"] = _("Autorefresh values")
|
||||||
mc_values = MultiCall(self.cli)
|
self.chk_auto["variable"] = self.autorw
|
||||||
|
self.chk_auto.pack(anchor="w")
|
||||||
|
|
||||||
if iotype == "inp":
|
self.chk_dowrite = tkinter.Checkbutton(cntgrp)
|
||||||
lst_ios = self.dict_inpvar[device]
|
self.chk_dowrite["command"] = self.togglewrite
|
||||||
elif iotype == "out":
|
self.chk_dowrite["state"] = "normal" if self.xmlmode >= 3 \
|
||||||
lst_ios = self.dict_outvar[device]
|
and self.autorw.get() else "disabled"
|
||||||
|
self.chk_dowrite["text"] = _("Write values to RevPi")
|
||||||
|
self.chk_dowrite["variable"] = self.dowrite
|
||||||
|
self.chk_dowrite.pack(anchor="w")
|
||||||
|
|
||||||
for io in lst_ios:
|
def _onfrmconf(self, canvas):
|
||||||
mc_values.get_iovalue(device, io[0])
|
u"""Erstellt Fenster in einem Canvas.
|
||||||
|
@param canvas Canvas in dem Objekte erstellt werden sollen"""
|
||||||
|
canvas.configure(scrollregion=canvas.bbox("all"))
|
||||||
|
|
||||||
i = 0
|
def _warnwrite(self):
|
||||||
for value in mc_values():
|
u"""Warnung für Benutzer über Schreibfunktion einmal fragen.
|
||||||
value = pickle.loads(value.data)
|
@return True, wenn Warnung einmal mit OK bestätigt wurde"""
|
||||||
if type(value) == bytes:
|
if self.__checkwrite:
|
||||||
value = int.from_bytes(value, byteorder="little")
|
self.__checkwrite = not tkmsg.askokcancel(
|
||||||
|
_("Warning"),
|
||||||
|
_("You want to set outputs on the RevPi! Note that these are "
|
||||||
|
"set IMMEDIATELY!!! \nIf another control program is "
|
||||||
|
"running on the RevPi, it could interfere and reset the "
|
||||||
|
"outputs."),
|
||||||
|
icon=tkmsg.WARNING,
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
return not self.__checkwrite
|
||||||
|
|
||||||
lst_ios[i][8].set(value)
|
def _workvalues(self, io_dicts=None, writeout=False):
|
||||||
i += 1
|
u"""Alle Werte der Inputs und Outputs abrufen.
|
||||||
|
|
||||||
def _writevaluesdev(self, device):
|
@param io_dicts Arbeit nur für dieses Dict()
|
||||||
"""Sendet Werte der Outputs fuer ein Device."""
|
@param writeout Änderungen auf RevPi schreiben
|
||||||
# Multicall vorbereiten
|
@return None
|
||||||
mc_values = MultiCall(self.cli)
|
|
||||||
lst_ios = lst_ios = self.dict_outvar[device]
|
|
||||||
|
|
||||||
for io in lst_ios:
|
"""
|
||||||
mc_values.set_iovalue(device, io[0], pickle.dumps(io[8].get(), 3))
|
# Abfragelisten vorbereiten
|
||||||
|
if io_dicts is None:
|
||||||
|
io_dicts = [self.dict_inps, self.dict_outs]
|
||||||
|
|
||||||
# Multicall ausführen
|
# Werte abrufen
|
||||||
mc_values()
|
with self.lk:
|
||||||
|
try:
|
||||||
|
ba_values = bytearray(self.cli.ps_values().data)
|
||||||
|
self.err_workvalues = 0
|
||||||
|
except Exception:
|
||||||
|
if self.autorw.get():
|
||||||
|
self.err_workvalues += 1
|
||||||
|
else:
|
||||||
|
self.err_workvalues = self.max_errors
|
||||||
|
|
||||||
|
if self.err_workvalues >= self.max_errors:
|
||||||
|
# Fenster zerstören bei zu vielen Fehlern
|
||||||
|
self.hideallwindows()
|
||||||
|
if self.autorw.get():
|
||||||
|
self.autorw.set(False)
|
||||||
|
self.toggleauto()
|
||||||
|
self.dowrite.set(False)
|
||||||
|
self.pack_forget()
|
||||||
|
|
||||||
|
tkmsg.showerror(
|
||||||
|
_("Error"),
|
||||||
|
_("To many errors while reading IO data. "
|
||||||
|
"Can not show the Watch-Mode."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Multicall zum Schreiben vorbereiten
|
||||||
|
if writeout:
|
||||||
|
xmlmc = MultiCall(self.cli)
|
||||||
|
|
||||||
|
for dev in self.dict_devices:
|
||||||
|
# io = [name,blen,baddr,bmk,bitaddr,(tkinter_var),border,signed]
|
||||||
|
|
||||||
|
# IO Typ verarbeiten
|
||||||
|
for iotype in io_dicts:
|
||||||
|
# ios verarbeiten
|
||||||
|
for io in iotype[dev]:
|
||||||
|
|
||||||
|
# Gesperrte Variable überspringen
|
||||||
|
if io[5] == self.__lockedvar:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Bytes umwandeln
|
||||||
|
int_byte = int.from_bytes(
|
||||||
|
ba_values[io[2]:io[2] + io[1]],
|
||||||
|
byteorder="little" if len(io) < 7 else io[6],
|
||||||
|
signed=False if len(io) < 8 else io[7],
|
||||||
|
)
|
||||||
|
if io[4] >= 0:
|
||||||
|
# Bit-IO
|
||||||
|
new_val = bool(int_byte & 1 << io[4])
|
||||||
|
if writeout and new_val != io[5].get():
|
||||||
|
xmlmc.ps_setvalue(dev, io[0], io[5].get())
|
||||||
|
else:
|
||||||
|
io[5].set(new_val)
|
||||||
|
else:
|
||||||
|
# Byte-IO
|
||||||
|
if writeout and int_byte != io[5].get():
|
||||||
|
xmlmc.ps_setvalue(dev, io[0], io[5].get())
|
||||||
|
else:
|
||||||
|
io[5].set(int_byte)
|
||||||
|
|
||||||
|
# Werte per Multicall schreiben
|
||||||
|
if writeout:
|
||||||
|
with self.lk:
|
||||||
|
self.validatereturn(xmlmc())
|
||||||
|
|
||||||
|
def hideallwindows(self):
|
||||||
|
u"""Versteckt alle Fenster."""
|
||||||
|
for win in self.dict_wins:
|
||||||
|
self.dict_wins[win].withdraw()
|
||||||
|
|
||||||
|
def maxint(self, io):
|
||||||
|
u"""Errechnet maximalen int() Wert für Bytes max 22.
|
||||||
|
@param io IO-Liste, deren Wert berechnet werden soll
|
||||||
|
@return int() max oder 0 bei Überschreitung"""
|
||||||
|
# io = [name,blen,baddr,bmk,bitaddr,(tkinter_var),border,signed]
|
||||||
|
bytelen = io[1]
|
||||||
|
if bytelen == 0:
|
||||||
|
return 0
|
||||||
|
signed = io[-1] if type(io[-1]) == bool else False
|
||||||
|
return 0 if bytelen > 22 else int.from_bytes(
|
||||||
|
(b'\x7f' if signed else b'\xff') + b'\xff' * (bytelen - 1),
|
||||||
|
byteorder="big"
|
||||||
|
)
|
||||||
|
|
||||||
|
def minint(self, io):
|
||||||
|
u"""Errechnet maximalen int() Wert für Bytes max 22.
|
||||||
|
@param io IO-Liste, deren Wert berechnet werden soll
|
||||||
|
@return int() max oder 0 bei Überschreitung"""
|
||||||
|
# io = [name,blen,baddr,bmk,bitaddr,(tkinter_var),border,signed]
|
||||||
|
bytelen = io[1]
|
||||||
|
if bytelen == 0:
|
||||||
|
return 0
|
||||||
|
signed = io[-1] if type(io[-1]) == bool else False
|
||||||
|
rc = 0 if bytelen > 22 or not signed else int.from_bytes(
|
||||||
|
b'\x80' + b'\x00' * (bytelen - 1),
|
||||||
|
byteorder="big",
|
||||||
|
signed=True
|
||||||
|
)
|
||||||
|
return rc
|
||||||
|
|
||||||
def readvalues(self):
|
def readvalues(self):
|
||||||
"""Alle Werte der Inputs und Outputs abrufen."""
|
u"""Ruft nur Input Werte von RevPi ab und aktualisiert Fenster."""
|
||||||
# Werte aus Prozessabbild einlesen
|
if not self.autorw.get():
|
||||||
self.cli.readprocimg()
|
self._workvalues([self.dict_inps])
|
||||||
|
|
||||||
for dev in self.lst_devices:
|
def refreshvalues(self):
|
||||||
self._readvaluesdev(dev, "inp")
|
u"""Ruft alle IO Werte von RevPi ab und aktualisiert Fenster."""
|
||||||
self._readvaluesdev(dev, "out")
|
if not self.autorw.get():
|
||||||
|
self._workvalues()
|
||||||
|
|
||||||
|
def tmr_workvalues(self):
|
||||||
|
u"""Timer für zyklische Abfrage.
|
||||||
|
@return None"""
|
||||||
|
# Verbleibener Timer könnte schon ungültig sein
|
||||||
|
if not self.autorw.get():
|
||||||
|
try:
|
||||||
|
self.chk_auto["state"] = "normal"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
self._workvalues()
|
||||||
|
|
||||||
|
self.master.after(200, self.tmr_workvalues)
|
||||||
|
|
||||||
def toggleauto(self):
|
def toggleauto(self):
|
||||||
self.btn_read["state"] = "disabled" if self.autorw.get() else "normal"
|
u"""Schaltet zwischen Autorefresh um und aktualisiert Widgets."""
|
||||||
self.btn_write["state"] = "disabled" if self.autorw.get() else "normal"
|
stateval = "disabled" if self.autorw.get() else "normal"
|
||||||
if self.autorw.get() \
|
self.btn_refresh["state"] = stateval
|
||||||
and (self.fut_autorw is None or self.fut_autorw.done()):
|
self.btn_read["state"] = stateval
|
||||||
e = ThreadPoolExecutor(max_workers=1)
|
self.btn_write["state"] = stateval if self.xmlmode >= 3 \
|
||||||
self.fut_autorw = e.submit(self._autorw)
|
else "disabled"
|
||||||
|
|
||||||
|
self.chk_dowrite["state"] = "normal" if self.xmlmode >= 3 \
|
||||||
|
and self.autorw.get() else "disabled"
|
||||||
|
|
||||||
|
if self.autorw.get():
|
||||||
|
self.tmr_workvalues()
|
||||||
|
else:
|
||||||
|
self.chk_auto["state"] = "disabled"
|
||||||
|
self.dowrite.set(False)
|
||||||
|
|
||||||
|
def togglewrite(self):
|
||||||
|
u"""Schaltet zwischen DoWrite um und aktiviert Schreibfunktion."""
|
||||||
|
if self._warnwrite():
|
||||||
|
self.refreshvalues()
|
||||||
|
else:
|
||||||
|
self.dowrite.set(False)
|
||||||
|
|
||||||
|
def validatereturn(self, returnlist):
|
||||||
|
u"""Überprüft die Rückgaben der setvalue Funktion.
|
||||||
|
@param returnlist list() der xml Rückgabe"""
|
||||||
|
if type(returnlist[0]) != list:
|
||||||
|
returnlist = [returnlist]
|
||||||
|
|
||||||
|
str_errmsg = ""
|
||||||
|
for lst_result in returnlist:
|
||||||
|
# [device, io, status, msg]
|
||||||
|
|
||||||
|
if not lst_result[2]:
|
||||||
|
# Fehlermeldungen erstellen
|
||||||
|
devicename = self.dict_devices[lst_result[0]]
|
||||||
|
str_errmsg += _(
|
||||||
|
"Error set value of device '{0}' Output '{1}': {2} \n"
|
||||||
|
).format(devicename, lst_result[1], lst_result[3])
|
||||||
|
|
||||||
|
if str_errmsg != "":
|
||||||
|
tkmsg.showerror(_("Error"), str_errmsg, parent=self.master)
|
||||||
|
|
||||||
def writevalues(self):
|
def writevalues(self):
|
||||||
"""Alle Outputs senden."""
|
u"""Schreibt geänderte Outputs auf den RevPi."""
|
||||||
pass
|
if self._warnwrite() and not self.autorw.get():
|
||||||
#for dev in self.lst_devices:
|
self._workvalues([self.dict_outs], True)
|
||||||
#self._writevaluesdev(dev)
|
|
||||||
|
|
||||||
# Werte in Prozessabbild schreiben
|
|
||||||
#self.cli.writeprocimg()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
root = tkinter.Tk()
|
|
||||||
myapp = RevPiCheckClient(root)
|
|
||||||
myapp.mainloop()
|
|
||||||
|
|||||||
318
revpipycontrol/revpidevelop.py
Normal file
318
revpipycontrol/revpidevelop.py
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
u"""PLC Programm und Konfig hoch und runterladen."""
|
||||||
|
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
|
import gzip
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
import tkinter
|
||||||
|
import tkinter.filedialog as tkfd
|
||||||
|
import tkinter.messagebox as tkmsg
|
||||||
|
from mytools import homedir
|
||||||
|
from mytools import gettrans
|
||||||
|
from mytools import savefile_developer as savefile
|
||||||
|
from tkinter import ttk
|
||||||
|
from xmlrpc.client import Binary
|
||||||
|
|
||||||
|
# Übersetzung laden
|
||||||
|
_ = gettrans()
|
||||||
|
|
||||||
|
|
||||||
|
def _loaddefaults(revpiname=None):
|
||||||
|
u"""Übernimmt für den Pi die letzen Pfade.
|
||||||
|
@param revpiname Einstellungen nur für RevPi laden
|
||||||
|
@return <class 'dict'> mit Einstellungen"""
|
||||||
|
if os.path.exists(savefile):
|
||||||
|
with open(savefile, "rb") as fh:
|
||||||
|
dict_all = pickle.load(fh)
|
||||||
|
if revpiname is None:
|
||||||
|
return dict_all
|
||||||
|
else:
|
||||||
|
return dict_all.get(revpiname, {})
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _savedefaults(revpiname, settings):
|
||||||
|
u"""Schreibt fuer den Pi die letzen Pfade.
|
||||||
|
|
||||||
|
@param revpiname Einstellungen sind für diesen RevPi
|
||||||
|
@param settings <class 'dict'> mit Einstellungen
|
||||||
|
@return True, bei erfolgreicher Verarbeitung
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(savefile), exist_ok=True)
|
||||||
|
if revpiname is None:
|
||||||
|
dict_all = settings
|
||||||
|
else:
|
||||||
|
dict_all = _loaddefaults()
|
||||||
|
dict_all[revpiname] = settings
|
||||||
|
with open(savefile, "wb") as fh:
|
||||||
|
pickle.dump(dict_all, fh)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class RevPiDevelop(ttk.Frame):
|
||||||
|
|
||||||
|
u"""Zeigt Debugfenster an."""
|
||||||
|
|
||||||
|
def __init__(self, master, xmlcli, xmlmode, revpi):
|
||||||
|
u"""Init RevPiDevelop-Class.
|
||||||
|
@return None"""
|
||||||
|
if xmlmode < 3:
|
||||||
|
return None
|
||||||
|
|
||||||
|
super().__init__(master)
|
||||||
|
self.pack(expand=True, fill="both")
|
||||||
|
|
||||||
|
self.revpi = revpi
|
||||||
|
self.xmlcli = xmlcli
|
||||||
|
|
||||||
|
# Letzte Einstellungen übernehmen
|
||||||
|
self.opt = _loaddefaults(revpi)
|
||||||
|
|
||||||
|
# Einstellungen
|
||||||
|
self.pathselected = self.opt.get("pathselected", False)
|
||||||
|
self.watchpath = self.opt.get("watchpath", homedir)
|
||||||
|
self.watchfiles = self.opt.get("watchfiles", [])
|
||||||
|
|
||||||
|
# Fenster bauen
|
||||||
|
self._createwidgets()
|
||||||
|
|
||||||
|
# Alte Einstellungen anwenden
|
||||||
|
if self.pathselected:
|
||||||
|
self.load_pathfiles(silent=True)
|
||||||
|
|
||||||
|
self.refresh_stats()
|
||||||
|
|
||||||
|
def _checkclose(self, event=None):
|
||||||
|
u"""Prüft ob Fenster beendet werden soll.
|
||||||
|
@param event tkinter-Event"""
|
||||||
|
|
||||||
|
# Einstellungen speichern
|
||||||
|
self.opt["pathselected"] = self.pathselected
|
||||||
|
self.opt["watchpath"] = self.watchpath
|
||||||
|
self.opt["watchfiles"] = self.watchfiles
|
||||||
|
_savedefaults(self.revpi, self.opt)
|
||||||
|
|
||||||
|
def _createwidgets(self):
|
||||||
|
u"""Erstellt alle Widgets."""
|
||||||
|
self.rowconfigure(0, weight=1)
|
||||||
|
self.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# cpad = {"padx": 4, "pady": 2}
|
||||||
|
# cpade = {"padx": 4, "pady": 2, "sticky": "e"}
|
||||||
|
cpadw = {"padx": 4, "pady": 2, "sticky": "w"}
|
||||||
|
cpadwe = {"padx": 4, "pady": 2, "sticky": "we"}
|
||||||
|
|
||||||
|
# Gruppe Develop
|
||||||
|
devel = ttk.LabelFrame(self)
|
||||||
|
devel.columnconfigure(0, weight=1)
|
||||||
|
devel["text"] = _("File watcher for PLC development")
|
||||||
|
devel.grid(**cpadwe)
|
||||||
|
|
||||||
|
r = 0
|
||||||
|
lbl = ttk.Label(devel)
|
||||||
|
lbl["text"] = _("Path to list files:")
|
||||||
|
lbl.grid(row=r, **cpadw)
|
||||||
|
|
||||||
|
btn = ttk.Button(devel)
|
||||||
|
btn["command"] = self.btn_selectpath
|
||||||
|
btn["text"] = _("Select path")
|
||||||
|
btn.grid(row=r, column=1, **cpadw)
|
||||||
|
|
||||||
|
r += 1
|
||||||
|
self.lbl_path = ttk.Label(devel)
|
||||||
|
self.lbl_path["width"] = 50
|
||||||
|
self.lbl_path.grid(row=r, column=0, columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
# Listbox
|
||||||
|
r += 1
|
||||||
|
trv = ttk.Frame(devel)
|
||||||
|
trv.columnconfigure(0, weight=1)
|
||||||
|
trv.grid(row=r, columnspan=2, sticky="we")
|
||||||
|
scb_files = ttk.Scrollbar(trv)
|
||||||
|
self.trv_files = ttk.Treeview(trv)
|
||||||
|
self.trv_files.bind("<<TreeviewSelect>>", self.select_pathfiles)
|
||||||
|
self.trv_files["height"] = 15
|
||||||
|
self.trv_files["yscrollcommand"] = scb_files.set
|
||||||
|
self.trv_files.grid(row=0, column=0, sticky="we")
|
||||||
|
scb_files["command"] = self.trv_files.yview
|
||||||
|
scb_files.grid(row=0, column=1, sticky="ns")
|
||||||
|
|
||||||
|
# Uploadbutton
|
||||||
|
r += 1
|
||||||
|
btnlist = ttk.Frame(devel)
|
||||||
|
btnlist.columnconfigure(1, weight=1)
|
||||||
|
btnlist.grid(row=r, columnspan=2, sticky="we")
|
||||||
|
|
||||||
|
self.btn_jobs = ttk.Button(btnlist)
|
||||||
|
self.btn_jobs["command"] = lambda: self.btn_domyjob(stop_restart=True)
|
||||||
|
self.btn_jobs["text"] = _("Stop / Upload / Start")
|
||||||
|
self.btn_jobs.grid(row=0, column=0, **cpadwe)
|
||||||
|
|
||||||
|
self.btn_jobs = ttk.Button(btnlist)
|
||||||
|
self.btn_jobs["command"] = lambda: self.btn_domyjob(stop_restart=False)
|
||||||
|
self.btn_jobs["text"] = _("Just upload")
|
||||||
|
self.btn_jobs.grid(row=0, column=1, **cpadwe)
|
||||||
|
|
||||||
|
def btn_domyjob(self, stop_restart=True):
|
||||||
|
u"""Hochladen und neu starten.
|
||||||
|
@param stop_restart Bestehendes Programm Beenden/Starten"""
|
||||||
|
|
||||||
|
if stop_restart:
|
||||||
|
# PLC Programm anhalten
|
||||||
|
self.xmlcli.plcstop()
|
||||||
|
|
||||||
|
# Aktuell konfiguriertes Programm lesen (für uploaded Flag)
|
||||||
|
opt_program = self.xmlcli.get_config()
|
||||||
|
opt_program = opt_program.get("plcprogram", "none.py")
|
||||||
|
uploaded = True
|
||||||
|
ec = 0
|
||||||
|
|
||||||
|
for fname in self.watchfiles:
|
||||||
|
|
||||||
|
# FIXME: Fehlerabfang bei Dateilesen
|
||||||
|
with open(fname, "rb") as fh:
|
||||||
|
|
||||||
|
# Ordnernamen vom System entfernen
|
||||||
|
sendname = fname.replace(self.watchpath, "")[1:]
|
||||||
|
|
||||||
|
# Prüfen ob Dateiname bereits als Startprogramm angegeben ist
|
||||||
|
if sendname == opt_program:
|
||||||
|
uploaded = False
|
||||||
|
|
||||||
|
# Datei übertragen
|
||||||
|
try:
|
||||||
|
ustatus = self.xmlcli.plcupload(
|
||||||
|
Binary(gzip.compress(fh.read())), sendname
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
ec = -2
|
||||||
|
break
|
||||||
|
|
||||||
|
if not ustatus:
|
||||||
|
ec = -1
|
||||||
|
break
|
||||||
|
|
||||||
|
if ec == 0:
|
||||||
|
# Wenn eines der Dateien nicht das Hauptprogram ist, info
|
||||||
|
if uploaded:
|
||||||
|
tkmsg.showinfo(
|
||||||
|
_("Information"),
|
||||||
|
_("A PLC program has been uploaded. Please check the "
|
||||||
|
"PLC options to see if the correct program is "
|
||||||
|
"specified as the start program."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
|
elif ec == -1:
|
||||||
|
tkmsg.showerror(
|
||||||
|
_("Error"),
|
||||||
|
_("The Revolution Pi could not process some parts of the "
|
||||||
|
"transmission."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
|
elif ec == -2:
|
||||||
|
tkmsg.showerror(
|
||||||
|
_("Error"),
|
||||||
|
_("Errors occurred during transmission"),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
|
if stop_restart:
|
||||||
|
# PLC Programm starten
|
||||||
|
self.xmlcli.plcstart()
|
||||||
|
|
||||||
|
def btn_selectpath(self):
|
||||||
|
u"""Lässt dem Benuzter ein Verzeichnis auswählen."""
|
||||||
|
dirselect = tkfd.askdirectory(
|
||||||
|
parent=self.master,
|
||||||
|
title=_("Directory to watch"),
|
||||||
|
mustexist=False,
|
||||||
|
initialdir=self.watchpath
|
||||||
|
)
|
||||||
|
if not dirselect:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Neuen Pfad übernehmen
|
||||||
|
if os.path.exists(dirselect):
|
||||||
|
self.pathselected = True
|
||||||
|
self.watchpath = dirselect
|
||||||
|
self.load_pathfiles()
|
||||||
|
|
||||||
|
else:
|
||||||
|
tkmsg.showerror(
|
||||||
|
_("Error"),
|
||||||
|
_("Can not open the selected folder."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
|
self.refresh_stats()
|
||||||
|
|
||||||
|
def load_pathfiles(self, silent=False):
|
||||||
|
u"""Aktualisiert die Dateiliste.
|
||||||
|
@param silent Keinen Dialog anzeigen"""
|
||||||
|
# Liste leeren
|
||||||
|
self.trv_files.delete(*self.trv_files.get_children())
|
||||||
|
|
||||||
|
# Dateiliste erstellen
|
||||||
|
filecount = 0
|
||||||
|
for tup_walk in os.walk(self.watchpath):
|
||||||
|
for filename in sorted(tup_walk[2]):
|
||||||
|
fullname = os.path.join(tup_walk[0], filename)
|
||||||
|
self.trv_files.insert(
|
||||||
|
"", "end", fullname,
|
||||||
|
text=fullname.replace(self.watchpath, "")[1:],
|
||||||
|
values=fullname
|
||||||
|
)
|
||||||
|
|
||||||
|
# Dateiobergrenze
|
||||||
|
filecount += 1
|
||||||
|
if filecount >= 1000:
|
||||||
|
break
|
||||||
|
|
||||||
|
if filecount >= 1000:
|
||||||
|
if not silent:
|
||||||
|
tkmsg.showwarning(
|
||||||
|
_("Warning"),
|
||||||
|
_("Found more than 1000 files! Only 1000 files can be "
|
||||||
|
"shown in this dialog, all other will be ignored."
|
||||||
|
""),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
# Alle Elemente für Selection prüfen und anwenden
|
||||||
|
for watchfile in self.watchfiles.copy():
|
||||||
|
try:
|
||||||
|
self.trv_files.item(watchfile)
|
||||||
|
except Exception:
|
||||||
|
self.watchfiles.remove(watchfile)
|
||||||
|
self.trv_files.selection_set(self.watchfiles)
|
||||||
|
|
||||||
|
def select_pathfiles(self, tkevt):
|
||||||
|
u"""Setzt state der Buttons."""
|
||||||
|
self.watchfiles = list(self.trv_files.selection())
|
||||||
|
self.refresh_stats()
|
||||||
|
|
||||||
|
def refresh_stats(self):
|
||||||
|
u"""Passt die Widgets an."""
|
||||||
|
self.btn_jobs["state"] = "normal" if len(self.watchfiles) > 0 \
|
||||||
|
else "disabled"
|
||||||
|
self.lbl_path["text"] = self.watchpath
|
||||||
|
|
||||||
|
|
||||||
|
# Debugging
|
||||||
|
if __name__ == "__main__":
|
||||||
|
from xmlrpc.client import ServerProxy
|
||||||
|
cli = ServerProxy("http://localhost:55123")
|
||||||
|
root = tkinter.Tk()
|
||||||
|
app = RevPiDevelop(root, cli, 3, "debugging")
|
||||||
|
app.mainloop()
|
||||||
145
revpipycontrol/revpiinfo.py
Normal file
145
revpipycontrol/revpiinfo.py
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
u"""Programminformationen anzeigen."""
|
||||||
|
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
|
import tkinter
|
||||||
|
import tkinter.font as tkf
|
||||||
|
import webbrowser
|
||||||
|
from mytools import gettrans
|
||||||
|
|
||||||
|
# Übersetzung laden
|
||||||
|
_ = gettrans()
|
||||||
|
|
||||||
|
|
||||||
|
class RevPiInfo(tkinter.Frame):
|
||||||
|
|
||||||
|
u"""Baut Frame für Programminformationen."""
|
||||||
|
|
||||||
|
def __init__(self, master, xmlcli, version):
|
||||||
|
u"""Init RevPiLogfile-Class."""
|
||||||
|
self.master = master
|
||||||
|
self.xmlcli = xmlcli
|
||||||
|
|
||||||
|
# Systemvariablen
|
||||||
|
self.version = version
|
||||||
|
|
||||||
|
# Fenster bauen
|
||||||
|
self._createwidgets()
|
||||||
|
|
||||||
|
def _checkclose(self, event=None):
|
||||||
|
u"""Prüft ob Fenster beendet werden soll.
|
||||||
|
@param event tkinter-Event"""
|
||||||
|
self.master.destroy()
|
||||||
|
|
||||||
|
def _createwidgets(self, extended=False):
|
||||||
|
u"""Erstellt alle Widgets."""
|
||||||
|
super().__init__(self.master)
|
||||||
|
self.master.wm_title(_("RevPi Python PLC info"))
|
||||||
|
self.master.bind("<KeyPress-Escape>", self._checkclose)
|
||||||
|
self.master.resizable(False, False)
|
||||||
|
self.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
# Fonts laden
|
||||||
|
fntlarge = tkf.Font(size=20, weight="bold")
|
||||||
|
fntmid = tkf.Font(size=15)
|
||||||
|
fntbold = tkf.Font(size=10, weight="bold")
|
||||||
|
|
||||||
|
# Kopfdaten
|
||||||
|
lbl = tkinter.Label(self)
|
||||||
|
lbl["font"] = fntlarge
|
||||||
|
lbl["text"] = _("RevPi Python PLC - Control")
|
||||||
|
lbl.pack(pady=5)
|
||||||
|
lbl = tkinter.Label(self)
|
||||||
|
lbl["font"] = fntmid
|
||||||
|
lbl["text"] = _("Version: {0}").format(self.version)
|
||||||
|
lbl.bind(
|
||||||
|
"<ButtonPress-2>",
|
||||||
|
lambda event: self._createwidgets(extended=not extended)
|
||||||
|
)
|
||||||
|
lbl.pack(pady=5)
|
||||||
|
|
||||||
|
# Mittelframe geteilt (links/rechts) ---------------------------------
|
||||||
|
frame_main = tkinter.Frame(self)
|
||||||
|
frame_main.pack(anchor="nw", fill="x", pady=5)
|
||||||
|
|
||||||
|
# Rows konfigurieren
|
||||||
|
frame_main.rowconfigure(0, weight=0)
|
||||||
|
frame_main.rowconfigure(1, weight=1)
|
||||||
|
frame_main.rowconfigure(2, weight=1)
|
||||||
|
|
||||||
|
int_row = 0
|
||||||
|
cpadnw = {"padx": 4, "pady": 2, "sticky": "nw"}
|
||||||
|
cpadsw = {"padx": 4, "pady": 2, "sticky": "sw"}
|
||||||
|
|
||||||
|
# Linke Seite Mittelframe ----------------
|
||||||
|
lbl = tkinter.Label(frame_main)
|
||||||
|
lbl["font"] = fntbold
|
||||||
|
lbl["text"] = _("RevPiPyLoad version on RevPi:")
|
||||||
|
lbl.grid(column=0, row=int_row, **cpadnw)
|
||||||
|
|
||||||
|
lbl = tkinter.Label(frame_main)
|
||||||
|
lbl["font"] = fntbold
|
||||||
|
lbl["text"] = _("not conn.") if self.xmlcli is None \
|
||||||
|
else self.xmlcli.version()
|
||||||
|
lbl.grid(column=1, row=int_row, **cpadnw)
|
||||||
|
|
||||||
|
int_row += 1 # 1
|
||||||
|
lbl = tkinter.Label(frame_main)
|
||||||
|
lbl["justify"] = "left"
|
||||||
|
lbl["text"] = _(
|
||||||
|
"\nRevPiModIO, RevPiPyLoad and RevPiPyControl\n"
|
||||||
|
"are community driven projects. They are all\n"
|
||||||
|
"free and open source software.\n"
|
||||||
|
"All of them comes with ABSOLUTELY NO\n"
|
||||||
|
"WARRANTY, to the extent permitted by \n"
|
||||||
|
"applicable law.\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"(c) Sven Sager, License: LGPLv3"
|
||||||
|
)
|
||||||
|
lbl.grid(column=0, row=int_row, columnspan=2, **cpadnw)
|
||||||
|
|
||||||
|
int_row += 1 # 2
|
||||||
|
lbl = tkinter.Label(frame_main)
|
||||||
|
lbl.bind("<ButtonPress-1>", self.visitwebsite)
|
||||||
|
lbl["fg"] = "blue"
|
||||||
|
lbl["text"] = "https://revpimodio.org/"
|
||||||
|
lbl.grid(column=0, row=int_row, columnspan=2, **cpadsw)
|
||||||
|
|
||||||
|
# int_row += 1 # 3
|
||||||
|
|
||||||
|
# Rechte Seite Mittelframe ---------------
|
||||||
|
|
||||||
|
# Funktionen der Gegenstelle
|
||||||
|
if self.xmlcli is not None:
|
||||||
|
frame_func = tkinter.Frame(frame_main)
|
||||||
|
txt_xmlfunc = tkinter.Text(frame_func, width=30, height=15)
|
||||||
|
scr_xmlfunc = tkinter.Scrollbar(frame_func)
|
||||||
|
if extended:
|
||||||
|
txt_xmlfunc.insert(tkinter.END, "\n".join(
|
||||||
|
self.xmlcli.system.listMethods()
|
||||||
|
))
|
||||||
|
elif "get_filelist" in self.xmlcli.system.listMethods():
|
||||||
|
txt_xmlfunc.insert(tkinter.END, "\n".join(
|
||||||
|
self.xmlcli.get_filelist()
|
||||||
|
))
|
||||||
|
txt_xmlfunc["yscrollcommand"] = scr_xmlfunc.set
|
||||||
|
txt_xmlfunc["state"] = "disabled"
|
||||||
|
scr_xmlfunc["command"] = txt_xmlfunc.yview
|
||||||
|
txt_xmlfunc.pack(side="left")
|
||||||
|
scr_xmlfunc.pack(fill="y", side="right")
|
||||||
|
if txt_xmlfunc.get(1.0) != "\n":
|
||||||
|
frame_func.grid(column=3, row=0, rowspan=int_row + 1, **cpadnw)
|
||||||
|
|
||||||
|
# Unten Beenden-Button -----------------------------------------------
|
||||||
|
self.btnapplog = tkinter.Button(self)
|
||||||
|
self.btnapplog["command"] = self._checkclose
|
||||||
|
self.btnapplog["text"] = _("Close")
|
||||||
|
self.btnapplog.pack(fill="x", padx=100)
|
||||||
|
|
||||||
|
def visitwebsite(self, event=None):
|
||||||
|
u"""Öffnet auf dem System einen Webbrowser zur Projektseite."""
|
||||||
|
webbrowser.open("https://revpimodio.org")
|
||||||
358
revpipycontrol/revpilegacy.py
Normal file
358
revpipycontrol/revpilegacy.py
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
u"""Alte Klassen laden hier, bevor sie entsorgt werden."""
|
||||||
|
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
|
import tkinter
|
||||||
|
import tkinter.messagebox as tkmsg
|
||||||
|
from mytools import gettrans
|
||||||
|
|
||||||
|
# Übersetzung laden
|
||||||
|
_ = gettrans()
|
||||||
|
|
||||||
|
|
||||||
|
class RevPiOption(tkinter.Frame):
|
||||||
|
|
||||||
|
u"""Optionen für RevPiPyload vor 0.6.0."""
|
||||||
|
|
||||||
|
def __init__(self, master, xmlcli):
|
||||||
|
u"""Init RevPiOption-Class.
|
||||||
|
@return None"""
|
||||||
|
try:
|
||||||
|
self.dc = xmlcli.get_config()
|
||||||
|
except Exception:
|
||||||
|
self.dc = None
|
||||||
|
return None
|
||||||
|
|
||||||
|
super().__init__(master)
|
||||||
|
self.master.bind("<KeyPress-Escape>", self._checkclose)
|
||||||
|
self.master.protocol("WM_DELETE_WINDOW", self._checkclose)
|
||||||
|
self.pack(expand=True, fill="both")
|
||||||
|
|
||||||
|
self.xmlcli = xmlcli
|
||||||
|
self.mrk_var_xmlmod2 = False
|
||||||
|
self.mrk_var_xmlmod3 = False
|
||||||
|
self.mrk_xmlmodask = False
|
||||||
|
self.dorestart = False
|
||||||
|
|
||||||
|
# Fenster bauen
|
||||||
|
self._createwidgets()
|
||||||
|
self._loadappdata()
|
||||||
|
|
||||||
|
def _changesdone(self):
|
||||||
|
u"""Prüft ob sich die Einstellungen geändert haben.
|
||||||
|
@return True, wenn min. eine Einstellung geändert wurde"""
|
||||||
|
return (
|
||||||
|
self.var_start.get() != self.dc.get("autostart", "1") or
|
||||||
|
self.var_reload.get() != self.dc.get("autoreload", "1") or
|
||||||
|
self.var_zexit.get() != self.dc.get("zeroonexit", "0") or
|
||||||
|
self.var_zerr.get() != self.dc.get("zeroonerror", "0") or
|
||||||
|
self.var_startpy.get() != self.dc.get("plcprogram", "none.py") or
|
||||||
|
self.var_startargs.get() != self.dc.get("plcarguments", "") or
|
||||||
|
self.var_pythonver.get() != self.dc.get("pythonversion", "3") or
|
||||||
|
self.var_slave.get() != self.dc.get("plcslave", "0") or
|
||||||
|
self.var_xmlon.get() != (self.dc.get("xmlrpc", 0) >= 1) or
|
||||||
|
self.var_xmlmod2.get() != (self.dc.get("xmlrpc", 0) >= 2) or
|
||||||
|
self.var_xmlmod3.get() != (self.dc.get("xmlrpc", 0) >= 3)
|
||||||
|
# or self.var_xmlport.get() != self.dc.get("xmlrpcport", "55123")
|
||||||
|
)
|
||||||
|
|
||||||
|
def _checkclose(self, event=None):
|
||||||
|
u"""Prüft ob Fenster beendet werden soll.
|
||||||
|
@param event tkinter-Event"""
|
||||||
|
ask = True
|
||||||
|
if self._changesdone():
|
||||||
|
ask = tkmsg.askyesno(
|
||||||
|
_("Question"),
|
||||||
|
_("Do you really want to quit? \nUnsaved changes will "
|
||||||
|
"be lost"),
|
||||||
|
parent=self.master, default="no"
|
||||||
|
)
|
||||||
|
|
||||||
|
if ask:
|
||||||
|
self.master.destroy()
|
||||||
|
|
||||||
|
def _createwidgets(self):
|
||||||
|
u"""Erstellt Widgets."""
|
||||||
|
self.master.wm_title(_("RevPi Python PLC Options"))
|
||||||
|
self.master.wm_resizable(width=False, height=False)
|
||||||
|
|
||||||
|
xmlstate = "normal" if self.dc["xmlrpc"] >= 3 else "disabled"
|
||||||
|
|
||||||
|
cpadw = {"padx": 4, "pady": 2, "sticky": "w"}
|
||||||
|
cpadwe = {"padx": 4, "pady": 2, "sticky": "we"}
|
||||||
|
|
||||||
|
# Gruppe Start/Stop
|
||||||
|
stst = tkinter.LabelFrame(self)
|
||||||
|
stst["text"] = _("Start / Stop behavior")
|
||||||
|
stst.grid(columnspan=2, pady=2, sticky="we")
|
||||||
|
|
||||||
|
self.var_start = tkinter.BooleanVar(stst)
|
||||||
|
self.var_reload = tkinter.BooleanVar(stst)
|
||||||
|
self.var_zexit = tkinter.BooleanVar(stst)
|
||||||
|
self.var_zerr = tkinter.BooleanVar(stst)
|
||||||
|
|
||||||
|
ckb_start = tkinter.Checkbutton(stst)
|
||||||
|
ckb_start["text"] = _("Start program automatically")
|
||||||
|
ckb_start["state"] = xmlstate
|
||||||
|
ckb_start["variable"] = self.var_start
|
||||||
|
ckb_start.grid(**cpadw)
|
||||||
|
|
||||||
|
ckb_reload = tkinter.Checkbutton(stst)
|
||||||
|
ckb_reload["text"] = _("Restart program after exit")
|
||||||
|
ckb_reload["state"] = xmlstate
|
||||||
|
ckb_reload["variable"] = self.var_reload
|
||||||
|
ckb_reload.grid(**cpadw)
|
||||||
|
|
||||||
|
lbl = tkinter.Label(stst)
|
||||||
|
lbl["text"] = _("Set process image to NULL if program terminates...")
|
||||||
|
lbl.grid(**cpadw)
|
||||||
|
|
||||||
|
ckb_zexit = tkinter.Checkbutton(stst, justify="left")
|
||||||
|
ckb_zexit["state"] = xmlstate
|
||||||
|
ckb_zexit["text"] = _("... successfully")
|
||||||
|
ckb_zexit["variable"] = self.var_zexit
|
||||||
|
ckb_zexit.grid(**cpadw)
|
||||||
|
|
||||||
|
ckb_zerr = tkinter.Checkbutton(stst, justify="left")
|
||||||
|
ckb_zerr["state"] = xmlstate
|
||||||
|
ckb_zerr["text"] = _("... with errors")
|
||||||
|
ckb_zerr["variable"] = self.var_zerr
|
||||||
|
ckb_zerr.grid(**cpadw)
|
||||||
|
|
||||||
|
# Gruppe Programm
|
||||||
|
prog = tkinter.LabelFrame(self)
|
||||||
|
prog["text"] = _("PLC program")
|
||||||
|
prog.grid(columnspan=2, pady=2, sticky="we")
|
||||||
|
|
||||||
|
self.var_pythonver = tkinter.IntVar(prog)
|
||||||
|
self.var_startpy = tkinter.StringVar(prog)
|
||||||
|
self.var_startargs = tkinter.StringVar(prog)
|
||||||
|
self.var_slave = tkinter.BooleanVar(prog)
|
||||||
|
|
||||||
|
self.var_pythonver.set(3)
|
||||||
|
|
||||||
|
lbl = tkinter.Label(prog)
|
||||||
|
lbl["text"] = _("Python version")
|
||||||
|
lbl.grid(columnspan=2, row=0, **cpadw)
|
||||||
|
|
||||||
|
rbn = tkinter.Radiobutton(prog)
|
||||||
|
rbn["state"] = xmlstate
|
||||||
|
rbn["text"] = "Python2"
|
||||||
|
rbn["value"] = 2
|
||||||
|
rbn["variable"] = self.var_pythonver
|
||||||
|
rbn.grid(column=0, row=1, **cpadw)
|
||||||
|
|
||||||
|
rbn = tkinter.Radiobutton(prog)
|
||||||
|
rbn["state"] = xmlstate
|
||||||
|
rbn["text"] = "Python3"
|
||||||
|
rbn["value"] = 3
|
||||||
|
rbn["variable"] = self.var_pythonver
|
||||||
|
rbn.grid(column=1, row=1, **cpadw)
|
||||||
|
|
||||||
|
# Row 2
|
||||||
|
lbl = tkinter.Label(prog)
|
||||||
|
lbl["text"] = _("Python PLC program name")
|
||||||
|
lbl.grid(columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
# Row 3
|
||||||
|
lst = self.xmlcli.get_filelist()
|
||||||
|
if len(lst) == 0:
|
||||||
|
lst.append("none")
|
||||||
|
opt_startpy = tkinter.OptionMenu(
|
||||||
|
prog, self.var_startpy, *lst
|
||||||
|
)
|
||||||
|
opt_startpy["state"] = xmlstate
|
||||||
|
opt_startpy.grid(columnspan=2, **cpadwe)
|
||||||
|
|
||||||
|
# Row 4
|
||||||
|
lbl = tkinter.Label(prog)
|
||||||
|
lbl["text"] = _("Program arguments")
|
||||||
|
lbl.grid(columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
# Row 5
|
||||||
|
txt = tkinter.Entry(prog)
|
||||||
|
txt["textvariable"] = self.var_startargs
|
||||||
|
txt.grid(columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
# Row 6
|
||||||
|
ckb_slave = tkinter.Checkbutton(prog, justify="left")
|
||||||
|
ckb_slave["state"] = xmlstate
|
||||||
|
ckb_slave["text"] = _("Use RevPi as PLC-Slave")
|
||||||
|
ckb_slave["variable"] = self.var_slave
|
||||||
|
ckb_slave.grid(column=0, **cpadw)
|
||||||
|
|
||||||
|
# Gruppe XMLRPC
|
||||||
|
xmlrpc = tkinter.LabelFrame(self)
|
||||||
|
xmlrpc["text"] = _("XML-RPC server")
|
||||||
|
xmlrpc.grid(columnspan=2, pady=2, sticky="we")
|
||||||
|
|
||||||
|
self.var_xmlon = tkinter.BooleanVar(xmlrpc)
|
||||||
|
self.var_xmlmod2 = tkinter.BooleanVar(xmlrpc)
|
||||||
|
self.var_xmlmod3 = tkinter.BooleanVar(xmlrpc)
|
||||||
|
# self.var_xmlport = tkinter.StringVar(xmlrpc)
|
||||||
|
# self.var_xmlport.set("55123")
|
||||||
|
|
||||||
|
ckb_xmlon = tkinter.Checkbutton(xmlrpc)
|
||||||
|
ckb_xmlon["command"] = self.askxmlon
|
||||||
|
ckb_xmlon["state"] = xmlstate
|
||||||
|
ckb_xmlon["text"] = _("Activate XML-RPC server on RevPi")
|
||||||
|
ckb_xmlon["variable"] = self.var_xmlon
|
||||||
|
ckb_xmlon.grid(**cpadw)
|
||||||
|
|
||||||
|
self.ckb_xmlmod2 = tkinter.Checkbutton(xmlrpc, justify="left")
|
||||||
|
self.ckb_xmlmod2["command"] = self.xmlmod2_tail
|
||||||
|
self.ckb_xmlmod2["state"] = xmlstate
|
||||||
|
self.ckb_xmlmod2["text"] = \
|
||||||
|
_("Allow download of piCtory configuration and\nPLC programm")
|
||||||
|
self.ckb_xmlmod2["variable"] = self.var_xmlmod2
|
||||||
|
self.ckb_xmlmod2.grid(**cpadw)
|
||||||
|
|
||||||
|
self.ckb_xmlmod3 = tkinter.Checkbutton(xmlrpc, justify="left")
|
||||||
|
self.ckb_xmlmod3["state"] = xmlstate
|
||||||
|
self.ckb_xmlmod3["text"] = \
|
||||||
|
_("Allow upload of piCtory configuration and\nPLC programm")
|
||||||
|
self.ckb_xmlmod3["variable"] = self.var_xmlmod3
|
||||||
|
self.ckb_xmlmod3.grid(**cpadw)
|
||||||
|
|
||||||
|
lbl = tkinter.Label(xmlrpc)
|
||||||
|
lbl["text"] = _("XML-RPC server port")
|
||||||
|
lbl.grid(**cpadw)
|
||||||
|
|
||||||
|
# spb_xmlport = tkinter.Spinbox(xmlrpc)
|
||||||
|
# spb_xmlport["to"] = 65535
|
||||||
|
# spb_xmlport["from"] = 1024
|
||||||
|
# spb_xmlport["state"] = xmlstate
|
||||||
|
# spb_xmlport["textvariable"] = self.var_xmlport
|
||||||
|
# spb_xmlport.grid(**cpadwe)
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
btn_save = tkinter.Button(self)
|
||||||
|
btn_save["command"] = self._setappdata
|
||||||
|
btn_save["state"] = xmlstate
|
||||||
|
btn_save["text"] = _("Save")
|
||||||
|
btn_save.grid(column=0, row=3)
|
||||||
|
|
||||||
|
btn_close = tkinter.Button(self)
|
||||||
|
btn_close["command"] = self._checkclose
|
||||||
|
btn_close["text"] = _("Close")
|
||||||
|
btn_close.grid(column=1, row=3)
|
||||||
|
|
||||||
|
def _loadappdata(self, refresh=False):
|
||||||
|
u"""Läd aktuelle Einstellungen vom RevPi.
|
||||||
|
@param refresh Wenn True, werden Einstellungen heruntergeladen."""
|
||||||
|
if refresh:
|
||||||
|
self.dc = self.xmlcli.get_config()
|
||||||
|
|
||||||
|
self.var_start.set(self.dc.get("autostart", "1"))
|
||||||
|
self.var_reload.set(self.dc.get("autoreload", "1"))
|
||||||
|
self.var_zexit.set(self.dc.get("zeroonexit", "0"))
|
||||||
|
self.var_zerr.set(self.dc.get("zeroonerror", "0"))
|
||||||
|
|
||||||
|
self.var_startpy.set(self.dc.get("plcprogram", "none.py"))
|
||||||
|
self.var_startargs.set(self.dc.get("plcarguments", ""))
|
||||||
|
self.var_pythonver.set(self.dc.get("pythonversion", "3"))
|
||||||
|
self.var_slave.set(self.dc.get("plcslave", "0"))
|
||||||
|
|
||||||
|
self.var_xmlon.set(self.dc.get("xmlrpc", 0) >= 1)
|
||||||
|
self.var_xmlmod2.set(self.dc.get("xmlrpc", 0) >= 2)
|
||||||
|
self.mrk_var_xmlmod2 = self.var_xmlmod2.get()
|
||||||
|
self.var_xmlmod3.set(self.dc.get("xmlrpc", 0) >= 3)
|
||||||
|
self.mrk_var_xmlmod3 = self.var_xmlmod3.get()
|
||||||
|
|
||||||
|
# self.var_xmlport.set(self.dc.get("xmlrpcport", "55123"))
|
||||||
|
|
||||||
|
def _setappdata(self):
|
||||||
|
u"""Speichert geänderte Einstellungen auf RevPi.
|
||||||
|
@return None"""
|
||||||
|
|
||||||
|
if not self._changesdone():
|
||||||
|
tkmsg.showinfo(
|
||||||
|
_("Information"),
|
||||||
|
_("You have not made any changes to save."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
self._checkclose()
|
||||||
|
return None
|
||||||
|
|
||||||
|
ask = tkmsg.askyesnocancel(
|
||||||
|
_("Question"),
|
||||||
|
_("The settings are now saved on the Revolution Pi. \n\n"
|
||||||
|
"Should the new settings take effect immediately? \nThis "
|
||||||
|
"means a restart of the service and the PLC program!"),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
if ask is not None:
|
||||||
|
self.dc["autostart"] = int(self.var_start.get())
|
||||||
|
self.dc["autoreload"] = int(self.var_reload.get())
|
||||||
|
self.dc["zeroonexit"] = int(self.var_zexit.get())
|
||||||
|
self.dc["zeroonerror"] = int(self.var_zerr.get())
|
||||||
|
|
||||||
|
self.dc["plcprogram"] = self.var_startpy.get()
|
||||||
|
self.dc["plcarguments"] = self.var_startargs.get()
|
||||||
|
self.dc["pythonversion"] = self.var_pythonver.get()
|
||||||
|
self.dc["plcslave"] = int(self.var_slave.get())
|
||||||
|
|
||||||
|
self.dc["xmlrpc"] = 0
|
||||||
|
if self.var_xmlon.get():
|
||||||
|
self.dc["xmlrpc"] += 1
|
||||||
|
if self.var_xmlmod2.get():
|
||||||
|
self.dc["xmlrpc"] += 1
|
||||||
|
if self.var_xmlmod3.get():
|
||||||
|
self.dc["xmlrpc"] += 1
|
||||||
|
|
||||||
|
# self.dc["xmlrpcport"] = self.var_xmlport.get()
|
||||||
|
|
||||||
|
if self.xmlcli.set_config(self.dc, ask):
|
||||||
|
tkmsg.showinfo(
|
||||||
|
_("Information"),
|
||||||
|
_("Settings saved"),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
self.dorestart = ask
|
||||||
|
self._checkclose()
|
||||||
|
else:
|
||||||
|
tkmsg.showerror(
|
||||||
|
_("Error"),
|
||||||
|
_("The settings could not be saved. This can happen if "
|
||||||
|
"values are wrong!"),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
|
def askxmlon(self):
|
||||||
|
u"""Fragt Nuter, ob wirklicht abgeschaltet werden soll."""
|
||||||
|
if not (self.var_xmlon.get() or self.mrk_xmlmodask):
|
||||||
|
self.mrk_xmlmodask = tkmsg.askyesno(
|
||||||
|
_("Question"),
|
||||||
|
_("Are you sure you want to deactivate the XML-RPC server? "
|
||||||
|
"You will NOT be able to access the Revolution Pi with "
|
||||||
|
"this program."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
if not self.mrk_xmlmodask:
|
||||||
|
self.var_xmlon.set(True)
|
||||||
|
|
||||||
|
self.xmlmod_tail()
|
||||||
|
|
||||||
|
def xmlmod_tail(self):
|
||||||
|
u"""Passt XML-Optionszugriff an."""
|
||||||
|
if self.var_xmlon.get():
|
||||||
|
self.var_xmlmod2.set(self.mrk_var_xmlmod2)
|
||||||
|
self.ckb_xmlmod2["state"] = "normal"
|
||||||
|
else:
|
||||||
|
self.mrk_var_xmlmod2 = self.var_xmlmod2.get()
|
||||||
|
self.var_xmlmod2.set(False)
|
||||||
|
self.ckb_xmlmod2["state"] = "disabled"
|
||||||
|
self.xmlmod2_tail()
|
||||||
|
|
||||||
|
def xmlmod2_tail(self):
|
||||||
|
u"""Passt XML-Optionszugriff an."""
|
||||||
|
if self.var_xmlmod2.get():
|
||||||
|
self.var_xmlmod3.set(self.mrk_var_xmlmod3)
|
||||||
|
self.ckb_xmlmod3["state"] = "normal"
|
||||||
|
else:
|
||||||
|
self.mrk_var_xmlmod3 = self.var_xmlmod3.get()
|
||||||
|
self.var_xmlmod3.set(False)
|
||||||
|
self.ckb_xmlmod3["state"] = "disabled"
|
||||||
@@ -1,26 +1,46 @@
|
|||||||
#
|
|
||||||
# RevPiPyControl
|
|
||||||
#
|
|
||||||
# Webpage: https://revpimodio.org/revpipyplc/
|
|
||||||
# (c) Sven Sager, License: LGPLv3
|
|
||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import pickle
|
u"""Zeigt die Logfiles an."""
|
||||||
|
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
import tkinter
|
import tkinter
|
||||||
|
from mytools import gettrans
|
||||||
|
|
||||||
|
# Übersetzung laden
|
||||||
|
_ = gettrans()
|
||||||
|
|
||||||
|
|
||||||
class RevPiLogfile(tkinter.Frame):
|
class RevPiLogfile(tkinter.Frame):
|
||||||
|
|
||||||
|
u"""Baut Fenster für Logfiles."""
|
||||||
|
|
||||||
def __init__(self, master, xmlcli):
|
def __init__(self, master, xmlcli):
|
||||||
|
u"""Init RevPiLogfile-Class."""
|
||||||
super().__init__(master)
|
super().__init__(master)
|
||||||
|
self.master.bind("<KeyPress-Escape>", self._checkclose)
|
||||||
self.pack(fill="both", expand=True)
|
self.pack(fill="both", expand=True)
|
||||||
self.xmlcli = xmlcli
|
self.xmlcli = xmlcli
|
||||||
|
|
||||||
|
# Systemvariablen
|
||||||
|
self.loadblock = 16384
|
||||||
|
self.errapp = 0
|
||||||
|
self.errplc = 0
|
||||||
|
self.mrkapp = 0
|
||||||
|
self.mrkplc = 0
|
||||||
|
|
||||||
# Fenster bauen
|
# Fenster bauen
|
||||||
self._createwidgets()
|
self._createwidgets()
|
||||||
|
|
||||||
|
def _checkclose(self, event=None):
|
||||||
|
u"""Prüft ob Fenster beendet werden soll.
|
||||||
|
@param event tkinter-Event"""
|
||||||
|
self.master.destroy()
|
||||||
|
|
||||||
def _createwidgets(self):
|
def _createwidgets(self):
|
||||||
self.master.wm_title("RevPi Python PLC Logs")
|
u"""Erstellt alle Widgets."""
|
||||||
|
self.master.wm_title(_("RevPi Python PLC Logs"))
|
||||||
|
|
||||||
self.rowconfigure(0, weight=0)
|
self.rowconfigure(0, weight=0)
|
||||||
self.rowconfigure(1, weight=1)
|
self.rowconfigure(1, weight=1)
|
||||||
@@ -33,12 +53,13 @@ class RevPiLogfile(tkinter.Frame):
|
|||||||
|
|
||||||
# PLC Log
|
# PLC Log
|
||||||
self.lblapplog = tkinter.Label(self)
|
self.lblapplog = tkinter.Label(self)
|
||||||
self.lblapplog["text"] = "RevPyPyLoad - Logfile"
|
self.lblapplog["text"] = _("RevPiPyLoad - Logfile")
|
||||||
self.lblapplog.grid(column=0, row=0, sticky="w")
|
self.lblapplog.grid(column=0, row=0, sticky="w")
|
||||||
self.btnapplog = tkinter.Button(self)
|
self.btnapplog = tkinter.Button(self)
|
||||||
self.btnapplog["command"] = self.btn_clearplc
|
self.btnapplog["command"] = self.btn_clearplc
|
||||||
self.btnapplog["text"] = "Clear screen"
|
self.btnapplog["text"] = _("Clear screen")
|
||||||
self.btnapplog.grid(column=1, row=0, sticky="e")
|
self.btnapplog.grid(column=1, row=0, sticky="e")
|
||||||
|
|
||||||
self.plclog = tkinter.Text(self)
|
self.plclog = tkinter.Text(self)
|
||||||
self.plcscr = tkinter.Scrollbar(self)
|
self.plcscr = tkinter.Scrollbar(self)
|
||||||
self.plclog.grid(sticky="wnse", columnspan=2, column=0, row=1)
|
self.plclog.grid(sticky="wnse", columnspan=2, column=0, row=1)
|
||||||
@@ -48,12 +69,13 @@ class RevPiLogfile(tkinter.Frame):
|
|||||||
|
|
||||||
# APP Log
|
# APP Log
|
||||||
self.lblapplog = tkinter.Label(self)
|
self.lblapplog = tkinter.Label(self)
|
||||||
self.lblapplog["text"] = "Python PLC program - Logfile"
|
self.lblapplog["text"] = _("Python PLC program - Logfile")
|
||||||
self.lblapplog.grid(column=3, row=0, sticky="w")
|
self.lblapplog.grid(column=3, row=0, sticky="w")
|
||||||
self.btnapplog = tkinter.Button(self)
|
self.btnapplog = tkinter.Button(self)
|
||||||
self.btnapplog["command"] = self.btn_clearapp
|
self.btnapplog["command"] = self.btn_clearapp
|
||||||
self.btnapplog["text"] = "Clear screen"
|
self.btnapplog["text"] = _("Clear screen")
|
||||||
self.btnapplog.grid(column=4, row=0, sticky="e")
|
self.btnapplog.grid(column=4, row=0, sticky="e")
|
||||||
|
|
||||||
self.applog = tkinter.Text(self)
|
self.applog = tkinter.Text(self)
|
||||||
self.appscr = tkinter.Scrollbar(self)
|
self.appscr = tkinter.Scrollbar(self)
|
||||||
self.applog.grid(sticky="nesw", columnspan=2, column=3, row=1)
|
self.applog.grid(sticky="nesw", columnspan=2, column=3, row=1)
|
||||||
@@ -61,47 +83,92 @@ class RevPiLogfile(tkinter.Frame):
|
|||||||
self.applog["yscrollcommand"] = self.appscr.set
|
self.applog["yscrollcommand"] = self.appscr.set
|
||||||
self.appscr["command"] = self.applog.yview
|
self.appscr["command"] = self.applog.yview
|
||||||
|
|
||||||
self.get_applog()
|
# Logtimer zum Laden starten
|
||||||
self.get_plclog()
|
self.get_applog(full=True)
|
||||||
|
self.get_plclog(full=True)
|
||||||
# Timer zum nachladen aktivieren
|
|
||||||
self.master.after(1000, self.get_applines)
|
|
||||||
self.master.after(1000, self.get_plclines)
|
|
||||||
|
|
||||||
def btn_clearapp(self):
|
def btn_clearapp(self):
|
||||||
|
u"""Leert die Logliste der App."""
|
||||||
self.applog.delete(1.0, tkinter.END)
|
self.applog.delete(1.0, tkinter.END)
|
||||||
|
|
||||||
def btn_clearplc(self):
|
def btn_clearplc(self):
|
||||||
|
u"""Leert die Logliste des PLC."""
|
||||||
self.plclog.delete(1.0, tkinter.END)
|
self.plclog.delete(1.0, tkinter.END)
|
||||||
|
|
||||||
def get_applines(self):
|
def get_applog(self, full=False):
|
||||||
roll = self.applog.yview()[1] == 1.0
|
u"""Ruft App Logbuch ab.
|
||||||
|
@param full Ganzes Logbuch laden"""
|
||||||
|
|
||||||
|
# Logs abrufen und letzte Position merken
|
||||||
try:
|
try:
|
||||||
for line in pickle.loads(self.xmlcli.get_applines().data):
|
self.mrkapp = self._load_log(
|
||||||
self.applog.insert(tkinter.END, line)
|
self.applog, self.xmlcli.load_applog, self.mrkapp, full
|
||||||
except:
|
)
|
||||||
pass
|
self.errapp = 0
|
||||||
if roll:
|
except Exception:
|
||||||
self.applog.see(tkinter.END)
|
self.errapp += 1
|
||||||
self.master.after(1000, self.get_applines)
|
|
||||||
|
|
||||||
def get_applog(self):
|
# Timer neu starten
|
||||||
self.applog.delete(1.0, tkinter.END)
|
self.master.after(1000, self.get_applog)
|
||||||
self.applog.insert(1.0, pickle.loads(self.xmlcli.get_applog().data))
|
|
||||||
self.applog.see(tkinter.END)
|
|
||||||
|
|
||||||
def get_plclines(self):
|
def get_plclog(self, full=False):
|
||||||
roll = self.plclog.yview()[1] == 1.0
|
u"""Ruft PLC Logbuch ab.
|
||||||
|
@param full Ganzes Logbuch laden"""
|
||||||
|
|
||||||
|
# Logs abrufen und letzte Position merken
|
||||||
try:
|
try:
|
||||||
for line in pickle.loads(self.xmlcli.get_plclines().data):
|
self.mrkplc = self._load_log(
|
||||||
self.plclog.insert(tkinter.END, line)
|
self.plclog, self.xmlcli.load_plclog, self.mrkplc, full
|
||||||
except:
|
)
|
||||||
pass
|
self.errplc = 0
|
||||||
if roll:
|
except Exception:
|
||||||
self.plclog.see(tkinter.END)
|
self.errplc += 1
|
||||||
self.master.after(1000, self.get_plclines)
|
|
||||||
|
|
||||||
def get_plclog(self):
|
# Timer neu starten
|
||||||
self.plclog.delete(1.0, tkinter.END)
|
self.master.after(1000, self.get_plclog)
|
||||||
self.plclog.insert(1.0, pickle.loads(self.xmlcli.get_plclog().data))
|
|
||||||
self.plclog.see(tkinter.END)
|
def _load_log(self, textwidget, xmlcall, startposition, full):
|
||||||
|
u"""Läd die angegebenen Logfiles herunter.
|
||||||
|
|
||||||
|
@param textwidget Widget in das Logs eingefügt werden sollen
|
||||||
|
@param xmlcall xmlrpc Funktion zum Abrufen der Logdaten
|
||||||
|
@param startposition Startposition ab der Logdaten kommen sollen
|
||||||
|
@param full Komplettes Logbuch laden
|
||||||
|
@return Ende der Datei (neue Startposition)
|
||||||
|
|
||||||
|
"""
|
||||||
|
roll = textwidget.yview()[1] == 1.0
|
||||||
|
startposition = 0 if full else startposition
|
||||||
|
logbytes = b''
|
||||||
|
while True:
|
||||||
|
# Datenblock vom XML-RPC Server holen
|
||||||
|
bytebuff = xmlcall(startposition, self.loadblock).data
|
||||||
|
|
||||||
|
logbytes += bytebuff
|
||||||
|
startposition += len(bytebuff)
|
||||||
|
|
||||||
|
# Prüfen ob alle Daten übertragen wurden
|
||||||
|
if len(bytebuff) < self.loadblock:
|
||||||
|
break
|
||||||
|
|
||||||
|
if full:
|
||||||
|
textwidget.delete(1.0, tkinter.END)
|
||||||
|
|
||||||
|
if bytebuff == b'\x16': # 'ESC'
|
||||||
|
# Kein Zugriff auf Logdatei
|
||||||
|
textwidget.delete(1.0, tkinter.END)
|
||||||
|
textwidget.insert(
|
||||||
|
tkinter.END, _("Can not access log file on the RevPi")
|
||||||
|
)
|
||||||
|
elif bytebuff == b'\x19': # 'EndOfMedia'
|
||||||
|
# Logdatei neu begonnen
|
||||||
|
startposition = 0
|
||||||
|
else:
|
||||||
|
# Text in Widget übernehmen
|
||||||
|
textwidget.insert(tkinter.END, logbytes.decode("utf-8"))
|
||||||
|
|
||||||
|
# Automatisch ans Ende rollen
|
||||||
|
if roll or full:
|
||||||
|
textwidget.see(tkinter.END)
|
||||||
|
|
||||||
|
return startposition
|
||||||
|
|||||||
@@ -1,261 +1,597 @@
|
|||||||
#
|
|
||||||
# RevPiPyControl
|
|
||||||
#
|
|
||||||
# Webpage: https://revpimodio.org/revpipyplc/
|
|
||||||
# (c) Sven Sager, License: LGPLv3
|
|
||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
u"""Optionsfenster."""
|
||||||
|
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
import tkinter
|
import tkinter
|
||||||
import tkinter.messagebox as tkmsg
|
import tkinter.messagebox as tkmsg
|
||||||
|
from aclmanager import AclManager
|
||||||
|
from mqttmanager import MqttManager
|
||||||
|
from mytools import gettrans
|
||||||
|
|
||||||
|
# Übersetzung laden
|
||||||
|
_ = gettrans()
|
||||||
|
|
||||||
|
|
||||||
class RevPiOption(tkinter.Frame):
|
class RevPiOption(tkinter.Frame):
|
||||||
|
|
||||||
def __init__(self, master, xmlcli, xmlmode):
|
u"""Zeigt Optionen von RevPiPyLoad an."""
|
||||||
if xmlmode < 2:
|
|
||||||
|
def __init__(self, master, xmlcli):
|
||||||
|
u"""Init RevPiOption-Class.
|
||||||
|
@return None"""
|
||||||
|
try:
|
||||||
|
self.dc = xmlcli.get_config()
|
||||||
|
except Exception:
|
||||||
|
self.dc = None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
super().__init__(master)
|
super().__init__(master)
|
||||||
|
self.master.bind("<KeyPress-Escape>", self._checkclose)
|
||||||
|
self.master.protocol("WM_DELETE_WINDOW", self._checkclose)
|
||||||
self.pack(expand=True, fill="both")
|
self.pack(expand=True, fill="both")
|
||||||
|
|
||||||
|
self.frm_mqttmgr = None
|
||||||
|
self.frm_slaveacl = None
|
||||||
|
self.frm_xmlacl = None
|
||||||
|
|
||||||
|
# XML-RPC Server konfigurieren
|
||||||
self.xmlcli = xmlcli
|
self.xmlcli = xmlcli
|
||||||
self.xmlmode = xmlmode
|
self.xmlmodus = self.xmlcli.xmlmodus()
|
||||||
self.xmlstate = "normal" if xmlmode == 3 else "disabled"
|
|
||||||
|
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.replace_ios_options = [
|
||||||
|
_("Do not use replace io file"),
|
||||||
|
_("Use static file from RevPiPyLoad"),
|
||||||
|
_("Use dynamic file from work directory"),
|
||||||
|
_("Give own path and filename"),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.mrk_xmlmodask = False
|
||||||
|
self.dorestart = False
|
||||||
|
|
||||||
# Fenster bauen
|
# Fenster bauen
|
||||||
self._createwidgets()
|
self._createwidgets()
|
||||||
self._loadappdata()
|
self._loadappdata()
|
||||||
|
|
||||||
|
def __state_replace_ios(self, text):
|
||||||
|
u"""Konfiguriert Werte für replace_io.
|
||||||
|
@param text: Ausgewählter Eintrag in Liste"""
|
||||||
|
selected_id = self.replace_ios_options.index(text)
|
||||||
|
|
||||||
|
# Preset value
|
||||||
|
if selected_id == 0:
|
||||||
|
self.var_replace_ios.set("")
|
||||||
|
elif selected_id == 1:
|
||||||
|
self.var_replace_ios.set("/etc/revpipyload/replace_ios.conf")
|
||||||
|
else:
|
||||||
|
self.var_replace_ios.set("replace_ios.conf")
|
||||||
|
|
||||||
|
# Set state of input field
|
||||||
|
self.txt_replace_ios["state"] = "normal" \
|
||||||
|
if self.xmlmodus >= 4 and \
|
||||||
|
selected_id == 3 \
|
||||||
|
else "disabled"
|
||||||
|
|
||||||
|
def _changesdone(self):
|
||||||
|
u"""Prüft ob sich die Einstellungen geändert haben.
|
||||||
|
@return True, wenn min. eine Einstellung geändert wurde"""
|
||||||
|
return (
|
||||||
|
self.var_start.get() != self.dc.get("autostart", 1) or
|
||||||
|
self.var_reload.get() != self.dc.get("autoreload", 1) or
|
||||||
|
self.var_reload_delay.get() !=
|
||||||
|
str(self.dc.get("autoreloaddelay", 5)) or
|
||||||
|
self.var_zexit.get() != self.dc.get("zeroonexit", 0) or
|
||||||
|
self.var_zerr.get() != self.dc.get("zeroonerror", 0) or
|
||||||
|
self.var_replace_ios.get() != self.dc.get("replace_ios", "") or
|
||||||
|
# TODO: rtlevel (0)
|
||||||
|
self.var_startpy.get() != self.dc.get("plcprogram", "none.py") or
|
||||||
|
self.var_startargs.get() != self.dc.get("plcarguments", "") or
|
||||||
|
self.var_pythonver.get() != self.dc.get("pythonversion", 3) or
|
||||||
|
self.var_plcworkdir_set_uid.get() != \
|
||||||
|
self.dc.get("plcworkdir_set_uid") or
|
||||||
|
self.var_slave.get() != self.dc.get("plcslave", 0) or
|
||||||
|
self.var_slaveacl.get() != self.dc.get("plcslaveacl", "") or
|
||||||
|
self.var_mqtton.get() != self.dc.get("mqtt", 0) or
|
||||||
|
self.var_xmlon.get() != self.dc.get("xmlrpc", 0) or
|
||||||
|
self.var_xmlacl.get() != self.dc.get("xmlrpcacl", "") or
|
||||||
|
self._changesdone_mqtt()
|
||||||
|
)
|
||||||
|
|
||||||
|
def _changesdone_mqtt(self):
|
||||||
|
u"""Prüft ob MQTT-Settings geändert wurden.
|
||||||
|
@return True, wenn Änderungen existieren"""
|
||||||
|
for key in self._dict_mqttsettings:
|
||||||
|
if key in self.dc:
|
||||||
|
if self._dict_mqttsettings[key] != self.dc[key]:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _checkclose(self, event=None):
|
||||||
|
u"""Prüft ob Fenster beendet werden soll.
|
||||||
|
@param event tkinter-Event"""
|
||||||
|
ask = True
|
||||||
|
if self._changesdone():
|
||||||
|
ask = tkmsg.askyesno(
|
||||||
|
_("Question"),
|
||||||
|
_("Do you really want to quit? \nUnsaved changes will "
|
||||||
|
"be lost"),
|
||||||
|
parent=self.master, default="no"
|
||||||
|
)
|
||||||
|
|
||||||
|
if ask:
|
||||||
|
self.master.destroy()
|
||||||
|
|
||||||
|
def _checkvalues(self):
|
||||||
|
u"""Prüft alle Werte auf Gültigkeit.
|
||||||
|
@return True, wenn alle Werte gültig sind"""
|
||||||
|
if not self.var_reload_delay.get().isdigit():
|
||||||
|
tkmsg.showerror(
|
||||||
|
_("Error"),
|
||||||
|
_("The value of 'restart delay' ist not valid."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _createwidgets(self):
|
def _createwidgets(self):
|
||||||
self.master.wm_title("RevPi Python PLC Options")
|
u"""Erstellt Widgets."""
|
||||||
|
self.master.wm_title(_("RevPi Python PLC Options"))
|
||||||
self.master.wm_resizable(width=False, height=False)
|
self.master.wm_resizable(width=False, height=False)
|
||||||
|
|
||||||
|
xmlstate = "normal" if self.xmlmodus >= 4 else "disabled"
|
||||||
|
|
||||||
|
cpade = {"padx": 4, "pady": 2, "sticky": "e"}
|
||||||
cpadw = {"padx": 4, "pady": 2, "sticky": "w"}
|
cpadw = {"padx": 4, "pady": 2, "sticky": "w"}
|
||||||
cpadwe = {"padx": 4, "pady": 2, "sticky": "we"}
|
cpadwe = {"padx": 4, "pady": 2, "sticky": "we"}
|
||||||
|
|
||||||
# Gruppe Start/Stop
|
# Gruppe Start/Stop
|
||||||
stst = tkinter.LabelFrame(self)
|
stst = tkinter.LabelFrame(self)
|
||||||
stst["text"] = "Start / Stopp Verhalten"
|
stst.columnconfigure(0, weight=1)
|
||||||
|
stst.columnconfigure(1, weight=1)
|
||||||
|
stst.columnconfigure(2, weight=1)
|
||||||
|
stst["text"] = _("Start / Stop behavior")
|
||||||
stst.grid(columnspan=2, pady=2, sticky="we")
|
stst.grid(columnspan=2, pady=2, sticky="we")
|
||||||
|
|
||||||
self.var_start = tkinter.BooleanVar(stst)
|
self.var_start = tkinter.BooleanVar(stst)
|
||||||
self.var_reload = tkinter.BooleanVar(stst)
|
self.var_reload = tkinter.BooleanVar(stst)
|
||||||
|
self.var_reload_delay = tkinter.StringVar(stst)
|
||||||
self.var_zexit = tkinter.BooleanVar(stst)
|
self.var_zexit = tkinter.BooleanVar(stst)
|
||||||
self.var_zerr = tkinter.BooleanVar(stst)
|
self.var_zerr = tkinter.BooleanVar(stst)
|
||||||
|
self.var_replace_ios = tkinter.StringVar(stst)
|
||||||
|
self.var_replace_ios_options = tkinter.StringVar(stst)
|
||||||
|
|
||||||
|
# Row 0
|
||||||
ckb_start = tkinter.Checkbutton(stst)
|
ckb_start = tkinter.Checkbutton(stst)
|
||||||
ckb_start["text"] = "Programm automatisch starten"
|
ckb_start["text"] = _("Start program automatically")
|
||||||
ckb_start["state"] = self.xmlstate
|
ckb_start["state"] = xmlstate
|
||||||
ckb_start["variable"] = self.var_start
|
ckb_start["variable"] = self.var_start
|
||||||
ckb_start.grid(**cpadw)
|
ckb_start.grid(columnspan=3, **cpadw)
|
||||||
|
|
||||||
|
# Row 1
|
||||||
ckb_reload = tkinter.Checkbutton(stst)
|
ckb_reload = tkinter.Checkbutton(stst)
|
||||||
ckb_reload["text"] = "Programm nach Beenden neu starten"
|
ckb_reload["text"] = _("Restart program after exit")
|
||||||
ckb_reload["state"] = self.xmlstate
|
ckb_reload["state"] = xmlstate
|
||||||
ckb_reload["variable"] = self.var_reload
|
ckb_reload["variable"] = self.var_reload
|
||||||
ckb_reload.grid(**cpadw)
|
ckb_reload.grid(columnspan=3, **cpadw)
|
||||||
|
|
||||||
|
# Row 2
|
||||||
|
lbl = tkinter.Label(stst)
|
||||||
|
lbl["text"] = _("Restart after n seconds of delay")
|
||||||
|
lbl.grid(columnspan=2, **cpadw)
|
||||||
|
sbx = tkinter.Spinbox(stst)
|
||||||
|
sbx["to"] = 60
|
||||||
|
sbx["from_"] = 5
|
||||||
|
sbx["textvariable"] = self.var_reload_delay
|
||||||
|
sbx["width"] = 4
|
||||||
|
sbx.grid(column=2, row=2, **cpade)
|
||||||
|
|
||||||
|
# Row 3
|
||||||
|
lbl = tkinter.Label(stst)
|
||||||
|
lbl["text"] = _("Set process image to NULL if program terminates...")
|
||||||
|
lbl.grid(columnspan=3, **cpadw)
|
||||||
|
|
||||||
|
# Row 4
|
||||||
ckb_zexit = tkinter.Checkbutton(stst, justify="left")
|
ckb_zexit = tkinter.Checkbutton(stst, justify="left")
|
||||||
ckb_zexit["state"] = self.xmlstate
|
ckb_zexit["state"] = xmlstate
|
||||||
ckb_zexit["text"] = "Prozessabbild auf NULL setzen, wenn " \
|
ckb_zexit["text"] = _("... successfully")
|
||||||
"Programm\nerfolgreich beendet wird"
|
|
||||||
ckb_zexit["variable"] = self.var_zexit
|
ckb_zexit["variable"] = self.var_zexit
|
||||||
ckb_zexit.grid(**cpadw)
|
ckb_zexit.grid(column=1, **cpadw)
|
||||||
|
|
||||||
|
# Row 5
|
||||||
ckb_zerr = tkinter.Checkbutton(stst, justify="left")
|
ckb_zerr = tkinter.Checkbutton(stst, justify="left")
|
||||||
ckb_zerr["state"] = self.xmlstate
|
ckb_zerr["state"] = xmlstate
|
||||||
ckb_zerr["text"] = "Prozessabbild auf NULL setzen, wenn " \
|
ckb_zerr["text"] = _("... with errors")
|
||||||
"Programm\ndurch Absturz beendet wird"
|
|
||||||
ckb_zerr["variable"] = self.var_zerr
|
ckb_zerr["variable"] = self.var_zerr
|
||||||
ckb_zerr.grid(**cpadw)
|
ckb_zerr.grid(column=1, **cpadw)
|
||||||
|
|
||||||
|
# Row 6
|
||||||
|
lbl = tkinter.Label(stst)
|
||||||
|
lbl["text"] = _("Replace IO file:")
|
||||||
|
lbl.grid(row=6, **cpadw)
|
||||||
|
|
||||||
|
opt = tkinter.OptionMenu(
|
||||||
|
stst, self.var_replace_ios_options, *self.replace_ios_options,
|
||||||
|
command=self.__state_replace_ios
|
||||||
|
)
|
||||||
|
opt["state"] = xmlstate
|
||||||
|
opt["width"] = 30
|
||||||
|
opt.grid(row=6, column=1, columnspan=2, **cpadwe)
|
||||||
|
|
||||||
|
# Row 7
|
||||||
|
self.txt_replace_ios = tkinter.Entry(stst)
|
||||||
|
self.txt_replace_ios["state"] = xmlstate
|
||||||
|
self.txt_replace_ios["textvariable"] = self.var_replace_ios
|
||||||
|
self.txt_replace_ios.grid(column=1, columnspan=2, **cpadwe)
|
||||||
|
|
||||||
# Gruppe Programm
|
# Gruppe Programm
|
||||||
prog = tkinter.LabelFrame(self)
|
prog = tkinter.LabelFrame(self)
|
||||||
prog["text"] = "PLC Programm"
|
prog.columnconfigure(0, weight=1)
|
||||||
|
prog.columnconfigure(1, weight=1)
|
||||||
|
prog.columnconfigure(2, weight=1)
|
||||||
|
prog["text"] = _("PLC program")
|
||||||
prog.grid(columnspan=2, pady=2, sticky="we")
|
prog.grid(columnspan=2, pady=2, sticky="we")
|
||||||
|
|
||||||
self.var_pythonver = tkinter.IntVar(prog)
|
self.var_pythonver = tkinter.IntVar(prog)
|
||||||
self.var_startpy = tkinter.StringVar(prog)
|
self.var_startpy = tkinter.StringVar(prog)
|
||||||
self.var_startargs = tkinter.StringVar(prog)
|
self.var_startargs = tkinter.StringVar(prog)
|
||||||
self.var_slave = tkinter.BooleanVar(prog)
|
self.var_plcworkdir_set_uid = tkinter.BooleanVar(prog)
|
||||||
|
|
||||||
self.var_pythonver.set(3)
|
self.var_pythonver.set(3)
|
||||||
|
|
||||||
|
# Row 0
|
||||||
lbl = tkinter.Label(prog)
|
lbl = tkinter.Label(prog)
|
||||||
lbl["text"] = "Python Version"
|
lbl["text"] = _("Python version") + ":"
|
||||||
lbl.grid(columnspan=2, row=0, **cpadw)
|
lbl.grid(row=0, **cpadw)
|
||||||
|
|
||||||
rbn = tkinter.Radiobutton(prog)
|
rbn = tkinter.Radiobutton(prog)
|
||||||
rbn["state"] = self.xmlstate
|
rbn["state"] = xmlstate
|
||||||
rbn["text"] = "Python2"
|
rbn["text"] = "Python2"
|
||||||
rbn["value"] = 2
|
rbn["value"] = 2
|
||||||
rbn["variable"] = self.var_pythonver
|
rbn["variable"] = self.var_pythonver
|
||||||
rbn.grid(column=0, row=1, **cpadw)
|
rbn.grid(row=0, column=1, **cpade)
|
||||||
|
|
||||||
rbn = tkinter.Radiobutton(prog)
|
rbn = tkinter.Radiobutton(prog)
|
||||||
rbn["state"] = self.xmlstate
|
rbn["state"] = xmlstate
|
||||||
rbn["text"] = "Python3"
|
rbn["text"] = "Python3"
|
||||||
rbn["value"] = 3
|
rbn["value"] = 3
|
||||||
rbn["variable"] = self.var_pythonver
|
rbn["variable"] = self.var_pythonver
|
||||||
rbn.grid(column=1, row=1, **cpadw)
|
rbn.grid(row=0, column=2, **cpadw)
|
||||||
|
|
||||||
|
# Row 1
|
||||||
lbl = tkinter.Label(prog)
|
lbl = tkinter.Label(prog)
|
||||||
lbl["text"] = "Python PLC Programname"
|
lbl["text"] = _("Python PLC program name")
|
||||||
lbl.grid(columnspan=2, **cpadw)
|
lbl.grid(columnspan=3, **cpadw)
|
||||||
|
|
||||||
|
# Row 2
|
||||||
lst = self.xmlcli.get_filelist()
|
lst = self.xmlcli.get_filelist()
|
||||||
|
lst.sort()
|
||||||
|
if ".placeholder" in lst:
|
||||||
|
lst.remove(".placeholder")
|
||||||
if len(lst) == 0:
|
if len(lst) == 0:
|
||||||
lst.append("none")
|
lst.append("none")
|
||||||
opt_startpy = tkinter.OptionMenu(
|
opt_startpy = tkinter.OptionMenu(
|
||||||
prog, self.var_startpy, *lst)
|
prog, self.var_startpy, *lst
|
||||||
opt_startpy["state"] = self.xmlstate
|
)
|
||||||
opt_startpy.grid(columnspan=2, **cpadwe)
|
opt_startpy["state"] = xmlstate
|
||||||
|
opt_startpy.grid(columnspan=3, **cpadwe)
|
||||||
|
|
||||||
|
# Row 3
|
||||||
lbl = tkinter.Label(prog)
|
lbl = tkinter.Label(prog)
|
||||||
lbl["text"] = "Programm Argumente"
|
lbl["text"] = _("Program arguments:")
|
||||||
lbl.grid(columnspan=2, **cpadw)
|
lbl.grid(**cpadw)
|
||||||
|
|
||||||
txt = tkinter.Entry(prog)
|
txt = tkinter.Entry(prog)
|
||||||
txt["textvariable"] = self.var_startargs
|
txt["textvariable"] = self.var_startargs
|
||||||
txt.grid(columnspan=2, **cpadw)
|
txt.grid(row=3, column=1, columnspan=2, **cpadwe)
|
||||||
|
|
||||||
ckb_slave = tkinter.Checkbutton(prog, justify="left")
|
# Row 4
|
||||||
ckb_slave["state"] = self.xmlstate
|
ckb = tkinter.Checkbutton(prog)
|
||||||
ckb_slave["text"] = "RevPi als PLC-Slave verwenden"
|
ckb["text"] = _("Set write access to workdirectory")
|
||||||
ckb_slave["state"] = "disabled"
|
ckb["state"] = xmlstate
|
||||||
|
ckb["variable"] = self.var_plcworkdir_set_uid
|
||||||
|
ckb.grid(columnspan=2, **cpadw)
|
||||||
|
|
||||||
|
# Gruppe Services
|
||||||
|
services = tkinter.LabelFrame(self)
|
||||||
|
services["text"] = _("RevPiPyLoad server services")
|
||||||
|
services.grid(columnspan=2, pady=2, sticky="we")
|
||||||
|
|
||||||
|
# RevPiSlave Service
|
||||||
|
self.var_slave = tkinter.BooleanVar(services)
|
||||||
|
self.var_slaveacl = tkinter.StringVar(services)
|
||||||
|
row = 0
|
||||||
|
ckb_slave = tkinter.Checkbutton(services, justify="left")
|
||||||
|
ckb_slave["state"] = xmlstate
|
||||||
|
ckb_slave["text"] = _("Use RevPi as PLC-Slave")
|
||||||
ckb_slave["variable"] = self.var_slave
|
ckb_slave["variable"] = self.var_slave
|
||||||
ckb_slave.grid(columnspan=2, **cpadw)
|
ckb_slave.grid(column=0, **cpadw)
|
||||||
|
|
||||||
# Gruppe XMLRPC
|
btn_slaveacl = tkinter.Button(services, justify="center")
|
||||||
xmlrpc = tkinter.LabelFrame(self)
|
btn_slaveacl["command"] = self.btn_slaveacl
|
||||||
xmlrpc["text"] = "XML-RPC Server"
|
btn_slaveacl["text"] = _("Edit ACL")
|
||||||
xmlrpc.grid(columnspan=2, pady=2, sticky="we")
|
btn_slaveacl.grid(column=1, row=row, **cpadwe)
|
||||||
|
|
||||||
self.var_xmlon = tkinter.BooleanVar(xmlrpc)
|
row = 1
|
||||||
self.var_xmlmod2 = tkinter.BooleanVar(xmlrpc)
|
lbl = tkinter.Label(services)
|
||||||
self.var_xmlmod3 = tkinter.BooleanVar(xmlrpc)
|
lbl["text"] = _("RevPi-Slave service is:")
|
||||||
self.var_xmlport = tkinter.StringVar(xmlrpc)
|
lbl.grid(column=0, **cpade)
|
||||||
self.var_xmlport.set("55123")
|
|
||||||
|
|
||||||
ckb_xmlon = tkinter.Checkbutton(xmlrpc)
|
status = self.xmlcli.plcslaverunning()
|
||||||
|
lbl = tkinter.Label(services)
|
||||||
|
lbl["fg"] = "green" if status else "red"
|
||||||
|
lbl["text"] = _("running") if status else _("stopped")
|
||||||
|
lbl.grid(column=1, row=row, **cpadwe)
|
||||||
|
|
||||||
|
# MQTT Service
|
||||||
|
self.var_mqtton = tkinter.BooleanVar(services)
|
||||||
|
try:
|
||||||
|
status = self.xmlcli.mqttrunning()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
row = 2
|
||||||
|
ckb_slave = tkinter.Checkbutton(services, justify="left")
|
||||||
|
ckb_slave["state"] = xmlstate
|
||||||
|
ckb_slave["text"] = _("MQTT process image publisher")
|
||||||
|
ckb_slave["variable"] = self.var_mqtton
|
||||||
|
ckb_slave.grid(column=0, **cpadw)
|
||||||
|
|
||||||
|
btn_slaveacl = tkinter.Button(services, justify="center")
|
||||||
|
btn_slaveacl["command"] = self.btn_mqttsettings
|
||||||
|
btn_slaveacl["text"] = _("Settings")
|
||||||
|
btn_slaveacl.grid(column=1, row=row, **cpadwe)
|
||||||
|
|
||||||
|
row = 3
|
||||||
|
lbl = tkinter.Label(services)
|
||||||
|
lbl["text"] = _("MQTT publish service is:")
|
||||||
|
lbl.grid(column=0, **cpade)
|
||||||
|
|
||||||
|
lbl = tkinter.Label(services)
|
||||||
|
lbl["fg"] = "green" if status else "red"
|
||||||
|
lbl["text"] = _("running") if status else _("stopped")
|
||||||
|
lbl.grid(column=1, row=row, **cpadwe)
|
||||||
|
|
||||||
|
# XML-RPC Service
|
||||||
|
self.var_xmlon = tkinter.BooleanVar(services)
|
||||||
|
self.var_xmlacl = tkinter.StringVar(services)
|
||||||
|
row = 4
|
||||||
|
ckb_xmlon = tkinter.Checkbutton(services)
|
||||||
ckb_xmlon["command"] = self.askxmlon
|
ckb_xmlon["command"] = self.askxmlon
|
||||||
ckb_xmlon["state"] = self.xmlstate
|
ckb_xmlon["state"] = xmlstate
|
||||||
ckb_xmlon["text"] = "XML-RPC Server aktiv auf RevPi"
|
ckb_xmlon["text"] = _("Activate XML-RPC server on RevPi")
|
||||||
ckb_xmlon["variable"] = self.var_xmlon
|
ckb_xmlon["variable"] = self.var_xmlon
|
||||||
ckb_xmlon.grid(**cpadw)
|
ckb_xmlon.grid(**cpadw)
|
||||||
|
|
||||||
self.ckb_xmlmod2 = tkinter.Checkbutton(xmlrpc, justify="left")
|
btn_slaveacl = tkinter.Button(services, justify="center")
|
||||||
self.ckb_xmlmod2["command"] = self.xmlmods
|
btn_slaveacl["command"] = self.btn_xmlacl
|
||||||
self.ckb_xmlmod2["state"] = self.xmlstate
|
btn_slaveacl["text"] = _("Edit ACL")
|
||||||
self.ckb_xmlmod2["text"] = \
|
btn_slaveacl.grid(column=1, row=row, **cpadwe)
|
||||||
"Download von piCtory Konfiguration und\nPLC Programm zulassen"
|
|
||||||
self.ckb_xmlmod2["variable"] = self.var_xmlmod2
|
|
||||||
self.ckb_xmlmod2.grid(**cpadw)
|
|
||||||
|
|
||||||
self.ckb_xmlmod3 = tkinter.Checkbutton(xmlrpc, justify="left")
|
# Buttons am Ende
|
||||||
self.ckb_xmlmod3["state"] = self.xmlstate
|
|
||||||
self.ckb_xmlmod3["text"] = \
|
|
||||||
"Upload von piCtory Konfiguration und\nPLC Programm zualssen"
|
|
||||||
self.ckb_xmlmod3["variable"] = self.var_xmlmod3
|
|
||||||
self.ckb_xmlmod3.grid(**cpadw)
|
|
||||||
|
|
||||||
lbl = tkinter.Label(xmlrpc)
|
|
||||||
lbl["text"] = "XML-RPC Serverport"
|
|
||||||
lbl.grid(**cpadw)
|
|
||||||
|
|
||||||
spb_xmlport = tkinter.Spinbox(xmlrpc)
|
|
||||||
spb_xmlport["to"] = 65535
|
|
||||||
spb_xmlport["from"] = 1024
|
|
||||||
spb_xmlport["state"] = self.xmlstate
|
|
||||||
spb_xmlport["textvariable"] = self.var_xmlport
|
|
||||||
spb_xmlport.grid(**cpadwe)
|
|
||||||
|
|
||||||
# Buttons
|
|
||||||
btn_save = tkinter.Button(self)
|
btn_save = tkinter.Button(self)
|
||||||
btn_save["command"] = self._setappdata
|
btn_save["command"] = self._setappdata
|
||||||
btn_save["state"] = self.xmlstate
|
btn_save["state"] = xmlstate
|
||||||
btn_save["text"] = "Speichern"
|
btn_save["text"] = _("Save")
|
||||||
btn_save.grid(column=0, row=3)
|
btn_save.grid(column=0, row=3)
|
||||||
|
|
||||||
btn_close = tkinter.Button(self)
|
btn_close = tkinter.Button(self)
|
||||||
btn_close["command"] = self.master.destroy
|
btn_close["command"] = self._checkclose
|
||||||
btn_close["text"] = "Schließen"
|
btn_close["text"] = _("Close")
|
||||||
btn_close.grid(column=1, row=3)
|
btn_close.grid(column=1, row=3)
|
||||||
|
|
||||||
def _loadappdata(self):
|
def _loadappdata(self, refresh=False):
|
||||||
dc = self.xmlcli.get_config()
|
u"""Läd aktuelle Einstellungen vom RevPi.
|
||||||
|
@param refresh Wenn True, werden Einstellungen heruntergeladen."""
|
||||||
|
if refresh:
|
||||||
|
self.dc = self.xmlcli.get_config()
|
||||||
|
|
||||||
self.var_start.set(dc.get("autostart", "1"))
|
self.var_start.set(self.dc.get("autostart", 1))
|
||||||
self.var_reload.set(dc.get("autoreload", "1"))
|
self.var_reload.set(self.dc.get("autoreload", 1))
|
||||||
self.var_zexit.set(dc.get("zeroonexit", "0"))
|
self.var_reload_delay.set(self.dc.get("autoreloaddelay", 5))
|
||||||
self.var_zerr.set(dc.get("zeroonerror", "0"))
|
self.var_zexit.set(self.dc.get("zeroonexit", 0))
|
||||||
|
self.var_zerr.set(self.dc.get("zeroonerror", 0))
|
||||||
|
replace_ios = self.dc.get("replace_ios", "")
|
||||||
|
self.var_replace_ios.set(replace_ios)
|
||||||
|
if replace_ios == "":
|
||||||
|
self.var_replace_ios_options.set(self.replace_ios_options[0])
|
||||||
|
elif replace_ios == "/etc/revpipyload/replace_ios.conf":
|
||||||
|
self.var_replace_ios_options.set(self.replace_ios_options[1])
|
||||||
|
elif replace_ios == "replace_ios.conf":
|
||||||
|
self.var_replace_ios_options.set(self.replace_ios_options[2])
|
||||||
|
else:
|
||||||
|
self.var_replace_ios_options.set(self.replace_ios_options[3])
|
||||||
|
self.__state_replace_ios(self.var_replace_ios_options.get())
|
||||||
|
# TODO: rtlevel (0)
|
||||||
|
|
||||||
self.var_startpy.set(dc.get("plcprogram", "none.py"))
|
self.var_startpy.set(self.dc.get("plcprogram", "none.py"))
|
||||||
self.var_startargs.set(dc.get("plcarguments", ""))
|
self.var_startargs.set(self.dc.get("plcarguments", ""))
|
||||||
self.var_pythonver.set(dc.get("pythonversion", "3"))
|
self.var_pythonver.set(self.dc.get("pythonversion", 3))
|
||||||
self.var_slave.set(dc.get("plcslave", "0"))
|
self.var_plcworkdir_set_uid.set(
|
||||||
|
self.dc.get("plcworkdir_set_uid", False))
|
||||||
|
|
||||||
self.var_xmlon.set(dc.get("xmlrpc", 0) >= 1)
|
# MQTT Einstellungen laden
|
||||||
self.var_xmlmod2.set(dc.get("xmlrpc", 0) >= 2)
|
self.var_mqtton.set(self.dc.get("mqtt", 0))
|
||||||
self.var_xmlmod3.set(dc.get("xmlrpc", 0) >= 3)
|
for key in self._dict_mqttsettings:
|
||||||
|
if key in self.dc:
|
||||||
|
self._dict_mqttsettings[key] = self.dc[key]
|
||||||
|
|
||||||
self.var_xmlport.set(dc.get("xmlrpcport", "55123"))
|
self.var_slave.set(self.dc.get("plcslave", 0))
|
||||||
|
self.var_slaveacl.set(self.dc.get("plcslaveacl", ""))
|
||||||
|
|
||||||
|
self.var_xmlon.set(self.dc.get("xmlrpc", 0))
|
||||||
|
self.var_xmlacl.set(self.dc.get("xmlrpcacl", ""))
|
||||||
|
|
||||||
def _setappdata(self):
|
def _setappdata(self):
|
||||||
dc = {}
|
u"""Speichert geänderte Einstellungen auf RevPi.
|
||||||
dc["autostart"] = int(self.var_start.get())
|
@return None"""
|
||||||
dc["autoreload"] = int(self.var_reload.get())
|
|
||||||
dc["zeroonexit"] = int(self.var_zexit.get())
|
|
||||||
dc["zeroonerror"] = int(self.var_zerr.get())
|
|
||||||
|
|
||||||
dc["plcprogram"] = self.var_startpy.get()
|
if not self._changesdone():
|
||||||
dc["plcarguments"] = self.var_startargs.get()
|
tkmsg.showinfo(
|
||||||
dc["pythonversion"] = self.var_pythonver.get()
|
_("Information"),
|
||||||
dc["plcslave"] = int(self.var_slave.get())
|
_("You have not made any changes to save."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
self._checkclose()
|
||||||
|
return None
|
||||||
|
|
||||||
dc["xmlrpc"] = 0
|
# Gültigkeitsprüfung
|
||||||
if self.var_xmlon.get():
|
if not self._checkvalues():
|
||||||
dc["xmlrpc"] += 1
|
return None
|
||||||
if self.var_xmlmod2.get():
|
|
||||||
dc["xmlrpc"] += 1
|
|
||||||
if self.var_xmlmod3.get():
|
|
||||||
dc["xmlrpc"] += 1
|
|
||||||
|
|
||||||
dc["xmlrpcport"] = self.var_xmlport.get()
|
ask = tkmsg.askokcancel(
|
||||||
self.xmlmode = dc["xmlrpc"]
|
_("Question"),
|
||||||
|
_("The settings will be set on the Revolution Pi now. \n\n"
|
||||||
ask = tkmsg.askyesnocancel(
|
"If you made changes on the 'PCL Program' section, your plc "
|
||||||
"Frage", "Die Einstellungen werden jetzt auf dem Revolution Pi "
|
"program will restart! \n"
|
||||||
"gespeichert. \n\nSollen die neuen Einstellungen sofort in Kraft "
|
"ACL changes and service settings are applied immediately."),
|
||||||
"treten? \nDies bedeutet einen Neustart des Dienstes und des ggf. "
|
parent=self.master
|
||||||
"laufenden PLC-Programms!", parent=self.master
|
|
||||||
)
|
)
|
||||||
if ask is not None:
|
if ask:
|
||||||
if self.xmlcli.set_config(dc, ask):
|
self.dc["autoreload"] = int(self.var_reload.get())
|
||||||
|
self.dc["autoreloaddelay"] = int(self.var_reload_delay.get())
|
||||||
|
self.dc["autostart"] = int(self.var_start.get())
|
||||||
|
self.dc["plcprogram"] = self.var_startpy.get()
|
||||||
|
self.dc["plcarguments"] = self.var_startargs.get()
|
||||||
|
self.dc["pythonversion"] = self.var_pythonver.get()
|
||||||
|
self.dc["plcworkdir_set_uid"] = \
|
||||||
|
int(self.var_plcworkdir_set_uid.get())
|
||||||
|
# TODO: rtlevel (0)
|
||||||
|
self.dc["zeroonerror"] = int(self.var_zerr.get())
|
||||||
|
self.dc["zeroonexit"] = int(self.var_zexit.get())
|
||||||
|
self.dc["replace_ios"] = self.var_replace_ios.get()
|
||||||
|
|
||||||
|
# MQTT Settings
|
||||||
|
self.dc["mqtt"] = int(self.var_mqtton.get())
|
||||||
|
self.dc["mqttbasetopic"] = \
|
||||||
|
self._dict_mqttsettings["mqttbasetopic"]
|
||||||
|
self.dc["mqttclient_id"] = \
|
||||||
|
self._dict_mqttsettings["mqttclient_id"]
|
||||||
|
self.dc["mqttbroker_address"] = \
|
||||||
|
self._dict_mqttsettings["mqttbroker_address"]
|
||||||
|
self.dc["mqttpassword"] = \
|
||||||
|
self._dict_mqttsettings["mqttpassword"]
|
||||||
|
self.dc["mqttusername"] = \
|
||||||
|
self._dict_mqttsettings["mqttusername"]
|
||||||
|
self.dc["mqttport"] = \
|
||||||
|
int(self._dict_mqttsettings["mqttport"])
|
||||||
|
self.dc["mqttsend_on_event"] = \
|
||||||
|
int(self._dict_mqttsettings["mqttsend_on_event"])
|
||||||
|
self.dc["mqttsendinterval"] = \
|
||||||
|
int(self._dict_mqttsettings["mqttsendinterval"])
|
||||||
|
self.dc["mqtttls_set"] = \
|
||||||
|
int(self._dict_mqttsettings["mqtttls_set"])
|
||||||
|
self.dc["mqttwrite_outputs"] = \
|
||||||
|
int(self._dict_mqttsettings["mqttwrite_outputs"])
|
||||||
|
|
||||||
|
# PLCSlave Settings
|
||||||
|
self.dc["plcslave"] = int(self.var_slave.get())
|
||||||
|
self.dc["plcslaveacl"] = self.var_slaveacl.get()
|
||||||
|
|
||||||
|
# XML Settings
|
||||||
|
self.dc["xmlrpc"] = int(self.var_xmlon.get())
|
||||||
|
self.dc["xmlrpcacl"] = self.var_xmlacl.get()
|
||||||
|
|
||||||
|
if self.xmlcli.set_config(self.dc, ask):
|
||||||
tkmsg.showinfo(
|
tkmsg.showinfo(
|
||||||
"Information", "Einstellungen gespeichert.",
|
_("Information"),
|
||||||
|
_("Settings saved"),
|
||||||
parent=self.master
|
parent=self.master
|
||||||
)
|
)
|
||||||
|
self.dorestart = ask
|
||||||
|
self._checkclose()
|
||||||
else:
|
else:
|
||||||
tkmsg.showerror(
|
tkmsg.showerror(
|
||||||
"Fehler", "Die Einstellungen konnten nicht gesichert"
|
_("Error"),
|
||||||
"werden. Dies kann passieren, wenn Werte falsch sind!",
|
_("The settings could not be saved. This can happen if "
|
||||||
|
"values are wrong!"),
|
||||||
parent=self.master
|
parent=self.master
|
||||||
)
|
)
|
||||||
|
|
||||||
def askxmlon(self):
|
def askxmlon(self):
|
||||||
if not self.var_xmlon.get():
|
u"""Fragt Nuter, ob wirklicht abgeschaltet werden soll."""
|
||||||
ask = tkmsg.askyesno(
|
if not (self.var_xmlon.get() or self.mrk_xmlmodask):
|
||||||
"Frage", "Soll der XML-RPC Server wirklich beendet werden? "
|
self.mrk_xmlmodask = tkmsg.askyesno(
|
||||||
"Sie können dann NICHT mehr mit diesem Programm auf den "
|
_("Question"),
|
||||||
"Revolution Pi zugreifen.", parent=self.master
|
_("Are you sure you want to deactivate the XML-RPC server? "
|
||||||
|
"You will NOT be able to access the Revolution Pi with "
|
||||||
|
"this program."),
|
||||||
|
parent=self.master
|
||||||
)
|
)
|
||||||
if not ask:
|
if not self.mrk_xmlmodask:
|
||||||
self.var_xmlon.set(True)
|
self.var_xmlon.set(True)
|
||||||
|
|
||||||
self.xmlmods()
|
def btn_mqttsettings(self):
|
||||||
|
u"""Öffnet Fenster für MQTT Einstellungen."""
|
||||||
|
win = tkinter.Toplevel(self)
|
||||||
|
win.focus_set()
|
||||||
|
win.grab_set()
|
||||||
|
self.frm_mqttmgr = MqttManager(
|
||||||
|
win, self._dict_mqttsettings,
|
||||||
|
readonly=self.xmlmodus < 4
|
||||||
|
)
|
||||||
|
self.wait_window(win)
|
||||||
|
self._dict_mqttsettings = self.frm_mqttmgr.settings
|
||||||
|
|
||||||
def xmlmods(self):
|
def btn_slaveacl(self):
|
||||||
self.ckb_xmlmod2["state"] = \
|
u"""Öffnet Fenster für ACL-Verwaltung."""
|
||||||
"normal" if self.var_xmlon.get() else "disabled"
|
win = tkinter.Toplevel(self)
|
||||||
self.ckb_xmlmod3["state"] = \
|
win.focus_set()
|
||||||
"normal" if self.var_xmlmod2.get() else "disabled"
|
win.grab_set()
|
||||||
|
self.frm_slaveacl = AclManager(
|
||||||
|
win, 0, 1,
|
||||||
|
self.var_slaveacl.get(),
|
||||||
|
readonly=self.xmlmodus < 4
|
||||||
|
)
|
||||||
|
self.frm_slaveacl.acltext = {
|
||||||
|
0: _("read only"),
|
||||||
|
1: _("read and write")
|
||||||
|
}
|
||||||
|
self.wait_window(win)
|
||||||
|
self.var_slaveacl.set(self.frm_slaveacl.acl)
|
||||||
|
|
||||||
|
def btn_xmlacl(self):
|
||||||
|
u"""Öffnet Fenster für ACL-Verwaltung."""
|
||||||
|
win = tkinter.Toplevel(self)
|
||||||
|
win.focus_set()
|
||||||
|
win.grab_set()
|
||||||
|
self.frm_xmlacl = AclManager(
|
||||||
|
win, 0, 4,
|
||||||
|
self.var_xmlacl.get(),
|
||||||
|
readonly=self.xmlmodus < 4
|
||||||
|
)
|
||||||
|
self.frm_xmlacl.acltext = {
|
||||||
|
0: _("Start/Stop PLC program and read logs"),
|
||||||
|
1: _("+ read IOs in watch modus"),
|
||||||
|
2: _("+ read properties and download PLC program"),
|
||||||
|
3: _("+ upload PLC program"),
|
||||||
|
4: _("+ set properties")
|
||||||
|
}
|
||||||
|
self.wait_window(win)
|
||||||
|
self.var_xmlacl.set(self.frm_xmlacl.acl)
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
u"""Beendet alle Unterfenster und sich selbst."""
|
||||||
|
if self.frm_mqttmgr is not None:
|
||||||
|
self.frm_mqttmgr.master.destroy()
|
||||||
|
if self.frm_slaveacl is not None:
|
||||||
|
self.frm_slaveacl.master.destroy()
|
||||||
|
if self.frm_xmlacl is not None:
|
||||||
|
self.frm_xmlacl.master.destroy()
|
||||||
|
|
||||||
|
super().destroy()
|
||||||
|
|||||||
@@ -1,31 +1,33 @@
|
|||||||
#
|
|
||||||
# RevPiPyControl
|
|
||||||
#
|
|
||||||
# Webpage: https://revpimodio.org/revpipyplc/
|
|
||||||
# (c) Sven Sager, License: LGPLv3
|
|
||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
u"""Fenster um RevPi-Verbindungen einzurichten."""
|
||||||
|
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import pickle
|
import pickle
|
||||||
import tkinter
|
import tkinter
|
||||||
import tkinter.messagebox as tkmsg
|
import tkinter.messagebox as tkmsg
|
||||||
from os import environ
|
from mytools import gettrans
|
||||||
|
from mytools import savefile_connections as savefile
|
||||||
|
from revpidevelop import _loaddefaults as developloaddefaults
|
||||||
|
from revpidevelop import _savedefaults as developsavedefaults
|
||||||
|
from revpiprogram import _loaddefaults as programloaddefaults
|
||||||
|
from revpiprogram import _savedefaults as programsavedefaults
|
||||||
from os import makedirs
|
from os import makedirs
|
||||||
from sys import platform
|
|
||||||
|
|
||||||
# Systemwerte
|
|
||||||
if platform == "linux":
|
|
||||||
homedir = environ["HOME"]
|
|
||||||
else:
|
|
||||||
homedir = environ["APPDATA"]
|
|
||||||
savefile = os.path.join(homedir, ".revpipyplc", "connections.dat")
|
|
||||||
|
|
||||||
|
|
||||||
# Für andere Module zum Laden der Connections
|
# Übersetzungen laden
|
||||||
|
_ = gettrans()
|
||||||
|
|
||||||
|
|
||||||
def get_connections():
|
def get_connections():
|
||||||
|
u"""Verbindungen aus Datei laden.
|
||||||
|
@return dict() mit Verbindungen"""
|
||||||
if os.path.exists(savefile):
|
if os.path.exists(savefile):
|
||||||
fh = open(savefile, "rb")
|
with open(savefile, "rb") as fh:
|
||||||
connections = pickle.load(fh)
|
connections = pickle.load(fh)
|
||||||
return connections
|
return connections
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
@@ -33,22 +35,44 @@ def get_connections():
|
|||||||
|
|
||||||
class RevPiPlcList(tkinter.Frame):
|
class RevPiPlcList(tkinter.Frame):
|
||||||
|
|
||||||
|
u"""TK Fenster."""
|
||||||
|
|
||||||
def __init__(self, master):
|
def __init__(self, master):
|
||||||
|
u"""Init RevPiPlcList-class.
|
||||||
|
@param master tkinter master"""
|
||||||
super().__init__(master)
|
super().__init__(master)
|
||||||
|
self.master.bind("<KeyPress-Escape>", self._checkclose)
|
||||||
self.pack()
|
self.pack()
|
||||||
|
|
||||||
self.changes = False
|
self.changes = False
|
||||||
|
|
||||||
# Daten laden
|
# Daten laden
|
||||||
self._connections = {}
|
self._connections = get_connections()
|
||||||
|
|
||||||
# Fenster bauen
|
# Fenster bauen
|
||||||
self._createwidgets()
|
self._createwidgets()
|
||||||
self._loadappdata()
|
self.build_listconn()
|
||||||
|
|
||||||
|
def _checkclose(self, event=None):
|
||||||
|
u"""Prüft ob Fenster beendet werden soll.
|
||||||
|
@param event tkinter-Event"""
|
||||||
|
ask = True
|
||||||
|
if self.changes:
|
||||||
|
ask = tkmsg.askyesno(
|
||||||
|
_("Question"),
|
||||||
|
_("Do you really want to quit? \nUnsaved changes will "
|
||||||
|
"be lost"),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
|
if ask:
|
||||||
|
self.master.destroy()
|
||||||
|
|
||||||
def _createwidgets(self):
|
def _createwidgets(self):
|
||||||
self.master.wm_title("RevPi Python PLC Connections")
|
u"""Erstellt alle Widgets."""
|
||||||
|
self.master.wm_title(_("RevPi Python PLC connections"))
|
||||||
self.master.wm_resizable(width=False, height=False)
|
self.master.wm_resizable(width=False, height=False)
|
||||||
|
self.master.protocol("WM_DELETE_WINDOW", self._checkclose)
|
||||||
|
|
||||||
# Listbox mit vorhandenen Verbindungen
|
# Listbox mit vorhandenen Verbindungen
|
||||||
self.scr_conn = tkinter.Scrollbar(self)
|
self.scr_conn = tkinter.Scrollbar(self)
|
||||||
@@ -66,71 +90,92 @@ class RevPiPlcList(tkinter.Frame):
|
|||||||
self.var_port.set("55123")
|
self.var_port.set("55123")
|
||||||
|
|
||||||
# Eingabefelder für Adresse und Namen
|
# Eingabefelder für Adresse und Namen
|
||||||
tkinter.Label(self, text="Name").grid(
|
tkinter.Label(self, text=_("Name")).grid(
|
||||||
column=2, row=0, sticky="wn", padx=5, pady=5)
|
column=2, row=0, sticky="wn", padx=5, pady=5
|
||||||
|
)
|
||||||
self.txt_name = tkinter.Entry(self, textvariable=self.var_name)
|
self.txt_name = tkinter.Entry(self, textvariable=self.var_name)
|
||||||
self.txt_name.bind("<KeyRelease>", self.evt_keypress)
|
self.txt_name.bind("<KeyRelease>", self.evt_keypress)
|
||||||
self.txt_name.grid(
|
self.txt_name.grid(
|
||||||
column=3, row=0, columnspan=3, sticky="n", padx=5, pady=5)
|
column=3, row=0, columnspan=3, sticky="n", padx=5, pady=5
|
||||||
|
)
|
||||||
|
|
||||||
tkinter.Label(self, text="IP-Adresse").grid(
|
tkinter.Label(self, text=_("IP address")).grid(
|
||||||
column=2, row=1, sticky="wn", padx=5, pady=5
|
column=2, row=1, sticky="wn", padx=5, pady=5
|
||||||
)
|
)
|
||||||
self.txt_address = tkinter.Entry(self, textvariable=self.var_address)
|
self.txt_address = tkinter.Entry(self, textvariable=self.var_address)
|
||||||
self.txt_address.bind("<KeyRelease>", self.evt_keypress)
|
self.txt_address.bind("<KeyRelease>", self.evt_keypress)
|
||||||
self.txt_address.grid(
|
self.txt_address.grid(
|
||||||
column=3, row=1, columnspan=3, sticky="n", padx=5, pady=5)
|
column=3, row=1, columnspan=3, sticky="n", padx=5, pady=5
|
||||||
|
)
|
||||||
|
|
||||||
tkinter.Label(self, text="Port").grid(
|
tkinter.Label(self, text=_("Port")).grid(
|
||||||
column=2, row=2, sticky="wn", padx=5, pady=5)
|
column=2, row=2, sticky="wn", padx=5, pady=5
|
||||||
|
)
|
||||||
self.txt_port = tkinter.Entry(self, textvariable=self.var_port)
|
self.txt_port = tkinter.Entry(self, textvariable=self.var_port)
|
||||||
self.txt_port.bind("<KeyRelease>", self.evt_keypress)
|
self.txt_port.bind("<KeyRelease>", self.evt_keypress)
|
||||||
self.txt_port.grid(
|
self.txt_port.grid(
|
||||||
column=3, row=2, columnspan=3, sticky="n", padx=5, pady=5)
|
column=3, row=2, columnspan=3, sticky="n", padx=5, pady=5
|
||||||
|
)
|
||||||
|
|
||||||
# Listenbutton
|
# Listenbutton
|
||||||
self.btn_new = tkinter.Button(
|
self.btn_new = tkinter.Button(
|
||||||
self, text="Neu", command=self.evt_btnnew)
|
self, text=_("New"), command=self.evt_btnnew
|
||||||
|
)
|
||||||
self.btn_new.grid(column=2, row=3, sticky="s")
|
self.btn_new.grid(column=2, row=3, sticky="s")
|
||||||
self.btn_add = tkinter.Button(
|
self.btn_add = tkinter.Button(
|
||||||
self, text="Übernehmen", command=self.evt_btnadd,
|
self, text=_("Apply"),
|
||||||
state="disabled")
|
command=self.evt_btnadd, state="disabled"
|
||||||
|
)
|
||||||
self.btn_add.grid(column=3, row=3, sticky="s")
|
self.btn_add.grid(column=3, row=3, sticky="s")
|
||||||
self.btn_remove = tkinter.Button(
|
self.btn_remove = tkinter.Button(
|
||||||
self, text="Entfernen", command=self.evt_btnremove,
|
self, text=_("Remove"),
|
||||||
state="disabled")
|
command=self.evt_btnremove, state="disabled"
|
||||||
|
)
|
||||||
self.btn_remove.grid(column=4, row=3, sticky="s")
|
self.btn_remove.grid(column=4, row=3, sticky="s")
|
||||||
|
|
||||||
# Fensterbuttons
|
# Fensterbuttons
|
||||||
self.btn_save = tkinter.Button(
|
self.btn_save = tkinter.Button(
|
||||||
self, text="Speichern", command=self.evt_btnsave)
|
self, text=_("Save"), command=self.evt_btnsave
|
||||||
|
)
|
||||||
self.btn_save.grid(column=3, row=9, sticky="se")
|
self.btn_save.grid(column=3, row=9, sticky="se")
|
||||||
self.btn_close = tkinter.Button(
|
self.btn_close = tkinter.Button(
|
||||||
self, text="Schließen", command=self.evt_btnclose)
|
self, text=_("Close"), command=self._checkclose
|
||||||
|
)
|
||||||
self.btn_close.grid(column=4, row=9, sticky="se")
|
self.btn_close.grid(column=4, row=9, sticky="se")
|
||||||
|
|
||||||
def _loadappdata(self):
|
|
||||||
if os.path.exists(savefile):
|
|
||||||
fh = open(savefile, "rb")
|
|
||||||
self._connections = pickle.load(fh)
|
|
||||||
self.build_listconn()
|
|
||||||
|
|
||||||
def _saveappdata(self):
|
def _saveappdata(self):
|
||||||
|
u"""Speichert Verbindungen im home Dir.
|
||||||
|
@return True, bei erfolgreicher Verarbeitung"""
|
||||||
try:
|
try:
|
||||||
makedirs(os.path.dirname(savefile), exist_ok=True)
|
makedirs(os.path.dirname(savefile), exist_ok=True)
|
||||||
fh = open(savefile, "wb")
|
with open(savefile, "wb") as fh:
|
||||||
pickle.dump(self._connections, fh)
|
pickle.dump(self._connections, fh)
|
||||||
self.changes = False
|
self.changes = False
|
||||||
except:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Andere Einstellungen aufräumen
|
||||||
|
dict_o = developloaddefaults()
|
||||||
|
for revpi in tuple(dict_o.keys()):
|
||||||
|
if revpi not in self._connections:
|
||||||
|
del dict_o[revpi]
|
||||||
|
developsavedefaults(None, dict_o)
|
||||||
|
dict_o = programloaddefaults()
|
||||||
|
for revpi in tuple(dict_o.keys()):
|
||||||
|
if revpi not in self._connections:
|
||||||
|
del dict_o[revpi]
|
||||||
|
programsavedefaults(None, dict_o)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def build_listconn(self):
|
def build_listconn(self):
|
||||||
|
u"""Füllt Verbindungsliste."""
|
||||||
self.list_conn.delete(0, "end")
|
self.list_conn.delete(0, "end")
|
||||||
lst_conns = sorted(self._connections.keys(), key=lambda x: x.lower())
|
lst_conns = sorted(self._connections.keys(), key=lambda x: x.lower())
|
||||||
self.list_conn.insert("end", *lst_conns)
|
self.list_conn.insert("end", *lst_conns)
|
||||||
|
|
||||||
def evt_btnadd(self):
|
def evt_btnadd(self):
|
||||||
|
u"""Verbindungseinstellungen übernehmen."""
|
||||||
# TODO: Daten prüfen
|
# TODO: Daten prüfen
|
||||||
self._connections[self.var_name.get()] = \
|
self._connections[self.var_name.get()] = \
|
||||||
(self.var_address.get(), self.var_port.get())
|
(self.var_address.get(), self.var_port.get())
|
||||||
@@ -139,20 +184,8 @@ class RevPiPlcList(tkinter.Frame):
|
|||||||
self.evt_btnnew()
|
self.evt_btnnew()
|
||||||
self.changes = True
|
self.changes = True
|
||||||
|
|
||||||
def evt_btnclose(self):
|
|
||||||
if self.changes:
|
|
||||||
ask = tkmsg.askyesno(
|
|
||||||
parent=self.master, title="Frage...",
|
|
||||||
message="Wollen Sie wirklich beenden?\n"
|
|
||||||
"Nicht gespeicherte Änderungen gehen verloren",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
ask = True
|
|
||||||
|
|
||||||
if ask:
|
|
||||||
self.master.destroy()
|
|
||||||
|
|
||||||
def evt_btnnew(self):
|
def evt_btnnew(self):
|
||||||
|
u"""Neue Verbindung erstellen."""
|
||||||
self.list_conn.select_clear(0, "end")
|
self.list_conn.select_clear(0, "end")
|
||||||
self.evt_listconn()
|
self.evt_listconn()
|
||||||
|
|
||||||
@@ -162,13 +195,14 @@ class RevPiPlcList(tkinter.Frame):
|
|||||||
self.var_port.set("55123")
|
self.var_port.set("55123")
|
||||||
|
|
||||||
def evt_btnremove(self):
|
def evt_btnremove(self):
|
||||||
|
u"""Verbindung löschen."""
|
||||||
item_index = self.list_conn.curselection()
|
item_index = self.list_conn.curselection()
|
||||||
if len(item_index) == 1:
|
if len(item_index) == 1:
|
||||||
item = self.list_conn.get(item_index[0])
|
item = self.list_conn.get(item_index[0])
|
||||||
ask = tkmsg.askyesno(
|
ask = tkmsg.askyesno(
|
||||||
"Frage",
|
_("Question"),
|
||||||
"Wollen Sie die Ausgewählte Verbindung '{}' wirklich "
|
_("Do you really want to delete the selected connection '{0}'?"
|
||||||
"löschen?".format(item),
|
"").format(item),
|
||||||
parent=self.master
|
parent=self.master
|
||||||
)
|
)
|
||||||
if ask:
|
if ask:
|
||||||
@@ -179,21 +213,24 @@ class RevPiPlcList(tkinter.Frame):
|
|||||||
self.changes = True
|
self.changes = True
|
||||||
|
|
||||||
def evt_btnsave(self):
|
def evt_btnsave(self):
|
||||||
|
u"""Alle Verbindungen speichern."""
|
||||||
if self._saveappdata():
|
if self._saveappdata():
|
||||||
ask = tkmsg.askyesno(
|
ask = tkmsg.askyesno(
|
||||||
"Information", "Verbindungen erfolgreich gespeichert.\n"
|
_("Information"),
|
||||||
"Möchten Sie dieses Fenster jetzt schließen?",
|
_("Successfully saved. \nDo you want to close this window?"),
|
||||||
parent=self.master
|
parent=self.master
|
||||||
)
|
)
|
||||||
if ask:
|
if ask:
|
||||||
self.master.destroy()
|
self.master.destroy()
|
||||||
else:
|
else:
|
||||||
tkmsg.showerror(
|
tkmsg.showerror(
|
||||||
"Fehler", "Verbindungen konnten nicht gespeichert werden",
|
_("Error"),
|
||||||
|
_("Failed to save connections"),
|
||||||
parent=self.master
|
parent=self.master
|
||||||
)
|
)
|
||||||
|
|
||||||
def evt_listconn(self, evt=None):
|
def evt_listconn(self, evt=None):
|
||||||
|
u"""Übernimmt Einstellungen in Eingabefelder."""
|
||||||
|
|
||||||
item_index = self.list_conn.curselection()
|
item_index = self.list_conn.curselection()
|
||||||
if len(item_index) == 1:
|
if len(item_index) == 1:
|
||||||
@@ -205,16 +242,16 @@ class RevPiPlcList(tkinter.Frame):
|
|||||||
self.var_port.set(self._connections[item][1])
|
self.var_port.set(self._connections[item][1])
|
||||||
|
|
||||||
self.btn_add["state"] == "normal"
|
self.btn_add["state"] == "normal"
|
||||||
|
|
||||||
self.btn_remove["state"] = "normal"
|
self.btn_remove["state"] = "normal"
|
||||||
else:
|
else:
|
||||||
self.btn_remove["state"] = "disabled"
|
self.btn_remove["state"] = "disabled"
|
||||||
|
|
||||||
def evt_keypress(self, evt=None):
|
def evt_keypress(self, evt=None):
|
||||||
|
u"""Passt bei Tastendruck den Status der Buttons an."""
|
||||||
okvalue = "normal" if (
|
okvalue = "normal" if (
|
||||||
self.var_address.get() != ""
|
self.var_address.get() != "" and
|
||||||
and self.var_name.get() != ""
|
self.var_name.get() != "" and
|
||||||
and self.var_port.get() != ""
|
self.var_port.get() != ""
|
||||||
) else "disabled"
|
) else "disabled"
|
||||||
self.btn_add["state"] = okvalue
|
self.btn_add["state"] = okvalue
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#
|
|
||||||
# RevPiPyControl
|
|
||||||
#
|
|
||||||
# Webpage: https://revpimodio.org/revpipyplc/
|
|
||||||
# (c) Sven Sager, License: LGPLv3
|
|
||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
u"""PLC Programm und Konfig hoch und runterladen."""
|
||||||
|
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
import gzip
|
import gzip
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
@@ -13,40 +13,75 @@ import tkinter
|
|||||||
import tkinter.filedialog as tkfd
|
import tkinter.filedialog as tkfd
|
||||||
import tkinter.messagebox as tkmsg
|
import tkinter.messagebox as tkmsg
|
||||||
import zipfile
|
import zipfile
|
||||||
from os import environ
|
from mytools import gettrans
|
||||||
from os import makedirs
|
from mytools import homedir
|
||||||
|
from mytools import savefile_programpath as savefile
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from sys import platform
|
from tempfile import mkstemp, mkdtemp
|
||||||
from tempfile import mktemp, mkdtemp
|
|
||||||
from xmlrpc.client import Binary
|
from xmlrpc.client import Binary
|
||||||
|
|
||||||
|
# Übersetzung laden
|
||||||
|
_ = gettrans()
|
||||||
|
|
||||||
# Systemwerte
|
|
||||||
if platform == "linux":
|
def _loaddefaults(revpiname=None):
|
||||||
homedir = environ["HOME"]
|
u"""Übernimmt für den Pi die letzen Pfade.
|
||||||
else:
|
@param revpiname Einstellungen nur für RevPi laden
|
||||||
homedir = environ["APPDATA"]
|
@return <class 'dict'> mit Einstellungen"""
|
||||||
savefile = os.path.join(homedir, ".revpipyplc", "programpath.dat")
|
if os.path.exists(savefile):
|
||||||
|
with open(savefile, "rb") as fh:
|
||||||
|
dict_all = pickle.load(fh)
|
||||||
|
if revpiname is None:
|
||||||
|
return dict_all
|
||||||
|
else:
|
||||||
|
return dict_all.get(revpiname, {})
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _savedefaults(revpiname, settings):
|
||||||
|
u"""Schreibt fuer den Pi die letzen Pfade.
|
||||||
|
|
||||||
|
@param revpiname Einstellungen sind für diesen RevPi
|
||||||
|
@param settings <class 'dict'> mit Einstellungen
|
||||||
|
@return True, bei erfolgreicher Verarbeitung
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(savefile), exist_ok=True)
|
||||||
|
if revpiname is None:
|
||||||
|
dict_all = settings
|
||||||
|
else:
|
||||||
|
dict_all = _loaddefaults()
|
||||||
|
dict_all[revpiname] = settings
|
||||||
|
with open(savefile, "wb") as fh:
|
||||||
|
pickle.dump(dict_all, fh)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class RevPiProgram(tkinter.Frame):
|
class RevPiProgram(tkinter.Frame):
|
||||||
|
|
||||||
|
u"""Zeigt Programmfenster an."""
|
||||||
|
|
||||||
def __init__(self, master, xmlcli, xmlmode, revpi):
|
def __init__(self, master, xmlcli, xmlmode, revpi):
|
||||||
|
u"""Init RevPiProgram-Class.
|
||||||
|
@return None"""
|
||||||
if xmlmode < 2:
|
if xmlmode < 2:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
super().__init__(master)
|
super().__init__(master)
|
||||||
# master.protocol("WM_DELETE_WINDOW", self._checkclose)
|
self.master.protocol("WM_DELETE_WINDOW", self._checkclose)
|
||||||
|
self.master.bind("<KeyPress-Escape>", self._checkclose)
|
||||||
self.pack(expand=True, fill="both")
|
self.pack(expand=True, fill="both")
|
||||||
|
|
||||||
self.uploaded = False
|
self.uploaded = False
|
||||||
self.revpi = revpi
|
self.revpi = revpi
|
||||||
self.xmlcli = xmlcli
|
self.xmlcli = xmlcli
|
||||||
self.xmlmode = xmlmode
|
self.xmlstate = "normal" if xmlmode >= 3 else "disabled"
|
||||||
self.xmlstate = "normal" if xmlmode == 3 else "disabled"
|
|
||||||
|
|
||||||
# Letzte Einstellungen übernehmen
|
# Letzte Einstellungen übernehmen
|
||||||
self.opt = self._loaddefault()
|
self.opt = _loaddefaults(revpi)
|
||||||
|
|
||||||
# Fenster bauen
|
# Fenster bauen
|
||||||
self._createwidgets()
|
self._createwidgets()
|
||||||
@@ -54,15 +89,22 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
self._evt_optdown()
|
self._evt_optdown()
|
||||||
self._evt_optup()
|
self._evt_optup()
|
||||||
|
|
||||||
# def _checkclose(self):
|
def _checkclose(self, event=None):
|
||||||
# if self.uploaded:
|
u"""Prüft ob Fenster beendet werden soll.
|
||||||
# tkmsg.showinfo("Ein PLC Programm wurde hochgeladen. "
|
@param event tkinter-Event"""
|
||||||
# "Bitte die PLC options prüfen ob dort das neue Programm"
|
if self.uploaded:
|
||||||
# "eingestellt werden muss.")
|
tkmsg.showinfo(
|
||||||
# self.master.destroy()
|
_("Information"),
|
||||||
|
_("A PLC program has been uploaded. Please check the "
|
||||||
|
"PLC options to see if the correct program is specified "
|
||||||
|
"as the start program."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
self.master.destroy()
|
||||||
|
|
||||||
def _createwidgets(self):
|
def _createwidgets(self):
|
||||||
self.master.wm_title("RevPi Python PLC Programm")
|
u"""Erstellt alle Widgets."""
|
||||||
|
self.master.wm_title(_("RevPi Python PLC program"))
|
||||||
self.master.wm_resizable(width=False, height=False)
|
self.master.wm_resizable(width=False, height=False)
|
||||||
|
|
||||||
self.rowconfigure(0, weight=1)
|
self.rowconfigure(0, weight=1)
|
||||||
@@ -76,7 +118,7 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
# Gruppe Programm
|
# Gruppe Programm
|
||||||
prog = tkinter.LabelFrame(self)
|
prog = tkinter.LabelFrame(self)
|
||||||
prog.columnconfigure(0, weight=1)
|
prog.columnconfigure(0, weight=1)
|
||||||
prog["text"] = "PLC Python programm"
|
prog["text"] = _("PLC python program")
|
||||||
prog.grid(columnspan=2, pady=2, sticky="we")
|
prog.grid(columnspan=2, pady=2, sticky="we")
|
||||||
|
|
||||||
# Variablen vorbereiten
|
# Variablen vorbereiten
|
||||||
@@ -86,40 +128,54 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
self.var_typedown = tkinter.StringVar(prog)
|
self.var_typedown = tkinter.StringVar(prog)
|
||||||
self.var_typeup = tkinter.StringVar(prog)
|
self.var_typeup = tkinter.StringVar(prog)
|
||||||
|
|
||||||
self.lst_typedown = ["Dateien", "Zip Archiv", "TGZ Archiv"]
|
self.lst_typedown = [_("Files"), _("Zip archive"), _("TGZ archive")]
|
||||||
self.lst_typeup = ["Dateien", "Ordner", "Zip Archiv", "TGZ Archiv"]
|
self.lst_typeup = [
|
||||||
|
_("Files"), _("Folder"), _("Zip archive"), _("TGZ archive")
|
||||||
|
]
|
||||||
self.var_picdown.set(self.opt.get("picdown", False))
|
self.var_picdown.set(self.opt.get("picdown", False))
|
||||||
self.var_picup.set(self.opt.get("picup", False))
|
self.var_picup.set(self.opt.get("picup", False))
|
||||||
self.var_typedown.set(self.opt.get("typedown", self.lst_typedown[0]))
|
|
||||||
self.var_typeup.set(self.opt.get("typeup", self.lst_typeup[0]))
|
# Gespeicherte Werte übernehmen
|
||||||
|
saved_val = self.opt.get("typedown", self.lst_typedown[0])
|
||||||
|
self.var_typedown.set(
|
||||||
|
saved_val if saved_val in self.lst_typedown else _("Files")
|
||||||
|
)
|
||||||
|
saved_val = self.opt.get("typeup", self.lst_typeup[0])
|
||||||
|
self.var_typeup.set(
|
||||||
|
saved_val if saved_val in self.lst_typeup else _("Files")
|
||||||
|
)
|
||||||
|
|
||||||
r = 0
|
r = 0
|
||||||
lbl = tkinter.Label(prog)
|
lbl = tkinter.Label(prog)
|
||||||
lbl["text"] = "PLC Programm herunterladen als:"
|
lbl["text"] = _("Download PLC program as:")
|
||||||
lbl.grid(column=0, row=r, **cpadw)
|
lbl.grid(column=0, row=r, **cpadw)
|
||||||
opt = tkinter.OptionMenu(
|
opt = tkinter.OptionMenu(
|
||||||
prog, self.var_typedown, *self.lst_typedown,
|
prog, self.var_typedown, command=self._evt_optdown,
|
||||||
command=self._evt_optdown)
|
*self.lst_typedown
|
||||||
|
)
|
||||||
opt["width"] = 10
|
opt["width"] = 10
|
||||||
opt.grid(column=1, row=r, **cpad)
|
opt.grid(column=1, row=r, **cpad)
|
||||||
|
|
||||||
r = 1
|
r = 1
|
||||||
self.ckb_picdown = tkinter.Checkbutton(prog)
|
self.ckb_picdown = tkinter.Checkbutton(prog)
|
||||||
self.ckb_picdown["text"] = "inkl. piCtory Konfiguration"
|
self.ckb_picdown["text"] = _("include piCtory configuration")
|
||||||
self.ckb_picdown["variable"] = self.var_picdown
|
self.ckb_picdown["variable"] = self.var_picdown
|
||||||
self.ckb_picdown.grid(column=0, row=r, **cpadw)
|
self.ckb_picdown.grid(column=0, row=r, **cpadw)
|
||||||
|
|
||||||
btn = tkinter.Button(prog)
|
btn = tkinter.Button(prog)
|
||||||
btn["command"] = self.plcdownload
|
btn["command"] = self.plcdownload
|
||||||
btn["text"] = "Download"
|
btn["text"] = _("Download")
|
||||||
btn.grid(column=1, row=r, **cpad)
|
btn.grid(column=1, row=r, **cpad)
|
||||||
|
|
||||||
r = 2
|
r = 2
|
||||||
lbl = tkinter.Label(prog)
|
lbl = tkinter.Label(prog)
|
||||||
lbl["text"] = "PLC Programm hochladen als:"
|
lbl["text"] = _("Upload PLC program as:")
|
||||||
lbl.grid(column=0, row=r, **cpadw)
|
lbl.grid(column=0, row=r, **cpadw)
|
||||||
|
|
||||||
opt = tkinter.OptionMenu(
|
opt = tkinter.OptionMenu(
|
||||||
prog, self.var_typeup, *self.lst_typeup,
|
prog, self.var_typeup, command=self._evt_optup,
|
||||||
command=self._evt_optup)
|
*self.lst_typeup
|
||||||
|
)
|
||||||
opt["state"] = self.xmlstate
|
opt["state"] = self.xmlstate
|
||||||
opt["width"] = 10
|
opt["width"] = 10
|
||||||
opt.grid(column=1, row=r, **cpad)
|
opt.grid(column=1, row=r, **cpad)
|
||||||
@@ -127,77 +183,86 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
r = 3
|
r = 3
|
||||||
ckb = tkinter.Checkbutton(prog)
|
ckb = tkinter.Checkbutton(prog)
|
||||||
ckb["state"] = self.xmlstate
|
ckb["state"] = self.xmlstate
|
||||||
ckb["text"] = "vorher alles im Uploadverzeichnis löschen"
|
ckb["text"] = _("clean upload folder before upload")
|
||||||
ckb["variable"] = self.var_cleanup
|
ckb["variable"] = self.var_cleanup
|
||||||
ckb.grid(column=0, row=r, columnspan=2, **cpadw)
|
ckb.grid(column=0, row=r, columnspan=2, **cpadw)
|
||||||
|
|
||||||
r = 4
|
r = 4
|
||||||
self.ckb_picup = tkinter.Checkbutton(prog)
|
self.ckb_picup = tkinter.Checkbutton(prog)
|
||||||
self.ckb_picup["state"] = self.xmlstate
|
self.ckb_picup["state"] = self.xmlstate
|
||||||
self.ckb_picup["text"] = "enthält piCtory Konfiguration"
|
self.ckb_picup["text"] = _("includes piCtory configuration")
|
||||||
self.ckb_picup["variable"] = self.var_picup
|
self.ckb_picup["variable"] = self.var_picup
|
||||||
self.ckb_picup.grid(column=0, row=r, **cpadw)
|
self.ckb_picup.grid(column=0, row=r, **cpadw)
|
||||||
|
|
||||||
btn = tkinter.Button(prog)
|
btn = tkinter.Button(prog)
|
||||||
btn["command"] = self.plcupload
|
btn["command"] = self.plcupload
|
||||||
btn["state"] = self.xmlstate
|
btn["state"] = self.xmlstate
|
||||||
btn["text"] = "Upload"
|
btn["text"] = _("Upload")
|
||||||
btn.grid(column=1, row=r, **cpad)
|
btn.grid(column=1, row=r, **cpad)
|
||||||
|
|
||||||
# Gruppe piCtory
|
# Gruppe piCtory
|
||||||
picto = tkinter.LabelFrame(self)
|
picto = tkinter.LabelFrame(self)
|
||||||
picto.columnconfigure(0, weight=1)
|
picto.columnconfigure(0, weight=1)
|
||||||
picto["text"] = "piCtory Konfiguration"
|
picto["text"] = _("piCtory configuration")
|
||||||
picto.grid(columnspan=2, pady=2, sticky="we")
|
picto.grid(columnspan=2, pady=2, sticky="we")
|
||||||
|
|
||||||
lbl = tkinter.Label(picto)
|
lbl = tkinter.Label(picto)
|
||||||
lbl["text"] = "piCtory Konfiguration herunterladen"
|
lbl["text"] = _("Download piCtory configuration")
|
||||||
lbl.grid(column=0, row=0, **cpadw)
|
lbl.grid(column=0, row=0, **cpadw)
|
||||||
|
|
||||||
btn = tkinter.Button(picto)
|
btn = tkinter.Button(picto)
|
||||||
btn["command"] = self.getpictoryrsc
|
btn["command"] = self.getpictoryrsc
|
||||||
btn["text"] = "Download"
|
btn["text"] = _("Download")
|
||||||
btn.grid(column=1, row=0, **cpad)
|
btn.grid(column=1, row=0, **cpad)
|
||||||
|
|
||||||
lbl = tkinter.Label(picto)
|
lbl = tkinter.Label(picto)
|
||||||
lbl["text"] = "piCtory Konfiguration hochladen"
|
lbl["text"] = _("Upload piCtory configuration")
|
||||||
lbl.grid(column=0, row=1, **cpadw)
|
lbl.grid(column=0, row=1, **cpadw)
|
||||||
|
|
||||||
btn = tkinter.Button(picto)
|
btn = tkinter.Button(picto)
|
||||||
btn["command"] = self.setpictoryrsc
|
btn["command"] = self.setpictoryrsc
|
||||||
btn["state"] = self.xmlstate
|
btn["state"] = self.xmlstate
|
||||||
btn["text"] = "Upload"
|
btn["text"] = _("Upload")
|
||||||
btn.grid(column=1, row=1, **cpad)
|
btn.grid(column=1, row=1, **cpad)
|
||||||
|
|
||||||
# Gruppe ProcImg
|
# Gruppe ProcImg
|
||||||
proc = tkinter.LabelFrame(self)
|
proc = tkinter.LabelFrame(self)
|
||||||
proc.columnconfigure(0, weight=1)
|
proc.columnconfigure(0, weight=1)
|
||||||
proc["text"] = "piControl0 Prozessabbild"
|
proc["text"] = _("piControl0 process image")
|
||||||
proc.grid(columnspan=2, pady=2, sticky="we")
|
proc.grid(columnspan=2, pady=2, sticky="we")
|
||||||
|
|
||||||
lbl = tkinter.Label(proc)
|
lbl = tkinter.Label(proc)
|
||||||
lbl["text"] = "Prozessabbild-Dump herunterladen"
|
lbl["text"] = _("Download process image dump")
|
||||||
lbl.grid(column=0, row=0, **cpadw)
|
lbl.grid(column=0, row=0, **cpadw)
|
||||||
|
|
||||||
btn = tkinter.Button(proc)
|
btn = tkinter.Button(proc)
|
||||||
btn["command"] = self.getprocimg
|
btn["command"] = self.getprocimg
|
||||||
btn["text"] = "Download"
|
btn["text"] = _("Download")
|
||||||
btn.grid(column=1, row=0, **cpad)
|
btn.grid(column=1, row=0, **cpad)
|
||||||
|
|
||||||
# Gruppe piControlReset
|
# Gruppe piControlReset
|
||||||
picon = tkinter.LabelFrame(self)
|
picon = tkinter.LabelFrame(self)
|
||||||
picon.columnconfigure(0, weight=1)
|
picon.columnconfigure(0, weight=1)
|
||||||
picon["text"] = "piControl Reset"
|
picon["text"] = _("Reset piControl")
|
||||||
picon.grid(columnspan=2, pady=2, sticky="we")
|
picon.grid(columnspan=2, pady=2, sticky="we")
|
||||||
|
|
||||||
lbl = tkinter.Label(picon)
|
lbl = tkinter.Label(picon)
|
||||||
lbl["text"] = "piControlReset ausführen"
|
lbl["text"] = _("Execute piControlReset")
|
||||||
lbl.grid(column=0, row=0, **cpadw)
|
lbl.grid(column=0, row=0, **cpadw)
|
||||||
|
|
||||||
btn = tkinter.Button(picon)
|
btn = tkinter.Button(picon)
|
||||||
btn["command"] = self.picontrolreset
|
btn["command"] = self.picontrolreset
|
||||||
btn["text"] = "ausführen"
|
btn["text"] = _("execute")
|
||||||
btn.grid(column=1, row=0, **cpad)
|
btn.grid(column=1, row=0, **cpad)
|
||||||
|
|
||||||
# Beendenbutton
|
# Beendenbutton
|
||||||
btn = tkinter.Button(self)
|
btn = tkinter.Button(self)
|
||||||
btn["command"] = self.master.destroy
|
btn["command"] = self._checkclose
|
||||||
btn["text"] = "Beenden"
|
btn["text"] = _("Exit")
|
||||||
btn.grid()
|
btn.grid()
|
||||||
|
|
||||||
def _evt_optdown(self, text=""):
|
def _evt_optdown(self, text=""):
|
||||||
|
u"""Passt je nach gewählter Option den Status der Widgets an."""
|
||||||
if self.lst_typedown.index(self.var_typedown.get()) == 0:
|
if self.lst_typedown.index(self.var_typedown.get()) == 0:
|
||||||
self.var_picdown.set(False)
|
self.var_picdown.set(False)
|
||||||
self.ckb_picdown["state"] = "disable"
|
self.ckb_picdown["state"] = "disable"
|
||||||
@@ -205,40 +270,17 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
self.ckb_picdown["state"] = "normal"
|
self.ckb_picdown["state"] = "normal"
|
||||||
|
|
||||||
def _evt_optup(self, text=""):
|
def _evt_optup(self, text=""):
|
||||||
|
u"""Passt je nach gewählter Option den Status der Widgets an."""
|
||||||
if self.lst_typeup.index(self.var_typeup.get()) <= 1:
|
if self.lst_typeup.index(self.var_typeup.get()) <= 1:
|
||||||
self.var_picup.set(False)
|
self.var_picup.set(False)
|
||||||
self.ckb_picup["state"] = "disable"
|
self.ckb_picup["state"] = "disable"
|
||||||
else:
|
else:
|
||||||
self.ckb_picup["state"] = "normal"
|
self.ckb_picup["state"] = "normal"
|
||||||
|
|
||||||
def _loaddefault(self, full=False):
|
|
||||||
"""Uebernimmt fuer den Pi die letzen Pfade."""
|
|
||||||
if os.path.exists(savefile):
|
|
||||||
fh = open(savefile, "rb")
|
|
||||||
dict_all = pickle.load(fh)
|
|
||||||
if full:
|
|
||||||
return dict_all
|
|
||||||
else:
|
|
||||||
return dict_all.get(self.revpi, {})
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def _savedefaults(self):
|
|
||||||
"""Schreibt fuer den Pi die letzen Pfade."""
|
|
||||||
try:
|
|
||||||
makedirs(os.path.dirname(savefile), exist_ok=True)
|
|
||||||
dict_all = self._loaddefault(full=True)
|
|
||||||
dict_all[self.revpi] = self.opt
|
|
||||||
fh = open(savefile, "wb")
|
|
||||||
pickle.dump(dict_all, fh)
|
|
||||||
self.changes = False
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def create_filelist(self, rootdir):
|
def create_filelist(self, rootdir):
|
||||||
"""Erstellt eine Dateiliste von einem Verzeichnis.
|
u"""Erstellt eine Dateiliste von einem Verzeichnis.
|
||||||
@param rootdir: Verzeichnis fuer das eine Liste erstellt werden soll
|
@param rootdir Verzeichnis fuer das eine Liste erstellt werden soll
|
||||||
@returns: Dateiliste"""
|
@return Dateiliste"""
|
||||||
filelist = []
|
filelist = []
|
||||||
for tup_dir in os.walk(rootdir):
|
for tup_dir in os.walk(rootdir):
|
||||||
for fname in tup_dir[2]:
|
for fname in tup_dir[2]:
|
||||||
@@ -249,9 +291,9 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
"""Gibt das rootdir von einem entpackten Verzeichnis zurueck.
|
"""Gibt das rootdir von einem entpackten Verzeichnis zurueck.
|
||||||
|
|
||||||
Dabei wird geprueft, ob es sich um einen einzelnen Ordner handelt
|
Dabei wird geprueft, ob es sich um einen einzelnen Ordner handelt
|
||||||
und ob es eine piCtory Konfiguraiton im rootdir gibt.
|
und ob es eine piCtory Konfiguration im rootdir gibt.
|
||||||
@param rootdir: Verzeichnis fuer Pruefung
|
@param rootdir Verzeichnis fuer Pruefung
|
||||||
@returns: Abgeaendertes rootdir
|
@return Abgeaendertes rootdir
|
||||||
|
|
||||||
"""
|
"""
|
||||||
lst_dir = os.listdir(rootdir)
|
lst_dir = os.listdir(rootdir)
|
||||||
@@ -270,137 +312,180 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
return (rootdir, None)
|
return (rootdir, None)
|
||||||
|
|
||||||
def getpictoryrsc(self):
|
def getpictoryrsc(self):
|
||||||
|
u"""Läd die piCtory Konfiguration herunter."""
|
||||||
fh = tkfd.asksaveasfile(
|
fh = tkfd.asksaveasfile(
|
||||||
mode="wb", parent=self.master,
|
mode="wb", parent=self.master,
|
||||||
confirmoverwrite=True,
|
confirmoverwrite=True,
|
||||||
title="Speichern als...",
|
title=_("Save as..."),
|
||||||
initialdir=self.opt.get("getpictoryrsc_dir", ""),
|
initialdir=self.opt.get("getpictoryrsc_dir", ""),
|
||||||
initialfile=self.revpi + ".rsc",
|
initialfile=self.revpi + ".rsc",
|
||||||
filetypes=(("piCtory Config", "*.rsc"), ("All Files", "*.*"))
|
filetypes=((_("piCtory config"), "*.rsc"), (_("All files"), "*.*"))
|
||||||
)
|
)
|
||||||
if fh is not None:
|
if fh is not None:
|
||||||
try:
|
try:
|
||||||
fh.write(self.xmlcli.get_pictoryrsc().data)
|
fh.write(self.xmlcli.get_pictoryrsc().data)
|
||||||
except:
|
except Exception:
|
||||||
tkmsg.showerror(
|
tkmsg.showerror(
|
||||||
parent=self.master, title="Fehler",
|
_("Error"),
|
||||||
message="Datei konnte nicht geladen und gespeichert "
|
_("Could not load and save file!"),
|
||||||
"werden!"
|
parent=self.master,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
tkmsg.showinfo(
|
tkmsg.showinfo(
|
||||||
parent=self.master, title="Erfolgreich",
|
_("Success"),
|
||||||
message="Datei erfolgreich vom Revolution Pi geladen "
|
_("File successfully loaded and saved."),
|
||||||
"und gespeichert.",
|
parent=self.master
|
||||||
)
|
)
|
||||||
# Einstellungen speichern
|
# Einstellungen speichern
|
||||||
self.opt["getpictoryrsc_dir"] = os.path.dirname(fh.name)
|
self.opt["getpictoryrsc_dir"] = os.path.dirname(fh.name)
|
||||||
self._savedefaults()
|
_savedefaults(self.revpi, self.opt)
|
||||||
finally:
|
finally:
|
||||||
fh.close()
|
fh.close()
|
||||||
|
|
||||||
def getprocimg(self):
|
def getprocimg(self):
|
||||||
|
u"""Läd das aktuelle Prozessabbild herunter."""
|
||||||
fh = tkfd.asksaveasfile(
|
fh = tkfd.asksaveasfile(
|
||||||
mode="wb", parent=self.master,
|
mode="wb", parent=self.master,
|
||||||
confirmoverwrite=True,
|
confirmoverwrite=True,
|
||||||
title="Speichern als...",
|
title=_("Save as..."),
|
||||||
initialdir=self.opt.get("getprocimg_dir", ""),
|
initialdir=self.opt.get("getprocimg_dir", ""),
|
||||||
initialfile=self.revpi + ".img",
|
initialfile=self.revpi + ".img",
|
||||||
filetypes=(("Imagefiles", "*.img"), ("All Files", "*.*"))
|
filetypes=((_("Imagefiles"), "*.img"), (_("All files"), "*.*"))
|
||||||
)
|
)
|
||||||
if fh is not None:
|
if fh is not None:
|
||||||
try:
|
try:
|
||||||
fh.write(self.xmlcli.get_procimg().data)
|
fh.write(self.xmlcli.get_procimg().data)
|
||||||
except:
|
except Exception:
|
||||||
tkmsg.showerror(
|
tkmsg.showerror(
|
||||||
parent=self.master, title="Fehler",
|
_("Error"),
|
||||||
message="Datei konnte nicht geladen und gespeichert"
|
_("Could not load and save file!"),
|
||||||
"werden!"
|
parent=self.master
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
tkmsg.showinfo(
|
tkmsg.showinfo(
|
||||||
parent=self.master, title="Erfolgreich",
|
_("Success"),
|
||||||
message="Datei erfolgreich vom Revolution Pi geladen "
|
_("File successfully loaded and saved."),
|
||||||
"und gespeichert.",
|
parent=self.master
|
||||||
)
|
)
|
||||||
# Einstellungen speichern
|
# Einstellungen speichern
|
||||||
self.opt["getprocimg_dir"] = os.path.dirname(fh.name)
|
self.opt["getprocimg_dir"] = os.path.dirname(fh.name)
|
||||||
self._savedefaults()
|
_savedefaults(self.revpi, self.opt)
|
||||||
finally:
|
finally:
|
||||||
fh.close()
|
fh.close()
|
||||||
|
|
||||||
def setpictoryrsc(self, filename=None):
|
def setpictoryrsc(self, filename=None):
|
||||||
|
u"""Überträgt die angegebene piCtory-Konfiguration."""
|
||||||
if filename is None:
|
if filename is None:
|
||||||
fh = tkfd.askopenfile(
|
fh = tkfd.askopenfile(
|
||||||
mode="rb", parent=self.master,
|
mode="rb", parent=self.master,
|
||||||
title="piCtory Datei öffnen...",
|
title=_("Open piCtory file..."),
|
||||||
initialdir=self.opt.get("setpictoryrsc_dir", ""),
|
initialdir=self.opt.get("setpictoryrsc_dir", ""),
|
||||||
initialfile=self.revpi + ".rsc",
|
initialfile=self.revpi + ".rsc",
|
||||||
filetypes=(("piCtory Config", "*.rsc"), ("All Files", "*.*"))
|
filetypes=(
|
||||||
|
(_("piCtory config"), "*.rsc"), (_("All files"), "*.*")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
fh = open(filename, "rb")
|
fh = open(filename, "rb")
|
||||||
|
|
||||||
if fh is not None:
|
if fh is not None:
|
||||||
ask = tkmsg.askyesno(
|
ask = tkmsg.askyesno(
|
||||||
parent=self.master, title="Frage",
|
_("Question"),
|
||||||
message="Soll nach dem Hochladen der piCtory Konfiguration "
|
_("Should the piControl driver be reset after "
|
||||||
"ein Reset am piControl Treiber durchgeführt werden?"
|
"uploading the piCtory configuration?"),
|
||||||
|
parent=self.master
|
||||||
)
|
)
|
||||||
|
|
||||||
ec = self.xmlcli.set_pictoryrsc(Binary(fh.read()), ask)
|
ec = self.xmlcli.set_pictoryrsc(Binary(fh.read()), ask)
|
||||||
print(ec)
|
|
||||||
if ec == 0:
|
if ec == 0:
|
||||||
if ask:
|
if ask:
|
||||||
tkmsg.showinfo(
|
tkmsg.showinfo(
|
||||||
parent=self.master, title="Erfolgreich",
|
_("Success"),
|
||||||
message="Die Übertragung der piCtory Konfiguration "
|
_("The transfer of the piCtory configuration "
|
||||||
"und der Reset von piControl wurden erfolgreich "
|
"and the reset of piControl have been "
|
||||||
"ausgeführt")
|
"successfully executed."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
tkmsg.showinfo(
|
tkmsg.showinfo(
|
||||||
parent=self.master, title="Erfolgreich",
|
_("Success"),
|
||||||
message="Die Übertragung der piCtory Konfiguration "
|
_("The piCtory configuration was "
|
||||||
"wurde erfolgreich ausgeführt")
|
"successfully transferred."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
#Einstellungen speichern
|
# Einstellungen speichern
|
||||||
self.opt["setpictoryrsc_dir"] = os.path.dirname(fh.name)
|
self.opt["setpictoryrsc_dir"] = os.path.dirname(fh.name)
|
||||||
self._savedefaults()
|
_savedefaults(self.revpi, self.opt)
|
||||||
|
elif ec == -1:
|
||||||
|
tkmsg.showerror(
|
||||||
|
_("Error"),
|
||||||
|
_("Can not process the transferred file."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
elif ec == -2:
|
||||||
|
tkmsg.showerror(
|
||||||
|
_("Error"),
|
||||||
|
_("Can not find main elements in piCtory file."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
elif ec == -4:
|
||||||
|
tkmsg.showerror(
|
||||||
|
_("Error"),
|
||||||
|
_("Contained devices could not be found on Revolution "
|
||||||
|
"Pi. The configuration may be from a newer piCtory "
|
||||||
|
"version!"),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
elif ec == -5:
|
||||||
|
tkmsg.showerror(
|
||||||
|
_("Error"),
|
||||||
|
_("Could not load RAP catalog on Revolution Pi."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
elif ec < 0:
|
elif ec < 0:
|
||||||
tkmsg.showerror(
|
tkmsg.showerror(
|
||||||
parent=self.master, title="Fehler",
|
_("Error"),
|
||||||
message="Die piCtory Konfiguration konnte auf dem "
|
_("The piCtory configuration could not be "
|
||||||
"Revolution Pi nicht geschrieben werden.")
|
"written on the Revolution Pi."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
elif ec > 0:
|
elif ec > 0:
|
||||||
tkmsg.showwarning(
|
tkmsg.showwarning(
|
||||||
parent=self.master, title="Warnung",
|
_("Warning"),
|
||||||
message="Die piCtroy Konfiguration wurde erfolgreich "
|
_("The piCtroy configuration has been saved successfully."
|
||||||
"gespeichert. \nBeim piControl Reset trat allerdings ein "
|
" \nAn error occurred on piControl reset!"),
|
||||||
"Fehler auf!")
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
fh.close()
|
fh.close()
|
||||||
|
|
||||||
def picontrolreset(self):
|
def picontrolreset(self):
|
||||||
|
u"""Fürt ein Reset der piBridge durch."""
|
||||||
ask = tkmsg.askyesno(
|
ask = tkmsg.askyesno(
|
||||||
parent=self.master, title="Frage...",
|
_("Question"),
|
||||||
message="Soll piControlReset wirklich durchgeführt werden? \n"
|
_("Are you sure to reset piControl? \nThe process image "
|
||||||
"Das Prozessabbild und die Steuerung werden dann unterbrochen!!!"
|
"and the piBridge are interrupted !!!"),
|
||||||
|
parent=self.master
|
||||||
)
|
)
|
||||||
if ask:
|
if ask:
|
||||||
ec = self.xmlcli.resetpicontrol()
|
ec = self.xmlcli.resetpicontrol()
|
||||||
if ec == 0:
|
if ec == 0:
|
||||||
tkmsg.showinfo(
|
tkmsg.showinfo(
|
||||||
parent=self.master, title="Erfolgreich",
|
_("Success"),
|
||||||
message="piControlReset erfolgreich durchgeführt"
|
_("piControl reset executed successfully"),
|
||||||
|
parent=self.master
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
tkmsg.showerror(
|
tkmsg.showerror(
|
||||||
parten=self.master, title="Fehler",
|
_("Error"),
|
||||||
message="piControlReset konnte nicht erfolgreich "
|
_("piControl reset could not be executed successfully"),
|
||||||
"durchgeführt werden"
|
parten=self.master
|
||||||
)
|
)
|
||||||
|
|
||||||
def plcdownload(self):
|
def plcdownload(self):
|
||||||
|
u"""Läd das aktuelle Projekt herunter."""
|
||||||
tdown = self.lst_typedown.index(self.var_typedown.get())
|
tdown = self.lst_typedown.index(self.var_typedown.get())
|
||||||
fh = None
|
fh = None
|
||||||
dirselect = ""
|
dirselect = ""
|
||||||
@@ -409,23 +494,25 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
# Ordner
|
# Ordner
|
||||||
dirselect = tkfd.askdirectory(
|
dirselect = tkfd.askdirectory(
|
||||||
parent=self.master,
|
parent=self.master,
|
||||||
title="Verzeichnis zum Ablegen",
|
title=_("Directory to save"),
|
||||||
mustexist=False,
|
mustexist=False,
|
||||||
initialdir=self.opt.get("plcdownload_dir", self.revpi)
|
initialdir=self.opt.get("plcdownload_dir", self.revpi)
|
||||||
)
|
)
|
||||||
|
|
||||||
if type(dirselect) == str and dirselect != "":
|
if type(dirselect) == str and dirselect != "":
|
||||||
fh = open(mktemp(), "wb")
|
fh = open(mkstemp()[1], "wb")
|
||||||
|
|
||||||
elif tdown == 1:
|
elif tdown == 1:
|
||||||
# Zip
|
# Zip
|
||||||
fh = tkfd.asksaveasfile(
|
fh = tkfd.asksaveasfile(
|
||||||
mode="wb", parent=self.master,
|
mode="wb", parent=self.master,
|
||||||
confirmoverwrite=True,
|
confirmoverwrite=True,
|
||||||
title="Speichern als...",
|
title=_("Save as..."),
|
||||||
initialdir=self.opt.get("plcdownload_file", ""),
|
initialdir=self.opt.get("plcdownload_file", ""),
|
||||||
initialfile=self.revpi + ".zip",
|
initialfile=self.revpi + ".zip",
|
||||||
filetypes=(("Zip Archiv", "*.zip"), ("All Files", "*.*"))
|
filetypes=(
|
||||||
|
(_("Zip archive"), "*.zip"), (_("All files"), "*.*")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
elif tdown == 2:
|
elif tdown == 2:
|
||||||
@@ -433,19 +520,23 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
fh = tkfd.asksaveasfile(
|
fh = tkfd.asksaveasfile(
|
||||||
mode="wb", parent=self.master,
|
mode="wb", parent=self.master,
|
||||||
confirmoverwrite=True,
|
confirmoverwrite=True,
|
||||||
title="Speichern als...",
|
title=_("Save as..."),
|
||||||
initialdir=self.opt.get("plcdownload_file", ""),
|
initialdir=self.opt.get("plcdownload_file", ""),
|
||||||
initialfile=self.revpi + ".tar.gz",
|
initialfile=self.revpi + ".tar.gz",
|
||||||
filetypes=(("Tar Archiv", "*.tar.gz"), ("All Files", "*.*"))
|
filetypes=(
|
||||||
|
(_("TGZ archive"), "*.tar.gz"), (_("All files"), "*.*")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if fh is not None:
|
if fh is not None:
|
||||||
if tdown == 1:
|
if tdown == 1:
|
||||||
plcfile = self.xmlcli.plcdownload(
|
plcfile = self.xmlcli.plcdownload(
|
||||||
"zip", self.var_picdown.get())
|
"zip", self.var_picdown.get()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
plcfile = self.xmlcli.plcdownload(
|
plcfile = self.xmlcli.plcdownload(
|
||||||
"tar", self.var_picdown.get())
|
"tar", self.var_picdown.get()
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fh.write(plcfile.data)
|
fh.write(plcfile.data)
|
||||||
@@ -456,9 +547,7 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
fh_pack = tarfile.open(fh.name)
|
fh_pack = tarfile.open(fh.name)
|
||||||
|
|
||||||
# Unterverzeichnis streichen
|
# Unterverzeichnis streichen
|
||||||
rootname = ""
|
|
||||||
for taritem in fh_pack.getmembers():
|
for taritem in fh_pack.getmembers():
|
||||||
print(rootname)
|
|
||||||
if not taritem.name == "revpipyload":
|
if not taritem.name == "revpipyload":
|
||||||
taritem.name = \
|
taritem.name = \
|
||||||
taritem.name.replace("revpipyload/", "")
|
taritem.name.replace("revpipyload/", "")
|
||||||
@@ -471,40 +560,43 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
self.opt["typedown"] = self.var_typedown.get()
|
self.opt["typedown"] = self.var_typedown.get()
|
||||||
self.opt["picdown"] = self.var_picdown.get()
|
self.opt["picdown"] = self.var_picdown.get()
|
||||||
|
|
||||||
except:
|
except Exception:
|
||||||
raise
|
raise
|
||||||
tkmsg.showerror(
|
tkmsg.showerror(
|
||||||
parent=self.master, title="Fehler",
|
_("Error"),
|
||||||
message="Datei konnte nicht geladen und gespeichert "
|
_("Could not load and save file!"),
|
||||||
"werden!"
|
parent=self.master
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
tkmsg.showinfo(
|
tkmsg.showinfo(
|
||||||
parent=self.master, title="Erfolgreich",
|
_("Success"),
|
||||||
message="Datei erfolgreich vom Revolution Pi geladen "
|
_("File successfully loaded and saved."),
|
||||||
"und gespeichert.",
|
parent=self.master
|
||||||
)
|
)
|
||||||
|
|
||||||
# Einstellungen speichern
|
# Einstellungen speichern
|
||||||
self._savedefaults()
|
_savedefaults(self.revpi, self.opt)
|
||||||
finally:
|
finally:
|
||||||
fh.close()
|
fh.close()
|
||||||
|
|
||||||
def plcupload(self):
|
def plcupload(self):
|
||||||
|
u"""Lädt das angegebene Projekt auf den RevPi.
|
||||||
|
@return True, bei erfolgreicher Verarbeitung"""
|
||||||
tup = self.lst_typeup.index(self.var_typeup.get())
|
tup = self.lst_typeup.index(self.var_typeup.get())
|
||||||
dirselect = ""
|
dirselect = ""
|
||||||
dirtmp = None
|
dirtmp = None
|
||||||
filelist = []
|
filelist = []
|
||||||
fileselect = None
|
fileselect = None
|
||||||
|
foldername = ""
|
||||||
rscfile = None
|
rscfile = None
|
||||||
|
|
||||||
if tup == 0:
|
if tup == 0:
|
||||||
# Datei
|
# Datei
|
||||||
fileselect = tkfd.askopenfilenames(
|
fileselect = tkfd.askopenfilenames(
|
||||||
parent=self.master,
|
parent=self.master,
|
||||||
title="Python Programm übertragen...",
|
title="Upload Python program...",
|
||||||
initialdir=self.opt.get("plcupload_dir", ""),
|
initialdir=self.opt.get("plcupload_dir", homedir),
|
||||||
filetypes=(("Python", "*.py"), ("All Files", "*.*"))
|
filetypes=(("Python", "*.py"), (_("All files"), "*.*"))
|
||||||
)
|
)
|
||||||
if type(fileselect) == tuple and len(fileselect) > 0:
|
if type(fileselect) == tuple and len(fileselect) > 0:
|
||||||
for file in fileselect:
|
for file in fileselect:
|
||||||
@@ -514,10 +606,14 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
# Ordner
|
# Ordner
|
||||||
dirselect = tkfd.askdirectory(
|
dirselect = tkfd.askdirectory(
|
||||||
parent=self.master,
|
parent=self.master,
|
||||||
title="Verzeichnis zum Hochladen",
|
title=_("Folder to upload"),
|
||||||
mustexist=True,
|
mustexist=True,
|
||||||
initialdir=self.opt.get("plcupload_dir", self.revpi)
|
initialdir=self.opt.get("plcupload_dir", homedir)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Ordnernamen merken um diesen auf RevPi anzulegen
|
||||||
|
foldername = os.path.basename(dirselect)
|
||||||
|
|
||||||
if type(dirselect) == str and dirselect != "":
|
if type(dirselect) == str and dirselect != "":
|
||||||
filelist = self.create_filelist(dirselect)
|
filelist = self.create_filelist(dirselect)
|
||||||
|
|
||||||
@@ -525,10 +621,12 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
# Zip
|
# Zip
|
||||||
fileselect = tkfd.askopenfilename(
|
fileselect = tkfd.askopenfilename(
|
||||||
parent=self.master,
|
parent=self.master,
|
||||||
title="Zip-Archive übertragen...",
|
title=_("Upload Zip archive..."),
|
||||||
initialdir=self.opt.get("plcupload_file", ""),
|
initialdir=self.opt.get("plcupload_file", ""),
|
||||||
initialfile=self.revpi + ".zip",
|
initialfile=self.revpi + ".zip",
|
||||||
filetypes=(("Zip Archiv", "*.zip"), ("All Files", "*.*"))
|
filetypes=(
|
||||||
|
(_("Zip archive"), "*.zip"), (_("All files"), "*.*")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if type(fileselect) == str and fileselect != "":
|
if type(fileselect) == str and fileselect != "":
|
||||||
# Zipdatei prüfen
|
# Zipdatei prüfen
|
||||||
@@ -543,18 +641,22 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
tkmsg.showerror(
|
tkmsg.showerror(
|
||||||
parent=self.master, title="Fehler",
|
_("Error"),
|
||||||
message="Die angegebene Datei ist kein ZIP-Archiv.")
|
_("The specified file is not a ZIP archive."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif tup == 3:
|
elif tup == 3:
|
||||||
# TarGz
|
# TarGz
|
||||||
fileselect = tkfd.askopenfilename(
|
fileselect = tkfd.askopenfilename(
|
||||||
parent=self.master,
|
parent=self.master,
|
||||||
title="TarGz-Archiv übertragen...",
|
title=_("Upload TarGz archiv..."),
|
||||||
initialdir=self.opt.get("plcupload_file", ""),
|
initialdir=self.opt.get("plcupload_file", ""),
|
||||||
initialfile=self.revpi + ".tar.gz",
|
initialfile=self.revpi + ".tar.gz",
|
||||||
filetypes=(("Tar Archiv", "*.tar.gz"), ("All Files", "*.*"))
|
filetypes=(
|
||||||
|
(_("TGZ archive"), "*.tar.gz"), (_("All files"), "*.*")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if type(fileselect) == str and fileselect != "":
|
if type(fileselect) == str and fileselect != "":
|
||||||
|
|
||||||
@@ -570,8 +672,10 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
tkmsg.showerror(
|
tkmsg.showerror(
|
||||||
parent=self.master, title="Fehler",
|
_("Error"),
|
||||||
message="Die angegebene Datei ist kein TAR-Archiv.")
|
_("The specified file is not a TAR archive."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Wenn keine Dateien gewählt
|
# Wenn keine Dateien gewählt
|
||||||
@@ -581,12 +685,16 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
# Vor Übertragung aufräumen wenn ausgewählt
|
# Vor Übertragung aufräumen wenn ausgewählt
|
||||||
if self.var_cleanup.get() and not self.xmlcli.plcuploadclean():
|
if self.var_cleanup.get() and not self.xmlcli.plcuploadclean():
|
||||||
tkmsg.showerror(
|
tkmsg.showerror(
|
||||||
parent=self.masger, title="Fehler",
|
_("Error"),
|
||||||
message="Beim Löschen der Dateien auf dem Revolution Pi ist "
|
_("There was an error deleting the files on the "
|
||||||
"ein Fehler aufgetreten.")
|
"Revolution Pi."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Flag setzen, weil ab hier Veränderungen existieren
|
# Aktuell konfiguriertes Programm lesen (für uploaded Flag)
|
||||||
|
opt_program = self.xmlcli.get_config()
|
||||||
|
opt_program = opt_program.get("plcprogram", "none.py")
|
||||||
self.uploaded = True
|
self.uploaded = True
|
||||||
ec = 0
|
ec = 0
|
||||||
|
|
||||||
@@ -595,20 +703,28 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
if fname == rscfile:
|
if fname == rscfile:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# TODO: Fehlerabfang bei Dateilesen
|
# FIXME: Fehlerabfang bei Dateilesen
|
||||||
with open(fname, "rb") as fh:
|
with open(fname, "rb") as fh:
|
||||||
|
|
||||||
# Dateinamen ermitteln
|
# Dateinamen ermitteln
|
||||||
if dirselect == "":
|
if dirselect == "":
|
||||||
sendname = os.path.basename(fname)
|
sendname = os.path.basename(fname)
|
||||||
else:
|
else:
|
||||||
sendname = fname.replace(dirselect, "")[1:]
|
# Ordnernamen in Dateipfad für RevPi übernehmen
|
||||||
|
sendname = os.path.join(
|
||||||
|
foldername,
|
||||||
|
fname.replace(dirselect, "")[1:]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Prüfen ob Dateiname bereits als Startprogramm angegeben ist
|
||||||
|
if sendname == opt_program:
|
||||||
|
self.uploaded = False
|
||||||
|
|
||||||
# Datei übertragen
|
# Datei übertragen
|
||||||
try:
|
try:
|
||||||
ustatus = self.xmlcli.plcupload(
|
ustatus = self.xmlcli.plcupload(
|
||||||
Binary(gzip.compress(fh.read())), sendname)
|
Binary(gzip.compress(fh.read())), sendname)
|
||||||
except:
|
except Exception:
|
||||||
ec = -2
|
ec = -2
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -618,17 +734,21 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
|
|
||||||
if ec == 0:
|
if ec == 0:
|
||||||
tkmsg.showinfo(
|
tkmsg.showinfo(
|
||||||
parent=self.master, title="Erfolgreich",
|
_("Success"),
|
||||||
message="Die Übertragung war erfolgreich.")
|
_("The PLC program was transferred successfully."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
if self.var_picup.get():
|
if self.var_picup.get():
|
||||||
if rscfile is not None:
|
if rscfile is not None:
|
||||||
self.setpictoryrsc(rscfile)
|
self.setpictoryrsc(rscfile)
|
||||||
else:
|
else:
|
||||||
tkmsg.showerror(
|
tkmsg.showerror(
|
||||||
parent=self.master, title="Fehler",
|
_("Error"),
|
||||||
message="Es wurde im Archiv keine piCtory "
|
_("There is no piCtory configuration in this "
|
||||||
"Konfiguration gefunden")
|
"archive."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
# Einstellungen speichern
|
# Einstellungen speichern
|
||||||
if tup == 0:
|
if tup == 0:
|
||||||
@@ -640,25 +760,25 @@ class RevPiProgram(tkinter.Frame):
|
|||||||
|
|
||||||
self.opt["typeup"] = self.var_typeup.get()
|
self.opt["typeup"] = self.var_typeup.get()
|
||||||
self.opt["picup"] = self.var_picup.get()
|
self.opt["picup"] = self.var_picup.get()
|
||||||
self._savedefaults()
|
_savedefaults(self.revpi, self.opt)
|
||||||
|
|
||||||
elif ec == -1:
|
elif ec == -1:
|
||||||
tkmsg.showerror(
|
tkmsg.showerror(
|
||||||
parent=self.master, title="Fehler",
|
_("Error"),
|
||||||
message="Der Revoluton Pi konnte Teile der Übertragung nicht "
|
_("The Revolution Pi could not process some parts of the "
|
||||||
"verarbeiten.")
|
"transmission."),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
elif ec == -2:
|
elif ec == -2:
|
||||||
tkmsg.showerror(
|
tkmsg.showerror(
|
||||||
parent=self.master, title="Fehler",
|
_("Error"),
|
||||||
message="Bei der Übertragung traten Fehler auf")
|
_("Errors occurred during transmission"),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
# Temp-Dir aufräumen
|
# Temp-Dir aufräumen
|
||||||
if dirtmp is not None:
|
if dirtmp is not None:
|
||||||
rmtree(dirtmp)
|
rmtree(dirtmp)
|
||||||
|
|
||||||
|
return True
|
||||||
if __name__ == "__main__":
|
|
||||||
root = tkinter.Tk()
|
|
||||||
myapp = RevPiProgram(root, None, "test")
|
|
||||||
root.mainloop()
|
|
||||||
|
|||||||
@@ -1,55 +1,57 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
#
|
|
||||||
# RevPiPyControl
|
|
||||||
# Version: 0.2.12
|
|
||||||
#
|
|
||||||
# Webpage: https://revpimodio.org/revpipyplc/
|
|
||||||
# (c) Sven Sager, License: LGPLv3
|
|
||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
u"""RevPiPyControl main program."""
|
||||||
|
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
|
__version__ = "0.8.1"
|
||||||
|
|
||||||
|
import revpicheckclient
|
||||||
|
import revpidevelop
|
||||||
|
import revpiinfo
|
||||||
import revpilogfile
|
import revpilogfile
|
||||||
import revpioption
|
import revpioption
|
||||||
|
import revpilegacy
|
||||||
import revpiplclist
|
import revpiplclist
|
||||||
import revpiprogram
|
import revpiprogram
|
||||||
import socket
|
import socket
|
||||||
import sys
|
|
||||||
import tkinter
|
import tkinter
|
||||||
import tkinter.messagebox as tkmsg
|
import tkinter.messagebox as tkmsg
|
||||||
from functools import partial
|
import webbrowser
|
||||||
from os.path import dirname
|
from mytools import addroot, gettrans
|
||||||
from os.path import join as pathjoin
|
|
||||||
from xmlrpc.client import ServerProxy
|
from xmlrpc.client import ServerProxy
|
||||||
|
|
||||||
|
# Übersetzung laden
|
||||||
def addroot(filename):
|
_ = gettrans()
|
||||||
u"""Hängt root-dir der Anwendung vor Dateinamen.
|
|
||||||
|
|
||||||
Je nach Ausführungsart der Anwendung muss das root-dir über
|
|
||||||
andere Arten abgerufen werden.
|
|
||||||
|
|
||||||
@param filename: Datei oder Ordnername
|
|
||||||
@returns: root dir
|
|
||||||
|
|
||||||
"""
|
|
||||||
if getattr(sys, "frozen", False):
|
|
||||||
return pathjoin(dirname(sys.executable), filename)
|
|
||||||
else:
|
|
||||||
return pathjoin(dirname(__file__), filename)
|
|
||||||
|
|
||||||
|
|
||||||
class RevPiPyControl(tkinter.Frame):
|
class RevPiPyControl(tkinter.Frame):
|
||||||
|
|
||||||
|
u"""Baut Hauptprogramm auf."""
|
||||||
|
|
||||||
def __init__(self, master=None):
|
def __init__(self, master=None):
|
||||||
|
u"""Init RevPiPyControl-Class.
|
||||||
|
@param master tkinter master"""
|
||||||
super().__init__(master)
|
super().__init__(master)
|
||||||
|
self.master.protocol("WM_DELETE_WINDOW", self._closeapp)
|
||||||
self.pack(fill="both", expand=True)
|
self.pack(fill="both", expand=True)
|
||||||
|
|
||||||
self.cli = None
|
self.cli = None
|
||||||
self.dict_conn = revpiplclist.get_connections()
|
self.dict_conn = revpiplclist.get_connections()
|
||||||
self.errcount = 0
|
self.errcount = 0
|
||||||
|
self.revpiaddress = None
|
||||||
self.revpiname = None
|
self.revpiname = None
|
||||||
|
self.revpipyversion = [0, 0, 0]
|
||||||
|
self.xmlfuncs = []
|
||||||
self.xmlmode = 0
|
self.xmlmode = 0
|
||||||
|
|
||||||
|
# Frames vorbereiten
|
||||||
|
self.debugframe = None
|
||||||
|
self.developframe = None
|
||||||
|
|
||||||
# Globale Fenster
|
# Globale Fenster
|
||||||
|
self.tkcheckclient = None
|
||||||
self.tklogs = None
|
self.tklogs = None
|
||||||
self.tkoptions = None
|
self.tkoptions = None
|
||||||
self.tkprogram = None
|
self.tkprogram = None
|
||||||
@@ -61,14 +63,45 @@ class RevPiPyControl(tkinter.Frame):
|
|||||||
self.tmr_plcrunning()
|
self.tmr_plcrunning()
|
||||||
|
|
||||||
def _btnstate(self):
|
def _btnstate(self):
|
||||||
|
u"""Setzt den state der Buttons."""
|
||||||
stateval = "disabled" if self.cli is None else "normal"
|
stateval = "disabled" if self.cli is None else "normal"
|
||||||
self.btn_plclogs["state"] = stateval
|
self.btn_plclogs["state"] = stateval
|
||||||
self.btn_plcstart["state"] = stateval
|
self.btn_plcstart["state"] = stateval
|
||||||
self.btn_plcstop["state"] = stateval
|
self.btn_plcstop["state"] = stateval
|
||||||
self.btn_plcrestart["state"] = stateval
|
self.btn_plcrestart["state"] = stateval
|
||||||
|
self.btn_debug["state"] = stateval
|
||||||
|
|
||||||
|
def _closeall(self):
|
||||||
|
u"""Schließt alle Fenster."""
|
||||||
|
if self.tkcheckclient is not None:
|
||||||
|
self.tkcheckclient.destroy()
|
||||||
|
if self.developframe is not None:
|
||||||
|
self.developframe._checkclose()
|
||||||
|
self.developframe.destroy()
|
||||||
|
self.developframe = None
|
||||||
|
if self.tklogs is not None:
|
||||||
|
self.tklogs.master.destroy()
|
||||||
|
if self.tkoptions is not None:
|
||||||
|
self.tkoptions.destroy()
|
||||||
|
self.tkoptions.master.destroy()
|
||||||
|
if self.tkprogram is not None:
|
||||||
|
self.tkprogram.destroy()
|
||||||
|
if self.debugframe is not None:
|
||||||
|
self.debugframe.destroy()
|
||||||
|
self.debugframe = None
|
||||||
|
try:
|
||||||
|
self.cli.psstop()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _closeapp(self, event=None):
|
||||||
|
u"""Räumt auf und beendet Programm.
|
||||||
|
@param event tkinter Event"""
|
||||||
|
self._closeall()
|
||||||
|
self.master.destroy()
|
||||||
|
|
||||||
def _createwidgets(self):
|
def _createwidgets(self):
|
||||||
"""Erstellt den Fensterinhalt."""
|
u"""Erstellt den Fensterinhalt."""
|
||||||
# Hauptfenster
|
# Hauptfenster
|
||||||
self.master.wm_title("RevPi Python PLC Loader")
|
self.master.wm_title("RevPi Python PLC Loader")
|
||||||
self.master.wm_iconphoto(
|
self.master.wm_iconphoto(
|
||||||
@@ -81,171 +114,336 @@ class RevPiPyControl(tkinter.Frame):
|
|||||||
self.master.config(menu=self.mbar)
|
self.master.config(menu=self.mbar)
|
||||||
|
|
||||||
menu1 = tkinter.Menu(self.mbar, tearoff=False)
|
menu1 = tkinter.Menu(self.mbar, tearoff=False)
|
||||||
menu1.add_command(label="Connections...", command=self.plclist)
|
menu1.add_command(label=_("Connections..."), command=self.plclist)
|
||||||
menu1.add_separator()
|
menu1.add_separator()
|
||||||
menu1.add_command(label="Exit", command=self.master.destroy)
|
menu1.add_command(label=_("Exit"), command=self.master.destroy)
|
||||||
self.mbar.add_cascade(label="Main", menu=menu1)
|
self.mbar.add_cascade(label=_("Main"), menu=menu1)
|
||||||
|
|
||||||
self._fillmbar()
|
self._fillmbar()
|
||||||
self._fillconnbar()
|
self._fillconnbar()
|
||||||
|
|
||||||
self.var_conn = tkinter.StringVar(self)
|
# Hilfe Menü
|
||||||
|
menu1 = tkinter.Menu(self.mbar, tearoff=False)
|
||||||
|
menu1.add_command(
|
||||||
|
label=_("Visit website..."), command=self.visitwebsite)
|
||||||
|
menu1.add_separator()
|
||||||
|
menu1.add_command(label=_("Info..."), command=self.infowindow)
|
||||||
|
self.mbar.add_cascade(label=_("Help"), menu=menu1)
|
||||||
|
|
||||||
|
self.main_frame = tkinter.Frame(self)
|
||||||
|
self.main_frame.pack(side="left", fill="y")
|
||||||
|
|
||||||
|
self.var_conn = tkinter.StringVar(self.main_frame)
|
||||||
self.txt_connect = tkinter.Entry(
|
self.txt_connect = tkinter.Entry(
|
||||||
self, textvariable=self.var_conn, state="readonly", width=30)
|
self.main_frame, state="readonly", width=40
|
||||||
|
)
|
||||||
|
self.txt_connect["textvariable"] = self.var_conn
|
||||||
self.txt_connect.pack(fill="x")
|
self.txt_connect.pack(fill="x")
|
||||||
|
|
||||||
self.btn_plcstart = tkinter.Button(self)
|
self.btn_plcstart = tkinter.Button(self.main_frame)
|
||||||
self.btn_plcstart["text"] = "PLC Start"
|
self.btn_plcstart["text"] = _("PLC start")
|
||||||
self.btn_plcstart["command"] = self.plcstart
|
self.btn_plcstart["command"] = self.plcstart
|
||||||
self.btn_plcstart.pack(fill="x")
|
self.btn_plcstart.pack(fill="x")
|
||||||
|
|
||||||
self.btn_plcstop = tkinter.Button(self)
|
self.btn_plcstop = tkinter.Button(self.main_frame)
|
||||||
self.btn_plcstop["text"] = "PLC Stop"
|
self.btn_plcstop["text"] = _("PLC stop")
|
||||||
self.btn_plcstop["command"] = self.plcstop
|
self.btn_plcstop["command"] = self.plcstop
|
||||||
self.btn_plcstop.pack(fill="x")
|
self.btn_plcstop.pack(fill="x")
|
||||||
|
|
||||||
self.btn_plcrestart = tkinter.Button(self)
|
self.btn_plcrestart = tkinter.Button(self.main_frame)
|
||||||
self.btn_plcrestart["text"] = "PLC Restart"
|
self.btn_plcrestart["text"] = _("PLC restart")
|
||||||
self.btn_plcrestart["command"] = self.plcrestart
|
self.btn_plcrestart["command"] = self.plcrestart
|
||||||
self.btn_plcrestart.pack(fill="x")
|
self.btn_plcrestart.pack(fill="x")
|
||||||
|
|
||||||
self.btn_plclogs = tkinter.Button(self)
|
self.btn_plclogs = tkinter.Button(self.main_frame)
|
||||||
self.btn_plclogs["text"] = "PLC Logs"
|
self.btn_plclogs["text"] = _("PLC logs")
|
||||||
self.btn_plclogs["command"] = self.plclogs
|
self.btn_plclogs["command"] = self.plclogs
|
||||||
self.btn_plclogs.pack(fill="x")
|
self.btn_plclogs.pack(fill="x")
|
||||||
|
|
||||||
self.var_status = tkinter.StringVar(self)
|
self.var_status = tkinter.StringVar(self.main_frame)
|
||||||
self.txt_status = tkinter.Entry(self)
|
self.txt_status = tkinter.Entry(self.main_frame)
|
||||||
self.txt_status["state"] = "readonly"
|
self.txt_status["state"] = "readonly"
|
||||||
self.txt_status["textvariable"] = self.var_status
|
self.txt_status["textvariable"] = self.var_status
|
||||||
self.txt_status.pack(fill="x")
|
self.txt_status.pack(fill="x")
|
||||||
|
|
||||||
|
self.btn_debug = tkinter.Button(self.main_frame)
|
||||||
|
self.btn_debug["text"] = _("PLC watch mode")
|
||||||
|
self.btn_debug["command"] = self.plcdebug
|
||||||
|
self.btn_debug.pack(fill="x")
|
||||||
|
|
||||||
|
def _fillconnbar(self):
|
||||||
|
u"""Generiert Menüeinträge für Verbindungen."""
|
||||||
|
self.mconn.delete(0, "end")
|
||||||
|
for con in sorted(self.dict_conn.keys(), key=lambda x: x.lower()):
|
||||||
|
self.mconn.add_command(
|
||||||
|
label=con, command=lambda con=con: self._opt_conn(con)
|
||||||
|
)
|
||||||
|
|
||||||
def _fillmbar(self):
|
def _fillmbar(self):
|
||||||
|
u"""Generiert Menüeinträge."""
|
||||||
# PLC Menü
|
# PLC Menü
|
||||||
self.mplc = tkinter.Menu(self.mbar, tearoff=False)
|
self.mplc = tkinter.Menu(self.mbar, tearoff=False)
|
||||||
self.mplc.add_command(label="PLC log...", command=self.plclogs)
|
self.mplc.add_command(
|
||||||
#self.mplc.add_command(label="PLC monitor...", command=self.plcmonitor)
|
label=_("PLC log..."), command=self.plclogs)
|
||||||
self.mplc.add_command(label="PLC options...", command=self.plcoptions)
|
self.mplc.add_command(
|
||||||
self.mplc.add_command(label="PLC program...", command=self.plcprogram)
|
label=_("PLC options..."), command=self.plcoptions)
|
||||||
|
self.mplc.add_command(
|
||||||
|
label=_("PLC program..."), command=self.plcprogram)
|
||||||
|
self.mplc.add_command(
|
||||||
|
label=_("PLC developer..."), command=self.plcdevelop)
|
||||||
|
self.mplc.add_separator()
|
||||||
|
|
||||||
|
self.mplc.add_command(
|
||||||
|
label=_("piCtory configuration"), command=self.plcpictory)
|
||||||
|
self.mplc.add_separator()
|
||||||
|
|
||||||
|
self.mplc.add_command(
|
||||||
|
label=_("Disconnect"), command=self.serverdisconnect)
|
||||||
self.mbar.add_cascade(label="PLC", menu=self.mplc, state="disabled")
|
self.mbar.add_cascade(label="PLC", menu=self.mplc, state="disabled")
|
||||||
|
|
||||||
# Connection Menü
|
# Connection Menü
|
||||||
self.mconn = tkinter.Menu(self.mbar, tearoff=False)
|
self.mconn = tkinter.Menu(self.mbar, tearoff=False)
|
||||||
self.mbar.add_cascade(label="Connect", menu=self.mconn)
|
self.mbar.add_cascade(label=_("Connect"), menu=self.mconn)
|
||||||
|
|
||||||
def _fillconnbar(self):
|
def _opt_conn(self, text, reconnect=False):
|
||||||
self.mconn.delete(0, "end")
|
u"""Stellt eine neue Verbindung zu RevPiPyLoad her.
|
||||||
for con in sorted(self.dict_conn.keys(), key=lambda x: x.lower()):
|
@param text Verbindungsname
|
||||||
self.mconn.add_command(
|
@param reconnect Socket Timeout nicht heruntersetzen"""
|
||||||
label=con, command=partial(self._opt_conn, con)
|
if reconnect:
|
||||||
)
|
socket.setdefaulttimeout(6)
|
||||||
|
else:
|
||||||
|
socket.setdefaulttimeout(2)
|
||||||
|
|
||||||
def _opt_conn(self, text):
|
|
||||||
socket.setdefaulttimeout(2)
|
|
||||||
sp = ServerProxy(
|
sp = ServerProxy(
|
||||||
"http://{}:{}".format(
|
"http://{0}:{1}".format(
|
||||||
self.dict_conn[text][0], int(self.dict_conn[text][1])
|
self.dict_conn[text][0], int(self.dict_conn[text][1])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# Server prüfen
|
# Server prüfen
|
||||||
try:
|
try:
|
||||||
|
self.xmlfuncs = sp.system.listMethods()
|
||||||
self.xmlmode = sp.xmlmodus()
|
self.xmlmode = sp.xmlmodus()
|
||||||
except:
|
self.revpipyversion = list(map(int, sp.version().split(".")))
|
||||||
|
except Exception as e:
|
||||||
self.servererror()
|
self.servererror()
|
||||||
else:
|
else:
|
||||||
self._closeall()
|
self._closeall()
|
||||||
socket.setdefaulttimeout(15)
|
socket.setdefaulttimeout(6)
|
||||||
self.cli = ServerProxy(
|
self.cli = ServerProxy(
|
||||||
"http://{}:{}".format(
|
"http://{0}:{1}".format(
|
||||||
self.dict_conn[text][0], int(self.dict_conn[text][1])
|
self.dict_conn[text][0], int(self.dict_conn[text][1])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
self.revpiaddress = self.dict_conn[text][0]
|
||||||
self.revpiname = text
|
self.revpiname = text
|
||||||
self.var_conn.set("{} - {}:{}".format(
|
self.var_conn.set("{0} - {1}:{2}".format(
|
||||||
text, self.dict_conn[text][0], int(self.dict_conn[text][1])
|
text, self.dict_conn[text][0], int(self.dict_conn[text][1])
|
||||||
))
|
))
|
||||||
self.mbar.entryconfig("PLC", state="normal")
|
self.mbar.entryconfig("PLC", state="normal")
|
||||||
|
|
||||||
def _closeall(self):
|
def infowindow(self):
|
||||||
if self.tklogs is not None:
|
u"""Öffnet das Fenster für die Info."""
|
||||||
self.tklogs.master.destroy()
|
|
||||||
if self.tkoptions is not None:
|
|
||||||
self.tkoptions.destroy()
|
|
||||||
if self.tkprogram is not None:
|
|
||||||
self.tkprogram.destroy()
|
|
||||||
|
|
||||||
def plclist(self):
|
|
||||||
win = tkinter.Toplevel(self)
|
win = tkinter.Toplevel(self)
|
||||||
revpiplclist.RevPiPlcList(win)
|
|
||||||
win.focus_set()
|
win.focus_set()
|
||||||
win.grab_set()
|
win.grab_set()
|
||||||
|
revpiinfo.RevPiInfo(win, self.cli, __version__)
|
||||||
|
self.wait_window(win)
|
||||||
|
|
||||||
|
|
||||||
|
def plcdebug(self):
|
||||||
|
u"""Baut den Debugframe und packt ihn.
|
||||||
|
@return None"""
|
||||||
|
self.btn_debug["state"] = "disabled"
|
||||||
|
|
||||||
|
if "psstart" not in self.xmlfuncs:
|
||||||
|
tkmsg.showwarning(
|
||||||
|
_("Warning"),
|
||||||
|
_("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(self.cli.version()),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# FIXME: Bei neuer piCtory Konfig schneller vernichten
|
||||||
|
if self.debugframe is None:
|
||||||
|
try:
|
||||||
|
self.debugframe = revpicheckclient.RevPiCheckClient(
|
||||||
|
self.main_frame, self.cli, self.xmlmode
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
tkmsg.showwarning(
|
||||||
|
_("Error"),
|
||||||
|
_("Can not load piCtory configuration. \n"
|
||||||
|
"Did you create a hardware configuration? "
|
||||||
|
"Please check this in piCtory!"),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
self.btn_debug["state"] = "normal"
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Fehler prüfen
|
||||||
|
if self.debugframe.err_workvalues >= self.debugframe.max_errors:
|
||||||
|
self.debugframe = None
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Show/Hide wechseln
|
||||||
|
if self.debugframe.winfo_viewable():
|
||||||
|
self.debugframe.hideallwindows()
|
||||||
|
if self.debugframe.autorw.get():
|
||||||
|
self.debugframe.autorw.set(False)
|
||||||
|
self.debugframe.toggleauto()
|
||||||
|
self.debugframe.dowrite.set(False)
|
||||||
|
self.debugframe.pack_forget()
|
||||||
|
else:
|
||||||
|
self.debugframe.pack(fill="x")
|
||||||
|
|
||||||
|
self.btn_debug["state"] = "normal"
|
||||||
|
|
||||||
|
def plcdevelop(self):
|
||||||
|
u"""Startet das Developfenster."""
|
||||||
|
if self.xmlmode < 3:
|
||||||
|
tkmsg.showwarning(
|
||||||
|
_("Warning"),
|
||||||
|
_("XML-RPC access mode in the RevPiPyLoad "
|
||||||
|
"configuration is too small to access this dialog!"),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Developframe laden
|
||||||
|
if self.developframe is None:
|
||||||
|
self.developframe = revpidevelop.RevPiDevelop(
|
||||||
|
self, self.cli, self.xmlmode, self.revpiname
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.developframe.winfo_viewable():
|
||||||
|
self.developframe._checkclose()
|
||||||
|
self.developframe.pack_forget()
|
||||||
|
else:
|
||||||
|
self.developframe.pack(side="right") # fill="x")
|
||||||
|
|
||||||
|
def plclist(self):
|
||||||
|
u"""Öffnet das Fenster für die Verbindungen."""
|
||||||
|
win = tkinter.Toplevel(self)
|
||||||
|
win.focus_set()
|
||||||
|
win.grab_set()
|
||||||
|
revpiplclist.RevPiPlcList(win)
|
||||||
self.wait_window(win)
|
self.wait_window(win)
|
||||||
self.dict_conn = revpiplclist.get_connections()
|
self.dict_conn = revpiplclist.get_connections()
|
||||||
self._fillconnbar()
|
self._fillconnbar()
|
||||||
|
|
||||||
def plclogs(self):
|
def plclogs(self):
|
||||||
|
u"""Öffnet das Fenster für Logdateien.
|
||||||
|
@return None"""
|
||||||
|
if "load_plclog" not in self.xmlfuncs:
|
||||||
|
tkmsg.showwarning(
|
||||||
|
_("Warning"),
|
||||||
|
_("This version of Logviewer ist not supported in version {0} "
|
||||||
|
"of RevPiPyLoad on your RevPi! You need at least version "
|
||||||
|
"0.4.1.").format(self.cli.version()),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
if self.tklogs is None or len(self.tklogs.children) == 0:
|
if self.tklogs is None or len(self.tklogs.children) == 0:
|
||||||
win = tkinter.Toplevel(self)
|
win = tkinter.Toplevel(self)
|
||||||
self.tklogs = revpilogfile.RevPiLogfile(win, self.cli)
|
self.tklogs = revpilogfile.RevPiLogfile(win, self.cli)
|
||||||
else:
|
else:
|
||||||
self.tklogs.focus_set()
|
self.tklogs.focus_set()
|
||||||
|
|
||||||
def plcmonitor(self):
|
|
||||||
# TODO: Monitorfenster
|
|
||||||
pass
|
|
||||||
|
|
||||||
def plcoptions(self):
|
def plcoptions(self):
|
||||||
|
u"""Startet das Optionsfenster."""
|
||||||
if self.xmlmode < 2:
|
if self.xmlmode < 2:
|
||||||
tkmsg.showwarning(
|
tkmsg.showwarning(
|
||||||
parent=self.master, title="Warnung",
|
_("Warning"),
|
||||||
message="Der XML-RPC Modus ist beim RevPiPyLoad nicht hoch "
|
_("XML-RPC access mode in the RevPiPyLoad "
|
||||||
"genug eingestellt, um diesen Dialog zu verwenden!"
|
"configuration is too small to access this dialog!"),
|
||||||
|
parent=self.master
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
win = tkinter.Toplevel(self)
|
win = tkinter.Toplevel(self)
|
||||||
self.tkoptions = \
|
|
||||||
revpioption.RevPiOption(win, self.cli, self.xmlmode)
|
|
||||||
win.focus_set()
|
win.focus_set()
|
||||||
win.grab_set()
|
win.grab_set()
|
||||||
|
|
||||||
|
# Gegenstelle prüfen und passende Optionen laden
|
||||||
|
if self.revpipyversion[0] == 0 and self.revpipyversion[1] < 6:
|
||||||
|
self.tkoptions = \
|
||||||
|
revpilegacy.RevPiOption(win, self.cli)
|
||||||
|
else:
|
||||||
|
self.tkoptions = \
|
||||||
|
revpioption.RevPiOption(win, self.cli)
|
||||||
|
|
||||||
self.wait_window(win)
|
self.wait_window(win)
|
||||||
self.xmlmode = self.tkoptions.xmlmode
|
if self.tkoptions.dc is not None and self.tkoptions.dorestart:
|
||||||
|
|
||||||
|
# Wenn XML-Modus anders und Dienstneustart
|
||||||
|
if self.xmlmode != self.cli.xmlmodus():
|
||||||
|
self.serverdisconnect()
|
||||||
|
self._opt_conn(self.revpiname, True)
|
||||||
|
|
||||||
|
if self.debugframe is not None:
|
||||||
|
self.cli.psstart()
|
||||||
|
|
||||||
|
def plcpictory(self):
|
||||||
|
u"""Öffnet piCtory Konfiguraiton."""
|
||||||
|
webbrowser.open("http://{0}/".format(self.revpiaddress))
|
||||||
|
|
||||||
def plcprogram(self):
|
def plcprogram(self):
|
||||||
|
u"""Startet das Programmfenster."""
|
||||||
if self.xmlmode < 2:
|
if self.xmlmode < 2:
|
||||||
tkmsg.showwarning(
|
tkmsg.showwarning(
|
||||||
parent=self.master, title="Warnung",
|
_("Warning"),
|
||||||
message="Der XML-RPC Modus ist beim RevPiPyLoad nicht hoch "
|
_("XML-RPC access mode in the RevPiPyLoad "
|
||||||
"genug eingestellt, um diesen Dialog zu verwenden!"
|
"configuration is too small to access this dialog!"),
|
||||||
|
parent=self.master
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
win = tkinter.Toplevel(self)
|
win = tkinter.Toplevel(self)
|
||||||
self.tkprogram = revpiprogram.RevPiProgram(
|
|
||||||
win, self.cli, self.xmlmode, self.revpiname)
|
|
||||||
win.focus_set()
|
win.focus_set()
|
||||||
win.grab_set()
|
win.grab_set()
|
||||||
|
self.tkprogram = revpiprogram.RevPiProgram(
|
||||||
|
win, self.cli, self.xmlmode, self.revpiname)
|
||||||
self.wait_window(win)
|
self.wait_window(win)
|
||||||
|
|
||||||
def plcstart(self):
|
def plcstart(self):
|
||||||
|
u"""Startet das PLC Programm."""
|
||||||
self.cli.plcstart()
|
self.cli.plcstart()
|
||||||
|
|
||||||
def plcstop(self):
|
def plcstop(self):
|
||||||
|
u"""Beendet das PLC Programm."""
|
||||||
self.cli.plcstop()
|
self.cli.plcstop()
|
||||||
|
|
||||||
def plcrestart(self):
|
def plcrestart(self):
|
||||||
|
u"""Startet das PLC Programm neu."""
|
||||||
self.cli.plcstop()
|
self.cli.plcstop()
|
||||||
self.cli.plcstart()
|
self.cli.plcstart()
|
||||||
|
|
||||||
def servererror(self):
|
def serverdisconnect(self):
|
||||||
"""Setzt alles auf NULL."""
|
u"""Trennt eine bestehende Verbindung."""
|
||||||
|
self._closeall()
|
||||||
socket.setdefaulttimeout(2)
|
socket.setdefaulttimeout(2)
|
||||||
self.cli = None
|
self.cli = None
|
||||||
self._btnstate()
|
self._btnstate()
|
||||||
self.mbar.entryconfig("PLC", state="disabled")
|
self.mbar.entryconfig("PLC", state="disabled")
|
||||||
self.var_conn.set("")
|
self.var_conn.set("")
|
||||||
self._closeall()
|
|
||||||
tkmsg.showerror("Fehler", "Server ist nicht erreichbar!")
|
def servererror(self):
|
||||||
|
u"""Setzt alles zurück für neue Verbindungen."""
|
||||||
|
self.serverdisconnect()
|
||||||
|
tkmsg.showerror(
|
||||||
|
_("Error"),
|
||||||
|
_("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!!!"),
|
||||||
|
parent=self.master
|
||||||
|
)
|
||||||
|
|
||||||
def tmr_plcrunning(self):
|
def tmr_plcrunning(self):
|
||||||
|
u"""Timer der den Status des PLC Programms prüft."""
|
||||||
self._btnstate()
|
self._btnstate()
|
||||||
if self.cli is None:
|
if self.cli is None:
|
||||||
self.txt_status["readonlybackground"] = "lightblue"
|
self.txt_status["readonlybackground"] = "lightblue"
|
||||||
@@ -253,7 +451,7 @@ class RevPiPyControl(tkinter.Frame):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
plcec = self.cli.plcexitcode()
|
plcec = self.cli.plcexitcode()
|
||||||
except:
|
except Exception:
|
||||||
self.errcount += 1
|
self.errcount += 1
|
||||||
if self.errcount >= 5:
|
if self.errcount >= 5:
|
||||||
self.var_status.set("SERVER ERROR")
|
self.var_status.set("SERVER ERROR")
|
||||||
@@ -267,16 +465,22 @@ class RevPiPyControl(tkinter.Frame):
|
|||||||
plcec = "RUNNING"
|
plcec = "RUNNING"
|
||||||
elif plcec == -2:
|
elif plcec == -2:
|
||||||
plcec = "FILE NOT FOUND"
|
plcec = "FILE NOT FOUND"
|
||||||
|
elif plcec == -3:
|
||||||
|
plcec = "NOT RUNNING (NO STATUS)"
|
||||||
elif plcec == -9:
|
elif plcec == -9:
|
||||||
plcec = "PROGRAM KILLED"
|
plcec = "PROGRAM KILLED"
|
||||||
elif plcec == -15:
|
elif plcec == -15:
|
||||||
plcec = "PROGRAMS TERMED"
|
plcec = "PROGRAM TERMED"
|
||||||
elif plcec == 0:
|
elif plcec == 0:
|
||||||
plcec = "NOT RUNNING"
|
plcec = "NOT RUNNING"
|
||||||
self.var_status.set(plcec)
|
self.var_status.set(plcec)
|
||||||
|
|
||||||
self.master.after(1000, self.tmr_plcrunning)
|
self.master.after(1000, self.tmr_plcrunning)
|
||||||
|
|
||||||
|
def visitwebsite(self):
|
||||||
|
u"""Öffnet auf dem System einen Webbrowser zur Projektseite."""
|
||||||
|
webbrowser.open("https://revpimodio.org")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
root = tkinter.Tk()
|
root = tkinter.Tk()
|
||||||
|
|||||||
6
revpipycontrol/shared/__init__.py
Normal file
6
revpipycontrol/shared/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Shared modules."""
|
||||||
|
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
188
revpipycontrol/shared/ipaclmanager.py
Normal file
188
revpipycontrol/shared/ipaclmanager.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Verwaltet IP Adressen und deren ACLs."""
|
||||||
|
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
|
from os import access, R_OK, W_OK
|
||||||
|
from re import match as rematch
|
||||||
|
|
||||||
|
|
||||||
|
def refullmatch(regex, string):
|
||||||
|
"""re.fullmatch wegen alter python version aus wheezy nachgebaut.
|
||||||
|
|
||||||
|
@param regex RegEx Statement
|
||||||
|
@param string Zeichenfolge gegen die getestet wird
|
||||||
|
@return True, wenn komplett passt sonst False
|
||||||
|
|
||||||
|
"""
|
||||||
|
m = rematch(regex, string)
|
||||||
|
return m is not None and m.end() == len(string)
|
||||||
|
|
||||||
|
|
||||||
|
class IpAclManager():
|
||||||
|
|
||||||
|
"""Verwaltung fuer IP Adressen und deren ACL Level."""
|
||||||
|
|
||||||
|
def __init__(self, minlevel, maxlevel, acl=None):
|
||||||
|
"""Init IpAclManager class.
|
||||||
|
|
||||||
|
@param minlevel Smallest access level (min. 0)
|
||||||
|
@param maxlevel Biggest access level (max. 9)
|
||||||
|
@param acl ACL Liste fuer Berechtigungen als <class 'str'>
|
||||||
|
|
||||||
|
"""
|
||||||
|
if type(minlevel) != int:
|
||||||
|
raise ValueError("parameter minlevel must be <class 'int'>")
|
||||||
|
if type(maxlevel) != int:
|
||||||
|
raise ValueError("parameter maxlevel must be <class 'int'>")
|
||||||
|
if minlevel < 0:
|
||||||
|
raise ValueError("minlevel must be 0 or more")
|
||||||
|
if maxlevel > 9:
|
||||||
|
raise ValueError("maxlevel maximum is 9")
|
||||||
|
if minlevel > maxlevel:
|
||||||
|
raise ValueError("minlevel is smaller than maxlevel")
|
||||||
|
|
||||||
|
self.__dict_acl = {}
|
||||||
|
self.__dict_regex = {}
|
||||||
|
self.__dict_knownips = {}
|
||||||
|
self.__filename = None
|
||||||
|
self.__re_ipacl = "(([\\d\\*]{1,3}\\.){3}[\\d\\*]{1,3},[" \
|
||||||
|
+ str(minlevel) + "-" + str(maxlevel) + "] ?)*"
|
||||||
|
|
||||||
|
# Liste erstellen, wenn übergeben
|
||||||
|
if acl is not None:
|
||||||
|
self.__set_acl(acl)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
"""Gibt einzelne ACLs als <class 'tuple'> aus."""
|
||||||
|
for aclip in sorted(self.__dict_acl):
|
||||||
|
yield (aclip, self.__dict_acl[aclip])
|
||||||
|
|
||||||
|
def __get_acl(self):
|
||||||
|
"""Getter fuer den rohen ACL-String.
|
||||||
|
return ACLs als <class 'str'>"""
|
||||||
|
str_acl = ""
|
||||||
|
for aclip in sorted(self.__dict_acl):
|
||||||
|
str_acl += "{0},{1} ".format(aclip, self.__dict_acl[aclip])
|
||||||
|
return str_acl.strip()
|
||||||
|
|
||||||
|
def __get_filename(self):
|
||||||
|
"""Getter fuer Dateinamen.
|
||||||
|
@return Filename der ACL <class 'str'>"""
|
||||||
|
return "" if self.__filename is None else self.__filename
|
||||||
|
|
||||||
|
def __get_regex_acl(self):
|
||||||
|
"""Gibt formatierten RegEx-String zurueck.
|
||||||
|
return RegEx Code als <class 'str'>"""
|
||||||
|
return self.__re_ipacl
|
||||||
|
|
||||||
|
def __set_acl(self, value):
|
||||||
|
"""Uebernimmt neue ACL-Liste fuer die Ausertung der Level.
|
||||||
|
@param value Neue ACL-Liste als <class 'str'>"""
|
||||||
|
if type(value) != str:
|
||||||
|
raise ValueError("parameter acl must be <class 'str'>")
|
||||||
|
|
||||||
|
value = value.strip()
|
||||||
|
if not refullmatch(self.__re_ipacl, value):
|
||||||
|
raise ValueError("acl format ist not okay - 1.2.3.4,0 5.6.7.8,1")
|
||||||
|
|
||||||
|
# Klassenwerte übernehmen
|
||||||
|
self.__dict_acl = {}
|
||||||
|
self.__dict_regex = {}
|
||||||
|
self.__dict_knownips = {}
|
||||||
|
|
||||||
|
# Liste neu füllen mit regex Strings
|
||||||
|
for ip_level in value.split():
|
||||||
|
ip, level = ip_level.split(",", 1)
|
||||||
|
self.__dict_acl[ip] = int(level)
|
||||||
|
self.__dict_regex[ip] = \
|
||||||
|
ip.replace(".", "\\.").replace("*", "\\d{1,3}")
|
||||||
|
|
||||||
|
def get_acllevel(self, ipaddress):
|
||||||
|
"""Prueft IP gegen ACL List und gibt ACL-Wert aus.
|
||||||
|
@param ipaddress zum pruefen
|
||||||
|
@return <class 'int'> ACL Wert oder -1 wenn nicht gefunden"""
|
||||||
|
# Bei bereits aufgelösten IPs direkt ACL auswerten
|
||||||
|
if ipaddress in self.__dict_knownips:
|
||||||
|
return self.__dict_knownips[ipaddress]
|
||||||
|
|
||||||
|
for aclip in sorted(self.__dict_acl, reverse=True):
|
||||||
|
if refullmatch(self.__dict_regex[aclip], ipaddress):
|
||||||
|
# IP und Level merken
|
||||||
|
self.__dict_knownips[ipaddress] = self.__dict_acl[aclip]
|
||||||
|
|
||||||
|
# Level zurückgeben
|
||||||
|
return self.__dict_acl[aclip]
|
||||||
|
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def loadacl(self, str_acl):
|
||||||
|
"""Laed ACL String und gibt erfolg zurueck.
|
||||||
|
@param str_acl ACL als <class 'str'>
|
||||||
|
@return True, wenn erfolgreich uebernommen"""
|
||||||
|
if not refullmatch(self.__re_ipacl, str_acl):
|
||||||
|
return False
|
||||||
|
self.__set_acl(str_acl)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def loadaclfile(self, filename):
|
||||||
|
"""Laed ACL Definitionen aus Datei.
|
||||||
|
@param filename Dateiname fuer Definitionen
|
||||||
|
@return True, wenn Laden erfolgreich war"""
|
||||||
|
if type(filename) != str:
|
||||||
|
raise ValueError("parameter filename must be <class 'str'>")
|
||||||
|
|
||||||
|
# Zugriffsrecht prüfen
|
||||||
|
if not access(filename, R_OK):
|
||||||
|
return False
|
||||||
|
|
||||||
|
str_acl = ""
|
||||||
|
with open(filename, "r") as fh:
|
||||||
|
while True:
|
||||||
|
buff = fh.readline()
|
||||||
|
if buff == "":
|
||||||
|
break
|
||||||
|
buff = buff.split("#")[0].strip()
|
||||||
|
if len(buff) > 0:
|
||||||
|
str_acl += buff + " "
|
||||||
|
|
||||||
|
acl_okay = self.loadacl(str_acl.strip())
|
||||||
|
if acl_okay:
|
||||||
|
# Dateinamen für Schreiben übernehmen
|
||||||
|
self.__filename = filename
|
||||||
|
|
||||||
|
return acl_okay
|
||||||
|
|
||||||
|
def writeaclfile(self, filename=None, aclname=None):
|
||||||
|
"""Schreibt ACL Definitionen in Datei.
|
||||||
|
@param filename Dateiname fuer Definitionen
|
||||||
|
@return True, wenn Schreiben erfolgreich war"""
|
||||||
|
if filename is not None and type(filename) != str:
|
||||||
|
raise ValueError("parameter filename must be <class 'str'>")
|
||||||
|
if aclname is not None and type(aclname) != str:
|
||||||
|
raise ValueError("parameter aclname must be <class 'str'>")
|
||||||
|
|
||||||
|
# Dateinamen prüfen
|
||||||
|
if filename is None and self.__filename is not None:
|
||||||
|
filename = self.__filename
|
||||||
|
|
||||||
|
# Zugriffsrecht prüfen
|
||||||
|
if not access(filename, W_OK):
|
||||||
|
return False
|
||||||
|
|
||||||
|
header = "# {0}Access Control List (acl)\n" \
|
||||||
|
"# One entry per Line IPADRESS,LEVEL\n" \
|
||||||
|
"#\n".format("" if aclname is None else aclname + " ")
|
||||||
|
|
||||||
|
with open(filename, "w") as fh:
|
||||||
|
fh.write(header)
|
||||||
|
for aclip in sorted(self.__dict_acl):
|
||||||
|
fh.write("{0},{1}\n".format(aclip, self.__dict_acl[aclip]))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
acl = property(__get_acl, __set_acl)
|
||||||
|
filename = property(__get_filename)
|
||||||
|
regex_acl = property(__get_regex_acl)
|
||||||
103
setup.py
103
setup.py
@@ -1,11 +1,11 @@
|
|||||||
#! /usr/bin/env python3
|
|
||||||
#
|
|
||||||
# (c) Sven Sager, License: LGPLv3
|
|
||||||
#
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Setupscript fuer RevPiPyLoad."""
|
"""Setupscript fuer RevPiPyLoad."""
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2018 Sven Sager"
|
||||||
|
__license__ = "LGPLv3"
|
||||||
|
|
||||||
import distutils.command.install_egg_info
|
import distutils.command.install_egg_info
|
||||||
from sys import platform
|
from distutils.core import setup
|
||||||
from glob import glob
|
from glob import glob
|
||||||
|
|
||||||
|
|
||||||
@@ -18,73 +18,42 @@ class MyEggInfo(distutils.command.install_egg_info.install_egg_info):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
globsetup = {
|
setup(
|
||||||
"author": "Sven Sager",
|
version="0.8.1",
|
||||||
"author_email": "akira@narux.de",
|
python_requires="~=3.4",
|
||||||
"url": "https://revpimodio.org/revpipyplc/",
|
requires=["tkinter", "zeroconf"],
|
||||||
"license": "LGPLv3",
|
|
||||||
"version": "0.2.12",
|
|
||||||
|
|
||||||
"name": "revpipycontrol",
|
scripts=["data/revpipycontrol"],
|
||||||
|
data_files=[
|
||||||
|
("share/applications", ["data/revpipycontrol.desktop"]),
|
||||||
|
("share/icons/hicolor/32x32/apps", ["data/revpipycontrol.png"]),
|
||||||
|
("share/revpipycontrol", glob("revpipycontrol/*.*")),
|
||||||
|
("share/revpipycontrol/shared", glob("revpipycontrol/shared/*.*")),
|
||||||
|
(
|
||||||
|
"share/revpipycontrol/locale/de/LC_MESSAGES",
|
||||||
|
glob("revpipycontrol/locale/de/LC_MESSAGES/*.mo")
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
"description": "PLC Loader für Python-Projekte auf den RevolutionPi",
|
# Additional meta-data
|
||||||
"long_description": ""
|
name="revpipycontrol",
|
||||||
|
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"
|
"Dieses Programm startet beim Systemstart ein angegebenes Python PLC\n"
|
||||||
"Programm. Es überwacht das Programm und startet es im Fehlerfall neu.\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"
|
"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"
|
"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"
|
"auf den RevPi geladen werden kann. Das Prozessabbild kann über ein Tool\n"
|
||||||
"zur Laufzeit überwacht werden.",
|
"zur Laufzeit überwacht werden.",
|
||||||
}
|
classifiers=[
|
||||||
|
"License :: OSI Approved :: GNU Lesser General Public License v3 (GPLv3)",
|
||||||
if platform == "linux":
|
"Operating System :: POSIX :: Linux",
|
||||||
from setuptools import setup
|
],
|
||||||
setup(
|
license="LGPLv3",
|
||||||
maintainer="Sven Sager",
|
cmdclass={"install_egg_info": MyEggInfo},
|
||||||
maintainer_email="akira@revpimodio.org",
|
)
|
||||||
|
|
||||||
scripts=["data/revpipycontrol"],
|
|
||||||
|
|
||||||
data_files=[
|
|
||||||
("share/applications", ["data/revpipycontrol.desktop"]),
|
|
||||||
("share/icons/hicolor/32x32/apps", ["data/revpipycontrol.png"]),
|
|
||||||
("share/revpipycontrol", glob("revpipycontrol/*.*")),
|
|
||||||
],
|
|
||||||
|
|
||||||
install_requires=["tkinter"],
|
|
||||||
|
|
||||||
classifiers=[
|
|
||||||
"License :: OSI Approved :: "
|
|
||||||
"GNU Lesser General Public License v3 (LGPLv3)",
|
|
||||||
"Operating System :: POSIX :: Linux",
|
|
||||||
],
|
|
||||||
cmdclass={"install_egg_info": MyEggInfo},
|
|
||||||
**globsetup
|
|
||||||
)
|
|
||||||
|
|
||||||
elif platform == "win32":
|
|
||||||
import sys
|
|
||||||
from cx_Freeze import setup, Executable
|
|
||||||
|
|
||||||
sys.path.append("revpipycontrol")
|
|
||||||
|
|
||||||
exe = Executable(
|
|
||||||
script="revpipycontrol/revpipycontrol.py",
|
|
||||||
base="Win32GUI",
|
|
||||||
compress=False,
|
|
||||||
copyDependentFiles=True,
|
|
||||||
appendScriptToExe=True,
|
|
||||||
appendScriptToLibrary=False,
|
|
||||||
icon="data/revpipycontrol.ico"
|
|
||||||
)
|
|
||||||
|
|
||||||
setup(
|
|
||||||
options={"build_exe": {
|
|
||||||
"include_files": [
|
|
||||||
"revpipycontrol/revpipycontrol.png",
|
|
||||||
# "m4server/locale"
|
|
||||||
]
|
|
||||||
}},
|
|
||||||
executables=[exe],
|
|
||||||
**globsetup
|
|
||||||
)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user