From e67a357d8ef34ae937485a54befa29be2688c363 Mon Sep 17 00:00:00 2001 From: JHCD Date: Fri, 22 May 2015 16:44:23 +0200 Subject: [PATCH] (re-)structure code - use include-files now - move gobals and other stuff to includes - extract code from boswatch.py to include-files bugfix in BosMon-plugin --- boswatch.py | 195 ++------------------ globals.py | 7 - includes/decoder.py | 120 ++++++++++++ includes/{decoder => decoders}/__init__.py | 0 includes/{decoder => decoders}/fms.py | 0 includes/{decoder => decoders}/poc.py | 0 includes/{decoder => decoders}/zvei.py | 0 includes/globals.py | 20 ++ includes/pluginHandler.py | 26 +++ pluginloader.py => includes/pluginloader.py | 3 +- includes/shellHeader.py | 32 ++++ plugins/BosMon/BosMon.py | 12 +- plugins/MySQL/MySQL.py | 5 +- plugins/httpRequest/httpRequest.py | 9 +- plugins/template/template.py | 10 +- 15 files changed, 243 insertions(+), 196 deletions(-) mode change 100644 => 100755 boswatch.py delete mode 100644 globals.py create mode 100644 includes/decoder.py rename includes/{decoder => decoders}/__init__.py (100%) rename includes/{decoder => decoders}/fms.py (100%) rename includes/{decoder => decoders}/poc.py (100%) rename includes/{decoder => decoders}/zvei.py (100%) create mode 100644 includes/globals.py create mode 100644 includes/pluginHandler.py rename pluginloader.py => includes/pluginloader.py (95%) create mode 100644 includes/shellHeader.py diff --git a/boswatch.py b/boswatch.py old mode 100644 new mode 100755 index 7e1082e..c529c2a --- a/boswatch.py +++ b/boswatch.py @@ -8,26 +8,16 @@ # For more Information see the README.md ##### Info ##### -import globals # Global variables -import pluginloader - import logging import argparse #for parse the args import ConfigParser #for parse the config file -import re #Regex for validation import os #for log mkdir -import time #timestamp for doublealarm +import time #timestamp import subprocess +from includes import globals # Global variables -def throwAlarm(typ,data): - logging.debug("[ ALARM ]") - for name, plugin in pluginList.items(): - logging.debug("call Plugin: %s", name) - plugin.run(typ,"0",data) - logging.debug("[END ALARM]") - # Programm try: try: @@ -35,6 +25,10 @@ try: #create logger globals.script_path = os.path.dirname(os.path.abspath(__file__)) + #define include-path + globals.include_path = os.path.join(globals.script_path, "includes") + + if not os.path.exists(globals.script_path+"/log/"): os.mkdir(globals.script_path+"/log/") @@ -128,54 +122,12 @@ try: ch.setLevel(logging.CRITICAL) if not args.quiet: #only if not quiet mode - 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 "" + from includes import shellHeader + shellHeader.printHeader(args) except: logging.exception("cannot display/log args") - try: - - #preload vars - logging.debug("pre-load variables") - fms_id = 0 - fms_id_old = 0 - fms_time_old = 0 - - zvei_id = 0 - zvei_id_old = 0 - zvei_time_old = 0 - - poc_id = 0 - poc_id_old = 0 - poc_time_old = 0 - - except: - logging.debug("cannot pre-load variables") + try: #read config @@ -188,17 +140,11 @@ try: except: logging.debug("cannot read config file") else: - try: - #load plugins - logging.debug("loading plugins") - pluginList = {} - for i in pluginloader.getPlugins(): - plugin = pluginloader.loadPlugin(i) - pluginList[i["name"]] = plugin - - except: - logging.exception("cannot load Plugins") + #load plugins + from includes import pluginHandler + pluginHandler.loadPlugins() + try: #start rtl_fm @@ -213,7 +159,6 @@ try: logging.exception("cannot start rtl_fm") else: try: - #start multimon logging.debug("starting multimon-ng") multimon_ng = subprocess.Popen("multimon-ng "+str(demodulation)+" -f alpha -t raw /dev/stdin - ", @@ -227,123 +172,21 @@ try: else: logging.debug("start decoding") + while True: #RAW Data from Multimon-NG #ZVEI2: 25832 #FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 0=FZG->LST2=III(mit NA,ohneSIGNAL)) CRC correct\n' - decoded = str(multimon_ng.stdout.readline()) #Get line data from multimon stdout + #decoded = str(multimon_ng.stdout.readline()) #Get line data from multimon stdout #only for develop #decoded = "ZVEI2: 25832" #decoded = "FMS: 43f314170000 (9=Rotkreuz 3=Bayern 1 Ort 0x25=037FZG 7141Status 3=Einsatz Ab 0=FZG->LST 2=III(mit NA,ohneSIGNAL)) CRC correct\n'" - #decoded = "POCSAG1200: Address: 1234567 Function: 1 Alpha: XXMSG MEfeweffsjh" - #time.sleep(1) + decoded = "POCSAG1200: Address: 1234567 Function: 1 Alpha: Hello World" + time.sleep(1) - timestamp = int(time.time())#Get Timestamp - - #FMS Decoder Section - #check FMS: -> check CRC -> validate -> check double alarm -> log - if "FMS:" in decoded: - logging.debug("recieved FMS") - - fms_service = decoded[19] #Organisation - fms_country = decoded[36] #Bundesland - fms_location = decoded[65:67] #Ort - fms_vehicle = decoded[72:76] #Fahrzeug - fms_status = decoded[84] #Status - fms_direction = decoded[101] #Richtung - fms_tsi = decoded[114:117] #Taktische Kruzinformation - - if "CRC correct" in decoded: #check CRC is correct - fms_id = fms_service+fms_country+fms_location+fms_vehicle+fms_status+fms_direction #build FMS id - if re.search("[0-9a-f]{8}[0-9a-f]{1}[01]{1}", fms_id): #if FMS is valid - if fms_id == fms_id_old and timestamp < fms_time_old + globals.config.getint("BOSWatch", "fms_double_ignore_time"): #check for double alarm - logging.info("FMS double alarm: %s within %s second(s)", fms_id_old, timestamp-fms_time_old) - fms_time_old = timestamp #in case of double alarm, fms_double_ignore_time set new - else: - logging.info("FMS:%s Status:%s Richtung:%s TKI:%s", fms_id[0:8], fms_status, fms_direction, fms_tsi) - data = {"fms":fms_id[0:8], "status":fms_status, "direction":fms_direction, "tsi":fms_tsi} - throwAlarm("FMS",data) - - fms_id_old = fms_id #save last id - fms_time_old = timestamp #save last time - else: - logging.warning("No valid FMS: %s", fms_id) - else: - logging.warning("FMS CRC incorrect") - - - #ZVEI Decoder Section - #check ZVEI: -> validate -> check double alarm -> log - if "ZVEI2:" in decoded: - logging.debug("recieved ZVEI") - - zvei_id = decoded[7:12] #ZVEI Code - if re.search("[0-9F]{5}", zvei_id): #if ZVEI is valid - if zvei_id == zvei_id_old and timestamp < zvei_time_old + globals.config.getint("BOSWatch", "zvei_double_ignore_time"): #check for double alarm - logging.info("ZVEI double alarm: %s within %s second(s)", zvei_id_old, timestamp-zvei_time_old) - zvei_time_old = timestamp #in case of double alarm, zvei_double_ignore_time set new - else: - logging.info("5-Ton: %s", zvei_id) - data = {"zvei":zvei_id} - throwAlarm("ZVEI",data) - - zvei_id_old = zvei_id #save last id - zvei_time_old = timestamp #save last time - else: - logging.warning("No valid ZVEI: %s", zvei_id) - - - #POCSAG Decoder Section - #check POCSAG -> validate -> check double alarm -> log - if "POCSAG" in decoded: - logging.debug("recieved POCSAG") - bitrate = 0 - - if "POCSAG512:" in decoded: - bitrate = 512 - poc_id = decoded[20:27] - poc_sub = decoded[39].replace("3", "4").replace("2", "3").replace("1", "2").replace("0", "1") - - elif "POCSAG1200:" in decoded: - bitrate = 1200 - poc_id = decoded[21:28] - poc_sub = decoded[40].replace("3", "4").replace("2", "3").replace("1", "2").replace("0", "1") - - elif "POCSAG2400:" in decoded: - bitrate = 2400 - poc_id = decoded[21:28] - poc_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") - 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() - else: - poc_text = "" - - if re.search("[0-9]{7}", poc_id): #if POC is valid - if int(poc_id) >= globals.config.getint("BOSWatch", "poc_filter_range_start"): - if int(poc_id) <= globals.config.getint("BOSWatch", "poc_filter_range_end"): - if poc_id == poc_id_old and timestamp < poc_time_old + globals.config.getint("BOSWatch", "poc_double_ignore_time"): #check for double alarm - logging.info("POCSAG%s double alarm: %s within %s second(s)", bitrate, poc_id_old, timestamp-poc_time_old) - poc_time_old = timestamp #in case of double alarm, poc_double_ignore_time set new - else: - logging.info("POCSAG%s: %s %s %s ", bitrate, poc_id, poc_sub, poc_text) - data = {"ric":poc_id, "function":poc_sub, "msg":poc_text, "bitrate":bitrate} - throwAlarm("POC",data) - - poc_id_old = poc_id #save last id - poc_time_old = timestamp #save last time - else: - logging.info("POCSAG%s: %s out of filter range (high)", bitrate, poc_id) - else: - logging.info("POCSAG%s: %s out of filter range (low)", bitrate, poc_id) - else: - logging.warning("No valid POCSAG%s RIC: %s", bitrate, poc_id) + from includes import decoder + decoder.decode(decoded) except KeyboardInterrupt: logging.warning("Keyboard Interrupt") diff --git a/globals.py b/globals.py deleted file mode 100644 index 1de8ac4..0000000 --- a/globals.py +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/python -# -*- coding: cp1252 -*- - - -#Global variables -config = 0 -script_path = "" diff --git a/includes/decoder.py b/includes/decoder.py new file mode 100644 index 0000000..4dd3007 --- /dev/null +++ b/includes/decoder.py @@ -0,0 +1,120 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- + +import logging +import time #timestamp for doublealarm +import re #Regex for validation + +from includes import globals # Global variables +from includes import pluginloader + + +def decode(decoded): + timestamp = int(time.time())#Get Timestamp + + #FMS Decoder Section + #check FMS: -> check CRC -> validate -> check double alarm -> log + if "FMS:" in decoded: + logging.debug("recieved FMS") + + fms_service = decoded[19] #Organisation + fms_country = decoded[36] #Bundesland + fms_location = decoded[65:67] #Ort + fms_vehicle = decoded[72:76] #Fahrzeug + fms_status = decoded[84] #Status + fms_direction = decoded[101] #Richtung + fms_tsi = decoded[114:117] #Taktische Kruzinformation + + if "CRC correct" in decoded: #check CRC is correct + fms_id = fms_service+fms_country+fms_location+fms_vehicle+fms_status+fms_direction #build FMS id + if re.search("[0-9a-f]{8}[0-9a-f]{1}[01]{1}", fms_id): #if FMS is valid + if fms_id == globals.fms_id_old and timestamp < globals.fms_time_old + globals.config.getint("BOSWatch", "fms_double_ignore_time"): #check for double alarm + logging.info("FMS double alarm: %s within %s second(s)", globals.fms_id_old, timestamp-globals.fms_time_old) + globals.fms_time_old = timestamp #in case of double alarm, fms_double_ignore_time set new + else: + logging.info("FMS:%s Status:%s Richtung:%s TKI:%s", fms_id[0:8], fms_status, fms_direction, fms_tsi) + data = {"fms":fms_id[0:8], "status":fms_status, "direction":fms_direction, "tsi":fms_tsi} + from includes import pluginHandler + pluginHandler.throwAlarm("FMS",data) + + globals.fms_id_old = fms_id #save last id + globals.fms_time_old = timestamp #save last time + else: + logging.warning("No valid FMS: %s", fms_id) + else: + logging.warning("FMS CRC incorrect") + + + #ZVEI Decoder Section + #check ZVEI: -> validate -> check double alarm -> log + if "ZVEI2:" in decoded: + logging.debug("recieved ZVEI") + + zvei_id = decoded[7:12] #ZVEI Code + if re.search("[0-9F]{5}", zvei_id): #if ZVEI is valid + if zvei_id == globals.zvei_id_old and timestamp < globals.zvei_time_old + globals.config.getint("BOSWatch", "zvei_double_ignore_time"): #check for double alarm + logging.info("ZVEI double alarm: %s within %s second(s)", globals.zvei_id_old, timestamp-globals.zvei_time_old) + globals.zvei_time_old = timestamp #in case of double alarm, zvei_double_ignore_time set new + else: + logging.info("5-Ton: %s", zvei_id) + data = {"zvei":zvei_id} + from includes import pluginHandler + pluginHandler.throwAlarm("ZVEI",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) + + + #POCSAG Decoder Section + #check POCSAG -> validate -> check double alarm -> log + if "POCSAG" in decoded: + logging.debug("recieved POCSAG") + bitrate = 0 + + if "POCSAG512:" in decoded: + bitrate = 512 + poc_id = decoded[20:27] + poc_sub = decoded[39].replace("3", "4").replace("2", "3").replace("1", "2").replace("0", "1") + + elif "POCSAG1200:" in decoded: + bitrate = 1200 + poc_id = decoded[21:28] + poc_sub = decoded[40].replace("3", "4").replace("2", "3").replace("1", "2").replace("0", "1") + + elif "POCSAG2400:" in decoded: + bitrate = 2400 + poc_id = decoded[21:28] + poc_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") + 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() + else: + poc_text = "" + + if re.search("[0-9]{7}", poc_id): #if POC is valid + if int(poc_id) >= globals.config.getint("BOSWatch", "poc_filter_range_start"): + if int(poc_id) <= globals.config.getint("BOSWatch", "poc_filter_range_end"): + if poc_id == globals.poc_id_old and timestamp < globals.poc_time_old + globals.config.getint("BOSWatch", "poc_double_ignore_time"): #check for double alarm + logging.info("POCSAG%s double alarm: %s within %s second(s)", bitrate, globals.poc_id_old, timestamp-globals.poc_time_old) + globals.poc_time_old = timestamp #in case of double alarm, poc_double_ignore_time set new + else: + logging.info("POCSAG%s: %s %s %s ", bitrate, poc_id, poc_sub, poc_text) + data = {"ric":poc_id, "function":poc_sub, "msg":poc_text, "bitrate":bitrate} + from includes import pluginHandler + pluginHandler.throwAlarm("POC",data) + + globals.poc_id_old = poc_id #save last id + globals.poc_time_old = timestamp #save last time + else: + logging.info("POCSAG%s: %s out of filter range (high)", bitrate, poc_id) + else: + logging.info("POCSAG%s: %s out of filter range (low)", bitrate, poc_id) + else: + logging.warning("No valid POCSAG%s RIC: %s", bitrate, poc_id) \ No newline at end of file diff --git a/includes/decoder/__init__.py b/includes/decoders/__init__.py similarity index 100% rename from includes/decoder/__init__.py rename to includes/decoders/__init__.py diff --git a/includes/decoder/fms.py b/includes/decoders/fms.py similarity index 100% rename from includes/decoder/fms.py rename to includes/decoders/fms.py diff --git a/includes/decoder/poc.py b/includes/decoders/poc.py similarity index 100% rename from includes/decoder/poc.py rename to includes/decoders/poc.py diff --git a/includes/decoder/zvei.py b/includes/decoders/zvei.py similarity index 100% rename from includes/decoder/zvei.py rename to includes/decoders/zvei.py diff --git a/includes/globals.py b/includes/globals.py new file mode 100644 index 0000000..d81fde7 --- /dev/null +++ b/includes/globals.py @@ -0,0 +1,20 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- + +#Global variables +config = 0 +script_path = "" +include_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 + +#pluginHandler +pluginList = {} \ No newline at end of file diff --git a/includes/pluginHandler.py b/includes/pluginHandler.py new file mode 100644 index 0000000..93d1872 --- /dev/null +++ b/includes/pluginHandler.py @@ -0,0 +1,26 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- + +import logging + +from includes import globals # Global variables +from includes import pluginloader + +def throwAlarm(typ,data): + logging.debug("[ ALARM ]") + for name, plugin in globals.pluginList.items(): + logging.debug("call Plugin: %s", name) + plugin.run(typ,"0",data) + logging.debug("[END ALARM]") + + +def loadPlugins(): + try: + #load plugins + logging.debug("loading plugins") + for i in pluginloader.getPlugins(): + plugin = pluginloader.loadPlugin(i) + globals.pluginList[i["name"]] = plugin + + except: + logging.exception("cannot load Plugins") \ No newline at end of file diff --git a/pluginloader.py b/includes/pluginloader.py similarity index 95% rename from pluginloader.py rename to includes/pluginloader.py index d3efc6b..25c66ad 100644 --- a/pluginloader.py +++ b/includes/pluginloader.py @@ -2,10 +2,11 @@ # -*- coding: cp1252 -*- import logging # Global logger -import globals # Global variables import imp import os +from includes import globals # Global variables + def getPlugins(): try: diff --git a/includes/shellHeader.py b/includes/shellHeader.py new file mode 100644 index 0000000..4387a6f --- /dev/null +++ b/includes/shellHeader.py @@ -0,0 +1,32 @@ +#!/usr/bin/python +# -*- coding: cp1252 -*- + + +def printHeader(args): + 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 "" \ No newline at end of file diff --git a/plugins/BosMon/BosMon.py b/plugins/BosMon/BosMon.py index 3c37435..bf722f0 100644 --- a/plugins/BosMon/BosMon.py +++ b/plugins/BosMon/BosMon.py @@ -2,12 +2,14 @@ # -*- coding: cp1252 -*- import logging # Global logger -import globals # Global variables import httplib #for the HTTP request import urllib #for the HTTP request with parameters import base64 #for the HTTP request with User/Password +from includes import globals # Global variables + + def run(typ,freq,data): try: #ConfigParser @@ -38,9 +40,13 @@ def run(typ,freq,data): headers['Content-type'] = "application/x-www-form-urlencoded" headers['Accept'] = "text/plain" if globals.config.get("BosMon", "bosmon_user"): + logging.debug(" Gesicherte Verbindung ") headers['Authorization'] = "Basic {0}".format(base64.b64encode("{0}:{1}".format(globals.config.get("BosMon", "bosmon_user"), globals.config.get("BosMon", "bosmon_password")))) + logging.debug(" Open HTTPConnection ") httprequest = httplib.HTTPConnection(globals.config.get("BosMon", "bosmon_server"), globals.config.get("BosMon", "bosmon_port")) - httprequest.request("POST", "/telegramin/"+bosmon_channel+"/input.xml", params, headers) + logging.debug(" Start Request ") + httprequest.request("POST", "/telegramin/"+globals.config.get("BosMon", "bosmon_channel")+"/input.xml", params, headers) + logging.debug(" Get Response: ") httpresponse = httprequest.getresponse() if str(httpresponse.status) == "200": #Check HTTP Response an print a Log or Error logging.debug("BosMon response: %s - %s", str(httpresponse.status), str(httpresponse.reason)) @@ -53,4 +59,4 @@ def run(typ,freq,data): ########## User Plugin CODE ########## except: - logging.exception("") + logging.exception("") \ No newline at end of file diff --git a/plugins/MySQL/MySQL.py b/plugins/MySQL/MySQL.py index 621b401..bbe855f 100644 --- a/plugins/MySQL/MySQL.py +++ b/plugins/MySQL/MySQL.py @@ -2,10 +2,13 @@ # -*- coding: cp1252 -*- import logging # Global logger -import globals # Global variables + import mysql import mysql.connector +from includes import globals # Global variables + + def run(typ,freq,data): try: #ConfigParser diff --git a/plugins/httpRequest/httpRequest.py b/plugins/httpRequest/httpRequest.py index c0655ed..c0614c7 100644 --- a/plugins/httpRequest/httpRequest.py +++ b/plugins/httpRequest/httpRequest.py @@ -1,9 +1,6 @@ #!/usr/bin/python # -*- coding: cp1252 -*- -import logging # Global logger -import globals # Global variables - ######### # USAGE # @@ -23,8 +20,12 @@ import globals # Global variables # usable Loglevels debug|info|warning|error|exception|critical # if you use .exception in Try:Exception: Construct, it logs the Python EX.message too +import logging # Global logger import httplib #for the HTTP request +from includes import globals # Global variables + + def run(typ,freq,data): try: #ConfigParser @@ -68,4 +69,4 @@ def run(typ,freq,data): ########## User Plugin CODE ########## except: - logging.exception("unknown error") + logging.exception("unknown error") \ No newline at end of file diff --git a/plugins/template/template.py b/plugins/template/template.py index 5a899fe..5e99e1a 100644 --- a/plugins/template/template.py +++ b/plugins/template/template.py @@ -1,9 +1,6 @@ #!/usr/bin/python # -*- coding: cp1252 -*- -import logging # Global logger -import globals # Global variables - ######### # USAGE # @@ -23,6 +20,11 @@ import globals # Global variables # usable Loglevels debug|info|warning|error|exception|critical # if you use .exception in Try:Exception: Construct, it logs the Python EX.message too +import logging # Global logger + +from includes import globals # Global variables + + def run(typ,freq,data): try: #ConfigParser @@ -45,4 +47,4 @@ def run(typ,freq,data): ########## User Plugin CODE ########## except: - logging.exception("unknown error") + logging.exception("unknown error") \ No newline at end of file