diff --git a/boswatch.py b/boswatch.py index c6e8b2e..c8aa067 100755 --- a/boswatch.py +++ b/boswatch.py @@ -3,9 +3,9 @@ # """ BOSWatch -Python Script to receive and decode German BOS Information with rtl_fm and multimon-NG +Python script to receive and decode German BOS information with rtl_fm and multimon-NG Through a simple plugin system, data can easily be transferred to other applications -For more Information see the README.md +For more information see the README.md @author: Bastian Schroll @author: Jens Herrmann @@ -16,11 +16,11 @@ GitHUB: https://github.com/Schrolli91/BOSWatch import logging import logging.handlers -import argparse #for parse the args -import ConfigParser #for parse the config file -import os #for log mkdir -import time #timestamp -import subprocess +import argparse # for parse the args +import ConfigParser # for parse the config file +import os # for log mkdir +import time # for timestamp +import subprocess # for starting rtl_fm and multimon-ng from includes import globals # Global variables @@ -61,6 +61,7 @@ def freqToHz(freq): # # ArgParser +# Have to be before main program # try: # With -h or --help you get the Args help @@ -84,7 +85,7 @@ except: # -# Main Programm +# Main program # try: # initialization @@ -95,7 +96,7 @@ try: globals.script_path = os.path.dirname(os.path.abspath(__file__)) # - # If necessary create Log-Path + # If necessary create log-path # if not os.path.exists(globals.script_path+"/log/"): os.mkdir(globals.script_path+"/log/") @@ -105,19 +106,19 @@ try: # myLogger = logging.getLogger() myLogger.setLevel(logging.DEBUG) - #set log string format + # set log string format formatter = logging.Formatter('%(asctime)s - %(module)-15s [%(levelname)-8s] %(message)s', '%d.%m.%Y %H:%M:%S') - #create a file logger + # create a file logger fh = MyTimedRotatingFileHandler(globals.script_path+"/log/boswatch.log", "midnight", interval=1, backupCount=999) - #Starts with log level >= Debug - #will be changed with config.ini-param later + # Starts with log level >= Debug + # will be changed with config.ini-param later fh.setLevel(logging.DEBUG) fh.setFormatter(formatter) myLogger.addHandler(fh) - #create a display logger + # create a display logger ch = logging.StreamHandler() - #log level for display >= info - #will be changed later after parsing args + # log level for display >= info + # will be changed later after parsing args ch.setLevel(logging.INFO) ch.setFormatter(formatter) myLogger.addHandler(ch) @@ -189,8 +190,20 @@ try: logging.debug("reading config file") globals.config = ConfigParser.ConfigParser() globals.config.read(globals.script_path+"/config/config.ini") - for key,val in globals.config.items("BOSWatch"): - logging.debug(" - %s = %s", key, val) + # if given loglevel is debug: + if globals.config.getint("BOSWatch","loglevel") == 10: + logging.debug(" - BOSWatch:") + for key,val in globals.config.items("BOSWatch"): + logging.debug(" -- %s = %s", key, val) + logging.debug(" - FMS:") + for key,val in globals.config.items("FMS"): + logging.debug(" -- %s = %s", key, val) + logging.debug(" - ZVEI:") + for key,val in globals.config.items("ZVEI"): + logging.debug(" -- %s = %s", key, val) + logging.debug(" - POC:") + for key,val in globals.config.items("POC"): + logging.debug(" -- %s = %s", key, val) except: logging.exception("cannot read config file") else: diff --git a/includes/alarmHandler.py b/includes/alarmHandler.py index c5d75ea..4a1b9c5 100644 --- a/includes/alarmHandler.py +++ b/includes/alarmHandler.py @@ -2,9 +2,10 @@ # -*- coding: cp1252 -*- """ -Handler for the Filter and Plugins at an Alarm +Handler for the filter and plugins at an alarm @author: Bastian Schroll +@author: Jens Herrmann @requires: none """ @@ -13,10 +14,13 @@ import logging # Global logger from includes import globals # Global variables - +## +# +# main function for central filtering and calling the plugins +# def processAlarm(typ,freq,data): """ - Function to process Filters and Plugins at Alarm + Function to process filters and plugins at Alarm @type typ: string (FMS|ZVEI|POC) @param typ: Typ of the dataset @@ -25,29 +29,26 @@ def processAlarm(typ,freq,data): @type data: map of data (structure see interface.txt) @param data: Contains the parameter - @requires: active Plugins in pluginList + @requires: active plugins in pluginList @return: nothing @exception: Exception if Alarm processing failed """ try: logging.debug("[ ALARM ]") - #Go to all Plugins in pluginList + # Go to all plugins in pluginList for pluginName, plugin in globals.pluginList.items(): - - #if enabled use RegEx-Filter + # if enabled use RegEx-filter if globals.config.getint("BOSWatch","useRegExFilter"): from includes import filter if filter.checkFilters(typ,data,pluginName,freq): logging.debug("call Plugin: %s", pluginName) plugin.run(typ,freq,data) logging.debug("return from: %s", pluginName) - - else: #RegEX Filter off - Call Plugin direct + else: # RegEX filter off - call plugin directly logging.debug("call Plugin: %s", pluginName) plugin.run(typ,freq,data) logging.debug("return from: %s", pluginName) - logging.debug("[END ALARM]") except: logging.exception("Error in Alarm processing") \ No newline at end of file diff --git a/includes/decoder.py b/includes/decoder.py index 0fcfd41..2161968 100644 --- a/includes/decoder.py +++ b/includes/decoder.py @@ -2,18 +2,18 @@ # -*- coding: cp1252 -*- """ -Search for decode String and call the right decoder Funtion +Search for decode string and call the right decoder function @author: Jens Herrmann @requires: none """ -import logging +import logging # Global logger def decode(freq, decoded): """ - Search for decode String and call the right decoder Function + Search for decode string and call the right decoder function @type freq: string @param freq: frequency of the SDR Stick @@ -24,23 +24,27 @@ def decode(freq, decoded): @exception: Exception if decoder File call failed """ try: - #FMS Decoder Section - #check FMS: -> check CRC -> validate -> check double alarm -> log + # FMS Decoder Section + # check FMS: -> check CRC -> validate -> check double alarm -> log if "FMS:" in decoded: logging.debug("recieved FMS") from includes.decoders import fms fms.decode(freq, decoded) - #ZVEI Decoder Section - #check ZVEI: -> validate -> check double alarm -> log - if "ZVEI2:" in decoded: + # ZVEI Decoder Section + # check ZVEI: -> validate -> check double alarm -> log + elif "ZVEI2:" in decoded: logging.debug("recieved ZVEI") from includes.decoders import zvei zvei.decode(freq, decoded) - #POCSAG Decoder Section - #check POCSAG -> validate -> check double alarm -> log - if "POCSAG" in decoded: + # For POCSAG we have to ignore the multimon-ng line "Enabled demodulators:" + elif "Enabled demodulators:" in decoded: + pass + + # POCSAG Decoder Section + # check POCSAG -> validate -> check double alarm -> log + elif "POCSAG" in decoded: logging.debug("recieved POCSAG") from includes.decoders import poc poc.decode(freq, decoded) diff --git a/includes/decoders/fms.py b/includes/decoders/fms.py index cc542b9..3ef24f2 100644 --- a/includes/decoders/fms.py +++ b/includes/decoders/fms.py @@ -9,15 +9,15 @@ FMS Decoder @requires: Configuration has to be set in the config.ini """ -import logging -import time #timestamp for doublealarm -import re #Regex for validation +import logging # Global logger +import time # timestamp for doublealarm +import re # Regex for validation from includes import globals # Global variables ## # -# FMS Decoder Function +# FMS decoder function # validate -> check double alarm -> log # def decode(freq, decoded): @@ -34,23 +34,26 @@ def decode(freq, decoded): @return: nothing @exception: Exception if FMS decode failed """ - timestamp = int(time.time())#Get Timestamp + timestamp = int(time.time()) # Get Timestamp - fms_service = decoded[19] #Organisation - fms_country = decoded[36] #Bundesland - fms_location = decoded[65:67] #Ort - fms_vehicle = decoded[72:76] #Fahrzeug - fms_status = decoded[84] #Status - fms_direction = decoded[101] #Richtung - fms_directionText = decoded[103:110] #Richtung (Text) - fms_tsi = decoded[114:117] #Taktische Kruzinformation + fms_service = decoded[19] # Organisation + fms_country = decoded[36] # Bundesland + fms_location = decoded[65:67] # Ort + fms_vehicle = decoded[72:76] # Fahrzeug + fms_status = decoded[84] # Status + fms_direction = decoded[101] # Richtung + fms_directionText = decoded[103:110] # Richtung (Text) + fms_tsi = decoded[114:117] # Taktische Kruzinformation if "CRC correct" in decoded: #check CRC is correct - fms_id = fms_service+fms_country+fms_location+fms_vehicle+fms_status+fms_direction #build FMS id - if re.search("[0-9a-f]{8}[0-9a-f]{1}[01]{1}", fms_id): #if FMS is valid - if fms_id == globals.fms_id_old and timestamp < globals.fms_time_old + globals.config.getint("FMS", "double_ignore_time"): #check for double alarm + fms_id = fms_service+fms_country+fms_location+fms_vehicle+fms_status+fms_direction # build FMS id + # if FMS is valid + if re.search("[0-9a-f]{8}[0-9a-f]{1}[01]{1}", fms_id): + # check for double alarm + if fms_id == globals.fms_id_old and timestamp < globals.fms_time_old + globals.config.getint("FMS", "double_ignore_time"): logging.info("FMS double alarm: %s within %s second(s)", globals.fms_id_old, timestamp-globals.fms_time_old) - globals.fms_time_old = timestamp #in case of double alarm, fms_double_ignore_time set new + # in case of double alarm, fms_double_ignore_time set new + globals.fms_time_old = timestamp else: logging.info("FMS:%s Status:%s Richtung:%s TSI:%s", fms_id[0:8], fms_status, fms_direction, fms_tsi) data = {"fms":fms_id[0:8], "status":fms_status, "direction":fms_direction, "directionText":fms_directionText, "tsi":fms_tsi, "description":fms_id[0:8]} diff --git a/includes/decoders/poc.py b/includes/decoders/poc.py index f40bc40..4f8abc0 100644 --- a/includes/decoders/poc.py +++ b/includes/decoders/poc.py @@ -10,15 +10,15 @@ POCSAG Decoder @requires: Configuration has to be set in the config.ini """ -import logging -import time #timestamp for doublealarm -import re #Regex for validation +import logging # Global logger +import time # timestamp for doublealarm +import re # Regex for validation from includes import globals # Global variables ## # -# Simple Filter +# Simple local filter # def isAllowed(poc_id): """ @@ -36,27 +36,27 @@ def isAllowed(poc_id): # If RIC is the right one return True, else False if globals.config.get("POC", "allow_ric"): if poc_id in globals.config.get("POC", "allow_ric"): - logging.debug("RIC %s is allowed", poc_id) + logging.info("RIC %s is allowed", poc_id) return True else: - logging.debug("RIC %s is not in the allowed list", poc_id) + logging.info("RIC %s is not in the allowed list", poc_id) return False # 2.) If denied RIC, return False elif poc_id in globals.config.get("POC", "deny_ric"): - logging.debug("RIC %s is denied by config.ini", poc_id) + logging.info("RIC %s is denied by config.ini", poc_id) return False # 3.) Check Range, return False if outside def. range elif int(poc_id) < globals.config.getint("POC", "filter_range_start"): - logging.debug("RIC %s out of filter range (start)", poc_id) + logging.info("RIC %s out of filter range (start)", poc_id) return False elif int(poc_id) > globals.config.getint("POC", "filter_range_end"): - logging.debug("RIC %s out of filter range (end)", poc_id) + logging.info("RIC %s out of filter range (end)", poc_id) return False return True ## # -# POCSAG Decoder Function +# POCSAG decoder function # validate -> check double alarm -> log # def decode(freq, decoded): @@ -93,6 +93,7 @@ def decode(freq, decoded): if bitrate is 0: logging.warning("POCSAG Bitrate not found") + logging.debug(" - (%s)", decoded) else: logging.debug("POCSAG Bitrate: %s", bitrate) @@ -103,10 +104,10 @@ def decode(freq, decoded): if re.search("[0-9]{7}", poc_id): #if POC is valid if isAllowed(poc_id): - #check for double alarm + # check for double alarm if poc_id == globals.poc_id_old and timestamp < globals.poc_time_old + globals.config.getint("POC", "double_ignore_time"): logging.info("POCSAG%s double alarm: %s within %s second(s)", bitrate, globals.poc_id_old, timestamp-globals.poc_time_old) - #in case of double alarm, poc_double_ignore_time set new + # in case of double alarm, poc_double_ignore_time set new globals.poc_time_old = timestamp else: logging.info("POCSAG%s: %s %s %s ", bitrate, poc_id, poc_sub, poc_text) @@ -124,6 +125,6 @@ def decode(freq, decoded): globals.poc_id_old = poc_id #save last id globals.poc_time_old = timestamp #save last time else: - logging.info("POCSAG%s: %s is not allowed", bitrate, poc_id) + logging.debug("POCSAG%s: %s is not allowed", bitrate, poc_id) else: logging.warning("No valid POCSAG%s RIC: %s", bitrate, poc_id) \ No newline at end of file diff --git a/includes/decoders/zvei.py b/includes/decoders/zvei.py index 8d24ac0..789d5da 100644 --- a/includes/decoders/zvei.py +++ b/includes/decoders/zvei.py @@ -9,56 +9,16 @@ ZVEI Decoder @requires: Configuration has to be set in the config.ini """ -import logging -import time #timestamp for doublealarm -import re #Regex for validation +import logging # Global logger +import time # timestamp for doublealarm +import re # Regex for validation from includes import globals # Global variables ## # -# ZVEI Decoder Function -# validate -> check double alarm -> log +# Local function to remove the 'F' # -def decode(freq, decoded): - """ - Export ZVEI Information from Multimon-NG RAW String and call alarmHandler.processAlarm() - - @type freq: string - @param freq: frequency of the SDR Stick - @type decoded: string - @param decoded: RAW Information from Multimon-NG - - @requires: Configuration has to be set in the config.ini - - @return: nothing - @exception: Exception if ZVEI decode failed - """ - timestamp = int(time.time())#Get Timestamp - - zvei_id = decoded[7:12] #ZVEI Code - zvei_id = removeF(zvei_id) #resolve F - if re.search("[0-9]{5}", zvei_id): #if ZVEI is valid - if zvei_id == globals.zvei_id_old and timestamp < globals.zvei_time_old + globals.config.getint("ZVEI", "double_ignore_time"): #check for double alarm - logging.info("ZVEI double alarm: %s within %s second(s)", globals.zvei_id_old, timestamp-globals.zvei_time_old) - globals.zvei_time_old = timestamp #in case of double alarm, zvei_double_ignore_time set new - else: - logging.info("5-Ton: %s", zvei_id) - data = {"zvei":zvei_id, "description":zvei_id} - # If enabled, look up description - if globals.config.getint("ZVEI", "idDescribed"): - from includes import descriptionList - data["description"] = descriptionList.getDescription("ZVEI", zvei_id) - # processing the alarm - from includes import alarmHandler - alarmHandler.processAlarm("ZVEI",freq,data) - - globals.zvei_id_old = zvei_id #save last id - globals.zvei_time_old = timestamp #save last time - else: - logging.warning("No valid ZVEI: %s", zvei_id) - - def removeF(zvei): """ Resolve the F from the repeat Tone @@ -75,4 +35,49 @@ def removeF(zvei): if zvei[i] == "F": zvei = zvei.replace("F",zvei[i-1],1) logging.debug("resolve F: %s -> %s", zvei_old, zvei) - return zvei \ No newline at end of file + return zvei + +## +# +# ZVEI decoder function +# validate -> check double alarm -> log +# +def decode(freq, decoded): + """ + Export ZVEI Information from Multimon-NG RAW String and call alarmHandler.processAlarm() + + @type freq: string + @param freq: frequency of the SDR Stick + @type decoded: string + @param decoded: RAW Information from Multimon-NG + + @requires: Configuration has to be set in the config.ini + + @return: nothing + @exception: Exception if ZVEI decode failed + """ + timestamp = int(time.time()) # Get Timestamp + + zvei_id = decoded[7:12] # ZVEI Code + zvei_id = removeF(zvei_id) # resolve F + if re.search("[0-9]{5}", zvei_id): # if ZVEI is valid + # check for double alarm + if zvei_id == globals.zvei_id_old and timestamp < globals.zvei_time_old + globals.config.getint("ZVEI", "double_ignore_time"): + logging.info("ZVEI double alarm: %s within %s second(s)", globals.zvei_id_old, timestamp-globals.zvei_time_old) + # in case of double alarm, zvei_double_ignore_time set new + globals.zvei_time_old = timestamp + else: + logging.info("5-Ton: %s", zvei_id) + data = {"zvei":zvei_id, "description":zvei_id} + # If enabled, look up description + if globals.config.getint("ZVEI", "idDescribed"): + from includes import descriptionList + data["description"] = descriptionList.getDescription("ZVEI", zvei_id) + # processing the alarm + from includes import alarmHandler + alarmHandler.processAlarm("ZVEI",freq,data) + + globals.zvei_id_old = zvei_id # save last id + globals.zvei_time_old = timestamp # save last time + else: + logging.warning("No valid ZVEI: %s", zvei_id) \ No newline at end of file diff --git a/includes/descriptionList.py b/includes/descriptionList.py index c65c091..6f3e0ea 100644 --- a/includes/descriptionList.py +++ b/includes/descriptionList.py @@ -11,7 +11,7 @@ Function to expand the dataset with a description. import logging # Global logger -import csv +import csv # for loading the description files from includes import globals # Global variables @@ -99,4 +99,6 @@ def getDescription(typ, id): # will be thrown when there is no description for the id # -> nothing to do... pass + except: + logging.debug("Error during look up description lists") return resultStr \ No newline at end of file diff --git a/includes/filter.py b/includes/filter.py index ca72a5f..4849d50 100644 --- a/includes/filter.py +++ b/includes/filter.py @@ -2,7 +2,7 @@ # -*- coding: cp1252 -*- """ -Functions for the RegEX Filter +Functions for the RegEX filter @author: Bastian Schroll @@ -18,20 +18,20 @@ from includes import globals # Global variables def loadFilters(): """ - load all Filters from the config.ini into globals.filterList + load all filters from the config.ini into globals.filterList @requires: Configuration has to be set in the config.ini @return: nothing - @exception: Exception if Filter loading failed + @exception: Exception if filter loading failed """ try: logging.debug("loading filters") - #For each entry in config.ini [Filters] Section + # For each entry in config.ini [Filters] section for key,val in globals.config.items("Filters"): logging.debug(" - %s = %s", key, val) filter = val.split(";") - #insert splitet Data into globals.filterList + # insert splitet data into globals.filterList globals.filterList.append({"name": key, "typ": filter[0], "dataField": filter[1], "plugin": filter[2], "freq": freqToHz(filter[3]), "regex": filter[4]}) except: logging.exception("cannot read config file") @@ -39,34 +39,34 @@ def loadFilters(): def checkFilters(typ,data,plugin,freq): """ - Check the Typ/Plugin combination with the RegEX Filter - If no Filter for the combination is found, Function returns True. + Check the Typ/Plugin combination with the RegEX filter + If no filter for the combination is found, function returns True. @type typ: string (FMS|ZVEI|POC) @param typ: Typ of the dataset @type data: map of data (structure see interface.txt) @param data: Contains the parameter @type plugin: string - @param plugin: Name of the Plugin to checked + @param plugin: Name of the plugin to checked @type freq: string @param freq: frequency of the SDR Stick - @requires: all Filters in the filterList + @requires: all filters in the filterList @return: nothing - @exception: Exception if Filter check failed + @exception: Exception if filter check failed """ try: logging.debug("search Filter for %s to %s at %s Hz", typ, plugin, freq) foundFilter = False - #go to all Filter in globals.filterList + # go to all filter in globals.filterList for i in globals.filterList: - #if Typ/Plugin/Freq combination is found + # if typ/plugin/freq combination is found if i["typ"] == typ and (i["plugin"] == plugin or i['plugin'] == "*") and (i["freq"] == freq or i['freq'] == "*"): foundFilter = True logging.debug("found Filter: %s = %s", i["name"], i["regex"]) - #Check the RegEX + # Check the RegEX if re.search(i["regex"], data[i["dataField"]]): logging.debug("Filter passed: %s", i["name"]) return True diff --git a/includes/globals.py b/includes/globals.py index 46aa5fc..f2d4e6d 100644 --- a/includes/globals.py +++ b/includes/globals.py @@ -8,11 +8,11 @@ Global variables @author: Bastian Schroll """ -#Global variables +# Global variables config = 0 script_path = "" -#double alarm +# double alarm fms_id_old = 0 fms_time_old = 0 @@ -22,13 +22,13 @@ zvei_time_old = 0 poc_id_old = 0 poc_time_old = 0 -#pluginLoader +# pluginLoader pluginList = {} -#filter +# filter filterList = [] -#idDescribing +# idDescribing fmsDescribtionList = {} zveiDescribtionList = {} ricDescribtionList = {} diff --git a/includes/pluginLoader.py b/includes/pluginLoader.py index 7e7134b..31bd097 100644 --- a/includes/pluginLoader.py +++ b/includes/pluginLoader.py @@ -13,6 +13,7 @@ import logging # Global logger import imp import os +from ConfigParser import NoOptionError # we need this exception from includes import globals # Global variables def loadPlugins(): @@ -24,11 +25,11 @@ def loadPlugins(): """ try: logging.debug("loading plugins") - #go to all Plugins from getPlugins() + # go to all Plugins from getPlugins() for i in getPlugins(): - #call for each Plugin the loadPlugin() Methode + # call for each Plugin the loadPlugin() Methode plugin = loadPlugin(i) - #Add it to globals.pluginList + # Add it to globals.pluginList globals.pluginList[i["name"]] = plugin except: logging.exception("cannot load Plugins") @@ -45,11 +46,11 @@ def getPlugins(): logging.debug("Search in Plugin Folder") PluginFolder = globals.script_path+"/plugins" plugins = [] - #Go to all Folders in the Plugin-Dir + # Go to all Folders in the Plugin-Dir for i in os.listdir(PluginFolder): location = os.path.join(PluginFolder, i) - #Skip if Path.isdir() or no File DIR_NAME.py is found + # Skip if Path.isdir() or no File DIR_NAME.py is found if not os.path.isdir(location) or not i + ".py" in os.listdir(location): continue @@ -61,8 +62,10 @@ def getPlugins(): logging.debug("Plugin [ENABLED ] %s", i) else: logging.debug("Plugin [DISABLED] %s ", i) - except: #no entry for plugin found in config-file + # no entry for plugin found in config-file + except NoOptionError: logging.warning("Plugin [NO CONF ] %s", i) + pass except: logging.exception("Error during Plugin search") diff --git a/includes/shellHeader.py b/includes/shellHeader.py index b1bcb52..8d352eb 100644 --- a/includes/shellHeader.py +++ b/includes/shellHeader.py @@ -2,7 +2,7 @@ # -*- coding: cp1252 -*- """ -Shows the Header in Shell if quiet Mode is not active +Shows the header in shell if quiet mode is not active @author: Bastian Schroll @author: Jens Herrmann @@ -12,13 +12,13 @@ Shows the Header in Shell if quiet Mode is not active def printHeader(args): """ - Prints the Header to the Shell + Prints the header to the shell @type args: Array - @param args: All given Arguments from argsparser + @param args: All given arguments from argsparser @return: nothing - @exception: Exception if display of the Shell Header failed + @exception: Exception if display of the shell header failed """ try: print " ____ ____ ______ __ __ __ "