diff --git a/.gitignore b/.gitignore index aad2b93..7617a01 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.psd *.pyc *.log -config.ini \ No newline at end of file +config.ini +log/ diff --git a/LICENSE b/LICENSE index 8cdb845..23cb790 100644 --- a/LICENSE +++ b/LICENSE @@ -337,4 +337,3 @@ proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. - diff --git a/README.md b/README.md index eb2c636..3c2ed6a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![# BOSWatch](/www/gfx/logo.png) -Python Script to receive and decode German BOS Information with rtl_fm and multimon-NG +:satellite: Python Script to receive and decode German BOS Information with rtl_fm and multimon-NG :satellite: #### Notice: The intercept of the German BOS radio is **strictly prohibited** and will be prosecuted. the use is **only authorized** personnel permitted. @@ -15,25 +15,34 @@ unless you are developer you can use the develop-Branch - may be unstable! ##### Implemented features: - FMS, ZVEI and POCSAG512/1200/2400 decoding and displaying - Plugin support for easy functional extension -- Filtering double alarms with adjustable time +- Filtering double alarms with adjustable time and check width - 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) +- Description look-up from csv-files - Logfiles for better troubleshooting - verbose/quiet mode for more/none information +- Ready for use BOSWatch as daemon ##### Features for the future: - more plugins +- other Ideas per Issues please ###Plugins +If you want to code your own Plugin, see Section `Code your own Plugin` at the end of this readme.MD + ##### 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]) + +|Plugin|Function|FMS|ZVEI|POC| +|-----|---------|:-:|:--:|:-:| +|MySQL|insert data into MySQL database|:white_check_mark:|:white_check_mark:|:white_check_mark:| +|httpRequest|send a request with parameter to an URL|:white_check_mark:|:white_check_mark:|:white_check_mark:| +|eMail|send Mails with own text|:white_check_mark:|:white_check_mark:|:white_check_mark:| +|BosMon|send data to BosMon server|:white_check_mark:|:white_check_mark:|:white_check_mark:| +|firEmergency|send data to firEmergency server|:x:|:white_check_mark:|:white_check_mark:| +|jsonSocket|send data as jsonString to a socket server|:white_check_mark:|:white_check_mark:|:white_check_mark:| - for more Information to the plugins see `config.ini` @@ -54,25 +63,28 @@ For the other functions see "Usage" below. ##### 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 +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} +`testfilter = ZVEI;zvei;*;85500000;25[0-9]{3}` only POCSAG to MySQL with the text "ALARM:" in the message -pocTest = POC;msg;MySQL;*;ALARM: +`pocTest = POC;msg;MySQL;*;ALARM:` -##### Web frontend -Put the files in folder /wwww/ into your local webserver folder (f.e. /var/www/). +##### Web frontend (obsolete) +New version in future - old data in folder `/www/` + +~~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 +Take a look into the parser.php for the parsing functions~~ ### Usage @@ -90,12 +102,13 @@ usage: boswatch.py [-h] -f FREQ [-d DEVICE] [-e ERROR] -a 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) + -d DEVICE, --device DEVICE Device you want to use (Check with rtl_test) -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 + Demodulation functions + -s SQUELCH, --squelch SQUELCH level of squelch + -u, --usevarlog Use '/var/log/boswatch' for logfiles instead of subdir 'log' in BOSWatch directory -v, --verbose Shows more information -q, --quiet Shows no information. Only logfiles ``` @@ -114,10 +127,25 @@ In case of an error during the installation, check the logfile in `~/boswatch/in 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. +Database Structure `boswatch.sql` in the MySQL Plugin Folder +If you want to use BOSWatch as a daemon, you have to set your +configuration in `service/boswatch.sh` and copy it to `/etc/init.d`. +Then you can start BOSWatch with `sudo /etc/init.d/boswatch.sh start`. +For configuration-details see `service/README.md`. ### Requirements - RTL_SDR (rtl_fm) - Multimon-NG - Python Support - MySQL Connector for Python (for MySQL-plugin) + +Thanks to smith_fms and McBo from Funkmeldesystem.de - Forum for Inspiration and Groundwork! + + +### Code your own Plugin +See `plugins/README.md` + +~~To code your own Plugin look at the litte example `/plugins/template/template.py`~~ + +~~In the text-file `plugins/interface.txt` are all relevant data, that your plugin can use.~~ diff --git a/boswatch.py b/boswatch.py index c8aa067..9e63beb 100755 --- a/boswatch.py +++ b/boswatch.py @@ -10,6 +10,8 @@ For more information see the README.md @author: Bastian Schroll @author: Jens Herrmann +Thanks to smith_fms and McBo from Funkmeldesystem.de - Forum for Inspiration and Groundwork! + GitHUB: https://github.com/Schrolli91/BOSWatch """ @@ -19,54 +21,24 @@ import logging.handlers import argparse # for parse the args import ConfigParser # for parse the config file import os # for log mkdir -import time # for timestamp +import time # for time.sleep() import subprocess # for starting rtl_fm and multimon-ng from includes import globals # Global variables - -## -# -# 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()") +from includes import MyTimedRotatingFileHandler # extension of TimedRotatingFileHandler +from includes import converter # converter functions +from includes import signalHandler # TERM-Handler for use script as a daemon +from includes import checkSubprocesses # check startup of the subprocesses # # ArgParser # Have to be before main program # -try: +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", + 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) @@ -74,247 +46,342 @@ try: 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("-u", "--usevarlog", help="Use '/var/log/boswatch' for logfiles instead of subdir 'log' in BOSWatch directory", action="store_true") 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() + # We need this argument for testing (skip instantiate of rtl-fm and multimon-ng): + parser.add_argument("-t", "--test", help=argparse.SUPPRESS, action="store_true") + args = parser.parse_args() except SystemExit: # -h or --help called, exit right now exit(0) except: - print "cannot parsing the arguments" + # we couldn't work without arguments -> exit + print "ERROR: cannot parsing the arguments" + exit(1) + - # # Main program # try: - # initialization + # initialization: + rtl_fm = None + multimon_ng = None + try: # # Script-pathes # globals.script_path = os.path.dirname(os.path.abspath(__file__)) - + + # + # Set log_path + # + if args.usevarlog: + globals.log_path = "/var/log/BOSWatch/" + else: + globals.log_path = globals.script_path+"/log/" + # # If necessary create log-path # - if not os.path.exists(globals.script_path+"/log/"): - os.mkdir(globals.script_path+"/log/") - - # - # Create new myLogger... - # + if not os.path.exists(globals.log_path): + os.mkdir(globals.log_path) + except: + # we couldn't work without logging -> exit + print "ERROR: cannot initialize paths" + exit(1) + + # + # Create new myLogger... + # + try: myLogger = logging.getLogger() myLogger.setLevel(logging.DEBUG) # set log string format + #formatter = logging.Formatter('%(asctime)s - %(module)-15s %(funcName)-15s [%(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 = MyTimedRotatingFileHandler(globals.script_path+"/log/boswatch.log", "midnight", interval=1, backupCount=999) + fh = MyTimedRotatingFileHandler.MyTimedRotatingFileHandler(globals.log_path+"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.setLevel(logging.DEBUG) fh.setFormatter(formatter) myLogger.addHandler(fh) # create a display logger ch = logging.StreamHandler() - # log level for display >= info - # will be changed later after parsing args - ch.setLevel(logging.INFO) - ch.setFormatter(formatter) - myLogger.addHandler(ch) - except: - logging.exception("cannot create logger") - else: - # initialization of the logging was fine, continue... - - 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") - rtl_log.write("") - mon_log.write("") - rtl_log.close() - mon_log.close() - logging.debug("BOSWatch has started") - logging.debug("Logfiles cleared") - except: - logging.exception("cannot clear Logfiles") - - 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) - - 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") - - try: - # - # Read config.ini - # - logging.debug("reading config file") - globals.config = ConfigParser.ConfigParser() - globals.config.read(globals.script_path+"/config/config.ini") - # 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") + # log level for display: Default: info + if args.verbose: + ch.setLevel(logging.DEBUG) + elif args.quiet: + ch.setLevel(logging.CRITICAL) 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 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: - # - # 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: - # multimon-ng started, continue... - - 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 - - # 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) - - from includes import decoder - decoder.decode(freqToHz(args.freq), decoded) - + ch.setLevel(logging.INFO) + ch.setFormatter(formatter) + myLogger.addHandler(ch) + + except: + # we couldn't work without logging -> exit + print "ERROR: cannot create logger" + exit(1) + + # initialization of the logging was fine, continue... + try: + # + # Clear the logfiles + # + fh.doRollover() + rtl_log = open(globals.log_path+"rtl_fm.log", "w") + mon_log = open(globals.log_path+"multimon.log", "w") + rtl_log.write("") + mon_log.write("") + rtl_log.close() + mon_log.close() + logging.debug("BOSWatch has started") + logging.debug("Logfiles cleared") + + except: + # It's an error, but we could work without that stuff... + logging.error("cannot clear Logfiles") + logging.debug("cannot clear Logfiles", exc_info=True) + pass + + # + # For debug display/log args + # + try: + logging.debug("SW Version: %s",globals.getVers("vers")) + logging.debug("Build Date: %s",globals.getVers("date")) + logging.debug("BOSWatch given arguments") + if args.test: + logging.debug(" - Test-Mode!") + + logging.debug(" - Frequency: %s", converter.freqToHz(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: POC1200") + if "POC2400" in args.demod: + demodulation += "-a POCSAG2400 " + logging.debug(" - Demod: POC2400") + + logging.debug(" - Use /var/log: %s", args.usevarlog) + logging.debug(" - Verbose Mode: %s", args.verbose) + logging.debug(" - Quiet Mode: %s", args.quiet) + + if not args.quiet: #only if not quiet mode + from includes import shellHeader + shellHeader.printHeader(args) + + if args.test: + logging.warning("!!! We are in Test-Mode !!!") + + except: + # we couldn't work without config -> exit + logging.critical("cannot display/log args") + logging.debug("cannot display/log args", exc_info=True) + exit(1) + + # + # Read config.ini + # + try: + logging.debug("reading config file") + globals.config = ConfigParser.ConfigParser() + globals.config.read(globals.script_path+"/config/config.ini") + # 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: + # we couldn't work without config -> exit + logging.critical("cannot read config file") + logging.debug("cannot read config file", exc_info=True) + exit(1) + + # 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: + # It's an error, but we could work without that stuff... + logging.error("cannot set loglevel of fileHandler") + logging.debug("cannot set loglevel of fileHandler", exc_info=True) + pass + + # + # Load plugins + # + try: + from includes import pluginLoader + pluginLoader.loadPlugins() + except: + # we couldn't work without plugins -> exit + logging.critical("cannot load Plugins") + logging.debug("cannot load Plugins", exc_info=True) + exit(1) + + # + # Load filters + # + try: + if globals.config.getboolean("BOSWatch","useRegExFilter"): + from includes import filter + filter.loadFilters() + except: + # It's an error, but we could work without that stuff... + logging.error("cannot load filters") + logging.debug("cannot load filters", exc_info=True) + pass + + # + # Load description lists + # + try: + if globals.config.getboolean("FMS","idDescribed") or globals.config.getboolean("ZVEI","idDescribed") or globals.config.getboolean("POC","idDescribed"): + from includes import descriptionList + descriptionList.loadDescriptionLists() + except: + # It's an error, but we could work without that stuff... + logging.error("cannot load description lists") + logging.debug("cannot load description lists", exc_info=True) + pass + + # + # Start rtl_fm + # + try: + if not args.test: + logging.debug("starting rtl_fm") + command = "" + if globals.config.has_option("BOSWatch","rtl_path"): + command = globals.config.get("BOSWatch","rtl_path") + command = command+"rtl_fm -d "+str(args.device)+" -f "+str(converter.freqToHz(args.freq))+" -M fm -s 22050 -p "+str(args.error)+" -E DC -F 0 -l "+str(args.squelch)+" -g 100" + rtl_fm = subprocess.Popen(command.split(), + #stdin=rtl_fm.stdout, + stdout=subprocess.PIPE, + stderr=open(globals.log_path+"rtl_fm.log","a"), + shell=False) + # rtl_fm doesn't self-destruct, when an error occurs + # wait a moment to give the subprocess a chance to write the logfile + time.sleep(3) + checkSubprocesses.checkRTL() + else: + logging.warning("!!! Test-Mode: rtl_fm not started !!!") + except: + # we couldn't work without rtl_fm -> exit + logging.critical("cannot start rtl_fm") + logging.debug("cannot start rtl_fm", exc_info=True) + exit(1) + + # + # Start multimon + # + try: + if not args.test: + logging.debug("starting multimon-ng") + command = "" + if globals.config.has_option("BOSWatch","multimon_path"): + command = globals.config.get("BOSWatch","multimon_path") + command = command+"multimon-ng "+str(demodulation)+" -f alpha -t raw /dev/stdin - " + multimon_ng = subprocess.Popen(command.split(), + stdin=rtl_fm.stdout, + stdout=subprocess.PIPE, + stderr=open(globals.log_path+"multimon.log","a"), + shell=False) + # multimon-ng doesn't self-destruct, when an error occurs + # wait a moment to give the subprocess a chance to write the logfile + time.sleep(3) + checkSubprocesses.checkMultimon() + else: + logging.warning("!!! Test-Mode: multimon-ng not started !!!") + except: + # we couldn't work without multimon-ng -> exit + logging.critical("cannot start multimon-ng") + logging.debug("cannot start multimon-ng", exc_info=True) + exit(1) + + # + # Get decoded data from multimon-ng and call BOSWatch-decoder + # + if not args.test: + logging.debug("start decoding") + while True: + decoded = str(multimon_ng.stdout.readline()) #Get line data from multimon stdout + from includes import decoder + decoder.decode(converter.freqToHz(args.freq), decoded) + + else: + logging.debug("start testing") + testFile = open(globals.script_path+"/testdata/testdata.txt","r") + for testData in testFile: + if (len(testData.rstrip(' \t\n\r')) > 1) and ("#" not in testData[0]): + logging.info("Testdata: %s", testData.rstrip(' \t\n\r')) + from includes import decoder + decoder.decode(converter.freqToHz(args.freq), testData) + time.sleep(1) + logging.debug("test finished") + except KeyboardInterrupt: - logging.warning("Keyboard Interrupt") + logging.warning("Keyboard Interrupt") +except SystemExit: + # SystemExitException is thrown if daemon was terminated + logging.warning("SystemExit received") + # only exit to call finally-block + exit() except: logging.exception("unknown error") finally: try: logging.debug("BOSWatch shuting down") - rtl_fm.terminate() - logging.debug("rtl_fm terminated") - multimon_ng.terminate() - logging.debug("multimon-ng terminated") - logging.debug("exiting BOSWatch") + if multimon_ng and multimon_ng.pid: + logging.debug("terminate multimon-ng (%s)", multimon_ng.pid) + multimon_ng.terminate() + multimon_ng.wait() + logging.debug("multimon-ng terminated") + if rtl_fm and rtl_fm.pid: + logging.debug("terminate rtl_fm (%s)", rtl_fm.pid) + rtl_fm.terminate() + rtl_fm.wait() + logging.debug("rtl_fm terminated") + logging.debug("exiting BOSWatch") except: - logging.warning("failed in clean-up routine") - finally: + logging.warning("failed in clean-up routine") + logging.debug("failed in clean-up routine", exc_info=True) + + finally: # Close Logging - logging.debug("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 64d2137..0bfe019 100644 --- a/config/config.template.ini +++ b/config/config.template.ini @@ -17,49 +17,57 @@ loglevel = 10 # backupCount = 7 (keeps logfiles for a week) backupCount = 7 +# if you want to start BOSWatch as a daemon with rc2.d, +# you have to set the path to rtl_fm and multimon-ng ! +# the path have to end with an / +#rtl_path = /usr/local/bin/ +#multimon_path = /usr/local/bin/ + # Using RegEx-Filter (0|1) # Filter-configuration in section [Filters] useRegExFilter = 0 -# Using Description (0|1) -# You have to be enabled it for every typ in the sections below too -useDescription = 0 +# for double check save the last n IDs +# it is used in combination with double_ignore_time +# 1 is required if you want to use the double alarm filter +doubleFilter_ignore_entries = 10 + +# time to ignore same alarm (only ID is checked) (sek) +doubleFilter_ignore_time = 5 + +# ignore msg is only usefull for POCSAG (0|1) +# 0: double check ignores the msg-text (only check ID + function) +# 1: if you want to differentiate between with/ without msg +# f.e. if they use quick-alarm (without text, then same ric with msg) +# you will get more then one alarm anyway if the msg is different (receiving-problems) +doubleFilter_check_msg = 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) +# Using Description (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) +# Using Description (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 = +allow_ric = # Deny this RICs (empty: allow all, separator ",") # f.e.: deny_ric = 1234566,1234567,1234568 -deny_ric = +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) +# Using Description (0|1) idDescribed = 0 @@ -88,6 +96,7 @@ httpRequest = 0 eMail = 0 BosMon = 0 firEmergency = 0 +jsonSocket = 0 # for developing template-module template = 0 @@ -114,27 +123,38 @@ tablePOC = bos_pocsag # %FMS% = FMS Code # %STATUS% = FMS Status -# %DIR% = Direction of the telegram -# %TSI% = Tactical Short Information +# %DIR% = Direction of the telegram (0/1) +# %DIRT% = Direction of the telegram (Text-String) +# %TSI% = Tactical Short Information (I-IV) +# %DESCR% = Description from csv-file +# %TIME% = Time (by script) +# %DATE% = Date (by script) #fms_url = www.google.de?code=%FMS%&stat=%STATUS% -fms_url = +fms_url = # %ZVEI% = ZVEI 5-tone Code +# %DESCR% = Description from csv-file +# %TIME% = Time (by script) +# %DATE% = Date (by script) #zvei_url = www.google.de?zvei=%ZVEI% -zvei_url = +zvei_url = # %RIC% = Pocsag RIC -# %FUNC% = Pocsac function/Subric +# %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 +# %DESCR% = Description from csv-file +# %TIME% = Time (by script) +# %DATE% = Date (by script) #poc_url = www.google.de?ric=%RIC%&subric=%FUNC%&msg=%MSG% -poc_url = +poc_url = [eMail] # SMTP-Server smtp_server = localhost -# Port of SMTP-Server (default: +# Port of SMTP-Server (default: smtp_port = # use tls for connection (0|1) tls = 0 @@ -156,23 +176,29 @@ priority = urgent # %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) +# %DESCR% = Description from csv-file +# %TIME% = Time (by script) +# %DATE% = Date (by script) fms_subject = FMS: %FMS% -fms_message = %TIME%: %FMS% - Status: %STATUS% - Direction: %DIRT% - TSI: %TSI% +fms_message = %DATE% %TIME%: %FMS% - Status: %STATUS% - Direction: %DIRT% - TSI: %TSI% # %ZVEI% = ZVEI 5-tone Code -# %TIME% = Date/Time (by script) +# %DESCR% = Description from csv-file +# %TIME% = Time (by script) +# %DATE% = Date (by script) zvei_subject = Alarm: %ZVEI% -zvei_message = %TIME%: %ZVEI% +zvei_message = %DATE% %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) +# %DESCR% = Description from csv-file +# %TIME% = Time (by script) +# %DATE% = Date (by script) poc_subject = Alarm: %RIC%%FUNCCHAR% -poc_message = %TIME%: %MSG% +poc_message = %DATE% %TIME%: %MSG% [BosMon] @@ -185,8 +211,8 @@ bosmon_port = 80 bosmon_channel = channel # Use this, when BosMon has restricted access -bosmon_user = -bosmon_password = +bosmon_user = +bosmon_password = [firEmergency] @@ -194,6 +220,13 @@ bosmon_password = firserver = localhost firport = 9001 +[jsonSocket] +# Protocol for socket (TCP|UDP) +protocol = UDP +# Server as IP of DNS-Name (without http://) +server = 192.168.0.1 +port = 8888 + ##################### ##### Not ready yet # @@ -201,4 +234,4 @@ firport = 9001 [template] test1 = testString -test2 = 123456 \ No newline at end of file +test2 = 123456 diff --git a/csv/fms.csv b/csv/fms.csv index e52a17e..a9260fd 100644 --- a/csv/fms.csv +++ b/csv/fms.csv @@ -1,10 +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 +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 !!! +# +12345678,"FMS testdata" diff --git a/csv/poc.csv b/csv/poc.csv index 3a667fe..44ddacb 100644 --- a/csv/poc.csv +++ b/csv/poc.csv @@ -1,10 +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 +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,"POCSAG testdata" diff --git a/csv/zvei.csv b/csv/zvei.csv index da78fe3..ebfc9e0 100644 --- a/csv/zvei.csv +++ b/csv/zvei.csv @@ -1,10 +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 +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 !!! +# +12345,"ZVEI testdata" diff --git a/includes/MyTimedRotatingFileHandler.py b/includes/MyTimedRotatingFileHandler.py new file mode 100644 index 0000000..bafd4a8 --- /dev/null +++ b/includes/MyTimedRotatingFileHandler.py @@ -0,0 +1,18 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- +# + +""" +This Class extended the TimedRotatingFileHandler with the possibility +to change the backupCount after initialization. + +@author: Jens Herrmann +""" + +import logging + +class MyTimedRotatingFileHandler(logging.handlers.TimedRotatingFileHandler): + """Extended Version of TimedRotatingFileHandler""" + def setBackupCount(self, backupCount): + """Set/Change backupCount""" + self.backupCount = backupCount diff --git a/includes/alarmHandler.py b/includes/alarmHandler.py index 4a1b9c5..212593e 100644 --- a/includes/alarmHandler.py +++ b/includes/alarmHandler.py @@ -30,9 +30,9 @@ def processAlarm(typ,freq,data): @param data: Contains the parameter @requires: active plugins in pluginList - + @return: nothing - @exception: Exception if Alarm processing failed + @exception: Exception if Alarm processing itself failed """ try: logging.debug("[ ALARM ]") @@ -41,14 +41,22 @@ def processAlarm(typ,freq,data): # if enabled use RegEx-filter if globals.config.getint("BOSWatch","useRegExFilter"): from includes import filter - if filter.checkFilters(typ,data,pluginName,freq): + if filter.checkFilters(typ,data,pluginName,freq): logging.debug("call Plugin: %s", pluginName) - plugin.run(typ,freq,data) - logging.debug("return from: %s", pluginName) + try: + plugin.run(typ,freq,data) + logging.debug("return from: %s", pluginName) + except: + # call next plugin, if one has thrown an exception + pass else: # RegEX filter off - call plugin directly logging.debug("call Plugin: %s", pluginName) - plugin.run(typ,freq,data) - logging.debug("return from: %s", pluginName) + try: + plugin.run(typ,freq,data) + logging.debug("return from: %s", pluginName) + except: + # call next plugin, if one has thrown an exception + pass logging.debug("[END ALARM]") except: - logging.exception("Error in Alarm processing") \ No newline at end of file + logging.exception("Error in alarm processing") diff --git a/includes/checkSubprocesses.py b/includes/checkSubprocesses.py new file mode 100644 index 0000000..f76e1eb --- /dev/null +++ b/includes/checkSubprocesses.py @@ -0,0 +1,54 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- +# +""" +Functions for checking the subprocesses rtl_fm and multimon-ng +Used in boswatch.py at startup and designated for watching-service + +@author: Jens Herrmann +""" + +import logging + +from includes import globals # Global variables + + +def checkRTL(): + """ + check startup of rtl_fm + + @exception: OSError when rtl_fm returns an error + @exception: Exception when checkRTL throws an unexpected error + """ + try: + rtlLog = open(globals.log_path+"rtl_fm.log","r").read() + if ("exiting" in rtlLog) or ("Failed to open" in rtlLog): + logging.debug("\n%s", rtlLog) + raise OSError("starting rtl_fm returns an error") + except OSError: + raise + except: + # we couldn't work without rtl_fm + logging.critical("cannot check rtl_fm.log") + logging.debug("cannot check rtl_fm.log", exc_info=True) + raise + +def checkMultimon(): + """ + check startup of multimon-ng + + @exception: OSError when multimon-ng returns an error + @exception: Exception when checkMultimon throws an unexpected error + """ + try: + multimonLog = open(globals.log_path+"multimon.log","r").read() + if ("invalid" in multimonLog) or ("error" in multimonLog): + logging.debug("\n%s", multimonLog) + raise OSError("starting multimon-ng returns an error") + except OSError: + raise + except: + # we couldn't work without multimon-ng + logging.critical("cannot check multimon.log") + logging.debug("cannot check multimon.log", exc_info=True) + raise diff --git a/includes/converter.py b/includes/converter.py new file mode 100644 index 0000000..b662509 --- /dev/null +++ b/includes/converter.py @@ -0,0 +1,29 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- +# + +""" +convert frequency to Hz + +@author: Bastian Schroll +""" + +import logging + +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()") diff --git a/includes/decoder.py b/includes/decoder.py index 2161968..6224314 100644 --- a/includes/decoder.py +++ b/includes/decoder.py @@ -1,53 +1,49 @@ -#!/usr/bin/python -# -*- coding: cp1252 -*- - -""" -Search for decode string and call the right decoder function - -@author: Jens Herrmann - -@requires: none -""" - -import logging # Global logger - -def decode(freq, decoded): - """ - Search for decode string and call the right decoder function - - @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 - elif "ZVEI2:" in decoded: - logging.debug("recieved ZVEI") - from includes.decoders import zvei - zvei.decode(freq, 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) - - except: - logging.exception("cannot start decoder") \ No newline at end of file +#!/usr/bin/python +# -*- coding: cp1252 -*- + +""" +Search for decode string and call the right decoder function + +@author: Jens Herrmann + +@requires: none +""" + +import logging # Global logger + +def decode(freq, decoded): + """ + Search for decode string and call the right decoder function + + @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 + 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 + elif "POCSAG512:" in decoded or "POCSAG1200:" in decoded or "POCSAG2400:" in decoded: + logging.debug("recieved POCSAG") + from includes.decoders import poc + poc.decode(freq, decoded) + + except: + logging.exception("cannot start decoder") diff --git a/includes/decoders/fms.py b/includes/decoders/fms.py index 3ef24f2..08c06f8 100644 --- a/includes/decoders/fms.py +++ b/includes/decoders/fms.py @@ -10,15 +10,15 @@ FMS Decoder """ import logging # Global logger -import time # timestamp for doublealarm import re # Regex for validation from includes import globals # Global variables +from includes import doubleFilter # double alarm filter ## # # FMS decoder function -# validate -> check double alarm -> log +# validate -> check double alarm -> log # def decode(freq, decoded): """ @@ -30,44 +30,46 @@ def decode(freq, decoded): @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 + try: + 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 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) - # in case of double alarm, fms_double_ignore_time set new - globals.fms_time_old = timestamp + 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 FMS is valid + if re.search("[0-9a-f]{8}[0-9a-f]{1}[01]{1}", fms_id): + # check for double alarm + if doubleFilter.checkID("FMS", fms_id): + 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 + try: + from includes import alarmHandler + alarmHandler.processAlarm("FMS", freq, data) + except: + logging.error("processing alarm failed") + logging.debug("processing alarm failed", exc_info=True) + pass + # in every time save old data for double alarm + doubleFilter.newEntry(fms_id) 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]} - # 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) - - globals.fms_id_old = fms_id #save last id - globals.fms_time_old = timestamp #save last time + logging.warning("No valid FMS: %s", fms_id) else: - logging.warning("No valid FMS: %s", fms_id) - else: - logging.warning("FMS CRC incorrect") \ No newline at end of file + logging.warning("FMS CRC incorrect") + except: + logging.error("error while decoding") + logging.debug("error while decoding", exc_info=True) diff --git a/includes/decoders/poc.py b/includes/decoders/poc.py index 4f8abc0..b40dafe 100644 --- a/includes/decoders/poc.py +++ b/includes/decoders/poc.py @@ -11,10 +11,10 @@ POCSAG Decoder """ import logging # Global logger -import time # timestamp for doublealarm import re # Regex for validation from includes import globals # Global variables +from includes import doubleFilter # double alarm filter ## # @@ -28,11 +28,11 @@ def isAllowed(poc_id): @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, + # 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"): @@ -55,7 +55,7 @@ def isAllowed(poc_id): return True ## -# +# # POCSAG decoder function # validate -> check double alarm -> log # @@ -69,62 +69,65 @@ def decode(freq, decoded): @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].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].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].replace(" ", "").zfill(7) - poc_sub = decoded[40].replace("3", "4").replace("2", "3").replace("1", "2").replace("0", "1") - - if bitrate is 0: - logging.warning("POCSAG Bitrate not found") - logging.debug(" - (%s)", decoded) - else: - logging.debug("POCSAG Bitrate: %s", bitrate) - - if "Alpha:" in decoded: #check if there is a text message - poc_text = decoded.split('Alpha: ')[1].strip().rstrip('').strip() + try: + bitrate = 0 + + if "POCSAG512:" in decoded: + bitrate = 512 + poc_id = decoded[20:27].replace(" ", "").zfill(7) + poc_sub = str(int(decoded[39])+1) + + elif "POCSAG1200:" in decoded: + bitrate = 1200 + poc_id = decoded[21:28].replace(" ", "").zfill(7) + poc_sub = str(int(decoded[40])+1) + + elif "POCSAG2400:" in decoded: + bitrate = 2400 + poc_id = decoded[21:28].replace(" ", "").zfill(7) + poc_sub = str(int(decoded[40])+1) + + if bitrate is 0: + logging.warning("POCSAG Bitrate not found") + logging.debug(" - (%s)", decoded) else: - poc_text = "" - - if re.search("[0-9]{7}", poc_id): #if POC is valid - 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 %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 + logging.debug("POCSAG Bitrate: %s", bitrate) + + if "Alpha:" in decoded: #check if there is a text message + poc_text = decoded.split('Alpha: ')[1].strip().rstrip('').strip() else: - 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 + poc_text = "" + + if re.search("[0-9]{7}", poc_id) and re.search("[1-4]{1}", poc_sub): #if POC is valid + if isAllowed(poc_id): + # check for double alarm + if doubleFilter.checkID("POC", poc_id+poc_sub, poc_text): + 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 + try: + from includes import alarmHandler + alarmHandler.processAlarm("POC", freq, data) + except: + logging.error("processing alarm failed") + logging.debug("processing alarm failed", exc_info=True) + pass + # in every time save old data for double alarm + doubleFilter.newEntry(poc_id+poc_sub, poc_text) + else: + logging.debug("POCSAG%s: %s is not allowed", bitrate, poc_id) + else: + logging.warning("No valid POCSAG%s RIC: %s SUB: %s", bitrate, poc_id, poc_sub) + except: + logging.error("error while decoding") + logging.debug("error while decoding", exc_info=True) diff --git a/includes/decoders/zvei.py b/includes/decoders/zvei.py index 789d5da..ce6e9e2 100644 --- a/includes/decoders/zvei.py +++ b/includes/decoders/zvei.py @@ -10,10 +10,10 @@ ZVEI Decoder """ import logging # Global logger -import time # timestamp for doublealarm import re # Regex for validation from includes import globals # Global variables +from includes import doubleFilter # double alarm filter ## # @@ -25,7 +25,7 @@ def removeF(zvei): @type zvei: string @param zvei: ZVEI Information - + @return: ZVEI without F @exception: none """ @@ -40,7 +40,7 @@ def removeF(zvei): ## # # ZVEI decoder function -# validate -> check double alarm -> log +# validate -> check double alarm -> log # def decode(freq, decoded): """ @@ -52,32 +52,34 @@ def decode(freq, decoded): @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 + try: + 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 doubleFilter.checkID("ZVEI", zvei_id): + 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 + try: + from includes import alarmHandler + alarmHandler.processAlarm("ZVEI", freq, data) + except: + logging.error("processing alarm failed") + logging.debug("processing alarm failed", exc_info=True) + pass + # in every time save old data for double alarm + doubleFilter.newEntry(zvei_id) 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 + logging.warning("No valid ZVEI: %s", zvei_id) + except: + logging.error("error while decoding") + logging.debug("error while decoding", exc_info=True) diff --git a/includes/descriptionList.py b/includes/descriptionList.py index 6f3e0ea..80b7ea7 100644 --- a/includes/descriptionList.py +++ b/includes/descriptionList.py @@ -1,104 +1,114 @@ -#!/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 # for loading the description files - -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 - except: - logging.debug("Error during look up description lists") - return resultStr \ No newline at end of file +#!/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 # for loading the description files + +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 + """ + 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.error("loading csvList for typ: %s failed", typ) + logging.debug("loading csvList for typ: %s failed", typ, exc_info=True) + raise + 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.error("cannot load description lists") + logging.debug("cannot load description lists", exc_info=True) + pass + + +## +# +# 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 + + except: + logging.error("Error during look up description lists") + logging.debug("Error during look up description lists", exc_info=True) + pass + + logging.debug(" - result for %s: %s", id, resultStr) + return resultStr diff --git a/includes/doubleFilter.py b/includes/doubleFilter.py new file mode 100644 index 0000000..710a777 --- /dev/null +++ b/includes/doubleFilter.py @@ -0,0 +1,65 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- + +""" +doubleFilter is the central function to filter out double alarms. +You can set the number of historical entries the filter will check +and the time ignoring the id in case of a double alarm + +@author: Jens Herrmann + +@requires: Configuration has to be set in the config.ini +""" + +import logging # Global logger +import time # timestamp for doublealarm + +from includes import globals # Global variables + +# +# ListStructure [0..n] = (ID, TimeStamp, msg) +# + +def checkID(typ, id, msg=""): + """ + check if id was called in the last x sec and n entries + + @requires: Configuration has to be set in the config.ini + + @return: True if check was OK + @return: False if double was found + """ + timestamp = int(time.time()) # Get Timestamp + + for i in range(len(globals.doubleList)): + (xID, xTimestamp, xMsg) = globals.doubleList[i] + # given ID found? + # return False if the first entry in double_ignore_time is found, we will not check for younger ones... + if id == xID and timestamp < xTimestamp + globals.config.getint("BOSWatch", "doubleFilter_ignore_time"): + # if wanted, we have to check the msg additional + if "POC" in typ and globals.config.getint("BOSWatch", "doubleFilter_check_msg"): + # if msg is a substring of xMsg we found a double + if msg in xMsg: + logging.info("%s double alarm (id+msg): %s within %s second(s)", typ, xID, timestamp-xTimestamp) + return False + else: + logging.info("%s double alarm (id): %s within %s second(s)", typ, xID, timestamp-xTimestamp) + return False + return True + + +def newEntry(id, msg = ""): + """ + new entry in double alarm list + + @return: nothing + """ + timestamp = int(time.time()) # Get Timestamp + globals.doubleList.append((id, timestamp, msg)) + + logging.debug("Added %s to doubleList", id) + + # now check if list has more than n entries: + if len(globals.doubleList) > globals.config.getint("BOSWatch", "doubleFilter_ignore_entries"): + # we have to kill the oldest one + globals.doubleList.pop(0) diff --git a/includes/filter.py b/includes/filter.py index 4849d50..77712a4 100644 --- a/includes/filter.py +++ b/includes/filter.py @@ -15,15 +15,16 @@ import re #Regex for Filter Check from includes import globals # Global variables +from includes import converter # converter functions + 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") @@ -31,17 +32,24 @@ def loadFilters(): for key,val in globals.config.items("Filters"): logging.debug(" - %s = %s", key, val) filter = val.split(";") + + # resolve the * for freqToHz() + if not filter[3] == "*": + filter[3] = converter.freqToHz(filter[3]) + # 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]}) + globals.filterList.append({"name": key, "typ": filter[0], "dataField": filter[1], "plugin": filter[2], "freq": filter[3], "regex": filter[4]}) except: - logging.exception("cannot read config file") - - + logging.error("cannot read config file") + logging.debug("cannot read config file", exc_info=True) + return + + 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) @@ -50,15 +58,14 @@ def checkFilters(typ,data,plugin,freq): @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 at %s Hz", typ, plugin, freq) - + foundFilter = False # go to all filter in globals.filterList for i in globals.filterList: @@ -72,13 +79,16 @@ def checkFilters(typ,data,plugin,freq): return True else: logging.debug("Filter not passed: %s", i["name"]) - + if foundFilter: logging.debug("no Filter passed") return False else: logging.debug("no Filter found") return True - + except: - logging.exception("Error in Filter checking") \ No newline at end of file + logging.error("Error in filter checking") + logging.debug("Error in filter checking", exc_info=True) + # something goes wrong, data will path + return True diff --git a/includes/globals.py b/includes/globals.py index f2d4e6d..0cc0c5e 100644 --- a/includes/globals.py +++ b/includes/globals.py @@ -11,16 +11,10 @@ Global variables # Global variables config = 0 script_path = "" +log_path = "" # double alarm -fms_id_old = 0 -fms_time_old = 0 - -zvei_id_old = 0 -zvei_time_old = 0 - -poc_id_old = 0 -poc_time_old = 0 +doubleList = [] # pluginLoader pluginList = {} @@ -33,3 +27,10 @@ fmsDescribtionList = {} zveiDescribtionList = {} ricDescribtionList = {} +# returns the version or build date +# function -> read only in script +def getVers(mode="vers"): + if mode == "vers": + return "2.0" + elif mode == "date": + return " 2015/07/13" diff --git a/includes/helper/__init__.py b/includes/helper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/includes/helper/configHandler.py b/includes/helper/configHandler.py new file mode 100644 index 0000000..8dfe90e --- /dev/null +++ b/includes/helper/configHandler.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- +# + +""" +little Helper to handle config data +for direct use in plugins to save code + +@author: Bastian Schroll +""" + +import logging +from includes import globals + + +def checkConfig(section=""): + """ + Reads the config option from an section and prints it to debug log + + @type section: string + @param section: Section name from config.ini + + @return: true (false if reading failed) + @exception: Exception if Error at read an debug + """ + try: + if section is not "": # read only data if section is given + logging.debug("read [%s] from config file", section) + + for key,val in globals.config.items(section): + logging.debug(" - %s = %s", key, val) + + return True + except: + logging.warning("error in config read/debug") + logging.debug("error in config read/debug", exc_info=True) + return False diff --git a/includes/helper/timeHandler.py b/includes/helper/timeHandler.py new file mode 100644 index 0000000..246a5ee --- /dev/null +++ b/includes/helper/timeHandler.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- +# + +""" +little Helper to get easy the curent date or time +for direct use in plugins to save code + +@author: Bastian Schroll +""" + +import logging + +import time + + +def curtime(format="%d.%m.%Y %H:%M:%S"): + """ + Returns formated date and/or time + see: https://docs.python.org/2/library/time.html#time.strftime + + @type format: string + @param format: Python time Format-String + + @return: Formated Time and/or Date + @exception: Exception if Error in format + """ + try: + return time.strftime(format) + except: + logging.warning("error in time-format-string") + logging.debug("error in time-format-string", exc_info=True) + + +def getDate(): + """ + Returns the date + + @return: Formated date + """ + return curtime("%d.%m.%Y") + +def getTime(): + """ + Returns the time + + @return: Formated time + """ + return curtime("%H:%M:%S") + +def getTimestamp(): + """ + Returns a integer timestamp + + @return: integer timestamp + """ + return int(time.time()) diff --git a/includes/helper/wildcardHandler.py b/includes/helper/wildcardHandler.py new file mode 100644 index 0000000..1b85b96 --- /dev/null +++ b/includes/helper/wildcardHandler.py @@ -0,0 +1,59 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- +# + +""" +little Helper to replace fast and easy the standard wildcards +for direct use in plugins to save code + +@author: Bastian Schroll +""" + +import logging + +from includes.helper import timeHandler + + +def replaceWildcards(text,data): + """ + Replace all official Wildcards with the Information from the data[] var + + @type text: string + @param text: Input text with wildcards + @type data: map + @param data: map of data (structure see interface.txt) + + @return: text with replaced wildcards + @exception: Exception if Error at replace + """ + try: + # replace date and time wildcards + text = text.replace("%TIME%", timeHandler.getTime()).replace("%DATE%", timeHandler.getDate()) + + # replace FMS data + if "fms" in data: text = text.replace("%FMS%", data["fms"]) + if "status" in data: text = text.replace("%STATUS%", data["status"]) + if "direction" in data: text = text.replace("%DIR%", data["direction"]) + if "directionText" in data: text = text.replace("%DIRT%", data["directionText"]) + if "tsi" in data: text = text.replace("%TSI%", data["tsi"]) + + # replace ZVEI data + if "zvei" in data: text = text.replace("%ZVEI%", data["zvei"]) + + # replace POC data + if "ric" in data: text = text.replace("%RIC%", data["ric"]) + if "function" in data: text = text.replace("%FUNC%", data["function"]) + if "functionChar" in data: text = text.replace("%FUNCCHAR%", data["functionChar"]) + if "msg" in data: text = text.replace("%MSG%", data["msg"]) + if "bitrate" in data: text = text.replace("%BITRATE%", str(data["bitrate"])) + + # replace description (exists by all) + if "description" in data: text = text.replace("%DESCR%", data["description"]) + + logging.debug("wildcards been replaced") + + return text + + except: + logging.warning("error in wildcard replacement") + logging.debug("error in wildcard replacement", exc_info=True) diff --git a/includes/pluginLoader.py b/includes/pluginLoader.py index 31bd097..a79a779 100644 --- a/includes/pluginLoader.py +++ b/includes/pluginLoader.py @@ -18,7 +18,7 @@ from includes import globals # Global variables def loadPlugins(): """ - Load all Plugins into globals.pluginList + Load all plugins into globals.pluginList @return: nothing @exception: Exception if insert into globals.pluginList failed @@ -27,35 +27,54 @@ def loadPlugins(): 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 + try: + # call for each Plugin the loadPlugin() Methode + plugin = loadPlugin(i) + except: + # call next plugin, if one has thrown an exception + logging.error("error loading plugin: %s", i["name"]) + logging.debug("error loading plugin: %s", i["name"], exc_info=True) + pass + else: # only call onLoad() and insert into pluginList[] if import is succesfull + + try: + # Try to call the .onLoad() routine for all active plugins + logging.debug("call %s.onLoad()", i["name"]) + plugin.onLoad() + # Add it to globals.pluginList + globals.pluginList[i["name"]] = plugin + except: + # call next plugin, if one has thrown an exception + logging.error("error calling %s.onLoad()", i["name"]) + logging.debug("error calling %s.onLoad()", exc_info=True) + pass except: - logging.exception("cannot load Plugins") + logging.error("cannot load plugins") + logging.debug("cannot load plugins", exc_info=True) + raise def getPlugins(): """ - get a Python Dict of all activeated Plugins + get a Python Dict of all activeated plugins - @return: Plugins as Python Dict - @exception: Exception if Plugin search failed + @return: plugins as Python Dict + @exception: Exception if plugin search failed """ try: - logging.debug("Search in Plugin Folder") + 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) - - # 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 # is the plugin enabled in the config-file? - try: + try: if globals.config.getint("Plugins", i): info = imp.find_module(i, [location]) plugins.append({"name": i, "info": info}) @@ -63,28 +82,32 @@ def getPlugins(): else: logging.debug("Plugin [DISABLED] %s ", i) # no entry for plugin found in config-file - except NoOptionError: - logging.warning("Plugin [NO CONF ] %s", i) + except NoOptionError: + logging.warning("Plugin [NO CONF ] %s", i) pass except: - logging.exception("Error during Plugin search") + logging.error("Error during plugin search") + logging.debug("Error during plugin search", exc_info=True) + raise return plugins def loadPlugin(plugin): """ - Imports a single Plugin + Imports a single plugin + + @type plugin: plugin Data + @param plugin: Contains the information to import a plugin + - @type plugin: Plugin Data - @param plugin: Contains the information to import a Plugin - - @return: nothing - @exception: Exception if Plugin import failed + @exception: Exception if plugin import failed """ try: - logging.debug("load Plugin: %s", plugin["name"]) + logging.debug("load plugin: %s", plugin["name"]) return imp.load_module(plugin["name"], *plugin["info"]) except: - logging.exception("cannot load Plugin: %s", plugin["name"]) \ No newline at end of file + logging.error("cannot load plugin: %s", plugin["name"]) + logging.debug("cannot load plugin: %s", plugin["name"], exc_info=True) + raise diff --git a/includes/shellHeader.py b/includes/shellHeader.py index 8d352eb..6cf4a29 100644 --- a/includes/shellHeader.py +++ b/includes/shellHeader.py @@ -1,52 +1,59 @@ -#!/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): - """ - Prints the header to the shell - - @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 +#!/usr/bin/python +# -*- coding: cp1252 -*- + +""" +Shows the header in shell if quiet mode is not active + +@author: Bastian Schroll +@author: Jens Herrmann + +@requires: none +""" + +from includes import globals + +def printHeader(args): + """ + Prints the header to the shell + + @type args: Array + @param args: All given arguments from argsparser + + @return: nothing + """ + try: + print " ____ ____ ______ __ __ __ " + print " / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ " + print " / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ " + print " / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / " + print " /_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ " + print " German BOS Information Script " + print " by Bastian Schroll, Jens Herrmann " + print "" + print "SW Version: "+globals.getVers("vers") + print "Build Date: "+globals.getVers("date") + 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!" + if args.test: + print "Test Mode!" + print "" + except: + logging.error("cannot display shell header") + logging.debug("cannot display shell header", exc_info=True) diff --git a/includes/signalHandler.py b/includes/signalHandler.py new file mode 100644 index 0000000..7afb978 --- /dev/null +++ b/includes/signalHandler.py @@ -0,0 +1,32 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- +# + +""" +TERM-Handler for use script as a daemon +In order for the Python program to exit gracefully when the TERM signal is received, +it must have a function that exits the program when signal.SIGTERM is received. + +@author: Jens Herrmann +""" + +import logging +import signal # for use as daemon +import sys # throw SystemExitException when daemon is terminated + +def sigterm_handler(_signo, _stack_frame): + """ + TERM-Handler for use script as a daemon + + @type _signo: signalnum + @param _signo: signal number + @type _stack_frame: frame object + @param _stack_frame: current stack frame + + @exception: SystemExitException when daemon is terminated + """ + logging.warning("TERM signal received") + sys.exit(0) + +# Set the handler for signal to the function handler. +signal.signal(signal.SIGTERM, sigterm_handler) diff --git a/install.sh b/install.sh index dbcac0b..9ad0026 100644 --- a/install.sh +++ b/install.sh @@ -1,20 +1,20 @@ #!/bin/sh tput clear tput civis -echo " ____ ____ ______ __ __ __ " -echo " / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ " -echo " / __ / / / /\__ \| | /| / / __ / __/ ___/ __ \ " -echo " / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / " -echo " /_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ " -echo " German BOS Information Script " -echo " by Bastian Schroll " +echo " ____ ____ ______ __ __ __ " +echo " / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ " +echo " / __ / / / /\__ \| | /| / / __ / __/ ___/ __ \ " +echo " / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / " +echo " /_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ " +echo " German BOS Information Script " +echo " by Bastian Schroll " echo "" echo "This may take a several minutes... Don't panic!" echo "" echo "Caution, script don't install a Webserver with PHP and MySQL" echo "So you have to make up manually if you want to use MySQL support" -mkdir -p ~/boswatch/install +mkdir -p ~/boswatch/install tput cup 13 15 echo "[ 1/10] [#---------]" @@ -32,7 +32,7 @@ tput cup 13 15 echo "[ 3/10] [###-------]" tput cup 15 5 echo "-> download rtl_fm......................" -cd ~/boswatch/install +cd ~/boswatch/install git clone git://git.osmocom.org/rtl-sdr.git >> ~/boswatch/install/setup_log.txt 2>&1 cd rtl-sdr/ @@ -50,7 +50,7 @@ tput cup 13 15 echo "[ 5/10] [#####-----]" tput cup 15 5 echo "-> download multimon-ng................" -cd ~/boswatch/install +cd ~/boswatch/install git clone https://github.com/EliasOenal/multimonNG.git >> ~/boswatch/install/setup_log.txt 2>&1 cd multimonNG/ @@ -68,7 +68,7 @@ tput cup 13 15 echo "[ 7/10] [#######---]" tput cup 15 5 echo "-> download MySQL Connector for Python." -cd ~/boswatch/install +cd ~/boswatch/install wget "http://dev.mysql.com/get/Downloads/Connector-Python/mysql-connector-python-1.0.9.tar.gz/from/http://cdn.mysql.com/" -O mysql-connector.tar >> ~/boswatch/install/setup_log.txt 2>&1 tar xfv mysql-connector.tar >> ~/boswatch/install/setup_log.txt 2>&1 cd mysql-connector-python* @@ -97,4 +97,4 @@ echo "# BOSWatch - blacklist the DVB drivers to avoid conflict with the SDR driv tput cup 17 1 echo "BOSWatch are now installed in ~/boswatch/" -echo "Install ready!" \ No newline at end of file +echo "Install ready!" diff --git a/plugin_test.py b/plugin_test.py deleted file mode 100644 index ce6a67a..0000000 --- a/plugin_test.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/python -# -*- coding: cp1252 -*- - -########################################################################### -# Use this as a simple Plugin Loading Tool to test your own Coded Plugins # -########################################################################### - -import logging - -import ConfigParser #for parse the config file -import os #for log mkdir -import time #timestamp for doublealarm - -from includes import globals # Global variables -from includes import pluginLoader -from includes import alarmHandler -from includes import filter - -#create new logger -logger = logging.getLogger() -logger.setLevel(logging.DEBUG) - -#set log string format -formatter = logging.Formatter('%(asctime)s - %(module)-12s [%(levelname)-8s] %(message)s', '%d.%m.%Y %H:%M:%S') - -#create a display loger -ch = logging.StreamHandler() -ch.setLevel(logging.DEBUG) #log level >= info -ch.setFormatter(formatter) -logger.addHandler(ch) - -#https://docs.python.org/2/howto/logging.html#logging-basic-tutorial -#log levels -#---------- -#debug - debug messages only for log -#info - information for normal display -#warning -#error - normal error - program goes further -#exception - error with exception message in log -#critical - critical error, program exit - -globals.script_path = os.path.dirname(os.path.abspath(__file__)) - -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("Plugins"): - logging.debug(" - %s = %s", key, val) -except: - logging.exception("cannot read config file") - - -pluginLoader.loadPlugins() - -filter.loadFilters() - - -# ----- Test Data ----- # -#typ = "FMS" -#data = {"fms":"12345678", "status":"2", "direction":"1", "tsi":"III"} - -typ = "ZVEI" -data = {"zvei":"25345"} - -#typ = "POC" -#data = {"ric":"1234567", "function":"1", "msg":"Hello World!, "bitrate":"1200"} - -while True: - try: - time.sleep(1) - - print "" - alarmHandler.processAlarm(typ,"0",data) - - except KeyboardInterrupt: - logging.warning("Keyboard Interrupt") - exit() - except: - logging.exception("unknown error") - exit() diff --git a/plugins/BosMon/BosMon.py b/plugins/BosMon/BosMon.py index 968c423..8ccb7b0 100644 --- a/plugins/BosMon/BosMon.py +++ b/plugins/BosMon/BosMon.py @@ -19,6 +19,25 @@ import base64 #for the HTTP request with User/Password from includes import globals # Global variables +from includes.helper import configHandler + +## +# +# onLoad (init) function of plugin +# will be called one time by the pluginLoader on start +# +def onLoad(): + """ + While loading the plugins by pluginLoader.loadPlugins() + this onLoad() routine is called one time for initialize the plugin + + @requires: nothing + + @return: nothing + """ + # nothing to do for this plugin + return + ## # # do BosMon-Request @@ -33,9 +52,9 @@ def bosMonRequest(httprequest, params, headers): @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 + @exception: Exception if HTTP-Request failed """ try: # @@ -43,9 +62,11 @@ def bosMonRequest(httprequest, params, headers): # httprequest.request("POST", "/telegramin/"+globals.config.get("BosMon", "bosmon_channel")+"/input.xml", params, headers) except: - logging.exception("request to BosMon failed") - else: - # + logging.error("request to BosMon failed") + logging.debug("request to BosMon failed", exc_info=True) + raise + else: + # # check HTTP-Response # httpresponse = httprequest.getresponse() @@ -63,7 +84,7 @@ 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. @@ -75,101 +96,104 @@ def run(typ,freq,data): @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 - # - logging.debug("reading config file") - try: - for key,val in globals.config.items("BosMon"): - logging.debug(" - %s = %s", key, val) - except: - logging.exception("cannot read config file") + if configHandler.checkConfig("BosMon"): #read and debug the config - try: - # - # 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) - httprequest.set_debuglevel(0) - except: - logging.exception("cannot connect to BosMon") + try: + # + # 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"), timeout=5) + # debug-level to shell (0=no debug|1) + httprequest.set_debuglevel(0) + except: + logging.error("cannot connect to BosMon") + logging.debug("cannot connect to BosMon", exc_info=True) + # Without connection, plugin couldn't work + return - else: - # - # Format given data-structure to compatible BosMon string - # - if typ == "FMS": - 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.exception("ZVEI to BosMon failed") - - elif typ == "POC": - logging.debug("Start POC to BosMon") - try: - # 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.exception("POC to BosMon failed") - else: - logging.warning("Invalid Typ: %s", typ) + # + # Format given data-structure to compatible BosMon string + # + if typ == "FMS": + 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.error("FMS to BosMon failed") + logging.debug("FMS to BosMon failed", exc_info=True) + return + + 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.debug("ZVEI to BosMon failed", exc_info=True) + return + + elif typ == "POC": + logging.debug("Start POC to BosMon") + try: + # 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.debug("POC to BosMon failed", exc_info=True) + return + + else: + logging.warning("Invalid Typ: %s", typ) + + finally: + logging.debug("close BosMon-Connection") + try: + httprequest.close() + except: + pass - finally: - logging.debug("close BosMon-Connection") - httprequest.close() - except: - logging.exception("") \ No newline at end of file + # something very mysterious + logging.error("unknown error") + logging.debug("unknown error", exc_info=True) diff --git a/plugins/MySQL/MySQL.py b/plugins/MySQL/MySQL.py index 887d314..39cab72 100644 --- a/plugins/MySQL/MySQL.py +++ b/plugins/MySQL/MySQL.py @@ -18,6 +18,25 @@ import mysql.connector from includes import globals # Global variables +from includes.helper import configHandler + +## +# +# onLoad (init) function of plugin +# will be called one time by the pluginLoader on start +# +def onLoad(): + """ + While loading the plugins by pluginLoader.loadPlugins() + this onLoad() routine is called one time for initialize the plugin + + @requires: nothing + + @return: nothing + """ + # nothing to do for this plugin + return + ## # @@ -28,7 +47,7 @@ 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 @@ -41,59 +60,53 @@ def run(typ,freq,data): @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 - """ + @return: nothing + """ try: - #ConfigParser - logging.debug("reading config file") - try: - for key,val in globals.config.items("MySQL"): - logging.debug(" - %s = %s", key, val) - except: - 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: + if configHandler.checkConfig("MySQL"): #read and debug the config + try: + # + # Connect to MySQL # - # Create and execute SQL-statement - # - logging.debug("Insert %s", typ) - - if typ == "FMS": - #data = {"fms":fms_id[0:8], "status":fms_status, "direction":fms_direction, "tsi":fms_tsi} - cursor.execute("INSERT INTO "+globals.config.get("MySQL","tableFMS")+" (time,fms,status,direction,tsi) VALUES (NOW(),%s,%s,%s,%s)",(data["fms"],data["status"],data["direction"],data["tsi"])) - - elif typ == "ZVEI": - #data = {"zvei":zvei_id} - #Don't use %s here (bug in mysql-lib with one parameter) - cursor.execute("INSERT INTO "+globals.config.get("MySQL","tableZVEI")+" (time,zvei) VALUES (NOW(),"+(data["zvei"])+")") - - elif typ == "POC": - #data = {"ric":poc_id, "function":poc_sub, "msg":poc_text} - cursor.execute("INSERT INTO "+globals.config.get("MySQL","tablePOC")+" (time,ric,funktion,text) VALUES (NOW(),%s,%s,%s)",(data["ric"],data["function"],data["msg"])) - - else: - logging.warning("Invalid Typ: %s", typ) + 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 Insert %s", typ) - - finally: - logging.debug("close MySQL") - cursor.close() - connection.close() #Close connection in every case + logging.error("cannot connect to MySQL") + logging.debug("cannot connect to MySQL", exc_info=True) + else: # Without connection, plugin couldn't work + try: + # + # Create and execute SQL-statement + # + logging.debug("Insert %s", typ) + + if typ == "FMS": + cursor.execute("INSERT INTO "+globals.config.get("MySQL","tableFMS")+" (time,fms,status,direction,directionText,tsi,description) VALUES (NOW(),%s,%s,%s,%s,%s,%s)",(data["fms"],data["status"],data["direction"],data["directionText"],data["tsi"],data["description"])) + + elif typ == "ZVEI": + cursor.execute("INSERT INTO "+globals.config.get("MySQL","tableZVEI")+" (time,zvei,description) VALUES (NOW(),%s,%s)",(data["zvei"],data["description"])) + + elif typ == "POC": + cursor.execute("INSERT INTO "+globals.config.get("MySQL","tablePOC")+" (time,ric,funktion,funktionChar,msg,bitrate,description) VALUES (NOW(),%s,%s,%s,%s,%s,%s)",(data["ric"],data["function"],data["functionChar"],data["msg"],data["bitrate"],data["description"])) + + else: + logging.warning("Invalid Typ: %s", typ) + except: + logging.error("cannot Insert %s", typ) + logging.debug("cannot Insert %s", typ, exc_info=True) + return + + finally: + logging.debug("close MySQL") + try: + cursor.close() + connection.close() #Close connection in every case + except: + pass + except: - logging.exception("unknown error") \ No newline at end of file + logging.error("unknown error") + logging.debug("unknown error", exc_info=True) diff --git a/sql/boswatch.sql b/plugins/MySQL/boswatch.sql similarity index 86% rename from sql/boswatch.sql rename to plugins/MySQL/boswatch.sql index 735804b..cf91616 100644 --- a/sql/boswatch.sql +++ b/plugins/MySQL/boswatch.sql @@ -1,3 +1,6 @@ +-- MySQL Database Structure for the BOSWatch MySQL Plugin +-- @author: Bastian Schroll + -- phpMyAdmin SQL Dump -- version 3.4.11.1deb2+deb7u1 -- http://www.phpmyadmin.net @@ -32,7 +35,9 @@ CREATE TABLE IF NOT EXISTS `bos_fms` ( `fms` varchar(8) NOT NULL, `status` varchar(1) NOT NULL, `direction` varchar(1) NOT NULL, + `directionText` text(10) NOT NULL, `tsi` varchar(3) NOT NULL, + `description` text NOT NULL, PRIMARY KEY (`ID`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; @@ -47,7 +52,10 @@ CREATE TABLE IF NOT EXISTS `bos_pocsag` ( `time` datetime NOT NULL, `ric` varchar(7) NOT NULL DEFAULT '0', `funktion` int(1) NOT NULL, - `text` text NOT NULL, + `funktionChar` text(1) NOT NULL, + `msg` text NOT NULL, + `bitrate` int(4) NOT NULL, + `description` text NOT NULL, KEY `ID` (`ID`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; @@ -61,6 +69,7 @@ CREATE TABLE IF NOT EXISTS `bos_zvei` ( `id` int(11) NOT NULL AUTO_INCREMENT, `time` datetime NOT NULL, `zvei` varchar(5) NOT NULL DEFAULT '0', + `description` text NOT NULL, PRIMARY KEY (`ID`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..260d028 --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,191 @@ +## How to Code your own plugin: + +More information and a little Tutorial coming soon! + +## 1. Plugin template +#### 1.1 General +You can find a little template plugin file in `plugins/template/template.py` But you can also take a look in all other plugins. + +A plugin must be in an seperate folder with the same name of the .py file + +#### 1.2 Plugin Init `.onLoad()` +This `.onLoad()` routine is called one time for initialize the plugin + +#### 1.3 Plugin call `.run()` +This `.run()` routine is called every time an alarm comes in + +Here are the information from BOSWatch available. See section `5. Process the data from BOSWatch` + + +## 2. Use Global Logging +#### 2.1 Init and Use +First you must import the logging module +```python +import logging # Global logger +``` +Now you can send log messages with: + +```python +logging.LOGLEVEL("MESSAGE") +``` +You must replace the word `LOGLEVEL` with one if the following `debug`, `info`, `warning` or `error` + +To use the right loglevel see next section `2.2 Choose right Loglevel` + +#### 2.2 Choose right Loglevel +`debug` +all messages to find errors and for the internal program flow. + +`info` +messages that serve only to inform the user. + +`warning` +Warnings are notes and technical errors. Never leads to terminate BOSWatch. + +`error` +An error that does not necessarily lead to end of BOSWatch, but an administrator intervention required. + +`critical` +errors leading to the end of boswatch immediate - **in plugins not allowed** (Plugin cannot crash the entire program) + + +## 3. Use config file +#### 3.1 Own configuration in config.ini +First you must set a new Section in `config.ini` +A section is between brackets. Its recommended to give the section the same name as the plugin. `[SECTION_NAME]` + +Now you can an set a unlimited number of options with its own value in these format: `OPTION = VALUE`. + +Here is the sample from the template plugin: +```python +[template] +test1 = testString +test2 = 123456 +``` + +#### 3.2 Read data from config.ini +To read yout configuration data you must import the `globals.py` where the global config-object is located: +```python +from includes import globals # Global variables +``` + +Now you can get your configuration data with: +```python +VALUE = globals.config.get("SECTION", "OPTION") #Gets any value +``` +or better, use this: +```python +VALUE = globals.config.getint("SECTION", "OPTION") #Value must be an Integer +VALUE = globals.config.getfloat("SECTION", "OPTION") #Value must be an Float +VALUE = globals.config.getboolean("SECTION", "OPTION") #Value must be an Boolean +``` + + +## 4. Global helper functions +#### 4.1 configHandler.py +First you must include the helper file +```python +from includes.helper import configHandler +``` +##### 4.1.1 `.checkConfig(section)` +This function read all options from a config section and prints it to the debug log. The return value is `true`, also the section var is empty. In case of error a `false` is returned and error printed to log. +```python +if configHandler.checkConfig("template"): #check config file + ########## User Plugin CODE ########## + pass +``` + + +#### 4.2 timeHandler.py +First you must include the helper file +```python +from includes.helper import timeHandler +``` +##### 4.2.1 `.curtime(format)` +```python +timeHandler.curtime() # returns a formated datetime string +``` +you can give the function an format string. See https://docs.python.org/2/library/time.html#time.strftime + +default (without format parameter) the function returns a date time with this format `%d.%m.%Y %H:%M:%S` +##### 4.2.2 `.getDate()` +```python +timeHandler.getDate() # returns the current date in format `%d.%m.%Y` +``` +##### 4.2.3 `.getTime()` +```python +timeHandler.getTime() # returns the current time in format `%H:%M:%S` +``` +##### 4.2.4 `.getTimestamp()` +```python +timeHandler.getTimestamp() # returns the current linux timestamp +``` + +#### 4.3 wildcardHandler.py +First you must include the helper file +```python +from includes.helper import wildcardHandler +``` +##### 4.3.1 `.replaceWildcards(text,data)` +```python +wildcardHandler.replaceWildcards(text,data) # replace all standard wildcards +``` +replace all the standard wildcards in the given text +the function needs the data[ ] var + +defined wildcards: + +**General:** +- `%TIME%` = Time (by script) +- `%DATE%` = Date (by script) +- `%DESCR%` = Description from csv-file + +**FMS:** +- `%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) + +**ZVEI:** +- `%ZVEI%` = ZVEI 5-tone Code + +**POCSAG:** +- `%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 + +## 5. Process the data from BOSWatch +Three parameters are passed during the alarm to the .run() method + +#### 5.1 typ +Thats the function of the alarm. Possible values are `FMS`, `ZVEI` or `POC` + +#### 5.2 freq +The reception frequency of the tuner in Hz + +#### 5.3 data[ ] +You can get an information with `data["INFO"]` +In the data map are the folowing informations: + +**ZVEI:** +- zvei +- description + +**FMS:** +- fms +- status +- direction +- directionText +- tsi +- description + +**POCSAG:** +- ric +- function +- functionChar +- msg +- bitrate +- description diff --git a/plugins/eMail/eMail.py b/plugins/eMail/eMail.py index 24477a5..f7c7448 100644 --- a/plugins/eMail/eMail.py +++ b/plugins/eMail/eMail.py @@ -19,12 +19,25 @@ from email.utils import make_msgid # need for confirm to RFC2822 standard from includes import globals # Global variables +from includes.helper import timeHandler # helper function +from includes.helper import configHandler + ## # -# Private helper function for a printable Timestamp +# onLoad (init) function of plugin +# will be called one time by the pluginLoader on start # -def curtime(): - return time.strftime("%Y-%m-%d %H:%M:%S") +def onLoad(): + """ + While loading the plugins by pluginLoader.loadPlugins() + this onLoad() routine is called one time for initialize the plugin + + @requires: nothing + + @return: nothing + """ + # nothing to do for this plugin + return ## # @@ -40,11 +53,11 @@ def doSendmail(server, subject, mailtext): @param subject: Subject for the eMail @type mailtext: string @param mailtext: Mailtext for the eMail - + @return: nothing @exception: Exception if smtp.sendmail failed """ - try: + try: msg = MIMEText(mailtext) msg['From'] = globals.config.get("eMail", "from") msg['To'] = globals.config.get("eMail", "to") @@ -52,9 +65,11 @@ def doSendmail(server, subject, mailtext): 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()) + server.sendmail(globals.config.get("eMail", "from"), globals.config.get("eMail", "to").split(), msg.as_string()) except: - logging.exception("send eMail failed") + logging.error("send eMail failed") + logging.debug("send eMail failed", exc_info=True) + raise ## @@ -66,7 +81,7 @@ 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. @@ -78,113 +93,115 @@ def run(typ,freq,data): @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") + if configHandler.checkConfig("eMail"): #read and debug the config - 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") + 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) - else: + # if tls is enabled, starttls + if globals.config.get("eMail", "tls"): + server.starttls() - 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") + # if user is given, login + if globals.config.get("eMail", "user"): + server.login(globals.config.get("eMail", "user"), globals.config.get("eMail", "password")) - 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") + except: + logging.error("cannot connect to eMail") + logging.debug("cannot connect to eMail", exc_info=True) + # Without connection, plugin couldn't work + return - 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() - + 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%", timeHandler.curtime("H:M:S")).replace("%DATE%", timeHandler.curtime("Y-m-d")) # 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%", timeHandler.curtime("H:M:S")).replace("%DATE%", timeHandler.curtime("Y-m-d")) # replace Wildcards + # send eMail + doSendmail(server, subject, mailtext) + except: + logging.error("%s to eMail failed", typ) + logging.debug("%s to eMail failed", typ, exc_info=True) + return + + 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%", timeHandler.curtime("H:M:S")).replace("%DATE%", timeHandler.curtime("Y-m-d")) # 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%", timeHandler.curtime("H:M:S")).replace("%DATE%", timeHandler.curtime("Y-m-d")) # replace Wildcards + # send eMail + doSendmail(server, subject, mailtext) + except: + logging.error("%s to eMail failed", typ) + logging.debug("%s to eMail failed", typ, exc_info=True) + return + + 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%", timeHandler.curtime("H:M:S")).replace("%DATE%", timeHandler.curtime("Y-m-d")) # 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%", timeHandler.curtime("H:M:S")).replace("%DATE%", timeHandler.curtime("Y-m-d")) # replace Wildcards + # send eMail + doSendmail(server, subject, mailtext) + except: + logging.error("%s to eMail failed", typ) + logging.debug("%s to eMail failed", typ, exc_info=True) + return + + else: + logging.warning("Invalid Typ: %s", typ) + + finally: + logging.debug("close eMail-Connection") + try: + server.quit() + except: + pass + except: - logging.exception("") \ No newline at end of file + # something very mysterious + logging.error("unknown error") + logging.debug("unknown error", exc_info=True) diff --git a/plugins/firEmergency/firEmergency.py b/plugins/firEmergency/firEmergency.py index 3b2a4b2..84fefa3 100644 --- a/plugins/firEmergency/firEmergency.py +++ b/plugins/firEmergency/firEmergency.py @@ -4,6 +4,9 @@ """ firEmergency-Plugin to dispatch ZVEI- and POCSAG - messages to firEmergency +firEmergency configuration: +- set input to "FMS32" at Port 5555 + @autor: Smith-fms @requires: firEmergency-Configuration has to be set in the config.ini @@ -14,6 +17,26 @@ import socket from includes import globals # Global variables +from includes.helper import configHandler + +### +# +# onLoad (init) function of plugin +# will be called one time by the pluginLoader on start +# +def onLoad(): + """ + While loading the plugins by pluginLoader.loadPlugins() + this onLoad() routine is called one time for initialize the plugin + + @requires: nothing + + @return: nothing + """ + # nothing to do for this plugin + return + + ## # # Main function of firEmergency-plugin @@ -23,7 +46,7 @@ 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) @@ -34,60 +57,64 @@ def run(typ,freq,data): @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") + if configHandler.checkConfig("firEmergency"): #read and debug the config - 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") + 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.error("cannot connect to firEmergency") + logging.debug("cannot connect to firEmergency", exc_info=True) + # Without connection, plugin couldn't work + return else: - logging.warning("Invalid Typ: %s", typ) + # + # Format given data-structure to xml-string for firEmergency + # + if typ == "FMS": + logging.debug("FMS not supported by firEmgency") - finally: - logging.debug("close firEmergency-Connection") - firSocket.close() + elif typ == "ZVEI": + logging.debug("ZVEI to firEmergency") + try: + firXML = "\n
"+data["zvei"]+"
\n"+data["description"]+"\n"+data["zvei"]+" alarmiert.\n
\n" + firSocket.send(firXML) + except: + logging.error("%s to firEmergency failed", typ) + logging.debug("%s to firEmergency failed", typ, exc_info=True) + # Without connection, plugin couldn't work + return + + elif typ == "POC": + logging.debug("POC to firEmergency") + try: + # !!! Subric+"XX" because of an Issuse in firEmergency !!! + firXML = "\n
"+data["ric"]+"
\n"+data["function"]+"XX\n"+data["description"]+"\n"+data["msg"]+"\n
\n" + firSocket.send(firXML) + except: + logging.error("%s to firEmergency failed", typ) + logging.debug("%s to firEmergency failed", typ, exc_info=True) + # Without connection, plugin couldn't work + return + + else: + logging.warning("Invalid Typ: %s", typ) + + finally: + logging.debug("close firEmergency-Connection") + try: + firSocket.close() + except: + pass except: - logging.exception("unknown error") \ No newline at end of file + logging.error("unknown error") + logging.debug("unknown error", exc_info=True) diff --git a/plugins/httpRequest/httpRequest.py b/plugins/httpRequest/httpRequest.py index da48450..fd6815b 100644 --- a/plugins/httpRequest/httpRequest.py +++ b/plugins/httpRequest/httpRequest.py @@ -9,12 +9,34 @@ httpRequest-Plugin to dispatch FMS-, ZVEI- and POCSAG - messages to an URL @requires: httpRequest-Configuration has to be set in the config.ini """ +import time 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 +from includes.helper import timeHandler +from includes.helper import wildcardHandler +from includes.helper import configHandler + +## +# +# onLoad (init) function of plugin +# will be called one time by the pluginLoader on start +# +def onLoad(): + """ + While loading the plugins by pluginLoader.loadPlugins() + this onLoad() routine is called one time for initialize the plugin + + @requires: nothing + + @return: nothing + """ + # nothing to do for this plugin + return + ## # @@ -34,68 +56,66 @@ def run(typ,freq,data): @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 - # - 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") - - try: - # - # Create URL - # - logging.debug("send %s HTTP request", typ) - - if typ == "FMS": - 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": - url = globals.config.get("httpRequest", "zvei_url") #Get URL - url = url.replace("%ZVEI%", data["zvei"]) #replace Wildcards in URL - elif typ == "POC": - 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: + if configHandler.checkConfig("httpRequest"): #read and debug the config + 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)) + # Create URL + # + if typ == "FMS": + url = globals.config.get("httpRequest", "fms_url") #Get URL + url = wildcardHandler.replaceWildcards(url, data) # replace wildcards with helper function + elif typ == "ZVEI": + url = globals.config.get("httpRequest", "zvei_url") #Get URL + url = wildcardHandler.replaceWildcards(url, data) # replace wildcards with helper function + elif typ == "POC": + url = globals.config.get("httpRequest", "poc_url") #Get URL + url = wildcardHandler.replaceWildcards(url, data) # replace wildcards with helper function + else: - logging.warning("HTTP response: %s - %s" , str(httpresponse.status), str(httpresponse.reason)) - except: #otherwise - logging.exception("cannot get HTTP response") - - finally: - logging.debug("close HTTP-Connection") - httprequest.close() - + logging.warning("Invalid Typ: %s", typ) + return + + + # + # HTTP-Request + # + logging.debug("send %s HTTP request", typ) + 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.error("cannot send HTTP request") + logging.debug("cannot send HTTP request", exc_info=True) + return + + else: + 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: #otherwise + logging.error("cannot get HTTP response") + logging.debug("cannot get HTTP response", exc_info=True) + return + + finally: + logging.debug("close HTTP-Connection") + try: + httprequest.close() + except: + pass + except: - logging.exception("unknown error") \ No newline at end of file + logging.error("unknown error") + logging.debug("unknown error", exc_info=True) diff --git a/plugins/interface.txt b/plugins/interface.txt deleted file mode 100644 index f2e8c01..0000000 --- a/plugins/interface.txt +++ /dev/null @@ -1,41 +0,0 @@ -Handover to Plugin: -typ = [FMS|ZVEI|POC] -freq = [Freq in Hz] -data = {"KEY1":"VALUE1","KEY2":"VALUE2"} - - -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 - - -Global Objects: - -1.) -import logging # Global logger -Message into Log: logging.LOGLEVEL("MESSAGE") -Loglevel: debug|info|warning|error|exception|critical - -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/plugins/jsonSocket/jsonSocket.py b/plugins/jsonSocket/jsonSocket.py new file mode 100644 index 0000000..4bb7162 --- /dev/null +++ b/plugins/jsonSocket/jsonSocket.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- + +""" +jsonSocket-Plugin to dispatch FMS-, ZVEI- and POCSAG-messages via UDP/TCP + +@author: Jens Herrmann + +@requires: jsonSocket-Configuration has to be set in the config.ini +""" + +import logging # Global logger + +import socket # for connection +import json # for data-transfer + +from includes import globals # Global variables + +from includes.helper import configHandler + +## +# +# onLoad (init) function of plugin +# will be called one time by the pluginLoader on start +# +def onLoad(): + """ + While loading the plugins by pluginLoader.loadPlugins() + this onLoad() routine is called one time for initialize the plugin + + @requires: nothing + + @return: nothing + """ + # nothing to do for this plugin + return + + +## +# +# Main function of jsonSocket-plugin +# will be called by the alarmHandler +# +def run(typ,freq,data): + """ + This function is the implementation of the jsonSocket-Plugin. + It will send the data via UDP/TCP + + The configuration for the Connection is set in the config.ini. + + @type typ: string (FMS|ZVEI|POC) + @param typ: Typ of the dataset for sending via UDP/TCP + @type data: map of data (structure see interface.txt) + @param data: Contains the parameter for dispatch to UDP. + @type freq: string + @keyword freq: frequency of the SDR Stick + + @requires: jsonSocket-Configuration has to be set in the config.ini + + @return: nothing + """ + try: + if configHandler.checkConfig("jsonSocket"): #read and debug the config + + try: + # + # initialize to socket-Server + # + # SOCK_DGRAM is the socket type to use for UDP sockets + # SOCK_STREAM is the socket type to use for TCP sockets + if globals.config.get("jsonSocket", "protocol") == "TCP": + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((globals.config.get("jsonSocket", "server"), globals.config.getint("jsonSocket", "port"))) + else: + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + except: + logging.error("cannot initialize %s-socket", globals.config.get("jsonSocket", "protocol")) + logging.debug("cannot initialize %s-socket", globals.config.get("jsonSocket", "protocol"), exc_info=True) + # Without connection, plugin couldn't work + return + + else: + # toDo is equals for all types, so only check if typ is supported + supportedTypes = ["FMS", "ZVEI", "POC"] + if typ in supportedTypes: + logging.debug("Start %s to %s", typ, globals.config.get("jsonSocket", "protocol")) + try: + # dump data to json-string + sendData = json.dumps(data) + # send data + sock.sendto(sendData, (globals.config.get("jsonSocket", "server"), globals.config.getint("jsonSocket", "port"))) + except: + logging.error("%s to %s failed", typ, globals.config.get("jsonSocket", "protocol")) + logging.debug("%s to %s failed", typ, globals.config.get("jsonSocket", "protocol"), exc_info=True) + return + + else: + logging.warning("Invalid Typ: %s", typ) + + finally: + logging.debug("close %s-Connection", globals.config.get("jsonSocket", "protocol")) + try: + sock.close() + except: + pass + + except: + # something very mysterious + logging.error("unknown error") + logging.debug("unknown error", exc_info=True) diff --git a/plugins/template/template.py b/plugins/template/template.py index 5e99e1a..148fb44 100644 --- a/plugins/template/template.py +++ b/plugins/template/template.py @@ -1,50 +1,91 @@ #!/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 +""" +template plugin to show the function and usage of plugins +feel free to edit to yout own plugin +please edit theese desciption, the @author-Tag and the @requires-Tag +For more information take a look into the other plugins +@author: Jens Herrmann +@author: Bastian Schroll + +@requires: none +""" + +# +# Imports +# import logging # Global logger - from includes import globals # Global variables +# Helper function, uncomment to use +#from includes.helper import timeHandler +#from includes.helper import wildcardHandler +from includes.helper import configHandler -def run(typ,freq,data): +## +# +# onLoad (init) function of plugin +# will be called one time by the pluginLoader on start +# +def onLoad(): + """ + While loading the plugins by pluginLoader.loadPlugins() + this onLoad() routine is called one time for initialize the plugin + + @requires: nothing + + @return: nothing + @exception: Exception if init has an fatal error so that the plugin couldn't work + + """ try: - #ConfigParser - logging.debug("reading config file") - try: - for key,val in globals.config.items("template"): - logging.debug(" - %s = %s", key, val) - except: - logging.exception("cannot read config file") - -########## User Plugin CODE ########## - if typ == "FMS": - logging.warning("%s not supported", typ) - elif typ == "ZVEI": - logging.warning("%s not supported", typ) - elif typ == "POC": - logging.warning("%s not supported", typ) - else: - logging.warning("Invalid Typ: %s", typ) -########## User Plugin CODE ########## - + ########## User onLoad CODE ########## + pass + ########## User onLoad CODE ########## except: - logging.exception("unknown error") \ No newline at end of file + logging.error("unknown error") + logging.debug("unknown error", exc_info=True) + raise + +## +# +# Main function of plugin +# will be called by the alarmHandler +# +def run(typ,freq,data): + """ + This function is the implementation of the Plugin. + + If necessary the configuration hast to be set in the config.ini. + + @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 for dispatch + @type freq: string + @keyword freq: frequency of the SDR Stick + + @requires: If necessary the configuration hast to be set in the config.ini. + + @return: nothing + @exception: nothing, make sure this function will never thrown an exception + """ + try: + if configHandler.checkConfig("template"): #read and debug the config (let empty if no config used) + + ########## User Plugin CODE ########## + if typ == "FMS": + logging.warning("%s not supported", typ) + elif typ == "ZVEI": + logging.warning("%s not supported", typ) + elif typ == "POC": + logging.warning("%s not supported", typ) + else: + logging.warning("Invalid Typ: %s", typ) + ########## User Plugin CODE ########## + + except: + logging.error("unknown error") + logging.debug("unknown error", exc_info=True) diff --git a/serverExamples/jsonSocketServer.py b/serverExamples/jsonSocketServer.py new file mode 100644 index 0000000..d659dd0 --- /dev/null +++ b/serverExamples/jsonSocketServer.py @@ -0,0 +1,148 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- +# + +""" +jsonSocketServer +This is a small example of an jsonSocketServer for receive alarm-messages from BOSWatch. +The jsonSocketServer controlls an pibrella-bord in case of received POCSAG-RIC + +Implemented functions: +- asynchronous service for alarm-sound +- green LED if jsonSocketServer is running +- green LED is blinking if Dau-Test-RIC was received +- yellow LED is blinking if our RICs is reveived with functioncode "a" +- red LED is blinking in case of an alarm (our RICs with functioncode "b") +- siren will run with the pack +- press Pibrella button to stop alarm and reset the LEDs + +@author: Jens Herrmann + +BOSWatch: https://github.com/Schrolli91/BOSWatch +Pibrella: https://github.com/pimoroni/pibrella +""" + +# no IP for server necessary +IP = "" +# listen on port +PORT = 8112 + + +import logging +import logging.handlers + +import socket # for udp-socket +import pibrella # for pi-board +import json # for data + + +# +# Eventhandler for button +# will stop the alarm and reset the LEDs +# +def button_pressed(pin): + global siren_stopped + import pibrella + pibrella.light.off() + pibrella.light.green.on() + siren_stopped = True +# load Eventhandler +pibrella.button.pressed(button_pressed) + +# +# Siren-control +# + +# normally we have no alarm, siren-control-var is True +siren_stopped = True + +# asynchronous siren: +def siren(): + import time + if siren_stopped == True: + pibrella.buzzer.stop() + return True + for x in xrange(-30,30,2): + pibrella.buzzer.note(x) + time.sleep(0.01) + for x in reversed(xrange(-30,30,2)): + pibrella.buzzer.note(x) + time.sleep(0.01) +# start asynchronous siren +pibrella.async_start('siren',siren) + + +# +# Main Program +# +try: + # Logging + myLogger = logging.getLogger() + myLogger.setLevel(logging.DEBUG) + formatter = logging.Formatter('%(asctime)s [%(levelname)-8s] %(message)s', '%d.%m.%Y %H:%M:%S') + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + ch.setFormatter(formatter) + myLogger.addHandler(ch) + + # Start TCP socket: + logging.debug("Start jsonSocketServer") + sock = socket.socket() + sock.bind((IP,PORT)) + sock.listen(2) + logging.info("jsonSocketServer runs") + pibrella.light.green.on() + + # our Alarm-RICs: + ric_alarm = [12345677, 12345676, 12345675] + + while True: + # accept connections from outside + (clientsocket, address) = sock.accept() + logging.debug("connected client: %s", address) + + # receive message as json string + json_string = clientsocket.recv( 4096 ) # buffer size is 4096 bytes + try: + # parse json + parsed_json = json.loads(json_string) + logging.debug("parsed message: %s", parsed_json) + except ValueError: + # parsing error is foolish, but we don't have to exit + logging.warning("No JSON object could be decoded: %s", json_string) + pass + else: + # DAU-Test-RIC received + if parsed_json['ric'] == "1234567": + logging.debug("POCSAG is alive") + pibrella.light.green.blink(1, 1) + + elif int(parsed_json['ric']) in ric_alarm: + logging.debug("We have do to something") + if parsed_json['functionChar'] == "a": + logging.info("-> Probealarm: %", parsed_json['ric']) + pibrella.light.yellow.blink(1, 1) + elif parsed_json['functionChar'] == "b": + logging.info("-> Alarm: %", parsed_json['ric']) + pibrella.light.red.blink(1, 1) + # change variable to False to start the siren + siren_stopped = False + +except KeyboardInterrupt: + logging.warning("Keyboard Interrupt") +except: + logging.exception("unknown error") +finally: + try: + logging.debug("socketServer shuting down") + sock.close() + logging.debug("socket closed") + logging.debug("exiting socketServer") + except: + logging.warning("failed in clean-up routine") + finally: + logging.debug("close Logging") + logging.info("socketServer exit()") + logging.shutdown() + ch.close() + exit(0) diff --git a/service/README.md b/service/README.md new file mode 100644 index 0000000..c5c8cac --- /dev/null +++ b/service/README.md @@ -0,0 +1,28 @@ +### Start BOSWatch as a daemon + +##### Changing the init script + +Lines 14 and 15 define where to find the Python script. +In this case the script expects that there is a folder `/usr/local/bin/BOSWatch` and that the script is inside there. + +Line 23 sets what user to run the script as. Using a root-user is necessary for BOSWatch. + +Line 19 sets the parameters for BOSWatch, use the same as starting BOSWatch from the shell. +We recommend to use "-u" and "-q" when you want to run BOSWatch as a daemon. +- "-u": You will find the logfiles in `/var/log/BOSWatch` +- "-q": Shows no information. Only logfiles + +##### Using the init script + +To actually use this script, put BOSWatch where you want (recommend `/usr/local/bin/BOSWatch`) +and make sure it is executable (e.g. `sudo chmod 755 boswatch.py`). +Edit the init script accordingly. Copy it into /etc/init.d using e.g. `sudo cp boswatch.sh /etc/init.d`. +Make sure the script is executable (chmod again) and make sure that it has UNIX line-endings. + +At this point you should be able to start BOSWatchcd ~/srt using the command `sudo /etc/init.d/boswatch.sh start`, +check its status with the `sudo /etc/init.d/boswatch.sh status` argument and stop it with `sudo /etc/init.d/boswatch.sh stop`. + +To make the Raspberry Pi use your init script at the right time, one more step is required: +Running the command `sudo update-rc.d boswatch.sh defaults`. +This command adds in symbolic links to the /etc/rc.x directories so that the init script is run at the default times. +You can see these links if you do `ls -l /etc/rc?.d/*boswatch.sh` diff --git a/service/boswatch.sh b/service/boswatch.sh new file mode 100755 index 0000000..bbfacba --- /dev/null +++ b/service/boswatch.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: BOSWatch +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Python Service to receive and decode German BOS Information with rtl_fm and multimon-NG +# Description: Python Service to receive and decode German BOS Information with rtl_fm and multimon-NG +### END INIT INFO + +# Change the next 3 lines to suit where you install your script and what you want to call it +DIR=/usr/local/bin/BOSWatch +DAEMON=$DIR/boswatch.py +DAEMON_NAME=boswatch + +# Add any command line options for your daemon here +DAEMON_OPTS="-f xxx -a yyy -s zz -u -q" + +# This next line determines what user the script runs as. +# Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python. +DAEMON_USER=root + +# The process ID of the script when it runs is stored here: +PIDFILE=/var/run/$DAEMON_NAME.pid + +. /lib/lsb/init-functions + +do_start () { + log_daemon_msg "Starting system $DAEMON_NAME daemon" + start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- $DAEMON_OPTS + log_end_msg $? +} +do_stop () { + log_daemon_msg "Stopping system $DAEMON_NAME daemon" + start-stop-daemon --stop --pidfile $PIDFILE --retry 10 + log_end_msg $? +} + +case "$1" in + + start|stop) + do_${1} + ;; + + restart|reload|force-reload) + do_stop + do_start + ;; + + status) + status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $? + ;; + + *) + echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}" + exit 1 + ;; + +esac +exit 0 diff --git a/testdata/rt_fm errors.txt b/testdata/rt_fm errors.txt new file mode 100644 index 0000000..3b3705e --- /dev/null +++ b/testdata/rt_fm errors.txt @@ -0,0 +1,7 @@ +Error Messages from RTL_FM + +fprintf(stderr, "Signal caught, exiting!\n"); +fprintf(stderr, "Failed to open rtlsdr device #%d.\n", dongle.dev_index); +fprintf(stderr, "Failed to open %s\n", output.filename); +fprintf(stderr, "\nUser cancel, exiting...\n"); +fprintf(stderr, "\nLibrary error %d, exiting...\n", r); diff --git a/testdata/testdata.txt b/testdata/testdata.txt new file mode 100644 index 0000000..58e5a5e --- /dev/null +++ b/testdata/testdata.txt @@ -0,0 +1,120 @@ +# Testdata for the BOSWatch Test Mode function +# Data in Multimon-NG Raw Format +# Data is alternately passed to the decoder to simulate an used Radio-Frequency + +# +# POCSAG +# ------ +# +# The following settings in config.ini are expected for POCSAG +# +# [BOSWatch] +# useDescription = 1 +# doubleFilter_ignore_entries = 10 +# doubleFilter_check_msg = 1 +# +# [POC] +# deny_ric = 7777777 +# filter_range_start = 0000005 +# filter_range_end = 8999999 +# idDescribed = 1 +# + +# bitrate +POCSAG512: Address: 1000512 Function: 1 Alpha: BOSWatch-Test ÖÄÜß: okay +POCSAG1200: Address: 1001200 Function: 1 Alpha: BOSWatch-Test: okay +POCSAG2400: Address: 1002400 Function: 1 Alpha: BOSWatch-Test: okay + +# function-code +POCSAG512: Address: 1000000 Function: 0 Alpha: BOSWatch-Test: okay +POCSAG512: Address: 1000001 Function: 1 Alpha: BOSWatch-Test: okay +POCSAG512: Address: 1000002 Function: 2 Alpha: BOSWatch-Test: okay +POCSAG512: Address: 1000003 Function: 3 Alpha: BOSWatch-Test: okay + +# german special sign +POCSAG512: Address: 1200001 Function: 1 Alpha: BOSWatch-Test ÖÄÜß: okay +POCSAG512: Address: 1200001 Function: 1 Alpha: BOSWatch-Test öäü: okay + +# witch csv +POCSAG512: Address: 1234567 Function: 1 Alpha: BOSWatch-Test: with csv + +# without csv +POCSAG1200: Address: 2345678 Function: 2 Alpha: BOSWatch-Test: without csv +POCSAG2400: Address: 3456789 Function: 3 Alpha: BOSWatch-Test: without csv + +# OHNE TEXT???? +POCSAG1200: Address: 1100000 Function: 0 +POCSAG1200: Address: 1100000 Function: 1 +POCSAG1200: Address: 1100000 Function: 2 +POCSAG1200: Address: 1100000 Function: 3 + +# duplicate with same and other msg +POCSAG1200: Address: 2000001 Function: 2 Alpha: BOSWatch-Test: second is a duplicate +POCSAG1200: Address: 2000001 Function: 2 Alpha: BOSWatch-Test: second is a duplicate +POCSAG1200: Address: 2000001 Function: 2 Alpha: BOSWatch-Testing: okay + +# duplicate in different order +POCSAG1200: Address: 2100000 Function: 2 +POCSAG1200: Address: 2100001 Function: 2 +POCSAG1200: Address: 2100002 Function: 2 +POCSAG1200: Address: 2100000 Function: 2 +POCSAG1200: Address: 2100001 Function: 2 +POCSAG1200: Address: 2100002 Function: 2 +POCSAG1200: Address: 2100000 Function: 2 Alpha: BOSWatch-Test: second is a duplicate +POCSAG1200: Address: 2100001 Function: 2 Alpha: BOSWatch-Test: second is a duplicate +POCSAG1200: Address: 2100002 Function: 2 Alpha: BOSWatch-Test: second is a duplicate +POCSAG1200: Address: 2100000 Function: 2 Alpha: BOSWatch-Test: second is a duplicate +POCSAG1200: Address: 2100001 Function: 2 Alpha: BOSWatch-Test: second is a duplicate +POCSAG1200: Address: 2100002 Function: 2 Alpha: BOSWatch-Test: second is a duplicate + +# invalid +POCSAG512: Address: 3 Function: 0 Alpha: BOSWatch-Test: okay +POCSAG512: Address: 33 Function: 0 Alpha: BOSWatch-Test: okay +POCSAG512: Address: 333 Function: 0 Alpha: BOSWatch-Test: okay +POCSAG512: Address: 3333 Function: 0 Alpha: BOSWatch-Test: okay +POCSAG512: Address: 33333 Function: 0 Alpha: BOSWatch-Test: okay +POCSAG512: Address: 333333 Function: 0 Alpha: BOSWatch-Test: okay +POCSAG512: Address: 3333333 Function: 0 Alpha: BOSWatch-Test: okay +POCSAG512: Address: 333333F Function: 0 Alpha: BOSWatch-Test: invalid +POCSAG512: Address: 333333F Function: 1 Alpha: BOSWatch-Test: invalid +POCSAG512: Address: 3333333 Function: 4 Alpha: BOSWatch-Test: invalid + +# denied +POCSAG1200: Address: 7777777 Function: 1 Alpha: BOSWatch-Test: denied + +# out of filter Range +POCSAG1200: Address: 0000004 Function: 1 Alpha: BOSWatch-Test: out of filter start +POCSAG1200: Address: 9000000 Function: 1 Alpha: BOSWatch-Test: out of filter end + +# regEx-Filter? + + +# +# FMS +# --- +# +FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 0=FZG->LST 2=I (ohneNA,ohneSIGNAL)) CRC correct +FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 1=LST->FZG 2=I (ohneNA,ohneSIGNAL)) CRC correct +FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 0=FZG->LST 2=II (ohneNA,mit SIGNAL)) CRC correct +FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 1=LST->FZG 2=III(mit NA,ohneSIGNAL)) CRC correct +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 + + +# +# ZVEI +# ---- +# + +#with csv description +ZVEI2: 12345 +#without csv description +ZVEI2: 56789 +#duplicate +ZVEI2: 56789 +#with repeat Tone +ZVEI2: 1F2F3 +#in case of invalid id +ZVEI2: 135 +#in case of a double-tone for siren n-'D's are sended +ZVEI2: DDD +ZVEI2: DDDDD diff --git a/www/config.php b/www/config.php index e78cee2..5136c40 100644 --- a/www/config.php +++ b/www/config.php @@ -9,4 +9,4 @@ $tableFMS = "bos_fms"; $tableZVEI = "bos_zvei"; $tablePOC = "bos_pocsag"; -?> \ No newline at end of file +?> diff --git a/www/index.php b/www/index.php index e7146ec..aeaa785 100644 --- a/www/index.php +++ b/www/index.php @@ -17,12 +17,12 @@ $db = new Database($dbhost, $dbuser, $dbpassword, $database, 1); //Show Error =
- BOSWatch
- [Übersicht] - [Parser] - -

+ BOSWatch
+ [�bersicht] - [Parser] + +

+ + ?>
- + - \ No newline at end of file + diff --git a/www/mysql.class.php b/www/mysql.class.php index 9343fdd..69d5fbd 100644 --- a/www/mysql.class.php +++ b/www/mysql.class.php @@ -10,9 +10,9 @@ Simple Database Class (C) by Bastian Schroll /** * Database::__construct() - * + * * Stellt eine Verbung mit der MySQL Datenbank fest - * + * * @param mixed $host Hostname des Datenbank Server * @param mixed $user Username des Datenbank Nutzers * @param mixed $password Passwort des Datenbank Nutzers @@ -40,10 +40,10 @@ Simple Database Class (C) by Bastian Schroll /** * Database::query() - * - * Führt einen MySQL Query aus - * - * @param mixed $query Auszuführender Query + * + * F�hrt einen MySQL Query aus + * + * @param mixed $query Auszuf�hrender Query * @return Result-Handler/FALSE */ function query($query) @@ -59,11 +59,11 @@ Simple Database Class (C) by Bastian Schroll /** * Database::fetchAssoc() - * - * Liefert alle gefundnen Datensätze als Assoc - * + * + * Liefert alle gefundnen Datens�tze als Assoc + * * @param mixed $result Externer Result-Handler - * @return gefundene Datensätze als Assoc + * @return gefundene Datens�tze als Assoc */ function fetchAssoc($result = null) { @@ -78,11 +78,11 @@ Simple Database Class (C) by Bastian Schroll /** * Database::count() - * - * Zählt alle gefundenen Datensätze - * + * + * Z�hlt alle gefundenen Datens�tze + * * @param mixed $result Externer Result-Handler - * @return Anzahl gefundener Datensätze + * @return Anzahl gefundener Datens�tze */ function count($result = null) { @@ -97,9 +97,9 @@ Simple Database Class (C) by Bastian Schroll /** * Database::closeConnection() - * - * Schließt die bestehende MySQL Verbindung - * + * + * Schlie�t die bestehende MySQL Verbindung + * * @return TRUE/FALSE */ function closeConnection() @@ -114,9 +114,9 @@ Simple Database Class (C) by Bastian Schroll /** * Database::error() - * + * * Gibt eine Interne Fehlermeldung aus - * + * * @param mixed $error_msg Text der Fehlermeldung * @param mixed $sql_err MySQL Fehlermeldung per mysql_error() * @return NULL @@ -131,4 +131,4 @@ Simple Database Class (C) by Bastian Schroll } } -} ?> \ No newline at end of file +} ?> diff --git a/www/tpl/content.overview.php b/www/tpl/content.overview.php index 8f4f7f6..dd09404 100644 --- a/www/tpl/content.overview.php +++ b/www/tpl/content.overview.php @@ -7,7 +7,7 @@ $Rows[] = $daten; } $tpl['fms'] = $Rows; - + //read ZVEI $db->query("SELECT id, time, zvei FROM ".$tableZVEI." ORDER BY id DESC LIMIT 50"); $Rows = array(); diff --git a/www/tpl/content.parser.php b/www/tpl/content.parser.php index 15c5adc..62a2de0 100644 --- a/www/tpl/content.parser.php +++ b/www/tpl/content.parser.php @@ -1,3 +1,3 @@ \ No newline at end of file +?> diff --git a/www/tpl/template.overview.php b/www/tpl/template.overview.php index 6fd6e2a..fa4e3e6 100644 --- a/www/tpl/template.overview.php +++ b/www/tpl/template.overview.php @@ -1,5 +1,5 @@ Last alarms for FMS and ZVEI (max. 50)

- +
Last FMS alarms @@ -11,13 +11,13 @@ Last alarms for FMS and ZVEI (max. 50)

- "; echo ""; echo ""; @@ -39,13 +39,13 @@ Last alarms for FMS and ZVEI (max. 50)

- "; echo ""; echo ""; @@ -57,7 +57,7 @@ Last alarms for FMS and ZVEI (max. 50)

?>
Richt. TKI
". $fms['id'] . "". $time . " Funktion Text
". $poc['id'] . "". $time . "
- +
Last ZVEI alarms @@ -66,13 +66,13 @@ Last alarms for FMS and ZVEI (max. 50)

- "; echo ""; echo "";
Datum - Zeit Schleife
". $zvei['id'] . "". $time . "