mirror of
https://github.com/naruxde/revpimodio2.git
synced 2025-11-08 13:53:53 +01:00
Merge tag '2.7.0' into pkg/debian
Release 2.7.0
This commit is contained in:
53
.gitignore
vendored
53
.gitignore
vendored
@@ -21,6 +21,7 @@ parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
@@ -46,8 +47,10 @@ htmlcov/
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
@@ -57,6 +60,7 @@ coverage.xml
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
@@ -69,6 +73,7 @@ instance/
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
@@ -79,10 +84,38 @@ profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# celery beat schedule file
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
@@ -114,5 +147,17 @@ dmypy.json
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
/make.conf
|
||||
/test_local/
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
# .idea/
|
||||
|
||||
tests_local/
|
||||
|
||||
2
.idea/codeStyles/Project.xml
generated
2
.idea/codeStyles/Project.xml
generated
@@ -1,6 +1,6 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="LINE_SEPARATOR" value=" " />
|
||||
<option name="RIGHT_MARGIN" value="80" />
|
||||
<option name="RIGHT_MARGIN" value="100" />
|
||||
</code_scheme>
|
||||
</component>
|
||||
6
.idea/misc.xml
generated
6
.idea/misc.xml
generated
@@ -1,5 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="enabledOnReformat" value="true" />
|
||||
<option name="executionMode" value="BINARY" />
|
||||
<option name="pathToExecutable" value="/opt/homebrew/bin/black" />
|
||||
<option name="sdkName" value="Python 3.11 (revpimodio2)" />
|
||||
</component>
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
|
||||
627
LICENSE.txt
627
LICENSE.txt
@@ -1,221 +1,397 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. 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 not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
the Library or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
@@ -226,114 +402,101 @@ impose that choice.
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the 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.
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the 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 Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
That's all there is to it!
|
||||
@@ -6,3 +6,4 @@ include MANIFEST.in
|
||||
include README.md
|
||||
include requirements.txt
|
||||
include setup.py
|
||||
recursive-include tests *
|
||||
|
||||
50
Makefile
50
Makefile
@@ -26,32 +26,56 @@ venv-info:
|
||||
exit 0
|
||||
|
||||
venv:
|
||||
$(SYSTEM_PYTHON) -m venv "$(VENV_PATH)"
|
||||
source $(VENV_PATH)/bin/activate && \
|
||||
# Start with empty environment
|
||||
"$(SYSTEM_PYTHON)" -m venv "$(VENV_PATH)"
|
||||
source "$(VENV_PATH)/bin/activate" && \
|
||||
python3 -m pip install --upgrade pip && \
|
||||
python3 -m pip install -r requirements.txt
|
||||
exit 0
|
||||
|
||||
.PHONY: venv-info venv
|
||||
venv-ssp:
|
||||
# Include system installed site-packages and add just missing modules
|
||||
"$(SYSTEM_PYTHON)" -m venv --system-site-packages "$(VENV_PATH)"
|
||||
source "$(VENV_PATH)/bin/activate" && \
|
||||
python3 -m pip install --upgrade pip && \
|
||||
python3 -m pip install -r requirements.txt
|
||||
exit 0
|
||||
|
||||
.PHONY: venv-info venv venv-ssp
|
||||
|
||||
## Build steps
|
||||
test:
|
||||
PYTHONPATH=src "$(PYTHON)" -m pytest tests
|
||||
|
||||
## Build, install
|
||||
build:
|
||||
$(PYTHON) -m setup sdist
|
||||
$(PYTHON) -m setup bdist_wheel
|
||||
"$(PYTHON)" -m setup sdist
|
||||
"$(PYTHON)" -m setup bdist_wheel
|
||||
|
||||
install: build
|
||||
$(PYTHON) -m pip install dist/$(PACKAGE)-*.whl
|
||||
"$(PYTHON)" -m pip install dist/$(PACKAGE)-$(APP_VERSION)-*.whl
|
||||
|
||||
uninstall:
|
||||
"$(PYTHON)" -m pip uninstall --yes $(PACKAGE)
|
||||
|
||||
.PHONY: test build install uninstall
|
||||
|
||||
## Documentation
|
||||
docs:
|
||||
$(PYTHON) -m sphinx.cmd.build -b html docs docs/_build/html
|
||||
"$(PYTHON)" -m sphinx.cmd.build -b html docs docs/_build/html
|
||||
|
||||
.PHONY: build docs install
|
||||
.PHONY: docs
|
||||
|
||||
## Clean
|
||||
clean:
|
||||
rm -rf build docs/_build dist src/*.egg-info *.spec
|
||||
# PyTest caches
|
||||
rm -rf .pytest_cache
|
||||
# Build artifacts
|
||||
rm -rf build dist src/*.egg-info
|
||||
# PyInstaller created files
|
||||
rm -rf *.spec
|
||||
|
||||
clean-all: clean
|
||||
rm -R $(VENV_PATH)
|
||||
distclean: clean
|
||||
# Virtual environment
|
||||
rm -rf "$(VENV_PATH)"
|
||||
|
||||
.PHONY: clean clean-all
|
||||
.PHONY: clean distclean
|
||||
|
||||
2
pyproject.toml
Normal file
2
pyproject.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
@@ -1,3 +1,7 @@
|
||||
setuptools>=58.0.4
|
||||
wheel
|
||||
# Build dependencies
|
||||
pytest-cov
|
||||
setuptools
|
||||
sphinx
|
||||
wheel
|
||||
|
||||
# Runtime dependencies, must match install_requires in setup.py
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
__author__ = "Sven Sager <akira@revpimodio.org>"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "LGPLv2"
|
||||
__version__ = "2.6.1"
|
||||
__version__ = "2.7.0"
|
||||
|
||||
@@ -14,10 +14,25 @@ fuehrt das Modul bei Datenaenderung aus.
|
||||
"""
|
||||
__all__ = [
|
||||
"IOEvent",
|
||||
"RevPiModIO", "RevPiModIODriver", "RevPiModIOSelected", "run_plc",
|
||||
"RevPiNetIO", "RevPiNetIODriver", "RevPiNetIOSelected", "run_net_plc",
|
||||
"Cycletools", "EventCallback",
|
||||
"ProductType", "AIO", "COMPACT", "DI", "DO", "DIO", "FLAT", "MIO",
|
||||
"RevPiModIO",
|
||||
"RevPiModIODriver",
|
||||
"RevPiModIOSelected",
|
||||
"run_plc",
|
||||
"RevPiNetIO",
|
||||
"RevPiNetIODriver",
|
||||
"RevPiNetIOSelected",
|
||||
"run_net_plc",
|
||||
"Cycletools",
|
||||
"EventCallback",
|
||||
"ProductType",
|
||||
"DeviceType",
|
||||
"AIO",
|
||||
"COMPACT",
|
||||
"DI",
|
||||
"DO",
|
||||
"DIO",
|
||||
"FLAT",
|
||||
"MIO",
|
||||
]
|
||||
__author__ = "Sven Sager <akira@revpimodio.org>"
|
||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
@@ -29,4 +44,4 @@ from .helper import Cycletools, EventCallback
|
||||
from .io import IOEvent
|
||||
from .modio import RevPiModIO, RevPiModIODriver, RevPiModIOSelected, run_plc
|
||||
from .netio import RevPiNetIO, RevPiNetIODriver, RevPiNetIOSelected, run_net_plc
|
||||
from .pictory import ProductType, AIO, COMPACT, DI, DO, DIO, FLAT, MIO
|
||||
from .pictory import ProductType, DeviceType, AIO, COMPACT, DI, DO, DIO, FLAT, MIO
|
||||
|
||||
@@ -29,11 +29,11 @@ def acheck(check_type, **kwargs) -> None:
|
||||
for var_name in kwargs:
|
||||
none_okay = var_name.endswith("_noneok")
|
||||
|
||||
if not (isinstance(kwargs[var_name], check_type) or
|
||||
none_okay and kwargs[var_name] is None):
|
||||
if not (isinstance(kwargs[var_name], check_type) or none_okay and kwargs[var_name] is None):
|
||||
msg = "Argument '{0}' must be {1}{2}".format(
|
||||
var_name.rstrip("_noneok"), str(check_type),
|
||||
" or <class 'NoneType'>" if none_okay else ""
|
||||
var_name.rstrip("_noneok"),
|
||||
str(check_type),
|
||||
" or <class 'NoneType'>" if none_okay else "",
|
||||
)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -87,12 +87,31 @@ class Cycletools:
|
||||
Lampen synchron blinken zu lassen.
|
||||
"""
|
||||
|
||||
__slots__ = "__cycle", "__cycletime", "__ucycle", "__dict_ton", \
|
||||
"__dict_tof", "__dict_tp", "__dict_change", \
|
||||
"_start_timer", "core", "device", \
|
||||
"first", "io", "last", "var", \
|
||||
"flag1c", "flag5c", "flag10c", "flag15c", "flag20c", \
|
||||
"flank5c", "flank10c", "flank15c", "flank20c"
|
||||
__slots__ = (
|
||||
"__cycle",
|
||||
"__cycletime",
|
||||
"__ucycle",
|
||||
"__dict_ton",
|
||||
"__dict_tof",
|
||||
"__dict_tp",
|
||||
"__dict_change",
|
||||
"_start_timer",
|
||||
"core",
|
||||
"device",
|
||||
"first",
|
||||
"io",
|
||||
"last",
|
||||
"var",
|
||||
"flag1c",
|
||||
"flag5c",
|
||||
"flag10c",
|
||||
"flag15c",
|
||||
"flag20c",
|
||||
"flank5c",
|
||||
"flank10c",
|
||||
"flank15c",
|
||||
"flank20c",
|
||||
)
|
||||
|
||||
def __init__(self, cycletime, revpi_object):
|
||||
"""Init Cycletools class."""
|
||||
@@ -208,16 +227,13 @@ class Cycletools:
|
||||
else:
|
||||
value = io.get_value()
|
||||
return self.__dict_change[io] != value and (
|
||||
value and edge == RISING or
|
||||
not value and edge == FALLING
|
||||
value and edge == RISING or not value and edge == FALLING
|
||||
)
|
||||
else:
|
||||
if not isinstance(io, IOBase):
|
||||
raise TypeError("parameter 'io' must be an io object")
|
||||
if not (edge == BOTH or type(io.value) == bool):
|
||||
raise ValueError(
|
||||
"parameter 'edge' can be used with bit io objects only"
|
||||
)
|
||||
raise ValueError("parameter 'edge' can be used with bit io objects only")
|
||||
self.__dict_change[io] = None
|
||||
return False
|
||||
|
||||
@@ -283,8 +299,7 @@ class Cycletools:
|
||||
:param milliseconds: Millisekunden, der Verzoegerung wenn neu gestartet
|
||||
"""
|
||||
if self.__dict_ton.get(name, [-1])[0] == -1:
|
||||
self.__dict_ton[name] = \
|
||||
[ceil(milliseconds / self.__cycletime), True]
|
||||
self.__dict_ton[name] = [ceil(milliseconds / self.__cycletime), True]
|
||||
else:
|
||||
self.__dict_ton[name][1] = True
|
||||
|
||||
@@ -326,8 +341,7 @@ class Cycletools:
|
||||
:param milliseconds: Millisekunden, die der Impuls anstehen soll
|
||||
"""
|
||||
if self.__dict_tp.get(name, [-1])[0] == -1:
|
||||
self.__dict_tp[name] = \
|
||||
[ceil(milliseconds / self.__cycletime), True]
|
||||
self.__dict_tp[name] = [ceil(milliseconds / self.__cycletime), True]
|
||||
else:
|
||||
self.__dict_tp[name][1] = True
|
||||
|
||||
@@ -364,9 +378,19 @@ class ProcimgWriter(Thread):
|
||||
Event-Handling verwendet.
|
||||
"""
|
||||
|
||||
__slots__ = "__dict_delay", "__eventth", "_eventqth", "__eventwork", \
|
||||
"_eventq", "_modio", \
|
||||
"_refresh", "_work", "daemon", "lck_refresh", "newdata"
|
||||
__slots__ = (
|
||||
"__dict_delay",
|
||||
"__eventth",
|
||||
"_eventqth",
|
||||
"__eventwork",
|
||||
"_eventq",
|
||||
"_modio",
|
||||
"_refresh",
|
||||
"_work",
|
||||
"daemon",
|
||||
"lck_refresh",
|
||||
"newdata",
|
||||
)
|
||||
|
||||
def __init__(self, parentmodio):
|
||||
"""Init ProcimgWriter class."""
|
||||
@@ -387,43 +411,38 @@ class ProcimgWriter(Thread):
|
||||
def __check_change(self, dev) -> None:
|
||||
"""Findet Aenderungen fuer die Eventueberwachung."""
|
||||
for io_event in dev._dict_events:
|
||||
|
||||
if dev._ba_datacp[io_event._slc_address] == \
|
||||
dev._ba_devdata[io_event._slc_address]:
|
||||
if dev._ba_datacp[io_event._slc_address] == dev._ba_devdata[io_event._slc_address]:
|
||||
continue
|
||||
|
||||
if io_event._bitshift:
|
||||
boolcp = dev._ba_datacp[io_event._slc_address.start] \
|
||||
& io_event._bitshift
|
||||
boolor = dev._ba_devdata[io_event._slc_address.start] \
|
||||
& io_event._bitshift
|
||||
boolcp = dev._ba_datacp[io_event._slc_address.start] & io_event._bitshift
|
||||
boolor = dev._ba_devdata[io_event._slc_address.start] & io_event._bitshift
|
||||
|
||||
if boolor == boolcp:
|
||||
continue
|
||||
|
||||
for regfunc in dev._dict_events[io_event]:
|
||||
if regfunc.edge == BOTH \
|
||||
or regfunc.edge == RISING and boolor \
|
||||
or regfunc.edge == FALLING and not boolor:
|
||||
if (
|
||||
regfunc.edge == BOTH
|
||||
or regfunc.edge == RISING
|
||||
and boolor
|
||||
or regfunc.edge == FALLING
|
||||
and not boolor
|
||||
):
|
||||
if regfunc.delay == 0:
|
||||
if regfunc.as_thread:
|
||||
self._eventqth.put(
|
||||
(regfunc, io_event._name, io_event.value),
|
||||
False
|
||||
)
|
||||
self._eventqth.put((regfunc, io_event._name, io_event.value), False)
|
||||
else:
|
||||
self._eventq.put(
|
||||
(regfunc, io_event._name, io_event.value),
|
||||
False
|
||||
)
|
||||
self._eventq.put((regfunc, io_event._name, io_event.value), False)
|
||||
else:
|
||||
# Verzögertes Event in dict einfügen
|
||||
tup_fire = (
|
||||
regfunc, io_event._name, io_event.value,
|
||||
regfunc,
|
||||
io_event._name,
|
||||
io_event.value,
|
||||
io_event,
|
||||
)
|
||||
if regfunc.overwrite \
|
||||
or tup_fire not in self.__dict_delay:
|
||||
if regfunc.overwrite or tup_fire not in self.__dict_delay:
|
||||
self.__dict_delay[tup_fire] = ceil(
|
||||
regfunc.delay / 1000 / self._refresh
|
||||
)
|
||||
@@ -431,26 +450,19 @@ class ProcimgWriter(Thread):
|
||||
for regfunc in dev._dict_events[io_event]:
|
||||
if regfunc.delay == 0:
|
||||
if regfunc.as_thread:
|
||||
self._eventqth.put(
|
||||
(regfunc, io_event._name, io_event.value),
|
||||
False
|
||||
)
|
||||
self._eventqth.put((regfunc, io_event._name, io_event.value), False)
|
||||
else:
|
||||
self._eventq.put(
|
||||
(regfunc, io_event._name, io_event.value),
|
||||
False
|
||||
)
|
||||
self._eventq.put((regfunc, io_event._name, io_event.value), False)
|
||||
else:
|
||||
# Verzögertes Event in dict einfügen
|
||||
tup_fire = (
|
||||
regfunc, io_event._name, io_event.value,
|
||||
regfunc,
|
||||
io_event._name,
|
||||
io_event.value,
|
||||
io_event,
|
||||
)
|
||||
if regfunc.overwrite \
|
||||
or tup_fire not in self.__dict_delay:
|
||||
self.__dict_delay[tup_fire] = ceil(
|
||||
regfunc.delay / 1000 / self._refresh
|
||||
)
|
||||
if regfunc.overwrite or tup_fire not in self.__dict_delay:
|
||||
self.__dict_delay[tup_fire] = ceil(regfunc.delay / 1000 / self._refresh)
|
||||
|
||||
# Nach Verarbeitung aller IOs die Bytes kopieren (Lock ist noch drauf)
|
||||
dev._ba_datacp = dev._ba_devdata[:]
|
||||
@@ -460,9 +472,7 @@ class ProcimgWriter(Thread):
|
||||
while self.__eventwork:
|
||||
try:
|
||||
tup_fireth = self._eventqth.get(timeout=1)
|
||||
th = EventCallback(
|
||||
tup_fireth[0].func, tup_fireth[1], tup_fireth[2]
|
||||
)
|
||||
th = EventCallback(tup_fireth[0].func, tup_fireth[1], tup_fireth[2])
|
||||
th.start()
|
||||
self._eventqth.task_done()
|
||||
except queue.Empty:
|
||||
@@ -524,7 +534,7 @@ class ProcimgWriter(Thread):
|
||||
warnings.warn(
|
||||
"cycle time of {0} ms exceeded in your cycle function"
|
||||
"".format(int(self._refresh * 1000)),
|
||||
RuntimeWarning
|
||||
RuntimeWarning,
|
||||
)
|
||||
mrk_delay = self._refresh
|
||||
# Nur durch cycleloop erreichbar - keine verzögerten Events
|
||||
@@ -545,23 +555,25 @@ class ProcimgWriter(Thread):
|
||||
|
||||
# Read all device bytes, because it is shared
|
||||
fh.seek(dev.offset)
|
||||
bytesbuff[dev._slc_devoff] = \
|
||||
fh.read(len(dev._ba_devdata))
|
||||
bytesbuff[dev._slc_devoff] = fh.read(len(dev._ba_devdata))
|
||||
|
||||
if self._modio._monitoring or dev._shared_procimg:
|
||||
# Inputs und Outputs in Puffer
|
||||
dev._ba_devdata[:] = bytesbuff[dev._slc_devoff]
|
||||
if self.__eventwork \
|
||||
and len(dev._dict_events) > 0 \
|
||||
and dev._ba_datacp != dev._ba_devdata:
|
||||
if (
|
||||
self.__eventwork
|
||||
and len(dev._dict_events) > 0
|
||||
and dev._ba_datacp != dev._ba_devdata
|
||||
):
|
||||
self.__check_change(dev)
|
||||
else:
|
||||
# Inputs in Puffer, Outputs in Prozessabbild
|
||||
dev._ba_devdata[dev._slc_inp] = \
|
||||
bytesbuff[dev._slc_inpoff]
|
||||
if self.__eventwork \
|
||||
and len(dev._dict_events) > 0 \
|
||||
and dev._ba_datacp != dev._ba_devdata:
|
||||
dev._ba_devdata[dev._slc_inp] = bytesbuff[dev._slc_inpoff]
|
||||
if (
|
||||
self.__eventwork
|
||||
and len(dev._dict_events) > 0
|
||||
and dev._ba_datacp != dev._ba_devdata
|
||||
):
|
||||
self.__check_change(dev)
|
||||
|
||||
fh.seek(dev._slc_outoff.start)
|
||||
@@ -579,16 +591,13 @@ class ProcimgWriter(Thread):
|
||||
else:
|
||||
if not mrk_warn:
|
||||
if self._modio._debug == 0:
|
||||
warnings.warn(
|
||||
"recover from io errors on process image",
|
||||
RuntimeWarning
|
||||
)
|
||||
warnings.warn("recover from io errors on process image", RuntimeWarning)
|
||||
else:
|
||||
warnings.warn(
|
||||
"recover from io errors on process image - total "
|
||||
"count of {0} errors now"
|
||||
"".format(self._modio._ioerror),
|
||||
RuntimeWarning
|
||||
RuntimeWarning,
|
||||
)
|
||||
mrk_warn = True
|
||||
|
||||
@@ -600,8 +609,7 @@ class ProcimgWriter(Thread):
|
||||
# Verzögerte Events prüfen
|
||||
if self.__eventwork:
|
||||
for tup_fire in tuple(self.__dict_delay.keys()):
|
||||
if tup_fire[0].overwrite and \
|
||||
tup_fire[3].value != tup_fire[2]:
|
||||
if tup_fire[0].overwrite and tup_fire[3].value != tup_fire[2]:
|
||||
del self.__dict_delay[tup_fire]
|
||||
else:
|
||||
self.__dict_delay[tup_fire] -= 1
|
||||
@@ -617,9 +625,8 @@ class ProcimgWriter(Thread):
|
||||
# Second default_timer call include calculation time from above
|
||||
if default_timer() - ot > self._refresh:
|
||||
warnings.warn(
|
||||
"io refresh time of {0} ms exceeded!"
|
||||
"".format(int(self._refresh * 1000)),
|
||||
RuntimeWarning
|
||||
"io refresh time of {0} ms exceeded!".format(int(self._refresh * 1000)),
|
||||
RuntimeWarning,
|
||||
)
|
||||
mrk_delay = 0.0
|
||||
else:
|
||||
@@ -641,8 +648,6 @@ class ProcimgWriter(Thread):
|
||||
if type(value) == int and 5 <= value <= 2000:
|
||||
self._refresh = value / 1000
|
||||
else:
|
||||
raise ValueError(
|
||||
"refresh time must be 5 to 2000 milliseconds"
|
||||
)
|
||||
raise ValueError("refresh time must be 5 to 2000 milliseconds")
|
||||
|
||||
refresh = property(get_refresh, set_refresh)
|
||||
|
||||
@@ -5,11 +5,11 @@ __copyright__ = "Copyright (C) 2023 Sven Sager"
|
||||
__license__ = "LGPLv2"
|
||||
|
||||
import struct
|
||||
import warnings
|
||||
from re import match as rematch
|
||||
from threading import Event
|
||||
|
||||
from ._internal import consttostr, RISING, FALLING, BOTH, INP, OUT, \
|
||||
MEM, PROCESS_IMAGE_SIZE
|
||||
from ._internal import consttostr, RISING, FALLING, BOTH, INP, OUT, MEM, PROCESS_IMAGE_SIZE
|
||||
|
||||
try:
|
||||
# Funktioniert nur auf Unix
|
||||
@@ -36,10 +36,11 @@ class IOEvent(object):
|
||||
class IOList(object):
|
||||
"""Basisklasse fuer direkten Zugriff auf IO Objekte."""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, modio):
|
||||
"""Init IOList class."""
|
||||
self.__dict_iobyte = {k: [] for k in range(PROCESS_IMAGE_SIZE)}
|
||||
self.__dict_iorefname = {}
|
||||
self.__modio = modio
|
||||
|
||||
def __contains__(self, key):
|
||||
"""
|
||||
@@ -69,8 +70,16 @@ class IOList(object):
|
||||
self.__dict_iobyte[io_del.address][io_del._bitaddress] = None
|
||||
|
||||
# Do not use any() because we want to know None, not 0
|
||||
if self.__dict_iobyte[io_del.address] == \
|
||||
[None, None, None, None, None, None, None, None]:
|
||||
if self.__dict_iobyte[io_del.address] == [
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
]:
|
||||
self.__dict_iobyte[io_del.address] = []
|
||||
else:
|
||||
self.__dict_iobyte[io_del.address].remove(io_del)
|
||||
@@ -78,6 +87,44 @@ class IOList(object):
|
||||
object.__delattr__(self, key)
|
||||
io_del._parentdevice._update_my_io_list()
|
||||
|
||||
def __enter__(self):
|
||||
"""
|
||||
Read inputs on entering context manager and write outputs on leaving.
|
||||
|
||||
All entries are read when entering the context manager. Within the
|
||||
context manager, further .readprocimg() or .writeprocimg() calls can
|
||||
be made and the process image can be read or written. When exiting,
|
||||
all outputs are always written into the process image.
|
||||
|
||||
When 'autorefresh=True' is used, all read or write actions in the
|
||||
background are performed automatically.
|
||||
"""
|
||||
if not self.__modio._context_manager:
|
||||
# If ModIO itself is in a context manager, it sets the _looprunning=True flag itself
|
||||
if self.__modio._looprunning:
|
||||
raise RuntimeError("can not enter context manager inside mainloop or cycleloop")
|
||||
self.__modio._looprunning = True
|
||||
|
||||
self.__modio.readprocimg()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Write outputs to process image before leaving the context manager."""
|
||||
if self.__modio._imgwriter.is_alive():
|
||||
# Reset new data flat to sync with imgwriter
|
||||
self.__modio._imgwriter.newdata.clear()
|
||||
|
||||
# Write outputs on devices without autorefresh
|
||||
self.__modio.writeprocimg()
|
||||
|
||||
if self.__modio._imgwriter.is_alive():
|
||||
# Wait until imgwriter has written outputs
|
||||
self.__modio._imgwriter.newdata.wait(2.5)
|
||||
|
||||
if self.__modio._context_manager:
|
||||
# Do not reset if ModIO is in a context manager itself, it will handle that flag
|
||||
self.__modio._looprunning = False
|
||||
|
||||
def __getattr__(self, key):
|
||||
"""
|
||||
Verwaltet geloeschte IOs (Attribute, die nicht existieren).
|
||||
@@ -110,9 +157,7 @@ class IOList(object):
|
||||
elif type(key) == slice:
|
||||
return [
|
||||
self.__dict_iobyte[int_io]
|
||||
for int_io in range(
|
||||
key.start, key.stop, 1 if key.step is None else key.step
|
||||
)
|
||||
for int_io in range(key.start, key.stop, 1 if key.step is None else key.step)
|
||||
]
|
||||
else:
|
||||
return getattr(self, key)
|
||||
@@ -145,13 +190,12 @@ class IOList(object):
|
||||
"""Verbietet aus Leistungsguenden das direkte Setzen von Attributen."""
|
||||
if key in (
|
||||
"_IOList__dict_iobyte",
|
||||
"_IOList__dict_iorefname"
|
||||
"_IOList__dict_iorefname",
|
||||
"_IOList__modio",
|
||||
):
|
||||
object.__setattr__(self, key, value)
|
||||
else:
|
||||
raise AttributeError(
|
||||
"direct assignment is not supported - use .value Attribute"
|
||||
)
|
||||
raise AttributeError("direct assignment is not supported - use .value Attribute")
|
||||
|
||||
def __private_replace_oldio_with_newio(self, io) -> None:
|
||||
"""
|
||||
@@ -168,16 +212,17 @@ class IOList(object):
|
||||
scan_stop = scan_start + (1 if io._length == 0 else io._length)
|
||||
|
||||
# Defaultvalue über mehrere Bytes sammeln
|
||||
calc_defaultvalue = b''
|
||||
calc_defaultvalue = b""
|
||||
|
||||
for i in range(scan_start, scan_stop):
|
||||
for oldio in self.__dict_iobyte[i]:
|
||||
|
||||
if type(oldio) == StructIO:
|
||||
# Hier gibt es schon einen neuen IO
|
||||
if oldio._bitshift:
|
||||
if io._bitshift == oldio._bitshift \
|
||||
and io._slc_address == oldio._slc_address:
|
||||
if (
|
||||
io._bitshift == oldio._bitshift
|
||||
and io._slc_address == oldio._slc_address
|
||||
):
|
||||
raise MemoryError(
|
||||
"bit {0} already assigned to '{1}'".format(
|
||||
io._bitaddress, oldio._name
|
||||
@@ -186,9 +231,7 @@ class IOList(object):
|
||||
else:
|
||||
# Bereits überschriebene bytes sind ungültig
|
||||
raise MemoryError(
|
||||
"new io '{0}' overlaps memory of '{1}'".format(
|
||||
io._name, oldio._name
|
||||
)
|
||||
"new io '{0}' overlaps memory of '{1}'".format(io._name, oldio._name)
|
||||
)
|
||||
elif oldio is not None:
|
||||
# IOs im Speicherbereich des neuen IO merken
|
||||
@@ -201,8 +244,7 @@ class IOList(object):
|
||||
if io._byteorder == "little":
|
||||
calc_defaultvalue += oldio._defaultvalue
|
||||
else:
|
||||
calc_defaultvalue = \
|
||||
oldio._defaultvalue + calc_defaultvalue
|
||||
calc_defaultvalue = oldio._defaultvalue + calc_defaultvalue
|
||||
|
||||
# ios aus listen entfernen
|
||||
delattr(self, oldio._name)
|
||||
@@ -211,9 +253,7 @@ class IOList(object):
|
||||
# Nur bei StructIO und keiner gegebenen defaultvalue übernehmen
|
||||
if io._bitshift:
|
||||
io_byte_address = io._parentio_address - io.address
|
||||
io._defaultvalue = bool(
|
||||
io._parentio_defaultvalue[io_byte_address] & io._bitshift
|
||||
)
|
||||
io._defaultvalue = bool(io._parentio_defaultvalue[io_byte_address] & io._bitshift)
|
||||
else:
|
||||
io._defaultvalue = calc_defaultvalue
|
||||
|
||||
@@ -226,25 +266,75 @@ class IOList(object):
|
||||
if isinstance(new_io, IOBase):
|
||||
if hasattr(self, new_io._name):
|
||||
raise AttributeError(
|
||||
"attribute {0} already exists - can not set io"
|
||||
"".format(new_io._name)
|
||||
"attribute {0} already exists - can not set io".format(new_io._name)
|
||||
)
|
||||
|
||||
if type(new_io) is StructIO:
|
||||
do_replace = type(new_io) is StructIO
|
||||
if do_replace:
|
||||
self.__private_replace_oldio_with_newio(new_io)
|
||||
|
||||
object.__setattr__(self, new_io._name, new_io)
|
||||
|
||||
# Bytedict für Adresszugriff anpassen
|
||||
if new_io._bitshift:
|
||||
if len(self.__dict_iobyte[new_io.address]) != 8:
|
||||
# "schnell" 8 Einträge erstellen da es BIT IOs sind
|
||||
self.__dict_iobyte[new_io.address] += \
|
||||
[None, None, None, None, None, None, None, None]
|
||||
self.__dict_iobyte[new_io.address] += [
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
]
|
||||
# Check for overlapping IOs
|
||||
if (
|
||||
not do_replace
|
||||
and self.__dict_iobyte[new_io.address][new_io._bitaddress] is not None
|
||||
):
|
||||
warnings.warn(
|
||||
"ignore io '{0}', as an io already exists at the address '{1} Bit {2}'. "
|
||||
"this can be caused by an incorrect pictory configuration.".format(
|
||||
new_io.name,
|
||||
new_io.address,
|
||||
new_io._bitaddress,
|
||||
),
|
||||
Warning,
|
||||
)
|
||||
return
|
||||
|
||||
self.__dict_iobyte[new_io.address][new_io._bitaddress] = new_io
|
||||
else:
|
||||
# Search the previous IO to calculate the length
|
||||
offset_end = new_io.address
|
||||
search_index = new_io.address
|
||||
while search_index >= 0:
|
||||
previous_io = self.__dict_iobyte[search_index]
|
||||
if len(previous_io) == 8:
|
||||
# Bits on this address are always 1 byte
|
||||
offset_end -= 1
|
||||
elif len(previous_io) == 1:
|
||||
# Found IO, calculate offset + length of IO
|
||||
offset_end = previous_io[0].address + previous_io[0].length
|
||||
break
|
||||
search_index -= 1
|
||||
|
||||
# Check if the length of the previous IO overlaps with the new IO
|
||||
if offset_end > new_io.address:
|
||||
warnings.warn(
|
||||
"ignore io '{0}', as an io already exists at the address '{1}'. "
|
||||
"this can be caused by an incorrect pictory configuration.".format(
|
||||
new_io.name,
|
||||
new_io.address,
|
||||
),
|
||||
Warning,
|
||||
)
|
||||
return
|
||||
|
||||
self.__dict_iobyte[new_io.address].append(new_io)
|
||||
|
||||
object.__setattr__(self, new_io._name, new_io)
|
||||
|
||||
if type(new_io) is StructIO:
|
||||
new_io._parentdevice._update_my_io_list()
|
||||
else:
|
||||
@@ -289,13 +379,26 @@ class IOBase(object):
|
||||
auch als <class 'int'> verwendet werden koennen.
|
||||
"""
|
||||
|
||||
__slots__ = "__bit_ioctl_off", "__bit_ioctl_on", "_bitaddress", \
|
||||
"_bitshift", "_bitlength", "_byteorder", "_defaultvalue", \
|
||||
"_export", "_iotype", "_length", "_name", "_parentdevice", \
|
||||
"_read_only_io", "_signed", "_slc_address", "bmk"
|
||||
__slots__ = (
|
||||
"__bit_ioctl_off",
|
||||
"__bit_ioctl_on",
|
||||
"_bitaddress",
|
||||
"_bitshift",
|
||||
"_bitlength",
|
||||
"_byteorder",
|
||||
"_defaultvalue",
|
||||
"_export",
|
||||
"_iotype",
|
||||
"_length",
|
||||
"_name",
|
||||
"_parentdevice",
|
||||
"_read_only_io",
|
||||
"_signed",
|
||||
"_slc_address",
|
||||
"bmk",
|
||||
)
|
||||
|
||||
def __init__(self, parentdevice, valuelist: list, iotype: int,
|
||||
byteorder: str, signed: bool):
|
||||
def __init__(self, parentdevice, valuelist: list, iotype: int, byteorder: str, signed: bool):
|
||||
"""
|
||||
Instantiierung der IOBase-Klasse.
|
||||
|
||||
@@ -312,8 +415,7 @@ class IOBase(object):
|
||||
|
||||
# Bitadressen auf Bytes aufbrechen und umrechnen
|
||||
self._bitaddress = -1 if valuelist[7] == "" else int(valuelist[7]) % 8
|
||||
self._bitshift = None if self._bitaddress == -1 \
|
||||
else 1 << self._bitaddress
|
||||
self._bitshift = None if self._bitaddress == -1 else 1 << self._bitaddress
|
||||
|
||||
# Längenberechnung
|
||||
self._bitlength = int(valuelist[2])
|
||||
@@ -333,9 +435,7 @@ class IOBase(object):
|
||||
if self._bitshift:
|
||||
# Höhere Bits als 7 auf nächste Bytes umbrechen
|
||||
int_startaddress += int(int(valuelist[7]) / 8)
|
||||
self._slc_address = slice(
|
||||
int_startaddress, int_startaddress + 1
|
||||
)
|
||||
self._slc_address = slice(int_startaddress, int_startaddress + 1)
|
||||
|
||||
# Defaultvalue ermitteln, sonst False
|
||||
if valuelist[1] is None and type(self) == StructIO:
|
||||
@@ -347,14 +447,10 @@ class IOBase(object):
|
||||
self._defaultvalue = False
|
||||
|
||||
# Ioctl für Bitsetzung setzen
|
||||
self.__bit_ioctl_off = struct.pack(
|
||||
"<HB", self._get_address(), self._bitaddress
|
||||
)
|
||||
self.__bit_ioctl_on = self.__bit_ioctl_off + b'\x01'
|
||||
self.__bit_ioctl_off = struct.pack("<HB", self._get_address(), self._bitaddress)
|
||||
self.__bit_ioctl_on = self.__bit_ioctl_off + b"\x01"
|
||||
else:
|
||||
self._slc_address = slice(
|
||||
int_startaddress, int_startaddress + self._length
|
||||
)
|
||||
self._slc_address = slice(int_startaddress, int_startaddress + self._length)
|
||||
if str(valuelist[1]).isdigit():
|
||||
# Defaultvalue aus Zahl in Bytes umrechnen
|
||||
self._defaultvalue = int(valuelist[1]).to_bytes(
|
||||
@@ -382,8 +478,7 @@ class IOBase(object):
|
||||
try:
|
||||
buff = valuelist[1].encode("ASCII")
|
||||
if len(buff) <= self._length:
|
||||
self._defaultvalue = \
|
||||
buff + bytes(self._length - len(buff))
|
||||
self._defaultvalue = buff + bytes(self._length - len(buff))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -394,10 +489,7 @@ class IOBase(object):
|
||||
:return: <class 'bool'> Nur False wenn False oder 0 sonst True
|
||||
"""
|
||||
if self._bitshift:
|
||||
return bool(
|
||||
self._parentdevice._ba_devdata[self._slc_address.start]
|
||||
& self._bitshift
|
||||
)
|
||||
return bool(self._parentdevice._ba_devdata[self._slc_address.start] & self._bitshift)
|
||||
else:
|
||||
return any(self._parentdevice._ba_devdata[self._slc_address])
|
||||
|
||||
@@ -406,8 +498,7 @@ class IOBase(object):
|
||||
# Inline get_value()
|
||||
if self._bitshift:
|
||||
return bool(
|
||||
self._parentdevice._ba_devdata[self._slc_address.start]
|
||||
& self._bitshift
|
||||
self._parentdevice._ba_devdata[self._slc_address.start] & self._bitshift
|
||||
)
|
||||
else:
|
||||
return bytes(self._parentdevice._ba_devdata[self._slc_address])
|
||||
@@ -430,8 +521,9 @@ class IOBase(object):
|
||||
"""
|
||||
return self._name
|
||||
|
||||
def __reg_xevent(self, func, delay: int, edge: int, as_thread: bool,
|
||||
overwrite: bool, prefire: bool) -> None:
|
||||
def __reg_xevent(
|
||||
self, func, delay: int, edge: int, as_thread: bool, overwrite: bool, prefire: bool
|
||||
) -> None:
|
||||
"""
|
||||
Verwaltet reg_event und reg_timerevent.
|
||||
|
||||
@@ -444,26 +536,19 @@ class IOBase(object):
|
||||
"""
|
||||
# Prüfen ob Funktion callable ist
|
||||
if not callable(func):
|
||||
raise ValueError(
|
||||
"registered function '{0}' is not callable".format(func)
|
||||
)
|
||||
raise ValueError("registered function '{0}' is not callable".format(func))
|
||||
if type(delay) != int or delay < 0:
|
||||
raise ValueError(
|
||||
"'delay' must be <class 'int'> and greater or equal 0"
|
||||
)
|
||||
raise ValueError("'delay' must be <class 'int'> and greater or equal 0")
|
||||
if edge != BOTH and not self._bitshift:
|
||||
raise ValueError(
|
||||
"parameter 'edge' can be used with bit io objects only"
|
||||
)
|
||||
raise ValueError("parameter 'edge' can be used with bit io objects only")
|
||||
if prefire and self._parentdevice._modio._looprunning:
|
||||
raise RuntimeError(
|
||||
"prefire can not be used if mainloop is running"
|
||||
)
|
||||
raise RuntimeError("prefire can not be used if mainloop is running")
|
||||
|
||||
if self not in self._parentdevice._dict_events:
|
||||
with self._parentdevice._filelock:
|
||||
self._parentdevice._dict_events[self] = \
|
||||
[IOEvent(func, edge, as_thread, delay, overwrite, prefire)]
|
||||
self._parentdevice._dict_events[self] = [
|
||||
IOEvent(func, edge, as_thread, delay, overwrite, prefire)
|
||||
]
|
||||
else:
|
||||
# Prüfen ob Funktion schon registriert ist
|
||||
for regfunc in self._parentdevice._dict_events[self]:
|
||||
@@ -476,10 +561,7 @@ class IOBase(object):
|
||||
raise RuntimeError(
|
||||
"io '{0}' with function '{1}' already in list "
|
||||
"with edge '{2}' - edge '{3}' not allowed anymore"
|
||||
"".format(
|
||||
self._name, func,
|
||||
consttostr(regfunc.edge), consttostr(edge)
|
||||
)
|
||||
"".format(self._name, func, consttostr(regfunc.edge), consttostr(edge))
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
@@ -490,9 +572,7 @@ class IOBase(object):
|
||||
elif regfunc.edge == edge:
|
||||
raise RuntimeError(
|
||||
"io '{0}' with function '{1}' for given edge '{2}' "
|
||||
"already in list".format(
|
||||
self._name, func, consttostr(edge)
|
||||
)
|
||||
"already in list".format(self._name, func, consttostr(edge))
|
||||
)
|
||||
|
||||
# Eventfunktion einfügen
|
||||
@@ -548,9 +628,7 @@ class IOBase(object):
|
||||
|
||||
if self._bitshift:
|
||||
# Write single bit to process image
|
||||
value = \
|
||||
self._parentdevice._ba_devdata[self._slc_address.start] & \
|
||||
self._bitshift
|
||||
value = self._parentdevice._ba_devdata[self._slc_address.start] & self._bitshift
|
||||
if self._parentdevice._modio._run_on_pi:
|
||||
# IOCTL auf dem RevPi
|
||||
with self._parentdevice._modio._myfh_lck:
|
||||
@@ -559,8 +637,7 @@ class IOBase(object):
|
||||
ioctl(
|
||||
self._parentdevice._modio._myfh,
|
||||
19216,
|
||||
self.__bit_ioctl_on if value
|
||||
else self.__bit_ioctl_off
|
||||
self.__bit_ioctl_on if value else self.__bit_ioctl_off,
|
||||
)
|
||||
except Exception as e:
|
||||
self._parentdevice._modio._gotioerror("ioset", e)
|
||||
@@ -572,12 +649,10 @@ class IOBase(object):
|
||||
try:
|
||||
self._parentdevice._modio._myfh.ioctl(
|
||||
19216,
|
||||
self.__bit_ioctl_on if value
|
||||
else self.__bit_ioctl_off
|
||||
self.__bit_ioctl_on if value else self.__bit_ioctl_off,
|
||||
)
|
||||
except Exception as e:
|
||||
self._parentdevice._modio._gotioerror(
|
||||
"net_ioset", e)
|
||||
self._parentdevice._modio._gotioerror("net_ioset", e)
|
||||
return False
|
||||
|
||||
else:
|
||||
@@ -586,8 +661,7 @@ class IOBase(object):
|
||||
# Set value durchführen (Funktion K+16)
|
||||
self._parentdevice._modio._simulate_ioctl(
|
||||
19216,
|
||||
self.__bit_ioctl_on if value
|
||||
else self.__bit_ioctl_off
|
||||
self.__bit_ioctl_on if value else self.__bit_ioctl_off,
|
||||
)
|
||||
except Exception as e:
|
||||
self._parentdevice._modio._gotioerror("file_ioset", e)
|
||||
@@ -598,9 +672,7 @@ class IOBase(object):
|
||||
value = bytes(self._parentdevice._ba_devdata[self._slc_address])
|
||||
with self._parentdevice._modio._myfh_lck:
|
||||
try:
|
||||
self._parentdevice._modio._myfh.seek(
|
||||
self._get_address()
|
||||
)
|
||||
self._parentdevice._modio._myfh.seek(self._get_address())
|
||||
self._parentdevice._modio._myfh.write(value)
|
||||
if self._parentdevice._modio._buffedwrite:
|
||||
self._parentdevice._modio._myfh.flush()
|
||||
@@ -625,15 +697,11 @@ class IOBase(object):
|
||||
:return: IO-Wert als <class 'bytes'> oder <class 'bool'>
|
||||
"""
|
||||
if self._bitshift:
|
||||
return bool(
|
||||
self._parentdevice._ba_devdata[self._slc_address.start]
|
||||
& self._bitshift
|
||||
)
|
||||
return bool(self._parentdevice._ba_devdata[self._slc_address.start] & self._bitshift)
|
||||
else:
|
||||
return bytes(self._parentdevice._ba_devdata[self._slc_address])
|
||||
|
||||
def reg_event(
|
||||
self, func, delay=0, edge=BOTH, as_thread=False, prefire=False):
|
||||
def reg_event(self, func, delay=0, edge=BOTH, as_thread=False, prefire=False):
|
||||
"""
|
||||
Registriert fuer IO ein Event bei der Eventueberwachung.
|
||||
|
||||
@@ -652,8 +720,7 @@ class IOBase(object):
|
||||
"""
|
||||
self.__reg_xevent(func, delay, edge, as_thread, True, prefire)
|
||||
|
||||
def reg_timerevent(
|
||||
self, func, delay, edge=BOTH, as_thread=False, prefire=False):
|
||||
def reg_timerevent(self, func, delay, edge=BOTH, as_thread=False, prefire=False):
|
||||
"""
|
||||
Registriert fuer IO einen Timer, welcher nach delay func ausfuehrt.
|
||||
|
||||
@@ -685,20 +752,13 @@ class IOBase(object):
|
||||
if self._iotype == INP:
|
||||
if self._parentdevice._modio._simulator:
|
||||
raise RuntimeError(
|
||||
"can not write to output '{0}' in simulator mode"
|
||||
"".format(self._name)
|
||||
"can not write to output '{0}' in simulator mode".format(self._name)
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"can not write to input '{0}'".format(self._name)
|
||||
)
|
||||
raise RuntimeError("can not write to input '{0}'".format(self._name))
|
||||
elif self._iotype == MEM:
|
||||
raise RuntimeError(
|
||||
"can not write to memory '{0}'".format(self._name)
|
||||
)
|
||||
raise RuntimeError(
|
||||
"the io object '{0}' is read only".format(self._name)
|
||||
)
|
||||
raise RuntimeError("can not write to memory '{0}'".format(self._name))
|
||||
raise RuntimeError("the io object '{0}' is read only".format(self._name))
|
||||
|
||||
if self._bitshift:
|
||||
# Versuchen egal welchen Typ in Bool zu konvertieren
|
||||
@@ -722,8 +782,7 @@ class IOBase(object):
|
||||
int_byte -= self._bitshift
|
||||
|
||||
# Zurückschreiben wenn verändert
|
||||
self._parentdevice._ba_devdata[self._slc_address.start] = \
|
||||
int_byte
|
||||
self._parentdevice._ba_devdata[self._slc_address.start] = int_byte
|
||||
|
||||
self._parentdevice._filelock.release()
|
||||
|
||||
@@ -738,9 +797,7 @@ class IOBase(object):
|
||||
if self._length != len(value):
|
||||
raise ValueError(
|
||||
"'{0}' requires a <class 'bytes'> object of "
|
||||
"length {1}, but {2} was given".format(
|
||||
self._name, self._length, len(value)
|
||||
)
|
||||
"length {1}, but {2} was given".format(self._name, self._length, len(value))
|
||||
)
|
||||
|
||||
if self._parentdevice._shared_procimg:
|
||||
@@ -764,8 +821,7 @@ class IOBase(object):
|
||||
else:
|
||||
newlist = []
|
||||
for regfunc in self._parentdevice._dict_events[self]:
|
||||
if regfunc.func != func or edge is not None \
|
||||
and regfunc.edge != edge:
|
||||
if regfunc.func != func or edge is not None and regfunc.edge != edge:
|
||||
newlist.append(regfunc)
|
||||
|
||||
# Wenn Funktionen übrig bleiben, diese übernehmen
|
||||
@@ -832,17 +888,11 @@ class IOBase(object):
|
||||
"revpimodio2.FALLING or revpimodio2.BOTH"
|
||||
)
|
||||
if not (exitevent is None or type(exitevent) == Event):
|
||||
raise TypeError(
|
||||
"parameter 'exitevent' must be <class 'threading.Event'>"
|
||||
)
|
||||
raise TypeError("parameter 'exitevent' must be <class 'threading.Event'>")
|
||||
if type(timeout) != int or timeout < 0:
|
||||
raise ValueError(
|
||||
"parameter 'timeout' must be <class 'int'> and greater than 0"
|
||||
)
|
||||
raise ValueError("parameter 'timeout' must be <class 'int'> and greater than 0")
|
||||
if edge != BOTH and not self._bitshift:
|
||||
raise ValueError(
|
||||
"parameter 'edge' can be used with bit Inputs only"
|
||||
)
|
||||
raise ValueError("parameter 'edge' can be used with bit Inputs only")
|
||||
|
||||
# Abbruchwert prüfen
|
||||
if okvalue == self.value:
|
||||
@@ -858,23 +908,27 @@ class IOBase(object):
|
||||
exitevent = Event()
|
||||
|
||||
flt_timecount = 0 if bool_timecount else -1
|
||||
while not self._parentdevice._modio._waitexit.is_set() \
|
||||
and not exitevent.is_set() \
|
||||
and flt_timecount < timeout:
|
||||
|
||||
while (
|
||||
not self._parentdevice._modio._waitexit.is_set()
|
||||
and not exitevent.is_set()
|
||||
and flt_timecount < timeout
|
||||
):
|
||||
if self._parentdevice._modio._imgwriter.newdata.wait(2.5):
|
||||
self._parentdevice._modio._imgwriter.newdata.clear()
|
||||
|
||||
if val_start != self.value:
|
||||
if edge == BOTH \
|
||||
or edge == RISING and not val_start \
|
||||
or edge == FALLING and val_start:
|
||||
if (
|
||||
edge == BOTH
|
||||
or edge == RISING
|
||||
and not val_start
|
||||
or edge == FALLING
|
||||
and val_start
|
||||
):
|
||||
return 0
|
||||
else:
|
||||
val_start = not val_start
|
||||
if bool_timecount:
|
||||
flt_timecount += \
|
||||
self._parentdevice._modio._imgwriter._refresh
|
||||
flt_timecount += self._parentdevice._modio._imgwriter._refresh
|
||||
elif bool_timecount:
|
||||
flt_timecount += 2.5
|
||||
|
||||
@@ -922,7 +976,7 @@ class IntIO(IOBase):
|
||||
return int.from_bytes(
|
||||
self._parentdevice._ba_devdata[self._slc_address],
|
||||
byteorder=self._byteorder,
|
||||
signed=self._signed
|
||||
signed=self._signed,
|
||||
)
|
||||
|
||||
def __call__(self, value=None):
|
||||
@@ -931,16 +985,18 @@ class IntIO(IOBase):
|
||||
return int.from_bytes(
|
||||
self._parentdevice._ba_devdata[self._slc_address],
|
||||
byteorder=self._byteorder,
|
||||
signed=self._signed
|
||||
signed=self._signed,
|
||||
)
|
||||
else:
|
||||
# Inline from set_intvalue()
|
||||
if type(value) == int:
|
||||
self.set_value(value.to_bytes(
|
||||
self.set_value(
|
||||
value.to_bytes(
|
||||
self._length,
|
||||
byteorder=self._byteorder,
|
||||
signed=self._signed
|
||||
))
|
||||
signed=self._signed,
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise TypeError(
|
||||
"'{0}' need a <class 'int'> value, but {1} was given"
|
||||
@@ -983,9 +1039,7 @@ class IntIO(IOBase):
|
||||
|
||||
:return: <class 'int'> Defaultvalue
|
||||
"""
|
||||
return int.from_bytes(
|
||||
self._defaultvalue, byteorder=self._byteorder, signed=self._signed
|
||||
)
|
||||
return int.from_bytes(self._defaultvalue, byteorder=self._byteorder, signed=self._signed)
|
||||
|
||||
def get_intvalue(self) -> int:
|
||||
"""
|
||||
@@ -996,7 +1050,7 @@ class IntIO(IOBase):
|
||||
return int.from_bytes(
|
||||
self._parentdevice._ba_devdata[self._slc_address],
|
||||
byteorder=self._byteorder,
|
||||
signed=self._signed
|
||||
signed=self._signed,
|
||||
)
|
||||
|
||||
def set_intvalue(self, value: int) -> None:
|
||||
@@ -1006,11 +1060,13 @@ class IntIO(IOBase):
|
||||
:param value: <class 'int'> Wert
|
||||
"""
|
||||
if type(value) == int:
|
||||
self.set_value(value.to_bytes(
|
||||
self.set_value(
|
||||
value.to_bytes(
|
||||
self._length,
|
||||
byteorder=self._byteorder,
|
||||
signed=self._signed
|
||||
))
|
||||
signed=self._signed,
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise TypeError(
|
||||
"'{0}' need a <class 'int'> value, but {1} was given"
|
||||
@@ -1028,9 +1084,7 @@ class IntIOCounter(IntIO):
|
||||
|
||||
__slots__ = ("__ioctl_arg",)
|
||||
|
||||
def __init__(
|
||||
self, counter_id,
|
||||
parentdevice, valuelist, iotype, byteorder, signed):
|
||||
def __init__(self, counter_id, parentdevice, valuelist, iotype, byteorder, signed):
|
||||
"""
|
||||
Instantiierung der IntIOCounter-Klasse.
|
||||
|
||||
@@ -1044,10 +1098,11 @@ class IntIOCounter(IntIO):
|
||||
|
||||
# Deviceposition + leer + Counter_ID
|
||||
# ID-Bits: 7|6|5|4|3|2|1|0|15|14|13|12|11|10|9|8
|
||||
self.__ioctl_arg = \
|
||||
parentdevice._position.to_bytes(1, "little") \
|
||||
+ b'\x00' \
|
||||
self.__ioctl_arg = (
|
||||
parentdevice._position.to_bytes(1, "little")
|
||||
+ b"\x00"
|
||||
+ (1 << counter_id).to_bytes(2, "little")
|
||||
)
|
||||
|
||||
"""
|
||||
IOCTL fuellt dieses struct, welches durch padding im Speicher nach
|
||||
@@ -1067,23 +1122,16 @@ class IntIOCounter(IntIO):
|
||||
def reset(self) -> None:
|
||||
"""Setzt den Counter des Inputs zurueck."""
|
||||
if self._parentdevice._modio._monitoring:
|
||||
raise RuntimeError(
|
||||
"can not reset counter, while system is in monitoring mode"
|
||||
)
|
||||
raise RuntimeError("can not reset counter, while system is in monitoring mode")
|
||||
if self._parentdevice._modio._simulator:
|
||||
raise RuntimeError(
|
||||
"can not reset counter, while system is in simulator mode"
|
||||
)
|
||||
raise RuntimeError("can not reset counter, while system is in simulator mode")
|
||||
|
||||
if self._parentdevice._modio._run_on_pi:
|
||||
# IOCTL auf dem RevPi
|
||||
with self._parentdevice._modio._myfh_lck:
|
||||
try:
|
||||
# Counter reset durchführen (Funktion K+20)
|
||||
ioctl(
|
||||
self._parentdevice._modio._myfh,
|
||||
19220, self.__ioctl_arg
|
||||
)
|
||||
ioctl(self._parentdevice._modio._myfh, 19220, self.__ioctl_arg)
|
||||
except Exception as e:
|
||||
self._parentdevice._modio._gotioerror("iorst", e)
|
||||
|
||||
@@ -1091,9 +1139,7 @@ class IntIOCounter(IntIO):
|
||||
# IOCTL über Netzwerk
|
||||
with self._parentdevice._modio._myfh_lck:
|
||||
try:
|
||||
self._parentdevice._modio._myfh.ioctl(
|
||||
19220, self.__ioctl_arg
|
||||
)
|
||||
self._parentdevice._modio._myfh.ioctl(19220, self.__ioctl_arg)
|
||||
except Exception as e:
|
||||
self._parentdevice._modio._gotioerror("net_iorst", e)
|
||||
|
||||
@@ -1101,9 +1147,7 @@ class IntIOCounter(IntIO):
|
||||
# IOCTL in Datei simulieren
|
||||
try:
|
||||
# Set value durchführen (Funktion K+20)
|
||||
self._parentdevice._modio._simulate_ioctl(
|
||||
19220, self.__ioctl_arg
|
||||
)
|
||||
self._parentdevice._modio._simulate_ioctl(19220, self.__ioctl_arg)
|
||||
except Exception as e:
|
||||
self._parentdevice._modio._gotioerror("file_iorst", e)
|
||||
|
||||
@@ -1153,12 +1197,7 @@ class IntIOReplaceable(IntIO):
|
||||
`<https://docs.python.org/3/library/struct.html#format-characters>`_
|
||||
"""
|
||||
# StructIO erzeugen
|
||||
io_new = StructIO(
|
||||
self,
|
||||
name,
|
||||
frm,
|
||||
**kwargs
|
||||
)
|
||||
io_new = StructIO(self, name, frm, **kwargs)
|
||||
|
||||
# StructIO in IO-Liste einfügen
|
||||
self._parentdevice._modio.io._private_register_new_io_object(io_new)
|
||||
@@ -1170,10 +1209,129 @@ class IntIOReplaceable(IntIO):
|
||||
reg_event,
|
||||
kwargs.get("delay", 0),
|
||||
kwargs.get("edge", BOTH),
|
||||
kwargs.get("as_thread", False)
|
||||
kwargs.get("as_thread", False),
|
||||
)
|
||||
|
||||
|
||||
class RelaisOutput(IOBase):
|
||||
"""
|
||||
Class for relais outputs to access the cycle counters.
|
||||
|
||||
This class extends the function of <class 'IOBase'> to the function
|
||||
'get_cycles' and the property 'cycles' to retrieve the relay cycle
|
||||
counters.
|
||||
|
||||
:ref: :class:`IOBase`
|
||||
"""
|
||||
|
||||
def __init__(self, parentdevice, valuelist, iotype, byteorder, signed):
|
||||
"""
|
||||
Extend <class 'IOBase'> with functions to access cycle counters.
|
||||
|
||||
:ref: :func:`IOBase.__init__(...)`
|
||||
"""
|
||||
super().__init__(parentdevice, valuelist, iotype, byteorder, signed)
|
||||
|
||||
"""
|
||||
typedef struct SROGetCountersStr
|
||||
{
|
||||
/* Address of module in current configuration */
|
||||
uint8_t i8uAddress;
|
||||
uint32_t counter[REVPI_RO_NUM_RELAY_COUNTERS];
|
||||
} SROGetCounters;
|
||||
"""
|
||||
# Device position + padding + four counter with 4 byte each
|
||||
self.__ioctl_arg_format = "<BIIII"
|
||||
self.__ioctl_arg = struct.pack(
|
||||
self.__ioctl_arg_format,
|
||||
parentdevice._position,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
|
||||
def get_switching_cycles(self):
|
||||
"""
|
||||
Get the number of switching cycles from this relay.
|
||||
|
||||
If each relay output is represented as BOOL, this function returns a
|
||||
single integer value. If all relays are displayed as a BYTE, this
|
||||
function returns a tuple that contains the values of all relay outputs.
|
||||
The setting is determined by PiCtory and the selected output variant by
|
||||
the RO device.
|
||||
|
||||
This function is only available locally on a Revolution Pi. This
|
||||
function cannot be used via RevPiNetIO.
|
||||
|
||||
:return: Integer of switching cycles as single value or tuple of all
|
||||
"""
|
||||
# Using ioctl request K+29 = 19229
|
||||
if self._parentdevice._modio._run_on_pi:
|
||||
# IOCTL to piControl on the RevPi
|
||||
with self._parentdevice._modio._myfh_lck:
|
||||
try:
|
||||
ioctl_return_value = ioctl(
|
||||
self._parentdevice._modio._myfh,
|
||||
19229,
|
||||
self.__ioctl_arg,
|
||||
)
|
||||
except Exception as e:
|
||||
# If not implemented, we return the max value and set an error
|
||||
ioctl_return_value = b"\xff" * struct.calcsize(self.__ioctl_arg_format)
|
||||
self._parentdevice._modio._gotioerror("rocounter", e)
|
||||
|
||||
elif hasattr(self._parentdevice._modio._myfh, "ioctl"):
|
||||
# IOCTL over network
|
||||
"""
|
||||
The ioctl function over the network does not return a value. Only the successful
|
||||
execution of the ioctl call is checked and reported back. If a new function has been
|
||||
implemented in RevPiPyLoad, the subsequent source code can be activated.
|
||||
|
||||
with self._parentdevice._modio._myfh_lck:
|
||||
try:
|
||||
ioctl_return_value = self._parentdevice._modio._myfh.ioctl(
|
||||
19229, self.__ioctl_arg
|
||||
)
|
||||
except Exception as e:
|
||||
self._parentdevice._modio._gotioerror("net_rocounter", e)
|
||||
"""
|
||||
raise RuntimeError("Can not be called over network via RevPiNetIO")
|
||||
|
||||
else:
|
||||
# Simulate IOCTL on a regular file returns the value of relais index
|
||||
ioctl_return_value = self.__ioctl_arg
|
||||
|
||||
if self._bitaddress == -1:
|
||||
# Return cycle values of all relais as tuple, if this is a BYTE output
|
||||
# Remove fist element, which is the ioctl request value
|
||||
return struct.unpack(self.__ioctl_arg_format, ioctl_return_value)[1:]
|
||||
else:
|
||||
# Return cycle value of just one relais as int, if this is a BOOL output
|
||||
# Increase bit-address bei 1 to ignore fist element, which is the ioctl request value
|
||||
return struct.unpack(self.__ioctl_arg_format, ioctl_return_value)[self._bitaddress + 1]
|
||||
|
||||
switching_cycles = property(get_switching_cycles)
|
||||
|
||||
|
||||
class IntRelaisOutput(IntIO, RelaisOutput):
|
||||
"""
|
||||
Class for relais outputs to access the cycle counters.
|
||||
|
||||
This class combines the function of <class 'IntIO'> and
|
||||
<class 'RelaisOutput'> to add the function 'get_cycles' and the property
|
||||
'cycles' to retrieve the relay cycle counters.
|
||||
|
||||
Since both classes inherit from BaseIO, both __init__ functions are called
|
||||
and the logic is combined. In this case, there is only one 'self' object of
|
||||
IOBase, which of both classes in inheritance is extended with this.
|
||||
|
||||
:ref: :class:`IOBase`
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class StructIO(IOBase):
|
||||
"""
|
||||
Klasse fuer den Zugriff auf Daten ueber ein definierten struct.
|
||||
@@ -1182,8 +1340,14 @@ class StructIO(IOBase):
|
||||
bereit. Der struct-Formatwert wird bei der Instantiierung festgelegt.
|
||||
"""
|
||||
|
||||
__slots__ = "__frm", "_parentio_address", "_parentio_defaultvalue", \
|
||||
"_parentio_length", "_parentio_name", "_wordorder"
|
||||
__slots__ = (
|
||||
"__frm",
|
||||
"_parentio_address",
|
||||
"_parentio_defaultvalue",
|
||||
"_parentio_length",
|
||||
"_parentio_name",
|
||||
"_wordorder",
|
||||
)
|
||||
|
||||
def __init__(self, parentio, name: str, frm: str, **kwargs):
|
||||
"""
|
||||
@@ -1215,15 +1379,12 @@ class StructIO(IOBase):
|
||||
|
||||
if frm == "?":
|
||||
if self._wordorder:
|
||||
raise ValueError(
|
||||
"you can not use wordorder for bit based ios"
|
||||
)
|
||||
raise ValueError("you can not use wordorder for bit based ios")
|
||||
bitaddress = kwargs.get("bit", 0)
|
||||
max_bits = parentio._length * 8
|
||||
if not (0 <= bitaddress < max_bits):
|
||||
raise ValueError(
|
||||
"bitaddress must be a value between 0 and {0}"
|
||||
"".format(max_bits - 1)
|
||||
"bitaddress must be a value between 0 and {0}".format(max_bits - 1)
|
||||
)
|
||||
bitlength = 1
|
||||
|
||||
@@ -1246,8 +1407,7 @@ class StructIO(IOBase):
|
||||
raise ValueError("wordorder must be 'little' or 'big'")
|
||||
if byte_length % 2 != 0:
|
||||
raise ValueError(
|
||||
"the byte length of new io must must be even to "
|
||||
"use wordorder"
|
||||
"the byte length of new io must must be even to use wordorder"
|
||||
)
|
||||
|
||||
# [name,default,anzbits,adressbyte,export,adressid,bmk,bitaddress]
|
||||
@@ -1260,7 +1420,7 @@ class StructIO(IOBase):
|
||||
False,
|
||||
str(parentio._slc_address.start).rjust(4, "0"),
|
||||
kwargs.get("bmk", ""),
|
||||
bitaddress
|
||||
bitaddress,
|
||||
]
|
||||
else:
|
||||
raise ValueError(
|
||||
@@ -1270,11 +1430,7 @@ class StructIO(IOBase):
|
||||
|
||||
# Basisklasse instantiieren
|
||||
super().__init__(
|
||||
parentio._parentdevice,
|
||||
valuelist,
|
||||
parentio._iotype,
|
||||
byteorder,
|
||||
frm == frm.lower()
|
||||
parentio._parentdevice, valuelist, parentio._iotype, byteorder, frm == frm.lower()
|
||||
)
|
||||
self.__frm = bofrm + frm
|
||||
if "export" in kwargs:
|
||||
@@ -1286,13 +1442,11 @@ class StructIO(IOBase):
|
||||
self._export = parentio._export
|
||||
|
||||
# Platz für neuen IO prüfen
|
||||
if not (self._slc_address.start >=
|
||||
parentio._parentdevice._dict_slc[parentio._iotype].start and
|
||||
self._slc_address.stop <=
|
||||
parentio._parentdevice._dict_slc[parentio._iotype].stop):
|
||||
raise BufferError(
|
||||
"registered value does not fit process image scope"
|
||||
)
|
||||
if not (
|
||||
self._slc_address.start >= parentio._parentdevice._dict_slc[parentio._iotype].start
|
||||
and self._slc_address.stop <= parentio._parentdevice._dict_slc[parentio._iotype].stop
|
||||
):
|
||||
raise BufferError("registered value does not fit process image scope")
|
||||
|
||||
def __call__(self, value=None):
|
||||
if value is None:
|
||||
@@ -1310,9 +1464,7 @@ class StructIO(IOBase):
|
||||
if self._bitshift:
|
||||
self.set_value(value)
|
||||
elif self._wordorder == "little" and self._length > 2:
|
||||
self.set_value(
|
||||
self._swap_word_order(struct.pack(self.__frm, value))
|
||||
)
|
||||
self.set_value(self._swap_word_order(struct.pack(self.__frm, value)))
|
||||
else:
|
||||
self.set_value(struct.pack(self.__frm, value))
|
||||
|
||||
@@ -1343,8 +1495,10 @@ class StructIO(IOBase):
|
||||
array_length = len(bytes_to_swap)
|
||||
swap_array = bytearray(bytes_to_swap)
|
||||
for i in range(0, array_length // 2, 2):
|
||||
swap_array[-i - 2:array_length - i], swap_array[i:i + 2] = \
|
||||
swap_array[i:i + 2], swap_array[-i - 2:array_length - i]
|
||||
swap_array[-i - 2 : array_length - i], swap_array[i : i + 2] = (
|
||||
swap_array[i : i + 2],
|
||||
swap_array[-i - 2 : array_length - i],
|
||||
)
|
||||
return bytes(swap_array)
|
||||
|
||||
def get_structdefaultvalue(self):
|
||||
@@ -1394,9 +1548,7 @@ class StructIO(IOBase):
|
||||
if self._bitshift:
|
||||
self.set_value(value)
|
||||
elif self._wordorder == "little" and self._length > 2:
|
||||
self.set_value(
|
||||
self._swap_word_order(struct.pack(self.__frm, value))
|
||||
)
|
||||
self.set_value(self._swap_word_order(struct.pack(self.__frm, value)))
|
||||
else:
|
||||
self.set_value(struct.pack(self.__frm, value))
|
||||
|
||||
@@ -1422,7 +1574,7 @@ class MemIO(IOBase):
|
||||
if self._bitlength > 64:
|
||||
# STRING
|
||||
try:
|
||||
val = val.strip(b'\x00').decode()
|
||||
val = val.strip(b"\x00").decode()
|
||||
except Exception:
|
||||
pass
|
||||
return val
|
||||
|
||||
@@ -65,20 +65,55 @@ class RevPiModIO(object):
|
||||
Device Positionen oder Device Namen.
|
||||
"""
|
||||
|
||||
__slots__ = "__cleanupfunc", \
|
||||
"_autorefresh", "_buffedwrite", "_configrsc", "_debug", "_devselect", \
|
||||
"_exit", "_exit_level", "_imgwriter", "_ioerror", \
|
||||
"_length", "_looprunning", "_lst_devselect", "_lst_refresh", \
|
||||
"_maxioerrors", "_monitoring", "_myfh", "_myfh_lck", \
|
||||
"_procimg", "_replace_io_file", "_run_on_pi", \
|
||||
"_set_device_based_cycle_time", "_simulator", "_init_shared_procimg", \
|
||||
"_syncoutputs", "_th_mainloop", "_waitexit", \
|
||||
"app", "core", "device", "exitsignal", "io", "summary"
|
||||
__slots__ = (
|
||||
"__cleanupfunc",
|
||||
"_autorefresh",
|
||||
"_buffedwrite",
|
||||
"_configrsc",
|
||||
"_context_manager",
|
||||
"_debug",
|
||||
"_devselect",
|
||||
"_exit",
|
||||
"_exit_level",
|
||||
"_imgwriter",
|
||||
"_ioerror",
|
||||
"_length",
|
||||
"_looprunning",
|
||||
"_lst_devselect",
|
||||
"_lst_refresh",
|
||||
"_maxioerrors",
|
||||
"_monitoring",
|
||||
"_myfh",
|
||||
"_myfh_lck",
|
||||
"_procimg",
|
||||
"_replace_io_file",
|
||||
"_run_on_pi",
|
||||
"_set_device_based_cycle_time",
|
||||
"_simulator",
|
||||
"_init_shared_procimg",
|
||||
"_syncoutputs",
|
||||
"_th_mainloop",
|
||||
"_waitexit",
|
||||
"app",
|
||||
"core",
|
||||
"device",
|
||||
"exitsignal",
|
||||
"io",
|
||||
"summary",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self, autorefresh=False, monitoring=False, syncoutputs=True,
|
||||
procimg=None, configrsc=None, simulator=False, debug=True,
|
||||
replace_io_file=None, shared_procimg=False, direct_output=False):
|
||||
self,
|
||||
autorefresh=False,
|
||||
monitoring=False,
|
||||
syncoutputs=True,
|
||||
procimg=None,
|
||||
configrsc=None,
|
||||
simulator=False,
|
||||
debug=True,
|
||||
replace_io_file=None,
|
||||
shared_procimg=False,
|
||||
):
|
||||
"""
|
||||
Instantiiert die Grundfunktionen.
|
||||
|
||||
@@ -92,32 +127,32 @@ class RevPiModIO(object):
|
||||
:param replace_io_file: Replace IO Konfiguration aus Datei laden
|
||||
:param shared_procimg: Share process image with other processes, this
|
||||
could be insecure for automation
|
||||
:param direct_output: Deprecated, use shared_procimg
|
||||
"""
|
||||
# Parameterprüfung
|
||||
acheck(
|
||||
bool, autorefresh=autorefresh, monitoring=monitoring,
|
||||
syncoutputs=syncoutputs, simulator=simulator, debug=debug,
|
||||
shared_procimg=shared_procimg, direct_output=direct_output
|
||||
bool,
|
||||
autorefresh=autorefresh,
|
||||
monitoring=monitoring,
|
||||
syncoutputs=syncoutputs,
|
||||
simulator=simulator,
|
||||
debug=debug,
|
||||
shared_procimg=shared_procimg,
|
||||
)
|
||||
acheck(
|
||||
str, procimg_noneok=procimg, configrsc_noneok=configrsc,
|
||||
replace_io_file_noneok=replace_io_file
|
||||
str,
|
||||
procimg_noneok=procimg,
|
||||
configrsc_noneok=configrsc,
|
||||
replace_io_file_noneok=replace_io_file,
|
||||
)
|
||||
|
||||
# TODO: Remove in next release
|
||||
if direct_output:
|
||||
warnings.warn(DeprecationWarning(
|
||||
"direct_output is deprecated - use shared_procimg instead!"
|
||||
))
|
||||
|
||||
self._autorefresh = autorefresh
|
||||
self._configrsc = configrsc
|
||||
self._context_manager = False
|
||||
self._monitoring = monitoring
|
||||
self._procimg = "/dev/piControl0" if procimg is None else procimg
|
||||
self._set_device_based_cycle_time = True
|
||||
self._simulator = simulator
|
||||
self._init_shared_procimg = shared_procimg or direct_output
|
||||
self._init_shared_procimg = shared_procimg
|
||||
self._syncoutputs = syncoutputs
|
||||
|
||||
# TODO: bei simulator und procimg prüfen ob datei existiert / anlegen?
|
||||
@@ -172,6 +207,22 @@ class RevPiModIO(object):
|
||||
if self._myfh is not None:
|
||||
self._myfh.close()
|
||||
|
||||
def __enter__(self):
|
||||
if self._context_manager:
|
||||
raise RuntimeError("can not use multiple context managers of same instance")
|
||||
if self._looprunning:
|
||||
raise RuntimeError("can not enter context manager with running mainloop or cycleloop")
|
||||
self._context_manager = True
|
||||
self._looprunning = True
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.writeprocimg()
|
||||
self.exit(full=True)
|
||||
self._looprunning = False
|
||||
self._context_manager = False
|
||||
|
||||
def __evt_exit(self, signum, sigframe) -> None:
|
||||
"""
|
||||
Eventhandler fuer Programmende.
|
||||
@@ -233,13 +284,11 @@ class RevPiModIO(object):
|
||||
|
||||
# Apply device filter
|
||||
if self._devselect.values:
|
||||
|
||||
# Check for supported types in values
|
||||
for dev in self._devselect.values:
|
||||
if type(dev) not in (int, str):
|
||||
raise ValueError(
|
||||
"need device position as <class 'int'> or "
|
||||
"device name as <class 'str'>"
|
||||
"need device position as <class 'int'> or device name as <class 'str'>"
|
||||
)
|
||||
|
||||
lst_devices = []
|
||||
@@ -253,8 +302,10 @@ class RevPiModIO(object):
|
||||
continue
|
||||
else:
|
||||
# Auto search depending of value item type
|
||||
if not (dev["name"] in self._devselect.values
|
||||
or int(dev["position"]) in self._devselect.values):
|
||||
if not (
|
||||
dev["name"] in self._devselect.values
|
||||
or int(dev["position"]) in self._devselect.values
|
||||
):
|
||||
continue
|
||||
|
||||
lst_devices.append(dev)
|
||||
@@ -264,11 +315,23 @@ class RevPiModIO(object):
|
||||
|
||||
# Device und IO Klassen anlegen
|
||||
self.device = devicemodule.DeviceList()
|
||||
self.io = IOList()
|
||||
self.io = IOList(self)
|
||||
|
||||
# Devices initialisieren
|
||||
err_names_check = {}
|
||||
for device in sorted(lst_devices, key=lambda x: x["offset"]):
|
||||
# Pre-check of values
|
||||
if float(device.get("offset")) != int(device.get("offset")):
|
||||
# Offset misconfigured
|
||||
warnings.warn(
|
||||
"Offset value {0} of device {1} on position {2} is invalid. "
|
||||
"This device and all IOs are ignored.".format(
|
||||
device.get("offset"),
|
||||
device.get("name"),
|
||||
device.get("position"),
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
# VDev alter piCtory Versionen auf KUNBUS-Standard ändern
|
||||
if device["position"] == "adap.":
|
||||
@@ -281,64 +344,45 @@ class RevPiModIO(object):
|
||||
pt = int(device["productType"])
|
||||
if pt == ProductType.REVPI_CORE:
|
||||
# RevPi Core
|
||||
dev_new = devicemodule.Core(
|
||||
self, device, simulator=self._simulator
|
||||
)
|
||||
dev_new = devicemodule.Core(self, device, simulator=self._simulator)
|
||||
self.core = dev_new
|
||||
elif pt == ProductType.REVPI_CONNECT:
|
||||
# RevPi Connect
|
||||
dev_new = devicemodule.Connect(
|
||||
self, device, simulator=self._simulator
|
||||
)
|
||||
dev_new = devicemodule.Connect(self, device, simulator=self._simulator)
|
||||
self.core = dev_new
|
||||
elif pt == ProductType.REVPI_CONNECT_4:
|
||||
# RevPi Connect 4
|
||||
dev_new = devicemodule.Connect4(
|
||||
self, device, simulator=self._simulator
|
||||
)
|
||||
dev_new = devicemodule.Connect4(self, device, simulator=self._simulator)
|
||||
self.core = dev_new
|
||||
elif pt == ProductType.REVPI_COMPACT:
|
||||
# RevPi Compact
|
||||
dev_new = devicemodule.Compact(
|
||||
self, device, simulator=self._simulator
|
||||
)
|
||||
dev_new = devicemodule.Compact(self, device, simulator=self._simulator)
|
||||
self.core = dev_new
|
||||
elif pt == ProductType.REVPI_FLAT:
|
||||
# RevPi Flat
|
||||
dev_new = devicemodule.Flat(
|
||||
self, device, simulator=self._simulator
|
||||
)
|
||||
dev_new = devicemodule.Flat(self, device, simulator=self._simulator)
|
||||
self.core = dev_new
|
||||
else:
|
||||
# Base immer als Fallback verwenden
|
||||
dev_new = devicemodule.Base(
|
||||
self, device, simulator=self._simulator
|
||||
)
|
||||
dev_new = devicemodule.Base(self, device, simulator=self._simulator)
|
||||
elif device["type"] == DeviceType.LEFT_RIGHT:
|
||||
# IOs
|
||||
pt = int(device["productType"])
|
||||
if pt == ProductType.DIO \
|
||||
or pt == ProductType.DI \
|
||||
or pt == ProductType.DO:
|
||||
if pt == ProductType.DIO or pt == ProductType.DI or pt == ProductType.DO:
|
||||
# DIO / DI / DO
|
||||
dev_new = devicemodule.DioModule(
|
||||
self, device, simulator=self._simulator
|
||||
)
|
||||
dev_new = devicemodule.DioModule(self, device, simulator=self._simulator)
|
||||
elif pt == ProductType.RO:
|
||||
# RO
|
||||
dev_new = devicemodule.RoModule(self, device, simulator=self._simulator)
|
||||
else:
|
||||
# Alle anderen IO-Devices
|
||||
dev_new = devicemodule.Device(
|
||||
self, device, simulator=self._simulator
|
||||
)
|
||||
dev_new = devicemodule.Device(self, device, simulator=self._simulator)
|
||||
elif device["type"] == DeviceType.VIRTUAL:
|
||||
# Virtuals
|
||||
dev_new = devicemodule.Virtual(
|
||||
self, device, simulator=self._simulator
|
||||
)
|
||||
dev_new = devicemodule.Virtual(self, device, simulator=self._simulator)
|
||||
elif device["type"] == DeviceType.EDGE:
|
||||
# Gateways
|
||||
dev_new = devicemodule.Gateway(
|
||||
self, device, simulator=self._simulator
|
||||
)
|
||||
dev_new = devicemodule.Gateway(self, device, simulator=self._simulator)
|
||||
elif device["type"] == DeviceType.RIGHT:
|
||||
# Connectdevice
|
||||
dev_new = None
|
||||
@@ -347,7 +391,7 @@ class RevPiModIO(object):
|
||||
warnings.warn(
|
||||
"device type '{0}' on position {1} unknown"
|
||||
"".format(device["type"], device["position"]),
|
||||
Warning
|
||||
Warning,
|
||||
)
|
||||
dev_new = None
|
||||
|
||||
@@ -378,7 +422,7 @@ class RevPiModIO(object):
|
||||
"equal device name '{0}' in pictory configuration. you can "
|
||||
"access this devices by position number .device[{1}] only!"
|
||||
"".format(check_dev, "|".join(err_names_check[check_dev])),
|
||||
Warning
|
||||
Warning,
|
||||
)
|
||||
|
||||
# ImgWriter erstellen
|
||||
@@ -393,17 +437,12 @@ class RevPiModIO(object):
|
||||
self.syncoutputs()
|
||||
|
||||
# Für RS485 errors am core defaults laden sollte procimg NULL sein
|
||||
if isinstance(self.core, devicemodule.Core) and \
|
||||
not (self._monitoring or self._simulator):
|
||||
if isinstance(self.core, devicemodule.Core) and not (self._monitoring or self._simulator):
|
||||
if self.core._slc_errorlimit1 is not None:
|
||||
io = self.io[
|
||||
self.core.offset + self.core._slc_errorlimit1.start
|
||||
][0]
|
||||
io = self.io[self.core.offset + self.core._slc_errorlimit1.start][0]
|
||||
io.set_value(io._defaultvalue)
|
||||
if self.core._slc_errorlimit2 is not None:
|
||||
io = self.io[
|
||||
self.core.offset + self.core._slc_errorlimit2.start
|
||||
][0]
|
||||
io = self.io[self.core.offset + self.core._slc_errorlimit2.start][0]
|
||||
io.set_value(io._defaultvalue)
|
||||
|
||||
# RS485 errors schreiben
|
||||
@@ -471,8 +510,7 @@ class RevPiModIO(object):
|
||||
if "defaultvalue" in creplaceio[io]:
|
||||
if dict_replace["frm"] == "?":
|
||||
try:
|
||||
dict_replace["defaultvalue"] = \
|
||||
creplaceio[io].getboolean("defaultvalue")
|
||||
dict_replace["defaultvalue"] = creplaceio[io].getboolean("defaultvalue")
|
||||
except Exception:
|
||||
raise ValueError(
|
||||
"replace_io_file: could not convert '{0}' "
|
||||
@@ -494,8 +532,7 @@ class RevPiModIO(object):
|
||||
)
|
||||
else:
|
||||
try:
|
||||
dict_replace["defaultvalue"] = \
|
||||
creplaceio[io].getint("defaultvalue")
|
||||
dict_replace["defaultvalue"] = creplaceio[io].getint("defaultvalue")
|
||||
except Exception:
|
||||
raise ValueError(
|
||||
"replace_io_file: could not convert '{0}' "
|
||||
@@ -634,23 +671,19 @@ class RevPiModIO(object):
|
||||
self._ioerror += 1
|
||||
if self._maxioerrors != 0 and self._ioerror >= self._maxioerrors:
|
||||
raise RuntimeError(
|
||||
"reach max io error count {0} on process image"
|
||||
"".format(self._maxioerrors)
|
||||
"reach max io error count {0} on process image".format(self._maxioerrors)
|
||||
)
|
||||
|
||||
if not show_warn or self._debug == -1:
|
||||
return
|
||||
|
||||
if self._debug == 0:
|
||||
warnings.warn(
|
||||
"got io error on process image",
|
||||
RuntimeWarning
|
||||
)
|
||||
warnings.warn("got io error on process image", RuntimeWarning)
|
||||
else:
|
||||
warnings.warn(
|
||||
"got io error during '{0}' and count {1} errors now | {2}"
|
||||
"".format(action, self._ioerror, str(e)),
|
||||
RuntimeWarning
|
||||
RuntimeWarning,
|
||||
)
|
||||
|
||||
def _set_cycletime(self, milliseconds: int) -> None:
|
||||
@@ -660,10 +693,7 @@ class RevPiModIO(object):
|
||||
:param milliseconds: <class 'int'> in Millisekunden
|
||||
"""
|
||||
if self._looprunning:
|
||||
raise RuntimeError(
|
||||
"can not change cycletime when cycleloop or mainloop is "
|
||||
"running"
|
||||
)
|
||||
raise RuntimeError("can not change cycletime when cycleloop or mainloop is running")
|
||||
else:
|
||||
self._imgwriter.refresh = milliseconds
|
||||
|
||||
@@ -701,7 +731,7 @@ class RevPiModIO(object):
|
||||
else:
|
||||
raise ValueError("value must be 0 or a positive integer")
|
||||
|
||||
def _simulate_ioctl(self, request: int, arg=b'') -> None:
|
||||
def _simulate_ioctl(self, request: int, arg=b"") -> None:
|
||||
"""
|
||||
Simuliert IOCTL Funktionen auf procimg Datei.
|
||||
|
||||
@@ -717,9 +747,7 @@ class RevPiModIO(object):
|
||||
# Simulatonsmodus schreibt direkt in Datei
|
||||
with self._myfh_lck:
|
||||
self._myfh.seek(byte_address)
|
||||
int_byte = int.from_bytes(
|
||||
self._myfh.read(1), byteorder="little"
|
||||
)
|
||||
int_byte = int.from_bytes(self._myfh.read(1), byteorder="little")
|
||||
int_bit = 1 << bit_address
|
||||
|
||||
if not bool(int_byte & int_bit) == new_value:
|
||||
@@ -741,8 +769,9 @@ class RevPiModIO(object):
|
||||
|
||||
for i in range(16):
|
||||
if bool(bit_field & 1 << i):
|
||||
io_byte = self.device[dev_position].offset \
|
||||
+ int(self.device[dev_position]._lst_counter[i])
|
||||
io_byte = self.device[dev_position].offset + int(
|
||||
self.device[dev_position]._lst_counter[i]
|
||||
)
|
||||
break
|
||||
|
||||
if io_byte == -1:
|
||||
@@ -750,7 +779,7 @@ class RevPiModIO(object):
|
||||
|
||||
with self._myfh_lck:
|
||||
self._myfh.seek(io_byte)
|
||||
self._myfh.write(b'\x00\x00\x00\x00')
|
||||
self._myfh.write(b"\x00\x00\x00\x00")
|
||||
if self._buffedwrite:
|
||||
self._myfh.flush()
|
||||
|
||||
@@ -794,11 +823,12 @@ class RevPiModIO(object):
|
||||
:param blocking: Wenn False, blockiert das Programm hier NICHT
|
||||
:return: None or the return value of the cycle function
|
||||
"""
|
||||
# Check for context manager
|
||||
if self._context_manager:
|
||||
raise RuntimeError("Can not start cycleloop inside a context manager (with statement)")
|
||||
# Prüfen ob ein Loop bereits läuft
|
||||
if self._looprunning:
|
||||
raise RuntimeError(
|
||||
"can not start multiple loops mainloop/cycleloop"
|
||||
)
|
||||
raise RuntimeError("can not start multiple loops mainloop/cycleloop")
|
||||
|
||||
# Prüfen ob Devices in autorefresh sind
|
||||
if len(self._lst_refresh) == 0:
|
||||
@@ -809,16 +839,14 @@ class RevPiModIO(object):
|
||||
|
||||
# Prüfen ob Funktion callable ist
|
||||
if not callable(func):
|
||||
raise RuntimeError(
|
||||
"registered function '{0}' ist not callable".format(func)
|
||||
)
|
||||
raise RuntimeError("registered function '{0}' ist not callable".format(func))
|
||||
|
||||
# Thread erstellen, wenn nicht blockieren soll
|
||||
if not blocking:
|
||||
self._th_mainloop = Thread(
|
||||
target=self.cycleloop,
|
||||
args=(func,),
|
||||
kwargs={"cycletime": cycletime, "blocking": True}
|
||||
kwargs={"cycletime": cycletime, "blocking": True},
|
||||
)
|
||||
self._th_mainloop.start()
|
||||
return
|
||||
@@ -851,9 +879,9 @@ class RevPiModIO(object):
|
||||
break
|
||||
|
||||
# Just warn, user has to use maxioerrors to kill program
|
||||
warnings.warn(RuntimeWarning(
|
||||
"no new io data in cycle loop for 2500 milliseconds"
|
||||
))
|
||||
warnings.warn(
|
||||
RuntimeWarning("no new io data in cycle loop for 2500 milliseconds")
|
||||
)
|
||||
cycleinfo.last = self._exit.is_set()
|
||||
continue
|
||||
|
||||
@@ -942,7 +970,6 @@ class RevPiModIO(object):
|
||||
cp = ConfigParser()
|
||||
for io in self.io:
|
||||
if isinstance(io, StructIO):
|
||||
|
||||
# Required values
|
||||
cp.add_section(io.name)
|
||||
cp[io.name]["replace"] = io._parentio_name
|
||||
@@ -958,8 +985,7 @@ class RevPiModIO(object):
|
||||
if type(io.defaultvalue) is bytes:
|
||||
if any(io.defaultvalue):
|
||||
# Convert each byte to an integer
|
||||
cp[io.name]["defaultvalue"] = \
|
||||
" ".join(map(str, io.defaultvalue))
|
||||
cp[io.name]["defaultvalue"] = " ".join(map(str, io.defaultvalue))
|
||||
elif io.defaultvalue != 0:
|
||||
cp[io.name]["defaultvalue"] = str(io.defaultvalue)
|
||||
if io.bmk != "":
|
||||
@@ -971,10 +997,7 @@ class RevPiModIO(object):
|
||||
with open(filename, "w") as fh:
|
||||
cp.write(fh)
|
||||
except Exception as e:
|
||||
raise RuntimeError(
|
||||
"could not write export file '{0}' | {1}"
|
||||
"".format(filename, e)
|
||||
)
|
||||
raise RuntimeError("could not write export file '{0}' | {1}".format(filename, e))
|
||||
|
||||
def get_jconfigrsc(self) -> dict:
|
||||
"""
|
||||
@@ -986,8 +1009,8 @@ class RevPiModIO(object):
|
||||
if self._configrsc is not None:
|
||||
if not access(self._configrsc, F_OK | R_OK):
|
||||
raise RuntimeError(
|
||||
"can not access pictory configuration at {0}".format(
|
||||
self._configrsc))
|
||||
"can not access pictory configuration at {0}".format(self._configrsc)
|
||||
)
|
||||
else:
|
||||
# piCtory Konfiguration an bekannten Stellen prüfen
|
||||
lst_rsc = ["/etc/revpi/config.rsc", "/opt/KUNBUS/config.rsc"]
|
||||
@@ -1035,10 +1058,7 @@ class RevPiModIO(object):
|
||||
"""
|
||||
# Prüfen ob Funktion callable ist
|
||||
if not (cleanupfunc is None or callable(cleanupfunc)):
|
||||
raise RuntimeError(
|
||||
"registered function '{0}' ist not callable"
|
||||
"".format(cleanupfunc)
|
||||
)
|
||||
raise RuntimeError("registered function '{0}' ist not callable".format(cleanupfunc))
|
||||
self.__cleanupfunc = cleanupfunc
|
||||
signal(SIGINT, self.__evt_exit)
|
||||
signal(SIGTERM, self.__evt_exit)
|
||||
@@ -1061,11 +1081,12 @@ class RevPiModIO(object):
|
||||
|
||||
:param blocking: Wenn False, blockiert das Programm hier NICHT
|
||||
"""
|
||||
# Check for context manager
|
||||
if self._context_manager:
|
||||
raise RuntimeError("Can not start mainloop inside a context manager (with statement)")
|
||||
# Prüfen ob ein Loop bereits läuft
|
||||
if self._looprunning:
|
||||
raise RuntimeError(
|
||||
"can not start multiple loops mainloop/cycleloop"
|
||||
)
|
||||
raise RuntimeError("can not start multiple loops mainloop/cycleloop")
|
||||
|
||||
# Prüfen ob Devices in autorefresh sind
|
||||
if len(self._lst_refresh) == 0:
|
||||
@@ -1076,9 +1097,7 @@ class RevPiModIO(object):
|
||||
|
||||
# Thread erstellen, wenn nicht blockieren soll
|
||||
if not blocking:
|
||||
self._th_mainloop = Thread(
|
||||
target=self.mainloop, kwargs={"blocking": True}
|
||||
)
|
||||
self._th_mainloop = Thread(target=self.mainloop, kwargs={"blocking": True})
|
||||
self._th_mainloop.start()
|
||||
return
|
||||
|
||||
@@ -1100,17 +1119,17 @@ class RevPiModIO(object):
|
||||
if not regfunc.prefire:
|
||||
continue
|
||||
|
||||
if regfunc.edge == BOTH \
|
||||
or regfunc.edge == RISING and io.value \
|
||||
or regfunc.edge == FALLING and not io.value:
|
||||
if (
|
||||
regfunc.edge == BOTH
|
||||
or regfunc.edge == RISING
|
||||
and io.value
|
||||
or regfunc.edge == FALLING
|
||||
and not io.value
|
||||
):
|
||||
if regfunc.as_thread:
|
||||
self._imgwriter._eventqth.put(
|
||||
(regfunc, io._name, io.value), False
|
||||
)
|
||||
self._imgwriter._eventqth.put((regfunc, io._name, io.value), False)
|
||||
else:
|
||||
self._imgwriter._eventq.put(
|
||||
(regfunc, io._name, io.value), False
|
||||
)
|
||||
self._imgwriter._eventq.put((regfunc, io._name, io.value), False)
|
||||
|
||||
# ImgWriter mit Eventüberwachung aktivieren
|
||||
self._imgwriter._collect_events(True)
|
||||
@@ -1118,7 +1137,6 @@ class RevPiModIO(object):
|
||||
runtime = -1 if self._debug == -1 else 0
|
||||
|
||||
while not self._exit.is_set():
|
||||
|
||||
# Laufzeit der Eventqueue auf 0 setzen
|
||||
if self._imgwriter._eventq.qsize() == 0:
|
||||
runtime = -1 if self._debug == -1 else 0
|
||||
@@ -1135,13 +1153,12 @@ class RevPiModIO(object):
|
||||
self._imgwriter._eventq.task_done()
|
||||
|
||||
# Laufzeitprüfung
|
||||
if runtime != -1 and \
|
||||
default_timer() - runtime > self._imgwriter._refresh:
|
||||
if runtime != -1 and default_timer() - runtime > self._imgwriter._refresh:
|
||||
runtime = -1
|
||||
warnings.warn(
|
||||
"can not execute all event functions in one cycle - "
|
||||
"optimize your event functions or rise .cycletime",
|
||||
RuntimeWarning
|
||||
RuntimeWarning,
|
||||
)
|
||||
except Empty:
|
||||
if not self._exit.is_set() and not self._imgwriter.is_alive():
|
||||
@@ -1177,8 +1194,11 @@ class RevPiModIO(object):
|
||||
if device is None:
|
||||
mylist = self.device
|
||||
else:
|
||||
dev = device if isinstance(device, devicemodule.Device) \
|
||||
dev = (
|
||||
device
|
||||
if isinstance(device, devicemodule.Device)
|
||||
else self.device.__getitem__(device)
|
||||
)
|
||||
|
||||
if dev._selfupdate:
|
||||
raise RuntimeError(
|
||||
@@ -1200,7 +1220,6 @@ class RevPiModIO(object):
|
||||
|
||||
for dev in mylist:
|
||||
if not dev._selfupdate:
|
||||
|
||||
# FileHandler sperren
|
||||
dev._filelock.acquire()
|
||||
|
||||
@@ -1226,16 +1245,16 @@ class RevPiModIO(object):
|
||||
:param device: nur auf einzelnes Device anwenden
|
||||
"""
|
||||
if self._monitoring:
|
||||
raise RuntimeError(
|
||||
"can not set default values, while system is in monitoring "
|
||||
"mode"
|
||||
)
|
||||
raise RuntimeError("can not set default values, while system is in monitoring mode")
|
||||
|
||||
if device is None:
|
||||
mylist = self.device
|
||||
else:
|
||||
dev = device if isinstance(device, devicemodule.Device) \
|
||||
dev = (
|
||||
device
|
||||
if isinstance(device, devicemodule.Device)
|
||||
else self.device.__getitem__(device)
|
||||
)
|
||||
mylist = [dev]
|
||||
|
||||
for dev in mylist:
|
||||
@@ -1254,8 +1273,11 @@ class RevPiModIO(object):
|
||||
if device is None:
|
||||
mylist = self.device
|
||||
else:
|
||||
dev = device if isinstance(device, devicemodule.Device) \
|
||||
dev = (
|
||||
device
|
||||
if isinstance(device, devicemodule.Device)
|
||||
else self.device.__getitem__(device)
|
||||
)
|
||||
|
||||
if dev._selfupdate:
|
||||
raise RuntimeError(
|
||||
@@ -1292,16 +1314,16 @@ class RevPiModIO(object):
|
||||
:return: True, wenn Arbeiten an allen Devices erfolgreich waren
|
||||
"""
|
||||
if self._monitoring:
|
||||
raise RuntimeError(
|
||||
"can not write process image, while system is in monitoring "
|
||||
"mode"
|
||||
)
|
||||
raise RuntimeError("can not write process image, while system is in monitoring mode")
|
||||
|
||||
if device is None:
|
||||
mylist = self.device
|
||||
else:
|
||||
dev = device if isinstance(device, devicemodule.Device) \
|
||||
dev = (
|
||||
device
|
||||
if isinstance(device, devicemodule.Device)
|
||||
else self.device.__getitem__(device)
|
||||
)
|
||||
|
||||
if dev._selfupdate:
|
||||
raise RuntimeError(
|
||||
@@ -1321,9 +1343,7 @@ class RevPiModIO(object):
|
||||
if dev._shared_procimg:
|
||||
for io in dev._shared_write:
|
||||
if not io._write_to_procimg():
|
||||
global_ex = IOError(
|
||||
"error on shared procimg while write"
|
||||
)
|
||||
global_ex = IOError("error on shared procimg while write")
|
||||
dev._shared_write.clear()
|
||||
else:
|
||||
# Outpus auf Bus schreiben
|
||||
@@ -1375,10 +1395,18 @@ class RevPiModIOSelected(RevPiModIO):
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(
|
||||
self, deviceselection, autorefresh=False, monitoring=False,
|
||||
syncoutputs=True, procimg=None, configrsc=None,
|
||||
simulator=False, debug=True, replace_io_file=None,
|
||||
shared_procimg=False, direct_output=False):
|
||||
self,
|
||||
deviceselection,
|
||||
autorefresh=False,
|
||||
monitoring=False,
|
||||
syncoutputs=True,
|
||||
procimg=None,
|
||||
configrsc=None,
|
||||
simulator=False,
|
||||
debug=True,
|
||||
replace_io_file=None,
|
||||
shared_procimg=False,
|
||||
):
|
||||
"""
|
||||
Instantiiert nur fuer angegebene Devices die Grundfunktionen.
|
||||
|
||||
@@ -1390,8 +1418,15 @@ class RevPiModIOSelected(RevPiModIO):
|
||||
:ref: :func:`RevPiModIO.__init__(...)`
|
||||
"""
|
||||
super().__init__(
|
||||
autorefresh, monitoring, syncoutputs, procimg, configrsc,
|
||||
simulator, debug, replace_io_file, shared_procimg, direct_output
|
||||
autorefresh,
|
||||
monitoring,
|
||||
syncoutputs,
|
||||
procimg,
|
||||
configrsc,
|
||||
simulator,
|
||||
debug,
|
||||
replace_io_file,
|
||||
shared_procimg,
|
||||
)
|
||||
|
||||
if type(deviceselection) is not DevSelect:
|
||||
@@ -1410,24 +1445,19 @@ class RevPiModIOSelected(RevPiModIO):
|
||||
if len(self.device) == 0:
|
||||
if self._devselect.type:
|
||||
raise DeviceNotFoundError(
|
||||
"could not find ANY given {0} devices in config"
|
||||
"".format(self._devselect.type)
|
||||
"could not find ANY given {0} devices in config".format(self._devselect.type)
|
||||
)
|
||||
else:
|
||||
raise DeviceNotFoundError(
|
||||
"could not find ANY given devices in config"
|
||||
)
|
||||
elif not self._devselect.other_device_key \
|
||||
and len(self.device) != len(self._devselect.values):
|
||||
raise DeviceNotFoundError("could not find ANY given devices in config")
|
||||
elif not self._devselect.other_device_key and len(self.device) != len(
|
||||
self._devselect.values
|
||||
):
|
||||
if self._devselect.type:
|
||||
raise DeviceNotFoundError(
|
||||
"could not find ALL given {0} devices in config"
|
||||
"".format(self._devselect.type)
|
||||
"could not find ALL given {0} devices in config".format(self._devselect.type)
|
||||
)
|
||||
else:
|
||||
raise DeviceNotFoundError(
|
||||
"could not find ALL given devices in config"
|
||||
)
|
||||
raise DeviceNotFoundError("could not find ALL given devices in config")
|
||||
|
||||
|
||||
class RevPiModIODriver(RevPiModIOSelected):
|
||||
@@ -1443,9 +1473,16 @@ class RevPiModIODriver(RevPiModIOSelected):
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(
|
||||
self, virtdev, autorefresh=False,
|
||||
syncoutputs=True, procimg=None, configrsc=None, debug=True,
|
||||
replace_io_file=None, shared_procimg=False, direct_output=False):
|
||||
self,
|
||||
virtdev,
|
||||
autorefresh=False,
|
||||
syncoutputs=True,
|
||||
procimg=None,
|
||||
configrsc=None,
|
||||
debug=True,
|
||||
replace_io_file=None,
|
||||
shared_procimg=False,
|
||||
):
|
||||
"""
|
||||
Instantiiert die Grundfunktionen.
|
||||
|
||||
@@ -1460,14 +1497,20 @@ class RevPiModIODriver(RevPiModIOSelected):
|
||||
virtdev = (virtdev,)
|
||||
dev_select = DevSelect(DeviceType.VIRTUAL, "", virtdev)
|
||||
super().__init__(
|
||||
dev_select, autorefresh, False, syncoutputs, procimg, configrsc,
|
||||
True, debug, replace_io_file, shared_procimg, direct_output
|
||||
dev_select,
|
||||
autorefresh,
|
||||
False,
|
||||
syncoutputs,
|
||||
procimg,
|
||||
configrsc,
|
||||
True,
|
||||
debug,
|
||||
replace_io_file,
|
||||
shared_procimg,
|
||||
)
|
||||
|
||||
|
||||
def run_plc(
|
||||
func, cycletime=50, replace_io_file=None, debug=True,
|
||||
procimg=None, configrsc=None):
|
||||
def run_plc(func, cycletime=50, replace_io_file=None, debug=True, procimg=None, configrsc=None):
|
||||
"""
|
||||
Run Revoluton Pi as real plc with cycle loop and exclusive IO access.
|
||||
|
||||
|
||||
@@ -18,22 +18,22 @@ from .modio import DevSelect, RevPiModIO as _RevPiModIO
|
||||
from .pictory import DeviceType
|
||||
|
||||
# Synchronisierungsbefehl
|
||||
_syssync = b'\x01\x06\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17'
|
||||
_syssync = b"\x01\x06\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17"
|
||||
# Disconnectbefehl
|
||||
_sysexit = b'\x01EX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17'
|
||||
_sysexit = b"\x01EX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17"
|
||||
# DirtyBytes von Server entfernen
|
||||
_sysdeldirty = b'\x01EY\x00\x00\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x17'
|
||||
_sysdeldirty = b"\x01EY\x00\x00\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x17"
|
||||
# piCtory Konfiguration laden
|
||||
_syspictory = b'\x01PI\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17'
|
||||
_syspictoryh = b'\x01PH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17'
|
||||
_syspictory = b"\x01PI\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17"
|
||||
_syspictoryh = b"\x01PH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17"
|
||||
# ReplaceIO Konfiguration laden
|
||||
_sysreplaceio = b'\x01RP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17'
|
||||
_sysreplaceioh = b'\x01RH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17'
|
||||
_sysreplaceio = b"\x01RP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17"
|
||||
_sysreplaceioh = b"\x01RH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17"
|
||||
# Hashvalues
|
||||
HASH_FAIL = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
HASH_FAIL = b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
# Header start/stop
|
||||
HEADER_START = b'\x01'
|
||||
HEADER_STOP = b'\x17'
|
||||
HEADER_START = b"\x01"
|
||||
HEADER_STOP = b"\x17"
|
||||
|
||||
|
||||
class AclException(Exception):
|
||||
@@ -57,12 +57,28 @@ class NetFH(Thread):
|
||||
so gesteuert werden.
|
||||
"""
|
||||
|
||||
__slots__ = "__buff_size", "__buff_block", "__buff_recv", \
|
||||
"__by_buff", "__check_replace_ios", "__config_changed", \
|
||||
"__int_buff", "__dictdirty", "__flusherr", "__replace_ios_h", \
|
||||
"__pictory_h", "__position", "__sockerr", "__sockend", \
|
||||
"__socklock", "__timeout", "__waitsync", "_address", \
|
||||
"_serversock", "daemon"
|
||||
__slots__ = (
|
||||
"__buff_size",
|
||||
"__buff_block",
|
||||
"__buff_recv",
|
||||
"__by_buff",
|
||||
"__check_replace_ios",
|
||||
"__config_changed",
|
||||
"__int_buff",
|
||||
"__dictdirty",
|
||||
"__flusherr",
|
||||
"__replace_ios_h",
|
||||
"__pictory_h",
|
||||
"__position",
|
||||
"__sockerr",
|
||||
"__sockend",
|
||||
"__socklock",
|
||||
"__timeout",
|
||||
"__waitsync",
|
||||
"_address",
|
||||
"_serversock",
|
||||
"daemon",
|
||||
)
|
||||
|
||||
def __init__(self, address: tuple, check_replace_ios: bool, timeout=500):
|
||||
"""
|
||||
@@ -83,8 +99,8 @@ class NetFH(Thread):
|
||||
self.__config_changed = False
|
||||
self.__int_buff = 0
|
||||
self.__dictdirty = {}
|
||||
self.__replace_ios_h = b''
|
||||
self.__pictory_h = b''
|
||||
self.__replace_ios_h = b""
|
||||
self.__pictory_h = b""
|
||||
self.__sockerr = Event()
|
||||
self.__sockend = Event()
|
||||
self.__socklock = Lock()
|
||||
@@ -95,9 +111,7 @@ class NetFH(Thread):
|
||||
|
||||
# Parameterprüfung
|
||||
if not isinstance(address, tuple):
|
||||
raise TypeError(
|
||||
"parameter address must be <class 'tuple'> ('IP', PORT)"
|
||||
)
|
||||
raise TypeError("parameter address must be <class 'tuple'> ('IP', PORT)")
|
||||
if not isinstance(timeout, int):
|
||||
raise TypeError("parameter timeout must be <class 'int'>")
|
||||
|
||||
@@ -125,7 +139,7 @@ class NetFH(Thread):
|
||||
|
||||
:param bytecode: Antwort, die geprueft werden solll
|
||||
"""
|
||||
if bytecode == b'\x18':
|
||||
if bytecode == b"\x18":
|
||||
# Alles beenden, wenn nicht erlaubt
|
||||
self.__sockend.set()
|
||||
self.__sockerr.set()
|
||||
@@ -174,7 +188,7 @@ class NetFH(Thread):
|
||||
buff_recv = bytearray(recv_len)
|
||||
while recv_len > 0:
|
||||
block = so.recv(recv_len)
|
||||
if block == b'':
|
||||
if block == b"":
|
||||
raise OSError("lost connection on hash receive")
|
||||
buff_recv += block
|
||||
recv_len -= len(block)
|
||||
@@ -183,18 +197,19 @@ class NetFH(Thread):
|
||||
if self.__pictory_h and buff_recv[:16] != self.__pictory_h:
|
||||
self.__config_changed = True
|
||||
self.close()
|
||||
raise ConfigChanged(
|
||||
"configuration on revolution pi was changed")
|
||||
raise ConfigChanged("configuration on revolution pi was changed")
|
||||
else:
|
||||
self.__pictory_h = buff_recv[:16]
|
||||
|
||||
# Änderung an replace_ios prüfen
|
||||
if self.__check_replace_ios and self.__replace_ios_h \
|
||||
and buff_recv[16:] != self.__replace_ios_h:
|
||||
if (
|
||||
self.__check_replace_ios
|
||||
and self.__replace_ios_h
|
||||
and buff_recv[16:] != self.__replace_ios_h
|
||||
):
|
||||
self.__config_changed = True
|
||||
self.close()
|
||||
raise ConfigChanged(
|
||||
"configuration on revolution pi was changed")
|
||||
raise ConfigChanged("configuration on revolution pi was changed")
|
||||
else:
|
||||
self.__replace_ios_h = buff_recv[16:]
|
||||
except ConfigChanged:
|
||||
@@ -295,11 +310,18 @@ class NetFH(Thread):
|
||||
else:
|
||||
# Nur bestimmte Dirtybytes löschen
|
||||
# b CM ii xx c0000000 b = 16
|
||||
buff = self._direct_sr(pack(
|
||||
buff = self._direct_sr(
|
||||
pack(
|
||||
"=c2sH2xc7xc",
|
||||
HEADER_START, b'EY', position, b'\xfe', HEADER_STOP
|
||||
), 1)
|
||||
if buff != b'\x1e':
|
||||
HEADER_START,
|
||||
b"EY",
|
||||
position,
|
||||
b"\xfe",
|
||||
HEADER_STOP,
|
||||
),
|
||||
1,
|
||||
)
|
||||
if buff != b"\x1e":
|
||||
# ACL prüfen und ggf Fehler werfen
|
||||
self.__check_acl(buff)
|
||||
|
||||
@@ -343,12 +365,18 @@ class NetFH(Thread):
|
||||
|
||||
try:
|
||||
# b CM ii ii 00000000 b = 16
|
||||
buff = self._direct_sr(pack(
|
||||
buff = self._direct_sr(
|
||||
pack(
|
||||
"=c2sHH8xc",
|
||||
HEADER_START,
|
||||
b'FD', self.__int_buff, len(self.__by_buff),
|
||||
HEADER_STOP
|
||||
) + self.__by_buff, 1)
|
||||
b"FD",
|
||||
self.__int_buff,
|
||||
len(self.__by_buff),
|
||||
HEADER_STOP,
|
||||
)
|
||||
+ self.__by_buff,
|
||||
1,
|
||||
)
|
||||
except Exception:
|
||||
raise
|
||||
finally:
|
||||
@@ -356,7 +384,7 @@ class NetFH(Thread):
|
||||
self.__int_buff = 0
|
||||
self.__by_buff.clear()
|
||||
|
||||
if buff != b'\x1e':
|
||||
if buff != b"\x1e":
|
||||
# ACL prüfen und ggf Fehler werfen
|
||||
self.__check_acl(buff)
|
||||
|
||||
@@ -403,7 +431,7 @@ class NetFH(Thread):
|
||||
"""
|
||||
return int(self.__timeout * 1000)
|
||||
|
||||
def ioctl(self, request: int, arg=b'') -> None:
|
||||
def ioctl(self, request: int, arg=b"") -> None:
|
||||
"""
|
||||
IOCTL Befehle ueber das Netzwerk senden.
|
||||
|
||||
@@ -419,11 +447,10 @@ class NetFH(Thread):
|
||||
raise TypeError("arg must be <class 'bytes'>")
|
||||
|
||||
# b CM xx ii iiii0000 b = 16
|
||||
buff = self._direct_sr(pack(
|
||||
"=c2s2xHI4xc",
|
||||
HEADER_START, b'IC', len(arg), request, HEADER_STOP
|
||||
) + arg, 1)
|
||||
if buff != b'\x1e':
|
||||
buff = self._direct_sr(
|
||||
pack("=c2s2xHI4xc", HEADER_START, b"IC", len(arg), request, HEADER_STOP) + arg, 1
|
||||
)
|
||||
if buff != b"\x1e":
|
||||
# ACL prüfen und ggf Fehler werfen
|
||||
self.__check_acl(buff)
|
||||
|
||||
@@ -443,10 +470,9 @@ class NetFH(Thread):
|
||||
raise ValueError("read of closed file")
|
||||
|
||||
# b CM ii ii 00000000 b = 16
|
||||
buff = self._direct_sr(pack(
|
||||
"=c2sHH8xc",
|
||||
HEADER_START, b'DA', self.__position, length, HEADER_STOP
|
||||
), length)
|
||||
buff = self._direct_sr(
|
||||
pack("=c2sHH8xc", HEADER_START, b"DA", self.__position, length, HEADER_STOP), length
|
||||
)
|
||||
|
||||
self.__position += length
|
||||
return buff
|
||||
@@ -466,10 +492,9 @@ class NetFH(Thread):
|
||||
length = len(buffer)
|
||||
|
||||
# b CM ii ii 00000000 b = 16
|
||||
buff = self._direct_sr(pack(
|
||||
"=c2sHH8xc",
|
||||
HEADER_START, b'DA', self.__position, length, HEADER_STOP
|
||||
), length)
|
||||
buff = self._direct_sr(
|
||||
pack("=c2sHH8xc", HEADER_START, b"DA", self.__position, length, HEADER_STOP), length
|
||||
)
|
||||
|
||||
buffer[:] = buff
|
||||
return len(buffer)
|
||||
@@ -484,13 +509,11 @@ class NetFH(Thread):
|
||||
raise ValueError("read of closed file")
|
||||
|
||||
if self.__pictory_h == HASH_FAIL:
|
||||
raise RuntimeError(
|
||||
"could not read/parse piCtory configuration over network"
|
||||
)
|
||||
raise RuntimeError("could not read/parse piCtory configuration over network")
|
||||
|
||||
buff = self._direct_sr(_syspictory, 4)
|
||||
recv_length, = unpack("=I", buff)
|
||||
return self._direct_sr(b'', recv_length)
|
||||
(recv_length,) = unpack("=I", buff)
|
||||
return self._direct_sr(b"", recv_length)
|
||||
|
||||
def readreplaceio(self) -> bytes:
|
||||
"""
|
||||
@@ -502,27 +525,21 @@ class NetFH(Thread):
|
||||
raise ValueError("read of closed file")
|
||||
|
||||
if self.__replace_ios_h == HASH_FAIL:
|
||||
raise RuntimeError(
|
||||
"replace_io_file: could not read/parse over network"
|
||||
)
|
||||
raise RuntimeError("replace_io_file: could not read/parse over network")
|
||||
|
||||
buff = self._direct_sr(_sysreplaceio, 4)
|
||||
recv_length, = unpack("=I", buff)
|
||||
return self._direct_sr(b'', recv_length)
|
||||
(recv_length,) = unpack("=I", buff)
|
||||
return self._direct_sr(b"", recv_length)
|
||||
|
||||
def run(self) -> None:
|
||||
"""Handler fuer Synchronisierung."""
|
||||
state_reconnect = False
|
||||
while not self.__sockend.is_set():
|
||||
|
||||
# Bei Fehlermeldung neu verbinden
|
||||
if self.__sockerr.is_set():
|
||||
if not state_reconnect:
|
||||
state_reconnect = True
|
||||
warnings.warn(
|
||||
"got a network error and try to reconnect",
|
||||
RuntimeWarning
|
||||
)
|
||||
warnings.warn("got a network error and try to reconnect", RuntimeWarning)
|
||||
self._connect()
|
||||
if self.__sockerr.is_set():
|
||||
# Verhindert beim Scheitern 100% CPU last
|
||||
@@ -530,10 +547,7 @@ class NetFH(Thread):
|
||||
continue
|
||||
else:
|
||||
state_reconnect = False
|
||||
warnings.warn(
|
||||
"successfully reconnected after network error",
|
||||
RuntimeWarning
|
||||
)
|
||||
warnings.warn("successfully reconnected after network error", RuntimeWarning)
|
||||
|
||||
# Kein Fehler aufgetreten, sync durchführen wenn socket frei
|
||||
if self.__socklock.acquire(blocking=False):
|
||||
@@ -543,9 +557,7 @@ class NetFH(Thread):
|
||||
self.__buff_recv.clear()
|
||||
recv_lenght = 2
|
||||
while recv_lenght > 0:
|
||||
count = self._serversock.recv_into(
|
||||
self.__buff_block, recv_lenght
|
||||
)
|
||||
count = self._serversock.recv_into(self.__buff_block, recv_lenght)
|
||||
if count == 0:
|
||||
raise IOError("lost network connection on sync")
|
||||
self.__buff_recv += self.__buff_block[:count]
|
||||
@@ -554,11 +566,8 @@ class NetFH(Thread):
|
||||
except IOError:
|
||||
self.__sockerr.set()
|
||||
else:
|
||||
if self.__buff_recv != b'\x06\x16':
|
||||
warnings.warn(
|
||||
"data error on network sync",
|
||||
RuntimeWarning
|
||||
)
|
||||
if self.__buff_recv != b"\x06\x16":
|
||||
warnings.warn("data error on network sync", RuntimeWarning)
|
||||
self.__sockerr.set()
|
||||
continue
|
||||
finally:
|
||||
@@ -596,12 +605,13 @@ class NetFH(Thread):
|
||||
|
||||
try:
|
||||
# b CM ii ii 00000000 b = 16
|
||||
buff = self._direct_sr(pack(
|
||||
"=c2sHH8xc",
|
||||
HEADER_START, b'EY', position, len(dirtybytes), HEADER_STOP
|
||||
) + dirtybytes, 1)
|
||||
buff = self._direct_sr(
|
||||
pack("=c2sHH8xc", HEADER_START, b"EY", position, len(dirtybytes), HEADER_STOP)
|
||||
+ dirtybytes,
|
||||
1,
|
||||
)
|
||||
|
||||
if buff != b'\x1e':
|
||||
if buff != b"\x1e":
|
||||
# ACL prüfen und ggf Fehler werfen
|
||||
self.__check_acl(buff)
|
||||
|
||||
@@ -627,11 +637,8 @@ class NetFH(Thread):
|
||||
|
||||
try:
|
||||
# b CM ii xx 00000000 b = 16
|
||||
buff = self._direct_sr(pack(
|
||||
"=c2sH10xc",
|
||||
HEADER_START, b'CF', value, HEADER_STOP
|
||||
), 1)
|
||||
if buff != b'\x1e':
|
||||
buff = self._direct_sr(pack("=c2sH10xc", HEADER_START, b"CF", value, HEADER_STOP), 1)
|
||||
if buff != b"\x1e":
|
||||
raise IOError("set timeout error on network")
|
||||
except Exception:
|
||||
self.__sockerr.set()
|
||||
@@ -666,10 +673,11 @@ class NetFH(Thread):
|
||||
self.__int_buff += 1
|
||||
|
||||
# Datenblock mit Position und Länge in Puffer ablegen
|
||||
self.__by_buff += \
|
||||
self.__position.to_bytes(length=2, byteorder="little") \
|
||||
+ len(bytebuff).to_bytes(length=2, byteorder="little") \
|
||||
self.__by_buff += (
|
||||
self.__position.to_bytes(length=2, byteorder="little")
|
||||
+ len(bytebuff).to_bytes(length=2, byteorder="little")
|
||||
+ bytebuff
|
||||
)
|
||||
|
||||
# TODO: Bufferlänge und dann flushen?
|
||||
|
||||
@@ -697,9 +705,16 @@ class RevPiNetIO(_RevPiModIO):
|
||||
__slots__ = "_address"
|
||||
|
||||
def __init__(
|
||||
self, address, autorefresh=False, monitoring=False,
|
||||
syncoutputs=True, simulator=False, debug=True,
|
||||
replace_io_file=None, shared_procimg=False, direct_output=False):
|
||||
self,
|
||||
address,
|
||||
autorefresh=False,
|
||||
monitoring=False,
|
||||
syncoutputs=True,
|
||||
simulator=False,
|
||||
debug=True,
|
||||
replace_io_file=None,
|
||||
shared_procimg=False,
|
||||
):
|
||||
"""
|
||||
Instantiiert die Grundfunktionen.
|
||||
|
||||
@@ -712,30 +727,21 @@ class RevPiNetIO(_RevPiModIO):
|
||||
:param replace_io_file: Replace IO Konfiguration aus Datei laden
|
||||
:param shared_procimg: Share process image with other processes, this
|
||||
could be insecure for automation
|
||||
:param direct_output: Deprecated, use shared_procimg
|
||||
"""
|
||||
check_ip = compile(
|
||||
r"^(25[0-5]|(2[0-4]|[01]?\d|)\d)"
|
||||
r"(\.(25[0-5]|(2[0-4]|[01]?\d|)\d)){3}$"
|
||||
)
|
||||
check_ip = compile(r"^(25[0-5]|(2[0-4]|[01]?\d|)\d)(\.(25[0-5]|(2[0-4]|[01]?\d|)\d)){3}$")
|
||||
|
||||
# Adresse verarbeiten
|
||||
if isinstance(address, str):
|
||||
self._address = (address, 55234)
|
||||
elif isinstance(address, tuple):
|
||||
if len(address) == 2 \
|
||||
and isinstance(address[0], str) \
|
||||
and isinstance(address[1], int):
|
||||
|
||||
if len(address) == 2 and isinstance(address[0], str) and isinstance(address[1], int):
|
||||
# Werte prüfen
|
||||
if not 0 < address[1] <= 65535:
|
||||
raise ValueError("port number out of range 1 - 65535")
|
||||
|
||||
self._address = address
|
||||
else:
|
||||
raise TypeError(
|
||||
"address tuple must be (<class 'str'>, <class 'int'>)"
|
||||
)
|
||||
raise TypeError("address tuple must be (<class 'str'>, <class 'int'>)")
|
||||
else:
|
||||
raise TypeError(
|
||||
"parameter address must be <class 'str'> or <class 'tuple'> "
|
||||
@@ -749,8 +755,7 @@ class RevPiNetIO(_RevPiModIO):
|
||||
self._address = (ipv4, self._address[1])
|
||||
except Exception:
|
||||
raise ValueError(
|
||||
"can not resolve ip address for hostname '{0}'"
|
||||
"".format(self._address[0])
|
||||
"can not resolve ip address for hostname '{0}'".format(self._address[0])
|
||||
)
|
||||
|
||||
# Vererben
|
||||
@@ -764,7 +769,6 @@ class RevPiNetIO(_RevPiModIO):
|
||||
debug=debug,
|
||||
replace_io_file=replace_io_file,
|
||||
shared_procimg=shared_procimg,
|
||||
direct_output=direct_output,
|
||||
)
|
||||
self._set_device_based_cycle_time = False
|
||||
|
||||
@@ -801,10 +805,7 @@ class RevPiNetIO(_RevPiModIO):
|
||||
try:
|
||||
cp.read_string(byte_buff.decode("utf-8"))
|
||||
except Exception as e:
|
||||
raise RuntimeError(
|
||||
"replace_io_file: could not read/parse network data | {0}"
|
||||
"".format(e)
|
||||
)
|
||||
raise RuntimeError("replace_io_file: could not read/parse network data | {0}".format(e))
|
||||
return cp
|
||||
|
||||
def disconnect(self) -> None:
|
||||
@@ -862,16 +863,12 @@ class RevPiNetIO(_RevPiModIO):
|
||||
:param device: nur auf einzelnes Device anwenden, sonst auf Alle
|
||||
"""
|
||||
if self.monitoring:
|
||||
raise RuntimeError(
|
||||
"can not send default values, while system is in "
|
||||
"monitoring mode"
|
||||
)
|
||||
raise RuntimeError("can not send default values, while system is in monitoring mode")
|
||||
|
||||
if device is None:
|
||||
self._myfh.clear_dirtybytes()
|
||||
else:
|
||||
dev = device if isinstance(device, Device) \
|
||||
else self.device.__getitem__(device)
|
||||
dev = device if isinstance(device, Device) else self.device.__getitem__(device)
|
||||
mylist = [dev]
|
||||
|
||||
for dev in mylist:
|
||||
@@ -887,16 +884,12 @@ class RevPiNetIO(_RevPiModIO):
|
||||
:param device: nur auf einzelnes Device anwenden, sonst auf Alle
|
||||
"""
|
||||
if self.monitoring:
|
||||
raise RuntimeError(
|
||||
"can not send default values, while system is in "
|
||||
"monitoring mode"
|
||||
)
|
||||
raise RuntimeError("can not send default values, while system is in monitoring mode")
|
||||
|
||||
if device is None:
|
||||
mylist = self.device
|
||||
else:
|
||||
dev = device if isinstance(device, Device) \
|
||||
else self.device.__getitem__(device)
|
||||
dev = device if isinstance(device, Device) else self.device.__getitem__(device)
|
||||
mylist = [dev]
|
||||
|
||||
for dev in mylist:
|
||||
@@ -921,13 +914,10 @@ class RevPiNetIO(_RevPiModIO):
|
||||
int_byte += 1 if bitio._defaultvalue else 0
|
||||
|
||||
# Errechneten Int-Wert in ein Byte umwandeln
|
||||
dirtybytes += \
|
||||
int_byte.to_bytes(length=1, byteorder="little")
|
||||
dirtybytes += int_byte.to_bytes(length=1, byteorder="little")
|
||||
|
||||
# Dirtybytes an PLC Server senden
|
||||
self._myfh.set_dirtybytes(
|
||||
dev._offset + dev._slc_out.start, dirtybytes
|
||||
)
|
||||
self._myfh.set_dirtybytes(dev._offset + dev._slc_out.start, dirtybytes)
|
||||
|
||||
config_changed = property(get_config_changed)
|
||||
reconnecting = property(get_reconnecting)
|
||||
@@ -946,9 +936,17 @@ class RevPiNetIOSelected(RevPiNetIO):
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(
|
||||
self, address, deviceselection, autorefresh=False,
|
||||
monitoring=False, syncoutputs=True, simulator=False, debug=True,
|
||||
replace_io_file=None, shared_procimg=False, direct_output=False):
|
||||
self,
|
||||
address,
|
||||
deviceselection,
|
||||
autorefresh=False,
|
||||
monitoring=False,
|
||||
syncoutputs=True,
|
||||
simulator=False,
|
||||
debug=True,
|
||||
replace_io_file=None,
|
||||
shared_procimg=False,
|
||||
):
|
||||
"""
|
||||
Instantiiert nur fuer angegebene Devices die Grundfunktionen.
|
||||
|
||||
@@ -961,8 +959,14 @@ class RevPiNetIOSelected(RevPiNetIO):
|
||||
:ref: :func:`RevPiNetIO.__init__()`
|
||||
"""
|
||||
super().__init__(
|
||||
address, autorefresh, monitoring, syncoutputs, simulator, debug,
|
||||
replace_io_file, shared_procimg, direct_output
|
||||
address,
|
||||
autorefresh,
|
||||
monitoring,
|
||||
syncoutputs,
|
||||
simulator,
|
||||
debug,
|
||||
replace_io_file,
|
||||
shared_procimg,
|
||||
)
|
||||
|
||||
if type(deviceselection) is not DevSelect:
|
||||
@@ -981,24 +985,19 @@ class RevPiNetIOSelected(RevPiNetIO):
|
||||
if len(self.device) == 0:
|
||||
if self._devselect.type:
|
||||
raise DeviceNotFoundError(
|
||||
"could not find ANY given {0} devices in config"
|
||||
"".format(self._devselect.type)
|
||||
"could not find ANY given {0} devices in config".format(self._devselect.type)
|
||||
)
|
||||
else:
|
||||
raise DeviceNotFoundError(
|
||||
"could not find ANY given devices in config"
|
||||
)
|
||||
elif not self._devselect.other_device_key \
|
||||
and len(self.device) != len(self._devselect.values):
|
||||
raise DeviceNotFoundError("could not find ANY given devices in config")
|
||||
elif not self._devselect.other_device_key and len(self.device) != len(
|
||||
self._devselect.values
|
||||
):
|
||||
if self._devselect.type:
|
||||
raise DeviceNotFoundError(
|
||||
"could not find ALL given {0} devices in config"
|
||||
"".format(self._devselect.type)
|
||||
"could not find ALL given {0} devices in config".format(self._devselect.type)
|
||||
)
|
||||
else:
|
||||
raise DeviceNotFoundError(
|
||||
"could not find ALL given devices in config"
|
||||
)
|
||||
raise DeviceNotFoundError("could not find ALL given devices in config")
|
||||
|
||||
|
||||
class RevPiNetIODriver(RevPiNetIOSelected):
|
||||
@@ -1014,9 +1013,15 @@ class RevPiNetIODriver(RevPiNetIOSelected):
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(
|
||||
self, address, virtdev, autorefresh=False,
|
||||
syncoutputs=True, debug=True, replace_io_file=None,
|
||||
shared_procimg=False, direct_output=False):
|
||||
self,
|
||||
address,
|
||||
virtdev,
|
||||
autorefresh=False,
|
||||
syncoutputs=True,
|
||||
debug=True,
|
||||
replace_io_file=None,
|
||||
shared_procimg=False,
|
||||
):
|
||||
"""
|
||||
Instantiiert die Grundfunktionen.
|
||||
|
||||
@@ -1032,13 +1037,19 @@ class RevPiNetIODriver(RevPiNetIOSelected):
|
||||
virtdev = (virtdev,)
|
||||
dev_select = DevSelect(DeviceType.VIRTUAL, "", virtdev)
|
||||
super().__init__(
|
||||
address, dev_select, autorefresh, False, syncoutputs, True, debug,
|
||||
replace_io_file, shared_procimg, direct_output
|
||||
address,
|
||||
dev_select,
|
||||
autorefresh,
|
||||
False,
|
||||
syncoutputs,
|
||||
True,
|
||||
debug,
|
||||
replace_io_file,
|
||||
shared_procimg,
|
||||
)
|
||||
|
||||
|
||||
def run_net_plc(
|
||||
address, func, cycletime=50, replace_io_file=None, debug=True):
|
||||
def run_net_plc(address, func, cycletime=50, replace_io_file=None, debug=True):
|
||||
"""
|
||||
Run Revoluton Pi as real plc with cycle loop and exclusive IO access.
|
||||
|
||||
|
||||
@@ -13,7 +13,12 @@ __license__ = "LGPLv2"
|
||||
# - RevPiConCan_20180425_1_0.rap
|
||||
# - RevPiGateCANopen_20161102_1_0.rap
|
||||
|
||||
|
||||
class ProductType:
|
||||
CON_BT = 111
|
||||
CON_CAN = 109
|
||||
CON_MBUS = 110
|
||||
|
||||
GATEWAY_CAN_OPEN = 71
|
||||
GATEWAY_CCLINK = 72
|
||||
GATEWAY_DEV_NET = 73
|
||||
@@ -38,6 +43,7 @@ class ProductType:
|
||||
DO = 98
|
||||
AIO = 103
|
||||
MIO = 118
|
||||
RO = 137
|
||||
|
||||
REVPI_CORE = 95
|
||||
REVPI_COMPACT = 104
|
||||
@@ -45,9 +51,22 @@ class ProductType:
|
||||
REVPI_FLAT = 135
|
||||
REVPI_CONNECT_4 = 136
|
||||
|
||||
VIRTUAL_CLOUD = 24584
|
||||
VIRTUAL_MODBUS_TCP_SERVER = 24577
|
||||
VIRTUAL_MODBUS_RTU_SERVER = 24578
|
||||
VIRTUAL_MODBUS_TCP_CLIENT = 24579
|
||||
VIRTUAL_MODBUS_RTU_CLIENT = 24580
|
||||
VIRTUAL_OPCUA_SERVER = 23001
|
||||
VIRTUAL_REVPI_SEVEN = 24583
|
||||
VIRTUAL_PN_CONTROLLER = 24581
|
||||
VIRTUAL_PN_DEVICE = 24582
|
||||
VIRTUAL_TIMER = 28673
|
||||
VIRTUAL_RAW = 32768
|
||||
|
||||
|
||||
class DeviceType:
|
||||
"""Module key "type" in piCtory file."""
|
||||
|
||||
IGNORED = ""
|
||||
BASE = "BASE" # Core devices
|
||||
EDGE = "EDGE" # Gateways
|
||||
@@ -58,6 +77,7 @@ class DeviceType:
|
||||
|
||||
class AIO:
|
||||
"""Memory value mappings for RevPi AIO 1.0 (RevPiAIO_20170301_1_0.rap)."""
|
||||
|
||||
OUT_RANGE_OFF = 0 # Off
|
||||
OUT_RANGE_0_5V = 1 # 0 - 5V
|
||||
OUT_RANGE_0_10V = 2 # 0 - 10V
|
||||
@@ -131,6 +151,7 @@ class AIO:
|
||||
|
||||
class DI:
|
||||
"""Memory value mappings for RevPi DI 1.0 (RevPiDI_20160818_1_0.rap)."""
|
||||
|
||||
IN_MODE_DIRECT = 0 # Direct
|
||||
IN_MODE_COUNT_RISING = 1 # Counter, rising edge
|
||||
IN_MODE_COUNT_FALLING = 2 # Counter, falling edge
|
||||
@@ -144,6 +165,7 @@ class DI:
|
||||
|
||||
class DO:
|
||||
"""Memory value mappings for RevPi DO 1.0 (RevPiDO_20160818_1_0.rap)."""
|
||||
|
||||
OUT_PWM_FREQ_40HZ = 1 # 40Hz 1%
|
||||
OUT_PWM_FREQ_80HZ = 2 # 80Hz 2%
|
||||
OUT_PWM_FREQ_160HZ = 4 # 160Hz 4%
|
||||
@@ -153,11 +175,13 @@ class DO:
|
||||
|
||||
class DIO(DI, DO):
|
||||
"""Memory value mappings for RevPi DIO 1.0 (RevPiDIO_20160818_1_0.rap)."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class MIO:
|
||||
"""Memory value mappings for RevPi MIO 1.0 (RevPiMIO_20200901_1_0.rap)."""
|
||||
|
||||
ENCODER_MODE_DISABLED = 0
|
||||
ENCODER_MODE_ENABLED = 1
|
||||
|
||||
@@ -176,6 +200,7 @@ class MIO:
|
||||
|
||||
class COMPACT:
|
||||
"""Memory value mappings for RevPi Compact 1.0 (RevPiCompact_20171023_1_0.rap)."""
|
||||
|
||||
DIN_DEBOUNCE_OFF = 0 # Off
|
||||
DIN_DEBOUNCE_25US = 1 # 25us
|
||||
DIN_DEBOUNCE_750US = 2 # 750us
|
||||
@@ -189,5 +214,6 @@ class COMPACT:
|
||||
|
||||
class FLAT:
|
||||
"""Memory value mappings for RevPi Flat 1.0 (RevPiFlat_20200921_1_0.rap)."""
|
||||
|
||||
IN_RANGE_0_10V = 0
|
||||
IN_RANGE_4_20MA = 1
|
||||
|
||||
14
tests/test_import.py
Normal file
14
tests/test_import.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Test module import."""
|
||||
# SPDX-FileCopyrightText: 2023 Sven Sager
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class ModuleImport(unittest.TestCase):
|
||||
def test_import(self):
|
||||
"""Test the import of the module."""
|
||||
import revpimodio2
|
||||
|
||||
self.assertEqual(type(revpimodio2.__version__), str)
|
||||
Reference in New Issue
Block a user