mirror of
https://github.com/BOSWatch/BW3-Core.git
synced 2026-01-10 18:49:59 +01:00
Merge branch 'develop' into docs_test
This commit is contained in:
commit
33cc717a22
32
.github/workflows/run_pytest.yml
vendored
Normal file
32
.github/workflows/run_pytest.yml
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
name: pytest
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
strategy:
|
||||
max-parallel: 3
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
python-version: [3.5, 3.6, 3.7]
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up Python ${{matrix.python-version}} at ${{matrix.os}}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{matrix.python-version}}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
mkdir log/
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pytest -c 'test/pytest.ini'
|
||||
- name: Save artifacts
|
||||
uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: test.log
|
||||
path: log/test.log
|
||||
16
.gitignore
vendored
16
.gitignore
vendored
|
|
@ -6,12 +6,14 @@
|
|||
# German BOS Information Script
|
||||
# by Bastian Schroll
|
||||
|
||||
# virtual environment
|
||||
\venv/
|
||||
|
||||
# generated files
|
||||
stats_*
|
||||
log/
|
||||
_docu/
|
||||
_bin/win/zadig/
|
||||
_bin/win/rtl_sdr/
|
||||
_bin/win/multimon_1_1_1/
|
||||
_bin/win/kalibrate/
|
||||
docu/docs/api/html
|
||||
docu/site/
|
||||
|
||||
# Logo Photoshop file
|
||||
*.psd
|
||||
|
|
@ -22,6 +24,10 @@ _bin/win/kalibrate/
|
|||
# Python precompiled
|
||||
*.pyc
|
||||
|
||||
# Visual Studio Code
|
||||
\.vscode/
|
||||
\.pytest_cache/
|
||||
|
||||
# PyCharm IDE
|
||||
\.cache/
|
||||
\.idea/
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,19 +0,0 @@
|
|||
@echo off
|
||||
|
||||
echo " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
|
||||
echo " ____ ____ ______ __ __ __ _____ "
|
||||
echo " / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ / "
|
||||
echo " / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ < "
|
||||
echo " / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ / "
|
||||
echo " /_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/ "
|
||||
echo " German BOS Information Script "
|
||||
echo " by Bastian Schroll "
|
||||
echo " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
|
||||
echo.
|
||||
echo Run 'count lines of code' ...
|
||||
echo.
|
||||
cd ..
|
||||
_bin\win\cloc_1_72\cloc-1.72.exe . --exclude-lang=XML --exclude-dir=_docu,_config,_info,doxygen.ini --by-file-by-lang
|
||||
echo.
|
||||
echo.
|
||||
pause
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
@echo off
|
||||
|
||||
echo " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
|
||||
echo " ____ ____ ______ __ __ __ _____ "
|
||||
echo " / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ / "
|
||||
echo " / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ < "
|
||||
echo " / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ / "
|
||||
echo " /_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/ "
|
||||
echo " German BOS Information Script "
|
||||
echo " by Bastian Schroll "
|
||||
echo " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
|
||||
echo.
|
||||
echo Build Doxygen Documentation
|
||||
echo.
|
||||
cd ..
|
||||
_bin\win\doxygen\doxygen.exe _gen/doxygen.ini
|
||||
echo.
|
||||
echo.
|
||||
pause
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
@echo off
|
||||
|
||||
cd ..
|
||||
if not exist "log/" mkdir log
|
||||
|
||||
echo " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
|
||||
echo " ____ ____ ______ __ __ __ _____ "
|
||||
echo " / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ / "
|
||||
echo " / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ < "
|
||||
echo " / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ / "
|
||||
echo " /_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/ "
|
||||
echo " German BOS Information Script "
|
||||
echo " by Bastian Schroll "
|
||||
echo " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
|
||||
echo.
|
||||
echo BOSWatch 3 Unittest framework
|
||||
echo.
|
||||
echo Which test? [ENTER] for all
|
||||
echo Or name a specific test test_[###].py
|
||||
echo.
|
||||
|
||||
set /p test=Testcase:
|
||||
if "%test%" == "" (
|
||||
goto start
|
||||
) else (
|
||||
goto start_spec
|
||||
)
|
||||
|
||||
:start
|
||||
echo.
|
||||
python -m pytest -c "_gen/pytest.ini"
|
||||
echo.
|
||||
echo --- Hit any key to repeat ---
|
||||
pause
|
||||
cls
|
||||
goto start
|
||||
|
||||
:start_spec
|
||||
echo.
|
||||
python -m pytest test/test_%test%.py -c "_gen/pytest.ini"
|
||||
echo.
|
||||
echo --- Hit any key to repeat ---
|
||||
pause
|
||||
cls
|
||||
goto start_spec
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
## Generatoren
|
||||
Im Verzeichnis `_gen/` befinden sich Windows Batch-Scripte um bestimmt Aufgaben zu
|
||||
vereinfachen und zu automatisieren.
|
||||
|
||||
---
|
||||
|
||||
### cloc.bat
|
||||
`CLoC` steht für Count Lines of Code
|
||||
|
||||
Dieses Tool erstellt einen kurzen Report über die Anzahl der Dateien, Codezeilen,
|
||||
Kommentare und Leerzeilen sortiert nach jeweiliger Programmiersprache
|
||||
|
||||
---
|
||||
|
||||
### doxygen.bat
|
||||
Doxygen ist ein Dokumentationswerkzeug zum automatischen dokumentieren von Quellcode.
|
||||
|
||||
Nach dem ausführen wird der gesamte Quellcode geparst und eine HTML Dokumentation
|
||||
im Verzeichnis `_docu/html/` angelegt.
|
||||
|
||||
Die Konfigurations Datei für Doxygen findet sich unter `_gen/doxygen.ini`
|
||||
|
||||
---
|
||||
|
||||
### pytest.bat
|
||||
pytest ist ein Python Testframework
|
||||
|
||||
Damit ist es möglich, automatisierte Tests laufen zu lassen.
|
||||
Vorher müssen die benötigten Plugins welche unter `_info/requirements.txt` gelistet sind
|
||||
mittels `pip` installiert werden.
|
||||
|
||||
Nach dem Start kann man mit einem Druck auf `[ENTER]` direkt alle bekannten Tests
|
||||
laufen lassen, oder durch die Eingabe eines spezifischen Tests auch nur diesen
|
||||
ausführen. Die Tests werden dabei in zufälliger Reihenfolge abgearbeitet um auch
|
||||
Fehler durch Abhängigkeiten voneinander zu erfassen.
|
||||
|
||||
Die Testfälle befinden sich im Unterordner `test/`
|
||||
Einzeltests werden durch Eingabe des Namens ohne `test_` und `.py` aufgerufen.
|
||||
Zum Beispiel `decoder` statt `test_decoder.py`
|
||||
|
||||
Zusätzlich werden alle Quellcode Dateien mittels eines PEP8 Parsers auf die Einhaltung
|
||||
der Code-Sytel Vorgaben von Python hin untersucht und etwaige Fehler ausgegeben.
|
||||
|
||||
Vom Testverlauf wird ein Logfile erstellt welches in `log/test.log` befindet.
|
||||
|
||||
Die Konfigurationsdatei für pytest findet sich unter `_gen/pytest.ini`
|
||||
117
_info/LICENSE
117
_info/LICENSE
|
|
@ -1,117 +0,0 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
The precise terms and conditions for copying, distribution and modification follow.
|
||||
|
||||
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".
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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 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 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) 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.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, 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 this License, whose permissions for other licensees extend to the 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.
|
||||
|
||||
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 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:
|
||||
|
||||
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,
|
||||
|
||||
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,
|
||||
|
||||
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 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. 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 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 all its terms and conditions for copying, distributing or modifying the Program 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 restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.
|
||||
|
||||
7. 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 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.
|
||||
|
||||
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 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 system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot 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 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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
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 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 an idea of what it does. Copyright (C) yyyy 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 program 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.
|
||||
|
||||
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. 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 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.
|
||||
|
||||
signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
Facts:
|
||||
Dokumentation mittels Doxygen
|
||||
Unittests mittels pytest (zufällige Reihenfolge durch pytest-randomly)
|
||||
Codeabdeckung mittels pytest-cov
|
||||
PEP8 Design Kontrolle mittels pytest-pep8
|
||||
Logging mittels eigener Config-Datei einstellbar
|
||||
Multi-Threading Server
|
||||
|
||||
Fuktionen:
|
||||
Server- Client Anwendung
|
||||
Verarbeitung von FMS, POCSAG und ZVEI
|
||||
Komfortables Plugin System
|
||||
Beschreibung aus CSV File
|
||||
Konfigurationsdateien Modulweit teilbar über "Sharepoints"
|
||||
Alarm-Daten Übermittlung per definierten bwPacket-Paket
|
||||
Plugin Statistiken sowie Ausführungszeiten
|
||||
Plugin Priorisierung
|
||||
Funktionaler und dynamischer Pluginloader
|
||||
Automatische Wildcard-Ersetzung in Plugins
|
||||
Verschiedene Filterfunktionen (doubleFilter, )
|
||||
289
_info/packet.md
289
_info/packet.md
|
|
@ -1,289 +0,0 @@
|
|||
## Format of the BOSWatch packets
|
||||
<table>
|
||||
<tr>
|
||||
<td>field</td>
|
||||
<td>fms</td>
|
||||
<td>pocsag</td>
|
||||
<td>zvei</td>
|
||||
<td>msg</td>
|
||||
<td>wildcard</td>
|
||||
<td>description</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>serverName</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>{SNAME}</td>
|
||||
<td>name of the boswatch server instance</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>serverVersion</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>{SVERS}</td>
|
||||
<td>in case of new version, server can notify</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>serverBuildDate</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>{SDATE}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>serverBranch</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>{SBRCH}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>clientName</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>{CNAME}</td>
|
||||
<td>name of the boswatch client instance</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>clientIP</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>{CIP}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>clientVersion</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>{CVERS}</td>
|
||||
<td>in case of new version, server can notify</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>clientBuildDate</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>{CDATE}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>clientBranch</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>{CBRCH}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>inputSource</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>{INSRC}</td>
|
||||
<td>(stick, audio)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>timestamp</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>{TIMES}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>frequency</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>{FREQ}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>mode</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>{MODE}</td>
|
||||
<td>(fms, pocsag, zvei, msg)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>descriptionShort</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td>{DESCS}</td>
|
||||
<td>loaded from optional CSV file</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>descriptionLong</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td>{DESCL}</td>
|
||||
<td>loaded from optional CSV file</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>bitrate</td>
|
||||
<td></td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{BIT}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ric</td>
|
||||
<td></td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{RIC}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>subric</td>
|
||||
<td></td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{SRIC}</td>
|
||||
<td>(1, 2, 3, 4)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>subricText</td>
|
||||
<td></td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{SRICT}</td>
|
||||
<td>(a, b, c, d)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>message</td>
|
||||
<td></td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td>X</td>
|
||||
<td>{MSG}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>tone</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td>{TONE}</td>
|
||||
<td>5-tone sequence</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>fms</td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{FMS}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>service</td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{SERV}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>country</td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{COUNT}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>location</td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{LOC}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>vehicle</td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{VEHC}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>status</td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{STAT}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>direction</td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{DIR}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>dirextionText</td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{DIRT}</td>
|
||||
<td>(Fhz->Lst, Lst->Fhz)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>tacticalInfo</td>
|
||||
<td>X</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{TACI}</td>
|
||||
<td>(I, II, III, IV)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br><br>
|
||||
### Other possible wildcards:
|
||||
- {BR} - Line break (\\r\\n)
|
||||
- {LPAR} - Left parenthesis (
|
||||
- {RPAR} - Right parenthesis )
|
||||
- {TIME} - Actual timestamp
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: config.py
|
||||
@date: 25.12.2017
|
||||
@author: Bastian Schroll
|
||||
@description: Module for the configuration
|
||||
"""
|
||||
import logging
|
||||
import configparser
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class Config:
|
||||
|
||||
_sharePoints = {}
|
||||
|
||||
def __init__(self):
|
||||
"""!Create a new config object and load the ini file directly"""
|
||||
self._config = configparser.ConfigParser()
|
||||
|
||||
def loadConfigFile(self, configPath, sharePoint=""):
|
||||
"""!loads a given configuration in the class wide config variable
|
||||
|
||||
@param configPath: Path to the config file
|
||||
@param sharePoint: If you want to share the config set name here
|
||||
@return True or False"""
|
||||
logging.debug("load config file from: %s", configPath)
|
||||
try:
|
||||
self._config.read(configPath, "utf-8")
|
||||
if sharePoint:
|
||||
self._shareConfig(sharePoint)
|
||||
return True
|
||||
except: # pragma: no cover
|
||||
logging.exception("cannot load config file")
|
||||
return False
|
||||
|
||||
def _shareConfig(self, sharePoint):
|
||||
"""!Shares the configuration
|
||||
|
||||
Shares the local _config to the class wide global _sharedConfig
|
||||
@param sharePoint: Name of the global share point
|
||||
@return True or False"""
|
||||
if sharePoint in self._sharePoints:
|
||||
logging.error("cannot share config - name is always in use: %s", sharePoint)
|
||||
return False
|
||||
else:
|
||||
self._sharePoints[sharePoint] = self._config
|
||||
logging.debug("add config sharePoint: %s", sharePoint)
|
||||
return True
|
||||
|
||||
def getInt(self, section, key, sharePoint=""):
|
||||
"""!Method to read a single config entry as integer
|
||||
|
||||
@param section: Section to read from
|
||||
@param key: Value to read
|
||||
@param sharePoint: Name of the global config share (empty is only local)
|
||||
@return An Integer or None"""
|
||||
value = self._get(section, key, sharePoint)
|
||||
if value is None:
|
||||
return None
|
||||
return int(value)
|
||||
|
||||
def getBool(self, section, key, sharePoint=""):
|
||||
"""!Method to read a single config entry as boolean
|
||||
|
||||
@param section: Section to read from
|
||||
@param key: Value to read
|
||||
@param sharePoint: Name of the global config share (empty is only local)
|
||||
@return True or False"""
|
||||
if self._get(section, key, sharePoint).lower() in ["1", "true", "yes"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def getStr(self, section, key, sharePoint=""):
|
||||
"""!Method to read a single config entry as string
|
||||
|
||||
@param section: Section to read from
|
||||
@param key: Value to read
|
||||
@param sharePoint: Name of the global config share (empty is only local)
|
||||
@return The value or None"""
|
||||
value = self._get(section, key, sharePoint)
|
||||
if value is None:
|
||||
return None
|
||||
return str(value)
|
||||
|
||||
def _get(self, section, key, sharePoint=""):
|
||||
"""!Method to read a single config entry
|
||||
|
||||
@param section: Section to read from
|
||||
@param key: Value to read
|
||||
@param sharePoint: Name of the global config share (empty is only local)
|
||||
@return The value or None"""
|
||||
if sharePoint:
|
||||
try:
|
||||
return self._sharePoints[sharePoint].get(section, key)
|
||||
except KeyError:
|
||||
logging.error("no sharePoint named: %s", sharePoint)
|
||||
except configparser.NoSectionError:
|
||||
logging.warning("no shared config section: %s", section)
|
||||
except configparser.NoOptionError:
|
||||
logging.warning("no shared config option: %s", key)
|
||||
except: # pragma: no cover
|
||||
logging.exception("error while reading shared config")
|
||||
return None
|
||||
|
||||
else:
|
||||
try:
|
||||
return self._config.get(section, key)
|
||||
except configparser.NoSectionError:
|
||||
logging.warning("no local config section: %s", section)
|
||||
except configparser.NoOptionError:
|
||||
logging.warning("no local config option: %s", key)
|
||||
except: # pragma: no cover
|
||||
logging.exception("error while reading local config")
|
||||
return None
|
||||
|
||||
def getAllSharepoints(self):
|
||||
"""!Return a python dict of all set sharepoints
|
||||
|
||||
@return Sharepoint dict"""
|
||||
return self._sharePoints
|
||||
77
boswatch/configYaml.py
Normal file
77
boswatch/configYaml.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: configYaml.py
|
||||
@date: 27.02.2019
|
||||
@author: Bastian Schroll
|
||||
@description: Module for the configuration in YAML format
|
||||
"""
|
||||
import logging
|
||||
import yaml
|
||||
import yaml.parser
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class ConfigYAML:
|
||||
|
||||
def __init__(self, config=None):
|
||||
self._config = config
|
||||
|
||||
def __iter__(self):
|
||||
for item in self._config:
|
||||
if type(item) is list or type(item) is dict:
|
||||
yield ConfigYAML(item)
|
||||
else:
|
||||
yield item
|
||||
|
||||
def __len__(self):
|
||||
"""!returns the length of an config element"""
|
||||
return len(self._config)
|
||||
|
||||
def __str__(self):
|
||||
"""!Returns the string representation of the internal config dict"""
|
||||
return str(self._config)
|
||||
|
||||
def loadConfigFile(self, configPath):
|
||||
"""!loads a given configuration file
|
||||
|
||||
@param configPath: Path to the config file
|
||||
@return True or False"""
|
||||
logging.debug("load config file from: %s", configPath)
|
||||
try:
|
||||
with open(configPath) as file:
|
||||
# use safe_load instead load
|
||||
self._config = yaml.safe_load(file)
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
logging.error("config file not found: %s", configPath)
|
||||
except yaml.parser.ParserError:
|
||||
logging.exception("syntax error in config file: %s", configPath)
|
||||
return False
|
||||
|
||||
def get(self, *args, default=None):
|
||||
"""!Get a single value from the config
|
||||
or a value set in a new configYAML class instance
|
||||
|
||||
@param *args: Config section (one ore more strings)
|
||||
@param default: Default value if section not found (None)
|
||||
@return: A single value, a value set in an configYAML instance, the default value"""
|
||||
tmp = self._config
|
||||
try:
|
||||
for arg in args:
|
||||
tmp = tmp.get(arg, default)
|
||||
if type(tmp) is list or type(tmp) is dict:
|
||||
return ConfigYAML(tmp)
|
||||
else:
|
||||
return tmp
|
||||
except AttributeError: # pragma: no cover
|
||||
return default
|
||||
|
|
@ -16,9 +16,9 @@
|
|||
"""
|
||||
import logging
|
||||
|
||||
from boswatch.decoder.fmsdecoder import FmsDecoder
|
||||
from boswatch.decoder.pocsagdecoder import PocsagDecoder
|
||||
from boswatch.decoder.zveidecoder import ZveiDecoder
|
||||
from boswatch.decoder.fmsDecoder import FmsDecoder
|
||||
from boswatch.decoder.pocsagDecoder import PocsagDecoder
|
||||
from boswatch.decoder.zveiDecoder import ZveiDecoder
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
|
@ -32,6 +32,7 @@ class Decoder:
|
|||
@param data: data to decode
|
||||
@return bwPacket instance"""
|
||||
logging.debug("search decoder")
|
||||
data = str(data)
|
||||
if "FMS" in data:
|
||||
return FmsDecoder.decode(data)
|
||||
elif "POCSAG" in data:
|
||||
|
|
@ -39,5 +40,5 @@ class Decoder:
|
|||
elif "ZVEI" in data:
|
||||
return ZveiDecoder.decode(data)
|
||||
else:
|
||||
logging.error("no decoder found for: %s", data)
|
||||
logging.warning("no decoder found for: %s", data)
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
from boswatch.packet import packet
|
||||
from boswatch.packet import Packet
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ class FmsDecoder:
|
|||
if re.search("[0-9a-f]{8}[0-9a-f][01]", fms_id):
|
||||
logging.debug("found valid FMS")
|
||||
|
||||
bwPacket = packet.Packet()
|
||||
bwPacket = Packet()
|
||||
bwPacket.set("mode", "fms")
|
||||
bwPacket.set("fms", fms_id)
|
||||
bwPacket.set("service", service)
|
||||
|
|
@ -62,7 +62,6 @@ class FmsDecoder:
|
|||
bwPacket.set("directionText", directionText)
|
||||
bwPacket.set("tacticalInfo", tacticalInfo)
|
||||
|
||||
logging.debug(bwPacket)
|
||||
return bwPacket
|
||||
|
||||
logging.warning("no valid data")
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
from boswatch.packet import packet
|
||||
from boswatch.packet import Packet
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ class PocsagDecoder:
|
|||
|
||||
logging.debug("found valid POCSAG")
|
||||
|
||||
bwPacket = packet.Packet()
|
||||
bwPacket = Packet()
|
||||
bwPacket.set("mode", "pocsag")
|
||||
bwPacket.set("bitrate", bitrate)
|
||||
bwPacket.set("ric", ric)
|
||||
|
|
@ -56,7 +56,6 @@ class PocsagDecoder:
|
|||
bwPacket.set("subricText", subricText)
|
||||
bwPacket.set("message", message)
|
||||
|
||||
logging.debug(bwPacket)
|
||||
return bwPacket
|
||||
|
||||
logging.warning("no valid data")
|
||||
|
|
@ -70,20 +69,20 @@ class PocsagDecoder:
|
|||
@return bitrate
|
||||
@return ric
|
||||
@return subric"""
|
||||
bitrate, ric, subric = 0, 0, 0
|
||||
bitrate, ric, subric = "0", "0", "0"
|
||||
|
||||
if "POCSAG512:" in data:
|
||||
bitrate = 512
|
||||
bitrate = "512"
|
||||
ric = data[20:27].replace(" ", "").zfill(7)
|
||||
subric = str(int(data[39]) + 1)
|
||||
|
||||
elif "POCSAG1200:" in data:
|
||||
bitrate = 1200
|
||||
bitrate = "1200"
|
||||
ric = data[21:28].replace(" ", "").zfill(7)
|
||||
subric = str(int(data[40]) + 1)
|
||||
|
||||
elif "POCSAG2400:" in data:
|
||||
bitrate = 2400
|
||||
bitrate = "2400"
|
||||
ric = data[21:28].replace(" ", "").zfill(7)
|
||||
subric = str(int(data[40]) + 1)
|
||||
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
from boswatch.packet import packet
|
||||
from boswatch.packet import Packet
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
|
@ -40,11 +40,10 @@ class ZveiDecoder:
|
|||
if re.search("[0-9E]{5}", data[7:12]):
|
||||
logging.debug("found valid ZVEI")
|
||||
|
||||
bwPacket = packet.Packet()
|
||||
bwPacket = Packet()
|
||||
bwPacket.set("mode", "zvei")
|
||||
bwPacket.set("zvei", ZveiDecoder._solveDoubleTone(data[7:12]))
|
||||
|
||||
logging.debug(bwPacket)
|
||||
return bwPacket
|
||||
|
||||
logging.warning("no valid data")
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: descriptor.py
|
||||
@date: 07.01.2018
|
||||
@author: Bastian Schroll
|
||||
@description: Descriptor to load Descriptions from csv files
|
||||
"""
|
||||
import logging
|
||||
import csv
|
||||
import re
|
||||
|
||||
from boswatch.utils import paths
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class Descriptor:
|
||||
"""!CSV Descriptor class to load specific descriptions from CSV files, manage and serve them"""
|
||||
|
||||
def __init__(self):
|
||||
"""!Initialise a private list for the DescriptionList objects"""
|
||||
self._lists = {}
|
||||
|
||||
def loadDescription(self, csvType):
|
||||
"""!Build a new description list from DescriptionList class
|
||||
|
||||
@param csvType: Name of the CSV file without `.csv`
|
||||
@return True or False"""
|
||||
bwDescriptionList = DescriptionList()
|
||||
if bwDescriptionList.loadCSV(csvType):
|
||||
self._lists[csvType] = bwDescriptionList
|
||||
return True
|
||||
return False
|
||||
|
||||
def addDescriptions(self, bwPacket):
|
||||
"""!Add the short and long description to a bwPacket
|
||||
|
||||
@param bwPacket: bwPacket instance to add descriptions
|
||||
@return True or False"""
|
||||
logging.debug("add descriptions to bwPacket")
|
||||
try:
|
||||
bwPacket.set("shortDescription",
|
||||
self._lists[bwPacket.get("mode")].getShortDescription(bwPacket.get(bwPacket.get("mode"))))
|
||||
|
||||
bwPacket.set("longDescription",
|
||||
self._lists[bwPacket.get("mode")].getLongDescription(bwPacket.get(bwPacket.get("mode"))))
|
||||
return True
|
||||
except: # pragma: no cover
|
||||
logging.exception("error while adding descriptions")
|
||||
return False
|
||||
|
||||
|
||||
class DescriptionList:
|
||||
def __init__(self):
|
||||
"""!Loads the given CSV file into internal list"""
|
||||
logging.debug("create new descriptionList")
|
||||
self._descriptionList = {}
|
||||
|
||||
def getShortDescription(self, checkId):
|
||||
"""!Returns the short description of given id
|
||||
|
||||
@return short description or empty string"""
|
||||
try:
|
||||
return self._descriptionList[str(checkId)]["shortDescription"]
|
||||
except:
|
||||
return ""
|
||||
|
||||
def getLongDescription(self, checkId):
|
||||
"""!Returns the long description of given id
|
||||
|
||||
@return long description or empty string"""
|
||||
try:
|
||||
return self._descriptionList[str(checkId)]["longDescription"]
|
||||
except:
|
||||
return ""
|
||||
|
||||
def loadCSV(self, csvType):
|
||||
"""!Load descriptions from an csv file
|
||||
|
||||
@param csvType: Name of the CSV file without `.csv`
|
||||
@return True or False"""
|
||||
count = 0
|
||||
logging.debug("loading csv file: %s", csvType)
|
||||
csvPath = paths.CSV_PATH + csvType + ".csv"
|
||||
try:
|
||||
csvFile = open(csvPath, 'r', -1, 'utf-8')
|
||||
reader = csv.DictReader(csvFile)
|
||||
|
||||
for line in reader:
|
||||
if re.match("^[0-9]+[A-D]?$", line["id"], re.IGNORECASE):
|
||||
self._descriptionList[line["id"]] = {"shortDescription": line["shortDescription"], "longDescription": line["longDescription"]}
|
||||
logging.debug("- %s", line)
|
||||
count += 1
|
||||
|
||||
logging.debug("%s entry's loaded", count)
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
logging.error("csv file not found: %s", csvPath)
|
||||
return False
|
||||
except: # pragma: no cover
|
||||
logging.exception("error while loading descriptions")
|
||||
return False
|
||||
167
boswatch/network/broadcast.py
Normal file
167
boswatch/network/broadcast.py
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: broadcast.py
|
||||
@date: 21.09.2018
|
||||
@author: Bastian Schroll
|
||||
@description: UDP broadcast server and client class
|
||||
"""
|
||||
import logging
|
||||
import socket
|
||||
import threading
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class BroadcastClient:
|
||||
"""!BroadcastClient class"""
|
||||
|
||||
def __init__(self, port=5000):
|
||||
"""!Create an BroadcastClient instance
|
||||
|
||||
@param port: port to send broadcast packets (5000)"""
|
||||
self._broadcastPort = port
|
||||
|
||||
self._serverIP = ""
|
||||
self._serverPort = 0
|
||||
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
self._socket.settimeout(3)
|
||||
|
||||
def getConnInfo(self, retry=0):
|
||||
"""!Get the connection info from server over udp broadcast
|
||||
|
||||
This function will send broadcast package(s)
|
||||
to get connection info from the server.
|
||||
|
||||
- send the magic packet <BW-Request> on broadcast address.
|
||||
- wait for a <BW-Result> magic packet.
|
||||
- extract the connection data from the magic packet and return
|
||||
|
||||
@param retry: Count of retry - 0 is infinite (0)
|
||||
|
||||
@return True or False"""
|
||||
sendPackages = 0
|
||||
while sendPackages < retry or retry == 0:
|
||||
try:
|
||||
logging.debug("send magic <BW3-Request> as broadcast - Try: %d", sendPackages)
|
||||
self._socket.sendto("<BW3-Request>".encode(), ('255.255.255.255', self._broadcastPort))
|
||||
sendPackages += 1
|
||||
payload, address = self._socket.recvfrom(1024)
|
||||
payload = str(payload, "UTF-8")
|
||||
|
||||
if payload.startswith("<BW3-Result>"):
|
||||
logging.debug("received magic <BW3-Result> from: %s", address[0])
|
||||
self._serverIP = address[0]
|
||||
self._serverPort = int(payload.split(";")[1])
|
||||
logging.info("got connection info from server: %s:%d", self._serverIP, self._serverPort)
|
||||
return True
|
||||
except socket.timeout: # nothing received - retry
|
||||
logging.debug("no magic packet received")
|
||||
logging.warning("cannot fetch connection info after %d tries", sendPackages)
|
||||
return False
|
||||
|
||||
@property
|
||||
def serverIP(self):
|
||||
"""!Property to get the server IP after successful broadcast"""
|
||||
return self._serverIP
|
||||
|
||||
@property
|
||||
def serverPort(self):
|
||||
"""!Property to get the server Port after successful broadcast"""
|
||||
return self._serverPort
|
||||
|
||||
|
||||
class BroadcastServer:
|
||||
"""!BroadcastServer class"""
|
||||
|
||||
def __init__(self, servePort=8080, listenPort=5000):
|
||||
"""!Create an BroadcastServer instance
|
||||
|
||||
@param servePort: port to serve as connection info (8080)
|
||||
@param listenPort: port to listen for broadcast packets (5000)"""
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
self._socket.settimeout(2)
|
||||
self._socket.bind(('', listenPort))
|
||||
self._serverThread = None
|
||||
self._serverShutdown = False
|
||||
self._servePort = servePort
|
||||
|
||||
def __del__(self): # pragma: no cover
|
||||
if self.isRunning:
|
||||
self.stop()
|
||||
while self.isRunning:
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
"""!Start the broadcast server in a new thread
|
||||
|
||||
@return True or False"""
|
||||
if not self.isRunning:
|
||||
logging.debug("start udp broadcast server")
|
||||
self._serverThread = threading.Thread(target=self._listen)
|
||||
self._serverThread.name = "BroadServ"
|
||||
self._serverThread.daemon = True
|
||||
self._serverShutdown = False
|
||||
self._serverThread.start()
|
||||
return True
|
||||
logging.warning("udp broadcast server always started")
|
||||
return True
|
||||
|
||||
def stop(self):
|
||||
"""!Stop the broadcast server
|
||||
|
||||
Due to the timeout of the socket,
|
||||
stopping the thread can be delayed by two seconds.
|
||||
But function returns immediately.
|
||||
|
||||
@return True or False"""
|
||||
|
||||
if self.isRunning:
|
||||
logging.debug("stop udp broadcast server")
|
||||
self._serverShutdown = True
|
||||
return True
|
||||
else:
|
||||
logging.warning("udp broadcast server always stopped")
|
||||
return True
|
||||
|
||||
def _listen(self):
|
||||
"""!Broadcast server worker thread
|
||||
|
||||
This function listen for magic packets on broadcast
|
||||
address and send the connection info to the clients.
|
||||
|
||||
- listen for the magic packet <BW-Request>
|
||||
- send connection info in an <BW-Result> macig packet"""
|
||||
logging.debug("start listening for magic")
|
||||
while not self._serverShutdown:
|
||||
try:
|
||||
payload, address = self._socket.recvfrom(1024)
|
||||
payload = str(payload, "UTF-8")
|
||||
if payload == "<BW3-Request>":
|
||||
logging.debug("received magic <BW3-Request> from: %s", address[0])
|
||||
logging.info("send connection info in magic <BW3-Result> to: %s", address[0])
|
||||
self._socket.sendto("<BW3-Result>;".encode() + str(self._servePort).encode(), address)
|
||||
except socket.timeout:
|
||||
continue # timeout is accepted (not block at recvfrom())
|
||||
self._serverThread = None
|
||||
logging.debug("udp broadcast server stopped")
|
||||
|
||||
@property
|
||||
def isRunning(self):
|
||||
"""!Property of broadcast server running state"""
|
||||
if self._serverThread:
|
||||
return True
|
||||
return False
|
||||
|
|
@ -16,9 +16,12 @@
|
|||
"""
|
||||
import logging
|
||||
import socket
|
||||
import select
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
HEADERSIZE = 10
|
||||
|
||||
|
||||
class TCPClient:
|
||||
"""!TCP client class"""
|
||||
|
|
@ -26,51 +29,43 @@ class TCPClient:
|
|||
def __init__(self, timeout=3):
|
||||
"""!Create a new instance
|
||||
|
||||
Create a new instance of an TCP Client.
|
||||
And set the timeout"""
|
||||
try:
|
||||
self._sock = None
|
||||
self._timeout = timeout
|
||||
except: # pragma: no cover
|
||||
logging.exception("cannot create a TCPClient")
|
||||
@param timeout: timeout for the client in sec. (3)"""
|
||||
socket.setdefaulttimeout(timeout)
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
def connect(self, host="localhost", port=8080):
|
||||
"""!Connect to the server
|
||||
|
||||
@param host: Server IP address (localhost)
|
||||
@param host: Server IP address ("localhost")
|
||||
@param port: Server Port (8080)
|
||||
@return True or False"""
|
||||
try:
|
||||
self._sock = socket
|
||||
self._sock.setdefaulttimeout(self._timeout)
|
||||
self._sock = socket.create_connection((host, port))
|
||||
|
||||
logging.debug("connected to %s:%s", host, port)
|
||||
if not self.isConnected:
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._sock.connect((host, port))
|
||||
logging.debug("connected to %s:%s", host, port)
|
||||
return True
|
||||
logging.warning("client always connected")
|
||||
return True
|
||||
except ConnectionRefusedError:
|
||||
logging.error("cannot connect to %s:%s - connection refused", host, port)
|
||||
return False
|
||||
except socket.timeout: # pragma: no cover
|
||||
logging.warning("cannot connect to %s:%s - timeout after %s sec", host, port, self._timeout)
|
||||
return False
|
||||
except: # pragma: no cover
|
||||
logging.exception("cannot connect to %s:%s", host, port)
|
||||
return False
|
||||
except socket.error as e:
|
||||
logging.error(e)
|
||||
return False
|
||||
|
||||
def disconnect(self):
|
||||
"""!Disconnect from the server
|
||||
|
||||
@return True or False"""
|
||||
try:
|
||||
self._sock.close()
|
||||
logging.debug("disconnected")
|
||||
if self.isConnected:
|
||||
self._sock.shutdown(socket.SHUT_RDWR)
|
||||
self._sock.close()
|
||||
logging.debug("disconnected")
|
||||
return True
|
||||
logging.warning("client always disconnected")
|
||||
return True
|
||||
except AttributeError:
|
||||
logging.error("cannot disconnect - no connection established")
|
||||
return False
|
||||
except: # pragma: no cover
|
||||
logging.exception("error while disconnecting")
|
||||
return False
|
||||
except socket.error as e:
|
||||
logging.error(e)
|
||||
return False
|
||||
|
||||
def transmit(self, data):
|
||||
"""!Send a data packet to the server
|
||||
|
|
@ -78,37 +73,55 @@ class TCPClient:
|
|||
@param data: data to send to the server
|
||||
@return True or False"""
|
||||
try:
|
||||
logging.debug("transmitting: %s", data)
|
||||
self._sock.sendall(bytes(data + "\n", "utf-8"))
|
||||
logging.debug("transmitting:\n%s", data)
|
||||
data = data.encode("utf-8")
|
||||
header = str(len(data)).ljust(HEADERSIZE).encode("utf-8")
|
||||
self._sock.sendall(header + data)
|
||||
logging.debug("transmitted...")
|
||||
return True
|
||||
except AttributeError:
|
||||
logging.error("cannot transmit - no connection established")
|
||||
return False
|
||||
except ConnectionResetError:
|
||||
logging.error("cannot transmit - host closed connection")
|
||||
return False
|
||||
except: # pragma: no cover
|
||||
logging.exception("error while transmitting")
|
||||
return False
|
||||
except socket.error as e:
|
||||
logging.error(e)
|
||||
return False
|
||||
|
||||
def receive(self):
|
||||
def receive(self, timeout=1):
|
||||
"""!Receive data from the server
|
||||
|
||||
@param: timeout to wait for incoming data in seconds
|
||||
@return received data"""
|
||||
try:
|
||||
received = str(self._sock.recv(1024), "utf-8")
|
||||
logging.debug("received: %d", received)
|
||||
read, _, _ = select.select([self._sock], [], [], timeout)
|
||||
if not read: # check if there is something to read
|
||||
return False
|
||||
|
||||
header = self._sock.recv(HEADERSIZE).decode("utf-8")
|
||||
if not len(header): # check if there data
|
||||
return False
|
||||
|
||||
length = int(header.strip())
|
||||
received = self._sock.recv(length).decode("utf-8")
|
||||
|
||||
logging.debug("recv header: '%s'", header)
|
||||
logging.debug("received %d bytes: %s", len(received), received)
|
||||
return received
|
||||
except AttributeError:
|
||||
logging.error("cannot receive - no connection established")
|
||||
except socket.error as e:
|
||||
logging.error(e)
|
||||
return False
|
||||
|
||||
@property
|
||||
def isConnected(self):
|
||||
"""!Property of client connected state"""
|
||||
try:
|
||||
if self._sock:
|
||||
_, write, _ = select.select([], [self._sock], [], 0.1)
|
||||
if write:
|
||||
data = "<keep-alive>".encode("utf-8")
|
||||
header = str(len(data)).ljust(HEADERSIZE).encode("utf-8")
|
||||
self._sock.sendall(header + data)
|
||||
return True
|
||||
return False
|
||||
except ConnectionResetError:
|
||||
logging.error("cannot receive - host closed connection")
|
||||
except socket.error as e:
|
||||
if e.errno != 32:
|
||||
logging.exception(e)
|
||||
return False
|
||||
except socket.timeout: # pragma: no cover
|
||||
logging.warning("cannot receive - timeout after %s sec", self._timeout)
|
||||
return False
|
||||
except: # pragma: no cover
|
||||
logging.exception("error while receiving")
|
||||
except ValueError:
|
||||
return False
|
||||
|
|
|
|||
48
boswatch/network/netCheck.py
Normal file
48
boswatch/network/netCheck.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: netCheck.py
|
||||
@date: 21.09.2018
|
||||
@author: Bastian Schroll
|
||||
@description: Worker to check internet connection
|
||||
"""
|
||||
import logging
|
||||
from urllib.request import urlopen
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class NetCheck:
|
||||
"""!Worker class to check internet connection"""
|
||||
|
||||
def __init__(self, hostname="https://www.google.com/", timeout=1):
|
||||
"""!Create a new NetCheck instance
|
||||
|
||||
@param hostname: host against connection check is running ("https://www.google.com/")
|
||||
@param timout: timout for connection check in sec. (1)"""
|
||||
self._hostname = hostname
|
||||
self._timeout = timeout
|
||||
self.connectionState = False
|
||||
self.checkConn() # initiate a first check
|
||||
|
||||
def checkConn(self):
|
||||
"""!Check the connection
|
||||
|
||||
@return True or False"""
|
||||
try:
|
||||
urlopen(self._hostname, timeout=self._timeout)
|
||||
logging.debug("%s is reachable", self._hostname)
|
||||
self.connectionState = True
|
||||
return True
|
||||
except: # todo find right exception type
|
||||
logging.warning("%s is not reachable", self._hostname)
|
||||
self.connectionState = False
|
||||
return False
|
||||
|
|
@ -15,70 +15,94 @@
|
|||
@description: Class implementation for a threaded TCP socket server
|
||||
"""
|
||||
import logging
|
||||
import socket
|
||||
import socketserver
|
||||
import threading
|
||||
import time
|
||||
import select
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
# module wide global list for received data sets
|
||||
_dataPackets = []
|
||||
_lockDataPackets = threading.Lock()
|
||||
|
||||
# module wide global list for all currently connected clients
|
||||
_clients = {} # _clients[ThreadName] = {"address", "timestamp"}
|
||||
_lockClients = threading.Lock()
|
||||
HEADERSIZE = 10
|
||||
|
||||
|
||||
class TCPHandler(socketserver.BaseRequestHandler):
|
||||
"""!RequestHandler class for our TCPServer class."""
|
||||
class _ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
|
||||
"""!ThreadedTCPRequestHandler class for our TCPServer class."""
|
||||
|
||||
def handle(self):
|
||||
"""!Handles the request from an single client in a own thread
|
||||
|
||||
Insert a request in the clients[] list and send a [ack]"""
|
||||
with _lockClients:
|
||||
_clients[threading.current_thread().name] = {"address": self.client_address[0], "timestamp": time.time()}
|
||||
with self.server.clientsConnectedLock: # because our list is not threadsafe
|
||||
self.server.clientsConnected[threading.current_thread().name] = {"address": self.client_address[0], "timestamp": time.time()}
|
||||
|
||||
logging.info("Client connected: %s", self.client_address[0])
|
||||
data = 1 # to enter while loop
|
||||
cur_thread = threading.current_thread().name
|
||||
req_name = str(cur_thread) + " " + self.client_address[0]
|
||||
|
||||
try:
|
||||
while data:
|
||||
data = str(self.request.recv(1024).strip(), 'utf-8')
|
||||
if data != "":
|
||||
logging.debug("%s recv: %s", req_name, data)
|
||||
while self.server.isActive:
|
||||
read, _, _ = select.select([self.request], [], [], 0.5)
|
||||
if not read:
|
||||
continue # nothing to read on the socket
|
||||
|
||||
# add a new entry at first position (index 0) with client IP
|
||||
# and the decoded data dict as an string in utf-8 and an timestamp
|
||||
with _lockDataPackets:
|
||||
_dataPackets.insert(0, (self.client_address[0], data, time.time())) # time() to calc time in queue
|
||||
logging.debug("Add data to queue")
|
||||
header = self.request.recv(HEADERSIZE).decode("utf-8")
|
||||
if not len(header):
|
||||
break # empty data -> socked closed
|
||||
|
||||
logging.debug("%s send: [ack]", req_name)
|
||||
self.request.sendall(bytes("[ack]", "utf-8"))
|
||||
self.request.close()
|
||||
length = int(header.strip())
|
||||
data = self.request.recv(length).decode("utf-8")
|
||||
|
||||
except (ConnectionResetError, ConnectionAbortedError): # pragma: no cover
|
||||
logging.debug("%s connection closed", req_name)
|
||||
except: # pragma: no cover
|
||||
logging.exception("%s error while receiving", req_name)
|
||||
if data == "<keep-alive>":
|
||||
continue
|
||||
|
||||
logging.debug("%s recv header: '%s'", req_name, header)
|
||||
logging.debug("%s recv %d bytes:\n%s", req_name, len(data), data)
|
||||
|
||||
# add a new entry and the decoded data dict as an string in utf-8 and an timestamp
|
||||
self.server.alarmQueue.put_nowait((self.client_address[0], data, time.time())) # queue is threadsafe
|
||||
logging.debug("Add data to queue")
|
||||
|
||||
logging.debug("%s send: [ack]", req_name)
|
||||
|
||||
data = "[ack]".encode("utf-8")
|
||||
header = str(len(data)).ljust(HEADERSIZE).encode("utf-8")
|
||||
self.request.sendall(header + data)
|
||||
|
||||
except socket.error as e:
|
||||
logging.error(e)
|
||||
return False
|
||||
finally:
|
||||
with _lockClients:
|
||||
del _clients[threading.current_thread().name]
|
||||
self.request.close()
|
||||
del self.server.clientsConnected[threading.current_thread().name]
|
||||
logging.info("Client disconnected: %s", self.client_address[0])
|
||||
|
||||
|
||||
class TCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
class _ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
"""!ThreadedTCPServer class for our TCPServer class."""
|
||||
pass
|
||||
|
||||
|
||||
class TCPServer:
|
||||
"""!TCP server class"""
|
||||
|
||||
def __init__(self, timeout=3):
|
||||
"""!Create a new instance"""
|
||||
def __init__(self, alarmQueue, timeout=3):
|
||||
"""!Create a new instance
|
||||
|
||||
@param alarmQueue: python queue instance
|
||||
@param timeout: server timeout in sec (3)
|
||||
"""
|
||||
self._server = None
|
||||
self._server_thread = None
|
||||
self._timeout = timeout
|
||||
self._alarmQueue = alarmQueue
|
||||
|
||||
self._clientsConnectedLock = threading.Lock()
|
||||
self._clientsConnected = {}
|
||||
|
||||
def __del__(self):
|
||||
if self.isRunning:
|
||||
self.stop()
|
||||
|
||||
def start(self, port=8080):
|
||||
"""!Start a threaded TCP socket server
|
||||
|
|
@ -90,78 +114,66 @@ class TCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
|||
@param port: Server Port (8080)
|
||||
|
||||
@return True or False"""
|
||||
try:
|
||||
self._server = socketserver.ThreadingTCPServer(("", port), TCPHandler)
|
||||
self._server.timeout = self._timeout
|
||||
if not self.isRunning:
|
||||
try:
|
||||
socketserver.TCPServer.allow_reuse_address = True # because we can start two instances on same port elsewhere
|
||||
self._server = _ThreadedTCPServer(("", port), _ThreadedTCPRequestHandler)
|
||||
self._server.timeout = self._timeout
|
||||
self._server.alarmQueue = self._alarmQueue
|
||||
self._server.isActive = True
|
||||
|
||||
self.flushQueue()
|
||||
self._server.clientsConnectedLock = self._clientsConnectedLock
|
||||
self._server.clientsConnected = self._clientsConnected
|
||||
|
||||
self._server_thread = threading.Thread(target=self._server.serve_forever)
|
||||
self._server_thread.name = "Thread-BWServer"
|
||||
self._server_thread.daemon = True
|
||||
self._server_thread.start()
|
||||
logging.debug("TCPServer started in Thread: %s", self._server_thread.name)
|
||||
self._server_thread = threading.Thread(target=self._server.serve_forever)
|
||||
self._server_thread.name = "Thread-BWServer"
|
||||
self._server_thread.daemon = True
|
||||
self._server_thread.start()
|
||||
logging.debug("TCPServer started in Thread: %s", self._server_thread.name)
|
||||
return True
|
||||
except socket.error as e:
|
||||
logging.error(e)
|
||||
return False
|
||||
else:
|
||||
logging.warning("server always started")
|
||||
return True
|
||||
except OSError:
|
||||
logging.exception("server always running?")
|
||||
except: # pragma: no cover
|
||||
logging.exception("cannot start the server")
|
||||
return False
|
||||
|
||||
def stop(self):
|
||||
"""!Stops the TCP socket server
|
||||
|
||||
@return True or False"""
|
||||
try:
|
||||
if self.isRunning:
|
||||
self._server.shutdown()
|
||||
self._server.isActive = False
|
||||
self._server.server_close()
|
||||
self._server_thread.join()
|
||||
self._server.socket.close()
|
||||
self._server_thread = None
|
||||
self._server = None
|
||||
logging.debug("TCPServer stopped")
|
||||
return True
|
||||
except AttributeError:
|
||||
logging.exception("cannot stop - server not started?")
|
||||
return False
|
||||
except: # pragma: no cover
|
||||
logging.exception("cannot stop the server")
|
||||
return False
|
||||
logging.warning("server always stopped")
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def countClientsConnected():
|
||||
def countClientsConnected(self):
|
||||
"""!Number of currently connected Clients
|
||||
|
||||
@return Connected clients"""
|
||||
with _lockClients:
|
||||
return len(_clients)
|
||||
with self._clientsConnectedLock: # because our list is not threadsafe
|
||||
return len(self._clientsConnected)
|
||||
|
||||
@staticmethod
|
||||
def getClientsConnected():
|
||||
# todo insert comment
|
||||
def getClientsConnected(self):
|
||||
"""!A list of all connected clients
|
||||
with their IP address and last seen timestamp
|
||||
_clients[ThreadName] = {"address", "timestamp"}
|
||||
|
||||
@return List of onnected clients"""
|
||||
# todo return full list or write a print/debug method?
|
||||
return _clients
|
||||
with self._clientsConnectedLock: # because our list is not threadsafe
|
||||
return self._clientsConnected
|
||||
|
||||
@staticmethod
|
||||
def getDataFromQueue():
|
||||
"""!Function to get the data packages from server
|
||||
must be polled by main program
|
||||
|
||||
@return Next data packet.py from intern queue"""
|
||||
if _dataPackets:
|
||||
with _lockDataPackets:
|
||||
message = _dataPackets.pop()
|
||||
logging.debug("Get data from queue")
|
||||
return message
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def countPacketsInQueue():
|
||||
"""!Get packets waiting in queue
|
||||
|
||||
@return Packets in queue"""
|
||||
return len(_dataPackets) # no lock needed - only reading
|
||||
|
||||
@staticmethod
|
||||
def flushQueue():
|
||||
"""!To flush all existing data in queue"""
|
||||
logging.debug("Flush data queue")
|
||||
with _lockDataPackets:
|
||||
_dataPackets.clear()
|
||||
@property
|
||||
def isRunning(self):
|
||||
"""!Property of server running state"""
|
||||
if self._server:
|
||||
return True
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@
|
|||
"""
|
||||
import logging
|
||||
import time
|
||||
from boswatch.config import Config
|
||||
from boswatch import version
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
|
@ -34,11 +32,7 @@ class Packet:
|
|||
self._packet = {"timestamp": time.time()}
|
||||
else:
|
||||
logging.debug("create bwPacket from string")
|
||||
try:
|
||||
self._packet = eval(str(bwPacket.strip()))
|
||||
except: # pragma: no cover
|
||||
# todo can we repair the packet anyway?
|
||||
logging.exception("error while create packet from string")
|
||||
self._packet = eval(str(bwPacket.strip()))
|
||||
|
||||
def __str__(self):
|
||||
"""!Return the intern _packet dict as string"""
|
||||
|
|
@ -63,44 +57,9 @@ class Packet:
|
|||
logging.warning("field not found: %s", fieldName)
|
||||
return None
|
||||
|
||||
def addClientData(self):
|
||||
"""!Add the client information to the decoded data
|
||||
|
||||
This function adds the following data to the bwPacket:
|
||||
- clientName
|
||||
- clientVersion
|
||||
- clientBuildDate
|
||||
- clientBranch
|
||||
- inputSource
|
||||
- frequency"""
|
||||
config = Config()
|
||||
logging.debug("add client data to bwPacket")
|
||||
self.set("clientName", config.getStr("Client", "Name", "clientConfig"))
|
||||
self.set("clientVersion", version.client)
|
||||
self.set("clientBuildDate", version.date)
|
||||
self.set("clientBranch", version.branch)
|
||||
self.set("inputSource", config.getStr("Client", "InputSource", "clientConfig"))
|
||||
self.set("frequency", config.getStr("Stick", "Frequency", "clientConfig"))
|
||||
|
||||
def addServerData(self):
|
||||
"""!Add the server information to the decoded data
|
||||
|
||||
This function adds the following data to the bwPacket:
|
||||
- serverName
|
||||
- serverVersion
|
||||
- serverBuildDate
|
||||
- serverBranch"""
|
||||
config = Config()
|
||||
logging.debug("add server data to bwPacket")
|
||||
self.set("serverName", config.getStr("Server", "Name", "serverConfig"))
|
||||
self.set("serverVersion", version.server)
|
||||
self.set("serverBuildDate", version.date)
|
||||
self.set("serverBranch", version.branch)
|
||||
|
||||
def printInfo(self):
|
||||
"""!Print a info message to the log on INFO level.
|
||||
Contains the most useful info about this packet.
|
||||
@todo not complete yet - must be edit to print nice formatted messages on console
|
||||
|
||||
@param bwPacket: BOSWatch packet instance"""
|
||||
"""
|
||||
logging.info("[%s]", self.get("mode"))
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: pluginManager.py
|
||||
@date: 08.01.2018
|
||||
@author: Bastian Schroll
|
||||
@description: Plugin manager class to load and call the plugins
|
||||
@todo must be mostly refactored
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import importlib
|
||||
|
||||
from boswatch.config import Config
|
||||
from boswatch.utils import paths
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class PluginManager:
|
||||
"""!Plugin manager class to load, manage and call the plugins
|
||||
|
||||
@todo refactor the class and add documentation"""
|
||||
|
||||
def __init__(self):
|
||||
"""!init comment"""
|
||||
self._config = Config()
|
||||
self._pluginList = []
|
||||
|
||||
def searchPluginDir(self):
|
||||
logging.debug("search for plugins in: %s", paths.PLUGIN_PATH)
|
||||
for name in os.listdir(paths.PLUGIN_PATH):
|
||||
location = os.path.join(paths.PLUGIN_PATH, name)
|
||||
|
||||
# Skip if Path.isdir() or no File DIR_NAME.py is found
|
||||
if not os.path.isdir(location) or not name + ".py" in os.listdir(location):
|
||||
continue
|
||||
|
||||
pluginPriority = self._config.getInt("Plugins", name, "serverConfig")
|
||||
|
||||
if pluginPriority is None:
|
||||
logging.warning("no entry in server config for plugin: %s", name)
|
||||
continue
|
||||
elif pluginPriority > 0:
|
||||
self._pluginList.append({"pluginName": name, "pluginPriority": pluginPriority})
|
||||
logging.debug("[ENABLED ] %s [%3d]", name, pluginPriority)
|
||||
elif pluginPriority <= 0:
|
||||
logging.debug("[DISABLED] %s ", name)
|
||||
|
||||
# sort pluginList on pluginPriority descending (High to Low)
|
||||
self._pluginList.sort(key=lambda x: x['pluginPriority'], reverse=True)
|
||||
|
||||
def importAllPlugins(self):
|
||||
logging.debug("importing all plugins")
|
||||
for item in self._pluginList:
|
||||
importPlugin = self._importPlugin(item["pluginName"])
|
||||
if importPlugin:
|
||||
item["pluginImport"] = importPlugin
|
||||
|
||||
@staticmethod
|
||||
def _importPlugin(pluginName):
|
||||
logging.debug("import plugin: %s", pluginName)
|
||||
try:
|
||||
return importlib.import_module("plugins." + pluginName + "." + pluginName)
|
||||
except:
|
||||
logging.exception("error while loading plugin: %s", pluginName)
|
||||
return False
|
||||
|
||||
def loadAllPlugins(self):
|
||||
logging.debug("loading all plugins")
|
||||
for item in self._pluginList:
|
||||
item["pluginObject"] = None # todo del or none ???
|
||||
item["pluginObject"] = item["pluginImport"].BoswatchPlugin()
|
||||
|
||||
def runAllPlugins(self, bwPacket):
|
||||
logging.info("ALARM - %0.3f sec. since radio reception", time.time() - bwPacket.get("timestamp"))
|
||||
for item in self._pluginList:
|
||||
item["pluginObject"]._run(bwPacket)
|
||||
item["pluginStatistics"] = item["pluginObject"]._getStatistics()
|
||||
self.printEndStats()
|
||||
|
||||
def unloadAllPlugins(self):
|
||||
logging.debug("unload all plugins")
|
||||
for item in self._pluginList:
|
||||
# todo del or None ???
|
||||
del item["pluginObject"] # delete plugin object to force __del__() running
|
||||
|
||||
def printEndStats(self):
|
||||
logging.debug("Plugin run statistics:")
|
||||
logging.debug("Plugin | runs | tRUN | tCUM | tSET | tALA | tTRD | eSET | eALA | eTRD")
|
||||
for item in self._pluginList:
|
||||
logging.debug("- %-12s | %4d | %0.2f | %6.1f | %0.2f | %0.2f | %0.2f | %4d | %4d | %4d",
|
||||
item["pluginName"],
|
||||
item["pluginStatistics"]["runCount"],
|
||||
item["pluginStatistics"]["sumTime"],
|
||||
item["pluginStatistics"]["cumTime"],
|
||||
item["pluginStatistics"]["setupTime"],
|
||||
item["pluginStatistics"]["alarmTime"],
|
||||
item["pluginStatistics"]["teardownTime"],
|
||||
item["pluginStatistics"]["setupErrorCount"],
|
||||
item["pluginStatistics"]["alarmErrorCount"],
|
||||
item["pluginStatistics"]["teardownErrorCount"])
|
||||
144
boswatch/processManager.py
Normal file
144
boswatch/processManager.py
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: processManager.py
|
||||
@date: 04.03.2018
|
||||
@author: Bastian Schroll
|
||||
@description: Class for managing sub processes
|
||||
"""
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class ProcessManager:
|
||||
"""!class to manage a extern sub process"""
|
||||
def __init__(self, process, textMode=False):
|
||||
logging.debug("create process instance %s - textMode: %s", process, textMode)
|
||||
self._args = []
|
||||
self._args.append(process)
|
||||
self._stdin = None
|
||||
self._stdout = subprocess.PIPE
|
||||
self._stderr = subprocess.STDOUT
|
||||
self._processHandle = None
|
||||
self._textMode = textMode
|
||||
|
||||
def __del__(self):
|
||||
self.stop()
|
||||
|
||||
def addArgument(self, arg):
|
||||
"""!add a new argument
|
||||
|
||||
@param arg: argument to add as string"""
|
||||
logging.debug("add argument to process: %s -> %s", self._args[0], arg)
|
||||
for splitArg in arg.split():
|
||||
self._args.append(splitArg)
|
||||
|
||||
def clearArguments(self):
|
||||
"""!clear all arguments"""
|
||||
self._args = self._args[0:1] # kept first element (process name)
|
||||
|
||||
def start(self):
|
||||
"""!start the new process
|
||||
|
||||
@return: True or False"""
|
||||
logging.debug("start new process: %s %s", self._args[0], self._args[1:])
|
||||
try:
|
||||
self._processHandle = subprocess.Popen(self._args,
|
||||
stdin=self._stdin,
|
||||
stdout=self._stdout,
|
||||
stderr=self._stderr,
|
||||
universal_newlines=self._textMode,
|
||||
shell=False)
|
||||
if not self.isRunning:
|
||||
logging.error("cannot start process")
|
||||
return False
|
||||
logging.debug("process started with PID %d", self._processHandle.pid)
|
||||
return True
|
||||
|
||||
except FileNotFoundError:
|
||||
logging.error("File not found: %s", self._args[0])
|
||||
return False
|
||||
|
||||
def stop(self):
|
||||
"""!Stop the process by sending SIGTERM and wait for ending"""
|
||||
logging.debug("stopping process: %s", self._args[0])
|
||||
if self.isRunning:
|
||||
self._processHandle.terminate()
|
||||
while self.isRunning:
|
||||
pass
|
||||
logging.debug("process %s returned %d", self._args[0], self._processHandle.returncode)
|
||||
|
||||
def readline(self):
|
||||
"""!Read one line from stdout stream
|
||||
|
||||
@return singe line or None"""
|
||||
if self.isRunning and self._stdout is not None:
|
||||
try:
|
||||
line = self._processHandle.stdout.readline().strip()
|
||||
except UnicodeDecodeError:
|
||||
return None
|
||||
return line
|
||||
return None
|
||||
|
||||
def skipLines(self, lineCount=1):
|
||||
"""!Skip given number of lines from the output
|
||||
|
||||
@param lineCount: number of lines to skip
|
||||
"""
|
||||
logging.debug("skip %d lines from output", lineCount)
|
||||
while self.isRunning and lineCount:
|
||||
self.readline()
|
||||
lineCount -= 1
|
||||
|
||||
def skipLinesUntil(self, matchText):
|
||||
"""!Skip lines from the output until the given string is in it
|
||||
|
||||
@param matchText: string to search for in output
|
||||
"""
|
||||
logging.debug("skip lines till '%s' from output", matchText)
|
||||
if not self._textMode:
|
||||
matchText = bytes(matchText, "utf-8")
|
||||
while self.isRunning and matchText not in self.readline():
|
||||
pass
|
||||
|
||||
def setStdin(self, stdin):
|
||||
"""!Set the stdin stream instance"""
|
||||
self._stdin = stdin
|
||||
|
||||
def setStdout(self, stdout):
|
||||
"""!Set the stdout stream instance"""
|
||||
self._stdout = stdout
|
||||
|
||||
def setStderr(self, stderr):
|
||||
"""!Set the stderr stream instance"""
|
||||
self._stderr = stderr
|
||||
|
||||
@property
|
||||
def stdout(self):
|
||||
"""!Property to get the stdout stream"""
|
||||
return self._processHandle.stdout
|
||||
|
||||
@property
|
||||
def stderr(self):
|
||||
"""!Property to get the stderr stream"""
|
||||
return self._processHandle.stderr
|
||||
|
||||
@property
|
||||
def isRunning(self):
|
||||
"""!Property to get process running state
|
||||
|
||||
@return True or False"""
|
||||
if self._processHandle:
|
||||
if self._processHandle.poll() is None:
|
||||
return True
|
||||
return False
|
||||
36
boswatch/router/route.py
Normal file
36
boswatch/router/route.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: route.py
|
||||
@date: 04.03.2019
|
||||
@author: Bastian Schroll
|
||||
@description: Class for a single BOSWatch packet router route point
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class Route:
|
||||
"""!Class for single routing points"""
|
||||
def __init__(self, name, callback, statsCallback=None, cleanupCallback=None):
|
||||
"""!Create a instance of an route point
|
||||
|
||||
@param name: name of the route point
|
||||
@param callback: instance of the callback function
|
||||
@param statsCallback: instance of the callback to get statistics (None)
|
||||
@param cleanupCallback: instance of the callback to run a cleanup method (None)
|
||||
"""
|
||||
self.name = name
|
||||
self.callback = callback
|
||||
self.statistics = statsCallback
|
||||
self.cleanup = cleanupCallback
|
||||
89
boswatch/router/router.py
Normal file
89
boswatch/router/router.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: router.py
|
||||
@date: 01.03.2019
|
||||
@author: Bastian Schroll
|
||||
@description: Class for the BOSWatch packet router
|
||||
"""
|
||||
import logging
|
||||
import copy
|
||||
import time
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class Router:
|
||||
"""!Class for the Router"""
|
||||
def __init__(self, name):
|
||||
"""!Create a new router
|
||||
|
||||
@param name: name of the router"""
|
||||
self.name = name
|
||||
self.routeList = []
|
||||
|
||||
# for time counting
|
||||
self._cumTime = 0
|
||||
self._routerTime = 0
|
||||
|
||||
# for statistics
|
||||
self._runCount = 0
|
||||
|
||||
logging.debug("[%s] add new router", self.name)
|
||||
|
||||
def addRoute(self, route):
|
||||
"""!Adds a route point to the router
|
||||
|
||||
@param route: instance of the Route class
|
||||
"""
|
||||
logging.debug("[%s] add route: %s", self.name, route.name)
|
||||
self.routeList.append(route)
|
||||
|
||||
def runRouter(self, bwPacket):
|
||||
"""!Run the router
|
||||
|
||||
@param bwPacket: instance of Packet class
|
||||
@return a instance of Packet class
|
||||
"""
|
||||
self._runCount += 1
|
||||
tmpTime = time.time()
|
||||
|
||||
logging.debug("[%s] started", self.name)
|
||||
|
||||
for routeObject in self.routeList:
|
||||
logging.debug("[%s] -> run route: %s", self.name, routeObject.name)
|
||||
bwPacket_tmp = routeObject.callback(copy.deepcopy(bwPacket)) # copy bwPacket to prevent edit the original
|
||||
|
||||
if bwPacket_tmp is None: # returning None doesnt change the bwPacket
|
||||
continue
|
||||
|
||||
if bwPacket_tmp is False: # returning False stops the router immediately
|
||||
logging.debug("[%s] stopped", self.name)
|
||||
break
|
||||
|
||||
bwPacket = bwPacket_tmp
|
||||
logging.debug("[%s] bwPacket returned", self.name)
|
||||
logging.debug("[%s] finished", self.name)
|
||||
|
||||
self._routerTime = time.time() - tmpTime
|
||||
self._cumTime += self._routerTime
|
||||
|
||||
return bwPacket
|
||||
|
||||
def _getStatistics(self):
|
||||
"""!Returns statistical information's from last router run
|
||||
|
||||
@return Statistics as pyton dict"""
|
||||
stats = {"type": "router",
|
||||
"runCount": self._runCount,
|
||||
"cumTime": self._cumTime,
|
||||
"moduleTime": self._routerTime}
|
||||
return stats
|
||||
160
boswatch/router/routerManager.py
Normal file
160
boswatch/router/routerManager.py
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: routerManager.py
|
||||
@date: 04.03.2019
|
||||
@author: Bastian Schroll
|
||||
@description: Class for the BOSWatch packet router manager class
|
||||
"""
|
||||
|
||||
# todo think about implement threading for routers and the plugin calls (THREAD SAFETY!!!)
|
||||
import logging
|
||||
import importlib
|
||||
import time
|
||||
from boswatch.configYaml import ConfigYAML
|
||||
from boswatch.router.router import Router
|
||||
from boswatch.router.route import Route
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class RouterManager:
|
||||
"""!Class to manage all routers"""
|
||||
def __init__(self):
|
||||
"""!Create new router"""
|
||||
self._routerDict = {}
|
||||
self._startTime = int(time.time())
|
||||
|
||||
# if there is an error, router list would be empty (see tmp variable)
|
||||
def buildRouters(self, config):
|
||||
"""!Initialize Routers from given config file
|
||||
|
||||
@param config: instance of ConfigYaml class
|
||||
@return True or False"""
|
||||
self._routerDict = {} # all routers and instances of modules/plugins would be destroyed
|
||||
routerDict_tmp = {}
|
||||
logging.debug("build routers")
|
||||
|
||||
# first we have to init all routers
|
||||
# because a router can be a valid target and we need his reference
|
||||
for router in config.get("router"):
|
||||
if router.get("name") is None or router.get("route") is None:
|
||||
logging.error("name or route not found in router: %s", router)
|
||||
return False
|
||||
if router.get("name") in self._routerDict:
|
||||
logging.error("duplicated router name: %s", router.get("name"))
|
||||
return False
|
||||
routerDict_tmp[router.get("name")] = Router(router.get("name"))
|
||||
|
||||
for router in config.get("router"):
|
||||
routerName = router.get("name")
|
||||
|
||||
for route in router.get("route"):
|
||||
routeType = route.get("type")
|
||||
routeRes = route.get("res")
|
||||
routeName = route.get("name", default=routeRes)
|
||||
|
||||
routeConfig = route.get("config", default=ConfigYAML()) # if no config - build a empty
|
||||
|
||||
if routeType is None or routeRes is None:
|
||||
logging.error("type or name not found in route: %s", route)
|
||||
return False
|
||||
|
||||
try:
|
||||
if routeType == "plugin":
|
||||
importedFile = importlib.import_module(routeType + "." + routeRes)
|
||||
loadedClass = importedFile.BoswatchPlugin(routeConfig)
|
||||
routerDict_tmp[routerName].addRoute(Route(routeName,
|
||||
loadedClass._run,
|
||||
loadedClass._getStatistics,
|
||||
loadedClass._cleanup))
|
||||
|
||||
elif routeType == "module":
|
||||
importedFile = importlib.import_module(routeType + "." + routeRes)
|
||||
loadedClass = importedFile.BoswatchModule(routeConfig)
|
||||
routerDict_tmp[routerName].addRoute(Route(routeName,
|
||||
loadedClass._run,
|
||||
loadedClass._getStatistics,
|
||||
loadedClass._cleanup))
|
||||
|
||||
elif routeType == "router":
|
||||
routerDict_tmp[routerName].addRoute(Route(routeName, routerDict_tmp[routeName].runRouter))
|
||||
|
||||
else:
|
||||
logging.error("unknown type '%s' in %s", routeType, route)
|
||||
return False
|
||||
|
||||
# except ModuleNotFoundError: # only since Py3.6
|
||||
except ImportError:
|
||||
logging.error("%s not found: %s", route.get("type"), route.get("res"))
|
||||
return False
|
||||
|
||||
logging.debug("finished building routers")
|
||||
self._routerDict = routerDict_tmp
|
||||
self._showRouterRoute()
|
||||
return True
|
||||
|
||||
def runRouters(self, routerRunList, bwPacket):
|
||||
"""!Run given Routers
|
||||
|
||||
@param routerRunList: string or list of router names in string form
|
||||
@param bwPacket: instance of Packet class"""
|
||||
if type(routerRunList) is str: # convert single string name to list
|
||||
routerRunList = [routerRunList]
|
||||
|
||||
for routerName in routerRunList:
|
||||
if routerName in self._routerDict:
|
||||
self._routerDict[routerName].runRouter(bwPacket)
|
||||
else:
|
||||
logging.warning("unknown router: %s", routerName)
|
||||
|
||||
self._saveStats() # write stats to stats file
|
||||
|
||||
def cleanup(self):
|
||||
"""!Run cleanup routines for all loaded route points"""
|
||||
for name, routerObject in self._routerDict.items():
|
||||
logging.debug("Start cleanup for %s", name)
|
||||
for routePoint in routerObject.routeList:
|
||||
if routePoint.cleanup:
|
||||
routePoint.cleanup()
|
||||
|
||||
def _showRouterRoute(self):
|
||||
"""!Show the routes of all routers"""
|
||||
for name, routerObject in self._routerDict.items():
|
||||
logging.debug("Route for %s", name)
|
||||
counter = 0
|
||||
for routePoint in routerObject.routeList:
|
||||
counter += 1
|
||||
logging.debug(" %d. %s", counter, routePoint.name)
|
||||
|
||||
def _saveStats(self):
|
||||
"""!Save current statistics to file"""
|
||||
lines = []
|
||||
for name, routerObject in self._routerDict.items():
|
||||
lines.append("[" + name + "]")
|
||||
lines.append(" - Route points: " + str(len(routerObject.routeList)))
|
||||
lines.append(" - Runs: " + str(routerObject._getStatistics()['runCount']))
|
||||
for routePoint in routerObject.routeList:
|
||||
lines.append("[+] " + routePoint.name)
|
||||
if routePoint.statistics:
|
||||
if routePoint.statistics()['type'] == "module":
|
||||
lines.append(" - Runs: " + str(routePoint.statistics()['runCount']))
|
||||
lines.append(" - Run errors: " + str(routePoint.statistics()['moduleErrorCount']))
|
||||
elif routePoint.statistics()['type'] == "plugin":
|
||||
lines.append(" - Runs: " + str(routePoint.statistics()['runCount']))
|
||||
lines.append(" - Setup errors: " + str(routePoint.statistics()['setupErrorCount']))
|
||||
lines.append(" - Alarm errors: " + str(routePoint.statistics()['alarmErrorCount']))
|
||||
lines.append(" - Teardown errors: " + str(routePoint.statistics()['teardownErrorCount']))
|
||||
lines.append("")
|
||||
|
||||
with open("stats_" + str(self._startTime) + ".txt", "w") as stats:
|
||||
for line in lines:
|
||||
stats.write(line + "\n")
|
||||
116
boswatch/timer.py
Normal file
116
boswatch/timer.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: timer.py
|
||||
@date: 21.09.2018
|
||||
@author: Bastian Schroll
|
||||
@description: Timer class for interval timed events
|
||||
"""
|
||||
import logging
|
||||
import time
|
||||
from threading import Thread, Event
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class RepeatedTimer:
|
||||
|
||||
def __init__(self, interval, targetFunction, *args, **kwargs):
|
||||
"""!Create a new instance of the RepeatedTimer
|
||||
|
||||
@param interval: interval in sec. to recall target function
|
||||
@param targetFunction: function to call on timer event
|
||||
@param *args: arguments for the called function
|
||||
@param *kwargs: keyword arguments for the called function
|
||||
"""
|
||||
self._interval = interval
|
||||
self._function = targetFunction
|
||||
self._args = args
|
||||
self._kwargs = kwargs
|
||||
self._start = 0
|
||||
self._overdueCount = 0
|
||||
self._lostEvents = 0
|
||||
self._isRunning = False
|
||||
self._event = Event()
|
||||
self._thread = None
|
||||
|
||||
def start(self):
|
||||
"""!Start a new timer worker thread
|
||||
|
||||
@return True or False"""
|
||||
if self._thread is None:
|
||||
self._event.clear()
|
||||
self._thread = Thread(target=self._target)
|
||||
self._thread.name = "RepTim(" + str(self._interval) + ")"
|
||||
self._thread.daemon = True # start as daemon (thread dies if main program ends)
|
||||
self._thread.start()
|
||||
logging.debug("start repeatedTimer: %s", self._thread.name)
|
||||
return True
|
||||
logging.debug("repeatedTimer always started")
|
||||
return True
|
||||
|
||||
def stop(self):
|
||||
"""!Stop the timer worker thread
|
||||
|
||||
@return True or False"""
|
||||
self._event.set()
|
||||
if self._thread is not None:
|
||||
logging.debug("stop repeatedTimer: %s", self._thread.name)
|
||||
self._thread.join()
|
||||
return True
|
||||
logging.warning("repeatedTimer always stopped")
|
||||
return True
|
||||
|
||||
def _target(self):
|
||||
"""!Runs the target function with his arguments in own thread"""
|
||||
self._start = time.time()
|
||||
while not self._event.wait(self.restTime):
|
||||
logging.debug("work")
|
||||
startTime = time.time()
|
||||
|
||||
try:
|
||||
self._function(*self._args, **self._kwargs)
|
||||
except: # pragma: no cover
|
||||
logging.exception("target throws an exception")
|
||||
|
||||
runTime = time.time() - startTime
|
||||
if runTime < self._interval:
|
||||
logging.debug("ready after: %0.3f sec. - next call in: %0.3f sec.", runTime, self.restTime)
|
||||
else:
|
||||
lostEvents = int(runTime / self._interval)
|
||||
logging.warning("timer overdue! interval: %0.3f sec. - runtime: %0.3f sec. - "
|
||||
"%d events lost - next call in: %0.3f sec.", self._interval, runTime, lostEvents, self.restTime)
|
||||
self._lostEvents += lostEvents
|
||||
self._overdueCount += 1
|
||||
logging.debug("repeatedTimer thread stopped: %s", self._thread.name)
|
||||
self._thread = None # set to none after leave teh thread (running recognize)
|
||||
|
||||
@property
|
||||
def isRunning(self):
|
||||
"""!Property for repeatedTimer running state"""
|
||||
if self._thread:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def restTime(self):
|
||||
"""!Property to get remaining time till next call"""
|
||||
return self._interval - ((time.time() - self._start) % self._interval)
|
||||
|
||||
@property
|
||||
def overdueCount(self):
|
||||
"""!Property to get a count over all overdues"""
|
||||
return self._overdueCount
|
||||
|
||||
@property
|
||||
def lostEvents(self):
|
||||
"""!Property to get a count over all lost events"""
|
||||
return self._lostEvents
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
import logging
|
||||
import platform # for python version nr
|
||||
|
||||
import boswatch.version
|
||||
from boswatch.utils import version
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
|
@ -26,66 +26,39 @@ def logoToLog():
|
|||
"""!Prints the BOSWatch logo to the log at debug level
|
||||
|
||||
@return True or False on error"""
|
||||
try:
|
||||
logging.debug(" ____ ____ ______ __ __ __ _____ ")
|
||||
logging.debug(" / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ / ")
|
||||
logging.debug(" / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ < ")
|
||||
logging.debug(" / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ / ")
|
||||
logging.debug("/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/ ")
|
||||
logging.debug(" German BOS Information Script ")
|
||||
logging.debug(" by Bastian Schroll ")
|
||||
logging.debug("")
|
||||
return True
|
||||
except: # pragma: no cover
|
||||
logging.exception("cannot display logo in log")
|
||||
return False
|
||||
logging.debug(" ____ ____ ______ __ __ __ _____ ")
|
||||
logging.debug(" / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ / ")
|
||||
logging.debug(" / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ < ")
|
||||
logging.debug(" / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ / ")
|
||||
logging.debug("/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/ ")
|
||||
logging.debug(" German BOS Information Script ")
|
||||
logging.debug(" by Bastian Schroll ")
|
||||
logging.debug("")
|
||||
return True
|
||||
|
||||
|
||||
def infoToLog():
|
||||
"""!Prints the BOSWatch and OS information to log at debug level
|
||||
|
||||
@return True or False on error"""
|
||||
try:
|
||||
logging.debug("BOSWatch and environment information")
|
||||
logging.debug("- Client version: %d.%d.%d",
|
||||
boswatch.version.client["major"],
|
||||
boswatch.version.client["minor"],
|
||||
boswatch.version.client["patch"])
|
||||
logging.debug("- Server version: %d.%d.%d",
|
||||
boswatch.version.server["major"],
|
||||
boswatch.version.server["minor"],
|
||||
boswatch.version.server["patch"])
|
||||
logging.debug("- Branch: %s",
|
||||
boswatch.version.branch)
|
||||
logging.debug("- Release date: %02d.%02d.%4d",
|
||||
boswatch.version.date["day"],
|
||||
boswatch.version.date["month"],
|
||||
boswatch.version.date["year"])
|
||||
logging.debug("- Python version: %s", platform.python_version())
|
||||
logging.debug("- Python build: %s", platform.python_build())
|
||||
logging.debug("- System: %s", platform.system())
|
||||
logging.debug("- OS Version: %s", platform.platform())
|
||||
logging.debug("")
|
||||
return True
|
||||
except: # pragma: no cover
|
||||
logging.exception("cannot display OS information")
|
||||
return False
|
||||
|
||||
|
||||
def logoToScreen():
|
||||
"""!Prints the BOSWatch logo to the screen at debug level
|
||||
|
||||
@return True or False on error"""
|
||||
try:
|
||||
print(" ____ ____ ______ __ __ __ _____ ")
|
||||
print(" / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ / ")
|
||||
print(" / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ < ")
|
||||
print(" / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ / ")
|
||||
print("/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/ ")
|
||||
print(" German BOS Information Script ")
|
||||
print(" by Bastian Schroll ")
|
||||
print("")
|
||||
return True
|
||||
except: # pragma: no cover
|
||||
logging.exception("cannot display logo on screen")
|
||||
return False
|
||||
logging.debug("BOSWatch and environment information")
|
||||
logging.debug("- Client version: %d.%d.%d",
|
||||
version.client["major"],
|
||||
version.client["minor"],
|
||||
version.client["patch"])
|
||||
logging.debug("- Server version: %d.%d.%d",
|
||||
version.server["major"],
|
||||
version.server["minor"],
|
||||
version.server["patch"])
|
||||
logging.debug("- Branch: %s",
|
||||
version.branch)
|
||||
logging.debug("- Release date: %02d.%02d.%4d",
|
||||
version.date["day"],
|
||||
version.date["month"],
|
||||
version.date["year"])
|
||||
logging.debug("- Python version: %s", platform.python_version())
|
||||
logging.debug("- Python build: %s", platform.python_build())
|
||||
logging.debug("- System: %s", platform.system())
|
||||
logging.debug("- OS Version: %s", platform.platform())
|
||||
logging.debug("")
|
||||
return True
|
||||
|
|
|
|||
54
boswatch/utils/misc.py
Normal file
54
boswatch/utils/misc.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: misc.py
|
||||
@date: 11.03.2019
|
||||
@author: Bastian Schroll
|
||||
@description: Some misc functions
|
||||
"""
|
||||
import logging
|
||||
from boswatch.utils import version
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
def addClientDataToPacket(bwPacket, config):
|
||||
"""!Add the client information to the decoded data
|
||||
|
||||
This function adds the following data to the bwPacket:
|
||||
- clientName
|
||||
- clientVersion
|
||||
- clientBuildDate
|
||||
- clientBranch
|
||||
- inputSource
|
||||
- frequency"""
|
||||
logging.debug("add client data to bwPacket")
|
||||
bwPacket.set("clientName", config.get("client", "name"))
|
||||
bwPacket.set("clientVersion", version.client)
|
||||
bwPacket.set("clientBuildDate", version.date)
|
||||
bwPacket.set("clientBranch", version.branch)
|
||||
bwPacket.set("inputSource", config.get("client", "inputSource"))
|
||||
bwPacket.set("frequency", config.get("inputSource", "sdr", "frequency"))
|
||||
|
||||
|
||||
def addServerDataToPacket(bwPacket, config):
|
||||
"""!Add the server information to the decoded data
|
||||
|
||||
This function adds the following data to the bwPacket:
|
||||
- serverName
|
||||
- serverVersion
|
||||
- serverBuildDate
|
||||
- serverBranch"""
|
||||
logging.debug("add server data to bwPacket")
|
||||
bwPacket.set("serverName", config.get("server", "name"))
|
||||
bwPacket.set("serverVersion", version.server)
|
||||
bwPacket.set("serverBuildDate", version.date)
|
||||
bwPacket.set("serverBranch", version.branch)
|
||||
|
|
@ -20,12 +20,11 @@ import sys
|
|||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
# todo searching for root part is not a nice solution atm
|
||||
# note searching for root part is not a nice solution atm
|
||||
ROOT_PATH = os.path.dirname(sys.modules['boswatch'].__file__).replace("\\", "/") + "/../"
|
||||
|
||||
LOG_PATH = ROOT_PATH + "log/"
|
||||
CONFIG_PATH = ROOT_PATH + "config/"
|
||||
PLUGIN_PATH = ROOT_PATH + "plugins/"
|
||||
CSV_PATH = ROOT_PATH + "csv/"
|
||||
BIN_PATH = ROOT_PATH + "_bin/"
|
||||
TEST_PATH = ROOT_PATH + "test/"
|
||||
|
||||
|
|
@ -39,11 +38,7 @@ def makeDirIfNotExist(dirPath):
|
|||
|
||||
@param dirPath: Path of the directory
|
||||
@return Path of the directory or False"""
|
||||
try:
|
||||
if not os.path.exists(dirPath):
|
||||
os.mkdir(dirPath)
|
||||
logging.debug("directory created: %s", dirPath)
|
||||
return dirPath
|
||||
except: # pragma: no cover
|
||||
logging.exception("error by creating a directory: %s", dirPath)
|
||||
return False
|
||||
if not os.path.exists(dirPath):
|
||||
os.mkdir(dirPath)
|
||||
logging.debug("directory created: %s", dirPath)
|
||||
return dirPath
|
||||
|
|
|
|||
|
|
@ -20,5 +20,5 @@ logging.debug("- %s loaded", __name__)
|
|||
|
||||
client = {"major": 3, "minor": 0, "patch": 0}
|
||||
server = {"major": 3, "minor": 0, "patch": 0}
|
||||
date = {"day": 1, "month": 1, "year": 2018}
|
||||
date = {"day": 1, "month": 1, "year": 2019}
|
||||
branch = "develop"
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: watchdog.py
|
||||
@date: ##.##.2018
|
||||
@author: Bastian Schroll
|
||||
@description: Watchdog to _check if BOSWatch client, server, rtl_fm or multimon-ng is still running
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class Watchdog:
|
||||
"""!Class for an Watchdog to observe,
|
||||
if needed subprocess still running"""
|
||||
|
||||
def __init__(self):
|
||||
"""!Create a new instance"""
|
||||
pass
|
||||
|
|
@ -12,32 +12,43 @@
|
|||
@file: wildcard.py
|
||||
@date: 15.01.2018
|
||||
@author: Bastian Schroll
|
||||
@description: Little Helper to replace wildcards in stings
|
||||
@todo not completed yet
|
||||
@description: Functions to replace wildcards in stings
|
||||
"""
|
||||
import logging
|
||||
import time
|
||||
|
||||
# from boswatch.module import file
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
# todo check function and document + write an test
|
||||
|
||||
_additionalWildcards = {}
|
||||
|
||||
|
||||
def registerWildcard(wildcard, bwPacketField):
|
||||
if wildcard in _additionalWildcards:
|
||||
logging.error("wildcard always registered: %s", wildcard)
|
||||
return
|
||||
logging.debug("register new wildcard %s for field: %s", wildcard, bwPacketField)
|
||||
_additionalWildcards[wildcard] = bwPacketField
|
||||
|
||||
|
||||
def replaceWildcards(message, bwPacket):
|
||||
_wildcards = {
|
||||
# formatting wildcards
|
||||
# todo check if br and par are needed - if not also change config
|
||||
"{BR}": "\r\n",
|
||||
"{LPAR}": "(",
|
||||
"{RPAR}": ")",
|
||||
"{TIME}": time.time(),
|
||||
"{TIME}": time.strftime("%d.%m.%Y %H:%M:%S"),
|
||||
|
||||
# info wildcards
|
||||
# server
|
||||
"{SNAME}": bwPacket.getField("serverName"),
|
||||
"{SVERS}": bwPacket.getField("serverVersion"),
|
||||
"{SDATE}": bwPacket.getField("serverBuildDate"),
|
||||
"{SBRCH}": bwPacket.getField("serverBranch"),
|
||||
|
||||
# client
|
||||
"{CNAME}": bwPacket.getField("clientName"),
|
||||
"{CIP}": bwPacket.getField("clientIP"),
|
||||
"{CVERS}": bwPacket.getField("clientVersion"),
|
||||
|
|
@ -49,8 +60,6 @@ def replaceWildcards(message, bwPacket):
|
|||
"{TIMES}": bwPacket.getField("mode"),
|
||||
"{FREQ}": bwPacket.getField("frequency"),
|
||||
"{MODE}": bwPacket.getField("mode"),
|
||||
"{DESCS}": bwPacket.getField("descriptionShort"),
|
||||
"{DESCL}": bwPacket.getField("descriptionLong"),
|
||||
|
||||
# fms wildcards
|
||||
"{FMS}": bwPacket.getField("fms"),
|
||||
|
|
@ -75,10 +84,10 @@ def replaceWildcards(message, bwPacket):
|
|||
|
||||
# message for MSG packet is done in poc
|
||||
}
|
||||
for wildcard in _wildcards:
|
||||
try:
|
||||
message = message.replace(wildcard, _wildcards[wildcard])
|
||||
except:
|
||||
logging.exception("error in wildcard replacement")
|
||||
for wildcard, field in _wildcards.items():
|
||||
message = message.replace(wildcard, field)
|
||||
|
||||
for wildcard, field in _additionalWildcards.items():
|
||||
message = message.replace(wildcard, bwPacket.getField(field))
|
||||
|
||||
return message
|
||||
11
build_docu.sh
Normal file
11
build_docu.sh
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "clean api documentation"
|
||||
rm -r docu/docs/api/html
|
||||
echo "generate api documentation"
|
||||
doxygen docu/doxygen.ini
|
||||
|
||||
echo "build documentation"
|
||||
source ./venv/bin/activate
|
||||
python3 -m mkdocs build -f docu/mkdocs.yml
|
||||
deactivate
|
||||
236
bw_client.py
236
bw_client.py
|
|
@ -14,99 +14,183 @@
|
|||
@author: Bastian Schroll
|
||||
@description: BOSWatch client application
|
||||
"""
|
||||
# pylint: disable=wrong-import-position
|
||||
# pylint: disable=wrong-import-order
|
||||
from boswatch.utils import paths
|
||||
|
||||
if not paths.makeDirIfNotExist(paths.LOG_PATH):
|
||||
print("cannot find/create log directory: %s", paths.LOG_PATH)
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
import logging
|
||||
import logging.config
|
||||
import logging.config
|
||||
logging.config.fileConfig(paths.CONFIG_PATH + "logger_client.ini")
|
||||
logging.debug("")
|
||||
logging.debug("######################## NEW LOG ############################")
|
||||
logging.debug("BOSWatch client has started ...")
|
||||
|
||||
logging.config.fileConfig(paths.CONFIG_PATH + "logger_client.ini")
|
||||
logging.debug("")
|
||||
logging.debug("######################## NEW LOG ############################")
|
||||
logging.debug("BOSWatch client has started ...")
|
||||
except Exception as e: # pragma: no cover
|
||||
print("cannot load logger")
|
||||
print(e)
|
||||
|
||||
logging.debug("Import python modules")
|
||||
import argparse
|
||||
logging.debug("- argparse")
|
||||
import threading
|
||||
logging.debug("- threading")
|
||||
import queue
|
||||
logging.debug("- queue")
|
||||
import time
|
||||
logging.debug("- time")
|
||||
|
||||
logging.debug("Import BOSWatch modules")
|
||||
from boswatch.configYaml import ConfigYAML
|
||||
from boswatch.network.client import TCPClient
|
||||
from boswatch.network.broadcast import BroadcastClient
|
||||
from boswatch.processManager import ProcessManager
|
||||
from boswatch.decoder.decoder import Decoder
|
||||
from boswatch.utils import header
|
||||
from boswatch.utils import misc
|
||||
|
||||
header.logoToLog()
|
||||
header.infoToLog()
|
||||
|
||||
# With -h or --help you get the Args help
|
||||
parser = argparse.ArgumentParser(prog="bw_client.py",
|
||||
description="""BOSWatch is a Python Script to receive and
|
||||
decode german BOS information with rtl_fm and multimon-NG""",
|
||||
epilog="""More options you can find in the extern client.ini
|
||||
file in the folder /config""")
|
||||
parser.add_argument("-c", "--config", help="Name to configuration File", required=True)
|
||||
parser.add_argument("-t", "--test", help="Start Client with testdata-set", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
bwConfig = ConfigYAML()
|
||||
if not bwConfig.loadConfigFile(paths.CONFIG_PATH + args.config):
|
||||
logging.error("cannot load config file")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
logging.debug("Import python modules")
|
||||
import argparse
|
||||
logging.debug("- argparse")
|
||||
import subprocess
|
||||
logging.debug("- subprocess")
|
||||
# following is temp for testing
|
||||
import time
|
||||
|
||||
logging.debug("Import BOSWatch modules")
|
||||
from boswatch.config import Config
|
||||
from boswatch.network.client import TCPClient
|
||||
from boswatch.decoder.decoder import Decoder
|
||||
from boswatch.utils import header
|
||||
except Exception as e: # pragma: no cover
|
||||
logging.exception("cannot import modules")
|
||||
print("cannot import modules")
|
||||
print(e)
|
||||
exit(1)
|
||||
# ========== CLIENT CODE ==========
|
||||
mmThread = None
|
||||
bwClient = None
|
||||
|
||||
try:
|
||||
header.logoToLog()
|
||||
header.infoToLog()
|
||||
header.logoToScreen()
|
||||
ip = bwConfig.get("server", "ip", default="127.0.0.1")
|
||||
port = bwConfig.get("server", "port", default="8080")
|
||||
|
||||
logging.debug("parse args")
|
||||
# With -h or --help you get the Args help
|
||||
parser = argparse.ArgumentParser(prog="bw_client.py",
|
||||
description="""BOSWatch is a Python Script to receive and
|
||||
decode german BOS information with rtl_fm and multimon-NG""",
|
||||
epilog="""More options you can find in the extern client.ini
|
||||
file in the folder /config""")
|
||||
parser.add_argument("-c", "--config", help="Name to configuration File", required=True)
|
||||
parser.add_argument("-t", "--test", help="Client will send some testdata", action="store_true") # todo implement testmode
|
||||
args = parser.parse_args()
|
||||
if bwConfig.get("client", "useBroadcast", default=False):
|
||||
broadcastClient = BroadcastClient()
|
||||
if broadcastClient.getConnInfo():
|
||||
ip = broadcastClient.serverIP
|
||||
port = broadcastClient.serverPort
|
||||
|
||||
bwConfig = Config()
|
||||
if bwConfig.loadConfigFile(paths.CONFIG_PATH + args.config, "clientConfig") is False:
|
||||
logging.exception("cannot load config file")
|
||||
print("cannot load config file")
|
||||
exit(1) # without config cannot _run
|
||||
# ========== INPUT CODE ==========
|
||||
def handleSDRInput(dataQueue, sdrConfig, decoderConfig): # todo exception handling inside
|
||||
sdrProc = ProcessManager(str(sdrConfig.get("rtlPath", default="rtl_fm")))
|
||||
sdrProc.addArgument("-d " + str(sdrConfig.get("device", default="0"))) # device id
|
||||
sdrProc.addArgument("-f " + sdrConfig.get("frequency")) # frequencies
|
||||
sdrProc.addArgument("-p " + str(sdrConfig.get("error", default="0"))) # frequency error in ppm
|
||||
sdrProc.addArgument("-l " + str(sdrConfig.get("squelch", default="1"))) # squelch
|
||||
sdrProc.addArgument("-g " + str(sdrConfig.get("gain", default="100"))) # gain
|
||||
sdrProc.addArgument("-M fm") # set mode to fm
|
||||
sdrProc.addArgument("-E DC") # set DC filter
|
||||
sdrProc.addArgument("-s 22050") # bit rate of audio stream
|
||||
sdrProc.setStderr(open(paths.LOG_PATH + "rtl_fm.log", "a"))
|
||||
sdrProc.start()
|
||||
# sdrProc.skipLinesUntil("Output at")
|
||||
|
||||
mmProc = ProcessManager(str(sdrConfig.get("mmPath", default="multimon-ng")), textMode=True)
|
||||
if decoderConfig.get("fms", default=0):
|
||||
mmProc.addArgument("-a FMSFSK")
|
||||
if decoderConfig.get("zvei", default=0):
|
||||
mmProc.addArgument("-a ZVEI1")
|
||||
if decoderConfig.get("poc512", default=0):
|
||||
mmProc.addArgument("-a POCSAG512")
|
||||
if decoderConfig.get("poc1200", default=0):
|
||||
mmProc.addArgument("-a POCSAG1200")
|
||||
if decoderConfig.get("poc2400", default=0):
|
||||
mmProc.addArgument("-a POCSAG2400")
|
||||
mmProc.addArgument("-f alpha")
|
||||
mmProc.addArgument("-t raw -")
|
||||
mmProc.setStdin(sdrProc.stdout)
|
||||
mmProc.setStderr(open(paths.LOG_PATH + "multimon-ng.log", "a"))
|
||||
mmProc.start()
|
||||
# mmProc.skipLinesUntil("Available demodulators:")
|
||||
|
||||
logging.info("start decoding")
|
||||
while inputThreadRunning:
|
||||
if not sdrProc.isRunning:
|
||||
logging.warning("rtl_fm was down - try to restart")
|
||||
sdrProc.start()
|
||||
# sdrProc.skipLinesUntil("Output at") # last line form rtl_fm before data
|
||||
elif not mmProc.isRunning:
|
||||
logging.warning("multimon was down - try to restart")
|
||||
mmProc.start()
|
||||
# mmProc.skipLinesUntil("Available demodulators:") # last line from mm before data
|
||||
elif sdrProc.isRunning and mmProc.isRunning:
|
||||
line = mmProc.readline()
|
||||
if line:
|
||||
dataQueue.put_nowait((line, time.time()))
|
||||
logging.debug("Add data to queue")
|
||||
print(line)
|
||||
logging.debug("stopping thread")
|
||||
mmProc.stop()
|
||||
sdrProc.stop()
|
||||
# ========== INPUT CODE ==========
|
||||
|
||||
inputQueue = queue.Queue()
|
||||
|
||||
if not args.test:
|
||||
inputThreadRunning = True
|
||||
mmThread = threading.Thread(target=handleSDRInput, name="mmReader",
|
||||
args=(inputQueue, bwConfig.get("inputSource", "sdr"), bwConfig.get("decoder")))
|
||||
mmThread.daemon = True
|
||||
mmThread.start()
|
||||
else:
|
||||
logging.warning("STARTING TESTMODE!")
|
||||
logging.debug("reading testdata from file")
|
||||
testFile = open("test/testdata.list", mode="r", encoding="utf-8")
|
||||
for testData in testFile:
|
||||
if (len(testData.rstrip(' \t\n\r')) > 1) and ("#" not in testData[0]):
|
||||
logging.info("Testdata: %s", testData.rstrip(' \t\n\r'))
|
||||
inputQueue.put_nowait((testData.rstrip(' \t\n\r'), time.time()))
|
||||
logging.debug("finished reading testdata")
|
||||
|
||||
bwClient = TCPClient()
|
||||
if bwClient.connect(bwConfig.getStr("Server", "IP"), bwConfig.getInt("Server", "PORT")):
|
||||
bwClient.connect(ip, port)
|
||||
while 1:
|
||||
|
||||
while 1:
|
||||
for i in range(0, 5):
|
||||
time.sleep(1)
|
||||
print("Alarm Nr #" + str(i))
|
||||
if not bwClient.isConnected:
|
||||
reconnectDelay = bwConfig.get("client", "reconnectDelay", default="3")
|
||||
logging.warning("connection to server lost - sleep %d seconds", reconnectDelay)
|
||||
time.sleep(reconnectDelay)
|
||||
bwClient.connect(ip, port)
|
||||
|
||||
data = "ZVEI1: 12345"
|
||||
bwPacket = Decoder.decode(data)
|
||||
elif not inputQueue.empty():
|
||||
data = inputQueue.get()
|
||||
logging.info("get data from queue (waited %0.3f sec.)", time.time() - data[1])
|
||||
logging.debug("%s packet(s) still waiting in queue", inputQueue.qsize())
|
||||
|
||||
if bwPacket:
|
||||
bwPacket.printInfo()
|
||||
bwPacket.addClientData()
|
||||
bwClient.transmit(str(bwPacket))
|
||||
bwPacket = Decoder.decode(data[0])
|
||||
inputQueue.task_done()
|
||||
|
||||
# todo should we do this in an thread, to not block receiving ???
|
||||
# todo but then we should use transmit() and receive() with Lock()
|
||||
failedTransmits = 0
|
||||
while not bwClient.receive() == "[ack]": # wait for ack or timeout
|
||||
if failedTransmits >= 3:
|
||||
logging.error("cannot transmit after 5 retires")
|
||||
break
|
||||
failedTransmits += 1
|
||||
logging.warning("attempt %d to resend packet", failedTransmits)
|
||||
bwClient.transmit(str(bwPacket)) # try to resend
|
||||
if bwPacket is None:
|
||||
continue
|
||||
|
||||
bwPacket.printInfo()
|
||||
misc.addClientDataToPacket(bwPacket, bwConfig)
|
||||
|
||||
for sendCnt in range(bwConfig.get("client", "sendTries", default="3")):
|
||||
bwClient.transmit(str(bwPacket))
|
||||
if bwClient.receive() == "[ack]":
|
||||
logging.debug("ack ok")
|
||||
break
|
||||
sendDelay = bwConfig.get("client", "sendDelay", default="3")
|
||||
logging.warning("cannot send packet - sleep %d seconds", sendDelay)
|
||||
time.sleep(sendDelay)
|
||||
|
||||
else:
|
||||
if args.test:
|
||||
break
|
||||
time.sleep(0.1) # reduce cpu load (wait 100ms)
|
||||
# in worst case a packet have to wait 100ms until it will be processed
|
||||
|
||||
bwClient.disconnect()
|
||||
break
|
||||
# test for server ####################################
|
||||
|
||||
except KeyboardInterrupt: # pragma: no cover
|
||||
logging.warning("Keyboard interrupt")
|
||||
|
|
@ -114,5 +198,11 @@ except SystemExit: # pragma: no cover
|
|||
logging.error("BOSWatch interrupted by an error")
|
||||
except: # pragma: no cover
|
||||
logging.exception("BOSWatch interrupted by an error")
|
||||
finally: # pragma: no cover
|
||||
logging.debug("BOSWatch has ended ...")
|
||||
finally:
|
||||
logging.debug("Starting shutdown routine")
|
||||
if bwClient:
|
||||
bwClient.disconnect()
|
||||
inputThreadRunning = False
|
||||
if mmThread:
|
||||
mmThread.join()
|
||||
logging.debug("BOSWatch client has stopped ...")
|
||||
|
|
|
|||
206
bw_server.py
206
bw_server.py
|
|
@ -14,148 +14,95 @@
|
|||
@author: Bastian Schroll
|
||||
@description: BOSWatch server application
|
||||
"""
|
||||
# pylint: disable=wrong-import-position
|
||||
# pylint: disable=wrong-import-order
|
||||
from boswatch.utils import paths
|
||||
|
||||
if not paths.makeDirIfNotExist(paths.LOG_PATH):
|
||||
print("cannot find/create log directory: %s", paths.LOG_PATH)
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
import logging
|
||||
import logging.config
|
||||
print(paths.CONFIG_PATH + "logger_server.ini")
|
||||
logging.config.fileConfig(paths.CONFIG_PATH + "logger_server.ini")
|
||||
logging.debug("")
|
||||
logging.debug("######################## NEW LOG ############################")
|
||||
logging.debug("BOSWatch server has started ...")
|
||||
except Exception as e: # pragma: no cover
|
||||
print("cannot load logger")
|
||||
print(e)
|
||||
import logging.config
|
||||
logging.config.fileConfig(paths.CONFIG_PATH + "logger_server.ini")
|
||||
logging.debug("")
|
||||
logging.debug("######################## NEW LOG ############################")
|
||||
logging.debug("BOSWatch server has started ...")
|
||||
|
||||
|
||||
logging.debug("Import python modules")
|
||||
import argparse
|
||||
logging.debug("- argparse")
|
||||
import queue
|
||||
logging.debug("- queue")
|
||||
import time
|
||||
logging.debug("- time")
|
||||
|
||||
logging.debug("Import BOSWatch modules")
|
||||
from boswatch.configYaml import ConfigYAML
|
||||
from boswatch.network.server import TCPServer
|
||||
from boswatch.packet import Packet
|
||||
from boswatch.utils import header
|
||||
from boswatch.network.broadcast import BroadcastServer
|
||||
from boswatch.router.routerManager import RouterManager
|
||||
from boswatch.utils import misc
|
||||
|
||||
header.logoToLog()
|
||||
header.infoToLog()
|
||||
|
||||
# With -h or --help you get the Args help
|
||||
parser = argparse.ArgumentParser(prog="bw_server.py",
|
||||
description="""BOSWatch is a Python Script to receive and
|
||||
decode german BOS information with rtl_fm and multimon-NG""",
|
||||
epilog="""More options you can find in the extern client.ini
|
||||
file in the folder /config""")
|
||||
parser.add_argument("-c", "--config", help="Name to configuration File", required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
bwConfig = ConfigYAML()
|
||||
if not bwConfig.loadConfigFile(paths.CONFIG_PATH + args.config):
|
||||
logging.fatal("cannot load config file")
|
||||
exit(1)
|
||||
|
||||
# ############################# begin server system
|
||||
bwRoutMan = None
|
||||
bwServer = None
|
||||
bcServer = None
|
||||
|
||||
try:
|
||||
logging.debug("Import python modules")
|
||||
import argparse
|
||||
logging.debug("- argparse")
|
||||
# following is temp for testing
|
||||
import time
|
||||
import sys
|
||||
import threading
|
||||
import threading
|
||||
bwRoutMan = RouterManager()
|
||||
if not bwRoutMan.buildRouters(bwConfig):
|
||||
logging.fatal("Error while building routers")
|
||||
exit(1)
|
||||
|
||||
logging.debug("Import BOSWatch modules")
|
||||
from boswatch.config import Config
|
||||
from boswatch.network.server import TCPServer
|
||||
from boswatch.packet.packet import Packet
|
||||
from boswatch.plugin.pluginManager import PluginManager
|
||||
from boswatch.descriptor.descriptor import Descriptor
|
||||
from boswatch.filter.doubeFilter import DoubleFilter
|
||||
from boswatch.utils import header
|
||||
except Exception as e: # pragma: no cover
|
||||
logging.exception("cannot import modules")
|
||||
print("cannot import modules")
|
||||
print(e)
|
||||
exit(1)
|
||||
bcServer = BroadcastServer()
|
||||
if bwConfig.get("server", "useBroadcast", default=False):
|
||||
bcServer.start()
|
||||
|
||||
try:
|
||||
header.logoToLog()
|
||||
header.infoToLog()
|
||||
header.logoToScreen()
|
||||
|
||||
logging.debug("parse args")
|
||||
# With -h or --help you get the Args help
|
||||
parser = argparse.ArgumentParser(prog="bw_server.py",
|
||||
description="""BOSWatch is a Python Script to receive and
|
||||
decode german BOS information with rtl_fm and multimon-NG""",
|
||||
epilog="""More options you can find in the extern client.ini
|
||||
file in the folder /config""")
|
||||
parser.add_argument("-c", "--config", help="Name to configuration File", required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
bwConfig = Config()
|
||||
if bwConfig.loadConfigFile(paths.CONFIG_PATH + args.config, "serverConfig") is False:
|
||||
logging.exception("cannot load config file")
|
||||
print("cannot load config file")
|
||||
exit(1) # without config cannot run
|
||||
|
||||
bwPluginManager = PluginManager()
|
||||
bwPluginManager.searchPluginDir()
|
||||
bwPluginManager.importAllPlugins()
|
||||
bwPluginManager.loadAllPlugins()
|
||||
|
||||
bwDescriptor = Descriptor()
|
||||
if bwConfig.getBool("Description", "fms"):
|
||||
bwDescriptor.loadDescription("fms")
|
||||
if bwConfig.getBool("Description", "pocsag"):
|
||||
bwDescriptor.loadDescription("pocsag")
|
||||
if bwConfig.getBool("Description", "zvei"):
|
||||
bwDescriptor.loadDescription("zvei")
|
||||
|
||||
bwDoubleFilter = DoubleFilter()
|
||||
|
||||
serverPaused = False # flag to pause alarming of server
|
||||
serverStop = False # flag to stop the whole server application
|
||||
|
||||
# server flags test code
|
||||
# ----------------------
|
||||
# import threading
|
||||
# def eins():
|
||||
# global serverPaused
|
||||
# serverPaused = True
|
||||
# def zwei():
|
||||
# global serverPaused
|
||||
# serverPaused = False
|
||||
def drei():
|
||||
global serverStop
|
||||
serverStop = True
|
||||
#
|
||||
# t1 = threading.Timer(1, eins)
|
||||
# t2 = threading.Timer(5, zwei)
|
||||
t3 = threading.Timer(15, drei)
|
||||
# t1.start()
|
||||
# t2.start()
|
||||
t3.start()
|
||||
|
||||
bwServer = TCPServer(bwConfig.getInt("Server", "PORT"))
|
||||
incomingQueue = queue.Queue()
|
||||
bwServer = TCPServer(incomingQueue)
|
||||
if bwServer.start():
|
||||
|
||||
while 1:
|
||||
if serverPaused:
|
||||
logging.warning("Server pause flag received ...")
|
||||
logging.debug("But all received packages will be cached!")
|
||||
packetsOld = 0
|
||||
while serverPaused is True:
|
||||
time.sleep(0.2) # reduce cpu load (run all 200ms)
|
||||
packetsNew = bwServer.countPacketsInQueue()
|
||||
if packetsNew is not packetsOld:
|
||||
logging.debug("%s packet(s) waiting in queue", packetsNew)
|
||||
packetsOld = packetsNew
|
||||
logging.warning("Server resumed ...")
|
||||
if incomingQueue.empty(): # pause only when no data
|
||||
time.sleep(0.1) # reduce cpu load (wait 100ms)
|
||||
# in worst case a packet have to wait 100ms until it will be processed
|
||||
|
||||
if serverStop:
|
||||
logging.warning("Server stop flag received ...")
|
||||
break
|
||||
else:
|
||||
data = incomingQueue.get()
|
||||
|
||||
if not bwServer.countPacketsInQueue(): # pause only when no data
|
||||
time.sleep(0.1) # reduce cpu load (run all 100ms)
|
||||
|
||||
data = bwServer.getDataFromQueue()
|
||||
if data is not None:
|
||||
logging.info("get data from %s (waited in queue %0.3f sec.)", data[0], time.time() - data[2])
|
||||
logging.debug("%s packet(s) waiting in queue", bwServer.countPacketsInQueue())
|
||||
logging.debug("%s packet(s) still waiting in queue", incomingQueue.qsize())
|
||||
bwPacket = Packet((data[1]))
|
||||
|
||||
if not bwDoubleFilter.filter(bwPacket):
|
||||
continue
|
||||
|
||||
bwPacket.set("clientIP", data[0])
|
||||
bwPacket.addServerData()
|
||||
misc.addServerDataToPacket(bwPacket, bwConfig)
|
||||
|
||||
if bwConfig.getBool("Description", bwPacket.get("mode")):
|
||||
bwDescriptor.addDescriptions(bwPacket)
|
||||
logging.debug("[ --- ALARM --- ]")
|
||||
bwRoutMan.runRouters(bwConfig.get("alarmRouter"), bwPacket)
|
||||
logging.debug("[ --- END ALARM --- ]")
|
||||
|
||||
bwPluginManager.runAllPlugins(bwPacket)
|
||||
# print(bwPacket.get("clientVersion")["major"])
|
||||
incomingQueue.task_done()
|
||||
|
||||
except KeyboardInterrupt: # pragma: no cover
|
||||
logging.warning("Keyboard interrupt")
|
||||
|
|
@ -163,15 +110,12 @@ except SystemExit: # pragma: no cover
|
|||
logging.error("BOSWatch interrupted by an error")
|
||||
except: # pragma: no cover
|
||||
logging.exception("BOSWatch interrupted by an error")
|
||||
finally: # pragma: no cover
|
||||
# try-except-blocks are necessary because there is a change that the vars
|
||||
# bwServer or bwPluginManager are not defined in case of an early error
|
||||
try:
|
||||
finally:
|
||||
logging.debug("Starting shutdown routine")
|
||||
if bwRoutMan:
|
||||
bwRoutMan.cleanup()
|
||||
if bwServer:
|
||||
bwServer.stop()
|
||||
except: # pragma: no cover
|
||||
pass
|
||||
try:
|
||||
bwPluginManager.unloadAllPlugins()
|
||||
except: # pragma: no cover
|
||||
pass
|
||||
logging.debug("BOSWatch has ended ...")
|
||||
if bcServer:
|
||||
bcServer.stop()
|
||||
logging.debug("BOSWatch server has stopped ...")
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# ____ ____ ______ __ __ __ _____
|
||||
# / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
# / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
# / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
#/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
# German BOS Information Script
|
||||
# by Bastian Schroll
|
||||
|
||||
[Server]
|
||||
# connection params to BOSWatch alarm server
|
||||
IP = 127.0.0.1
|
||||
PORT = 8080
|
||||
|
||||
# you can set multiple servers (one line for each)
|
||||
# the alarm data are served to all server simultane
|
||||
# for each server you can set one backup server that be used if master is not reachable
|
||||
# {indv. name} = {Master}:{PORT};{Slave}:{PORT}
|
||||
|
||||
# Name Master Port|Slave Port
|
||||
server1 = 127.0.0.1:8080
|
||||
serverTest = 127.0.0.1:1234;192.168.178.1:1234
|
||||
|
||||
[Client]
|
||||
# information about this BOSWatch client instance
|
||||
Name = BW3 Client
|
||||
|
||||
# choose input source for multimon
|
||||
# 'stick' or 'audio'
|
||||
InputSource = stick
|
||||
|
||||
[Stick]
|
||||
# configuration for your DVB-T stick
|
||||
Device = 0
|
||||
Frequency = 85.000M
|
||||
PPMError = 0
|
||||
Squelch = 0
|
||||
Gain = 100
|
||||
|
||||
[Audio]
|
||||
# configuration for audio input
|
||||
|
||||
[Decoder]
|
||||
# here you can enable needed decoders
|
||||
FMS = 0
|
||||
ZVEI = 0
|
||||
POC512 = 0
|
||||
POC1200 = 0
|
||||
POC2400 = 0
|
||||
37
config/client.yaml
Normal file
37
config/client.yaml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# ____ ____ ______ __ __ __ _____
|
||||
# / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
# / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
# / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
#/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
# German BOS Information Script
|
||||
# by Bastian Schroll
|
||||
|
||||
client:
|
||||
name: BW3 Client # name of the BW3 Client instance
|
||||
inputSource: sdr # atm only 'sdr' is possible
|
||||
useBroadcast: no # use broadcast to find server automatically
|
||||
reconnectDelay: 3 # time in seconds to delay reconnect try
|
||||
sendTries: 3 # how often should tried to send a packet
|
||||
sendDelay: 3 # time in seconds to delay the resend try
|
||||
|
||||
server: # only used if useBroadcast = no
|
||||
ip: 127.0.0.1
|
||||
port: 8080
|
||||
|
||||
inputSource:
|
||||
sdr:
|
||||
device: 0
|
||||
frequency: 85M
|
||||
error: 0
|
||||
squelch: 1
|
||||
gain: 100
|
||||
rtlPath: /usr/bin/rtl_fm
|
||||
mmPath: /opt/multimon/multimon-ng
|
||||
|
||||
decoder:
|
||||
fms: yes
|
||||
zvei: yes
|
||||
poc512: yes
|
||||
poc1200: yes
|
||||
poc2400: yes
|
||||
|
|
@ -22,7 +22,7 @@ format=%(asctime)s,%(msecs)03d - %(module)-15s [%(levelname)-8s] %(message)s
|
|||
datefmt=%d.%m.%Y %H:%M:%S
|
||||
|
||||
[formatter_complex]
|
||||
format=%(asctime)s,%(msecs)03d - %(module)-15s %(funcName)-18s [%(levelname)-8s] %(message)s
|
||||
format=%(asctime)s,%(msecs)03d - %(threadName)-15s %(module)-15s %(funcName)-18s [%(levelname)-8s] %(message)s
|
||||
datefmt=%d.%m.%Y %H:%M:%S
|
||||
|
||||
[handlers]
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ format=%(asctime)s,%(msecs)03d - %(module)-15s [%(levelname)-8s] %(message)s
|
|||
datefmt=%d.%m.%Y %H:%M:%S
|
||||
|
||||
[formatter_complex]
|
||||
format=%(asctime)s,%(msecs)03d - %(module)-15s %(funcName)-18s [%(levelname)-8s] %(message)s
|
||||
format=%(asctime)s,%(msecs)03d - %(threadName)-15s %(module)-15s %(funcName)-18s [%(levelname)-8s] %(message)s
|
||||
datefmt=%d.%m.%Y %H:%M:%S
|
||||
|
||||
[handlers]
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# ____ ____ ______ __ __ __ _____
|
||||
# / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
# / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
# / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
#/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
# German BOS Information Script
|
||||
# by Bastian Schroll
|
||||
|
||||
[Server]
|
||||
PORT = 8080
|
||||
Name = BW3 Server
|
||||
|
||||
[FMS]
|
||||
UseLists =
|
||||
Allowed =
|
||||
Denied =
|
||||
RegEx =
|
||||
|
||||
[POCSAG]
|
||||
UseLists =
|
||||
Allowed =
|
||||
Denied =
|
||||
Range =
|
||||
RegEx =
|
||||
|
||||
[ZVEI]
|
||||
UseLists =
|
||||
Allowed =
|
||||
Denied =
|
||||
Range =
|
||||
RegEx =
|
||||
|
||||
[Filter]
|
||||
UseDoubleFilter = 0
|
||||
UseRegexFilter = 0
|
||||
|
||||
[doubleFilter]
|
||||
# max number of entrys to save for comparison
|
||||
MaxEntry = 30
|
||||
# time to ignore same alarm in seconds
|
||||
IgnoreTime = 10
|
||||
# include pocsag msg in comparison
|
||||
CheckMsg = 0
|
||||
|
||||
[regexFilter]
|
||||
|
||||
[Description]
|
||||
# load CSV description files with short and lon description
|
||||
fms = 0
|
||||
pocsag = 0
|
||||
zvei = 0
|
||||
|
||||
[Plugins]
|
||||
# here you can enable needed plugins
|
||||
# 0 is disabled
|
||||
# all greater than 0 enable the plugin
|
||||
# the higher the number the earlier the plugin is called on alarm
|
||||
# we call ist Plugin Prioority
|
||||
template = 1
|
||||
30
config/server.yaml
Normal file
30
config/server.yaml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# ____ ____ ______ __ __ __ _____
|
||||
# / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
# / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
# / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
#/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
# German BOS Information Script
|
||||
# by Bastian Schroll
|
||||
|
||||
server:
|
||||
port: 8080
|
||||
name: BW3 Server # name of the BW3 Server instance
|
||||
useBroadcast: no # serve server ip on broadcast request
|
||||
|
||||
alarmRouter:
|
||||
- Router 1
|
||||
|
||||
router:
|
||||
- name: Router 1
|
||||
route:
|
||||
- type: module
|
||||
res: filter.modeFilter
|
||||
name: Filter Fms/Zvei
|
||||
config:
|
||||
allowed:
|
||||
- fms
|
||||
- zvei
|
||||
- type: plugin
|
||||
name: test plugin
|
||||
res: template_plugin
|
||||
12
csv/fms.csv
12
csv/fms.csv
|
|
@ -1,12 +0,0 @@
|
|||
id,shortDescription,longDescription
|
||||
#
|
||||
# BOSWatch CSV file for describing FMS-Addresses
|
||||
#
|
||||
# For each FMS-Address you could set a description-text
|
||||
# Use the structure: fms,"Description-Text"
|
||||
#
|
||||
# !!! DO NOT delete the first line !!!
|
||||
#
|
||||
12345678,"FMS testdata äöüß"
|
||||
56487234,"Test Nummer 18",""
|
||||
68734123,"Und noch einer ganz schnell",""
|
||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
|
|
@ -1,18 +0,0 @@
|
|||
id,shortDescription,longDescription
|
||||
#
|
||||
# BOSWatch CSV file for describing POCSAG-Addresses
|
||||
#
|
||||
# For each RIC-Address you could set a description-text
|
||||
# Use the structure: ric,"Description-Text"
|
||||
#
|
||||
# You can even define specific subrics, therefore you
|
||||
# 1. need to specify a main RIC: 1234567, "Unit One"
|
||||
# 2. specify a certain subric: 1234567B, "Subunit Bravo"
|
||||
# The result for 1234567B will be "Unit One Subunit Bravo"
|
||||
# - Be sure having defined the main RIC (step one)! -
|
||||
#
|
||||
# !!! DO NOT delete the first line !!!
|
||||
#
|
||||
1234567,"POCSAG testdata äöüß"
|
||||
4323424,"Test Nummer 18",""
|
||||
6873423,"Und noch einer ganz schnell",""
|
||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
12
csv/zvei.csv
12
csv/zvei.csv
|
|
@ -1,12 +0,0 @@
|
|||
id,shortDescription,longDescription
|
||||
#
|
||||
# BOSWatch CSV file for describing ZVEI-Addresses
|
||||
#
|
||||
# For each ZVEI-Address you could set a description-text
|
||||
# Use the structure: zvei,"Description-Text"
|
||||
#
|
||||
# !!! DO NOT delete the first line !!!
|
||||
#
|
||||
12345,"ZVEI testdata äöüß","Hier nochmal eine echt Lange Info die keinen Sinn macht"
|
||||
56487,"Test Nummer 18",""
|
||||
68734,"Und noch einer ganz schnell",""
|
||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
32
docu/docs/changelog.md
Normal file
32
docu/docs/changelog.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# <center>Changelog</center>
|
||||
---
|
||||
|
||||
## Version [2.9.0] - date
|
||||
|
||||
Functions implemented in initial version:
|
||||
|
||||
- Multithreaded Server/Client infrastructure for alarm handling
|
||||
- Client can auto fetch connection information over bradocast from server
|
||||
- Easy configuration with YAML file for all components
|
||||
- Simple module and plugin system to extend functionality
|
||||
- Alarmpacket routing system for flexible chains of modules nd plugins
|
||||
|
||||
### Modules
|
||||
- Mode filter to filter at specific packet types such as FMS, POCSAG, ZVEI or MSG packets
|
||||
|
||||
### Filter
|
||||
|
||||
|
||||
---
|
||||
|
||||
Zum schreiben des Changelog's siehe: [http://keepachangelog.com/de/1.0.0/](http://keepachangelog.com/de/1.0.0/)
|
||||
|
||||
<!--
|
||||
## Version [#.#.#] - date
|
||||
### Added
|
||||
### Changed
|
||||
### Deprecated
|
||||
### Removed
|
||||
### Fixed
|
||||
### Security
|
||||
-->
|
||||
133
docu/docs/config.md
Normal file
133
docu/docs/config.md
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# <center>Konfiguration</center>
|
||||
|
||||
Die Konfiguration von BOSWatch 3 ist im YAML Format abgelegt und wird nachfolgend beschrieben.
|
||||
Immer wenn für eine Einstellung ein **Default** Wert angegeben ist, muss diese Einstellung nicht
|
||||
zwingend in die Konfiguration eingetragen werden.
|
||||
|
||||
## Client
|
||||
|
||||
---
|
||||
### `client:`
|
||||
|Feld|Beschreibung|Default|
|
||||
|----|------------|-------|
|
||||
|name|Name zur Identifizierung der Client Instanz||
|
||||
|inputSource|Art der zu nutzenden Input Quelle (aktuell nur `sdr`)|sdr|
|
||||
|useBroadcast|Verbindungsdaten per [Broadcast](information/broadcast.md) beziehen|no|
|
||||
|reconnectDelay|Verzögerung für erneuten Verbindungsversuch zum Server|3|
|
||||
|sendTries|Anzahl der Sendeversuche eines Pakets|3|
|
||||
|sendDelay|Verzögerung für einen erneuten Sendeversuch|3|
|
||||
|
||||
---
|
||||
### `server:`
|
||||
Der Abschnitt `server:` wird nur genutzt, wenn `useBroadcast: no` gesetzt ist.
|
||||
Ansonsten wird versucht die Verbindungsdaten per Broadcast Paket direkt vom Server zu beziehen.
|
||||
|
||||
|Feld|Beschreibung|Default|
|
||||
|----|------------|-------|
|
||||
|ip|IP Adresse des Servers|127.0.0.1|
|
||||
|port|Port des Sever|8080|
|
||||
|
||||
**Beispiel:**
|
||||
```yaml
|
||||
server:
|
||||
ip: 10.10.10.2
|
||||
port: 9123
|
||||
```
|
||||
|
||||
---
|
||||
### `inputSource:`
|
||||
Aktuell gibt es nur `sdr:` als Input Quelle
|
||||
|
||||
#### `sdr:`
|
||||
|Feld|Beschreibung|Default|
|
||||
|----|------------|-------|
|
||||
|device|rtl_fm Device ID|0|
|
||||
|frequency|Zu empfangende Frequenz||
|
||||
|error|Frequenz Abweichung in ppm|0|
|
||||
|squelch|Einstellung der Rauschsperre|1|
|
||||
|gain|Verstärkung des Eingangssignals|100|
|
||||
|rtlPath|Pfad zur rtl_fm Binary|rtl_fm|
|
||||
|mmPath|Pfad zur multimon-ng Binary|multimon-ng|
|
||||
|
||||
**Beispiel:**
|
||||
```yaml
|
||||
inputSource:
|
||||
sdr:
|
||||
device: 0
|
||||
frequency: 85M
|
||||
error: 0
|
||||
squelch: 1
|
||||
gain: 100
|
||||
rtlPath: /usr/bin/rtl-fm
|
||||
mmPath: /opt/multimon/multimon-ng
|
||||
```
|
||||
|
||||
---
|
||||
### `decoder:`
|
||||
|Feld|Beschreibung|Default|
|
||||
|----|------------|-------|
|
||||
|fms|FMS Decoder|no|
|
||||
|zvei|ZVEI Decoder|no|
|
||||
|poc512|POCSAG Decoder (Bitrate 512)|no|
|
||||
|poc1200|POCSAG Decoder (Bitrate 1200)|no|
|
||||
|poc2400|POCSAG Decoder (Bitrate 2400)|no|
|
||||
|
||||
---
|
||||
## Server
|
||||
Nachfolgend alle Paramater der Server Konfiguration
|
||||
|
||||
### `server:`
|
||||
|Feld|Beschreibung|Default|
|
||||
|----|------------|-------|
|
||||
|port|Port auf dem der Server lauscht|8080
|
||||
|name|Name zur Identifizierung der Server Instanz||
|
||||
|useBroadcast|Verbindungsdaten per Broadcast Server bereitstellen|no|
|
||||
|
||||
---
|
||||
### `alarmRouter:`
|
||||
Enthält eine Liste der Router Namen, welche bei einem Alarm direkt gestartet werden sollen.
|
||||
|
||||
**Beispiel:**
|
||||
```yaml
|
||||
alarmRouter:
|
||||
- Name des Routers
|
||||
- ein weiter Router
|
||||
```
|
||||
|
||||
---
|
||||
### `router:`
|
||||
Mit den Routern kann der Verarbeitungsweg eines Alarm-Paketes festgelegt werden. Es können beliebig viele Router in Form einer Liste angegeben werden.
|
||||
|
||||
|Feld|Beschreibung|Default|
|
||||
|----|------------|-------|
|
||||
|name|Name des Routers||
|
||||
|route|Definiten des Routenverlaufs
|
||||
|
||||
#### `route:`
|
||||
|
||||
Jeder Router kann eine beliebige Anzahl einzelner Routenpunkte enthalten. Diese werden innerhalb des Routers sequentiel abgearbeitet. Mögliche Typen der Routenpunkte sind dabei ein Modul, ein Plugin oder ein anderer Router. Sie werden ebenfalls in Form einer Liste definiert.
|
||||
|
||||
|Feld|Beschreibung|Default|
|
||||
|----|------------|-------|
|
||||
|type|Art des Routenpunktes (module, plugin, router)||
|
||||
|res|Zu ladende Resource (Siehe entsprechende Kapitel)||
|
||||
|name|Optionaler Name des Routenpunktes|gleich wie Resource|
|
||||
|config|Konfigurationseinstellungen des Routenpunktes (Siehe entsprechende Kapitel)||
|
||||
|
||||
**Beispiel:**
|
||||
```yaml
|
||||
router:
|
||||
- name: Router 1
|
||||
route:
|
||||
- type: module
|
||||
res: filter.modeFilter
|
||||
name: Filter Fms/Zvei
|
||||
config:
|
||||
allowed:
|
||||
- fms
|
||||
```
|
||||
|
||||
---
|
||||
## Module/Plugins
|
||||
|
||||
Die möglichen Einstellungen der einzelnen Module und Plugins sind im jeweiligen Kapitel aufgelistet.
|
||||
133
docu/docs/develop/ModulPlugin.md
Normal file
133
docu/docs/develop/ModulPlugin.md
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# <center>Eigenes Modul/Plugin schreiben</center>
|
||||
|
||||
Um ein eigenes Modul oder Plugin zu schreiben, sollte man sich am besten zuerst einmal das das `template` im entsprechenden Ordner ansehen. Dies kann als Vorlage für das eigene Modul oder Plugin genutzt werden.
|
||||
|
||||
---
|
||||
## Allgemeine Informationen
|
||||
Im ersten Schritt sollte eine Kopie des jeweiligen Templates (Modul oder Plugin) erstellt werden. Nun sollten im Dateikopf die Angaben angepasst werden.
|
||||
|
||||
---
|
||||
## Benötigte Methoden überschreiben
|
||||
### Modul
|
||||
Die Modul Basisklasse bietet einige Methoden, welche vom Modul überschrieben werden können.
|
||||
|
||||
- `onLoad()` wird direkt beim Import des Moduls ausgeführt
|
||||
- `doWork(bwPacket)` wird bei der Ausführung aufgerufen
|
||||
- `onUnload()` wird beim Zerstören der Plugin Modul zum Programmende ausgeführt
|
||||
|
||||
### Plugin
|
||||
Die Plugin Basisklasse bietet einige Methoden, welche vom Plugin überschrieben werden können.
|
||||
|
||||
- `onLoad()` wird direkt beim Import des Plugins ausgeführt
|
||||
- `setup()` wird vor jeder Ausführung gerufen
|
||||
- `fms(bwPacket)` wird bei einem FMS Paket ausgeführt
|
||||
- `pocsag(bwPacket)` wird bei einem POCSAG Paket ausgeführt
|
||||
- `zvei(bwPacket)` wird bei einem ZVEI Packet ausgeführt
|
||||
- `msg(bwPacket)` wird bei einem Nachrichten Packet ausgeführt
|
||||
- `teardown()` wird nach jeder Ausführung gerufen
|
||||
- `onUnload()` wird beim Zerstören der Plugin Instanz zum Programmende ausgeführt
|
||||
|
||||
---
|
||||
## Konfiguration
|
||||
### Konfiguration anlegen
|
||||
Jedes Modul oder Plugin wird in einem Router folgendermaßen deklariert:
|
||||
```yaml
|
||||
- type: module # oder 'plugin'
|
||||
res: template_module # Name der Python Datei (ohne .py)
|
||||
name: Mein Modul # optionaler Name
|
||||
config: # config-Sektion
|
||||
option1: value 1
|
||||
option2:
|
||||
underOption1: value 21
|
||||
underOption2: value 22
|
||||
list:
|
||||
- list 1
|
||||
- list 2
|
||||
```
|
||||
Eine entsprechende Dokumentation der Parameter **muss** in der Dokumentation des jeweiligen Moduls oder Plugins hinterleget werden.
|
||||
|
||||
### Konfiguration verwenden
|
||||
Wird der Instanz eine Konfiguration übergeben wird diese in `self.config` abgelegt und kann wie folgt abgerufen werden:
|
||||
(Dies Ergebnisse beziehen sich auf das Konfigurationsbeispiel oben)
|
||||
|
||||
- Einzelnes Feld auslesen
|
||||
`self.config.get("option1")`
|
||||
> liefert `value 1`
|
||||
|
||||
- Verschachteltes Feld auslesen (beliebige tiefe möglich)
|
||||
`self.config.get("option2", "underOption1")`
|
||||
> liefert `value 21`
|
||||
|
||||
- Es kann ein Default Wert angegeben werden (falls entsprechender Eintrag fehlt)
|
||||
`self.config.get("notSet", default="defValue")`
|
||||
> liefert `defValue`
|
||||
|
||||
- Über Listen kann einfach iteriert werden
|
||||
`for item in self.config.get(FIELD):`
|
||||
> liefert ein Element je Iteration - hier `list 1` und `list 2`
|
||||
|
||||
Wird ein End-Wert ausgelesen, wird dieser direkt zurück gegeben.
|
||||
Sollten weitere Unterelemente oder eine Liste exisitieren wird erneut ein Objekt der Klasse `Config()` zurück gegeben, auf welches wiederum nach obigem Schema zugegriffen werden kann.
|
||||
|
||||
---
|
||||
## Arbeiten mit dem bwPacket
|
||||
An das Modul bzw. Plugin wird eine Instanz eines BOSWatch-Paket Objekts übergeben.
|
||||
Aus dieser kann mittels `bwPacket.get(FIELDNAME)` das entsprechende Feld ausgelesen werden.
|
||||
Mittels `bwPacket.set(FIELDNAME, VALUE)` kann ein Wert hinzugefügt oder modifiziert werden.
|
||||
Eine Auflistung der bereitgestellten Informationen findet sich im entsprechenden [BOSWatch Paket](packet.md) Dokumentation.
|
||||
|
||||
**Bitte beachten:**
|
||||
|
||||
- Selbst vom Modul hinzugefügte Felder **müssen** in der Modul Dokumentation unter `Paket Modifikation` aufgeführt werden.
|
||||
- Sollte ein Modul oder Plugin Felder benutzen, welche in einem anderen Modul erstellt werden, **muss** dies im Punkt `Abhänigkeiten` des jeweiligen Moduls oder Plugins dokumentiert werden.
|
||||
|
||||
### Rückgabewert bei Modulen
|
||||
Module können Pakete beliebig verändern. Diese Änderungen werden im Router entsprechend weitergeleitet.
|
||||
|
||||
Mögliche Rückgabewerte eines Moduls:
|
||||
|
||||
- `return bwPacket` Gibt das modifizierte bwPacket an den Router zurück (Paket Modifikation)
|
||||
- `return None` Der Router fährt mit dem unveränderten bwPacket fort (Input = Output)
|
||||
- `return False` Der Router stopt sofort die Ausführung (zB. in Filtern verwendet)
|
||||
|
||||
### Rückgabewert bei Plugins
|
||||
Plugins geben keine Pakete mehr zurück. Sie fungieren ausschließlich als Endpunkt.
|
||||
Die Plugin Basisklasse liefert intern immer ein `None` an den Router zurück,
|
||||
was zur weiteren Ausführung des Routers mit dem original Paket führt. Daher macht es in Plugins keinen Sinn ein Paket zu modifizieren.
|
||||
|
||||
---
|
||||
## Nutzung der Wildcards
|
||||
|
||||
Es gibt einige vordefinierte Wildcards welche in der [BOSWatch Paket](packet.md) Dokumentation zu finden sind.
|
||||
|
||||
Außerdem sind die folgenden allgemeinen Wildcards definiert:
|
||||
|
||||
- `{BR}` - Zeilenumbruch `\r\n`
|
||||
- `{LPAR}` - öffnende Klammer `(`
|
||||
- `{RPAR}` - schließende Klammer `)`
|
||||
- `{TIME}` - Aktueller Zeitstempel im Format `%d.%m.%Y %H:%M:%S`
|
||||
|
||||
### Wildcards registrieren [Module]
|
||||
Module können zusätzliche Wildcards registrieren welche anschließend in den Plugins ebenfalls geparst werden können.
|
||||
Dies kann über die interne Methode `self.registerWildcard(newWildcard, bwPacketField)` gemacht werden.
|
||||
|
||||
- `newWildcard` muss im folgenden Format angegeben werden: `{WILDCARD}`
|
||||
- `bwPacketField` ist der Name des Feldes im bwPacket - gestezt per `bwPacket.set(FIELDNAME, VALUE)`
|
||||
|
||||
**Bitte beachten:**
|
||||
|
||||
- Selbst vom Modul registrierte Wildcards **müssen** in der Modul Dokumentation unter `Zusätzliche Wildcards` aufgeführt werden.
|
||||
|
||||
### Wildcards parsen [Plugins]
|
||||
Das parsen der Wildcars funktioniert komfortabel über die interne Methode `msg = self.parseWildcards(msg)`.
|
||||
|
||||
- `msg` enstrpicht dabei dem String in welchem die Wildcards ersetzt werden sollen
|
||||
|
||||
Die Platzhalter der Wildcards findet man in der [BOSWatch Paket](packet.md) Dokumentation.
|
||||
|
||||
Sollten Module zusätzliche Wildcards registrieren, findet man Informationen dazu in der jeweiligen Plugin Dokumentation
|
||||
|
||||
---
|
||||
## Richtiges Logging
|
||||
tbd ...
|
||||
|
||||
51
docu/docs/develop/packet.md
Normal file
51
docu/docs/develop/packet.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# <center>BOSWatch Paket Format</center>
|
||||
|
||||
Ein BOSWatch Datenpaket wird in einem Python Dict abgebildet. In der nachfolgenden Tabelle sind die genutzten Felder abgebildet.
|
||||
|
||||
---
|
||||
## Allgemeine Informationen
|
||||
|Feldname|FMS|POCSAG|ZVEI|MSG|Wildcard|Beschreibung|
|
||||
|--------|:-:|:----:|:--:|:-:|--------|------------|
|
||||
|serverName|X|X|X|X|`{SNAME}`|Name der BOSWatch Server Instanz|
|
||||
|serverVersion|X|X|X|X|`{SVERS}`||
|
||||
|serverBuildDate|X|X|X|X|`{SDATE}`||
|
||||
|serverBranch|X|X|X|X|`{SBRCH}`||
|
||||
|clientName|X|X|X|X|`{CNAME}`|Name der BOSWatch Client Instanz|
|
||||
|clientIP|X|X|X|X|`{CIP}`||
|
||||
|clientVersion|X|X|X|X|`{CVERS}`||
|
||||
|clientBuildDate|X|X|X|X|`{CDATE}`||
|
||||
|clientBranch|X|X|X|X|`{CBRCH}`||
|
||||
|inputSource|X|X|X|X|`{INSRC}`|(sdr, audio)|
|
||||
|timestamp|X|X|X|X|`{TIMES}`||
|
||||
|frequency|X|X|X|X|`{FREQ}`||
|
||||
|mode|X|X|X|X|`{MODE}`|(fms, pocsag, zvei, msg)|
|
||||
|
||||
---
|
||||
## Speziell für POCSAG
|
||||
|Feldname|FMS|POCSAG|ZVEI|MSG|Wildcard|Beschreibung|
|
||||
|--------|:-:|:----:|:--:|:-:|--------|------------|
|
||||
|bitrate||X|||`{BIT}`||
|
||||
|ric||X|||`{RIC}`||
|
||||
|subric||X|||`{SRIC}`|(1, 2, 3, 4)|
|
||||
|subricText||X|||`{SRICT}`|(a, b, c, d)|
|
||||
|message||X||X|`{MSG}`|Kann außerdem für ein Message Paket genutzt werden|
|
||||
|
||||
---
|
||||
## Speziell für ZVEI
|
||||
|Feldname|FMS|POCSAG|ZVEI|MSG|Wildcard|Beschreibung|
|
||||
|--------|:-:|:----:|:--:|:-:|--------|------------|
|
||||
|tone|||X||`{TONE}`|5-Ton Sequenz nach ZVEI|
|
||||
|
||||
---
|
||||
## Speziell für FMS
|
||||
|Feldname|FMS|POCSAG|ZVEI|MSG|Wildcard|Beschreibung|
|
||||
|--------|:-:|:----:|:--:|:-:|--------|------------|
|
||||
|fms|X||||`{FMS}`||
|
||||
|service|X||||`{SERV}`||
|
||||
|country|X||||`{COUNT}`||
|
||||
|location|X||||`{LOC}`||
|
||||
|vehicle|X||||`{VEC}`||
|
||||
|status|X||||`{STAT}`||
|
||||
|direction|X||||`{DIR}`||
|
||||
|dirextionText|X||||`{DIRT}`|(Fhz->Lst, Lst->Fhz)|
|
||||
|tacticalInfo|X||||`{TACI}`|(I, II, III, IV)|
|
||||
1
docu/docs/img/broadcast.drawio
Normal file
1
docu/docs/img/broadcast.drawio
Normal file
|
|
@ -0,0 +1 @@
|
|||
<mxfile modified="2019-09-20T09:41:55.464Z" host="www.draw.io" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36" etag="JWctItHCjYtE4wFsANak" version="11.2.9" pages="1"><diagram id="dOOvDc07wKykiP7z2FdT" name="Page-1">7Vpbb9owFP41PK6K49x4bGm3adrWakzaeJpMYsBdEiNjCuzXzybO1VBCExo2WlVqfGyf2N93bnbTg4No/YGh+ewLDXDYM41g3YO3PdMEwLPFHynZJBIHOolgykigBuWCIfmDldBQ0iUJ8KI0kFMacjIvC30ax9jnJRlijK7KwyY0LL91jqZYEwx9FOrSHyTgs0TqmW4u/4jJdJa+GTj9pCdC6WC1k8UMBXRVEMG7HhwwSnnyFK0HOJTgpbgk897v6c0WxnDM60zof3K/umN0jwfcur8fGeNvv27eKS1PKFyqDavF8k2KAKPLOMBSidGDN6sZ4Xg4R77sXQnOhWzGo1C0gHickDAc0JCy7Vw48Xzs+0K+4Iz+xoWesWdbtlSoFoAZx+u9OwMZXsLQMI0wZxsxRE2wHQWxsjFLbWKVEwYsNWRWIMtMhUgZyTRTneMoHhSUR8BqnhbWAGFvshNWx/fweNIOrMA8N1gdDdYhZmKPGrhi07yMYBmpmMa4AqsSoZBMY9H0BUhCMbyREBIRD65VR0SCQL5mJ2VlUpN3pmFjyyONuYpxEvmkrZYNWqCs6gm2VZOyUzHmaYwNiNzLG2G7faxzwqChcYMDkRFVkzI+o1Mao/Aul1ZQfFxG8xQz0cynfKZ0rnB9xJxv1CC05LTMPV4T/lNqu7JVa1TouV2rF20bm7QRi+0XJsnmqNiXT9u2NrU5TwCRKDxPsgCNLpmPn0u3KoJxxKaYPzewv9tsGA4RJ0/llbRuBEAPtAz7T9uqZkr8M/Xe3by1Gk+B7XTtn0BD/80/2/PP9Ihx0D+dTv1TL9sXYsl8Ob9g1zSMjl0TWDorOA4uL2pWixpgu11T099Hjbw3kNcTiKPLIajqO4KxegS5J0tr+gH59dPa4VRTSGLmUVmsfo46mHogbJh61NQHSuRBLHPavnEFCz+WVzIRCCvUJ+tUSorXTrresq25/bKiJClrirZmlG2vwVWMsa+WvUjXr8bm7l3f1IsZPRbEwbW8zJW4hmixIP7RnqtK1rRIHZXK190la5uFpdGwsCzQY+9gJ5U1DQIV64B2hfU9zqorciuKjHrhozWvr3ED255RGUcYVZZCQCl/5IeixhnkoDGadk1jLPyf4s0YGxijdwbFzX97Zjdh3dBqNSycmqU5qEWkizuzawfD7s/senU4ZhQFPlqc6z8pTsALtM7twG7qdylnUhTmMbGz/J1eNB2OeJ3mb7N6Q+e4L8vf2lWf41wBr5//Oq+bzm3NNgPC5McnpvF98NDLPkYhNL7cGGLCzmOI+5ox5B87A6Q3oofPAHaXMUS7qbSsl8WQajDSFJ04aEC9/PNRnMcK8SSjhRFTFiGpbYU2lxM8ALBL7GTBpGBl2amtYfAQzfyju4Te/NNFePcX</diagram></mxfile>
|
||||
BIN
docu/docs/img/broadcast.png
Normal file
BIN
docu/docs/img/broadcast.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
BIN
docu/docs/img/bw3.png
Normal file
BIN
docu/docs/img/bw3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
1
docu/docs/img/client.drawio
Normal file
1
docu/docs/img/client.drawio
Normal file
|
|
@ -0,0 +1 @@
|
|||
<mxfile host="www.draw.io" modified="2019-10-26T07:55:48.917Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/77.0.3865.90 Chrome/77.0.3865.90 Safari/537.36" etag="82d1pq0jwhTcNaaLB_Ck" version="12.1.7" type="device" pages="1"><diagram id="9aEBdlF2oZdVepulqs3T" name="Page-1">7Vjbcts2EP0aPdrDi0gpj5FsJ9Nxpm7VadMnD0iuSNQgwYJLU+rXFzeSoijJlyhOMq09o9EeAIvdPdhDQhN/mW8+CFJmn3gCbOI5yWbiX008bx5M5acCtgbwZ74BUkETA7k9sKL/gAUdi9Y0gWowETlnSMshGPOigBgHGBGCN8Npa86Gu5YkhRGwigkbo3/QBDObljfr8Y9A06zd2Q3fmZGctJNtJlVGEt7sQP71xF8KztF8yzdLYKp2bV3Mupsjo11gAgp8zoKmWa0XC//q4Sf8rYohvZn/vrqY29hw2yYMiczfmlxgxlNeEHbdowvB6yIB5dWRVj/nlvNSgq4E/wLErSWT1MgllGHO7ChsKH5Wyy/DmTX/1GZgrauNda6N7Y5xB4LmgCBarECx/bxr7HhSZu9KW62vcfFsPSteixhOVKw9hESkgCfmeWaeKufOBpaaD8BlFmIrJwhgBOnj8LgRe2rTbl5PrPxiuX0Bz9bvI2G13WlE/CMIpPLgv2c0LSSEis1Fi96SCNgdryhSrkYjjshzOYHtDcSyooqeBbF+OmDnBPAaGS1g2TWt4mRNGVtyxoWOx1/PY4hjiVco+APsjERSVQK1IhUkodL9FRXSjdm/UAeyW9X2rKeQjJQq13yTKqm6LAAbLh6qS+WF3yNvdJhHj4YqBWxOktmqnm15q3mhNZteQILAQNmudjjOV2I//CZd/uat6T2zNYPvqjW9A60ZMlQNwWUFvDBFXRWDRfuAmbTDbfh3zduBi0qz815OcKflph9svfwKMagMZdUyASRpvcpMDu0u4VEAkXgaqUpSPCtI/3SQpEjUuwTE8jVDx6w+E4JEd5dsLi8kuZKtIqrKYdwmhGFY9PUxCWT363yinrndFvS8aetkT8Y/LvQXZJTX8q0q58VFkZ7KaXQy9pRlqBtNRhFWJdF920jZHWrESNod/Td+GNws1f951NkNn5RnN3DeUp9dd1TGNxDo1wvt9NwCapfecarFzBIV7hHlunsEGKW3q/Y46MJ4PS3Tb6nMnwgtvm9ZXoFWYyPCssUfAPXtrMMqELIrVZXXHRbxqiEYZ52M6/HKXJjijCrxsVLu0KKs0aj7C2Xwxxep6fRpkZq9qUjN/hsvkcEzXyLProFfRE5wVKo6YfilBjl2UDH2eG0vSvFWXtISfSl6okUiw/Nt1AFSDlLN/s/mqnesl7q73LHb3xl6yZ8Gg16ajXspPNBK86/VSe8OkPX/Zdxexhu5nEFV3efy4ZCf5wCE+2IajMXUOXQCghefAGn2P+mZF5H+d1H/+l8=</diagram></mxfile>
|
||||
BIN
docu/docs/img/client.png
Normal file
BIN
docu/docs/img/client.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
1
docu/docs/img/server.drawio
Normal file
1
docu/docs/img/server.drawio
Normal file
|
|
@ -0,0 +1 @@
|
|||
<mxfile host="www.draw.io" modified="2019-10-26T07:56:37.560Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/77.0.3865.90 Chrome/77.0.3865.90 Safari/537.36" etag="JWxWn9rJVMwJN8QniWVz" version="12.1.7" type="device" pages="1"><diagram id="9aEBdlF2oZdVepulqs3T" name="Page-1">7VrbkuI2EP0aHpcCC3N5HGBmt1IzlcmSyu48pYQtbGVki5XlNeTr07Il3wGTZRYqE6gy0tHFLXX3UUuihxbB7qPAW/+Ju4T1rIG766Flz7Km9gieCthnwEjlFOAJ6mbQsABW9G+iwYFGY+qSqFJRcs4k3VZBh4chcWQFw0LwpFptw1n1rVvskQawcjBrol+oK309LGtS4J8I9Xzz5uF4lpUE2FTWI4l87PKkBKH7HloIzmWWCnYLwtTcmXnJ2j0cKM0FEySUXRokyWozn6Pl6y/y98gh3sP0j9UHpLv5jlmsR6yllXszBYLHoUtUL4Memic+lWS1xY4qTUDngPkyYJAbQjKSgr+SBWdcpK3ROP1AyYYyVsI3tvoqnIeyhGcfwDGjXggYIxsY31zLSYQku4MzMMznFeyR8IBIsYcqusFMa0Kb4nCi80mh2PFAY35ZqQbE2pi8vOtiviGhp/yM6bfsxmwTF+xPZ7mQPvd4iNl9gc6r+ijqPHK+1Vr4i0i5186EY8mrOoLpEvuvqn3fNtkX3V2aWe4qub3OZbIqAY8rAMbDY+GQIwPXrCCx8Ig8NkGjdo0KwrCk36uCtKknbXonBN6XKmw5DWVU6vlZAYWhjAd21VLsmmvV6g+nR+tDIpOgMJR8KP/edkYtnjtmUnsUpL3MbTJsXQeySiXLG3+LuSn4EKW2cwcVhqPtrig0vTxhGir9+YJg13QJw2h7NcCNt6/FaSTa4rCThKhNwiUFKqLrWJJUTpIaqvNKlN6V3Dz2/LwEcpKGnqJt4vg4pFFQkj4T5JS4jdFflEYH6adJow8L9b0MP9pWlSBNDyV+zDnz5/Dj8Dr8uKPyayn9UlAl5ApyVBnDjQWnVhi1INi351S7I6f+KKX+kEbtg6yVc8RvMYGyVvKo2QKEVFuVdPaMgsrFab9aZ7bxuM4B4AQvtZhfYwm9kEMOuIZQ1m5xwM3UIY5zGQcc1RwQNR1w3OJ/0zdzP3Td8ORqrjTr6EpD65q+NDsduytzpLCbudPBtFQ6mBv0Ea8Je+YRlZSr0jWXkgdQgdUKHJjB1L1MUJ4DJb3xzIEW+U7smLcc9i9PYJdC90sqoJvs/aEyo7yV2YhZCtEUEOw8tf/sh0QmXLxG/QSaMxJFfwawJQ0u45/T2vo47dvNFbJtB2Hiw8t76OT/BbKzVw+tjm5tX9OrjZTXCew/E4eoId50bF8WUj1dLLE63BFAXtZgzaMES0dF9w5TTKK0FLrKaGKVpukjBKNWODy+1QKO9xLv54dsNxPvm2O1Y0vaT+C3C3KO1ZVzLHRN0rGuSjpP3I1hrb5JrlmkjcELVbxjDbib/kBQQTcOlg0O0sciClmnxw3vkFdQ/fhs2jxobSWWyZvxCrqmeT+z2KPhbZr3fehu9WEm31SMuQ+/K6xW2YXqnqQrKBcp6YFW/Oy8jLDoPS6ddm3pvAETH//nls7OlwRX3YVb5x3Ed3bXVippMNRn2HWrFekwh3T2yktTyxMOsUeixno42DCeKNScwQdm+TdheoMv3wut5JH1zdAKagsM67QSunfqqh1yDsNRRJ1e63HBoG+ZM4KXUtGp84L+pHJLmR8Rvv2BQffg/QADldRmt2jNYOfdZrZcV1aMBqGaMWTj1I0Ke2j0M5qd6Cibh0ZH516votG08h5zMX9QLoSO1T/3ehWyxb8usurFX1fQ/T8=</diagram></mxfile>
|
||||
BIN
docu/docs/img/server.png
Normal file
BIN
docu/docs/img/server.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
18
docu/docs/index.md
Normal file
18
docu/docs/index.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# <center>BOSWatch 3</center>
|
||||
---
|
||||
|
||||
<center>
|
||||

|
||||
|
||||
Falls du uns unterstützen möchtest würden wir uns über eine Spende freuen.
|
||||
Server, Hosting, Domain sowie Kaffee kosten leider Geld ;-)
|
||||
|
||||
[](https://www.paypal.me/BSchroll)
|
||||
</center>
|
||||
|
||||
**Es wird darauf hingewiesen, dass für die Teilnahme am BOS-Funk nur nach den Technischen Richtlinien der BOS zugelassene Funkanlagen verwendet werden dürfen.**
|
||||
**Der BOS-Funk ist ein nichtöffentlicher mobiler Landfunk. Privatpersonen gehören nicht zum Kreis der berechtigten Funkteilnehmer.** _(Quelle: TR-BOS)_
|
||||
|
||||
---
|
||||
|
||||
**The intercept of the German BOS radio is strictly prohibited and will be prosecuted. The use is only permitted for authorized personnel.**
|
||||
31
docu/docs/information/broadcast.md
Normal file
31
docu/docs/information/broadcast.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# <center>Broadcast Service</center>
|
||||
|
||||
Durch den Broadcast Service haben CLients die Möglichkeit, automatisch den Server zu finden und sich mit diesem zu verbinden. Dazu stellt der Server die benötigten Verbinungsinformationen per Broadcast Service bereit.
|
||||
|
||||
**Hinweis:** *Server und Client müssen sich im selben Subnetz befinden.*
|
||||
|
||||
---
|
||||
## Aufbau
|
||||
|
||||
Der Broadcast Service besteht aus 2 Teilen - einem Server und einem Clienten.
|
||||
Nachfolgend soll der Ablauf einer Verbunding des Clienen zum Server mittels des Broadcast Services erklärt werden.
|
||||
|
||||
<center></center>
|
||||
|
||||
---
|
||||
## Ablauf
|
||||
|
||||
### Schritt 1 - Broadcast Server starten
|
||||
Im ersten Schritt wird auf dem Server ein zusätzlicher Broadcast Server in einem seperaten Thread gestartet. Dieser lauscht auf einem festgelegten Port auf UDP Broadcast Pakete. Nun kann eine beliebige Anzahl von Clienten mittels des Broadcast Services die Verbinundgdaten des Server abfragen.
|
||||
|
||||
### Schritt 2 - Broadcast durch Clienten
|
||||
Die Client Applikation startet nun zur Abfrage der Verbindungsdaten einen BC Clienten und sendet dort auf dem festgelegten Port ein Paket per UDP Boradcast. Der Inhalt des Paketes ist das Magic-Word `<BW3-Request>` und wird von allen im selben Subnetz vohandenen Gegenstellen empfangen. Nun wird auf eine Antwort des Broadcast Server mit den Verbindungsdaten gewartet.
|
||||
|
||||
### Schritt 3 - Verbindungsdaten senden
|
||||
Wird nun ein Broadcast Paket empfangen, prüft der BC Server die Daten auf das Magic-Word `<BW3-Request>`. Wird dieses erkannt, liest der Server die Absender-IP-Addresse aus dem Paket aus und sendet eine Antwort direkt an diesen Clienten. Dieses Antwortpaket sieht folgendermaßen aus: `<BW3-Result>;8080` wobei die `8080` hier den normalen TCP Kommunikationsport des Server darstellt.
|
||||
|
||||
### Schritt 4 - Verbindungsdaten empfangen
|
||||
Nachdem der Client das direkt an ihn gerichtete Paket mit den Verbindungsdaten vom Server empfangen hat, prüft er auf das Magic-Word `<BW3-Result>`. Ist dieses enthalten wird der Port für die TCP Verbundindung aus dem Paket extrahiert. Außerdem wird die IP-Addresse des Absenders aus dem Paket gelesen.
|
||||
Anschließend stehen dem Clienten die Verbindungsdaten des Servers zur Verfügung und er kann sich per TCP über den angegebenen Port mit dem BOSWatch Server verbindden um seine Alarmierungs-Pakete zu versenden.
|
||||
|
||||
Da der Broadcast Server in einem eigenen Thread, unabhängig vom Hauptprogram läuft, können ganz einfach weitere Clienten per Broadcast Service die Verbindungsdaten des Servers abrufen.
|
||||
33
docu/docs/information/serverclient.md
Normal file
33
docu/docs/information/serverclient.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# <center>Server/Client Prinzip</center>
|
||||
|
||||
BOSWatch 3 wurde als Server/Client Anwedung entwickelt.
|
||||
|
||||
Dies ermöglicht es, mehrere Empfangsstationen an einer Auswerte- und Verteilereinheit zu bündeln.
|
||||
|
||||
---
|
||||
## BOSWatch Client
|
||||
|
||||
Der **BOSWatch Client** übernimmt den Empfang und die Dekodierung der Daten. Anschließend werden die Daten mittels der implemetierten
|
||||
Dekoder ausgewertet und in ein sogenanntes bwPacket verpackt.
|
||||
|
||||
Dieses Paket wird anschließend in einer Sende-Queue abgelegt. Nun werden Pakete aus der Queue an den BOSWatch Server per TCP-Socket
|
||||
gesendet. Der Ansatz, Pakete statt dem direkten versenden vorher in einer Queue zwischen zu speichern, verhindert den Verlust von
|
||||
Paketen, sollte die Verbindung zum Server einmal abreisen. Nach einer erfolgreichen Wiederverbdingun können die wartenden Pakete nun
|
||||
nachträglich an den Server übermittelt werden.
|
||||
|
||||
Dabei überwacht der Client selbstständig die benötigten Programme zum Empfang der Daten und startet diese bei einem Fehler ggf. neu.
|
||||
|
||||
<center></center>
|
||||
|
||||
---
|
||||
## BOSWatch Server
|
||||
|
||||
Nachdem die Daten vom Clienten über die TCP-Socket Verbindung empfangen wurden, übernimmt der **BOSWatch Server** die weitere
|
||||
Verarbeitung der Daten.
|
||||
|
||||
Auch hier werden die empfangenen Daten in From von bwPacket's in einer Queue abelegt um zu gewährleisten, das auch während einer länger
|
||||
dauernden Plugin Ausführung alle Pakete korrekt empfangen werden können und es zu keinen Verlusten kommt.
|
||||
Die Verarbeitung der Pakete geschieht anschließend in sogenannten Routern, welche aufgrund ihres Umfangs jedoch in einem eigenen Kapitel
|
||||
erklärt werden. Diese steuern die Verteilung der Daten an die einzelnen Plugins.
|
||||
|
||||
<center></center>
|
||||
39
docu/docs/modul/mode_filter.md
Normal file
39
docu/docs/modul/mode_filter.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# <center>Mode Filter</center>
|
||||
---
|
||||
|
||||
## Beschreibung
|
||||
Mit diesem Modul ist es möglich, die Pakete auf bestimmte Modes (FMS, POCSAG, ZVEI) zu Filtern. Je nach Konfiguration werden Pakete eines bestimmten Modes im aktuellen Router weitergeleitet oder verworfen.
|
||||
|
||||
## Resource
|
||||
`filter.modeFilter`
|
||||
|
||||
## Konfiguration
|
||||
|
||||
|Feld|Beschreibung|Default|
|
||||
|----|------------|-------|
|
||||
|allowed|Liste der erlaubten Paket Typen `fms` `zvei` `pocsag` `msg`||
|
||||
|
||||
**Beispiel:**
|
||||
```yaml
|
||||
- type: module
|
||||
res: filter.modeFilter
|
||||
config:
|
||||
allowed:
|
||||
- fms
|
||||
- pocsag
|
||||
```
|
||||
|
||||
---
|
||||
## Abhängigkeiten
|
||||
|
||||
- keine
|
||||
|
||||
---
|
||||
## Paket Modifikationen
|
||||
|
||||
- keine
|
||||
|
||||
---
|
||||
## Zusätzliche Wildcards
|
||||
|
||||
- keine
|
||||
70
docu/docs/modul/regex_filter.md
Normal file
70
docu/docs/modul/regex_filter.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# <center>Regex Filter</center>
|
||||
---
|
||||
|
||||
## Beschreibung
|
||||
Mit diesem Modul ist es möglich, komplexe Filter basierend auf Regulären Ausdrücken (Regex) anzulegen.
|
||||
Für einen Filter können beliebig viele Checks angelegt werden, welche Felder eines BOSWatch Pakets mittels Regex prüfen.
|
||||
|
||||
Folgendes gilt:
|
||||
|
||||
- Die Filter werden nacheinander abgearbeitet
|
||||
- Innerhalb des Filters werden die Checks nacheinander abgearbeitet
|
||||
- Sobald ein einzelner Check fehlschlägt ist der ganze Filter fehlgeschlagen
|
||||
- Sobald ein Filter mit all seinen Checks besteht, wird mit der Ausführung des Routers fortgefahren
|
||||
- Sollten alle Filter fehlschlagen wird die Ausführung des Routers beendet
|
||||
|
||||
## Resource
|
||||
`filter.regexFilter`
|
||||
|
||||
## Konfiguration
|
||||
|
||||
|Feld|Beschreibung|Default|
|
||||
|----|------------|-------|
|
||||
|filter|Enthält eine Liste der einzelnen Filter||
|
||||
|
||||
#### `filter:`
|
||||
|
||||
|Feld|Beschreibung|Default|
|
||||
|----|------------|-------|
|
||||
|name|Beliebiger Name des Filters||
|
||||
|checks|Liste der einzelnen Checks innerhalb des Filters||
|
||||
|
||||
#### `checks:`
|
||||
|
||||
|Feld|Beschreibung|Default|
|
||||
|----|------------|-------|
|
||||
|field|Name des Feldes innerhalb des BOSWatch Pakets welches untersucht werden soll||
|
||||
|regex|Regulärer Ausdruck (Bei Sonderzeichen " " verwenden)||
|
||||
|
||||
**Beispiel:**
|
||||
```yaml
|
||||
- type: module
|
||||
res: filter.regexFilter
|
||||
config:
|
||||
filter:
|
||||
- name: "Zvei filter"
|
||||
checks:
|
||||
- field: zvei
|
||||
regex: "65[0-9]{3}" # all zvei with starting 65
|
||||
- name: "FMS Stat 3"
|
||||
checks:
|
||||
- field: mode
|
||||
regex: "fms" # check if mode is fms
|
||||
- field: status
|
||||
regex: "3" # check if status is 3
|
||||
```
|
||||
|
||||
---
|
||||
## Abhängigkeiten
|
||||
|
||||
- keine
|
||||
|
||||
---
|
||||
## Paket Modifikationen
|
||||
|
||||
- keine
|
||||
|
||||
---
|
||||
## Zusätzliche Wildcards
|
||||
|
||||
- keine
|
||||
4
docu/docs/tbd.md
Normal file
4
docu/docs/tbd.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# <center>To be done ...</center>
|
||||
---
|
||||
|
||||
Hier existiert noch kein Inhalt, gerne kannst du uns aber Helfen die Dokumentation zu vervollständigen.
|
||||
|
|
@ -58,7 +58,7 @@ PROJECT_LOGO =
|
|||
# entered, it will be relative to the location where doxygen was started. If
|
||||
# left blank the current directory will be used.
|
||||
|
||||
OUTPUT_DIRECTORY = _docu
|
||||
OUTPUT_DIRECTORY = docu/docs/api/
|
||||
|
||||
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
|
||||
# directories (in 2 levels) under the output directory of each output format and
|
||||
|
|
@ -92,6 +92,7 @@ ALLOW_UNICODE_NAMES = NO
|
|||
# The default value is: English.
|
||||
|
||||
OUTPUT_LANGUAGE = English
|
||||
#German
|
||||
|
||||
# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
|
||||
# descriptions after the members that are listed in the file and class
|
||||
|
|
@ -666,21 +667,21 @@ MAX_INITIALIZER_LINES = 30
|
|||
# list will mention the files that were used to generate the documentation.
|
||||
# The default value is: YES.
|
||||
|
||||
SHOW_USED_FILES = YES
|
||||
SHOW_USED_FILES = NO
|
||||
|
||||
# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
|
||||
# will remove the Files entry from the Quick Index and from the Folder Tree View
|
||||
# (if specified).
|
||||
# The default value is: YES.
|
||||
|
||||
SHOW_FILES = YES
|
||||
SHOW_FILES = NO
|
||||
|
||||
# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
|
||||
# page. This will remove the Namespaces entry from the Quick Index and from the
|
||||
# Folder Tree View (if specified).
|
||||
# The default value is: YES.
|
||||
|
||||
SHOW_NAMESPACES = YES
|
||||
SHOW_NAMESPACES = NO
|
||||
|
||||
# The FILE_VERSION_FILTER tag can be used to specify a program or script that
|
||||
# doxygen should invoke to get the current version for each file (typically from
|
||||
|
|
@ -756,7 +757,7 @@ WARN_IF_DOC_ERROR = YES
|
|||
# parameter documentation, but not about the absence of documentation.
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_NO_PARAMDOC = NO
|
||||
WARN_NO_PARAMDOC = YES
|
||||
|
||||
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
|
||||
# a warning is encountered.
|
||||
|
|
@ -875,7 +876,9 @@ RECURSIVE = YES
|
|||
|
||||
EXCLUDE = bw_client.py \
|
||||
bw_server.py \
|
||||
_info/FileHead.template.py
|
||||
FileHead.template.py \
|
||||
venv \
|
||||
docu
|
||||
|
||||
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
|
||||
# directories that are symbolic links (a Unix file system feature) are excluded
|
||||
37
docu/mkdocs.yml
Normal file
37
docu/mkdocs.yml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
site_name: BOSWatch3 Core
|
||||
site_author: Bastian Schroll & BW3 Dev team
|
||||
|
||||
repo_url: https://github.com/BOSWatch/BW3-Core
|
||||
edit_uri: edit/develop/docu/docs/
|
||||
|
||||
nav:
|
||||
# - BW3: index.md
|
||||
- Quick Start:
|
||||
- Installation: tbd.md
|
||||
- Konfiguration: config.md
|
||||
# - BOSWatch benutzen: tbd.md
|
||||
# - Als Service einrichten: tbd.md
|
||||
- Informationen:
|
||||
- Server/Cient Prinzip: information/serverclient.md
|
||||
- Broadcast Service: information/broadcast.md
|
||||
# - Modul/Plugin Konzept: tbd.md
|
||||
# - Routing Mechanismus: tbd.md
|
||||
- Changelog: changelog.md
|
||||
- Module:
|
||||
- Mode Filter: modul/mode_filter.md
|
||||
- Regex Filter: modul/regex_filter.md
|
||||
- Plugins: tbd.md
|
||||
- Entwickler:
|
||||
- Eigenes Modul/Plugin schreiben: develop/ModulPlugin.md
|
||||
- BOSWatch Alarmpaket Format: develop/packet.md
|
||||
- BW3 Quellcode Dokumentation: api/html/index.html
|
||||
|
||||
|
||||
use_directory_urls: false
|
||||
|
||||
theme:
|
||||
name: mkdocs
|
||||
highlightjs: true
|
||||
hljs_style: github
|
||||
|
||||
|
||||
|
|
@ -13,23 +13,20 @@
|
|||
@date: 15.01.2018
|
||||
@author: Bastian Schroll
|
||||
@description: Class to implement a filter for double alarms
|
||||
@todo test, refactor and document
|
||||
@todo check_msg is not implemented yet
|
||||
@todo test, refactor and document / check_msg is not implemented yet
|
||||
"""
|
||||
import logging
|
||||
import time
|
||||
|
||||
from boswatch.config import Config
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class DoubleFilter:
|
||||
"""!Double Filter Class"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, config):
|
||||
"""!init"""
|
||||
self._config = Config()
|
||||
self._config = config
|
||||
self._filterLists = {}
|
||||
|
||||
def filter(self, bwPacket):
|
||||
|
|
@ -58,14 +55,14 @@ class DoubleFilter:
|
|||
# delete entries that are to old
|
||||
counter = 0
|
||||
for listPacket in self._filterLists[bwPacket.get("mode")][1:]: # [1:] skip first entry, thats the new one
|
||||
if listPacket.get("timestamp") < (time.time() - self._config.getInt("doubleFilter", "IgnoreTime", "serverConfig")):
|
||||
if listPacket.get("timestamp") < (time.time() - self._config["ignoreTime"]):
|
||||
self._filterLists[bwPacket.get("mode")].remove(listPacket)
|
||||
counter += 1
|
||||
if counter:
|
||||
logging.debug("%d old entry(s) removed", counter)
|
||||
|
||||
# delete last entry if list is to big
|
||||
if len(self._filterLists[bwPacket.get("mode")]) > self._config.getInt("doubleFilter", "MaxEntry", "serverConfig"):
|
||||
if len(self._filterLists[bwPacket.get("mode")]) > self._config["maxEntry"]:
|
||||
logging.debug("MaxEntry reached - delete oldest")
|
||||
self._filterLists[bwPacket.get("mode")].pop()
|
||||
|
||||
52
module/filter/modeFilter.py
Normal file
52
module/filter/modeFilter.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: modeFilter.py
|
||||
@date: 09.03.2019
|
||||
@author: Bastian Schroll
|
||||
@description: Filter module for the packet type
|
||||
"""
|
||||
import logging
|
||||
from module.module import Module
|
||||
|
||||
# ###################### #
|
||||
# Custom plugin includes #
|
||||
|
||||
# ###################### #
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class BoswatchModule(Module):
|
||||
"""!Filter of specific bwPacket mode"""
|
||||
def __init__(self, config):
|
||||
"""!Do not change anything here!"""
|
||||
super().__init__(__name__, config) # you can access the config class on 'self.config'
|
||||
|
||||
def onLoad(self):
|
||||
"""!Called by import of the plugin"""
|
||||
pass
|
||||
|
||||
def doWork(self, bwPacket):
|
||||
"""!start an run of the module.
|
||||
|
||||
@param bwPacket: A BOSWatch packet instance"""
|
||||
|
||||
for mode in self.config.get("allowed", default=[]):
|
||||
if bwPacket.get("mode") == mode:
|
||||
logging.debug("mode is allowed: %s", bwPacket.get("mode"))
|
||||
return None
|
||||
logging.debug("mode is denied: %s", bwPacket.get("mode"))
|
||||
return False
|
||||
|
||||
def onUnload(self):
|
||||
"""!Called by destruction of the plugin"""
|
||||
pass
|
||||
65
module/filter/regexFilter.py
Normal file
65
module/filter/regexFilter.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: regexFilter.py
|
||||
@date: 26.10.2019
|
||||
@author: Bastian Schroll
|
||||
@description: Regex filter module
|
||||
"""
|
||||
import logging
|
||||
from module.module import Module
|
||||
|
||||
# ###################### #
|
||||
# Custom plugin includes #
|
||||
import re
|
||||
# ###################### #
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class BoswatchModule(Module):
|
||||
"""!Description of the Module"""
|
||||
def __init__(self, config):
|
||||
"""!Do not change anything here!"""
|
||||
super().__init__(__name__, config) # you can access the config class on 'self.config'
|
||||
|
||||
def onLoad(self):
|
||||
"""!Called by import of the plugin"""
|
||||
pass
|
||||
|
||||
def doWork(self, bwPacket):
|
||||
"""!start an run of the module.
|
||||
|
||||
@param bwPacket: A BOSWatch packet instance"""
|
||||
for filter in self.config.get("filter"):
|
||||
checkFailed = False
|
||||
logging.debug("try filter '%s' with %d check(s)", filter.get("name"), len(filter.get("checks")))
|
||||
|
||||
for check in filter.get("checks"):
|
||||
fieldData = bwPacket.get(check.get("field"))
|
||||
|
||||
if not fieldData or not re.search(check.get("regex"), fieldData):
|
||||
logging.debug("[-] field '%s' with regex '%s'", check.get("field"), check.get("regex"))
|
||||
checkFailed = True
|
||||
break # if one check failed we break this filter
|
||||
else:
|
||||
logging.debug("[+] field '%s' with regex '%s'", check.get("field"), check.get("regex"))
|
||||
|
||||
if not checkFailed:
|
||||
logging.debug("[PASSED] filter '%s'", filter.get("name"))
|
||||
return None # None -> Router will go on with this packet
|
||||
logging.debug("[FAILED] filter '%s'", filter.get("name"))
|
||||
|
||||
return False # False -> Router will stop further processing
|
||||
|
||||
def onUnload(self):
|
||||
"""!Called by destruction of the plugin"""
|
||||
pass
|
||||
116
module/module.py
Normal file
116
module/module.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: module.py
|
||||
@date: 01.03.2019
|
||||
@author: Bastian Schroll
|
||||
@description: Module main class to inherit
|
||||
"""
|
||||
import logging
|
||||
import time
|
||||
|
||||
from boswatch import wildcard
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class Module:
|
||||
"""!Main module class"""
|
||||
|
||||
_modulesActive = []
|
||||
|
||||
def __init__(self, moduleName, config):
|
||||
"""!init preload some needed locals and then call onLoad() directly"""
|
||||
self._moduleName = moduleName
|
||||
self.config = config
|
||||
self._modulesActive.append(self)
|
||||
|
||||
# for time counting
|
||||
self._cumTime = 0
|
||||
self._moduleTime = 0
|
||||
|
||||
# for statistics
|
||||
self._runCount = 0
|
||||
self._moduleErrorCount = 0
|
||||
|
||||
logging.debug("[%s] onLoad()", moduleName)
|
||||
self.onLoad()
|
||||
|
||||
def _cleanup(self):
|
||||
"""!Cleanup routine calls onUnload() directly"""
|
||||
logging.debug("[%s] onUnload()", self._moduleName)
|
||||
self._modulesActive.remove(self)
|
||||
self.onUnload()
|
||||
|
||||
def _run(self, bwPacket):
|
||||
"""!start an run of the module.
|
||||
|
||||
@param bwPacket: A BOSWatch packet instance
|
||||
@return bwPacket or False"""
|
||||
self._runCount += 1
|
||||
logging.debug("[%s] run #%d", self._moduleName, self._runCount)
|
||||
|
||||
tmpTime = time.time()
|
||||
try:
|
||||
logging.debug("[%s] doWork()", self._moduleName)
|
||||
bwPacket = self.doWork(bwPacket)
|
||||
except:
|
||||
self._moduleErrorCount += 1
|
||||
logging.exception("[%s] alarm error", self._moduleName)
|
||||
self._moduleTime = time.time() - tmpTime
|
||||
|
||||
self._cumTime += self._moduleTime
|
||||
|
||||
logging.debug("[%s] took %0.3f seconds", self._moduleName, self._moduleTime)
|
||||
|
||||
return bwPacket
|
||||
|
||||
def _getStatistics(self):
|
||||
"""!Returns statistical information's from last module run
|
||||
|
||||
@return Statistics as pyton dict"""
|
||||
stats = {"type": "module",
|
||||
"runCount": self._runCount,
|
||||
"cumTime": self._cumTime,
|
||||
"moduleTime": self._moduleTime,
|
||||
"moduleErrorCount": self._moduleErrorCount}
|
||||
return stats
|
||||
|
||||
def onLoad(self):
|
||||
"""!Called by import of the module
|
||||
can be inherited"""
|
||||
pass
|
||||
|
||||
def doWork(self, bwPacket):
|
||||
"""!Called module run
|
||||
can be inherited
|
||||
|
||||
@param bwPacket: bwPacket instance"""
|
||||
logging.warning("no functionality in module %s", self._moduleName)
|
||||
|
||||
def onUnload(self):
|
||||
"""!Called on shutdown of boswatch
|
||||
can be inherited"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def registerWildcard(newWildcard, bwPacketField):
|
||||
"""!Register a new wildcard
|
||||
|
||||
@param newWildcard: wildcard where parser searching for
|
||||
@param bwPacketField: field from bwPacket where holds replacement data"""
|
||||
if not newWildcard.startswith("{") or not newWildcard.endswith("}"):
|
||||
logging.error("wildcard not registered - false format: %s", newWildcard)
|
||||
return
|
||||
if bwPacketField == "":
|
||||
logging.error("wildcard not registered - bwPacket field is empty")
|
||||
return
|
||||
wildcard.registerWildcard(newWildcard, bwPacketField)
|
||||
55
module/template_module.py
Normal file
55
module/template_module.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: template_module.py
|
||||
@date: 01.03.2019
|
||||
@author: Bastian Schroll
|
||||
@description: Template Module File
|
||||
"""
|
||||
import logging
|
||||
from module.module import Module
|
||||
|
||||
# ###################### #
|
||||
# Custom plugin includes #
|
||||
|
||||
# ###################### #
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class BoswatchModule(Module):
|
||||
"""!Description of the Module"""
|
||||
def __init__(self, config):
|
||||
"""!Do not change anything here!"""
|
||||
super().__init__(__name__, config) # you can access the config class on 'self.config'
|
||||
|
||||
def onLoad(self):
|
||||
"""!Called by import of the plugin"""
|
||||
pass
|
||||
|
||||
def doWork(self, bwPacket):
|
||||
"""!start an run of the module.
|
||||
|
||||
@param bwPacket: A BOSWatch packet instance"""
|
||||
if bwPacket.get("mode") == "fms":
|
||||
pass
|
||||
elif bwPacket.get("mode") == "zvei":
|
||||
pass
|
||||
elif bwPacket.get("mode") == "pocsag":
|
||||
pass
|
||||
elif bwPacket.get("mode") == "msg":
|
||||
pass
|
||||
|
||||
return bwPacket
|
||||
|
||||
def onUnload(self):
|
||||
"""!Called by destruction of the plugin"""
|
||||
pass
|
||||
|
|
@ -17,9 +17,7 @@
|
|||
import logging
|
||||
import time
|
||||
|
||||
from boswatch.utils import paths
|
||||
from boswatch.config import Config
|
||||
from boswatch.utils import wildcard
|
||||
from boswatch import wildcard
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
|
@ -27,12 +25,13 @@ logging.debug("- %s loaded", __name__)
|
|||
class Plugin:
|
||||
"""!Main plugin class"""
|
||||
|
||||
_pluginsActive = 0
|
||||
_pluginsActive = []
|
||||
|
||||
def __init__(self, pluginName):
|
||||
def __init__(self, pluginName, config):
|
||||
"""!init preload some needed locals and then call onLoad() directly"""
|
||||
self._pluginName = pluginName
|
||||
self._pluginsActive += 1
|
||||
self.config = config
|
||||
self._pluginsActive.append(self)
|
||||
|
||||
# to save the packet while alarm is running for other functions
|
||||
self._bwPacket = None
|
||||
|
|
@ -43,8 +42,6 @@ class Plugin:
|
|||
self._setupTime = 0
|
||||
self._alarmTime = 0
|
||||
self._teardownTime = 0
|
||||
self._endTime = 0
|
||||
self._tmpTime = 0
|
||||
|
||||
# for statistics
|
||||
self._runCount = 0
|
||||
|
|
@ -52,19 +49,13 @@ class Plugin:
|
|||
self._alarmErrorCount = 0
|
||||
self._teardownErrorCount = 0
|
||||
|
||||
if paths.fileExist(paths.PLUGIN_PATH + pluginName + "/" + pluginName + ".ini"):
|
||||
self.config = Config()
|
||||
self.config.loadConfigFile(paths.PLUGIN_PATH + pluginName + "/" + pluginName + ".ini")
|
||||
else:
|
||||
logging.debug("no config for %s found", pluginName)
|
||||
|
||||
logging.debug("[%s] onLoad()", pluginName)
|
||||
self.onLoad()
|
||||
|
||||
def __del__(self):
|
||||
"""!Destructor calls onUnload() directly"""
|
||||
def _cleanup(self):
|
||||
"""!Cleanup routine calls onUnload() directly"""
|
||||
logging.debug("[%s] onUnload()", self._pluginName)
|
||||
self._pluginsActive -= 1
|
||||
self._pluginsActive.remove(self)
|
||||
self.onUnload()
|
||||
|
||||
def _run(self, bwPacket):
|
||||
|
|
@ -78,7 +69,7 @@ class Plugin:
|
|||
|
||||
self._bwPacket = bwPacket
|
||||
|
||||
self._tmpTime = time.time()
|
||||
tmpTime = time.time()
|
||||
try:
|
||||
logging.debug("[%s] setup()", self._pluginName)
|
||||
self.setup()
|
||||
|
|
@ -86,8 +77,8 @@ class Plugin:
|
|||
self._setupErrorCount += 1
|
||||
logging.exception("[%s] error in setup()", self._pluginName)
|
||||
|
||||
self._setupTime = time.time() - self._tmpTime
|
||||
self._tmpTime = time.time()
|
||||
self._setupTime = time.time() - tmpTime
|
||||
tmpTime = time.time()
|
||||
try:
|
||||
|
||||
if bwPacket.get("mode") == "fms":
|
||||
|
|
@ -106,8 +97,8 @@ class Plugin:
|
|||
self._alarmErrorCount += 1
|
||||
logging.exception("[%s] alarm error", self._pluginName)
|
||||
|
||||
self._alarmTime = time.time() - self._tmpTime
|
||||
self._tmpTime = time.time()
|
||||
self._alarmTime = time.time() - tmpTime
|
||||
tmpTime = time.time()
|
||||
try:
|
||||
logging.debug("[%s] teardown()", self._pluginName)
|
||||
self.teardown()
|
||||
|
|
@ -115,10 +106,9 @@ class Plugin:
|
|||
self._teardownErrorCount += 1
|
||||
logging.exception("[%s] error in teardown()", self._pluginName)
|
||||
|
||||
self._teardownTime = time.time() - self._tmpTime
|
||||
self._teardownTime = time.time() - tmpTime
|
||||
self._sumTime = self._setupTime + self._alarmTime + self._teardownTime
|
||||
self._cumTime += self._sumTime
|
||||
self._endTime = time.time()
|
||||
|
||||
self._bwPacket = None
|
||||
|
||||
|
|
@ -127,11 +117,14 @@ class Plugin:
|
|||
# logging.debug("- alarm: %0.2f sec.", self._alarmTime)
|
||||
# logging.debug("- teardown: %0.2f sec.", self._teardownTime)
|
||||
|
||||
return None
|
||||
|
||||
def _getStatistics(self):
|
||||
"""!Returns statistical information's from last plugin run
|
||||
|
||||
@return Statistics as pyton dict"""
|
||||
stats = {"runCount": self._runCount,
|
||||
stats = {"type": "plugin",
|
||||
"runCount": self._runCount,
|
||||
"sumTime": self._sumTime,
|
||||
"cumTime": self._cumTime,
|
||||
"setupTime": self._setupTime,
|
||||
|
|
@ -144,50 +137,50 @@ class Plugin:
|
|||
|
||||
def onLoad(self):
|
||||
"""!Called by import of the plugin
|
||||
Must be inherit"""
|
||||
can be inherited"""
|
||||
pass
|
||||
|
||||
def setup(self):
|
||||
"""!Called before alarm
|
||||
Must be inherit"""
|
||||
can be inherited"""
|
||||
pass
|
||||
|
||||
def fms(self, bwPacket):
|
||||
"""!Called on FMS alarm
|
||||
Must be inherit
|
||||
can be inherited
|
||||
|
||||
@param bwPacket: bwPacket instance"""
|
||||
logging.warning("ZVEI not implemented in %s", self._pluginName)
|
||||
|
||||
def pocsag(self, bwPacket):
|
||||
"""!Called on POCSAG alarm
|
||||
Must be inherit
|
||||
can be inherited
|
||||
|
||||
@param bwPacket: bwPacket instance"""
|
||||
logging.warning("POCSAG not implemented in %s", self._pluginName)
|
||||
|
||||
def zvei(self, bwPacket):
|
||||
"""!Called on ZVEI alarm
|
||||
Must be inherit
|
||||
can be inherited
|
||||
|
||||
@param bwPacket: bwPacket instance"""
|
||||
logging.warning("ZVEI not implemented in %s", self._pluginName)
|
||||
|
||||
def msg(self, bwPacket):
|
||||
"""!Called on MSG packet
|
||||
Must be inherit
|
||||
can be inherited
|
||||
|
||||
@param bwPacket: bwPacket instance"""
|
||||
logging.warning("MSG not implemented in %s", self._pluginName)
|
||||
|
||||
def teardown(self):
|
||||
"""!Called after alarm
|
||||
Must be inherit"""
|
||||
can be inherited"""
|
||||
pass
|
||||
|
||||
def onUnload(self):
|
||||
"""!Called by destruction of the plugin
|
||||
Must be inherit"""
|
||||
"""!Called on shutdown of boswatch
|
||||
can be inherited"""
|
||||
pass
|
||||
|
||||
def parseWildcards(self, msg):
|
||||
|
|
@ -9,13 +9,13 @@
|
|||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: template.py
|
||||
@file: template_module.py
|
||||
@date: 14.01.2018
|
||||
@author: Bastian Schroll
|
||||
@description: Template Plugin File
|
||||
"""
|
||||
import logging
|
||||
from boswatch.plugin.plugin import Plugin
|
||||
from plugin.plugin import Plugin
|
||||
|
||||
# ###################### #
|
||||
# Custom plugin includes #
|
||||
|
|
@ -27,10 +27,9 @@ logging.debug("- %s loaded", __name__)
|
|||
|
||||
class BoswatchPlugin(Plugin):
|
||||
"""!Description of the Plugin"""
|
||||
def __init__(self):
|
||||
"""!Do not change anything here except the PLUGIN NAME in the super() call"""
|
||||
# PLEASE SET YOU PLUGIN NAME HERE !!!!
|
||||
Plugin.__init__("template")
|
||||
def __init__(self, config):
|
||||
"""!Do not change anything here!"""
|
||||
super().__init__(__name__, config) # you can access the config class on 'self.config'
|
||||
|
||||
def onLoad(self):
|
||||
"""!Called by import of the plugin"""
|
||||
|
|
@ -56,11 +55,13 @@ class BoswatchPlugin(Plugin):
|
|||
"""!Called on ZVEI alarm
|
||||
|
||||
@param bwPacket: bwPacket instance"""
|
||||
pass
|
||||
|
||||
def msg(self, bwPacket):
|
||||
"""!Called on MSG packet
|
||||
|
||||
@param bwPacket: bwPacket instance"""
|
||||
pass
|
||||
|
||||
def teardown(self):
|
||||
"""!Called after alarm"""
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
## Eigene Plugins schreiben
|
||||
|
||||
Um ein eigenes Plugin zu schreiben, sollte man sich am besten zuerst einmal das Plugin `template` ansehen.
|
||||
Dies kann als Vorlage für das eigene Plugin genutzt werden.
|
||||
|
||||
### 1.) Informationen anpassen
|
||||
- Dateikopf anpassen
|
||||
- Namen des Plugins vergeben in der `__init__` Methode `super().__init__("template")`
|
||||
|
||||
### 2.) Benötigte Methode überschreiben
|
||||
Die Plugin Basisklasse bietet einige Methoden, welche vom Plugin überschrieben werden können.
|
||||
- `onLoad()` wird direkt beim Import des Plugins ausgeführt
|
||||
- `setup()` wird vor jeder Ausführung gerufen
|
||||
- `fms(bwPacket)` wird bei einem FMS Paket ausgeführt
|
||||
- `pocsag(bwPacket)` wird bei einem POCSAG Paket ausgeführt
|
||||
- `zvei(bwPacket)` wird bei einem ZVEI Packet ausgeführt
|
||||
- `msg(bwPacket)` wird bei einem Nachrichten Packet ausgeführt
|
||||
- `teardown()` wird nach jeder Ausführung gerufen
|
||||
- `onUnload()` wird beim Zerstören der Plugin Instanz zum Programmende ausgeführt
|
||||
|
||||
### 3.) Zugriff auf Config Datei
|
||||
Wenn sich im Ordner des Plugins eine ini-Datei befindet,
|
||||
welche exakt so wie das Plugin heißt, kann deren Inhalt
|
||||
über die lokale Config-Reader Instanz
|
||||
- `self.config.getBool(SECTION, KEY)`
|
||||
- `self.config.getInt(SECTION, KEY)`
|
||||
- `self.config.getStr(SECTION, KEY)`
|
||||
|
||||
abgerufen werden.
|
||||
|
||||
### 4.) Daten aus dem BOSWatch Paket lesen
|
||||
An die Alarm Funktionen FMS, POCSAG und ZVEI wird eine Instanz eines
|
||||
BOSWatch-Packet Objekts übergeben.
|
||||
|
||||
Aus dieser kann mittels `bwPacket.get(FELDNAME)` das entsprechende Feld
|
||||
ausgelesen werden. Eine Auflistung der bereitgestellten Informationen
|
||||
findet sich im entsprechenden BOSWatch-Packet Dokument.
|
||||
|
||||
### 5.) Wildcards parsen
|
||||
Das parsen der Wildcars funktioniert komfortabel über die interne Methode `self.parseWildcards(MSG)`.
|
||||
Die Platzhalter für die Wildcards findet man in `boswatch/utils/wildcard.py` oder in der `packet.md`-
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[Example]
|
||||
String = Hello World!
|
||||
bool = 1
|
||||
integer = 12
|
||||
|
|
@ -1,7 +1,14 @@
|
|||
# for pip to install all needed
|
||||
# called with 'pip -r requirements.txt'
|
||||
|
||||
pyyaml
|
||||
|
||||
# for documentation generating
|
||||
mkdocs
|
||||
|
||||
# for develope only
|
||||
pytest
|
||||
pytest-cov
|
||||
pytest-pep8
|
||||
pytest-randomly
|
||||
pytest-flakes
|
||||
pytest-randomly
|
||||
3
some_old_stuff.txt
Normal file
3
some_old_stuff.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
python -m pytest -c "_gen/pytest.ini"
|
||||
_bin\win\doxygen\doxygen.exe _gen/doxygen.ini
|
||||
_bin\win\cloc_1_72\cloc-1.72.exe . --exclude-lang=XML --exclude-dir=_docu,_config,_info,doxygen.ini --by-file-by-lang
|
||||
1
test/boswatch/__init__.py
Normal file
1
test/boswatch/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# coding=utf-8
|
||||
247
test/boswatch/test_ServerClient.py
Normal file
247
test/boswatch/test_ServerClient.py
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: test_ServerClient.py
|
||||
@date: 10.12.2017
|
||||
@author: Bastian Schroll
|
||||
@description: Unittests for BOSWatch. File have to run as "pytest" unittest
|
||||
"""
|
||||
# problem of the pytest fixtures
|
||||
# pylint: disable=redefined-outer-name
|
||||
import logging
|
||||
import time
|
||||
import queue
|
||||
import pytest
|
||||
|
||||
from boswatch.network.server import TCPServer
|
||||
from boswatch.network.client import TCPClient
|
||||
import threading
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
logging.debug("[TEST] %s.%s", function.__module__, function.__name__)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def getClient():
|
||||
"""!Build and serve a TCPCLient"""
|
||||
return TCPClient()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def getServer():
|
||||
"""!Build and serve a TCPServer"""
|
||||
dataQueue = queue.Queue()
|
||||
testServer = TCPServer(dataQueue)
|
||||
return testServer
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def getRunningServer(getServer):
|
||||
"""!Build and serve a still running TCPServer"""
|
||||
logging.debug("start server")
|
||||
assert getServer.start()
|
||||
while not getServer.isRunning:
|
||||
pass
|
||||
yield getServer
|
||||
logging.debug("stop server")
|
||||
assert getServer.stop()
|
||||
time.sleep(0.1) # wait for safe stopped
|
||||
|
||||
|
||||
def test_clientConnectFailed(getClient):
|
||||
"""!Connect to a non available server"""
|
||||
assert not getClient.connect()
|
||||
|
||||
|
||||
def test_clientDisconnectFailed(getClient):
|
||||
"""!Disconnect while no connection is established"""
|
||||
assert getClient.disconnect()
|
||||
|
||||
|
||||
def test_clientTransmitFailed(getClient):
|
||||
"""!Transmit while no connection is established"""
|
||||
assert not getClient.transmit("test")
|
||||
|
||||
|
||||
def test_clientReceiveFailed(getClient):
|
||||
"""!Receive while no connection is established"""
|
||||
assert not getClient.receive()
|
||||
|
||||
|
||||
def test_clientConnect(getClient, getRunningServer):
|
||||
"""!Connect to a server"""
|
||||
assert getClient.connect()
|
||||
assert getClient.disconnect()
|
||||
|
||||
|
||||
def test_doubleConnect(getClient, getRunningServer):
|
||||
"""!Connect to a server twice"""
|
||||
assert getClient.connect()
|
||||
assert getClient.connect()
|
||||
assert getClient.disconnect()
|
||||
|
||||
|
||||
def test_clientReconnect(getClient, getRunningServer):
|
||||
"""!Try a reconnect after a established connection"""
|
||||
assert getClient.connect()
|
||||
assert getClient.disconnect()
|
||||
assert getClient.connect()
|
||||
assert getClient.disconnect()
|
||||
|
||||
|
||||
def test_clientMultiConnect(getClient, getRunningServer):
|
||||
"""!Connect with 2 clients to the server"""
|
||||
assert getClient.connect()
|
||||
testClient2 = TCPClient()
|
||||
assert testClient2.connect()
|
||||
time.sleep(0.1) # wait for all clients connected
|
||||
# check connected clients
|
||||
assert getRunningServer.countClientsConnected() == 2
|
||||
# disconnect all
|
||||
assert getClient.disconnect()
|
||||
assert testClient2.disconnect()
|
||||
|
||||
|
||||
def test_clientCommunicate(getClient, getRunningServer):
|
||||
"""!Try to send data to the server and check on '[ack]'"""
|
||||
assert getClient.connect()
|
||||
assert getClient.transmit("test")
|
||||
assert getClient.receive() == "[ack]"
|
||||
assert getClient.disconnect()
|
||||
|
||||
|
||||
@pytest.mark.skip("needs fixture for more than one client")
|
||||
def test_clientMultiCommunicate(getServer):
|
||||
"""!Try to send data to the server with 3 clients and check on '[ack]'"""
|
||||
# connect all
|
||||
testClient1 = TCPClient()
|
||||
assert testClient1.connect()
|
||||
testClient2 = TCPClient()
|
||||
assert testClient2.connect()
|
||||
testClient3 = TCPClient()
|
||||
assert testClient3.connect()
|
||||
# send all
|
||||
assert testClient1.transmit("test")
|
||||
assert testClient2.transmit("test")
|
||||
assert testClient3.transmit("test")
|
||||
# recv all
|
||||
assert testClient3.receive() == "[ack]"
|
||||
assert testClient2.receive() == "[ack]"
|
||||
assert testClient1.receive() == "[ack]"
|
||||
# check server msg queue
|
||||
assert getRunningServer._alarmQueue.qsize() == 3
|
||||
# disconnect all
|
||||
assert testClient1.disconnect()
|
||||
assert testClient2.disconnect()
|
||||
assert testClient3.disconnect()
|
||||
|
||||
|
||||
def test_serverRestart(getRunningServer):
|
||||
"""!Test a stop and restart of the server"""
|
||||
assert getRunningServer.stop()
|
||||
assert getRunningServer.start()
|
||||
assert getRunningServer.stop()
|
||||
|
||||
|
||||
def test_serverStopFailed(getServer):
|
||||
"""!Test to stop a stopped server"""
|
||||
assert getServer.stop()
|
||||
|
||||
|
||||
def test_serverDoubleStart(getServer):
|
||||
"""!Test to start the server twice"""
|
||||
assert getServer.start()
|
||||
assert getServer.start()
|
||||
assert getServer.stop()
|
||||
|
||||
|
||||
def test_serverStartTwoInstances():
|
||||
"""!Test to start two server different server instances"""
|
||||
dataQueue = queue.Queue()
|
||||
testServer1 = TCPServer(dataQueue)
|
||||
testServer2 = TCPServer(dataQueue)
|
||||
assert testServer1.start()
|
||||
assert testServer1.isRunning
|
||||
assert not testServer2.start()
|
||||
assert testServer1.isRunning
|
||||
assert not testServer2.isRunning
|
||||
assert testServer1.stop()
|
||||
assert testServer2.stop()
|
||||
|
||||
|
||||
def test_serverStopsWhileConnected(getRunningServer, getClient):
|
||||
"""!Shutdown server while client is connected"""
|
||||
getClient.connect()
|
||||
getRunningServer.stop()
|
||||
timeout = 5
|
||||
while getClient.isConnected:
|
||||
time.sleep(0.1)
|
||||
timeout = timeout - 1
|
||||
if timeout == 0:
|
||||
break
|
||||
assert timeout
|
||||
|
||||
|
||||
@pytest.mark.skip("needs fixture for more than one client")
|
||||
def test_serverGetOutput(getRunningServer):
|
||||
"""!Send data to server with 2 clients, check '[ack]' and data on server queue"""
|
||||
# connect all
|
||||
testClient1 = TCPClient()
|
||||
assert testClient1.connect()
|
||||
testClient2 = TCPClient()
|
||||
assert testClient2.connect()
|
||||
# send all
|
||||
assert testClient1.transmit("test1")
|
||||
time.sleep(0.1) # wait for recv to prevent fail of false order
|
||||
assert testClient2.transmit("test2")
|
||||
# recv all
|
||||
assert testClient1.receive() == "[ack]"
|
||||
assert testClient2.receive() == "[ack]"
|
||||
# _check server output data
|
||||
assert getRunningServer._alarmQueue.qsize() == 2
|
||||
assert getRunningServer._alarmQueue.get(True, 1)[1] == "test1"
|
||||
assert getRunningServer._alarmQueue.get(True, 1)[1] == "test2"
|
||||
assert getRunningServer._alarmQueue.qsize() == 0 # Last _check must be None
|
||||
# disconnect all
|
||||
assert testClient1.disconnect()
|
||||
assert testClient2.disconnect()
|
||||
|
||||
|
||||
def test_serverHighLoad(getRunningServer):
|
||||
"""!High load server test with 10 send threads each will send 100 msg with 324 bytes size"""
|
||||
logging.debug("start sendThreads")
|
||||
threads = []
|
||||
for thr_id in range(10):
|
||||
thr = threading.Thread(target=sendThread, name="sendThread-" + str(thr_id))
|
||||
thr.daemon = True
|
||||
thr.start()
|
||||
threads.append(thr)
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
logging.debug("finished sendThreads")
|
||||
assert getRunningServer._alarmQueue.qsize() == 1000
|
||||
|
||||
|
||||
def sendThread():
|
||||
client = TCPClient()
|
||||
client.connect()
|
||||
time.sleep(0.1)
|
||||
for i in range(100):
|
||||
# actually this string is 324 bytes long
|
||||
client.transmit("HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-"
|
||||
"HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-"
|
||||
"HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-")
|
||||
if not client.receive() == "[ack]":
|
||||
logging.error("missing [ACK]")
|
||||
|
||||
time.sleep(0.1)
|
||||
client.disconnect()
|
||||
77
test/boswatch/test_broadcast.py
Normal file
77
test/boswatch/test_broadcast.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: test_broadcast.py
|
||||
@date: 25.09.2018
|
||||
@author: Bastian Schroll
|
||||
@description: Unittests for BOSWatch. File have to run as "pytest" unittest
|
||||
"""
|
||||
# problem of the pytest fixtures
|
||||
# pylint: disable=redefined-outer-name
|
||||
import logging
|
||||
import pytest
|
||||
|
||||
from boswatch.network.broadcast import BroadcastServer
|
||||
from boswatch.network.broadcast import BroadcastClient
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
logging.debug("[TEST] %s.%s", function.__module__, function.__name__)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def broadcastServer():
|
||||
"""!Server a BroadcastServer instance"""
|
||||
broadcastServer = BroadcastServer()
|
||||
yield broadcastServer
|
||||
if broadcastServer.isRunning:
|
||||
assert broadcastServer.stop()
|
||||
while broadcastServer.isRunning:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def broadcastClient():
|
||||
"""!Server a BroadcastClient instance"""
|
||||
return BroadcastClient()
|
||||
|
||||
|
||||
def test_serverStartStop(broadcastServer):
|
||||
"""!Start a BroadcastServer, check if running and stop it"""
|
||||
assert broadcastServer.start()
|
||||
assert broadcastServer.isRunning
|
||||
assert broadcastServer.stop()
|
||||
|
||||
|
||||
def test_serverDoubleStart(broadcastServer):
|
||||
"""!Try to start a BroadcastServer twice"""
|
||||
assert broadcastServer.start()
|
||||
assert broadcastServer.start()
|
||||
assert broadcastServer.stop()
|
||||
|
||||
|
||||
def test_serverStopNotStarted(broadcastServer):
|
||||
"""!Try to stop a BroadcastServer where is not running"""
|
||||
assert broadcastServer.stop()
|
||||
|
||||
|
||||
def test_clientWithoutServer(broadcastClient):
|
||||
"""!Use BroadcastClient with no server"""
|
||||
assert not broadcastClient.getConnInfo(1)
|
||||
|
||||
|
||||
def test_serverClientFetchConnInfo(broadcastClient, broadcastServer):
|
||||
"""!Fetch connection info from BroadcastServer"""
|
||||
assert broadcastServer.start()
|
||||
assert broadcastClient.getConnInfo()
|
||||
assert broadcastServer.stop()
|
||||
assert broadcastClient.serverIP
|
||||
assert broadcastClient.serverPort
|
||||
105
test/boswatch/test_config.py
Normal file
105
test/boswatch/test_config.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: test_config.py
|
||||
@date: 08.01.2017
|
||||
@author: Bastian Schroll
|
||||
@description: Unittests for BOSWatch. File have to run as "pytest" unittest
|
||||
"""
|
||||
# problem of the pytest fixtures
|
||||
# pylint: disable=redefined-outer-name
|
||||
import logging
|
||||
import pytest
|
||||
from boswatch.utils import paths
|
||||
|
||||
from boswatch.configYaml import ConfigYAML
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
logging.debug("[TEST] %s.%s", function.__module__, function.__name__)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def getConfig():
|
||||
"""!Build a config object"""
|
||||
return ConfigYAML()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def getFilledConfig():
|
||||
"""!Build a config object and fill it with the config data"""
|
||||
filledConfig = ConfigYAML()
|
||||
assert filledConfig.loadConfigFile(paths.TEST_PATH + "test_config.yaml") is True
|
||||
return filledConfig
|
||||
|
||||
|
||||
def test_loadConfigFile(getConfig):
|
||||
"""!load a config file"""
|
||||
assert getConfig.loadConfigFile(paths.TEST_PATH + "test_config.yaml") is True
|
||||
|
||||
|
||||
def test_loadConfigFileFailed(getConfig):
|
||||
"""!load a config file with syntax error"""
|
||||
assert getConfig.loadConfigFile(paths.TEST_PATH + "test_configFailed.yaml") is False
|
||||
|
||||
|
||||
def test_loadConfigFileNotFound(getConfig):
|
||||
"""!load a config file where is not available"""
|
||||
assert getConfig.loadConfigFile(paths.TEST_PATH + "test_configNotFound.yaml") is False
|
||||
|
||||
|
||||
def test_getConfigAsString(getFilledConfig):
|
||||
"""!Get the string representation of the config"""
|
||||
assert type(str(getFilledConfig)) is str
|
||||
logging.debug(getFilledConfig)
|
||||
|
||||
|
||||
def test_getTypes(getFilledConfig):
|
||||
"""!Get and check different data types in config"""
|
||||
assert type(getFilledConfig.get("types")) is ConfigYAML
|
||||
assert type(getFilledConfig.get("types", "string")) is str
|
||||
assert type(getFilledConfig.get("types", "bool")) is bool
|
||||
assert type(getFilledConfig.get("types", "integer")) is int
|
||||
assert type(getFilledConfig.get("types", "float")) is float
|
||||
|
||||
|
||||
def test_getDefaultValue(getFilledConfig):
|
||||
"""!Get the default value of an not existent entry"""
|
||||
assert getFilledConfig.get("notExistent", default="defaultValue") == "defaultValue"
|
||||
|
||||
|
||||
def test_getNestedConfig(getFilledConfig):
|
||||
"""!Work with nested sub-config elements"""
|
||||
nestedConfig = getFilledConfig.get("types")
|
||||
assert type(nestedConfig) is ConfigYAML
|
||||
assert nestedConfig.get("string") == "Hello World"
|
||||
|
||||
|
||||
def test_configIterationList(getFilledConfig):
|
||||
"""!Try to iterate over a list in the config"""
|
||||
counter = 0
|
||||
for item in getFilledConfig.get("list"):
|
||||
assert type(item) is str
|
||||
counter += 1
|
||||
assert counter == 3
|
||||
|
||||
|
||||
def test_configIterationListWithNestedList(getFilledConfig):
|
||||
"""!Try to iterate over a list in the config where its elements are lists itself"""
|
||||
listCnt = 0
|
||||
strCnt = 0
|
||||
for item in getFilledConfig.get("list1"):
|
||||
if type(item) is ConfigYAML:
|
||||
listCnt += 1
|
||||
if type(item) is str:
|
||||
strCnt += 1
|
||||
assert listCnt == 2
|
||||
assert strCnt == 1
|
||||
104
test/boswatch/test_decoder.py
Normal file
104
test/boswatch/test_decoder.py
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: test_Decoder.py
|
||||
@date: 15.12.2017
|
||||
@author: Bastian Schroll
|
||||
@description: Unittests for BOSWatch. File have to run as "pytest" unittest
|
||||
"""
|
||||
# problem of the pytest fixtures
|
||||
# pylint: disable=redefined-outer-name
|
||||
import logging
|
||||
|
||||
from boswatch.decoder.decoder import Decoder
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
logging.debug("[TEST] %s.%s", function.__module__, function.__name__)
|
||||
|
||||
|
||||
def test_decoderNoData():
|
||||
"""!Test a empty string"""
|
||||
assert Decoder.decode("") is None
|
||||
|
||||
|
||||
def test_decoderZveiValid():
|
||||
"""!Test valid ZVEI"""
|
||||
assert not Decoder.decode("ZVEI1: 12345") is None
|
||||
assert not Decoder.decode("ZVEI1: 12838") is None
|
||||
assert not Decoder.decode("ZVEI1: 34675") is None
|
||||
|
||||
|
||||
def test_decoderZveiDoubleTone():
|
||||
"""!Test doubleTone included ZVEI"""
|
||||
assert not Decoder.decode("ZVEI1: 6E789") is None
|
||||
assert not Decoder.decode("ZVEI1: 975E7") is None
|
||||
assert not Decoder.decode("ZVEI1: 2E87E") is None
|
||||
|
||||
|
||||
def test_decoderZveiInvalid():
|
||||
"""Test invalid ZVEI"""
|
||||
assert Decoder.decode("ZVEI1: 1245A") is None
|
||||
assert Decoder.decode("ZVEI1: 1245") is None
|
||||
assert Decoder.decode("ZVEI1: 135") is None
|
||||
assert Decoder.decode("ZVEI1: 54") is None
|
||||
assert Decoder.decode("ZVEI1: 54") is None
|
||||
|
||||
|
||||
def test_decoderPocsagValid():
|
||||
"""!Test valid POCSAG"""
|
||||
assert not Decoder.decode("POCSAG512: Address: 1000000 Function: 0") is None
|
||||
assert not Decoder.decode("POCSAG512: Address: 1000001 Function: 1") is None
|
||||
assert not Decoder.decode("POCSAG1200: Address: 1000002 Function: 2") is None
|
||||
assert not Decoder.decode("POCSAG2400: Address: 1000003 Function: 3") is None
|
||||
|
||||
|
||||
def test_decoderPocsagText():
|
||||
"""!Test POCSAG with text"""
|
||||
assert not Decoder.decode("POCSAG512: Address: 1000000 Function: 0 Alpha: test") is None
|
||||
assert not Decoder.decode("POCSAG512: Address: 1000001 Function: 1 Alpha: test") is None
|
||||
assert not Decoder.decode("POCSAG1200: Address: 1000002 Function: 2 Alpha: test") is None
|
||||
assert not Decoder.decode("POCSAG2400: Address: 1000003 Function: 3 Alpha: test") is None
|
||||
|
||||
|
||||
def test_decoderPocsagShortRic():
|
||||
"""!Test short POCSAG"""
|
||||
assert not Decoder.decode("POCSAG512: Address: 3 Function: 0 Alpha: test") is None
|
||||
assert not Decoder.decode("POCSAG512: Address: 33 Function: 0 Alpha: test") is None
|
||||
assert not Decoder.decode("POCSAG1200: Address: 333 Function: 0 Alpha: test") is None
|
||||
assert not Decoder.decode("POCSAG1200: Address: 3333 Function: 0 Alpha: test") is None
|
||||
assert not Decoder.decode("POCSAG2400: Address: 33333 Function: 0 Alpha: test") is None
|
||||
assert not Decoder.decode("POCSAG2400: Address: 333333 Function: 0 Alpha: test") is None
|
||||
assert not Decoder.decode("POCSAG2400: Address: 3333333 Function: 0 Alpha: test") is None
|
||||
|
||||
|
||||
def test_decoderPocsagInvalid():
|
||||
"""!Test invalid POCSAG"""
|
||||
assert Decoder.decode("POCSAG512: Address: 333333F Function: 0 Alpha: invalid") is None
|
||||
assert Decoder.decode("POCSAG512: Address: 333333F Function: 1 Alpha: invalid") is None
|
||||
assert Decoder.decode("POCSAG512: Address: 3333333 Function: 4 Alpha: invalid") is None
|
||||
|
||||
|
||||
def test_decoderFmsValid():
|
||||
"""!Test valid FMS"""
|
||||
assert not Decoder.decode("""FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 0=FZG->LST 2=I (ohneNA,ohneSIGNAL)) CRC correct""") is None
|
||||
assert not Decoder.decode("""FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 1=LST->FZG 2=I (ohneNA,ohneSIGNAL)) CRC correct""") is None
|
||||
assert not Decoder.decode("""FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 0=FZG->LST 2=II (ohneNA,mit SIGNAL)) CRC correct""") is None
|
||||
assert not Decoder.decode("""FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 1=LST->FZG 2=III(mit NA,ohneSIGNAL)) CRC correct""") is None
|
||||
assert not Decoder.decode("""FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 0=FZG->LST 2=IV (mit NA,mit SIGNAL)) CRC correct""") is None
|
||||
|
||||
|
||||
def test_decoderFmsInvalid():
|
||||
"""!Test invalid FMS"""
|
||||
assert Decoder.decode("""FMS: 14170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 1=LST->FZG 2=III(mit NA,ohneSIGNAL)) CRC correct""") is None
|
||||
assert Decoder.decode("""FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Sta 3=Einsatz Ab 0=FZG->LST 2=IV (mit NA,mit SIGNAL)) CRC correct""") is None
|
||||
assert Decoder.decode("""FMS: 14170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 1=LST->FZG 2=III(mit NA,ohneSIGNAL)) CRC incorrect""") is None
|
||||
assert Decoder.decode("""FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Sta 3=Einsatz Ab 0=FZG->LST 2=IV (mit NA,mit SIGNAL)) CRC incorrect""") is None
|
||||
|
|
@ -12,27 +12,24 @@
|
|||
@file: test_header.py
|
||||
@date: 12.12.2017
|
||||
@author: Bastian Schroll
|
||||
@description: Unittests for BOSWatch. File must be _run as "pytest" unittest
|
||||
@description: Unittests for BOSWatch. File have to run as "pytest" unittest
|
||||
"""
|
||||
# problem of the pytest fixtures
|
||||
# pylint: disable=redefined-outer-name
|
||||
import logging
|
||||
|
||||
from boswatch.utils import header
|
||||
|
||||
|
||||
class Test_Header:
|
||||
"""!Unittests for the header"""
|
||||
def setup_function(function):
|
||||
logging.debug("[TEST] %s.%s", function.__module__, function.__name__)
|
||||
|
||||
def setup_method(self, method):
|
||||
logging.debug("[TEST] %s.%s", type(self).__name__, method.__name__)
|
||||
|
||||
def test_logoToLog(self):
|
||||
"""!Test logo to log"""
|
||||
assert header.logoToLog()
|
||||
def test_logoToLog():
|
||||
"""!Test logo to log"""
|
||||
assert header.logoToLog()
|
||||
|
||||
def test_infoToLog(self):
|
||||
"""!Test info to log"""
|
||||
assert header.infoToLog()
|
||||
|
||||
def test_logoToScreen(self):
|
||||
"""!Test logo to screen"""
|
||||
assert header.logoToScreen()
|
||||
def test_infoToLog():
|
||||
"""!Test info to log"""
|
||||
assert header.infoToLog()
|
||||
60
test/boswatch/test_packet.py
Normal file
60
test/boswatch/test_packet.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: test_packet.py
|
||||
@date: 12.12.2017
|
||||
@author: Bastian Schroll
|
||||
@description: Unittests for BOSWatch. File have to run as "pytest" unittest
|
||||
"""
|
||||
# problem of the pytest fixtures
|
||||
# pylint: disable=redefined-outer-name
|
||||
import logging
|
||||
import pytest
|
||||
|
||||
from boswatch.packet import Packet
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
logging.debug("[TEST] %s.%s", function.__module__, function.__name__)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def buildPacket():
|
||||
"""!Build a BOSWatch packet and serve it to each test"""
|
||||
return Packet()
|
||||
|
||||
|
||||
def test_createPacket(buildPacket):
|
||||
"""!Create a packet"""
|
||||
assert buildPacket != ""
|
||||
|
||||
|
||||
def test_copyPacket(buildPacket):
|
||||
"""!Copy a packet to an new instance"""
|
||||
bwCopyPacket = Packet(buildPacket.__str__())
|
||||
assert bwCopyPacket != ""
|
||||
|
||||
|
||||
def test_getPacketString(buildPacket):
|
||||
"""!get the intern packet dict as string"""
|
||||
assert type(buildPacket.__str__()) is str
|
||||
assert buildPacket.__str__() != ""
|
||||
|
||||
|
||||
def test_getNotSetField(buildPacket):
|
||||
"""!try to get a not set field"""
|
||||
assert not buildPacket.get("testfield")
|
||||
|
||||
|
||||
def test_setGetField(buildPacket):
|
||||
"""!set and get a field"""
|
||||
buildPacket.set("testField", "test")
|
||||
assert buildPacket.get("testField") == "test"
|
||||
49
test/boswatch/test_paths.py
Normal file
49
test/boswatch/test_paths.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: test_paths.py
|
||||
@date: 22.02.2017
|
||||
@author: Bastian Schroll
|
||||
@description: Unittests for BOSWatch. File have to run as "pytest" unittest
|
||||
"""
|
||||
# problem of the pytest fixtures
|
||||
# pylint: disable=redefined-outer-name
|
||||
import logging
|
||||
import os
|
||||
|
||||
from boswatch.utils import paths
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
logging.debug("[TEST] %s.%s", function.__module__, function.__name__)
|
||||
|
||||
|
||||
def test_fileExists():
|
||||
"""!load a local config file"""
|
||||
assert paths.fileExist("README.md")
|
||||
|
||||
|
||||
def test_fileNotExists():
|
||||
"""!load a local config file"""
|
||||
assert not paths.fileExist("notFound.txt")
|
||||
|
||||
|
||||
def test_makeDirNotExisting():
|
||||
"""!load a local config file"""
|
||||
assert paths.makeDirIfNotExist("UnItTeSt")
|
||||
os.removedirs("UnItTeSt")
|
||||
|
||||
|
||||
def test_makeDirExisting():
|
||||
"""!load a local config file"""
|
||||
paths.makeDirIfNotExist("UnItTeSt")
|
||||
assert paths.makeDirIfNotExist("UnItTeSt")
|
||||
os.removedirs("UnItTeSt")
|
||||
109
test/boswatch/test_timer.py
Normal file
109
test/boswatch/test_timer.py
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: test_timer.py
|
||||
@date: 21.09.2018
|
||||
@author: Bastian Schroll
|
||||
@description: Unittests for BOSWatch. File have to run as "pytest" unittest
|
||||
"""
|
||||
# problem of the pytest fixtures
|
||||
# pylint: disable=redefined-outer-name
|
||||
import logging
|
||||
import time
|
||||
import pytest
|
||||
|
||||
from boswatch.timer import RepeatedTimer
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
logging.debug("[TEST] %s.%s", function.__module__, function.__name__)
|
||||
|
||||
|
||||
def testTargetFast():
|
||||
"""!Fast worker thread"""
|
||||
logging.debug("run testTargetFast")
|
||||
|
||||
|
||||
def testTargetSlow():
|
||||
"""!Slow worker thread"""
|
||||
logging.debug("run testTargetSlow start")
|
||||
time.sleep(0.51)
|
||||
logging.debug("run testTargetSlow end")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def useTimerFast():
|
||||
"""!Server a RepeatedTimer instance with fast worker"""
|
||||
testTimer = RepeatedTimer(0.1, testTargetFast)
|
||||
yield testTimer
|
||||
if testTimer.isRunning:
|
||||
assert testTimer.stop()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def useTimerSlow():
|
||||
"""!Server a RepeatedTimer instance slow worker"""
|
||||
testTimer = RepeatedTimer(0.1, testTargetSlow)
|
||||
yield testTimer
|
||||
if testTimer.isRunning:
|
||||
assert testTimer.stop()
|
||||
|
||||
|
||||
def test_timerStartStop(useTimerFast):
|
||||
"""!Try to start and stop a timer"""
|
||||
assert useTimerFast.start()
|
||||
assert useTimerFast.stop()
|
||||
|
||||
|
||||
def test_timerDoubleStart(useTimerFast):
|
||||
"""!Try to start a timer twice"""
|
||||
assert useTimerFast.start()
|
||||
assert useTimerFast.start()
|
||||
assert useTimerFast.stop()
|
||||
|
||||
|
||||
def test_timerStopNotStarted(useTimerFast):
|
||||
"""!Try to stop a timer where is not started"""
|
||||
assert useTimerFast.stop()
|
||||
|
||||
|
||||
def test_timerIsRunning(useTimerFast):
|
||||
"""!Check if a timer is running"""
|
||||
assert useTimerFast.start()
|
||||
assert useTimerFast.isRunning
|
||||
assert useTimerFast.stop()
|
||||
|
||||
|
||||
def test_timerRun(useTimerFast):
|
||||
"""!Run a timer and check overdue and lostEvents"""
|
||||
assert useTimerFast.start()
|
||||
time.sleep(0.2)
|
||||
assert useTimerFast.stop()
|
||||
assert useTimerFast.overdueCount == 0
|
||||
assert useTimerFast.lostEvents == 0
|
||||
|
||||
|
||||
def test_timerOverdue(useTimerSlow):
|
||||
"""!Run a timer and check overdue and lostEvents"""
|
||||
assert useTimerSlow.start()
|
||||
time.sleep(0.2)
|
||||
assert useTimerSlow.stop()
|
||||
assert useTimerSlow.overdueCount == 1
|
||||
assert useTimerSlow.lostEvents == 5
|
||||
|
||||
|
||||
def test_timerOverdueLong(useTimerSlow):
|
||||
"""!Run a timer and check overdue and lostEvents"""
|
||||
assert useTimerSlow.start()
|
||||
time.sleep(1)
|
||||
assert useTimerSlow.stop()
|
||||
assert useTimerSlow.overdueCount == 2
|
||||
assert useTimerSlow.lostEvents == 10
|
||||
1
test/module/__init__.py
Normal file
1
test/module/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# coding=utf-8
|
||||
1
test/plugin/__init__.py
Normal file
1
test/plugin/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# coding=utf-8
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
# by Bastian Schroll
|
||||
|
||||
[pytest]
|
||||
addopts = -v --pep8 --cov=boswatch/ --cov-report=term-missing --log-level=CRITICAL
|
||||
addopts = -v --pep8 --flakes --cov=boswatch/ --cov-report=term-missing --log-level=CRITICAL
|
||||
|
||||
# classic or progress
|
||||
console_output_style = progress
|
||||
|
|
@ -19,6 +19,7 @@ log_file_format=%(asctime)s - %(module)-12s %(funcName)-15s [%(levelname)-8s] %(
|
|||
log_file_date_format=%d.%m.%Y %H:%M:%S
|
||||
|
||||
#pep8 plugin
|
||||
pep8ignore = E402, E501 # import not at top
|
||||
pep8ignore = E402 E501
|
||||
# E402 # import not at top
|
||||
# E501 # line too long
|
||||
# pep8maxlinelength = 99
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
[test]
|
||||
one = 1
|
||||
two = two
|
||||
|
||||
[testcase]
|
||||
test = ok
|
||||
|
||||
[boolTest]
|
||||
one = 1
|
||||
two = True
|
||||
three = 0
|
||||
four = False
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue