Merge branch 'develop' into process_manager

This commit is contained in:
Bastian Schroll 2019-09-20 14:54:07 +02:00
commit 0c6044072a
32 changed files with 267 additions and 357 deletions

View file

@ -1 +1,32 @@
## Changelog
# <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
-->

View file

@ -1,21 +1,21 @@
## Konfiguration
# <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
Nachfolgend alle Paramater der Client Konfiguration
#### `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 beziehen|no|
|useBroadcast|Verbindungsdaten per [Broadcast](information/broadcast.md) beziehen|no|
#### `server:`
---
### `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.
@ -24,10 +24,18 @@ Ansonsten wird versucht die Verbindungsdaten per Broadcast Paket direkt vom Serv
|ip|IP Adresse des Servers|127.0.0.1|
|port|Port des Sever|8080|
#### `inputSource:`
**Beispiel:**
```yaml
server:
ip: 10.10.10.2
port: 9123
```
---
### `inputSource:`
Aktuell gibt es nur `sdr:` als Input Quelle
##### `sdr:`
#### `sdr:`
|Feld|Beschreibung|Default|
|----|------------|-------|
|device|rtl_fm Device ID|0|
@ -36,7 +44,7 @@ Aktuell gibt es nur `sdr:` als Input Quelle
|squelch|Einstellung der Rauschsperre|0|
|gain|Verstärkung des Eingangssignals|100|
Bsp:
**Beispiel:**
```yaml
inputSource:
sdr:
@ -47,7 +55,8 @@ inputSource:
gain: 100
```
#### `decoder:`
---
### `decoder:`
|Feld|Beschreibung|Default|
|----|------------|-------|
|fms|FMS Decoder|no|
@ -57,44 +66,47 @@ inputSource:
|poc2400|POCSAG Decoder (Bitrate 2400)|no|
---
### Server
## Server
Nachfolgend alle Paramater der Server Konfiguration
#### `server:`
### `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:`
---
### `alarmRouter:`
Enthält eine Liste der Router Namen, welche bei einem Alarm direkt gestartet werden sollen.
Bsp:
**Beispiel:**
```yaml
alarmRouter:
- Name des Routers
- ein weiter Router
```
#### `router:`
Mit den Routern kann der Verarbeitungsweg eines Alarm-Paketes festgelegt werden.
Diese werden als Liste angegeben
---
### `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
Die einzelnen Routen werden wie folgt definiert
#### `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 (vollständige Liste siehe !!!TBD!!!)||
|config|Konfigurationseinstellungen des Routenpunktes||
|type|Art des Routenpunktes (module, plugin, router)||
|name|Zu ladende Resource (Siehe weiter unten)||
|config|Konfigurationseinstellungen des Routenpunktes (Siehe weiter unten)||
Bsp:
**Beispiel:**
```yaml
router:
- name: Router 1
@ -107,22 +119,8 @@ router:
```
---
### Module
Nachfolgend alle Paramater der Modul Konfigurationen
#### `filter.modeFilter`
## Module/Plugins
|Feld|Beschreibung|Default|
|----|------------|-------|
|allowed|Liste der erlaubten Paket Typen (`fms`, `zvei`, `pocsag`, `msg`)||
Bsp:
```yaml
config:
allowed:
- fms
- zvei
```
---
### Plugins
Nachfolgend alle Paramater der Plugin Konfigurationen
|allowed|Liste der erlaubten Paket Typen `fms` `zvei` `pocsag`||

View file

@ -1,19 +1,21 @@
## Eigenes Modul/Plugin schreiben
# <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.
### Informationen anpassen
- Dateikopf anpassen
---
## 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
---
## 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
### Plugin
Die Plugin Basisklasse bietet einige Methoden, welche vom Plugin überschrieben werden können.
- `onLoad()` wird direkt beim Import des Plugins ausgeführt
@ -25,50 +27,61 @@ Die Plugin Basisklasse bietet einige Methoden, welche vom Plugin überschrieben
- `teardown()` wird nach jeder Ausführung gerufen
- `onUnload()` wird beim Zerstören der Plugin Instanz zum Programmende ausgeführt
### Konfiguration
#### Konfiguration anlegen
---
## 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: value 2
underOption1: value 21
underOption2: value 22
list:
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 folgendermaßen abgerufen werden:
### Konfiguration verwenden
Wird der Instanz eine Konfiguration übergeben wird diese in `self.config` abgelegt und kann wie folgt abgerufen werden:
(Dies Ergebnisse beziehen sich auf das Konfigurationsbeispiel oben)
- `self.config.get("option1")` einzelnes Feld
> `value 1`
- `self.config.get("option2", "underOption1")` verschachteltes Feld (beliebig viele möglich)
- 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`
- `self.config.get("notSet", default="defValue")` Es kann ein Default Wert angegeben werden
- Es kann ein Default Wert angegeben werden
`self.config.get("notSet", default="defValue")`
> liefert `defValue`
- `for item in self.config.get(FIELD):` Über Listen kann iteriert werden
- Ü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-Packet Objekts übergeben.
Aus dieser kann mittels `bwPacket.get(FIELDNAME)` das entsprechende Feld ausgelesen werden.
Mittels `bwPacket.set(FIELDNAME, VALUE)` kann es hinzugefügt/modifiziert werden.
---
## 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.
#### Zu beachten bei Module
Module können Pakete beliebig verändern.
Diese Änderungen werden im Router entsprechend weitergeleitet.
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:
@ -76,11 +89,16 @@ Mögliche Rückgabewerte eines Moduls:
- `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
### 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 führt.
was zur weiteren Ausführung des Routers mit dem original Paket führt. Daher macht es in Plugins keinen Sinn ein Paket zu modifizieren.
### Wildcards parsen (Plugin only)
---
## 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

@ -1,9 +1,9 @@
## BOSWatch Packet Format
# <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
---
## Allgemeine Informationen
|Feldname|FMS|POCSAG|ZVEI|MSG|Wildcard|Beschreibung|
|--------|:-:|:----:|:--:|:-:|--------|------------|
|serverName|X|X|X|X|`{SNAME}`|Name der BOSWatch Server Instanz|
@ -19,11 +19,9 @@ Ein BOSWatch Datenpaket wird in einem Python Dict abgebildet. In der nachfolgend
|timestamp|X|X|X|X|`{TIMES}`||
|frequency|X|X|X|X|`{FREQ}`||
|mode|X|X|X|X|`{MODE}`|(fms, pocsag, zvei, msg)|
|descriptionShort|X|X|X||`{DESCS}`|Kann aus optinalem CSV File geladen werden|
|descriptionLong|X|X|X||`{DESCL}`|Kann aus optinalem CSV File geladen werden|
### Speziell für POCSAG
---
## Speziell für POCSAG
|Feldname|FMS|POCSAG|ZVEI|MSG|Wildcard|Beschreibung|
|--------|:-:|:----:|:--:|:-:|--------|------------|
|bitrate||X|||`{BIT}`||
@ -32,14 +30,14 @@ Ein BOSWatch Datenpaket wird in einem Python Dict abgebildet. In der nachfolgend
|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
---
## Speziell für ZVEI
|Feldname|FMS|POCSAG|ZVEI|MSG|Wildcard|Beschreibung|
|--------|:-:|:----:|:--:|:-:|--------|------------|
|tone|||X||`{TONE}`|5-Ton Sequenz nach ZVEI|
### Speziell für FMS
---
## Speziell für FMS
|Feldname|FMS|POCSAG|ZVEI|MSG|Wildcard|Beschreibung|
|--------|:-:|:----:|:--:|:-:|--------|------------|
|fms|X||||`{FMS}`||
@ -54,7 +52,8 @@ Ein BOSWatch Datenpaket wird in einem Python Dict abgebildet. In der nachfolgend
|vehicle|X||||`{VEC}`||
|tacticalInfo|X||||`{TACI}`|(I, II, III, IV)|
### Weitere Wildcards
---
## Weitere Wildcards
- `{BR}` - Zeilenumbruch `\r\n`
- `{LPAR}` - öffnende Klammer `(`
- `{RPAR}` - schließende Klammer `)`

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

View file

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

2492
docu/doxygen.ini Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,18 +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 Guide: config.md
# - Module: index.md
# - Plugins: index.md
# - 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: develop/ModulPlugin.md
- BOSWatch Packet Format: develop/packet.md
# - BW3 Source Docu: /api/index.html
- Changelog: changelog.md
- 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
name: mkdocs
highlightjs: true
hljs_style: github