Merge pull request #59 from Schrolli91/develop

merge for release 2.0
This commit is contained in:
Schrolli91 2015-07-13 09:45:59 +02:00
commit 432ff35222
50 changed files with 2527 additions and 1290 deletions

3
.gitignore vendored
View file

@ -1,4 +1,5 @@
*.psd
*.pyc
*.log
config.ini
config.ini
log/

View file

@ -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.

View file

@ -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.~~

View file

@ -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)

View file

@ -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
test2 = 123456

View file

@ -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"
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"

Can't render this file because it has a wrong number of fields in line 2.

View file

@ -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"
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"

Can't render this file because it has a wrong number of fields in line 2.

View file

@ -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"
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"

Can't render this file because it has a wrong number of fields in line 2.

View file

@ -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

View file

@ -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")
logging.exception("Error in alarm processing")

View file

@ -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

29
includes/converter.py Normal file
View file

@ -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()")

View file

@ -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")
#!/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")

View file

@ -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")
logging.warning("FMS CRC incorrect")
except:
logging.error("error while decoding")
logging.debug("error while decoding", exc_info=True)

View file

@ -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('<EOT>').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('<EOT>').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)
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)

View file

@ -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)
logging.warning("No valid ZVEI: %s", zvei_id)
except:
logging.error("error while decoding")
logging.debug("error while decoding", exc_info=True)

View file

@ -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
#!/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

65
includes/doubleFilter.py Normal file
View file

@ -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)

View file

@ -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")
logging.error("Error in filter checking")
logging.debug("Error in filter checking", exc_info=True)
# something goes wrong, data will path
return True

View file

@ -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"

View file

View file

@ -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

View file

@ -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())

View file

@ -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)

View file

@ -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"])
logging.error("cannot load plugin: %s", plugin["name"])
logging.debug("cannot load plugin: %s", plugin["name"], exc_info=True)
raise

View file

@ -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")
#!/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)

32
includes/signalHandler.py Normal file
View file

@ -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)

View file

@ -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!"
echo "Install ready!"

View file

@ -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()

View file

@ -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("")
# something very mysterious
logging.error("unknown error")
logging.debug("unknown error", exc_info=True)

View file

@ -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")
logging.error("unknown error")
logging.debug("unknown error", exc_info=True)

View file

@ -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 ;

191
plugins/README.md Normal file
View file

@ -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

View file

@ -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("")
# something very mysterious
logging.error("unknown error")
logging.debug("unknown error", exc_info=True)

View file

@ -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 = "<event>\n<address>"+data["zvei"]+"</address>\n<message>"+data["zvei"]+" alarmiert.</message>\n</event>\n"
firSocket.send(firXML)
except:
logging.exception("ZVEI to firEmergency failed")
elif typ == "POC":
logging.debug("POC to firEmergency")
try:
firXML = "<event>\n<address>"+data["ric"]+"</address>\n<message>"+data["msg"]+"</message>\n</event>\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 = "<event>\n<address>"+data["zvei"]+"</address>\n<description>"+data["description"]+"</description>\n<message>"+data["zvei"]+" alarmiert.</message>\n</event>\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 = "<event>\n<address>"+data["ric"]+"</address>\n<status>"+data["function"]+"XX</status>\n<description>"+data["description"]+"</description>\n<message>"+data["msg"]+"</message>\n</event>\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")
logging.error("unknown error")
logging.debug("unknown error", exc_info=True)

View file

@ -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")
logging.error("unknown error")
logging.debug("unknown error", exc_info=True)

View file

@ -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")

View file

@ -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)

View file

@ -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")
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)

View file

@ -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)

28
service/README.md Normal file
View file

@ -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`

62
service/boswatch.sh Executable file
View file

@ -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

7
testdata/rt_fm errors.txt vendored Normal file
View file

@ -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);

120
testdata/testdata.txt vendored Normal file
View file

@ -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

View file

@ -9,4 +9,4 @@ $tableFMS = "bos_fms";
$tableZVEI = "bos_zvei";
$tablePOC = "bos_pocsag";
?>
?>

View file

@ -17,12 +17,12 @@ $db = new Database($dbhost, $dbuser, $dbpassword, $database, 1); //Show Error =
<div style="text-align: center; width: 1250px; margin: 0px auto;">
<img src="gfx/logo.png" alt="BOSWatch"><br>
<a href="index.php?overview">[Übersicht]</a> - <a href="index.php?parser">[Parser]</a>
<br><br>
<img src="gfx/logo.png" alt="BOSWatch"><br>
<a href="index.php?overview">[<EFBFBD>bersicht]</a> - <a href="index.php?parser">[Parser]</a>
<br><br>
<?php
if(isset($_GET['overview']))
{
include("tpl/content.overview.php");
@ -38,9 +38,9 @@ $db = new Database($dbhost, $dbuser, $dbpassword, $database, 1); //Show Error =
include("tpl/content.overview.php");
include("tpl/template.overview.php");
}
?>
?>
</div>
</body>
</html>
</html>

View file

@ -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<EFBFBD>hrt einen MySQL Query aus
*
* @param mixed $query Auszuf<EFBFBD>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<EFBFBD>tze als Assoc
*
* @param mixed $result Externer Result-Handler
* @return gefundene Datensätze als Assoc
* @return gefundene Datens<EFBFBD>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<EFBFBD>hlt alle gefundenen Datens<EFBFBD>tze
*
* @param mixed $result Externer Result-Handler
* @return Anzahl gefundener Datensätze
* @return Anzahl gefundener Datens<EFBFBD>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<EFBFBD>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
}
}
} ?>
} ?>

View file

@ -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();

View file

@ -1,3 +1,3 @@
<?php
?>
?>

View file

@ -1,5 +1,5 @@
Last alarms for FMS and ZVEI (max. 50)<br><br>
<div style="float: left; width: 800px;">
<b>Last FMS alarms</b>
<table border="1" style="width: 800px;">
@ -11,13 +11,13 @@ Last alarms for FMS and ZVEI (max. 50)<br><br>
<td>Richt.</td>
<td>TKI</td>
</tr>
<?php
<?php
foreach ($tpl['fms'] as $fms)
{
$time = strtotime($fms['time']);
$time = date("d.m.Y H:i:s", $time);
echo "<tr>";
echo "<td>". $fms['id'] . "</td>";
echo "<td>". $time . "</td>";
@ -39,13 +39,13 @@ Last alarms for FMS and ZVEI (max. 50)<br><br>
<td>Funktion</td>
<td>Text</td>
</tr>
<?php
<?php
foreach ($tpl['poc'] as $poc)
{
$time = strtotime($poc['time']);
$time = date("d.m.Y H:i:s", $time);
echo "<tr>";
echo "<td>". $poc['id'] . "</td>";
echo "<td>". $time . "</td>";
@ -57,7 +57,7 @@ Last alarms for FMS and ZVEI (max. 50)<br><br>
?>
</table>
</div>
<div style="float: right; width: 400px;">
<b>Last ZVEI alarms</b>
<table border="1" style="width: 400px;">
@ -66,13 +66,13 @@ Last alarms for FMS and ZVEI (max. 50)<br><br>
<td>Datum - Zeit</td>
<td>Schleife</td>
</tr>
<?php
<?php
foreach ($tpl['zvei'] as $zvei)
{
$time = strtotime($zvei['time']);
$time = date("d.m.Y H:i:s", $time);
echo "<tr>";
echo "<td>". $zvei['id'] . "</td>";
echo "<td>". $time . "</td>";