diff --git a/.github/workflows/run_pytest.yml b/.github/workflows/run_pytest.yml index bf2f891..0b059ff 100644 --- a/.github/workflows/run_pytest.yml +++ b/.github/workflows/run_pytest.yml @@ -25,3 +25,8 @@ jobs: - name: Test with pytest run: | pytest -c 'test/pytest.ini' + - name: Save artifacts + uses: actions/upload-artifact@master + with: + name: test.log + path: log/test.log diff --git a/.gitignore b/.gitignore index 0bf01fb..7873218 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ \venv/ # generated files +stats_* log/ docu/docs/api/html docu/site/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..36568ce --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +FROM alpine:3.10 AS build-base +RUN apk add git make cmake g++ libusb-dev libpulse + +FROM build-base AS rtl_fm +ARG RTL_SDR_VERSION=0.6.0 +RUN git clone --depth 1 --branch ${RTL_SDR_VERSION} https://github.com/osmocom/rtl-sdr.git /opt/rtl_sdr +WORKDIR /opt/rtl_sdr/build +RUN cmake .. && make + +FROM build-base AS multimon +ARG MULTIMON_VERSION=1.1.8 +RUN git clone --depth 1 --branch ${MULTIMON_VERSION} https://github.com/EliasOenal/multimon-ng.git /opt/multimon +WORKDIR /opt/multimon/build +RUN cmake .. && make + +FROM alpine:3.10 AS boswatch +ARG BW_VERSION=develop +RUN apk add git && \ + git clone --depth 1 --branch ${BW_VERSION} https://github.com/BOSWatch/BW3-Core.git /opt/boswatch + + +FROM python:3.6-alpine AS runner +LABEL maintainer="bastian@schroll-software.de" + +# for RTL for MM +RUN apk add libusb-dev libpulse && \ + pip3 install pyyaml + +COPY --from=boswatch /opt/boswatch/ /opt/boswatch/ +COPY --from=multimon /opt/multimon/build/multimon-ng /opt/multimon/multimon-ng +COPY --from=rtl_fm /opt/rtl_sdr/build/src/ /opt/rtl_sdr/build/src/ \ No newline at end of file diff --git a/boswatch/configYaml.py b/boswatch/configYaml.py index 74af87e..3eb6521 100644 --- a/boswatch/configYaml.py +++ b/boswatch/configYaml.py @@ -33,6 +33,10 @@ class ConfigYAML: else: yield item + def __len__(self): + """!returns the length of an config element""" + return len(self._config) + def __str__(self): """!Returns the string representation of the internal config dict""" return str(self._config) diff --git a/boswatch/decoder/decoder.py b/boswatch/decoder/decoder.py index 8b60cb0..2ff2714 100644 --- a/boswatch/decoder/decoder.py +++ b/boswatch/decoder/decoder.py @@ -31,7 +31,6 @@ class Decoder: @param data: data to decode @return bwPacket instance""" - logging.debug("search decoder") data = str(data) if "FMS" in data: return FmsDecoder.decode(data) diff --git a/boswatch/decoder/fmsDecoder.py b/boswatch/decoder/fmsDecoder.py index 6384295..d374b28 100644 --- a/boswatch/decoder/fmsDecoder.py +++ b/boswatch/decoder/fmsDecoder.py @@ -62,10 +62,9 @@ class FmsDecoder: bwPacket.set("directionText", directionText) bwPacket.set("tacticalInfo", tacticalInfo) - logging.debug(bwPacket) return bwPacket - logging.warning("no valid data") + logging.warning("no valid FMS") return None logging.warning("CRC Error") return None diff --git a/boswatch/decoder/pocsagDecoder.py b/boswatch/decoder/pocsagDecoder.py index fdd7634..ff481b9 100644 --- a/boswatch/decoder/pocsagDecoder.py +++ b/boswatch/decoder/pocsagDecoder.py @@ -56,10 +56,9 @@ class PocsagDecoder: bwPacket.set("subricText", subricText) bwPacket.set("message", message) - logging.debug(bwPacket) return bwPacket - logging.warning("no valid data") + logging.warning("no valid POCSAG") return None @staticmethod diff --git a/boswatch/decoder/zveiDecoder.py b/boswatch/decoder/zveiDecoder.py index 6ccd65b..312435a 100644 --- a/boswatch/decoder/zveiDecoder.py +++ b/boswatch/decoder/zveiDecoder.py @@ -44,10 +44,9 @@ class ZveiDecoder: bwPacket.set("mode", "zvei") bwPacket.set("zvei", ZveiDecoder._solveDoubleTone(data[7:12])) - logging.debug(bwPacket) return bwPacket - logging.warning("no valid data") + logging.warning("no valid ZVEI") return None @staticmethod diff --git a/boswatch/inputSource/__init__.py b/boswatch/inputSource/__init__.py new file mode 100644 index 0000000..836e3e8 --- /dev/null +++ b/boswatch/inputSource/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- diff --git a/boswatch/inputSource/inputBase.py b/boswatch/inputSource/inputBase.py new file mode 100644 index 0000000..0a62410 --- /dev/null +++ b/boswatch/inputSource/inputBase.py @@ -0,0 +1,65 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""! + ____ ____ ______ __ __ __ _____ + / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ / + / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ < + / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ / +/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/ + German BOS Information Script + by Bastian Schroll + +@file: inoutSource.py +@date: 28.10.2018 +@author: Bastian Schroll +@description: Base class for boswatch input sources +""" +import time +import logging +import threading +from abc import ABC, abstractmethod + +logging.debug("- %s loaded", __name__) + + +class InputBase(ABC): + """!Base class for handling inout sources""" + + def __init__(self, inputQueue, inputConfig, decoderConfig): + """!Build a new InputSource class + + @param inputQueue: Python queue object to store input data + @param inputConfig: ConfigYaml object with the inoutSource config + @param decoderConfig: ConfigYaml object with the decoder config""" + self._inputThread = None + self._isRunning = False + self._inputQueue = inputQueue + self._inputConfig = inputConfig + self._decoderConfig = decoderConfig + + def start(self): + """!Start the input source thread""" + logging.debug("starting input thread") + self._isRunning = True + self._inputThread = threading.Thread(target=self._runThread, name="inputThread", + args=(self._inputQueue, self._inputConfig, self._decoderConfig)) + self._inputThread.daemon = True + self._inputThread.start() + + @abstractmethod + def _runThread(self, dataQueue, sdrConfig, decoderConfig): + """!Thread routine of the input source has to be inherit""" + + def shutdown(self): + """!Stop the input source thread""" + if self._isRunning: + logging.debug("wait for stopping the input thread") + self._isRunning = False + self._inputThread.join() + logging.debug("input thread stopped") + + def addToQueue(self, data): + """!Adds alarm data to the queue for further processing during boswatch client""" + self._inputQueue.put_nowait((data, time.time())) + logging.debug("Add received data to queue") + print(data) diff --git a/boswatch/inputSource/sdrInput.py b/boswatch/inputSource/sdrInput.py new file mode 100644 index 0000000..8f9ce1e --- /dev/null +++ b/boswatch/inputSource/sdrInput.py @@ -0,0 +1,77 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""! + ____ ____ ______ __ __ __ _____ + / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ / + / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ < + / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ / +/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/ + German BOS Information Script + by Bastian Schroll + +@file: sdrInput.py +@date: 28.10.2018 +@author: Bastian Schroll +@description: Input source for sdr with rtl_fm +""" +import logging +from boswatch.utils import paths +from boswatch.processManager import ProcessManager +from boswatch.inputSource.inputBase import InputBase + +logging.debug("- %s loaded", __name__) + + +class SdrInput(InputBase): + """!Class for the sdr input source""" + + def _runThread(self, dataQueue, sdrConfig, decoderConfig): + sdrProc = None + mmProc = None + try: + sdrProc = ProcessManager(str(sdrConfig.get("rtlPath", default="rtl_fm"))) + sdrProc.addArgument("-d " + str(sdrConfig.get("device", default="0"))) # device id + sdrProc.addArgument("-f " + str(sdrConfig.get("frequency"))) # frequencies + sdrProc.addArgument("-p " + str(sdrConfig.get("error", default="0"))) # frequency error in ppm + sdrProc.addArgument("-l " + str(sdrConfig.get("squelch", default="1"))) # squelch + sdrProc.addArgument("-g " + str(sdrConfig.get("gain", default="100"))) # gain + sdrProc.addArgument("-M fm") # set mode to fm + sdrProc.addArgument("-E DC") # set DC filter + sdrProc.addArgument("-s 22050") # bit rate of audio stream + sdrProc.setStderr(open(paths.LOG_PATH + "rtl_fm.log", "a")) + sdrProc.start() + + mmProc = ProcessManager(str(sdrConfig.get("mmPath", default="multimon-ng")), textMode=True) + if decoderConfig.get("fms", default=0): + mmProc.addArgument("-a FMSFSK") + if decoderConfig.get("zvei", default=0): + mmProc.addArgument("-a ZVEI1") + if decoderConfig.get("poc512", default=0): + mmProc.addArgument("-a POCSAG512") + if decoderConfig.get("poc1200", default=0): + mmProc.addArgument("-a POCSAG1200") + if decoderConfig.get("poc2400", default=0): + mmProc.addArgument("-a POCSAG2400") + mmProc.addArgument("-f alpha") + mmProc.addArgument("-t raw -") + mmProc.setStdin(sdrProc.stdout) + mmProc.setStderr(open(paths.LOG_PATH + "multimon-ng.log", "a")) + mmProc.start() + + logging.info("start decoding") + while self._isRunning: + if not sdrProc.isRunning: + logging.warning("rtl_fm was down - try to restart") + sdrProc.start() + elif not mmProc.isRunning: + logging.warning("multimon was down - try to restart") + mmProc.start() + elif sdrProc.isRunning and mmProc.isRunning: + line = mmProc.readline() + if line: + self.addToQueue(line) + except: + logging.exception("error in sdr input routine") + finally: + mmProc.stop() + sdrProc.stop() diff --git a/boswatch/network/client.py b/boswatch/network/client.py index 7c8cd3c..7792eec 100644 --- a/boswatch/network/client.py +++ b/boswatch/network/client.py @@ -57,6 +57,7 @@ class TCPClient: @return True or False""" try: if self.isConnected: + self._sock.shutdown(socket.SHUT_RDWR) self._sock.close() logging.debug("disconnected") return True @@ -72,29 +73,35 @@ class TCPClient: @param data: data to send to the server @return True or False""" try: - logging.debug("transmitting: %s", data) - header = str(len(data)).ljust(HEADERSIZE) - self._sock.sendall(bytes(header + data, "utf-8")) + logging.debug("transmitting:\n%s", data) + data = data.encode("utf-8") + header = str(len(data)).ljust(HEADERSIZE).encode("utf-8") + self._sock.sendall(header + data) logging.debug("transmitted...") return True except socket.error as e: logging.error(e) return False - def receive(self): + def receive(self, timeout=1): """!Receive data from the server + @param timeout: to wait for incoming data in seconds @return received data""" try: - read, _, _ = select.select([self._sock], [], [], 1) + read, _, _ = select.select([self._sock], [], [], timeout) if not read: # check if there is something to read return False - header = self._sock.recv(HEADERSIZE) + + header = self._sock.recv(HEADERSIZE).decode("utf-8") if not len(header): # check if there data return False - length = int(header.decode("utf-8").strip()) + + length = int(header.strip()) received = self._sock.recv(length).decode("utf-8") - logging.debug("received %d bytes: %s", length, received) + + logging.debug("recv header: '%s'", header) + logging.debug("received %d bytes: %s", len(received), received) return received except socket.error as e: logging.error(e) @@ -104,12 +111,17 @@ class TCPClient: def isConnected(self): """!Property of client connected state""" try: - aliveMsg = "" - header = str(len(aliveMsg)).ljust(HEADERSIZE) - self._sock.sendall(bytes(header + aliveMsg, "utf-8")) - return True + if self._sock: + _, write, _ = select.select([], [self._sock], [], 0.1) + if write: + data = "".encode("utf-8") + header = str(len(data)).ljust(HEADERSIZE).encode("utf-8") + self._sock.sendall(header + data) + return True + return False except socket.error as e: - if e.errno is 32: # broken pipe - no one will read from this pipe anymore - return False - logging.error(e) - return False + if e.errno != 32: + logging.exception(e) + return False + except ValueError: + return False diff --git a/boswatch/network/netCheck.py b/boswatch/network/netCheck.py index 5a4f3b8..2c208dc 100644 --- a/boswatch/network/netCheck.py +++ b/boswatch/network/netCheck.py @@ -27,10 +27,10 @@ class NetCheck: """!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)""" + @param timeout: timeout for connection check in sec. (1)""" self._hostname = hostname self._timeout = timeout - self._connectionState = False + self.connectionState = False self.checkConn() # initiate a first check def checkConn(self): @@ -40,14 +40,9 @@ class NetCheck: try: urlopen(self._hostname, timeout=self._timeout) logging.debug("%s is reachable", self._hostname) - self._connectionState = True + self.connectionState = True return True except: # todo find right exception type logging.warning("%s is not reachable", self._hostname) - self._connectionState = False + self.connectionState = False return False - - @property - def connectionState(self): - """!Property for the last connection state from checkConn()""" - return self._connectionState diff --git a/boswatch/network/server.py b/boswatch/network/server.py index 6f92846..eda41fc 100644 --- a/boswatch/network/server.py +++ b/boswatch/network/server.py @@ -46,17 +46,18 @@ class _ThreadedTCPRequestHandler(socketserver.BaseRequestHandler): if not read: continue # nothing to read on the socket - header = self.request.recv(HEADERSIZE) + header = self.request.recv(HEADERSIZE).decode("utf-8") if not len(header): break # empty data -> socked closed - length = int(header.decode("utf-8").strip()) + length = int(header.strip()) data = self.request.recv(length).decode("utf-8") - if data == "": + if data == "": continue - logging.debug("%s recv %d bytes: %s", req_name, length, data) + logging.debug("%s recv header: '%s'", req_name, header) + logging.debug("%s recv %d bytes:\n%s", req_name, len(data), data) # add a new entry and the decoded data dict as an string in utf-8 and an timestamp self.server.alarmQueue.put_nowait((self.client_address[0], data, time.time())) # queue is threadsafe @@ -64,15 +65,15 @@ class _ThreadedTCPRequestHandler(socketserver.BaseRequestHandler): logging.debug("%s send: [ack]", req_name) - data = "[ack]" - header = str(len(data)).ljust(HEADERSIZE) - self.request.sendall(bytes(header + data, "utf-8")) - self.request.close() + data = "[ack]".encode("utf-8") + header = str(len(data)).ljust(HEADERSIZE).encode("utf-8") + self.request.sendall(header + data) except socket.error as e: logging.error(e) return False finally: + self.request.close() del self.server.clientsConnected[threading.current_thread().name] logging.info("Client disconnected: %s", self.client_address[0]) @@ -115,7 +116,7 @@ class TCPServer: @return True or False""" if not self.isRunning: try: - socketserver.TCPServer.allow_reuse_address = False # because we can start two instances on same port elsewhere + socketserver.TCPServer.allow_reuse_address = True # because we can start two instances on same port elsewhere self._server = _ThreadedTCPServer(("", port), _ThreadedTCPRequestHandler) self._server.timeout = self._timeout self._server.alarmQueue = self._alarmQueue @@ -144,6 +145,7 @@ class TCPServer: if self.isRunning: self._server.shutdown() self._server.isActive = False + self._server.server_close() self._server_thread.join() self._server_thread = None self._server = None diff --git a/boswatch/packet.py b/boswatch/packet.py index 815d30b..014eed3 100644 --- a/boswatch/packet.py +++ b/boswatch/packet.py @@ -43,7 +43,7 @@ class Packet: @param fieldName: Name of the data to set @param value: Value to set""" - self._packet[fieldName] = value + self._packet[fieldName] = str(value) def get(self, fieldName): """!Returns the value from a single field. @@ -52,7 +52,7 @@ class Packet: @param fieldName: Name of the field @return Value or None""" try: - return self._packet[fieldName] + return str(self._packet[fieldName]) except: logging.warning("field not found: %s", fieldName) return None diff --git a/boswatch/processManager.py b/boswatch/processManager.py index 2e703be..f261fdc 100644 --- a/boswatch/processManager.py +++ b/boswatch/processManager.py @@ -32,9 +32,6 @@ class ProcessManager: self._processHandle = None self._textMode = textMode - def __del__(self): - self.stop() - def addArgument(self, arg): """!add a new argument diff --git a/boswatch/router/route.py b/boswatch/router/route.py index fb581bc..42072f5 100644 --- a/boswatch/router/route.py +++ b/boswatch/router/route.py @@ -15,24 +15,22 @@ @description: Class for a single BOSWatch packet router route point """ +import logging + +logging.debug("- %s loaded", __name__) + class Route: """!Class for single routing points""" - def __init__(self, name, callback): + def __init__(self, name, callback, statsCallback=None, cleanupCallback=None): """!Create a instance of an route point @param name: name of the route point @param callback: instance of the callback function + @param statsCallback: instance of the callback to get statistics (None) + @param cleanupCallback: instance of the callback to run a cleanup method (None) """ - self._name = name - self._callback = callback - - @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 + self.name = name + self.callback = callback + self.statistics = statsCallback + self.cleanup = cleanupCallback diff --git a/boswatch/router/router.py b/boswatch/router/router.py index 9f84b19..21d30b6 100644 --- a/boswatch/router/router.py +++ b/boswatch/router/router.py @@ -16,6 +16,7 @@ """ import logging import copy +import time logging.debug("- %s loaded", __name__) @@ -26,17 +27,25 @@ class Router: """!Create a new router @param name: name of the router""" - self._name = name - self._routeList = [] - logging.debug("[%s] new router", self._name) + self.name = name + self.routeList = [] + + # for time counting + self._cumTime = 0 + self._routerTime = 0 + + # for statistics + self._runCount = 0 + + logging.debug("[%s] add new router", self.name) def addRoute(self, route): """!Adds a route point to the router @param route: instance of the Route class """ - logging.debug("[%s] add route: %s", self._name, route.name) - self._routeList.append(route) + logging.debug("[%s] add route: %s", self.name, route.name) + self.routeList.append(route) def runRouter(self, bwPacket): """!Run the router @@ -44,29 +53,37 @@ class 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) + self._runCount += 1 + tmpTime = time.time() + + logging.debug("[%s] started", self.name) + + for routeObject in self.routeList: + logging.debug("[%s] -> run route: %s", self.name, routeObject.name) bwPacket_tmp = routeObject.callback(copy.deepcopy(bwPacket)) # copy bwPacket to prevent edit the original if bwPacket_tmp is None: # returning None doesnt change the bwPacket continue if bwPacket_tmp is False: # returning False stops the router immediately - logging.debug("[%s] stopped", self._name) + logging.debug("[%s] stopped", self.name) break bwPacket = bwPacket_tmp - logging.debug("[%s] <- bwPacket returned: %s", self._name, bwPacket) - logging.debug("[%s] finished", self._name) + logging.debug("[%s] bwPacket returned", self.name) + logging.debug("[%s] finished", self.name) + + self._routerTime = time.time() - tmpTime + self._cumTime += self._routerTime + return bwPacket - @property - def name(self): - """!Property to get the name of the router""" - return self._name + def _getStatistics(self): + """!Returns statistical information's from last router run - @property - def routeList(self): - """!Property to get a list of all route points of this router""" - return self._routeList + @return Statistics as pyton dict""" + stats = {"type": "router", + "runCount": self._runCount, + "cumTime": self._cumTime, + "moduleTime": self._routerTime} + return stats diff --git a/boswatch/router/routerManager.py b/boswatch/router/routerManager.py index c03bee4..cf26d5f 100644 --- a/boswatch/router/routerManager.py +++ b/boswatch/router/routerManager.py @@ -18,6 +18,7 @@ # todo think about implement threading for routers and the plugin calls (THREAD SAFETY!!!) import logging import importlib +import time from boswatch.configYaml import ConfigYAML from boswatch.router.router import Router from boswatch.router.route import Route @@ -30,16 +31,10 @@ class RouterManager: 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 + self._startTime = int(time.time()) # if there is an error, router list would be empty (see tmp variable) - def buildRouter(self, config): + def buildRouters(self, config): """!Initialize Routers from given config file @param config: instance of ConfigYaml class @@ -64,33 +59,42 @@ class RouterManager: for route in router.get("route"): routeType = route.get("type") - routeName = route.get("name") + routeRes = route.get("res") + routeName = route.get("name", default=routeRes) + routeConfig = route.get("config", default=ConfigYAML()) # if no config - build a empty - if routeType is None or routeName is None: + if routeType is None or routeRes is None: logging.error("type or name not found in route: %s", route) return False try: if routeType == "plugin": - importedFile = importlib.import_module(routeType + "." + routeName) + importedFile = importlib.import_module(routeType + "." + routeRes) loadedClass = importedFile.BoswatchPlugin(routeConfig) - routerDict_tmp[routerName].addRoute(Route(routeName, loadedClass._run)) + routerDict_tmp[routerName].addRoute(Route(routeName, + loadedClass._run, + loadedClass._getStatistics, + loadedClass._cleanup)) elif routeType == "module": - importedFile = importlib.import_module(routeType + "." + routeName) + importedFile = importlib.import_module(routeType + "." + routeRes) loadedClass = importedFile.BoswatchModule(routeConfig) - routerDict_tmp[routerName].addRoute(Route(routeName, loadedClass._run)) + routerDict_tmp[routerName].addRoute(Route(routeName, + loadedClass._run, + loadedClass._getStatistics, + loadedClass._cleanup)) elif routeType == "router": - routerDict_tmp[routerName].addRoute(Route(routeName, routerDict_tmp[routeName].runRouter)) + routerDict_tmp[routerName].addRoute(Route(routeName, routerDict_tmp[routeRes].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")) + # except ModuleNotFoundError: # only since Py3.6 + except ImportError: + logging.error("%s not found: %s", route.get("type"), route.get("res")) return False logging.debug("finished building routers") @@ -98,7 +102,7 @@ class RouterManager: self._showRouterRoute() return True - def runRouter(self, routerRunList, bwPacket): + def runRouters(self, routerRunList, bwPacket): """!Run given Routers @param routerRunList: string or list of router names in string form @@ -112,6 +116,16 @@ class RouterManager: else: logging.warning("unknown router: %s", routerName) + self._saveStats() # write stats to stats file + + def cleanup(self): + """!Run cleanup routines for all loaded route points""" + for name, routerObject in self._routerDict.items(): + logging.debug("Start cleanup for %s", name) + for routePoint in routerObject.routeList: + if routePoint.cleanup: + routePoint.cleanup() + def _showRouterRoute(self): """!Show the routes of all routers""" for name, routerObject in self._routerDict.items(): @@ -120,3 +134,27 @@ class RouterManager: for routePoint in routerObject.routeList: counter += 1 logging.debug(" %d. %s", counter, routePoint.name) + + def _saveStats(self): + """!Save current statistics to file""" + lines = [] + for name, routerObject in self._routerDict.items(): + lines.append("[" + name + "]") + lines.append(" - Route points: " + str(len(routerObject.routeList))) + lines.append(" - Runs: " + str(routerObject._getStatistics()['runCount'])) + for routePoint in routerObject.routeList: + lines.append("[+] " + routePoint.name) + if routePoint.statistics: + if routePoint.statistics()['type'] == "module": + lines.append(" - Runs: " + str(routePoint.statistics()['runCount'])) + lines.append(" - Run errors: " + str(routePoint.statistics()['moduleErrorCount'])) + elif routePoint.statistics()['type'] == "plugin": + lines.append(" - Runs: " + str(routePoint.statistics()['runCount'])) + lines.append(" - Setup errors: " + str(routePoint.statistics()['setupErrorCount'])) + lines.append(" - Alarm errors: " + str(routePoint.statistics()['alarmErrorCount'])) + lines.append(" - Teardown errors: " + str(routePoint.statistics()['teardownErrorCount'])) + lines.append("") + + with open("stats_" + str(self._startTime) + ".txt", "w") as stats: + for line in lines: + stats.write(line + "\n") diff --git a/boswatch/timer.py b/boswatch/timer.py index 92d1a62..fe600ea 100644 --- a/boswatch/timer.py +++ b/boswatch/timer.py @@ -36,8 +36,8 @@ class RepeatedTimer: self._args = args self._kwargs = kwargs self._start = 0 - self._overdueCount = 0 - self._lostEvents = 0 + self.overdueCount = 0 + self.lostEvents = 0 self._isRunning = False self._event = Event() self._thread = None @@ -88,8 +88,8 @@ class RepeatedTimer: 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 + 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) @@ -104,13 +104,3 @@ class RepeatedTimer: 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 diff --git a/boswatch/wildcard.py b/boswatch/wildcard.py index a57cce6..71d1c42 100644 --- a/boswatch/wildcard.py +++ b/boswatch/wildcard.py @@ -12,28 +12,43 @@ @file: wildcard.py @date: 15.01.2018 @author: Bastian Schroll -@description: Little Helper to replace wildcards in stings +@description: Functions to replace wildcards in stings """ import logging import time -# from boswatch.module import file - logging.debug("- %s loaded", __name__) -# todo check function and document + write an test -# 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 +# todo check function - write an test + +_additionalWildcards = {} + + +def registerWildcard(wildcard, bwPacketField): + """!Register a new additional wildcard + + @param wildcard: New wildcard string with format: '{WILDCARD}' + @param bwPacketField: Field of the bwPacket which is used for wildcard replacement""" + if wildcard in _additionalWildcards: + logging.error("wildcard always registered: %s", wildcard) + return + logging.debug("register new wildcard %s for field: %s", wildcard, bwPacketField) + _additionalWildcards[wildcard] = bwPacketField def replaceWildcards(message, bwPacket): + """!Replace the wildcards in a given message + + @param message: Message in which wildcards should be replaced + @param bwPacket: bwPacket instance with the replacement information + @return Input message with the replaced wildcards""" _wildcards = { # formatting wildcards + # todo check if br and par are needed - if not also change config "{BR}": "\r\n", "{LPAR}": "(", "{RPAR}": ")", - "{TIME}": time.time(), + "{TIME}": time.strftime("%d.%m.%Y %H:%M:%S"), # info wildcards # server @@ -78,7 +93,10 @@ def replaceWildcards(message, bwPacket): # message for MSG packet is done in poc } - for wildcard in _wildcards: - message = message.replace(wildcard, _wildcards.get(wildcard)) + for wildcard, field in _wildcards.items(): + message = message.replace(wildcard, field) + + for wildcard, field in _additionalWildcards.items(): + message = message.replace(wildcard, bwPacket.getField(field)) return message diff --git a/bw_client.py b/bw_client.py index 2d2a2f5..14da3af 100644 --- a/bw_client.py +++ b/bw_client.py @@ -32,8 +32,6 @@ logging.debug("BOSWatch client has started ...") logging.debug("Import python modules") import argparse logging.debug("- argparse") -import threading -logging.debug("- threading") import queue logging.debug("- queue") import time @@ -43,16 +41,14 @@ logging.debug("Import BOSWatch modules") from boswatch.configYaml import ConfigYAML from boswatch.network.client import TCPClient from boswatch.network.broadcast import BroadcastClient -from boswatch.processManager import ProcessManager from boswatch.decoder.decoder import Decoder from boswatch.utils import header from boswatch.utils import misc - +from boswatch.inputSource.sdrInput import SdrInput 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 @@ -60,7 +56,7 @@ parser = argparse.ArgumentParser(prog="bw_client.py", epilog="""More options you can find in the extern client.ini file in the folder /config""") parser.add_argument("-c", "--config", help="Name to configuration File", required=True) -parser.add_argument("-t", "--test", help="Start Client with testdata-set") +parser.add_argument("-t", "--test", help="Start Client with testdata-set", action="store_true") args = parser.parse_args() bwConfig = ConfigYAML() @@ -69,6 +65,10 @@ if not bwConfig.loadConfigFile(paths.CONFIG_PATH + args.config): exit(1) # ========== CLIENT CODE ========== +bwClient = None +inputSource = None +inputQueue = queue.Queue() + try: ip = bwConfig.get("server", "ip", default="127.0.0.1") port = bwConfig.get("server", "port", default="8080") @@ -79,73 +79,33 @@ try: ip = broadcastClient.serverIP port = broadcastClient.serverPort - inputQueue = queue.Queue() - inputThreadRunning = True + if not args.test: + logging.debug("loading input source: %s", bwConfig.get("client", "inputSource")) + if bwConfig.get("client", "inputSource") == "sdr": + inputSource = SdrInput(inputQueue, bwConfig.get("inputSource", "sdr"), bwConfig.get("decoder")) + else: + logging.fatal("Invalid input source: %s", bwConfig.get("client", "inputSource")) + exit(1) - # ========== INPUT CODE ========== - def handleSDRInput(dataQueue, sdrConfig, decoderConfig): # todo exception handling inside - sdrProc = ProcessManager(str(sdrConfig.get("rtlPath", default="rtl_fm"))) - sdrProc.addArgument("-d " + str(sdrConfig.get("device", default="0"))) # device id - sdrProc.addArgument("-f " + sdrConfig.get("frequency")) # frequencies - sdrProc.addArgument("-p " + str(sdrConfig.get("error", default="0"))) # frequency error in ppm - sdrProc.addArgument("-l " + str(sdrConfig.get("squelch", default="1"))) # squelch - sdrProc.addArgument("-g " + str(sdrConfig.get("gain", default="100"))) # gain - sdrProc.addArgument("-M fm") # set mode to fm - sdrProc.addArgument("-E DC") # set DC filter - sdrProc.addArgument("-s 22050") # bit rate of audio stream - sdrProc.start() - sdrProc.skipLinesUntil("Output at") - - mmProc = ProcessManager(str(sdrConfig.get("mmPath", default="multimon-ng")), textMode=True) - if decoderConfig.get("fms", default=0): - mmProc.addArgument("-a FMSFSK") - if decoderConfig.get("zvei", default=0): - mmProc.addArgument("-a ZVEI1") - if decoderConfig.get("poc512", default=0): - mmProc.addArgument("-a POCSAG512") - if decoderConfig.get("poc1200", default=0): - mmProc.addArgument("-a POCSAG1200") - if decoderConfig.get("poc2400", default=0): - mmProc.addArgument("-a POCSAG2400") - mmProc.addArgument("-f alpha") - mmProc.addArgument("-t raw -") - mmProc.setStdin(sdrProc.stdout) - mmProc.start() - mmProc.skipLinesUntil("Available demodulators:") - - logging.info("start decoding") - while inputThreadRunning: - if not sdrProc.isRunning: - logging.warning("rtl_fm was down - try to restart") - sdrProc.start() - sdrProc.skipLinesUntil("Output at") # last line form rtl_fm before data - elif not mmProc.isRunning: - logging.warning("multimon was down - try to restart") - mmProc.start() - mmProc.skipLinesUntil("Available demodulators:") # last line from mm before data - elif sdrProc.isRunning and mmProc.isRunning: - line = mmProc.readline() - if line: - dataQueue.put_nowait((line, time.time())) - logging.debug("Add data to queue") - print(line) - logging.debug("stopping thread") - mmProc.stop() - sdrProc.stop() - - # ========== INPUT CODE ========== - - mmThread = threading.Thread(target=handleSDRInput, name="mmReader", args=(inputQueue, bwConfig.get("inputSource", "sdr"), bwConfig.get("decoder"))) - mmThread.daemon = True - mmThread.start() + inputSource.start() + else: + logging.warning("STARTING TESTMODE!") + logging.debug("reading testdata from file") + testFile = open("test/testdata.list", mode="r", encoding="utf-8") + for testData in testFile: + if (len(testData.rstrip(' \t\n\r')) > 1) and ("#" not in testData[0]): + logging.info("Testdata: %s", testData.rstrip(' \t\n\r')) + inputQueue.put_nowait((testData.rstrip(' \t\n\r'), time.time())) + logging.debug("finished reading testdata") bwClient = TCPClient() bwClient.connect(ip, port) while 1: if not bwClient.isConnected: - logging.warning("connection to server lost - sleep %d seconds", bwConfig.get("client", "reconnectDelay", default="3")) - time.sleep(bwConfig.get("client", "reconnectDelay", default="3")) + reconnectDelay = bwConfig.get("client", "reconnectDelay", default="3") + logging.warning("connection to server lost - sleep %d seconds", reconnectDelay) + time.sleep(reconnectDelay) bwClient.connect(ip, port) elif not inputQueue.empty(): @@ -164,13 +124,16 @@ try: for sendCnt in range(bwConfig.get("client", "sendTries", default="3")): bwClient.transmit(str(bwPacket)) - if bwClient.receive() == "[ack-]": + if bwClient.receive() == "[ack]": logging.debug("ack ok") break - logging.warning("cannot send packet - sleep %d seconds", bwConfig.get("client", "sendDelay", default="3")) - time.sleep(bwConfig.get("client", "sendDelay", default="3")) + sendDelay = bwConfig.get("client", "sendDelay", default="3") + logging.warning("cannot send packet - sleep %d seconds", sendDelay) + time.sleep(sendDelay) else: + if args.test: + break time.sleep(0.1) # reduce cpu load (wait 100ms) # in worst case a packet have to wait 100ms until it will be processed @@ -183,7 +146,8 @@ except: # pragma: no cover logging.exception("BOSWatch interrupted by an error") finally: logging.debug("Starting shutdown routine") - bwClient.disconnect() - inputThreadRunning = False - mmThread.join() + if inputSource: + inputSource.shutdown() + if bwClient: + bwClient.disconnect() logging.debug("BOSWatch client has stopped ...") diff --git a/bw_server.py b/bw_server.py index abf0fef..5b0e6af 100644 --- a/bw_server.py +++ b/bw_server.py @@ -46,11 +46,9 @@ from boswatch.network.broadcast import BroadcastServer from boswatch.router.routerManager import RouterManager from boswatch.utils import misc - 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 @@ -67,10 +65,13 @@ if not bwConfig.loadConfigFile(paths.CONFIG_PATH + args.config): exit(1) # ############################# begin server system -try: +bwRoutMan = None +bwServer = None +bcServer = None +try: bwRoutMan = RouterManager() - if not bwRoutMan.buildRouter(bwConfig): + if not bwRoutMan.buildRouters(bwConfig): logging.fatal("Error while building routers") exit(1) @@ -97,7 +98,9 @@ try: bwPacket.set("clientIP", data[0]) misc.addServerDataToPacket(bwPacket, bwConfig) - bwRoutMan.runRouter(bwConfig.get("alarmRouter"), bwPacket) + logging.debug("[ --- ALARM --- ]") + bwRoutMan.runRouters(bwConfig.get("alarmRouter"), bwPacket) + logging.debug("[ --- END ALARM --- ]") incomingQueue.task_done() @@ -109,7 +112,10 @@ except: # pragma: no cover logging.exception("BOSWatch interrupted by an error") finally: logging.debug("Starting shutdown routine") - del bwRoutMan - bwServer.stop() - bcServer.stop() + if bwRoutMan: + bwRoutMan.cleanup() + if bwServer: + bwServer.stop() + if bcServer: + bcServer.stop() logging.debug("BOSWatch server has stopped ...") diff --git a/config/server.yaml b/config/server.yaml index bf17173..03e260d 100644 --- a/config/server.yaml +++ b/config/server.yaml @@ -19,8 +19,12 @@ router: - name: Router 1 route: - type: module - name: filter.modeFilter + res: filter.modeFilter + name: Filter Fms/Zvei config: allowed: - fms - zvei + - type: plugin + name: test plugin + res: template_plugin diff --git a/docu/docs/config.md b/docu/docs/config.md index 3a10798..3a327f5 100644 --- a/docu/docs/config.md +++ b/docu/docs/config.md @@ -110,7 +110,8 @@ Jeder Router kann eine beliebige Anzahl einzelner Routenpunkte enthalten. Diese |Feld|Beschreibung|Default| |----|------------|-------| |type|Art des Routenpunktes (module, plugin, router)|| -|name|Zu ladende Resource (Siehe entsprechende Kapitel)|| +|res|Zu ladende Resource (Siehe entsprechende Kapitel)|| +|name|Optionaler Name des Routenpunktes|gleich wie Resource| |config|Konfigurationseinstellungen des Routenpunktes (Siehe entsprechende Kapitel)|| **Beispiel:** @@ -119,7 +120,8 @@ router: - name: Router 1 route: - type: module - name: filter.modeFilter + res: filter.modeFilter + name: Filter Fms/Zvei config: allowed: - fms diff --git a/docu/docs/develop/ModulPlugin.md b/docu/docs/develop/ModulPlugin.md index 1b6b58e..30a85c1 100644 --- a/docu/docs/develop/ModulPlugin.md +++ b/docu/docs/develop/ModulPlugin.md @@ -33,7 +33,8 @@ Die Plugin Basisklasse bietet einige Methoden, welche vom Plugin überschrieben 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) + res: template_module # Name der Python Datei (ohne .py) + name: Mein Modul # optionaler Name config: # config-Sektion option1: value 1 option2: @@ -43,7 +44,7 @@ Jedes Modul oder Plugin wird in einem Router folgendermaßen deklariert: - list 1 - list 2 ``` -Eine entsprechende Dokumentation der Parameter ist in der Dokumentation der [Konfiguration](../config.md) zu hinterlegen. +Eine entsprechende Dokumentation der Parameter **muss** in der Dokumentation des jeweiligen Moduls oder Plugins hinterleget werden. ### Konfiguration verwenden Wird der Instanz eine Konfiguration übergeben wird diese in `self.config` abgelegt und kann wie folgt abgerufen werden: @@ -57,7 +58,7 @@ Wird der Instanz eine Konfiguration übergeben wird diese in `self.config` abgel `self.config.get("option2", "underOption1")` > liefert `value 21` -- Es kann ein Default Wert angegeben werden +- Es kann ein Default Wert angegeben werden (falls entsprechender Eintrag fehlt) `self.config.get("notSet", default="defValue")` > liefert `defValue` @@ -75,30 +76,58 @@ Aus dieser kann mittels `bwPacket.get(FIELDNAME)` das entsprechende Feld ausgele 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: +**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. +- Sollte ein Modul oder Plugin Felder benutzen, welche in einem anderen Modul erstellt werden, **muss** dies im Punkt `Abhänigkeiten` des jeweiligen Moduls oder Plugins dokumentiert werden. -### Zu beachten bei Module +### Rückgabewert bei Modulen Module können Pakete beliebig verändern. Diese Änderungen werden im Router entsprechend weitergeleitet. Mögliche Rückgabewerte eines Moduls: -- `return bwPacket` gibt das modifizierte bwPacket an den Router zurück -- `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) +- `return bwPacket` Gibt das modifizierte bwPacket an den Router zurück (Paket Modifikation) +- `return None` Der Router fährt mit dem unveränderten bwPacket fort (Input = Output) +- `return False` Der Router stopt sofort die Ausführung (zB. in Filtern verwendet) -### Zu beachten bei Plugins +### Rückgabewert bei Plugins Plugins geben keine Pakete mehr zurück. Sie fungieren ausschließlich als Endpunkt. Die Plugin Basisklasse liefert intern immer ein `None` an den Router zurück, was zur weiteren Ausführung des Routers mit dem original Paket führt. Daher macht es in Plugins keinen Sinn ein Paket zu modifizieren. + +--- +## Nutzung der Wildcards + +Es gibt einige vordefinierte Wildcards welche in der [BOSWatch Paket](packet.md) Dokumentation zu finden sind. + +Außerdem sind die folgenden allgemeinen Wildcards definiert: + +- `{BR}` - Zeilenumbruch `\r\n` +- `{LPAR}` - öffnende Klammer `(` +- `{RPAR}` - schließende Klammer `)` +- `{TIME}` - Aktueller Zeitstempel im Format `%d.%m.%Y %H:%M:%S` + +### Wildcards registrieren [Module] +Module können zusätzliche Wildcards registrieren welche anschließend in den Plugins ebenfalls geparst werden können. +Dies kann über die interne Methode `self.registerWildcard(newWildcard, bwPacketField)` gemacht werden. + +- `newWildcard` muss im folgenden Format angegeben werden: `{WILDCARD}` +- `bwPacketField` ist der Name des Feldes im bwPacket - gestezt per `bwPacket.set(FIELDNAME, VALUE)` + +**Bitte beachten:** + +- Selbst vom Modul registrierte Wildcards **müssen** in der Modul Dokumentation unter `Zusätzliche Wildcards` aufgeführt werden. +### Wildcards parsen [Plugins] +Das parsen der Wildcars funktioniert komfortabel über die interne Methode `msg = self.parseWildcards(msg)`. + +- `msg` enstrpicht dabei dem String in welchem die Wildcards ersetzt werden sollen + +Die Platzhalter der Wildcards findet man in der [BOSWatch Paket](packet.md) Dokumentation. + +Sollten Module zusätzliche Wildcards registrieren, findet man Informationen dazu in der jeweiligen Modul Dokumentation + --- ## 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. + \ No newline at end of file diff --git a/docu/docs/develop/packet.md b/docu/docs/develop/packet.md index 5a20bb1..14d2e88 100644 --- a/docu/docs/develop/packet.md +++ b/docu/docs/develop/packet.md @@ -1,4 +1,4 @@ -#
BOSWatch Packet Format
+#
BOSWatch Paket Format
Ein BOSWatch Datenpaket wird in einem Python Dict abgebildet. In der nachfolgenden Tabelle sind die genutzten Felder abgebildet. @@ -48,13 +48,4 @@ Ein BOSWatch Datenpaket wird in einem Python Dict abgebildet. In der nachfolgend |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 diff --git a/docu/docs/img/client.drawio b/docu/docs/img/client.drawio new file mode 100644 index 0000000..efe7227 --- /dev/null +++ b/docu/docs/img/client.drawio @@ -0,0 +1 @@ +7Vjbcts2EP0aPdrDi0gpj5FsJ9Nxpm7VadMnD0iuSNQgwYJLU+rXFzeSoijJlyhOMq09o9EeAIvdPdhDQhN/mW8+CFJmn3gCbOI5yWbiX008bx5M5acCtgbwZ74BUkETA7k9sKL/gAUdi9Y0gWowETlnSMshGPOigBgHGBGCN8Npa86Gu5YkhRGwigkbo3/QBDObljfr8Y9A06zd2Q3fmZGctJNtJlVGEt7sQP71xF8KztF8yzdLYKp2bV3Mupsjo11gAgp8zoKmWa0XC//q4Sf8rYohvZn/vrqY29hw2yYMiczfmlxgxlNeEHbdowvB6yIB5dWRVj/nlvNSgq4E/wLErSWT1MgllGHO7ChsKH5Wyy/DmTX/1GZgrauNda6N7Y5xB4LmgCBarECx/bxr7HhSZu9KW62vcfFsPSteixhOVKw9hESkgCfmeWaeKufOBpaaD8BlFmIrJwhgBOnj8LgRe2rTbl5PrPxiuX0Bz9bvI2G13WlE/CMIpPLgv2c0LSSEis1Fi96SCNgdryhSrkYjjshzOYHtDcSyooqeBbF+OmDnBPAaGS1g2TWt4mRNGVtyxoWOx1/PY4hjiVco+APsjERSVQK1IhUkodL9FRXSjdm/UAeyW9X2rKeQjJQq13yTKqm6LAAbLh6qS+WF3yNvdJhHj4YqBWxOktmqnm15q3mhNZteQILAQNmudjjOV2I//CZd/uat6T2zNYPvqjW9A60ZMlQNwWUFvDBFXRWDRfuAmbTDbfh3zduBi0qz815OcKflph9svfwKMagMZdUyASRpvcpMDu0u4VEAkXgaqUpSPCtI/3SQpEjUuwTE8jVDx6w+E4JEd5dsLi8kuZKtIqrKYdwmhGFY9PUxCWT363yinrndFvS8aetkT8Y/LvQXZJTX8q0q58VFkZ7KaXQy9pRlqBtNRhFWJdF920jZHWrESNod/Td+GNws1f951NkNn5RnN3DeUp9dd1TGNxDo1wvt9NwCapfecarFzBIV7hHlunsEGKW3q/Y46MJ4PS3Tb6nMnwgtvm9ZXoFWYyPCssUfAPXtrMMqELIrVZXXHRbxqiEYZ52M6/HKXJjijCrxsVLu0KKs0aj7C2Xwxxep6fRpkZq9qUjN/hsvkcEzXyLProFfRE5wVKo6YfilBjl2UDH2eG0vSvFWXtISfSl6okUiw/Nt1AFSDlLN/s/mqnesl7q73LHb3xl6yZ8Gg16ajXspPNBK86/VSe8OkPX/Zdxexhu5nEFV3efy4ZCf5wCE+2IajMXUOXQCghefAGn2P+mZF5H+d1H/+l8= \ No newline at end of file diff --git a/docu/docs/img/client.png b/docu/docs/img/client.png new file mode 100644 index 0000000..fea5512 Binary files /dev/null and b/docu/docs/img/client.png differ diff --git a/docu/docs/img/router.drawio b/docu/docs/img/router.drawio new file mode 100644 index 0000000..dd94468 --- /dev/null +++ b/docu/docs/img/router.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docu/docs/img/router.png b/docu/docs/img/router.png new file mode 100644 index 0000000..61af275 Binary files /dev/null and b/docu/docs/img/router.png differ diff --git a/docu/docs/img/server.drawio b/docu/docs/img/server.drawio new file mode 100644 index 0000000..7273c8c --- /dev/null +++ b/docu/docs/img/server.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docu/docs/img/server.png b/docu/docs/img/server.png new file mode 100644 index 0000000..f19189f Binary files /dev/null and b/docu/docs/img/server.png differ diff --git a/docu/docs/index.md b/docu/docs/index.md index a6840b4..114a3a5 100644 --- a/docu/docs/index.md +++ b/docu/docs/index.md @@ -3,10 +3,6 @@
![BOSWatch](img/bw3.png "BOSWatch 3 Logo") - -Falls du uns unterstützen möchtest würden wir uns über eine Spende freuen. -Server, Hosting, Domain sowie Kaffee kosten leider Geld ;-) -[![](https://www.paypalobjects.com/de_DE/DE/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CLK9VBN2MSLZY&source=url)
**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.** @@ -15,3 +11,12 @@ Server, Hosting, Domain sowie Kaffee kosten leider Geld ;-) --- **The intercept of the German BOS radio is strictly prohibited and will be prosecuted. The use is only permitted for authorized personnel.** + +--- + +
+Falls du uns unterstützen möchtest würden wir uns über eine Spende freuen. +Server, Hosting, Domain sowie Kaffee kosten leider Geld ;-) + +[![](https://www.paypalobjects.com/de_DE/DE/i/btn/btn_donate_LG.gif)](https://www.paypal.me/BSchroll) +
\ No newline at end of file diff --git a/docu/docs/information/broadcast.md b/docu/docs/information/broadcast.md index 1491410..70aad2f 100644 --- a/docu/docs/information/broadcast.md +++ b/docu/docs/information/broadcast.md @@ -1,6 +1,6 @@ #
Broadcast Service
-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. +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.* @@ -8,7 +8,7 @@ Durch den Broadcast Service haben CLients die Möglichkeit, automatisch den Serv ## 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. +Nachfolgend soll der Ablauf einer Verbindung des Clienten zum Server mittels des Broadcast Services erklärt werden.
![](../img/broadcast.png)
@@ -16,16 +16,16 @@ Nachfolgend soll der Ablauf einer Verbunding des Clienen zum Server mittels des ## 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. +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 Verbindungsgdaten des Servers 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 `` und wird von allen im selben Subnetz vohandenen Gegenstellen empfangen. Nun wird auf eine Antwort des Broadcast Server mit den Verbindungsdaten gewartet. +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 `` und wird von allen im selben Subnetz befindlichen Gegenstellen empfangen. Nun wartet der Client auf eine Antwort des Broadcast Server mit den Verbindungsdaten. ### Schritt 3 - Verbindungsdaten senden -Wird nun ein Broadcast Paket empfangen, prüft der BC Server die Daten auf das Magic-Word ``. 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: `;8080` wobei die `8080` hier den normalen TCP Kommunikationsport des Server darstellt. +Wird nun ein Broadcast Paket empfangen, prüft der BC Server die Daten auf das Magic-Word ``. 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: `;8080` wobei die `8080` hier den normalen TCP Kommunikationsport des Servers 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 ``. 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. +Nachdem der Client das direkt an ihn gerichtete Paket mit den Verbindungsdaten vom Server empfangen hat, prüft er auf das Magic-Word ``. Ist dieses enthalten wird der Port für die TCP Verbindung 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 auf den angegebenen Port mit dem BOSWatch Server verbinden um seine Alarmierungs-Pakete abzusetzen. 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. diff --git a/docu/docs/information/router.md b/docu/docs/information/router.md new file mode 100644 index 0000000..93a4bd4 --- /dev/null +++ b/docu/docs/information/router.md @@ -0,0 +1,93 @@ +#
Routing Mechanismus
+ +BOSWatch 3 hat einen Routing Mechanismus integriert. Mit diesem ist es auf einfache Weise möglich, den Verlauf von Alarmpaketen zu steuern. + + +--- +## Ablauf + +Nachfolgender Ablauf soll am Beispiel eines Alarms mit einem Pocsag Paket erklärt werden. + +
![](../img/router.png)
+ +- BOSWatch startet alle Router, welche in der config als `alarmRouter` konfiguriert worden sind (in diesem Fall nur `Router1`) +- Der Router `Router1` beginnt seine Ausführung und arbeitet die einzelnen Routenpunkte sequentiell ab + - Das Modul `descriptor` wird aufgerufen und fügt ggf. Beschreibungen zum Paket hinzu + - Das Modul `doubleFilter` wird aufgerufen und blockiert doppelte Alarme + (hier würde die Ausführung dieses Routers und damit des kompletten Alarmprozesses stoppen wenn der Alarm als doppelter erkannt würde) + - Der Router `Router2` wir nun aufgerufen (bis zur Rückkehr aus `Router2` ist der Router `Router1` angehalten) +- Der Router `Router2` beginnt seine Ausführung und arbeitet die einzelnen Routenpunkte sequentiell ab + - Das Modul `modeFilter` wird aufgerufen und stoppt den Router da es sich nicht um ein FMS Paket handelt + - Es wird zur Ausführung von `Router1` zurückgekehrt +- Der Router `Router3` beginnt seine Ausführung und arbeitet die einzelnen Routenpunkte sequentiell ab + - Das Modul `modeFilter` wird aufgerufen und leitet das Paket weiter da es sich um ein Pocsag Paket handelt + - Das Plugin `Telegram` wird aufgerufen + - Das Plugin `MySQL` wird augerufen + - Es wird zur Ausführung von `Router1` zurückgekehrt +- Der Router `Router1` setzt seine Ausführung fort + - Das Modul `modeFilter` wird aufgerufen und stoppt den Router da es sich nicht um ein ZVEI Paket handelt + +Jetzt sind alle Routenpunkte abgearbeitet und die Alarmierung damit abgeschlossen. + +--- +## Konfiguration + +Nachfolgend ist die Router Konfiguration des BW3-Servers für das obige Beispiel zu finden: + +```yaml +alarmRouter: + - Router1 + +router: + - name: Router1 + route: + - type: module + res: descriptor + config: + [...] + - type: module + res: filter.doubleFilter + config: + [...] + - type: router + res: Router2 + - type: router + res: Router3 + - type: module + res: filter.modeFilter + config: + allowed: + - zvei + - type: plugin + res: sms + config: + [...] + + - name: Router2 + route: + - type: module + res: filter.modeFilter + config: + allowed: + - fms + - type: plugin + res: mysql + config: + [...] + + - name: Router3 + route: + - type: module + res: filter.modeFilter + config: + allowed: + - pocsag + - type: plugin + res: telegram + config: + [...] + - type: plugin + res: mysql + config: + [...] +``` diff --git a/docu/docs/information/serverclient.md b/docu/docs/information/serverclient.md index f0d6bca..6916b67 100644 --- a/docu/docs/information/serverclient.md +++ b/docu/docs/information/serverclient.md @@ -17,6 +17,8 @@ nachträglich an den Server übermittelt werden. Dabei überwacht der Client selbstständig die benötigten Programme zum Empfang der Daten und startet diese bei einem Fehler ggf. neu. +
![](../img/client.png)
+ --- ## BOSWatch Server @@ -26,4 +28,6 @@ Verarbeitung der Daten. Auch hier werden die empfangenen Daten in From von bwPacket's in einer Queue abelegt um zu gewährleisten, das auch während einer länger dauernden Plugin Ausführung alle Pakete korrekt empfangen werden können und es zu keinen Verlusten kommt. Die Verarbeitung der Pakete geschieht anschließend in sogenannten Routern, welche aufgrund ihres Umfangs jedoch in einem eigenen Kapitel -erklärt werden. Diese steuern die Verteilung der Daten an die einzelnen Plugins. \ No newline at end of file +erklärt werden. Diese steuern die Verteilung der Daten an die einzelnen Plugins. + +
![](../img/server.png)
\ No newline at end of file diff --git a/docu/docs/modul/descriptor.md b/docu/docs/modul/descriptor.md new file mode 100644 index 0000000..dd53b5d --- /dev/null +++ b/docu/docs/modul/descriptor.md @@ -0,0 +1,66 @@ +#
Descriptor
+--- + +## Beschreibung +Mit diesem Modul können einem Alarmpaket beliebige Beschreibungen in Abhänigkeit der enthaltenen Informationen hinzugefügt werden. + +## Resource +`descriptor` + +## Konfiguration + +Informationen zum Aufbau eines [BOSWatch Pakets](../develop/packet.md) + +|Feld|Beschreibung|Default| +|----|------------|-------| +|scanField|Feld des BW Pakets welches geprüft werden soll|| +|descrField|Name des Feldes im BW Paket in welchem die Beschreibung gespeichert werden soll|| +|wildcard|Optional: Es kann für das angelegte `descrField` automatisch ein Wildcard registriert werden|None| +|descriptions|Liste der Beschreibungen|| + +#### `descriptions:` + +|Feld|Beschreibung|Default| +|----|------------|-------| +|for|Inhalt im `scanField` auf welchem geprüft werden soll|| +|add|Beschreibungstext welcher im `descrField` hinterlegt werden soll|| + +**Beispiel:** +```yaml +- type: module + res: descriptor + config: + - scanField: zvei + descrField: description + wildcard: "{DESCR}" + descriptions: + - for: 12345 + add: FF DescriptorTest + - for: 45678 + add: FF TestDescription + - scanField: status + descrField: fmsStatDescr + wildcard: "{STATUSTEXT}" + descriptions: + - for: 1 + add: Frei (Funk) + - for: 2 + add: Frei (Wache) + - ... +``` + +--- +## Abhängigkeiten + +- keine + +--- +## Paket Modifikationen + +- Wenn im Paket das Feld `scanField` vorhanden ist, wird das Feld `descrField` dem Paket hinzugefügt +- Wenn keine Beschreibung vorhanden ist, wird im Feld `descrField` der Inhalt des Feldes `scanField` hinterlegt + +--- +## Zusätzliche Wildcards + +- Von der Konfiguration abhängig \ No newline at end of file diff --git a/docu/docs/modul/mode_filter.md b/docu/docs/modul/mode_filter.md index 5f167f7..f805835 100644 --- a/docu/docs/modul/mode_filter.md +++ b/docu/docs/modul/mode_filter.md @@ -2,7 +2,7 @@ --- ## 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. +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` @@ -16,7 +16,7 @@ Mit diesem Modul ist es Möglich, die Pakete auf bestimmte Modes (FMS, POCSAG, Z **Beispiel:** ```yaml - type: module - name: filter.modeFilter + res: filter.modeFilter config: allowed: - fms @@ -32,3 +32,8 @@ Mit diesem Modul ist es Möglich, die Pakete auf bestimmte Modes (FMS, POCSAG, Z ## Paket Modifikationen - keine + +--- +## Zusätzliche Wildcards + +- keine diff --git a/docu/docs/modul/regex_filter.md b/docu/docs/modul/regex_filter.md new file mode 100644 index 0000000..f554452 --- /dev/null +++ b/docu/docs/modul/regex_filter.md @@ -0,0 +1,65 @@ +#
Regex Filter
+--- + +## Beschreibung +Mit diesem Modul ist es möglich, komplexe Filter basierend auf Regulären Ausdrücken (Regex) anzulegen. +Für einen Filter können beliebig viele Checks angelegt werden, welche Felder eines BOSWatch Pakets mittels Regex prüfen. + +Folgendes gilt: + +- Die Filter werden nacheinander abgearbeitet +- Innerhalb des Filters werden die Checks nacheinander abgearbeitet +- Sobald ein einzelner Check fehlschlägt ist der ganze Filter fehlgeschlagen +- Sobald ein Filter mit all seinen Checks besteht, wird mit der Ausführung des Routers fortgefahren +- Sollten alle Filter fehlschlagen wird die Ausführung des Routers beendet + +Vereinfacht kann man sagen, dass einzelnen Router ODER-verknüpft und die jeweiligen Checks UND-verknüpft sind. + +## Resource +`filter.regexFilter` + +## Konfiguration + +|Feld|Beschreibung|Default| +|----|------------|-------| +|name|Beliebiger Name des Filters|| +|checks|Liste der einzelnen Checks innerhalb des Filters|| + +#### `checks:` + +|Feld|Beschreibung|Default| +|----|------------|-------| +|field|Name des Feldes innerhalb des BOSWatch Pakets welches untersucht werden soll|| +|regex|Regulärer Ausdruck (Bei Sonderzeichen " " verwenden)|| + +**Beispiel:** +```yaml +- type: module + res: filter.regexFilter + config: + - name: "Zvei filter" + checks: + - field: zvei + regex: "65[0-9]{3}" # all zvei with starting 65 + - name: "FMS Stat 3" + checks: + - field: mode + regex: "fms" # check if mode is fms + - field: status + regex: "3" # check if status is 3 +``` + +--- +## Abhängigkeiten + +- keine + +--- +## Paket Modifikationen + +- keine + +--- +## Zusätzliche Wildcards + +- keine diff --git a/docu/mkdocs.yml b/docu/mkdocs.yml index 296b21e..0948dd6 100644 --- a/docu/mkdocs.yml +++ b/docu/mkdocs.yml @@ -15,10 +15,12 @@ nav: - Server/Cient Prinzip: information/serverclient.md - Broadcast Service: information/broadcast.md # - Modul/Plugin Konzept: tbd.md -# - Routing Mechanismus: tbd.md + - Routing Mechanismus: information/router.md - Changelog: changelog.md - Module: - Mode Filter: modul/mode_filter.md + - Regex Filter: modul/regex_filter.md + - Descriptor: modul/descriptor.md - Plugins: tbd.md - Entwickler: - Eigenes Modul/Plugin schreiben: develop/ModulPlugin.md diff --git a/module/descriptor.py b/module/descriptor.py new file mode 100644 index 0000000..77a28f8 --- /dev/null +++ b/module/descriptor.py @@ -0,0 +1,58 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""! + ____ ____ ______ __ __ __ _____ + / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ / + / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ < + / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ / +/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/ + German BOS Information Script + by Bastian Schroll + +@file: descriptor.py +@date: 27.10.2019 +@author: Bastian Schroll +@description: Module to add descriptions to bwPackets +""" +import logging +from module.moduleBase import ModuleBase + +# ###################### # +# Custom plugin includes # + +# ###################### # + +logging.debug("- %s loaded", __name__) + + +class BoswatchModule(ModuleBase): + """!Adds descriptions to bwPackets""" + 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""" + for descriptor in self.config: + if descriptor.get("wildcard", default=None): + self.registerWildcard(descriptor.get("wildcard"), descriptor.get("descrField")) + + def doWork(self, bwPacket): + """!start an run of the module. + + @param bwPacket: A BOSWatch packet instance""" + for descriptor in self.config: + for description in descriptor.get("descriptions"): + if not bwPacket.get(descriptor.get("scanField")): + break # scanField is not available in this packet + bwPacket.set(descriptor.get("descrField"), description.get("for")) + if str(description.get("for")) == bwPacket.get(descriptor.get("scanField")): + logging.debug("Description '%s' added in packet field '%s'", + description.get("add"), descriptor.get("descrField")) + bwPacket.set(descriptor.get("descrField"), description.get("add")) + break # this descriptor has found a description - run next descriptor + return bwPacket + + def onUnload(self): + """!Called by destruction of the plugin""" + pass diff --git a/module/filter/modeFilter.py b/module/filter/modeFilter.py index 4e08623..a81b688 100644 --- a/module/filter/modeFilter.py +++ b/module/filter/modeFilter.py @@ -15,7 +15,7 @@ @description: Filter module for the packet type """ import logging -from module.module import Module +from module.moduleBase import ModuleBase # ###################### # # Custom plugin includes # @@ -25,7 +25,7 @@ from module.module import Module logging.debug("- %s loaded", __name__) -class BoswatchModule(Module): +class BoswatchModule(ModuleBase): """!Filter of specific bwPacket mode""" def __init__(self, config): """!Do not change anything here!""" @@ -38,8 +38,7 @@ class BoswatchModule(Module): def doWork(self, bwPacket): """!start an run of the module. - @param bwPacket: A BOSWatch packet instance - @return bwPacket or False""" + @param bwPacket: A BOSWatch packet instance""" for mode in self.config.get("allowed", default=[]): if bwPacket.get("mode") == mode: diff --git a/module/filter/regexFilter.py b/module/filter/regexFilter.py new file mode 100644 index 0000000..887a420 --- /dev/null +++ b/module/filter/regexFilter.py @@ -0,0 +1,65 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""! + ____ ____ ______ __ __ __ _____ + / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ / + / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ < + / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ / +/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/ + German BOS Information Script + by Bastian Schroll + +@file: regexFilter.py +@date: 26.10.2019 +@author: Bastian Schroll +@description: Regex filter module +""" +import logging +from module.moduleBase import ModuleBase + +# ###################### # +# Custom plugin includes # +import re +# ###################### # + +logging.debug("- %s loaded", __name__) + + +class BoswatchModule(ModuleBase): + """!Regex based filter mechanism""" + def __init__(self, config): + """!Do not change anything here!""" + super().__init__(__name__, config) # you can access the config class on 'self.config' + + def onLoad(self): + """!Called by import of the plugin""" + pass + + def doWork(self, bwPacket): + """!start an run of the module. + + @param bwPacket: A BOSWatch packet instance""" + for regexFilter in self.config: + checkFailed = False + logging.debug("try filter '%s' with %d check(s)", regexFilter.get("name"), len(regexFilter.get("checks"))) + + for check in regexFilter.get("checks"): + fieldData = bwPacket.get(check.get("field")) + + if not fieldData or not re.search(check.get("regex"), fieldData): + logging.debug("[-] field '%s' with regex '%s'", check.get("field"), check.get("regex")) + checkFailed = True + break # if one check failed we break this filter + else: + logging.debug("[+] field '%s' with regex '%s'", check.get("field"), check.get("regex")) + + if not checkFailed: + logging.debug("[PASSED] filter '%s'", regexFilter.get("name")) + return None # None -> Router will go on with this packet + logging.debug("[FAILED] filter '%s'", regexFilter.get("name")) + + return False # False -> Router will stop further processing + + def onUnload(self): + """!Called by destruction of the plugin""" + pass diff --git a/module/module.py b/module/moduleBase.py similarity index 65% rename from module/module.py rename to module/moduleBase.py index 814ddaf..db3455f 100644 --- a/module/module.py +++ b/module/moduleBase.py @@ -9,32 +9,34 @@ German BOS Information Script by Bastian Schroll -@file: module.py +@file: moduleBase.py @date: 01.03.2019 @author: Bastian Schroll @description: Module main class to inherit """ import logging import time +from abc import ABC + +from boswatch import wildcard logging.debug("- %s loaded", __name__) -class Module: +class ModuleBase(ABC): """!Main module class""" - _modulesActive = 0 + _modulesActive = [] def __init__(self, moduleName, config): """!init preload some needed locals and then call onLoad() directly""" self._moduleName = moduleName self.config = config - self._modulesActive += 1 + self._modulesActive.append(self) # for time counting self._cumTime = 0 self._moduleTime = 0 - self._tmpTime = 0 # for statistics self._runCount = 0 @@ -43,28 +45,28 @@ class Module: logging.debug("[%s] onLoad()", moduleName) self.onLoad() - def __del__(self): - """!Destructor calls onUnload() directly""" + def _cleanup(self): + """!Cleanup routine calls onUnload() directly""" logging.debug("[%s] onUnload()", self._moduleName) - self._modulesActive -= 1 + self._modulesActive.remove(self) self.onUnload() def _run(self, bwPacket): - """!start an rund of the module. + """!start an run of the module. @param bwPacket: A BOSWatch packet instance @return bwPacket or False""" self._runCount += 1 logging.debug("[%s] run #%d", self._moduleName, self._runCount) - self._tmpTime = time.time() + 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._moduleTime = time.time() - tmpTime self._cumTime += self._moduleTime @@ -76,7 +78,8 @@ class Module: """!Returns statistical information's from last module run @return Statistics as pyton dict""" - stats = {"runCount": self._runCount, + stats = {"type": "module", + "runCount": self._runCount, "cumTime": self._cumTime, "moduleTime": self._moduleTime, "moduleErrorCount": self._moduleErrorCount} @@ -84,17 +87,31 @@ class Module: def onLoad(self): """!Called by import of the module - Must be inherit""" + can be inherited""" pass def doWork(self, bwPacket): """!Called module run - Must be inherit + can be inherited @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""" + """!Called on shutdown of boswatch + can be inherited""" pass + + @staticmethod + def registerWildcard(newWildcard, bwPacketField): + """!Register a new wildcard + + @param newWildcard: wildcard where parser searching for + @param bwPacketField: field from bwPacket where holds replacement data""" + if not newWildcard.startswith("{") or not newWildcard.endswith("}"): + logging.error("wildcard not registered - false format: %s", newWildcard) + return + if bwPacketField == "": + logging.error("wildcard not registered - bwPacket field is empty") + return + wildcard.registerWildcard(newWildcard, bwPacketField) diff --git a/module/template_module.py b/module/template_module.py index fe6a557..2cb6eb1 100644 --- a/module/template_module.py +++ b/module/template_module.py @@ -15,7 +15,7 @@ @description: Template Module File """ import logging -from module.module import Module +from module.moduleBase import ModuleBase # ###################### # # Custom plugin includes # @@ -25,21 +25,21 @@ from module.module import Module logging.debug("- %s loaded", __name__) -class BoswatchModule(Module): +class BoswatchModul(ModuleBase): """!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""" + """!Called by import of the plugin + Remove if not implemented""" pass def doWork(self, bwPacket): """!start an run of the module. - @param bwPacket: A BOSWatch packet instance - @return bwPacket or False""" + @param bwPacket: A BOSWatch packet instance""" if bwPacket.get("mode") == "fms": pass elif bwPacket.get("mode") == "zvei": @@ -52,5 +52,6 @@ class BoswatchModule(Module): return bwPacket def onUnload(self): - """!Called by destruction of the plugin""" + """!Called by destruction of the plugin + Remove if not implemented""" pass diff --git a/plugin/plugin.py b/plugin/pluginBase.py similarity index 86% rename from plugin/plugin.py rename to plugin/pluginBase.py index f0a6de6..5785d69 100644 --- a/plugin/plugin.py +++ b/plugin/pluginBase.py @@ -9,29 +9,30 @@ German BOS Information Script by Bastian Schroll -@file: plugin.py +@file: pluginBase.py @date: 08.01.2018 @author: Bastian Schroll @description: Plugin main class to inherit """ import logging import time +from abc import ABC from boswatch import wildcard logging.debug("- %s loaded", __name__) -class Plugin: +class PluginBase(ABC): """!Main plugin class""" - _pluginsActive = 0 + _pluginsActive = [] def __init__(self, pluginName, config): """!init preload some needed locals and then call onLoad() directly""" self._pluginName = pluginName self.config = config - self._pluginsActive += 1 + self._pluginsActive.append(self) # to save the packet while alarm is running for other functions self._bwPacket = None @@ -42,7 +43,6 @@ class Plugin: self._setupTime = 0 self._alarmTime = 0 self._teardownTime = 0 - self._tmpTime = 0 # for statistics self._runCount = 0 @@ -53,10 +53,10 @@ class Plugin: logging.debug("[%s] onLoad()", pluginName) self.onLoad() - def __del__(self): - """!Destructor calls onUnload() directly""" + def _cleanup(self): + """!Cleanup routine calls onUnload() directly""" logging.debug("[%s] onUnload()", self._pluginName) - self._pluginsActive -= 1 + self._pluginsActive.remove(self) self.onUnload() def _run(self, bwPacket): @@ -70,7 +70,7 @@ class Plugin: self._bwPacket = bwPacket - self._tmpTime = time.time() + tmpTime = time.time() try: logging.debug("[%s] setup()", self._pluginName) self.setup() @@ -78,8 +78,8 @@ class Plugin: self._setupErrorCount += 1 logging.exception("[%s] error in setup()", self._pluginName) - self._setupTime = time.time() - self._tmpTime - self._tmpTime = time.time() + self._setupTime = time.time() - tmpTime + tmpTime = time.time() try: if bwPacket.get("mode") == "fms": @@ -98,8 +98,8 @@ class Plugin: self._alarmErrorCount += 1 logging.exception("[%s] alarm error", self._pluginName) - self._alarmTime = time.time() - self._tmpTime - self._tmpTime = time.time() + self._alarmTime = time.time() - tmpTime + tmpTime = time.time() try: logging.debug("[%s] teardown()", self._pluginName) self.teardown() @@ -107,7 +107,7 @@ class Plugin: self._teardownErrorCount += 1 logging.exception("[%s] error in teardown()", self._pluginName) - self._teardownTime = time.time() - self._tmpTime + self._teardownTime = time.time() - tmpTime self._sumTime = self._setupTime + self._alarmTime + self._teardownTime self._cumTime += self._sumTime @@ -124,7 +124,8 @@ class Plugin: """!Returns statistical information's from last plugin run @return Statistics as pyton dict""" - stats = {"runCount": self._runCount, + stats = {"type": "plugin", + "runCount": self._runCount, "sumTime": self._sumTime, "cumTime": self._cumTime, "setupTime": self._setupTime, @@ -137,50 +138,50 @@ class Plugin: def onLoad(self): """!Called by import of the plugin - Must be inherit""" + can be inherited""" pass def setup(self): """!Called before alarm - Must be inherit""" + can be inherited""" pass def fms(self, bwPacket): """!Called on FMS alarm - Must be inherit + can be inherited @param bwPacket: bwPacket instance""" logging.warning("ZVEI not implemented in %s", self._pluginName) def pocsag(self, bwPacket): """!Called on POCSAG alarm - Must be inherit + can be inherited @param bwPacket: bwPacket instance""" logging.warning("POCSAG not implemented in %s", self._pluginName) def zvei(self, bwPacket): """!Called on ZVEI alarm - Must be inherit + can be inherited @param bwPacket: bwPacket instance""" logging.warning("ZVEI not implemented in %s", self._pluginName) def msg(self, bwPacket): """!Called on MSG packet - Must be inherit + can be inherited @param bwPacket: bwPacket instance""" logging.warning("MSG not implemented in %s", self._pluginName) def teardown(self): """!Called after alarm - Must be inherit""" + can be inherited""" pass def onUnload(self): - """!Called by destruction of the plugin - Must be inherit""" + """!Called on shutdown of boswatch + can be inherited""" pass def parseWildcards(self, msg): diff --git a/plugin/template_plugin.py b/plugin/template_plugin.py index 87b7ff3..b3a7acc 100644 --- a/plugin/template_plugin.py +++ b/plugin/template_plugin.py @@ -15,7 +15,7 @@ @description: Template Plugin File """ import logging -from plugin.plugin import Plugin +from plugin.pluginBase import PluginBase # ###################### # # Custom plugin includes # @@ -25,46 +25,56 @@ from plugin.plugin import Plugin logging.debug("- %s loaded", __name__) -class BoswatchPlugin(Plugin): +class BoswatchPlugin(PluginBase): """!Description of the Plugin""" 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""" + """!Called by import of the plugin + Remove if not implemented""" pass def setup(self): - """!Called before alarm""" + """!Called before alarm + Remove if not implemented""" pass def fms(self, bwPacket): """!Called on FMS alarm - @param bwPacket: bwPacket instance""" + @param bwPacket: bwPacket instance + Remove if not implemented""" pass def pocsag(self, bwPacket): """!Called on POCSAG alarm - @param bwPacket: bwPacket instance""" + @param bwPacket: bwPacket instance + Remove if not implemented""" pass def zvei(self, bwPacket): """!Called on ZVEI alarm - @param bwPacket: bwPacket instance""" + @param bwPacket: bwPacket instance + Remove if not implemented""" + pass def msg(self, bwPacket): """!Called on MSG packet - @param bwPacket: bwPacket instance""" + @param bwPacket: bwPacket instance + Remove if not implemented""" + pass def teardown(self): - """!Called after alarm""" + """!Called after alarm + Remove if not implemented""" pass def onUnload(self): - """!Called by destruction of the plugin""" + """!Called by destruction of the plugin + Remove if not implemented""" pass diff --git a/requirements.txt b/requirements.txt index 795804f..b401509 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,5 @@ mkdocs pytest pytest-cov pytest-pep8 +pytest-flakes pytest-randomly diff --git a/some_old_stuff.txt b/some_old_stuff.txt deleted file mode 100644 index 8858604..0000000 --- a/some_old_stuff.txt +++ /dev/null @@ -1,3 +0,0 @@ -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 diff --git a/test/boswatch/test_ServerClient.py b/test/boswatch/test_ServerClient.py index 358510e..52b7d49 100644 --- a/test/boswatch/test_ServerClient.py +++ b/test/boswatch/test_ServerClient.py @@ -23,10 +23,11 @@ import pytest from boswatch.network.server import TCPServer from boswatch.network.client import TCPClient +import threading -def setup_method(method): - logging.debug("[TEST] %s.%s", method.__module__, method.__name__) +def setup_function(function): + logging.debug("[TEST] %s.%s", function.__module__, function.__name__) @pytest.fixture @@ -137,7 +138,7 @@ def test_clientMultiCommunicate(getServer): assert testClient2.receive() == "[ack]" assert testClient1.receive() == "[ack]" # check server msg queue - assert dataQueue.qsize() == 3 + assert getRunningServer._alarmQueue.qsize() == 3 # disconnect all assert testClient1.disconnect() assert testClient2.disconnect() @@ -181,11 +182,11 @@ def test_serverStopsWhileConnected(getRunningServer, getClient): """!Shutdown server while client is connected""" getClient.connect() getRunningServer.stop() - timeout = 10 + timeout = 5 while getClient.isConnected: time.sleep(0.1) timeout = timeout - 1 - if timeout is 0: + if timeout == 0: break assert timeout @@ -206,10 +207,41 @@ def test_serverGetOutput(getRunningServer): 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 + assert getRunningServer._alarmQueue.qsize() == 2 + assert getRunningServer._alarmQueue.get(True, 1)[1] == "test1" + assert getRunningServer._alarmQueue.get(True, 1)[1] == "test2" + assert getRunningServer._alarmQueue.qsize() == 0 # Last _check must be None # disconnect all assert testClient1.disconnect() assert testClient2.disconnect() + + +def test_serverHighLoad(getRunningServer): + """!High load server test with 10 send threads each will send 100 msg with 324 bytes size""" + logging.debug("start sendThreads") + threads = [] + for thr_id in range(10): + thr = threading.Thread(target=sendThread, name="sendThread-" + str(thr_id)) + thr.daemon = True + thr.start() + threads.append(thr) + for thread in threads: + thread.join() + logging.debug("finished sendThreads") + assert getRunningServer._alarmQueue.qsize() == 1000 + + +def sendThread(): + client = TCPClient() + client.connect() + time.sleep(0.1) + for i in range(100): + # actually this string is 324 bytes long + client.transmit("HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-" + "HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-" + "HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-HigLoadTestString-") + if not client.receive() == "[ack]": + logging.error("missing [ACK]") + + time.sleep(0.1) + client.disconnect() diff --git a/test/boswatch/test_broadcast.py b/test/boswatch/test_broadcast.py index 9bda27d..32aa0ac 100644 --- a/test/boswatch/test_broadcast.py +++ b/test/boswatch/test_broadcast.py @@ -23,8 +23,8 @@ 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__) +def setup_function(function): + logging.debug("[TEST] %s.%s", function.__module__, function.__name__) @pytest.fixture() diff --git a/test/boswatch/test_config.py b/test/boswatch/test_config.py index 80fb346..ab8b9d5 100644 --- a/test/boswatch/test_config.py +++ b/test/boswatch/test_config.py @@ -23,8 +23,8 @@ from boswatch.utils import paths from boswatch.configYaml import ConfigYAML -def setup_method(method): - logging.debug("[TEST] %s.%s", method.__module__, method.__name__) +def setup_function(function): + logging.debug("[TEST] %s.%s", function.__module__, function.__name__) @pytest.fixture @@ -89,7 +89,7 @@ def test_configIterationList(getFilledConfig): for item in getFilledConfig.get("list"): assert type(item) is str counter += 1 - assert counter is 3 + assert counter == 3 def test_configIterationListWithNestedList(getFilledConfig): diff --git a/test/boswatch/test_decoder.py b/test/boswatch/test_decoder.py index 71bed94..bb610b1 100644 --- a/test/boswatch/test_decoder.py +++ b/test/boswatch/test_decoder.py @@ -21,8 +21,8 @@ import logging from boswatch.decoder.decoder import Decoder -def setup_method(method): - logging.debug("[TEST] %s.%s", method.__module__, method.__name__) +def setup_function(function): + logging.debug("[TEST] %s.%s", function.__module__, function.__name__) def test_decoderNoData(): diff --git a/test/boswatch/test_header.py b/test/boswatch/test_header.py index b5908a3..4aa0a69 100644 --- a/test/boswatch/test_header.py +++ b/test/boswatch/test_header.py @@ -21,8 +21,8 @@ import logging from boswatch.utils import header -def setup_method(method): - logging.debug("[TEST] %s.%s", method.__module__, method.__name__) +def setup_function(function): + logging.debug("[TEST] %s.%s", function.__module__, function.__name__) def test_logoToLog(): diff --git a/test/boswatch/test_packet.py b/test/boswatch/test_packet.py index ee638fd..ee3d170 100644 --- a/test/boswatch/test_packet.py +++ b/test/boswatch/test_packet.py @@ -22,8 +22,8 @@ import pytest from boswatch.packet import Packet -def setup_method(method): - logging.debug("[TEST] %s.%s", method.__module__, method.__name__) +def setup_function(function): + logging.debug("[TEST] %s.%s", function.__module__, function.__name__) @pytest.fixture() @@ -34,19 +34,19 @@ def buildPacket(): def test_createPacket(buildPacket): """!Create a packet""" - assert buildPacket is not "" + assert buildPacket != "" def test_copyPacket(buildPacket): """!Copy a packet to an new instance""" bwCopyPacket = Packet(buildPacket.__str__()) - assert bwCopyPacket is not "" + assert bwCopyPacket != "" def test_getPacketString(buildPacket): """!get the intern packet dict as string""" assert type(buildPacket.__str__()) is str - assert buildPacket.__str__() is not "" + assert buildPacket.__str__() != "" def test_getNotSetField(buildPacket): @@ -57,4 +57,4 @@ def test_getNotSetField(buildPacket): def test_setGetField(buildPacket): """!set and get a field""" buildPacket.set("testField", "test") - assert buildPacket.get("testField") is "test" + assert buildPacket.get("testField") == "test" diff --git a/test/boswatch/test_paths.py b/test/boswatch/test_paths.py index 8b72b13..e2a4e31 100644 --- a/test/boswatch/test_paths.py +++ b/test/boswatch/test_paths.py @@ -22,8 +22,8 @@ import os from boswatch.utils import paths -def setup_method(method): - logging.debug("[TEST] %s.%s", method.__module__, method.__name__) +def setup_function(function): + logging.debug("[TEST] %s.%s", function.__module__, function.__name__) def test_fileExists(): diff --git a/test/boswatch/test_timer.py b/test/boswatch/test_timer.py index 64157d2..a5ef2c9 100644 --- a/test/boswatch/test_timer.py +++ b/test/boswatch/test_timer.py @@ -23,8 +23,8 @@ import pytest from boswatch.timer import RepeatedTimer -def setup_method(method): - logging.debug("[TEST] %s.%s", method.__module__, method.__name__) +def setup_function(function): + logging.debug("[TEST] %s.%s", function.__module__, function.__name__) def testTargetFast(): diff --git a/test/pytest.ini b/test/pytest.ini index c75fe6c..6e8fc02 100644 --- a/test/pytest.ini +++ b/test/pytest.ini @@ -8,7 +8,7 @@ # by Bastian Schroll [pytest] -addopts = -v --pep8 --cov=boswatch/ --cov-report=term-missing --log-level=CRITICAL +addopts = -v --pep8 --flakes --cov=boswatch/ --cov-report=term-missing --log-level=CRITICAL # classic or progress console_output_style = progress diff --git a/test/testdata.list b/test/testdata.list index 6b742c0..12b71c0 100644 --- a/test/testdata.list +++ b/test/testdata.list @@ -90,16 +90,16 @@ POCSAG1200: Address: 9000000 Function: 1 Alpha: BOSWatch-Test: out of filter POCSAG1200: Address: 0871004 Function: 1 Alpha: Dies ist ein Probealarm! ## Multicast Alarm POCSAG1200: Address: 0871002 Function: 0 Alpha: -POCSAG1200: Address: 0860001 Function: 0 -POCSAG1200: Address: 0860002 Function: 0 -POCSAG1200: Address: 0860003 Function: 0 -POCSAG1200: Address: 0860004 Function: 0 -POCSAG1200: Address: 0860005 Function: 0 -POCSAG1200: Address: 0860006 Function: 0 -POCSAG1200: Address: 0860007 Function: 0 -POCSAG1200: Address: 0860008 Function: 0 -POCSAG1200: Address: 0860009 Function: 0 -POCSAG1200: Address: 0860010 Function: 0 +POCSAG1200: Address: 0860001 Function: 0 +POCSAG1200: Address: 0860002 Function: 0 +POCSAG1200: Address: 0860003 Function: 0 +POCSAG1200: Address: 0860004 Function: 0 +POCSAG1200: Address: 0860005 Function: 0 +POCSAG1200: Address: 0860006 Function: 0 +POCSAG1200: Address: 0860007 Function: 0 +POCSAG1200: Address: 0860008 Function: 0 +POCSAG1200: Address: 0860009 Function: 0 +POCSAG1200: Address: 0860010 Function: 0 POCSAG1200: Address: 0871003 Function: 0 Alpha: B2 Feuer Gebäude Pers in Gefahr. bla bla bla # regEx-Filter?