Merge branch 'develop' into Schrolli91-patch-1

This commit is contained in:
Bastian Schroll 2019-09-22 14:25:51 +02:00
commit 8578eaeaa6
101 changed files with 2565 additions and 2170 deletions

15
.gitignore vendored
View file

@ -6,12 +6,13 @@
# German BOS Information Script
# by Bastian Schroll
# virtual environment
\venv/
# generated files
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 +23,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.

View file

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

View file

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

View file

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

View file

@ -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`

View file

View file

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

View file

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

View file

@ -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-&gt;Lst, Lst-&gt;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

View file

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

66
boswatch/configYaml.py Normal file
View file

@ -0,0 +1,66 @@
#!/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 __str__(self):
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):
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:
return default

View file

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

View file

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

View file

@ -18,7 +18,7 @@
import logging
import re
from boswatch.packet import packet
from boswatch.packet import Packet
logging.debug("- %s loaded", __name__)
@ -40,7 +40,7 @@ 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]))

View file

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

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

View file

@ -26,51 +26,46 @@ 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)"""
self._sock = None
self._timeout = timeout
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
self._sock.setdefaulttimeout(self._timeout)
self._sock = socket.create_connection((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
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.close()
self._sock = None
logging.debug("disconnected")
return True
logging.warning("client not connected")
return True
except AttributeError:
logging.error("cannot disconnect - no connection established")
return False
except: # pragma: no cover
logging.exception("error while disconnecting")
return False
return False
def transmit(self, data):
"""!Send a data packet to the server
@ -84,13 +79,9 @@ class TCPClient:
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
return False
def receive(self):
"""!Receive data from the server
@ -98,17 +89,19 @@ class TCPClient:
@return received data"""
try:
received = str(self._sock.recv(1024), "utf-8")
logging.debug("received: %d", received)
logging.debug("received: %s", received)
return received
except AttributeError:
logging.error("cannot receive - no connection established")
return False
except ConnectionResetError:
logging.error("cannot receive - host closed connection")
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")
return False
return False
@property
def isConnected(self):
"""!Property of client connected state"""
if self._sock:
return True
return False

View file

@ -0,0 +1,53 @@
#!/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
@property
def connectionState(self):
"""!Property for the last connection state from checkConn()"""
return self._connectionState

View file

@ -21,24 +21,16 @@ import time
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()
class TCPHandler(socketserver.BaseRequestHandler):
"""!RequestHandler class for our TCPServer class."""
class _ThreadedTCPRequestHandler(socketserver.ThreadingMixIn, 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
@ -51,10 +43,8 @@ class TCPHandler(socketserver.BaseRequestHandler):
if data != "":
logging.debug("%s recv: %s", req_name, data)
# 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
# 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)
@ -63,22 +53,36 @@ class TCPHandler(socketserver.BaseRequestHandler):
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)
finally:
with _lockClients:
del _clients[threading.current_thread().name]
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 +94,62 @@ 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:
self._server = _ThreadedTCPServer(("", port), _ThreadedTCPRequestHandler)
self._server.timeout = self._timeout
self._server.alarmQueue = self._alarmQueue
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 OSError:
logging.exception("Socket Error")
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_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

View file

@ -16,7 +16,6 @@
"""
import logging
import time
from boswatch.config import Config
from boswatch import version
logging.debug("- %s loaded", __name__)
@ -34,11 +33,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,7 +58,7 @@ class Packet:
logging.warning("field not found: %s", fieldName)
return None
def addClientData(self):
def addClientData(self, config):
"""!Add the client information to the decoded data
This function adds the following data to the bwPacket:
@ -73,16 +68,15 @@ class Packet:
- clientBranch
- inputSource
- frequency"""
config = Config()
logging.debug("add client data to bwPacket")
self.set("clientName", config.getStr("Client", "Name", "clientConfig"))
self.set("clientName", config.get("client", "name"))
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"))
self.set("inputSource", config.get("client", "inputSource"))
self.set("frequency", config.get("inputSource", "sdr", "frequency"))
def addServerData(self):
def addServerData(self, config):
"""!Add the server information to the decoded data
This function adds the following data to the bwPacket:
@ -90,9 +84,8 @@ class Packet:
- serverVersion
- serverBuildDate
- serverBranch"""
config = Config()
logging.debug("add server data to bwPacket")
self.set("serverName", config.getStr("Server", "Name", "serverConfig"))
self.set("serverName", config.get("server", "name"))
self.set("serverVersion", version.server)
self.set("serverBuildDate", version.date)
self.set("serverBranch", version.branch)
@ -101,6 +94,5 @@ class Packet:
"""!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"))

View file

@ -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"])

38
boswatch/router/route.py Normal file
View file

@ -0,0 +1,38 @@
#!/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
"""
class Route:
"""!Class for single routing points"""
def __init__(self, name, callback):
"""!Create a instance of an route point
@param name: name of the route point
@param callback: instance of the callback function
"""
self._name = name
self._callback = callback
@property
def name(self):
"""!Property to get the route point name"""
return self._name
@property
def callback(self):
"""!Porperty to get the callback function instance"""
return self._callback

72
boswatch/router/router.py Normal file
View file

@ -0,0 +1,72 @@
#!/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
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 = []
logging.debug("[%s] 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
"""
logging.debug("[%s] started", self._name)
for routeObject in self._routeList:
logging.debug("[%s] -> run route: %s", self._name, routeObject)
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: %s", self._name, bwPacket)
logging.debug("[%s] ended", self._name)
return bwPacket
@property
def name(self):
"""!Property to get the name of the router"""
return self._name
@property
def routeList(self):
"""!Property to get a list of all route points of this router"""
return self._routeList

View file

@ -0,0 +1,120 @@
#!/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
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 = {}
def __del__(self):
"""!Destroy the internal routerDict
All routers and route point instances will be destroyed too
Also destroys all instances from modules or plugins"""
# destroy all routers (also destroys all instances of modules/plugins)
del self._routerDict
# if there is an error, router list would be empty (see tmp variable)
def buildRouter(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")
routeName = route.get("name")
routeConfig = route.get("config", default=ConfigYAML()) # if no config - build a empty
if routeType is None or routeName is None:
logging.error("type or name not found in route: %s", route)
return False
try:
if routeType == "plugin":
importedFile = importlib.import_module(routeType + "." + routeName)
loadedClass = importedFile.BoswatchPlugin(routeConfig)
routerDict_tmp[routerName].addRoute(Route(routeName, loadedClass._run))
elif routeType == "module":
importedFile = importlib.import_module(routeType + "." + routeName)
loadedClass = importedFile.BoswatchModule(routeConfig)
routerDict_tmp[routerName].addRoute(Route(routeName, loadedClass._run))
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:
logging.error("%s not found: %s", route.get("type"), route.get("name"))
return False
logging.debug("finished building routers")
self._routerDict = routerDict_tmp
self._showRouterRoute()
return True
def runRouter(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)
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)

View file

@ -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",
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

View file

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

116
boswatch/utils/timer.py Normal file
View 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

View file

@ -13,7 +13,6 @@
@date: 15.01.2018
@author: Bastian Schroll
@description: Little Helper to replace wildcards in stings
@todo not completed yet
"""
import logging
import time
@ -23,6 +22,9 @@ import time
logging.debug("- %s loaded", __name__)
# todo check function and document + write an test
# todo maybe can be a module instead of a native boswatch piece
# idea: maybe this can be a class with a register_wildcard() method
# so the list with wildcards can be modified by other modules
def replaceWildcards(message, bwPacket):
@ -34,10 +36,13 @@ def replaceWildcards(message, bwPacket):
"{TIME}": time.time(),
# 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 +54,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"),
@ -76,9 +79,6 @@ 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")
message = message.replace(wildcard, _wildcards.get(wildcard))
return message

View file

@ -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"

View file

@ -1,2 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

11
build_docu.sh Normal file
View 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

View file

@ -14,89 +14,93 @@
@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 subprocess
logging.debug("- subprocess")
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.decoder.decoder import Decoder
from boswatch.utils import header
header.logoToLog()
header.infoToLog()
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)
args = parser.parse_args()
bwConfig = ConfigYAML()
if not bwConfig.loadConfigFile(paths.CONFIG_PATH + args.config):
logging.error("cannot load config file")
exit(1)
# ############################# begin client system
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)
ip = bwConfig.get("server", "ip", default="127.0.0.1")
port = bwConfig.get("server", "port", default="8080")
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_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()
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
if bwConfig.get("client", "useBroadcast", default=False):
broadcastClient = BroadcastClient()
if broadcastClient.getConnInfo():
ip = broadcastClient.serverIP
port = broadcastClient.serverPort
bwClient = TCPClient()
if bwClient.connect(bwConfig.getStr("Server", "IP"), bwConfig.getInt("Server", "PORT")):
if bwClient.connect(ip, port):
testFile = open(paths.TEST_PATH + "testdata.list", "r")
while 1:
for i in range(0, 5):
time.sleep(1)
print("Alarm Nr #" + str(i))
data = "ZVEI1: 12345"
bwPacket = Decoder.decode(data)
for testData in testFile:
if (len(testData.rstrip(' \t\n\r')) == 0) or ("#" in testData[0]):
continue
logging.debug("Test: %s", testData)
bwPacket = Decoder.decode(testData)
if bwPacket:
bwPacket.printInfo()
bwPacket.addClientData()
bwPacket.addClientData(bwConfig)
bwClient.transmit(str(bwPacket))
# todo should we do this in an thread, to not block receiving ???
# todo but then we should use transmit() and receive() with Lock()
# todo should we do this in an thread, to not block receiving ??? 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")
logging.error("cannot transmit after 3 retires")
break
failedTransmits += 1
logging.warning("attempt %d to resend packet", failedTransmits)

View file

@ -14,148 +14,90 @@
@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
header.logoToLog()
header.infoToLog()
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 = ConfigYAML()
if not bwConfig.loadConfigFile(paths.CONFIG_PATH + args.config):
logging.error("cannot load config file")
exit(1)
# ############################# begin server system
try:
logging.debug("Import python modules")
import argparse
logging.debug("- argparse")
# following is temp for testing
import time
import sys
import threading
import threading
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)
bwRoutMan = RouterManager()
if not bwRoutMan.buildRouter(bwConfig):
exit()
try:
header.logoToLog()
header.infoToLog()
header.logoToScreen()
if bwConfig.get("server", "useBroadcast", default=False):
bcServer = BroadcastServer()
bcServer.start()
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()
bwPacket.addServerData(bwConfig)
if bwConfig.getBool("Description", bwPacket.get("mode")):
bwDescriptor.addDescriptions(bwPacket)
bwRoutMan.runRouter(bwConfig.get("alarmRouter"), bwPacket)
bwPluginManager.runAllPlugins(bwPacket)
# print(bwPacket.get("clientVersion")["major"])
incomingQueue.task_done()
except KeyboardInterrupt: # pragma: no cover
logging.warning("Keyboard interrupt")
@ -164,14 +106,22 @@ except SystemExit: # pragma: no cover
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
logging.debug("Starting shutdown routine")
del bwRoutMan
try:
bwServer.stop()
except: # pragma: no cover
except NameError:
pass
except:
raise
try:
bwPluginManager.unloadAllPlugins()
except: # pragma: no cover
bcServer.stop()
except NameError:
pass
except:
raise
logging.debug("BOSWatch has ended ...")

View file

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

32
config/client.yaml Normal file
View file

@ -0,0 +1,32 @@
# -*- 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
server: # only used if useBroadcast = no
ip: 127.0.0.1
port: 8080
inputSource:
sdr:
device: 0
frequency: 85.000M
error: 0
squelch: 0
gain: 100
decoder:
fms: yes
zvei: yes
poc512: yes
poc1200: yes
poc2400: yes

View file

@ -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]

View file

@ -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]

View file

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

26
config/server.yaml Normal file
View file

@ -0,0 +1,26 @@
# -*- 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
name: filter.modeFilter
config:
allowed:
- fms
- zvei

View file

@ -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.

View file

@ -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.

View file

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

124
docu/docs/config.md Normal file
View file

@ -0,0 +1,124 @@
# <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`)||
|useBroadcast|Verbindungsdaten per [Broadcast](information/broadcast.md) beziehen|no|
---
### `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|Frequenz des Empfängers||
|error|Frequenz Abweichung in ppm|0|
|squelch|Einstellung der Rauschsperre|0|
|gain|Verstärkung des Eingangssignals|100|
**Beispiel:**
```yaml
inputSource:
sdr:
device: 0
frequency: 85.000M
error: 0
squelch: 0
gain: 100
```
---
### `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. DEs 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)||
|name|Zu ladende Resource (Siehe weiter unten)||
|config|Konfigurationseinstellungen des Routenpunktes (Siehe weiter unten)||
**Beispiel:**
```yaml
router:
- name: Router 1
route:
- type: module
name: filter.modeFilter
config:
allowed:
- fms
```
---
## Module/Plugins
Für die Konfiguration der Module und Plugins ist in den entsprechenden Kategorien eine ausführliche Beschreibung zu finden.

View file

@ -0,0 +1,104 @@
# <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'
name: template_module # Name der Python Datei (ohne .py)
config: # config-Sektion
option1: value 1
option2:
underOption1: value 21
underOption2: value 22
list:
- list 1
- list 2
```
Eine entsprechende Dokumentation der Parameter ist in der Dokumentation der [Konfiguration](../config.md) zu hinterlegen.
### 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
`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 zu dokumentieren.
### Zu beachten bei Module
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
- `return None` Router fährt mit dem unveränderten bwPacket fort (Input = Output)
- `return False` Router stopt sofort die Ausführung (zB. in Filtern verwendet)
### Zu beachten 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.
---
## Richtiges Logging
tbd ...
---
## Wildcards parsen (Plugin only)
Das parsen der Wildcars funktioniert komfortabel über die interne Methode `self.parseWildcards(MSG)`.
Die Platzhalter der Wildcards findet man in der [BOSWatch Paket](packet.md) Dokumentation.

View file

@ -0,0 +1,60 @@
# <center>BOSWatch Packet 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)|
|vehicle|X||||`{VEC}`||
|vehicle|X||||`{VEC}`||
|tacticalInfo|X||||`{TACI}`|(I, II, III, IV)|
---
## Weitere Wildcards
- `{BR}` - Zeilenumbruch `\r\n`
- `{LPAR}` - öffnende Klammer `(`
- `{RPAR}` - schließende Klammer `)`
- `{TIME}` - Aktueller zeitstempel

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
docu/docs/img/bw3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

11
docu/docs/index.md Normal file
View file

@ -0,0 +1,11 @@
# <center>BOSWatch 3</center>
---
<center>![BOSWatch](img/bw3.png "BOSWatch 3 Logo")</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.**

View 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>![](../img/broadcast.png)</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.

View file

@ -0,0 +1,34 @@
# <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
name: filter.modeFilter
config:
allowed:
- fms
- pocsag
```
---
## Abhängigkeiten
- keine
---
## Paket Modifikationen
- keine

4
docu/docs/tbd.md Normal file
View 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.

View file

@ -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
@ -673,7 +674,7 @@ SHOW_USED_FILES = YES
# (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
@ -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

36
docu/mkdocs.yml Normal file
View file

@ -0,0 +1,36 @@
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: tbd.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
- 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

View file

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

View file

@ -0,0 +1,53 @@
#!/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
@return bwPacket or False"""
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

100
module/module.py Normal file
View file

@ -0,0 +1,100 @@
#!/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
logging.debug("- %s loaded", __name__)
class Module:
"""!Main module class"""
_modulesActive = 0
def __init__(self, moduleName, config):
"""!init preload some needed locals and then call onLoad() directly"""
self._moduleName = moduleName
self.config = config
self._modulesActive += 1
# for time counting
self._cumTime = 0
self._moduleTime = 0
self._tmpTime = 0
# for statistics
self._runCount = 0
self._moduleErrorCount = 0
logging.debug("[%s] onLoad()", moduleName)
self.onLoad()
def __del__(self):
"""!Destructor calls onUnload() directly"""
logging.debug("[%s] onUnload()", self._moduleName)
self._modulesActive -= 1
self.onUnload()
def _run(self, bwPacket):
"""!start an rund 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)
self._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() - self._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 = {"runCount": self._runCount,
"cumTime": self._cumTime,
"moduleTime": self._moduleTime,
"moduleErrorCount": self._moduleErrorCount}
return stats
def onLoad(self):
"""!Called by import of the module
Must be inherit"""
pass
def doWork(self, bwPacket):
"""!Called module run
Must be inherit
@param bwPacket: bwPacket instance"""
logging.warning("no functionality in module %s", self._moduleName)
def onUnload(self):
"""!Called by destruction of the module
Must be inherit"""
pass

47
module/template_module.py Normal file
View file

@ -0,0 +1,47 @@
#!/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
@return bwPacket or False"""
return bwPacket
def onUnload(self):
"""!Called by destruction of the plugin"""
pass

View file

@ -17,8 +17,6 @@
import logging
import time
from boswatch.utils import paths
from boswatch.config import Config
from boswatch.utils import wildcard
logging.debug("- %s loaded", __name__)
@ -29,9 +27,10 @@ class Plugin:
_pluginsActive = 0
def __init__(self, pluginName):
def __init__(self, pluginName, config):
"""!init preload some needed locals and then call onLoad() directly"""
self._pluginName = pluginName
self.config = config
self._pluginsActive += 1
# to save the packet while alarm is running for other functions
@ -43,7 +42,6 @@ class Plugin:
self._setupTime = 0
self._alarmTime = 0
self._teardownTime = 0
self._endTime = 0
self._tmpTime = 0
# for statistics
@ -52,12 +50,6 @@ 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()
@ -118,7 +110,6 @@ class Plugin:
self._teardownTime = time.time() - self._tmpTime
self._sumTime = self._setupTime + self._alarmTime + self._teardownTime
self._cumTime += self._sumTime
self._endTime = time.time()
self._bwPacket = None
@ -127,6 +118,8 @@ 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

View file

@ -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"""

View file

@ -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`-

View file

@ -1,4 +0,0 @@
[Example]
String = Hello World!
bool = 1
integer = 12

View file

@ -1,7 +1,13 @@
# 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-randomly

3
some_old_stuff.txt Normal file
View 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

View file

@ -0,0 +1 @@
# coding=utf-8

View file

@ -0,0 +1,185 @@
#!/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
def setup_method(method):
logging.debug("[TEST] %s.%s", method.__module__, method.__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_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 dataQueue.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():
"""!Test to start the server twice"""
dataQueue = queue.Queue()
testServer1 = TCPServer(dataQueue)
testServer2 = TCPServer(dataQueue)
assert testServer1.start()
assert not testServer2.start()
assert testServer1.stop()
assert testServer2.stop()
@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 dataQueue.qsize() == 2
assert dataQueue.get(True, 1)[1] == "test1"
assert dataQueue.get(True, 1)[1] == "test2"
assert dataQueue.qsize() is 0 # Last _check must be None
# disconnect all
assert testClient1.disconnect()
assert testClient2.disconnect()

View 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_method(method):
logging.debug("[TEST] %s.%s", method.__module__, method.__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

View file

@ -0,0 +1,81 @@
#!/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_method(method):
logging.debug("[TEST] %s.%s", method.__module__, method.__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_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_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

View 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_method(method):
logging.debug("[TEST] %s.%s", method.__module__, method.__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

View file

@ -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_method(method):
logging.debug("[TEST] %s.%s", method.__module__, method.__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()

View 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_method(method):
logging.debug("[TEST] %s.%s", method.__module__, method.__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 is not ""
def test_copyPacket(buildPacket):
"""!Copy a packet to an new instance"""
bwCopyPacket = Packet(buildPacket.__str__())
assert bwCopyPacket is not ""
def test_getPacketString(buildPacket):
"""!get the intern packet dict as string"""
assert type(buildPacket.__str__()) is str
assert buildPacket.__str__() is not ""
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") is "test"

View 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_method(method):
logging.debug("[TEST] %s.%s", method.__module__, method.__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
View 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.utils.timer import RepeatedTimer
def setup_method(method):
logging.debug("[TEST] %s.%s", method.__module__, method.__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
View file

@ -0,0 +1 @@
# coding=utf-8

1
test/plugin/__init__.py Normal file
View file

@ -0,0 +1 @@
# coding=utf-8

View file

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

View file

@ -1,12 +0,0 @@
[test]
one = 1
two = two
[testcase]
test = ok
[boolTest]
one = 1
two = True
three = 0
four = False

View file

@ -1,164 +0,0 @@
#!/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 must be _run as "pytest" unittest
"""
import pytest
import logging
import time
from boswatch.network.server import TCPServer
from boswatch.network.client import TCPClient
class Test_ServerClient:
"""!Unittest for the server/client environment"""
def setup_method(self, method):
logging.debug("[TEST] %s.%s", type(self).__name__, method.__name__)
@pytest.fixture(scope="function")
def useServer(self):
"""!Start and serve the sever for each functions where useServer is given"""
self.testServer = TCPServer()
assert self.testServer.start()
time.sleep(0.1) # wait for server
yield self.testServer # server to all test where useServer is given
assert self.testServer.stop()
time.sleep(0.1) # wait for server
def test_clientConnectFailed(self):
"""!Connect to a non available server"""
self.testClient = TCPClient()
assert not self.testClient.connect()
def test_clientDisconnectFailed(self):
"""!Disconnect while no connection is established"""
self.testClient = TCPClient()
assert not self.testClient.disconnect()
def test_clientTransmitFailed(self):
"""!Transmit while no connection is established"""
self.testClient = TCPClient()
assert not self.testClient.transmit("test")
def test_clientReceiveFailed(self):
"""!Receive while no connection is established"""
self.testClient = TCPClient()
assert not self.testClient.receive()
def test_clientConnect(self, useServer):
"""!Connect to a server"""
self.testClient = TCPClient()
assert self.testClient.connect()
assert self.testClient.disconnect()
def test_clientReconnect(self, useServer):
"""!Try a reconnect after a established connection"""
self.testClient = TCPClient()
assert self.testClient.connect()
assert self.testClient.disconnect()
assert self.testClient.connect()
assert self.testClient.disconnect()
def test_clientMultiConnect(self, useServer):
"""!Connect with 2 clients to the server"""
self.testClient1 = TCPClient()
assert self.testClient1.connect()
self.testClient2 = TCPClient()
assert self.testClient2.connect()
time.sleep(0.1) # wait for all clients connected
# check connected clients
assert useServer.countClientsConnected() == 2
# disconnect all
assert self.testClient1.disconnect()
assert self.testClient2.disconnect()
def test_clientCommunicate(self, useServer):
"""!Try to send data to the server and check on '[ack]'"""
self.testClient = TCPClient()
assert self.testClient.connect()
assert self.testClient.transmit("test")
assert self.testClient.receive() == "[ack]"
assert self.testClient.disconnect()
def test_clientMultiCommunicate(self, useServer):
"""!Try to send data to the server with 3 clients and check on '[ack]'"""
# connect all
self.testClient1 = TCPClient()
assert self.testClient1.connect()
self.testClient2 = TCPClient()
assert self.testClient2.connect()
self.testClient3 = TCPClient()
assert self.testClient3.connect()
# send all
assert self.testClient1.transmit("test")
assert self.testClient2.transmit("test")
assert self.testClient3.transmit("test")
# recv all
assert self.testClient3.receive() == "[ack]"
assert self.testClient2.receive() == "[ack]"
assert self.testClient1.receive() == "[ack]"
# check server msg queue
assert useServer.countPacketsInQueue() == 3
# disconnect all
assert self.testClient1.disconnect()
assert self.testClient2.disconnect()
assert self.testClient3.disconnect()
def test_serverRestart(self):
"""!Test a restart of the server"""
self.testServer = TCPServer()
assert self.testServer.start()
assert self.testServer.stop()
assert self.testServer.start()
assert self.testServer.stop()
def test_serverStopFailed(self):
"""!Test to start the server twice"""
self.testServer = TCPServer()
assert not self.testServer.stop()
def test_serverDoubleStart(self):
"""!Test to start the server twice"""
self.testServer1 = TCPServer()
self.testServer2 = TCPServer()
assert self.testServer1.start()
assert not self.testServer2.start()
assert self.testServer1.stop()
assert not self.testServer2.stop()
def test_serverGetOutput(self, useServer):
"""!Send data to server with 2 clients, check '[ack]' and data on server queue"""
# connect all
self.testClient1 = TCPClient()
assert self.testClient1.connect()
self.testClient2 = TCPClient()
assert self.testClient2.connect()
# send all
useServer.flushQueue()
assert self.testClient1.transmit("test1")
time.sleep(0.1) # wait for recv to prevent fail of false order
assert self.testClient2.transmit("test2")
# recv all
assert self.testClient1.receive() == "[ack]"
assert self.testClient2.receive() == "[ack]"
# _check server output data
assert useServer.countPacketsInQueue() == 2
assert useServer.getDataFromQueue()[1] == "test1"
assert useServer.getDataFromQueue()[1] == "test2"
assert useServer.getDataFromQueue() is None # Last _check must be None
# disconnect all
assert self.testClient1.disconnect()
assert self.testClient2.disconnect()

View file

@ -1,107 +0,0 @@
#!/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 must be _run as "pytest" unittest
"""
import logging
from boswatch.utils import paths
from boswatch.config import Config
class Test_Config:
"""!Unittests for the config"""
def setup_method(self, method):
logging.debug("[TEST] %s.%s", type(self).__name__, method.__name__)
def test_loadLocalConfig(self):
"""!load a local config file"""
bwConfig = Config()
bwConfig.loadConfigFile(paths.TEST_PATH + "test.ini")
assert bwConfig._config is not None
def test_getLocalConfig(self):
"""!get values from local config file"""
bwConfig = Config()
bwConfig.loadConfigFile(paths.TEST_PATH + "test.ini")
assert bwConfig.getInt("test", "one") == 1
assert bwConfig.getBool("test", "one") is True
assert bwConfig.getStr("test", "two") == "two"
assert bwConfig.getStr("testcase", "test") == "ok"
def test_getLocalConfigFailed(self):
"""!fail while get values from local config file"""
bwConfig = Config()
bwConfig.loadConfigFile(paths.TEST_PATH + "test.ini")
assert bwConfig.getStr("test", "abc") is None
assert bwConfig.getStr("abc", "test") is None
def test_shareConfig(self):
"""!load local config file and share it"""
bwConfig = Config()
bwConfig.loadConfigFile(paths.TEST_PATH + "test.ini", "test_shareConfig")
assert bwConfig._sharePoints["test_shareConfig"] is not None
def test_shareConfigUsed(self):
"""!load local config file and tr to share it twice with same name"""
bwConfig1 = Config()
bwConfig1.loadConfigFile(paths.TEST_PATH + "test.ini", "test_shareConfigUsed")
assert bwConfig1._sharePoints["test_shareConfigUsed"] is not None
bwConfig2 = Config()
bwConfig2.loadConfigFile(paths.TEST_PATH + "test.ini")
assert not bwConfig2._shareConfig("test_shareConfigUsed")
def test_getNotSetSharedConfig(self):
"""!try to get values from shared config where not exists"""
bwConfig = Config()
bwConfig.loadConfigFile(paths.TEST_PATH + "test.ini")
assert bwConfig.getInt("test", "one") == 1
assert bwConfig.getInt("test", "one", "NotSetSharedConfig") is None
def test_getSharedConfig(self):
"""!get values from shared config file"""
bwConfig1 = Config()
bwConfig1.loadConfigFile(paths.TEST_PATH + "test.ini", "test_getSharedConfig")
assert bwConfig1._sharePoints["test_getSharedConfig"] is not None
bwConfig2 = Config()
assert bwConfig2.getStr("test", "one") is None
assert bwConfig2.getInt("test", "one", "test_getSharedConfig") == 1
def test_getSharedConfigFailed(self):
"""!fail while get values from shared config file"""
bwConfig1 = Config()
bwConfig1.loadConfigFile(paths.TEST_PATH + "test.ini", "test_getSharedConfigFailed")
assert bwConfig1._sharePoints["test_getSharedConfigFailed"] is not None
bwConfig2 = Config()
assert bwConfig2.getStr("test", "abc", "test_getSharedConfigFailed") is None
assert bwConfig2.getStr("abc", "test", "test_getSharedConfigFailed") is None
def test_getDataTypes(self):
bwConfig = Config()
bwConfig.loadConfigFile(paths.TEST_PATH + "test.ini")
assert bwConfig.getStr("testcase", "test") == "ok"
assert bwConfig.getInt("test", "one") == 1
assert bwConfig.getInt("boolTest", "three") == 0
assert bwConfig.getBool("boolTest", "one")
assert bwConfig.getBool("boolTest", "two")
assert not bwConfig.getBool("boolTest", "three")
assert not bwConfig.getBool("boolTest", "four")
def test_getAllSharepoints(self):
bwConfig = Config()
bwConfig.loadConfigFile(paths.TEST_PATH + "test.ini", "test_shareConfig")
assert bwConfig.getAllSharepoints() is not None

21
test/test_config.yaml Normal file
View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# ____ ____ ______ __ __ __ _____
# / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
# / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
# / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
#/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
# German BOS Information Script
#
# for the test_config
types:
string: Hello World
bool: true
integer: 11
float: 3.14
# for the test_config
list:
- one
- two
- three

View file

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# ____ ____ ______ __ __ __ _____
# / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
# / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
# / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
#/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
# German BOS Information Script
#
# for the test_config - a config failing while loading
types:
failedIntend: Hello World # this line has false indentation
rightIntend: Hello World

View file

@ -1,95 +0,0 @@
#!/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 must be _run as "pytest" unittest
"""
import logging
from boswatch.decoder.decoder import Decoder
class Test_Decoder:
"""!Unittests for the decoder"""
def setup_method(self, method):
logging.debug("[TEST] %s.%s", type(self).__name__, method.__name__)
def test_decoderNoData(self):
"""!Test a empty string"""
assert Decoder.decode("") is None
def test_decoderZveiValid(self):
"""!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(self):
"""!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(self):
"""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(self):
"""!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(self):
"""!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(self):
"""!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(self):
"""!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(self):
"""!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(self):
"""!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

View file

@ -1,70 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""!
____ ____ ______ __ __ __ _____
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
German BOS Information Script
by Bastian Schroll
@file: test_descriptor.py
@date: 07.01.2017
@author: Bastian Schroll
@description: Unittests for BOSWatch. File must be _run as "pytest" unittest
"""
import logging
from boswatch.descriptor.descriptor import Descriptor
from boswatch.descriptor.descriptor import DescriptionList
from boswatch.packet.packet import Packet
class Test_Descriptor:
"""!Unittests for the descriptor"""
def setup_method(self, method):
logging.debug("[TEST] %s.%s", type(self).__name__, method.__name__)
def test_loadCsvNotExist(self):
"""!read CSV file where not exist direct per DescriptionList class"""
descList = DescriptionList()
assert not descList.loadCSV("boswatch")
def test_loadCsv(self):
"""!read CSV file direct per DescriptionList class"""
descList = DescriptionList()
assert descList.loadCSV("zvei")
def test_descriptorLoadFailed(self):
"""!read CSV file where not exist"""
bwDescriptor = Descriptor()
assert not bwDescriptor.loadDescription("boswatch")
def test_descriptorLoad(self):
"""!read CSV file"""
bwDescriptor = Descriptor()
assert bwDescriptor.loadDescription("zvei")
def test_loadDescriptionsNotSet(self):
"""!load descriptions where not set to an bwPacket"""
bwDescriptor = Descriptor()
assert bwDescriptor.loadDescription("zvei")
bwPacket = Packet()
bwPacket.set("mode", "zvei")
bwPacket.set("zvei", "54321")
assert bwDescriptor.addDescriptions(bwPacket)
assert bwPacket.get("shortDescription") is ""
assert bwPacket.get("longDescription") is ""
def test_loadDescriptions(self):
"""!load descriptions to an bwPacket"""
bwDescriptor = Descriptor()
assert bwDescriptor.loadDescription("zvei")
bwPacket = Packet()
bwPacket.set("mode", "zvei")
bwPacket.set("zvei", "12345")
assert bwDescriptor.addDescriptions(bwPacket)
assert bwPacket.get("shortDescription") is not ""
assert bwPacket.get("longDescription") is not ""

View file

@ -1,28 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""!
____ ____ ______ __ __ __ _____
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
German BOS Information Script
by Bastian Schroll
@file: test_doubleFilter.py
@date: 15.12.2017
@author: Bastian Schroll
@description: Unittests for BOSWatch. File must be _run as "pytest" unittest
"""
import logging
import boswatch.filter.doubeFilter
class Test_DoubleFilter:
def setup_method(self, method):
logging.debug("[TEST] %s.%s", type(self).__name__, method.__name__)
def test_none(self):
pass

View file

@ -1,67 +0,0 @@
#!/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 must be _run as "pytest" unittest
"""
import pytest
import logging
from boswatch.packet.packet import Packet
class Test_Packet:
"""!Unittests for the BOSWatch packet"""
def setup_method(self, method):
logging.debug("[TEST] %s.%s", type(self).__name__, method.__name__)
@pytest.fixture(scope="function")
def buildPacket(self):
"""!Build a BOSWatch packet and serve it to each test"""
bwPacket = Packet()
yield bwPacket
def test_createPacket(self):
"""!Create a packet"""
bwPacket = Packet()
assert bwPacket is not ""
def test_copyPacket(self, buildPacket):
"""!Copy a packet to an new instance"""
bwCopyPacket = Packet(buildPacket.__str__())
assert bwCopyPacket is not ""
def test_getPacketString(self, buildPacket):
"""!get the intern packet dict as string"""
assert type(buildPacket.__str__()) is str
assert buildPacket.__str__() is not ""
def test_getNotSetField(self, buildPacket):
"""!try to get a not set field"""
assert not buildPacket.get("testfield")
def test_setGetField(self, buildPacket):
"""!set and get a field"""
buildPacket.set("testField", "test")
assert buildPacket.get("testField") is "test"
def test_addClientData(self, buildPacket):
"""!add client data to packet"""
buildPacket.addClientData()
assert buildPacket.get("clientVersion")
def test_addServerData(self, buildPacket):
"""!add server data to packet"""
buildPacket.addServerData()
assert buildPacket.get("serverVersion")

View file

@ -1,46 +0,0 @@
#!/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 must be _run as "pytest" unittest
"""
import logging
import os
from boswatch.utils import paths
class Test_Config:
"""!Unittests for the paths"""
def setup_method(self, method):
logging.debug("[TEST] %s.%s", type(self).__name__, method.__name__)
def test_fileExists(self):
"""!load a local config file"""
assert paths.fileExist("README.md")
def test_fileNotExists(self):
"""!load a local config file"""
assert not paths.fileExist("notFound.txt")
def test_makeDirNotExisting(self):
"""!load a local config file"""
assert paths.makeDirIfNotExist("UnItTeSt")
os.removedirs("UnItTeSt")
def test_makeDirExisting(self):
"""!load a local config file"""
paths.makeDirIfNotExist("UnItTeSt")
assert paths.makeDirIfNotExist("UnItTeSt")
os.removedirs("UnItTeSt")

42
test/test_template.py Normal file
View file

@ -0,0 +1,42 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""!
____ ____ ______ __ __ __ _____
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
German BOS Information Script
by Bastian Schroll
@file: test_template.py
@date: 03.03.2019
@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
def setup_method(method):
logging.debug("[TEST] %s.%s", method.__module__, method.__name__)
@pytest.fixture
def fixtureTemplate():
return None
@pytest.mark.skip("Reason why i will skipped")
def test_skippedTest():
pass
def test_testName():
pass
def test_withFixture(fixtureTemplate):
assert fixtureTemplate is None

View file

@ -1,28 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""!
____ ____ ______ __ __ __ _____
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
German BOS Information Script
by Bastian Schroll
@file: test_watchdog.py
@date: 15.12.2017
@author: Bastian Schroll
@description: Unittests for BOSWatch. File must be _run as "pytest" unittest
"""
import logging
# import boswatch.watchdog.watchdog
class Test_Watchdog:
def setup_method(self, method):
logging.debug("[TEST] %s.%s", type(self).__name__, method.__name__)
def test_none(self):
pass

Some files were not shown because too many files have changed in this diff Show more