diff --git a/README.md b/README.md index 22b50d9..eb2c636 100644 --- a/README.md +++ b/README.md @@ -12,54 +12,75 @@ The software was developed using the Multimon-NG code, a function in the real op unless you are developer you can use the develop-Branch - may be unstable! ### Features -##### Implemented Features: -- FMS, ZVEI and POCSAG512/1200/2400 decoding and Displaying -- Plugin support for easy Functions extension +##### Implemented features: +- FMS, ZVEI and POCSAG512/1200/2400 decoding and displaying +- Plugin support for easy functional extension - Filtering double alarms with adjustable time -- Filtering Range of POCSAG RIC´s -- Filtering Data for each Typ/Plugin combination -- All configurations in seperate config File +- Filtering allowed, denied and range of POCSAG RIC´s +- Filtering data for each typ/plugin combination with RegEX +- All configurations in a seperate config file - Data validation (plausibility test) -- Logfiles for better Troubleshooting -- verbose/quiet Mode for more/none information +- Logfiles for better troubleshooting +- verbose/quiet mode for more/none information -##### Features for the Future: -- more Plugins +##### Features for the future: +- more plugins ###Plugins -##### Implemented Plugins: -- MySQL (insert Data into MySQL Database [FMS|ZVEI|POC]) -- BosMon (send Data to BosMon Server [ZVEI|POC]) -- httpRequest (send a request to an URL [FMS|ZVEI|POC]) +##### Implemented plugins: +- MySQL (insert data into MySQL database [FMS|ZVEI|POC]) +- httpRequest (send a request with parameter to an URL [FMS|ZVEI|POC]) +- eMail (send Mails [FMS|ZVEI|POC]) +- BosMon (send data to BosMon server [FMS|ZVEI|POC]) +- firEmergency (send data to firEmergency server [ZVEI|POC]) + +- for more Information to the plugins see `config.ini` ##### Plugins for the Future: -- E-mail Notification -- Other Ideas per Issues +- Ideas per Issues please ### Configuration ##### boswatch.py -Take a look into the Folder /config/ +Take a look into the folder /config/ Rename `config.template.ini` to `config.ini` In the Section `[BOSWatch]` you can set double_alarm_time etc. In the Section `[Plugins]` you can activate or deactivate the Plugins -For each Plugin that requires configurations a own Section with his Name is available +For each plugin that requires configurations, a own Section with his name is available -For the other Functions see "Usage" below. +For the other functions see "Usage" below. -##### Web Frontend -Put the Files in Folder /wwww/ into your local Webserver Folder (/var/www/). -Now you must edit the "config.php" with your Userdata to your local Database. -For the Parsing Functions take a look into the parser.php +##### Filtering Functions (RegEX) +For the RegEX filter functions see Section `[Filters]` +http://www.regexr.com/ - RegEX test tool an documentation +No filter for a combination typ/plugin = all data will pass + +Syntax: INDIVIDUAL_NAME = TYP;DATAFIELD;PLUGIN;FREQUENZ;REGEX (separator ";") +- TYP = the data typ (FMS|ZVEI|POC) +- DATAFIELD = the field of the data array (See interface.txt) +- PLUGIN = the name of the plugin to call with this filter (* for all) +- FREQUENZ = the frequenz to use the filter (for more SDR sticks (* for all)) +- REGEX = the RegEX + +only ZVEI to all plugins with 25### at 85.5MHz +testfilter = ZVEI;zvei;*;85500000;25[0-9]{3} + +only POCSAG to MySQL with the text "ALARM:" in the message +pocTest = POC;msg;MySQL;*;ALARM: + +##### Web frontend +Put the files in folder /wwww/ into your local webserver folder (f.e. /var/www/). +Now you must edit the "config.php" with your userdata to your local database. +Take a look into the parser.php for the parsing functions ### Usage `sudo python boswatch.py -f 85.235M -a FMS ZVEI` -Starts boswatch at Frequency 85.235 MHz with the Demodulation Functions FMS and ZVEI. +Starts boswatch at frequency 85.235 MHz with the demodulation functions FMS and ZVEI. Parameter -f/--freq and -a/--demod are required! -Help to all usable Parameters with `sudo python boswatch.py -h` +Help to all usable parameters with `sudo python boswatch.py -h` ``` usage: boswatch.py [-h] -f FREQ [-d DEVICE] [-e ERROR] -a @@ -70,28 +91,28 @@ optional arguments: -h, --help show this help message and exit -f FREQ, --freq FREQ Frequency you want to listen -d DEVICE, --device DEVICE Device you want to use (Check with rtl_test) - -e ERROR, --error ERROR Frequency-Error of your Device in PPM + -e ERROR, --error ERROR Frequency-Error of your device in PPM -a {FMS,ZVEI,POC512,POC1200,POC2400} [{FMS,ZVEI,POC512,POC1200,POC2400} ...], --demod {FMS,ZVEI,POC512,POC1200,POC2400} [{FMS,ZVEI,POC512,POC1200,POC2400} ...] - Demodulation Functions - -s SQUELCH, --squelch SQUELCH Level of Squelch - -v, --verbose Shows more Information - -q, --quiet Shows no Information. Only Logfiles + Demodulation functions + -s SQUELCH, --squelch SQUELCH level of squelch + -v, --verbose Shows more information + -q, --quiet Shows no information. Only logfiles ``` ### Installation You can easy install BOSWatch with the install.sh Script. -- Download the install.sh in any Folder you want. +- Download the install.sh in any folder you want. - Make it executeable `sudo chmod +x install.sh` - And use the script `sudo sh install.sh` Now the script downloads and compile all needed data. At the end you can find BOSWatch in `~/boswatch/` Rename `config.template.ini` to `config.ini` and configure -In case of an Error during the Installation, check the Logfile in `~/boswatch/install/setup_log.txt` +In case of an error during the installation, check the logfile in `~/boswatch/install/setup_log.txt` -Caution, script don't install a Webserver with PHP and MySQL. +Caution, script don't install a webserver with PHP and MySQL. So you have to make up manually if you want to use MySQL support. @@ -99,4 +120,4 @@ So you have to make up manually if you want to use MySQL support. - RTL_SDR (rtl_fm) - Multimon-NG - Python Support -- MySQL Connector for Python +- MySQL Connector for Python (for MySQL-plugin) diff --git a/boswatch.py b/boswatch.py index 3cf9867..c6e8b2e 100755 --- a/boswatch.py +++ b/boswatch.py @@ -1,14 +1,20 @@ #!/usr/bin/python # -*- coding: cp1252 -*- +# +""" +BOSWatch +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 -##### Info ##### -# BOSWatch -# Autor: Bastian Schroll -# Python Script to receive and decode German BOS Information with rtl_fm and multimon-NG -# For more Information see the README.md -##### Info ##### +@author: Bastian Schroll +@author: Jens Herrmann + +GitHUB: https://github.com/Schrolli91/BOSWatch +""" import logging +import logging.handlers import argparse #for parse the args import ConfigParser #for parse the config file @@ -18,43 +24,117 @@ import subprocess from includes import globals # Global variables -# Programm +## +# +# This Class extended the TimedRotatingFileHandler with the possibility to change the backupCount after initialization. +# +## +class MyTimedRotatingFileHandler(logging.handlers.TimedRotatingFileHandler): + """Extended Version of TimedRotatingFileHandler""" + def setBackupCount(self, backupCount): + """Set/Change backupCount""" + self.backupCount = backupCount + + +## +# +# convert frequency to Hz +# +def freqToHz(freq): + """ + gets a frequency and resolve it in Hz + + @type freq: string + @param freq: frequency of the SDR Stick + + @return: frequency in Hz + @exception: Exception if Error by recalc + """ + try: + freq = freq.replace("k","e3").replace("M","e6") + # freq has to be interpreted as float first... + # otherwise you will get the error: an invalid literal for int() with base 10 + return int(float(freq)) + except: + logging.exception("Error in freqToHz()") + + +# +# ArgParser +# +try: + # With -h or --help you get the Args help + parser = argparse.ArgumentParser(prog="boswatch.py", + description="BOSWatch is a Python Script to recive and decode german BOS information with rtl_fm and multimon-NG", + epilog="More options you can find in the extern config.ini file in the folder /config") + # parser.add_argument("-c", "--channel", help="BOS Channel you want to listen") + parser.add_argument("-f", "--freq", help="Frequency you want to listen", required=True) + parser.add_argument("-d", "--device", help="Device you want to use (Check with rtl_test)", type=int, default=0) + parser.add_argument("-e", "--error", help="Frequency-Error of your device in PPM", type=int, default=0) + parser.add_argument("-a", "--demod", help="Demodulation functions", choices=['FMS', 'ZVEI', 'POC512', 'POC1200', 'POC2400'], required=True, nargs="+") + parser.add_argument("-s", "--squelch", help="Level of squelch", type=int, default=0) + parser.add_argument("-v", "--verbose", help="Shows more information", action="store_true") + parser.add_argument("-q", "--quiet", help="Shows no information. Only logfiles", action="store_true") + args = parser.parse_args() +except SystemExit: + # -h or --help called, exit right now + exit(0) +except: + print "cannot parsing the arguments" + + +# +# Main Programm +# try: - try: - #create logger + # initialization + try: + # + # Script-pathes + # globals.script_path = os.path.dirname(os.path.abspath(__file__)) + # + # If necessary create Log-Path + # if not os.path.exists(globals.script_path+"/log/"): os.mkdir(globals.script_path+"/log/") - #create new logger - logger = logging.getLogger() - logger.setLevel(logging.DEBUG) + # + # Create new myLogger... + # + myLogger = logging.getLogger() + myLogger.setLevel(logging.DEBUG) #set log string format - formatter = logging.Formatter('%(asctime)s - %(module)-12s [%(levelname)-8s] %(message)s', '%d.%m.%Y %H:%M:%S') + formatter = logging.Formatter('%(asctime)s - %(module)-15s [%(levelname)-8s] %(message)s', '%d.%m.%Y %H:%M:%S') #create a file logger - fh = logging.FileHandler(globals.script_path+"/log/boswatch.log", "w") - fh.setLevel(logging.DEBUG) #log level >= Debug + 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 + fh.setLevel(logging.DEBUG) fh.setFormatter(formatter) - logger.addHandler(fh) + myLogger.addHandler(fh) #create a display logger ch = logging.StreamHandler() - ch.setLevel(logging.INFO) #log level >= info + #log level for display >= info + #will be changed later after parsing args + ch.setLevel(logging.INFO) ch.setFormatter(formatter) - logger.addHandler(ch) + myLogger.addHandler(ch) except: logging.exception("cannot create logger") else: + # initialization of the logging was fine, continue... - try: - #clear log - bos_log = open(globals.script_path+"/log/boswatch.log", "w") + try: + # + # Clear the logfiles + # + fh.doRollover() rtl_log = open(globals.script_path+"/log/rtl_fm.log", "w") mon_log = open(globals.script_path+"/log/multimon.log", "w") - bos_log.write("") rtl_log.write("") mon_log.write("") - bos_log.close() rtl_log.close() mon_log.close() logging.debug("BOSWatch has started") @@ -62,129 +142,146 @@ try: except: logging.exception("cannot clear Logfiles") - try: - #parse args - logging.debug("parse args") - #With -h or --help you get the Args help - #ArgsParser - parser = argparse.ArgumentParser(prog="boswatch.py", description="BOSWatch is a Python Script to Recive and Decode German BOS Information with rtl_fm and multimon-NG", epilog="More Options you can find in the extern config.ini File in this Folder") - #parser.add_argument("-c", "--channel", help="BOS Channel you want to listen") - parser.add_argument("-f", "--freq", help="Frequency you want to listen", required=True) - parser.add_argument("-d", "--device", help="Device you want to use (Check with rtl_test)", type=int, default=0) - parser.add_argument("-e", "--error", help="Frequency-Error of your Device in PPM", type=int, default=0) - parser.add_argument("-a", "--demod", help="Demodulation Functions", choices=['FMS', 'ZVEI', 'POC512', 'POC1200', 'POC2400'], required=True, nargs="+") - parser.add_argument("-s", "--squelch", help="Level of Squelch", type=int, default=0) - parser.add_argument("-v", "--verbose", help="Shows more Information", action="store_true") - parser.add_argument("-q", "--quiet", help="Shows no Information. Only Logfiles", action="store_true") - args = parser.parse_args() - except: - logging.error("cannot parse args") - else: + try: + # + # For debug display/log args + # + logging.debug(" - Frequency: %s", freqToHz(args.freq)) + logging.debug(" - Device: %s", args.device) + logging.debug(" - PPM Error: %s", args.error) + logging.debug(" - Squelch: %s", args.squelch) - try: - #display/log args - logging.debug(" - Frequency: %s", args.freq) - logging.debug(" - Device: %s", args.device) - logging.debug(" - PPM Error: %s", args.error) - logging.debug(" - Squelch: %s", args.squelch) - - demodulation = "" - if "FMS" in args.demod: - demodulation += "-a FMSFSK " - logging.debug(" - Demod: FMS") - if "ZVEI" in args.demod: - demodulation += "-a ZVEI2 " - logging.debug(" - Demod: ZVEI") - if "POC512" in args.demod: - demodulation += "-a POCSAG512 " - logging.debug(" - Demod: POC512") - if "POC1200" in args.demod: - demodulation += "-a POCSAG1200 " - logging.debug(" - Demod: P") - if "POC2400" in args.demod: - demodulation += "-a POCSAG2400 " - logging.debug(" - Demod: POC2400") - - logging.debug(" - Verbose Mode: %s", args.verbose) - logging.debug(" - Quiet Mode: %s", args.quiet) + demodulation = "" + if "FMS" in args.demod: + demodulation += "-a FMSFSK " + logging.debug(" - Demod: FMS") + if "ZVEI" in args.demod: + demodulation += "-a ZVEI2 " + logging.debug(" - Demod: ZVEI") + if "POC512" in args.demod: + demodulation += "-a POCSAG512 " + logging.debug(" - Demod: POC512") + if "POC1200" in args.demod: + demodulation += "-a POCSAG1200 " + logging.debug(" - Demod: POC1200") + if "POC2400" in args.demod: + demodulation += "-a POCSAG2400 " + logging.debug(" - Demod: POC2400") + + logging.debug(" - Verbose Mode: %s", args.verbose) + logging.debug(" - Quiet Mode: %s", args.quiet) - if args.verbose: - ch.setLevel(logging.DEBUG) - if args.quiet: - ch.setLevel(logging.CRITICAL) - - if not args.quiet: #only if not quiet mode - from includes import shellHeader - shellHeader.printHeader(args) - except: - logging.exception("cannot display/log args") + if args.verbose: + ch.setLevel(logging.DEBUG) + if args.quiet: + ch.setLevel(logging.CRITICAL) + + if not args.quiet: #only if not quiet mode + from includes import shellHeader + shellHeader.printHeader(args) + except: + logging.exception("cannot display/log args") - try: - #read config - 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) + try: + # + # Read config.ini + # + 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) + except: + logging.exception("cannot read config file") + else: + # initialization was fine, continue with main program... + + try: + # + # Set the loglevel and backupCount of the file handler + # + logging.debug("set loglevel of fileHandler to: %s",globals.config.getint("BOSWatch","loglevel") ) + fh.setLevel(globals.config.getint("BOSWatch","loglevel")) + logging.debug("set backupCount of fileHandler to: %s", globals.config.getint("BOSWatch","backupCount")) + fh.setBackupCount(globals.config.getint("BOSWatch","backupCount")) except: - logging.exception("cannot read config file") - else: + logging.exception("cannot set loglevel of fileHandler") + + # + # Load plugins + # + from includes import pluginLoader + pluginLoader.loadPlugins() + + # + # Load filters + # + if globals.config.getint("BOSWatch","useRegExFilter"): + from includes import filter + filter.loadFilters() + + # + # Load description lists + # + if globals.config.getint("BOSWatch","useDescription"): + from includes import descriptionList + descriptionList.loadDescriptionLists() + + try: + # + # Start rtl_fm + # + logging.debug("starting rtl_fm") + rtl_fm = subprocess.Popen("rtl_fm -d "+str(args.device)+" -f "+str(freqToHz(args.freq))+" -M fm -s 22050 -p "+str(args.error)+" -E DC -F 0 -l "+str(args.squelch)+" -g 100", + #stdin=rtl_fm.stdout, + stdout=subprocess.PIPE, + stderr=open(globals.script_path+"/log/rtl_fm.log","a"), + shell=True) + except: + logging.exception("cannot start rtl_fm") + else: + # rtl_fm started, continue... try: - #set the loglevel of the file handler - logging.debug("set loglevel of fileHandler") - fh.setLevel(globals.config.getint("BOSWatch","loglevel")) + # + # Start multimon + # + logging.debug("starting multimon-ng") + multimon_ng = subprocess.Popen("multimon-ng "+str(demodulation)+" -f alpha -t raw /dev/stdin - ", + stdin=rtl_fm.stdout, + stdout=subprocess.PIPE, + stderr=open(globals.script_path+"/log/multimon.log","a"), + shell=True) except: - logging.exception("cannot set loglevel of fileHandler") - - #load plugins - from includes import pluginLoader - pluginLoader.loadPlugins() - - #load filters - from includes import filter - filter.getFilters() - - try: - #start rtl_fm - logging.debug("starting rtl_fm") - rtl_fm = subprocess.Popen("rtl_fm -d "+str(args.device)+" -f "+str(args.freq)+" -M fm -s 22050 -p "+str(args.error)+" -E DC -F 0 -l "+str(args.squelch)+" -g 100", - #stdin=rtl_fm.stdout, - stdout=subprocess.PIPE, - stderr=open(globals.script_path+"/log/rtl_fm.log","a"), - shell=True) - except: - logging.exception("cannot start rtl_fm") - else: + logging.exception("cannot start multimon-ng") + else: + # multimon-ng started, continue... - try: - #start multimon - logging.debug("starting multimon-ng") - multimon_ng = subprocess.Popen("multimon-ng "+str(demodulation)+" -f alpha -t raw /dev/stdin - ", - stdin=rtl_fm.stdout, - stdout=subprocess.PIPE, - stderr=open(globals.script_path+"/log/multimon.log","a"), - shell=True) - except: - logging.exception("cannot start multimon-ng") - else: + logging.debug("start decoding") + + while True: + # + # Get decoded data from multimon-ng and call BOSWatch-decoder + # + + # RAW Data from Multimon-NG + # ZVEI2: 25832 + # FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 0=FZG->LST2=III(mit NA,ohneSIGNAL)) CRC correct\n' + # POCSAG1200: Address: 1234567 Function: 1 Alpha: Hello World + decoded = str(multimon_ng.stdout.readline()) #Get line data from multimon stdout - logging.debug("start decoding") + # Test-strings only for develop + #decoded = "ZVEI2: 25832" + #decoded = "FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 0=FZG->LST 2=I (ohneNA,ohneSIGNAL)) CRC correct\n'" + #decoded = "FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 1=LST->FZG 2=I (ohneNA,ohneSIGNAL)) CRC correct\n'" + #decoded = "FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 0=FZG->LST 2=II (ohneNA,mit SIGNAL)) CRC correct\n'" + #decoded = "FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 1=LST->FZG 2=III(mit NA,ohneSIGNAL)) CRC correct\n'" + #decoded = "FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 0=FZG->LST 2=IV (mit NA,mit SIGNAL)) CRC correct\n'" + #decoded = "POCSAG1200: Address: 1234567 Function: 1 Alpha: Hello World" + #time.sleep(1) - while True: - #RAW Data from Multimon-NG - #ZVEI2: 25832 - #FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 0=FZG->LST2=III(mit NA,ohneSIGNAL)) CRC correct\n' - decoded = str(multimon_ng.stdout.readline()) #Get line data from multimon stdout - - #only for develop - #decoded = "ZVEI2: 25832" - #decoded = "FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 0=FZG->LST 2=III(mit NA,ohneSIGNAL)) CRC correct\n'" - #decoded = "POCSAG1200: Address: 1234567 Function: 1 Alpha: Hello World" - #time.sleep(1) - - from includes import decoder - decoder.decode(args.freq, decoded) + from includes import decoder + decoder.decode(freqToHz(args.freq), decoded) except KeyboardInterrupt: logging.warning("Keyboard Interrupt") @@ -200,6 +297,11 @@ finally: logging.debug("exiting BOSWatch") except: logging.warning("failed in clean-up routine") - finally: - logging.info("BOSWatch exit()") - exit(0) + finally: + # Close Logging + logging.debug("close Logging") + logging.info("BOSWatch exit()") + logging.shutdown() + fh.close() + ch.close() + exit(0) \ No newline at end of file diff --git a/config/config.template.ini b/config/config.template.ini index febde63..64d2137 100644 --- a/config/config.template.ini +++ b/config/config.template.ini @@ -3,82 +3,202 @@ ######################## [BOSWatch] -#set loglevel for logfile -#10 = debug -#20 = info -#30 = warning -#40 = error -#50 = critical +# set loglevel for logfile +# 10 = debug +# 20 = info +# 30 = warning +# 40 = error +# 50 = critical loglevel = 10 -#time to ignore same alarm in a row (sek) -fms_double_ignore_time = 5 +# BOSWatch use a rotating logfile +# Rotating is at midnight +# You can set the backupCount here +# backupCount = 7 (keeps logfiles for a week) +backupCount = 7 -#time to ignore same alarm in a row (sek) -zvei_double_ignore_time = 5 +# Using RegEx-Filter (0|1) +# Filter-configuration in section [Filters] +useRegExFilter = 0 -#time to ignore same alarm in a row (sek) -poc_double_ignore_time = 10 -#start and end of the filter range -poc_filter_range_start = 0000000 -poc_filter_range_end = 9999999 +# Using Description (0|1) +# You have to be enabled it for every typ in the sections below too +useDescription = 0 + +[FMS] +# time to ignore same alarm in a row (sek) +double_ignore_time = 5 + +# look-up-table for adding a description +# turn on functionality (0|1) +idDescribed = 0 + +[ZVEI] +# time to ignore same alarm in a row (sek) +double_ignore_time = 5 + +# look-up-table for adding a description +# turn on functionality (0|1) +idDescribed = 0 + +[POC] +# time to ignore same alarm in a row (sek) +double_ignore_time = 5 + +# some very simple filters: +# Allow only this RICs (empty: allow all, separator ",") +# f.e.: allow_ric = 1234566,1234567,1234568 +allow_ric = + +# Deny this RICs (empty: allow all, separator ",") +# f.e.: deny_ric = 1234566,1234567,1234568 +deny_ric = + +# start and end of an allowed filter range +filter_range_start = 0000000 +filter_range_end = 9999999 + +# look-up-table for adding a description +# turn on functionality (0|1) +idDescribed = 0 [Filters] -#No Filter for a Typ/Plugin Combination = all Data pass -#INDIVIDUAL_NAME = TYP;PLUGIN;REGEX -zvei_local_filter = ZVEI;template;25[0-9F]{3} +# RegEX Filter Configuration +# http://www.regexr.com/ - RegEX Test Tool an Documentation +# No Filter for a Typ/Plugin Combination = all Data pass +# INDIVIDUAL_NAME = TYP;DATAFIELD;PLUGIN;FREQUENZ;REGEX +# TYP = the Data Typ (FMS|ZVEI|POC) +# DATAFIELD = the field of the Data Array (See interface.txt) +# PLUGIN = the name of the Plugin to call with this Filter (* for all) +# FREQUENZ = the Frequenz to use the Filter (for more SDR Sticks (* for all)) +# REGEX = the RegEX + +# only ZVEI to all Plugins with 25### at 85.5MHz +#testfilter = ZVEI;zvei;*;85500000;25[0-9]{3} + +# only POCSAG to MySQL with the text "ALARM:" in the Message +#pocTest = POC;msg;MySQL;*;ALARM: [Plugins] -#can take on or off the plugins (0|1) +# can take on or off the plugins (0|1) MySQL = 0 -BosMon = 0 httpRequest = 0 +eMail = 0 +BosMon = 0 +firEmergency = 0 + # for developing template-module template = 0 [MySQL] +# MySQL configuration dbserver = localhost dbuser = root dbpassword = root database = boswatch -#tables in the database +# tables in the database tableFMS = bos_fms tableZVEI = bos_zvei tablePOC = bos_pocsag +[httpRequest] +# URL without http:// + +# you can use the following wildcards in your URL as GET params: +# http://en.wikipedia.org/wiki/Query_string + +# %FMS% = FMS Code +# %STATUS% = FMS Status +# %DIR% = Direction of the telegram +# %TSI% = Tactical Short Information +#fms_url = www.google.de?code=%FMS%&stat=%STATUS% +fms_url = + +# %ZVEI% = ZVEI 5-tone Code +#zvei_url = www.google.de?zvei=%ZVEI% +zvei_url = + +# %RIC% = Pocsag RIC +# %FUNC% = Pocsac function/Subric +# %MSG% = Message of the Pocsag telegram +# %BITRATE% = Bitrate of the Pocsag telegram +#poc_url = www.google.de?ric=%RIC%&subric=%FUNC%&msg=%MSG% +poc_url = + + +[eMail] +# SMTP-Server +smtp_server = localhost +# Port of SMTP-Server (default: +smtp_port = +# use tls for connection (0|1) +tls = 0 +# Use this, when SMTP-Server has restricted access +user = +password = + +# Parameters for Alarm-Msg: +# "to" could be more than one address, comma separated +from = local@localhost +to = user@irgendwo, user2@woanders + +# Priority of the eMail: +# normal|urgent|non-urgent +priority = urgent + +# %FMS% = FMS Code +# %STATUS% = FMS Status +# %DIR% = Direction of the telegram (0/1) +# %DIRT% = Direction of the telegram (Text-String) +# %TSI% = Tactical Short Information (I-IV) +# %TIME% = Date/Time (by script) +fms_subject = FMS: %FMS% +fms_message = %TIME%: %FMS% - Status: %STATUS% - Direction: %DIRT% - TSI: %TSI% + +# %ZVEI% = ZVEI 5-tone Code +# %TIME% = Date/Time (by script) +zvei_subject = Alarm: %ZVEI% +zvei_message = %TIME%: %ZVEI% + +# %RIC% = Pocsag RIC +# %FUNC% = Pocsac function/Subric (1-4) +# %FUNCCHAR% = Pocsac function/Subric als character (a-d) +# %MSG% = Message of the Pocsag telegram +# %BITRATE% = Bitrate of the Pocsag telegram +# %TIME% = Date/Time (by script) +poc_subject = Alarm: %RIC%%FUNCCHAR% +poc_message = %TIME%: %MSG% + + [BosMon] -#Server as IP of DNS-Name (without http://) -#actually no ssl supported +# Server as IP of DNS-Name (without http://) +# actually no ssl supported bosmon_server = 192.168.0.1 bosmon_port = 80 -#channel-name of typ "Web-Telegramm" +# channel-name of typ "Web-Telegramm" bosmon_channel = channel -#Use this, when BosMon has restricted access +# Use this, when BosMon has restricted access bosmon_user = bosmon_password = -[httpRequest] -#URL without http:// -fms_url = www.google.de -zvei_url = www.google.de -poc_url = www.google.de +[firEmergency] +# firEmergency configuration +firserver = localhost +firport = 9001 ##################### ##### Not ready yet # ##################### - [template] -data1 = test123 -data2 = test345 -data3 = test567 -data4 = test789 \ No newline at end of file +test1 = testString +test2 = 123456 \ No newline at end of file diff --git a/csv/fms.csv b/csv/fms.csv new file mode 100644 index 0000000..e52a17e --- /dev/null +++ b/csv/fms.csv @@ -0,0 +1,10 @@ +fms,description +# +# BOSWatch CSV file for describing FMS-Addresses +# +# For each FMS-Address you could set a description-text +# Use the structure: fms,"Description-Text" +# +# !!! DO NOT delete the first line !!! +# +93377141,"John Q. Publics car" \ No newline at end of file diff --git a/csv/poc.csv b/csv/poc.csv new file mode 100644 index 0000000..3a667fe --- /dev/null +++ b/csv/poc.csv @@ -0,0 +1,10 @@ +ric,description +# +# BOSWatch CSV file for describing POCSAG-Addresses +# +# For each RIC-Address you could set a description-text +# Use the structure: ric,"Description-Text" +# +# !!! DO NOT delete the first line !!! +# +1234567,"John Q. Public" \ No newline at end of file diff --git a/csv/zvei.csv b/csv/zvei.csv new file mode 100644 index 0000000..da78fe3 --- /dev/null +++ b/csv/zvei.csv @@ -0,0 +1,10 @@ +zvei,description +# +# BOSWatch CSV file for describing ZVEI-Addresses +# +# For each ZVEI-Address you could set a description-text +# Use the structure: zvei,"Description-Text" +# +# !!! DO NOT delete the first line !!! +# +25832,"John Q. Public" \ No newline at end of file diff --git a/includes/alarmHandler.py b/includes/alarmHandler.py index 6c648cf..c5d75ea 100644 --- a/includes/alarmHandler.py +++ b/includes/alarmHandler.py @@ -1,16 +1,53 @@ #!/usr/bin/python # -*- coding: cp1252 -*- -import logging +""" +Handler for the Filter and Plugins at an Alarm + +@author: Bastian Schroll + +@requires: none +""" + +import logging # Global logger from includes import globals # Global variables + def processAlarm(typ,freq,data): - logging.debug("[ ALARM ]") - for pluginName, plugin in globals.pluginList.items(): - from includes import filter - if filter.checkFilters(data,typ,pluginName): - logging.debug("call Plugin: %s", pluginName) - plugin.run(typ,freq,data) - logging.debug("return from: %s", pluginName) - logging.debug("[END ALARM]") \ No newline at end of file + """ + Function to process Filters and Plugins at Alarm + + @type typ: string (FMS|ZVEI|POC) + @param typ: Typ of the dataset + @type freq: string + @param freq: frequency of the SDR Stick + @type data: map of data (structure see interface.txt) + @param data: Contains the parameter + + @requires: active Plugins in pluginList + + @return: nothing + @exception: Exception if Alarm processing failed + """ + try: + logging.debug("[ ALARM ]") + #Go to all Plugins in pluginList + for pluginName, plugin in globals.pluginList.items(): + + #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 + 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 b693120..0fcfd41 100644 --- a/includes/decoder.py +++ b/includes/decoder.py @@ -1,27 +1,49 @@ #!/usr/bin/python # -*- coding: cp1252 -*- +""" +Search for decode String and call the right decoder Funtion + +@author: Jens Herrmann + +@requires: none +""" + import logging def decode(freq, decoded): - - #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) + """ + Search for decode String and call the right decoder Function - #ZVEI Decoder Section - #check ZVEI: -> validate -> check double alarm -> log - if "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: - logging.debug("recieved POCSAG") - from includes.decoders import poc - poc.decode(freq, decoded) + @type freq: string + @param freq: frequency of the SDR Stick + @type decoded: string + @param decoded: RAW Information from Multimon-NG + + @return: nothing + @exception: Exception if decoder File call failed + """ + try: + #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: + 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: + logging.debug("recieved POCSAG") + from includes.decoders import poc + poc.decode(freq, decoded) + + except: + logging.exception("cannot start decoder") \ No newline at end of file diff --git a/includes/decoders/fms.py b/includes/decoders/fms.py index 2c983ef..cc542b9 100644 --- a/includes/decoders/fms.py +++ b/includes/decoders/fms.py @@ -1,34 +1,64 @@ #!/usr/bin/python # -*- coding: cp1252 -*- +""" +FMS Decoder + +@author: Bastian Schroll + +@requires: Configuration has to be set in the config.ini +""" + import logging import time #timestamp for doublealarm import re #Regex for validation from includes import globals # Global variables -#FMS Decoder Function -#validate -> check double alarm -> log +## +# +# FMS Decoder Function +# validate -> check double alarm -> log +# def decode(freq, decoded): + """ + Export FMS 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 FMS decode failed + """ 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_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("BOSWatch", "fms_double_ignore_time"): #check for double alarm + if fms_id == globals.fms_id_old and timestamp < globals.fms_time_old + globals.config.getint("FMS", "double_ignore_time"): #check for double alarm 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 else: - logging.info("FMS:%s Status:%s Richtung:%s TKI:%s", fms_id[0:8], fms_status, fms_direction, fms_tsi) - data = {"fms":fms_id[0:8], "status":fms_status, "direction":fms_direction, "tsi":fms_tsi} + 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]} + # If enabled, look up description + if globals.config.getint("FMS", "idDescribed"): + from includes import descriptionList + data["description"] = descriptionList.getDescription("FMS", fms_id[0:8]) + # processing the alarm from includes import alarmHandler alarmHandler.processAlarm("FMS",freq,data) diff --git a/includes/decoders/poc.py b/includes/decoders/poc.py index 0aa9e76..f40bc40 100644 --- a/includes/decoders/poc.py +++ b/includes/decoders/poc.py @@ -1,31 +1,94 @@ #!/usr/bin/python # -*- coding: cp1252 -*- +""" +POCSAG Decoder + +@author: Bastian Schroll +@author: Jens Herrmann + +@requires: Configuration has to be set in the config.ini +""" + import logging import time #timestamp for doublealarm import re #Regex for validation from includes import globals # Global variables -#POCSAG Decoder Function -#validate -> check double alarm -> log +## +# +# Simple Filter +# +def isAllowed(poc_id): + """ + Simple Filter Functions (Allowed, Denied and Range) + + @type poc_id: string + @param poc_id: POCSAG Ric + + @requires: Configuration has to be set in the config.ini + + @return: True if the Ric is allowed, other False + @exception: none + """ + # 1.) If allowed RICs is set, only they will path, + # 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) + return True + else: + logging.debug("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) + 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) + return False + elif int(poc_id) > globals.config.getint("POC", "filter_range_end"): + logging.debug("RIC %s out of filter range (end)", poc_id) + return False + return True + +## +# +# POCSAG Decoder Function +# validate -> check double alarm -> log +# def decode(freq, decoded): + """ + Export POCSAG 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 POCSAG decode failed + """ bitrate = 0 timestamp = int(time.time())#Get Timestamp if "POCSAG512:" in decoded: bitrate = 512 - poc_id = decoded[20:27] + poc_id = decoded[20:27].replace(" ", "").zfill(7) poc_sub = decoded[39].replace("3", "4").replace("2", "3").replace("1", "2").replace("0", "1") elif "POCSAG1200:" in decoded: bitrate = 1200 - poc_id = decoded[21:28] + poc_id = decoded[21:28].replace(" ", "").zfill(7) poc_sub = decoded[40].replace("3", "4").replace("2", "3").replace("1", "2").replace("0", "1") elif "POCSAG2400:" in decoded: bitrate = 2400 - poc_id = decoded[21:28] + poc_id = decoded[21:28].replace(" ", "").zfill(7) poc_sub = decoded[40].replace("3", "4").replace("2", "3").replace("1", "2").replace("0", "1") if bitrate is 0: @@ -39,22 +102,28 @@ def decode(freq, decoded): poc_text = "" if re.search("[0-9]{7}", poc_id): #if POC is valid - if int(poc_id) >= globals.config.getint("BOSWatch", "poc_filter_range_start"): - if int(poc_id) <= globals.config.getint("BOSWatch", "poc_filter_range_end"): - if poc_id == globals.poc_id_old and timestamp < globals.poc_time_old + globals.config.getint("BOSWatch", "poc_double_ignore_time"): #check for double alarm - logging.info("POCSAG%s double alarm: %s within %s second(s)", bitrate, globals.poc_id_old, timestamp-globals.poc_time_old) - globals.poc_time_old = timestamp #in case of double alarm, poc_double_ignore_time set new - else: - logging.info("POCSAG%s: %s %s %s ", bitrate, poc_id, poc_sub, poc_text) - data = {"ric":poc_id, "function":poc_sub, "msg":poc_text, "bitrate":bitrate} - from includes import alarmHandler - alarmHandler.processAlarm("POC",freq,data) - - globals.poc_id_old = poc_id #save last id - globals.poc_time_old = timestamp #save last time + if isAllowed(poc_id): + #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 + globals.poc_time_old = timestamp else: - logging.info("POCSAG%s: %s out of filter range (high)", bitrate, poc_id) + logging.info("POCSAG%s: %s %s %s ", bitrate, poc_id, poc_sub, poc_text) + data = {"ric":poc_id, "function":poc_sub, "msg":poc_text, "bitrate":bitrate, "description":poc_id} + # Add function as character a-d to dataset + data["functionChar"] = data["function"].replace("1", "a").replace("2", "b").replace("3", "c").replace("4", "d") + # If enabled, look up description + if globals.config.getint("POC", "idDescribed"): + from includes import descriptionList + data["description"] = descriptionList.getDescription("POC", poc_id) + # processing the alarm + from includes import alarmHandler + alarmHandler.processAlarm("POC",freq,data) + + globals.poc_id_old = poc_id #save last id + globals.poc_time_old = timestamp #save last time else: - logging.info("POCSAG%s: %s out of filter range (low)", bitrate, poc_id) + logging.info("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 81320dc..8d24ac0 100644 --- a/includes/decoders/zvei.py +++ b/includes/decoders/zvei.py @@ -1,29 +1,78 @@ #!/usr/bin/python # -*- coding: cp1252 -*- +""" +ZVEI Decoder + +@author: Bastian Schroll + +@requires: Configuration has to be set in the config.ini +""" + import logging import time #timestamp for doublealarm import re #Regex for validation from includes import globals # Global variables -#ZVEI Decoder Function -#validate -> check double alarm -> log +## +# +# 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 - if re.search("[0-9F]{5}", zvei_id): #if ZVEI is valid - if zvei_id == globals.zvei_id_old and timestamp < globals.zvei_time_old + globals.config.getint("BOSWatch", "zvei_double_ignore_time"): #check for double alarm + 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} + 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 + logging.warning("No valid ZVEI: %s", zvei_id) + + +def removeF(zvei): + """ + Resolve the F from the repeat Tone + + @type zvei: string + @param zvei: ZVEI Information + + @return: ZVEI without F + @exception: none + """ + if "F" in zvei: + zvei_old = zvei + for i in range(1, 5): + 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 diff --git a/includes/descriptionList.py b/includes/descriptionList.py new file mode 100644 index 0000000..c65c091 --- /dev/null +++ b/includes/descriptionList.py @@ -0,0 +1,102 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- + +""" +Function to expand the dataset with a description. + +@author: Jens Herrmann + +@requires: Configuration has to be set in the config.ini +""" + +import logging # Global logger + +import csv + +from includes import globals # Global variables + +## +# +# Local function will load the csv-file +# +def loadCSV(typ, idField): + """ + Local function for loading csv-file into python list + Structure: [id] = description + + @return: Python list of descriptions + @exception: Exception if loading failed + """ + resultList = {} + try: + logging.debug("-- loading %s.csv", typ) + with open(globals.script_path+'/csv/'+typ+'.csv') as csvfile: + # DictReader expected structure described in first line of csv-file + reader = csv.DictReader(csvfile) + for row in reader: + logging.debug(row) + # only import rows with an integer as id + if row[idField].isdigit() == True: + resultList[row[idField]] = row['description'] + logging.debug("-- loading csv finished") + except: + logging.exception("loading csvList for typ: %s failed", typ) + return resultList; + +## +# +# call this for loading the description lists +# +def loadDescriptionLists(): + """ + Load data from the csv-files in global description list for FMS, ZVEI and POCSAG + + @return: nothing + @exception: Exception if loading failed + """ + try: + logging.debug("loading description lists") + + if globals.config.getint("FMS", "idDescribed"): + logging.debug("- load FMS description list") + globals.fmsDescribtionList = loadCSV("fms", "fms") + + if globals.config.getint("ZVEI", "idDescribed"): + logging.debug("- load ZVEI description list") + globals.zveiDescribtionList = loadCSV("zvei", "zvei") + + if globals.config.getint("POC", "idDescribed"): + logging.debug("- load pocsag description list") + globals.ricDescribtionList = loadCSV("poc", "ric") + + except: + logging.exception("cannot load description lists") + + +## +# +# public function for getting a description +# +def getDescription(typ, id): + """ + Get description for id. + Will return id if no description will be found. + + @return: description as string + """ + resultStr = id; + logging.debug("look up description lists") + try: + if typ == "FMS": + resultStr = globals.fmsDescribtionList[id] + elif typ == "ZVEI": + resultStr = globals.zveiDescribtionList[id] + elif typ == "POC": + resultStr = globals.ricDescribtionList[id] + else: + logging.warning("Invalid Typ: %s", typ) + except KeyError: + # will be thrown when there is no description for the id + # -> nothing to do... + pass + return resultStr \ No newline at end of file diff --git a/includes/filter.py b/includes/filter.py index 6de5d44..ca72a5f 100644 --- a/includes/filter.py +++ b/includes/filter.py @@ -1,6 +1,14 @@ #!/usr/bin/python # -*- coding: cp1252 -*- +""" +Functions for the RegEX Filter + +@author: Bastian Schroll + +@requires: Configuration has to be set in the config.ini +""" + import logging # Global logger import re #Regex for Filter Check @@ -8,32 +16,58 @@ import re #Regex for Filter Check from includes import globals # Global variables -def getFilters(): - logging.debug("reading config file") +def loadFilters(): + """ + 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 + """ try: + logging.debug("loading filters") + #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(";") - globals.filterList.append({"name": key, "typ": filter[0], "plugin": filter[1], "regex": filter[2]}) + #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") -def checkFilters(data,typ,plugin): +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. + + @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 + @type freq: string + @param freq: frequency of the SDR Stick + + @requires: all Filters in the filterList + + @return: nothing + @exception: Exception if Filter check failed + """ try: - logging.debug("search Filter for %s to %s", typ, plugin) - - #extract the correct data for filtering - if typ == "FMS": data = data["fms"] - if typ == "ZVEI": data = data["zvei"] - if typ == "POC": data = data["poc"] + logging.debug("search Filter for %s to %s at %s Hz", typ, plugin, freq) foundFilter = False + #go to all Filter in globals.filterList for i in globals.filterList: - if i["typ"] == typ and i["plugin"] == plugin: + #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"]) - if re.search(i["regex"], data): + #Check the RegEX + if re.search(i["regex"], data[i["dataField"]]): logging.debug("Filter passed: %s", i["name"]) return True else: diff --git a/includes/globals.py b/includes/globals.py index f4af170..46aa5fc 100644 --- a/includes/globals.py +++ b/includes/globals.py @@ -1,6 +1,13 @@ #!/usr/bin/python # -*- coding: cp1252 -*- +""" +Global variables + +@author: Jens Herrmann +@author: Bastian Schroll +""" + #Global variables config = 0 script_path = "" @@ -19,4 +26,10 @@ poc_time_old = 0 pluginList = {} #filter -filterList = [] \ No newline at end of file +filterList = [] + +#idDescribing +fmsDescribtionList = {} +zveiDescribtionList = {} +ricDescribtionList = {} + diff --git a/includes/pluginLoader.py b/includes/pluginLoader.py index ae591cf..7e7134b 100644 --- a/includes/pluginLoader.py +++ b/includes/pluginLoader.py @@ -1,6 +1,14 @@ #!/usr/bin/python # -*- coding: cp1252 -*- +""" +Functions to Load and import the Plugins + +@author: Bastian Schroll + +@requires: Configuration has to be set in the config.ini +""" + import logging # Global logger import imp import os @@ -8,23 +16,40 @@ import os from includes import globals # Global variables def loadPlugins(): + """ + Load all Plugins into globals.pluginList + + @return: nothing + @exception: Exception if insert into globals.pluginList failed + """ try: logging.debug("loading plugins") + #go to all Plugins from getPlugins() for i in getPlugins(): + #call for each Plugin the loadPlugin() Methode plugin = loadPlugin(i) + #Add it to globals.pluginList globals.pluginList[i["name"]] = plugin except: logging.exception("cannot load Plugins") def getPlugins(): + """ + get a Python Dict of all activeated Plugins + + @return: Plugins as Python Dict + @exception: Exception if Plugin search failed + """ try: logging.debug("Search in Plugin Folder") PluginFolder = globals.script_path+"/plugins" plugins = [] + #Go to all Folders in the Plugin-Dir for i in os.listdir(PluginFolder): location = os.path.join(PluginFolder, i) - # plugins have to be a subdir with MainModule, if not skip + + #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 @@ -36,7 +61,7 @@ def getPlugins(): logging.debug("Plugin [ENABLED ] %s", i) else: logging.debug("Plugin [DISABLED] %s ", i) - except: #no entry for plugin found in config-file, skip + except: #no entry for plugin found in config-file logging.warning("Plugin [NO CONF ] %s", i) except: logging.exception("Error during Plugin search") @@ -45,6 +70,16 @@ def getPlugins(): def loadPlugin(plugin): + """ + Imports a single Plugin + + @type plugin: Plugin Data + @param plugin: Contains the information to import a Plugin + + + @return: nothing + @exception: Exception if Plugin import failed + """ try: logging.debug("load Plugin: %s", plugin["name"]) return imp.load_module(plugin["name"], *plugin["info"]) diff --git a/includes/shellHeader.py b/includes/shellHeader.py index 4387a6f..b1bcb52 100644 --- a/includes/shellHeader.py +++ b/includes/shellHeader.py @@ -1,32 +1,52 @@ #!/usr/bin/python # -*- coding: cp1252 -*- +""" +Shows the Header in Shell if quiet Mode is not active + +@author: Bastian Schroll +@author: Jens Herrmann + +@requires: none +""" def printHeader(args): - print " ____ ____ ______ __ __ __ " - print " / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ b" - print " / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ e" - print " / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / t" - print " /_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ a" - print " German BOS Information Script " - print " by Bastian Schroll " - print "" + """ + Prints the Header to the Shell - print "Frequency: "+args.freq - print "Device-ID: "+str(args.device) - print "Error in PPM: "+str(args.error) - print "Active Demods: "+str(len(args.demod)) - if "FMS" in args.demod: - print "- FMS" - if "ZVEI" in args.demod: - print "- ZVEI" - if "POC512" in args.demod: - print "- POC512" - if "POC1200" in args.demod: - print "- POC1200" - if "POC2400" in args.demod: - print "- POC2400" - print "Squelch: "+str(args.squelch) - if args.verbose: - print "Verbose Mode!" - print "" \ No newline at end of file + @type args: Array + @param args: All given Arguments from argsparser + + @return: nothing + @exception: Exception if display of the Shell Header failed + """ + try: + print " ____ ____ ______ __ __ __ " + print " / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ b" + print " / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ e" + print " / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / t" + print " /_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ a" + print " German BOS Information Script " + print " by Bastian Schroll " + print "" + + print "Frequency: "+args.freq + print "Device-ID: "+str(args.device) + print "Error in PPM: "+str(args.error) + print "Active Demods: "+str(len(args.demod)) + if "FMS" in args.demod: + print "- FMS" + if "ZVEI" in args.demod: + print "- ZVEI" + if "POC512" in args.demod: + print "- POC512" + if "POC1200" in args.demod: + print "- POC1200" + if "POC2400" in args.demod: + print "- POC2400" + print "Squelch: "+str(args.squelch) + if args.verbose: + print "Verbose Mode!" + print "" + except: + logging.exception("cannot display shell header") \ No newline at end of file diff --git a/plugin_test.py b/plugin_test.py index 624ca8b..ce6a67a 100644 --- a/plugin_test.py +++ b/plugin_test.py @@ -53,7 +53,7 @@ except: pluginLoader.loadPlugins() -filter.getFilters() +filter.loadFilters() # ----- Test Data ----- # @@ -61,7 +61,7 @@ filter.getFilters() #data = {"fms":"12345678", "status":"2", "direction":"1", "tsi":"III"} typ = "ZVEI" -data = {"zvei":"12345"} +data = {"zvei":"25345"} #typ = "POC" #data = {"ric":"1234567", "function":"1", "msg":"Hello World!, "bitrate":"1200"} diff --git a/plugins/BosMon/BosMon.py b/plugins/BosMon/BosMon.py index d22e5a0..968c423 100644 --- a/plugins/BosMon/BosMon.py +++ b/plugins/BosMon/BosMon.py @@ -1,6 +1,16 @@ #!/usr/bin/python # -*- coding: cp1252 -*- +""" +BOSWatch-Plugin to dispatch FMS-, ZVEI- and POCSAG - messages to BosMon + +-U {The BosMon hompage} + +@author: Jens Herrmann + +@requires: BosMon-Configuration has to be set in the config.ini +""" + import logging # Global logger import httplib #for the HTTP request @@ -9,23 +19,72 @@ import base64 #for the HTTP request with User/Password from includes import globals # Global variables - +## +# +# do BosMon-Request +# def bosMonRequest(httprequest, params, headers): + """ + This local function dispatch the BosMon-Request + + @type httprequest: HTTPConnection + @param httprequest: An HTTPConnection-Object that represents an open connection to a BosMon-Instance + @type params: string of urlencoded data + @param params: Contains the parameter for transfer to BosMon. + @type headers: map + @param headers: The headers argument should be a mapping of extra HTTP headers to send with the request. + + @return: nothing + @exception: Exception if httprequest.request failed + """ try: + # + # BosMon/HTTP-Request + # httprequest.request("POST", "/telegramin/"+globals.config.get("BosMon", "bosmon_channel")+"/input.xml", params, headers) except: logging.exception("request to BosMon failed") else: + # + # check HTTP-Response + # httpresponse = httprequest.getresponse() if str(httpresponse.status) == "200": #Check HTTP Response an print a Log or Error logging.debug("BosMon response: %s - %s", str(httpresponse.status), str(httpresponse.reason)) else: logging.warning("BosMon response: %s - %s", str(httpresponse.status), str(httpresponse.reason)) - +## +# +# Main function of BosMon-plugin +# will be called by the alarmHandler +# def run(typ,freq,data): + """ + This function is the implementation of the BosMon-Plugin. + It will send the data to an BosMon-Instance via http + + The configuration for the BosMon-Connection is set in the config.ini. + If an user is set, the HTTP-Request is authenticatet. + + @type typ: string (FMS|ZVEI|POC) + @param typ: Typ of the dataset for sending to BosMon + @type data: map of data (structure see interface.txt) + @param data: Contains the parameter for dispatch to BosMon. + @type freq: string + @keyword freq: frequency is not used in this plugin + + @requires: BosMon-Configuration has to be set in the config.ini + + @return: nothing + @exception: Exception if ConfigParser failed + @exception: Exception if initialize header and connect to BosMon-Server failed + @exception: Exception if urlencoding the params failed + """ try: - #ConfigParser + # + # ConfigParser + # logging.debug("reading config file") try: for key,val in globals.config.items("BosMon"): @@ -34,51 +93,76 @@ def run(typ,freq,data): logging.exception("cannot read config file") try: - #Initialize header an connect to BosMon-Server + # + # Initialize header an connect to BosMon-Server + # headers = {} headers['Content-type'] = "application/x-www-form-urlencoded" headers['Accept'] = "text/plain" + # if an user is set in the config.ini we will use HTTP-Authorization if globals.config.get("BosMon", "bosmon_user"): + # generate b64encoded autorization-token for HTTP-request headers['Authorization'] = "Basic {0}".format(base64.b64encode("{0}:{1}".format(globals.config.get("BosMon", "bosmon_user"), globals.config.get("BosMon", "bosmon_password")))) logging.debug("connect to BosMon") + # open connection to BosMon-Server httprequest = httplib.HTTPConnection(globals.config.get("BosMon", "bosmon_server"), globals.config.get("BosMon", "bosmon_port")) - #debug-level to shell (0=no debug|1) + # debug-level to shell (0=no debug|1) httprequest.set_debuglevel(0) except: logging.exception("cannot connect to BosMon") else: + # + # Format given data-structure to compatible BosMon string + # if typ == "FMS": - logging.warning("%s not supported", typ) - #logging.debug("Start FMS to BosMon") - #try: - #BosMon-Telegramin expected assembly group, direction and tsi in one field - #structure: - #params = urllib.urlencode({'type':'fms', 'address':data["fms"], 'status':data["status"], 'info':'0', 'flags':'0'}) - #logging.debug(" - Params: %s", params) - #bosMonRequest(httprequest, params, headers) - #except: - #logging.error("FMS to BosMon failed") + logging.debug("Start FMS to BosMon") + try: + # BosMon-Telegramin expected assembly group, direction and tsi in one field + # structure (binary as hex in base10): + # Byte 1: assembly group; Byte 2: Direction; Byte 3+4: tactic short info + info = 0 + # assembly group: + info = info + 1 # + b0001 (Assumption: is in every time 1 (no output from multimon-ng)) + # direction: + if data["direction"] == "1": + info = info + 2 # + b0010 + # tsi: + if "IV" in data["tsi"]: + info = info + 12 # + b1100 + elif "III" in data["tsi"]: + info = info + 8 # + b1000 + elif "II" in data["tsi"]: + info = info + 4 # + b0100 + # "I" is nothing to do + b0000 + + params = urllib.urlencode({'type':'fms', 'address':data["fms"], 'status':data["status"], 'info':info, 'flags':'0'}) + logging.debug(" - Params: %s", params) + # dispatch the BosMon-request + bosMonRequest(httprequest, params, headers) + except: + logging.exception("FMS to BosMon failed") elif typ == "ZVEI": logging.debug("Start ZVEI to BosMon") try: params = urllib.urlencode({'type':'zvei', 'address':data["zvei"], 'flags':'0'}) logging.debug(" - Params: %s", params) + # dispatch the BosMon-request bosMonRequest(httprequest, params, headers) except: - logging.error("ZVEI to BosMon failed") + logging.exception("ZVEI to BosMon failed") elif typ == "POC": logging.debug("Start POC to BosMon") try: - #BosMon-Telegramin expected "a-d" as RIC-sub/function - data["function"] = data["function"].replace("1", "a").replace("2", "b").replace("3", "c").replace("4", "d") - params = urllib.urlencode({'type':'pocsag', 'address':data["ric"], 'flags':'0', 'function':data["function"], 'message':data["msg"]}) + # BosMon-Telegramin expected "a-d" as RIC-sub/function + params = urllib.urlencode({'type':'pocsag', 'address':data["ric"], 'flags':'0', 'function':data["functionChar"], 'message':data["msg"]}) logging.debug(" - Params: %s", params) + # dispatch the BosMon-request bosMonRequest(httprequest, params, headers) except: - logging.error("POC to BosMon failed") + logging.exception("POC to BosMon failed") else: logging.warning("Invalid Typ: %s", typ) diff --git a/plugins/MySQL/MySQL.py b/plugins/MySQL/MySQL.py index be19b3e..887d314 100644 --- a/plugins/MySQL/MySQL.py +++ b/plugins/MySQL/MySQL.py @@ -1,6 +1,16 @@ #!/usr/bin/python # -*- coding: cp1252 -*- +""" +MySQL-Plugin to dispatch FMS-, ZVEI- and POCSAG - messages to a MySQL database + +@author: Jens Herrmann +@author: Bastian Schroll + +@requires: MySQL-Configuration has to be set in the config.ini +@requires: Created Database/Tables, see boswatch.sql +""" + import logging # Global logger import mysql @@ -9,7 +19,35 @@ import mysql.connector from includes import globals # Global variables +## +# +# Main function of MySQL-plugin +# will be called by the alarmHandler +# def run(typ,freq,data): + """ + This function is the implementation of the MySQL-Plugin. + It will store the data to an MySQL database + + The configuration for the MySQL-Connection is set in the config.ini. + For DB- and tablestructure see boswatch.sql + + @type typ: string (FMS|ZVEI|POC) + @param typ: Typ of the dataset for sending to BosMon + @type data: map of data (structure see interface.txt) + @param data: Contains the parameter for dispatch to BosMon. + @type freq: string + @keyword freq: frequency is not used in this plugin + + @requires: MySQL-Configuration has to be set in the config.ini + @requires: Created Database/Tables, see boswatch.sql + + @return: nothing + @exception: Exception if ConfigParser failed + @exception: Exception if connect to MySQL failed + @exception: Exception if executing the sql-statement is failed + """ + try: #ConfigParser logging.debug("reading config file") @@ -20,14 +58,19 @@ def run(typ,freq,data): logging.exception("cannot read config file") try: + # + # Connect to MySQL + # logging.debug("connect to MySQL") connection = mysql.connector.connect(host = globals.config.get("MySQL","dbserver"), user = globals.config.get("MySQL","dbuser"), passwd = globals.config.get("MySQL","dbpassword"), db = globals.config.get("MySQL","database")) cursor = connection.cursor() except: logging.exception("cannot connect to MySQL") else: - try: + # + # Create and execute SQL-statement + # logging.debug("Insert %s", typ) if typ == "FMS": diff --git a/plugins/eMail/eMail.py b/plugins/eMail/eMail.py new file mode 100644 index 0000000..24477a5 --- /dev/null +++ b/plugins/eMail/eMail.py @@ -0,0 +1,190 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- + +""" +eMail-Plugin to dispatch FMS-, ZVEI- and POCSAG - messages via eMail/SMTP + +@author: Jens Herrmann + +@requires: eMail-Configuration has to be set in the config.ini +""" + +import logging # Global logger + +import time +import smtplib #for the SMTP client +from email.mime.text import MIMEText # Import the email modules we'll need +from email.utils import formatdate # need for confirm to RFC2822 standard +from email.utils import make_msgid # need for confirm to RFC2822 standard + +from includes import globals # Global variables + +## +# +# Private helper function for a printable Timestamp +# +def curtime(): + return time.strftime("%Y-%m-%d %H:%M:%S") + +## +# +# do send mail +# +def doSendmail(server, subject, mailtext): + """ + This local function send the eMail + + @type server: SMTP + @param server: An SMTP-Object that represents an open connection to an eMail-Server + @type subject: string + @param subject: Subject for the eMail + @type mailtext: string + @param mailtext: Mailtext for the eMail + + @return: nothing + @exception: Exception if smtp.sendmail failed + """ + try: + msg = MIMEText(mailtext) + msg['From'] = globals.config.get("eMail", "from") + msg['To'] = globals.config.get("eMail", "to") + msg['Subject'] = subject + msg['Date'] = formatdate() + msg['Message-Id'] = make_msgid() + msg['Priority'] = globals.config.get("eMail", "priority") + server.sendmail(globals.config.get("eMail", "from"), globals.config.get("eMail", "to"), msg.as_string()) + except: + logging.exception("send eMail failed") + + +## +# +# Main function of eMail-plugin +# will be called by the alarmHandler +# +def run(typ,freq,data): + """ + This function is the implementation of the eMail-Plugin. + It will send the data via eMail (SMTP) + + The configuration for the eMail-Connection is set in the config.ini. + If an user is set, the HTTP-Request is authenticatet. + + @type typ: string (FMS|ZVEI|POC) + @param typ: Typ of the dataset for sending via eMail + @type data: map of data (structure see interface.txt) + @param data: Contains the parameter for dispatch to eMail. + @type freq: string + @keyword freq: frequency of the SDR Stick + + @requires: eMail-Configuration has to be set in the config.ini + + @return: nothing + @exception: Exception if ConfigParser failed + @exception: Exception if connect to SMTP-Server failed + @exception: Exception if sending the eMail failed + """ + try: + # + # ConfigParser + # + logging.debug("reading config file") + try: + for key,val in globals.config.items("eMail"): + logging.debug(" - %s = %s", key, val) + + except: + logging.exception("cannot read config file") + + try: + # + # connect to SMTP-Server + # + server = smtplib.SMTP(globals.config.get("eMail", "smtp_server"), globals.config.get("eMail", "smtp_port")) + # debug-level to shell (0=no debug|1) + server.set_debuglevel(0) + + # if tls is enabled, starttls + if globals.config.get("eMail", "tls"): + server.starttls() + + # if user is given, login + if globals.config.get("eMail", "user"): + server.login(globals.config.get("eMail", "user"), globals.config.get("eMail", "password")) + + except: + logging.exception("cannot connect to eMail") + + else: + + if typ == "FMS": + logging.debug("Start FMS to eMail") + try: + # read subject-structure from config.ini + subject = globals.config.get("eMail", "fms_subject") + subject = subject.replace("%FMS%", data["fms"]).replace("%STATUS%", data["status"]) #replace Wildcards + subject = subject.replace("%DIR%", data["direction"]).replace("%DIRT%", data["directionText"]) #replace Wildcards + subject = subject.replace("%TSI%", data["tsi"]) #replace Wildcards + subject = subject.replace("%DESCR%", data["description"]) # replace Wildcards + subject = subject.replace("%TIME%", curtime()) # replace Wildcards + # read mailtext-structure from config.ini + mailtext = globals.config.get("eMail", "fms_message") + mailtext = mailtext.replace("%FMS%", data["fms"]).replace("%STATUS%", data["status"]) #replace Wildcards + mailtext = mailtext.replace("%DIR%", data["direction"]).replace("%DIRT%", data["directionText"]) #replace Wildcards + mailtext = mailtext.replace("%TSI%", data["tsi"]) #replace Wildcards + mailtext = mailtext.replace("%DESCR%", data["description"]) # replace Wildcards + mailtext = mailtext.replace("%TIME%", curtime()) # replace Wildcards + # send eMail + doSendmail(server, subject, mailtext) + except: + logging.exception("FMS to eMail failed") + + elif typ == "ZVEI": + logging.debug("Start ZVEI to eMail") + try: + # read subject-structure from config.ini + subject = globals.config.get("eMail", "zvei_subject") + subject = subject.replace("%ZVEI%", data["zvei"]) #replace Wildcards + subject = subject.replace("%DESCR%", data["description"]) # replace Wildcards + subject = subject.replace("%TIME%", curtime()) # replace Wildcards + # read mailtext-structure from config.ini + mailtext = globals.config.get("eMail", "zvei_message") + mailtext = mailtext.replace("%ZVEI%", data["zvei"]) #replace Wildcards + mailtext = mailtext.replace("%DESCR%", data["description"]) # replace Wildcards + mailtext = mailtext.replace("%TIME%", curtime()) # replace Wildcards + # send eMail + doSendmail(server, subject, mailtext) + except: + logging.exception("ZVEI to eMail failed") + + elif typ == "POC": + logging.debug("Start POC to eMail") + try: + # read subject-structure from config.ini + subject = globals.config.get("eMail", "poc_subject") + subject = subject.replace("%RIC%", data["ric"]) #replace Wildcards + subject = subject.replace("%FUNC%", data["function"]).replace("%FUNCCHAR%", data["functionChar"]) #replace Wildcards + subject = subject.replace("%MSG%", data["msg"]).replace("%BITRATE%", str(data["bitrate"])) #replace Wildcards + subject = subject.replace("%DESCR%", data["description"]) # replace Wildcards + subject = subject.replace("%TIME%", curtime()) # replace Wildcards + # read mailtext-structure from config.ini + mailtext = globals.config.get("eMail", "poc_message") + mailtext = mailtext.replace("%RIC%", data["ric"]) #replace Wildcards + mailtext = mailtext.replace("%FUNC%", data["function"]).replace("%FUNCCHAR%", data["functionChar"]) #replace Wildcards + mailtext = mailtext.replace("%MSG%", data["msg"]).replace("%BITRATE%", str(data["bitrate"])) #replace Wildcards + mailtext = mailtext.replace("%DESCR%", data["description"]) # replace Wildcards + mailtext = mailtext.replace("%TIME%", curtime()) # replace Wildcards + # send eMail + doSendmail(server, subject, mailtext) + except: + logging.exception("POC to eMail failed") + + else: + logging.warning("Invalid Typ: %s", typ) + + finally: + logging.debug("close eMail-Connection") + server.quit() + + except: + logging.exception("") \ No newline at end of file diff --git a/plugins/firEmergency/firEmergency.py b/plugins/firEmergency/firEmergency.py new file mode 100644 index 0000000..3b2a4b2 --- /dev/null +++ b/plugins/firEmergency/firEmergency.py @@ -0,0 +1,93 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- + +""" +firEmergency-Plugin to dispatch ZVEI- and POCSAG - messages to firEmergency + +@autor: Smith-fms + +@requires: firEmergency-Configuration has to be set in the config.ini +""" + +import logging # Global logger +import socket + +from includes import globals # Global variables + +## +# +# Main function of firEmergency-plugin +# will be called by the alarmHandler +# +def run(typ,freq,data): + """ + This function is the implementation of the firEmergency-Plugin. + It will send the data to an firEmergency-Instance. + + The configuration for the firEmergency-Connection is set in the config.ini. + + @type typ: string (ZVEI|POC) + @param typ: Typ of the dataset for sending to firEmergency + @type data: map of data (structure see interface.txt) + @param data: Contains the parameter for dispatch to firEmergency. + @type freq: string + @keyword freq: frequency is not used in this plugin + + @requires: firEmergency-Configuration has to be set in the config.ini + + @return: nothing + @exception: Exception if ConfigParser failed + @exception: Exception ifconnect to firEmergency failed + @exception: Exception if sending the data failed + """ + try: + # + #ConfigParser + # + logging.debug("reading config file") + try: + for key,val in globals.config.items("firEmergency"): + logging.debug(" - %s = %s", key, val) + except: + logging.exception("cannot read config file") + + try: + # + # connect to firEmergency + # + firSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + firSocket.connect((globals.config.get("firEmergency", "firserver"), globals.config.getint("firEmergency", "firport"))) + except: + logging.exception("cannot connect to firEmergency") + else: + # + # Format given data-structure to xml-string for firEmergency + # + if typ == "FMS": + logging.debug("FMS not supported by firEmgency") + + elif typ == "ZVEI": + logging.debug("ZVEI to firEmergency") + try: + firXML = "\n
"+data["zvei"]+"
\n"+data["zvei"]+" alarmiert.\n
\n" + firSocket.send(firXML) + except: + logging.exception("ZVEI to firEmergency failed") + + elif typ == "POC": + logging.debug("POC to firEmergency") + try: + firXML = "\n
"+data["ric"]+"
\n"+data["msg"]+"\n
\n" + firSocket.send(firXML) + except: + logging.exception("POC to firEmergency failed") + + else: + logging.warning("Invalid Typ: %s", typ) + + finally: + logging.debug("close firEmergency-Connection") + firSocket.close() + + except: + logging.exception("unknown error") \ No newline at end of file diff --git a/plugins/httpRequest/httpRequest.py b/plugins/httpRequest/httpRequest.py index 85ee16f..da48450 100644 --- a/plugins/httpRequest/httpRequest.py +++ b/plugins/httpRequest/httpRequest.py @@ -1,76 +1,101 @@ #!/usr/bin/python # -*- coding: cp1252 -*- -######### -# USAGE -# -# Config -# ====== -# to read a option from config File -# VALUE = globals.config.get("SECTION", "OPTION") -# -# Data from boswatch.py -# ===================== -# use data["KEY"] for Alarm Data from boswatch.py -# for usable KEYs in different Functions (FMS|ZVEI|POC) see interface.txt -# -# LOG Messages -# ============ -# send Log Messages with logging.LOGLEVEL("MESSAGE") -# usable Loglevels debug|info|warning|error|exception|critical -# if you use .exception in Try:Exception: Construct, it logs the Python EX.message too +""" +httpRequest-Plugin to dispatch FMS-, ZVEI- and POCSAG - messages to an URL + +@author: Bastian Schroll + +@requires: httpRequest-Configuration has to be set in the config.ini +""" import logging # Global logger import httplib #for the HTTP request +from urlparse import urlparse #for split the URL into url and path from includes import globals # Global variables +## +# +# Main function of HTTP-plugin +# will be called by the alarmHandler +# def run(typ,freq,data): + """ + This function is the implementation of the httpRequest-Plugin. + It will send the data to an URL via http Request + + @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 freq: string + @keyword freq: frequency of the SDR Stick + + @requires: httpRequest-Configuration has to be set in the config.ini + + @return: nothing + @exception: Exception if ConfigParser failed + @exception: Exception if http Request failed + @exception: Exception if http Response failed + """ try: - #ConfigParser + # + # ConfigParser + # logging.debug("reading config file") try: for key,val in globals.config.items("httpRequest"): logging.debug(" - %s = %s", key, val) except: logging.exception("cannot read config file") - -########## User Plugin CODE ########## + try: + # + # Create URL + # logging.debug("send %s HTTP request", typ) if typ == "FMS": - httprequest = httplib.HTTPConnection(globals.config.get("httpRequest", "fms_url")) - httprequest.request("HEAD", "/") + url = globals.config.get("httpRequest", "fms_url") #Get URL + url = url.replace("%FMS%", data["fms"]).replace("%STATUS%", data["status"]) #replace Wildcards in URL + url = url.replace("%DIR%", data["direction"]).replace("%TSI%", data["tsi"]) #replace Wildcards in URL elif typ == "ZVEI": - httprequest = httplib.HTTPConnection(globals.config.get("httpRequest", "zvei_url")) - httprequest.request("HEAD", "/") + url = globals.config.get("httpRequest", "zvei_url") #Get URL + url = url.replace("%ZVEI%", data["zvei"]) #replace Wildcards in URL elif typ == "POC": - httprequest = httplib.HTTPConnection(globals.config.get("httpRequest", "poc_url")) - httprequest.request("HEAD", "/") + url = globals.config.get("httpRequest", "poc_url") #Get URL + url = url.replace("%RIC%", data["ric"]).replace("%FUNC%", data["function"]) #replace Wildcards in URL + url = url.replace("%MSG%", data["msg"]).replace("%BITRATE%", data["bitrate"]) #replace Wildcards in URL else: logging.warning("Invalid Typ: %s", typ) - + + # + # HTTP-Request + # + url = urlparse(url) #split URL into path and querry + httprequest = httplib.HTTPConnection(url[2]) #connect to URL Path + httprequest.request("GET", url[5]) #send URL Querry per GET + except: logging.exception("cannot send HTTP request") else: - - try: - httpresponse = httprequest.getresponse() + try: + # + # check HTTP-Response + # + httpresponse = httprequest.getresponse() if str(httpresponse.status) == "200": #Check HTTP Response an print a Log or Error logging.debug("HTTP response: %s - %s" , str(httpresponse.status), str(httpresponse.reason)) else: logging.warning("HTTP response: %s - %s" , str(httpresponse.status), str(httpresponse.reason)) - except NameError: #if var httprequest does not exist - logging.exception("no HTTP request been sended") except: #otherwise logging.exception("cannot get HTTP response") finally: logging.debug("close HTTP-Connection") httprequest.close() -########## User Plugin CODE ########## - + except: logging.exception("unknown error") \ No newline at end of file diff --git a/plugins/interface.txt b/plugins/interface.txt index 016f079..f2e8c01 100644 --- a/plugins/interface.txt +++ b/plugins/interface.txt @@ -1,36 +1,41 @@ -Übergabe an Plugin: - -typ = [FMS|ZVEI|POC] -freq = [FREQ(Hz)] -data = {"KEY1":"VALUE1","KEY2":"VALUE2"} als Python Dict +Handover to Plugin: +typ = [FMS|ZVEI|POC] +freq = [Freq in Hz] +data = {"KEY1":"VALUE1","KEY2":"VALUE2"} -Follgende Daten sind bei der jeweiligen Alarm-Art enthalten -und per data["OPTION"] abrufbar: +The following informations are included in the var "data". +They can be used by their Index Names: data['OPTION'] ZVEI: - zvei +- description FMS: - fms - status - direction +- directionText - tsi +- description POCSAG: - ric - function +- functionChar - msg - bitrate +- description -Es stehen folgende globale Objecte zur Verfügung: +Global Objects: -1.) import logging # Global logger -logging - Object -Nachricht ins Log per: logging.LOGLEVEL("MESSAGE") -Mögliche Loglevel: debug|info|warning|error|exception|critical +1.) +import logging # Global logger +Message into Log: logging.LOGLEVEL("MESSAGE") +Loglevel: debug|info|warning|error|exception|critical -2.) import globals # Global variables -config - Object (typ: ConfigParser, stellt config.ini bereit) +2.) +import globals # Global variables +reads Data from the config.ini VALUE = globals.config.get("SECTION", "OPTION") \ No newline at end of file diff --git a/boswatch.sql b/sql/boswatch.sql similarity index 100% rename from boswatch.sql rename to sql/boswatch.sql diff --git a/www/tpl/content.overview.php b/www/tpl/content.overview.php index 8d2f141..8f4f7f6 100644 --- a/www/tpl/content.overview.php +++ b/www/tpl/content.overview.php @@ -16,4 +16,12 @@ $Rows[] = $daten; } $tpl['zvei'] = $Rows; -?> \ No newline at end of file + //read POCSAG + $db->query("SELECT id, time, ric, funktion, text FROM ".$tablePOC." ORDER BY id DESC LIMIT 50"); + $Rows = array(); + while ($daten = $db->fetchAssoc()) + { + $Rows[] = $daten; + } + $tpl['poc'] = $Rows; +?> diff --git a/www/tpl/template.overview.php b/www/tpl/template.overview.php index e1c973a..6fd6e2a 100644 --- a/www/tpl/template.overview.php +++ b/www/tpl/template.overview.php @@ -28,6 +28,33 @@ Last alarms for FMS and ZVEI (max. 50)

echo ""; } ?> + +
+ Last POCSAG alarms + + + + + + + + + "; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + } + ?>
IDDatum - ZeitRICFunktionText
". $poc['id'] . "". $time . "". $poc['ric'] . "". $poc['funktion'] . "". $poc['text'] . "
@@ -54,4 +81,4 @@ Last alarms for FMS and ZVEI (max. 50)

} ?> - \ No newline at end of file +