2015-07-15 00:59:54 +02:00
|
|
|
#!/usr/bin/python
|
|
|
|
|
# -*- coding: UTF-8 -*-
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
alarmMonitor
|
|
|
|
|
|
|
|
|
|
This is an alarmMonitor for receive alarm-messages from BOSWatch and show them on a touchscreen
|
|
|
|
|
The jsonSocketServer controlls an Watterott RPi-Display in case of received POCSAG-RIC
|
|
|
|
|
|
|
|
|
|
Implemented functions:
|
|
|
|
|
- show ric-description and alarm-message on display
|
2015-07-15 19:59:06 +02:00
|
|
|
- history of up to 5 alarms
|
2015-07-15 00:59:54 +02:00
|
|
|
- different colours for no alarm, test alarm and alarm
|
2015-07-15 19:59:06 +02:00
|
|
|
- playing a soundfile in case of an alarm
|
2015-07-15 00:59:54 +02:00
|
|
|
- show POCSAG is alive status (coloured clock)
|
2015-07-15 19:59:06 +02:00
|
|
|
- asynchronous threads for display control
|
|
|
|
|
- auto-turn-off display
|
2015-07-15 16:02:36 +02:00
|
|
|
- status informations
|
2015-07-17 20:26:38 +02:00
|
|
|
- rotating logfile in /var/log/alarmMonitor
|
2015-07-15 00:59:54 +02:00
|
|
|
|
|
|
|
|
@author: Jens Herrmann
|
|
|
|
|
|
|
|
|
|
BOSWatch: https://github.com/Schrolli91/BOSWatch
|
|
|
|
|
RPi-Display: https://github.com/watterott/RPi-Display
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
import logging.handlers
|
|
|
|
|
import ConfigParser
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import time
|
|
|
|
|
import socket # for socket
|
|
|
|
|
import json # for data
|
|
|
|
|
from threading import Thread
|
2015-07-15 19:59:06 +02:00
|
|
|
import pygame
|
2015-07-15 00:59:54 +02:00
|
|
|
|
|
|
|
|
import globals
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
#
|
|
|
|
|
# Logging
|
|
|
|
|
#
|
2015-07-17 20:26:38 +02:00
|
|
|
try:
|
|
|
|
|
# set/create log_path
|
|
|
|
|
log_path = "/var/log/alarmMonitor/"
|
|
|
|
|
if not os.path.exists(log_path):
|
|
|
|
|
os.mkdir(log_path)
|
|
|
|
|
|
|
|
|
|
# init logger
|
|
|
|
|
myLogger = logging.getLogger()
|
|
|
|
|
myLogger.setLevel(logging.DEBUG)
|
|
|
|
|
formatter = logging.Formatter('%(asctime)s - %(module)-15s [%(levelname)-8s] %(message)s', '%d.%m.%Y %H:%M:%S')
|
2015-07-15 00:59:54 +02:00
|
|
|
|
2015-07-17 20:26:38 +02:00
|
|
|
# display logger
|
|
|
|
|
ch = logging.StreamHandler()
|
|
|
|
|
ch.setLevel(logging.INFO)
|
|
|
|
|
#ch.setLevel(logging.DEBUG)
|
|
|
|
|
ch.setFormatter(formatter)
|
|
|
|
|
myLogger.addHandler(ch)
|
|
|
|
|
|
|
|
|
|
# fileLogger:
|
|
|
|
|
fh = logging.handlers.TimedRotatingFileHandler(log_path+"alarmMonitor.log", "midnight", interval=1, backupCount=7)
|
|
|
|
|
fh.setLevel(logging.DEBUG)
|
|
|
|
|
fh.setFormatter(formatter)
|
|
|
|
|
myLogger.addHandler(fh)
|
|
|
|
|
# start with a new logfile
|
|
|
|
|
fh.doRollover()
|
|
|
|
|
except:
|
|
|
|
|
# we couldn't work without logging -> exit
|
|
|
|
|
logging.critical("cannot initialise logging")
|
|
|
|
|
logging.debug("cannot initialise logging", exc_info=True)
|
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
|
|
|
2015-07-15 00:59:54 +02:00
|
|
|
#
|
|
|
|
|
# Read config.ini
|
|
|
|
|
#
|
|
|
|
|
try:
|
|
|
|
|
logging.debug("reading config file")
|
|
|
|
|
globals.config = ConfigParser.SafeConfigParser()
|
|
|
|
|
globals.config.read("config.ini")
|
|
|
|
|
# if given loglevel is debug:
|
2015-07-17 20:26:38 +02:00
|
|
|
logging.debug("- [AlarmMonitor]")
|
2015-07-15 00:59:54 +02:00
|
|
|
for key,val in globals.config.items("AlarmMonitor"):
|
2015-07-17 20:26:38 +02:00
|
|
|
logging.debug("-- %s = %s", key, val)
|
|
|
|
|
logging.debug("- [Display]")
|
|
|
|
|
for key,val in globals.config.items("Display"):
|
|
|
|
|
logging.debug("-- %s = %s", key, val)
|
2015-07-15 00:59:54 +02:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# set environment for display and touchscreen
|
|
|
|
|
#
|
|
|
|
|
os.environ["SDL_FBDEV"] = "/dev/fb1"
|
|
|
|
|
os.environ["SDL_MOUSEDEV"] = "/dev/input/touchscreen"
|
|
|
|
|
os.environ["SDL_MOUSEDRV"] = "TSLIB"
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# start threads
|
|
|
|
|
#
|
|
|
|
|
try:
|
2015-07-15 07:47:44 +02:00
|
|
|
from displayServices import displayPainter, autoTurnOffDisplay, eventHandler
|
2015-07-15 00:59:54 +02:00
|
|
|
globals.screenBackground = pygame.Color(globals.config.get("AlarmMonitor","colourGreen"))
|
|
|
|
|
logging.debug("Start displayPainter-thread")
|
|
|
|
|
Thread(target=displayPainter).start()
|
2015-07-17 20:26:38 +02:00
|
|
|
logging.debug("start autoTurnOffDisplay-thread")
|
2015-07-15 00:59:54 +02:00
|
|
|
Thread(target=autoTurnOffDisplay).start()
|
2015-07-17 20:26:38 +02:00
|
|
|
logging.debug("start eventHandler-thread")
|
2015-07-15 00:59:54 +02:00
|
|
|
Thread(target=eventHandler).start()
|
|
|
|
|
except:
|
2015-07-17 20:26:38 +02:00
|
|
|
# we couldn't work without helper threads -> exit
|
|
|
|
|
logging.critical("cannot start service-Threads")
|
|
|
|
|
logging.debug("cannot start service-Threads", exc_info=True)
|
2015-07-15 00:59:54 +02:00
|
|
|
exit(1)
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# start socket
|
|
|
|
|
#
|
|
|
|
|
logging.debug("Start socketServer")
|
|
|
|
|
sock = socket.socket () # TCP
|
|
|
|
|
sock.bind(("",globals.config.getint("AlarmMonitor","socketPort")))
|
|
|
|
|
sock.listen(5)
|
2015-07-17 20:26:38 +02:00
|
|
|
logging.debug("socketServer runs")
|
2015-07-15 00:59:54 +02:00
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Build Lists out of config-entries
|
|
|
|
|
#
|
2015-07-15 07:47:44 +02:00
|
|
|
logging.debug("create lists")
|
|
|
|
|
keepAliveRICs = [int(x.strip()) for x in globals.config.get("AlarmMonitor","keepAliveRICs").replace(";", ",").split(",")]
|
|
|
|
|
logging.debug("-- keepAliveRICs: %s", keepAliveRICs)
|
|
|
|
|
alarmRICs = [int(x.strip()) for x in globals.config.get("AlarmMonitor","alarmRICs").replace(";", ",").split(",")]
|
|
|
|
|
logging.debug("-- alarmRICs: %s", alarmRICs)
|
2015-07-17 20:26:38 +02:00
|
|
|
functionCharTestAlarm = [str(x.strip()) for x in globals.config.get("AlarmMonitor","functionCharTestAlarm").replace(";", ",").split(",")]
|
2015-07-15 07:47:44 +02:00
|
|
|
logging.debug("-- functionCharTestAlarm: %s", functionCharTestAlarm)
|
2015-07-17 20:26:38 +02:00
|
|
|
functionCharAlarm = [str(x.strip()) for x in globals.config.get("AlarmMonitor","functionCharAlarm").replace(";", ",").split(",")]
|
2015-07-15 07:47:44 +02:00
|
|
|
logging.debug("-- functionCharAlarm: %s", functionCharAlarm)
|
2015-07-15 00:59:54 +02:00
|
|
|
|
2015-07-15 16:02:36 +02:00
|
|
|
#
|
|
|
|
|
# try to read History from MySQL-DB
|
|
|
|
|
#
|
|
|
|
|
try:
|
2015-07-17 20:26:38 +02:00
|
|
|
if globals.config.getboolean("AlarmMonitor","loadHistory") == True:
|
|
|
|
|
import mysql.connector
|
|
|
|
|
|
|
|
|
|
for key,val in globals.config.items("MySQL"):
|
|
|
|
|
logging.debug("-- %s = %s", key, val)
|
|
|
|
|
|
|
|
|
|
# Connect to DB
|
|
|
|
|
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"), charset='utf8')
|
|
|
|
|
cursor = connection.cursor()
|
|
|
|
|
logging.debug("MySQL connected")
|
|
|
|
|
|
|
|
|
|
# read countKeepAlive
|
|
|
|
|
# precondition: keepAliveRICs set
|
|
|
|
|
if (len(keepAliveRICs) > 0):
|
|
|
|
|
sql = "SELECT COUNT(*) FROM "+globals.config.get("MySQL","tablePOC")+" WHERE ric IN ("+globals.config.get("AlarmMonitor","keepAliveRICs")+")"
|
|
|
|
|
cursor.execute(sql)
|
|
|
|
|
result = int(cursor.fetchone()[0])
|
|
|
|
|
if result > 0:
|
|
|
|
|
globals.countKeepAlive = result
|
|
|
|
|
logging.debug("-- countKeepAlive: %s", globals.countKeepAlive)
|
|
|
|
|
|
|
|
|
|
# read countAlarm
|
|
|
|
|
# precondition: alarmRics and functionChar set
|
|
|
|
|
if (len(alarmRICs) > 0) and (len(functionCharAlarm) > 0):
|
|
|
|
|
sql = "SELECT COUNT(*) FROM "+globals.config.get("MySQL","tablePOC")+" WHERE ric IN ("+globals.config.get("AlarmMonitor","alarmRICs")+")"
|
|
|
|
|
if len(functionCharAlarm) == 1:
|
|
|
|
|
sql += " AND functionChar IN ('" + functionCharAlarm[0] + "')"
|
|
|
|
|
elif len(functionCharAlarm) > 1:
|
|
|
|
|
sql += " AND functionChar IN " + str(tuple(functionCharAlarm))
|
|
|
|
|
cursor.execute(sql)
|
|
|
|
|
result = int(cursor.fetchone()[0])
|
|
|
|
|
if result > 0:
|
|
|
|
|
globals.countAlarm = result
|
|
|
|
|
logging.debug("-- countAlarm: %s", globals.countAlarm)
|
|
|
|
|
|
|
|
|
|
# read countTestAlarm
|
|
|
|
|
# precondition: alarmRics and functionCharTestAlarm set
|
|
|
|
|
if (len(alarmRICs) > 0) and (len(functionCharTestAlarm) > 0):
|
|
|
|
|
sql = "SELECT COUNT(*) FROM "+globals.config.get("MySQL","tablePOC")+" WHERE ric IN ("+globals.config.get("AlarmMonitor","alarmRICs")+")"
|
|
|
|
|
if len(functionCharTestAlarm) == 1:
|
|
|
|
|
sql += " AND functionChar IN ('" + functionCharTestAlarm[0] + "')"
|
|
|
|
|
elif len(functionCharTestAlarm) > 1:
|
|
|
|
|
sql += " AND functionChar IN " + str(tuple(functionCharTestAlarm))
|
|
|
|
|
cursor.execute(sql)
|
|
|
|
|
result = int(cursor.fetchone()[0])
|
|
|
|
|
if result > 0:
|
|
|
|
|
globals.countTestAlarm = result
|
|
|
|
|
logging.debug("-- countTestAlarm: %s", globals.countTestAlarm)
|
|
|
|
|
|
|
|
|
|
# read the last 5 events in reverse order
|
|
|
|
|
# precondition: alarmRics and (functionChar or functionCharTestAlarm) set
|
|
|
|
|
if (len(alarmRICs) > 0) and ((len(functionCharAlarm) > 0) or (len(functionCharTestAlarm) > 0)):
|
|
|
|
|
sql = "SELECT UNIX_TIMESTAMP(time), ric, functionChar, msg, description FROM "+globals.config.get("MySQL","tablePOC")
|
|
|
|
|
sql += " WHERE ric IN ("+globals.config.get("AlarmMonitor","alarmRICs")+")"
|
|
|
|
|
functionChar = functionCharAlarm + functionCharTestAlarm
|
|
|
|
|
if len(functionChar) == 1:
|
|
|
|
|
sql += " AND functionChar IN ('" + functionChar[0] + "')"
|
|
|
|
|
elif len(functionChar) > 1:
|
|
|
|
|
sql += " AND functionChar IN " + str(tuple(functionChar))
|
|
|
|
|
sql += " ORDER BY time DESC LIMIT 0,5"
|
|
|
|
|
cursor.execute(sql)
|
|
|
|
|
# reverse sort into history data
|
|
|
|
|
for (timestamp, ric, functionChar, msg, description) in reversed(cursor.fetchall()):
|
|
|
|
|
data = {}
|
|
|
|
|
data['timestamp'] = timestamp
|
|
|
|
|
data['ric'] = ric
|
|
|
|
|
data['functionChar'] = functionChar
|
|
|
|
|
data['msg'] = msg
|
|
|
|
|
data['description'] = description
|
|
|
|
|
globals.alarmHistory.append(data)
|
|
|
|
|
logging.debug("-- history data loaded: %s", len(globals.alarmHistory))
|
|
|
|
|
|
|
|
|
|
logging.info("history loaded from database")
|
2015-07-15 16:02:36 +02:00
|
|
|
# if db is enabled
|
|
|
|
|
pass
|
|
|
|
|
except:
|
|
|
|
|
# error, but we could work without history
|
2015-07-17 20:26:38 +02:00
|
|
|
logging.error("cannot load history from MySQL")
|
|
|
|
|
logging.debug("cannot load history from MySQL", exc_info=True)
|
2015-07-15 16:02:36 +02:00
|
|
|
pass
|
2015-07-17 20:26:38 +02:00
|
|
|
finally:
|
|
|
|
|
try:
|
|
|
|
|
cursor.close()
|
|
|
|
|
connection.close()
|
|
|
|
|
logging.debug("MySQL closed")
|
|
|
|
|
except:
|
|
|
|
|
pass
|
2015-07-15 16:02:36 +02:00
|
|
|
|
2015-07-15 19:59:06 +02:00
|
|
|
#
|
|
|
|
|
# initialise alarm sound
|
|
|
|
|
#
|
|
|
|
|
alarmSound = False
|
|
|
|
|
try:
|
|
|
|
|
if globals.config.getboolean("AlarmMonitor","playSound") == True:
|
|
|
|
|
if not globals.config.get("AlarmMonitor","soundFile") == "":
|
|
|
|
|
pygame.mixer.init()
|
|
|
|
|
alarmSound = pygame.mixer.Sound(globals.config.get("AlarmMonitor","soundFile"))
|
|
|
|
|
logging.info("alarm with sound")
|
|
|
|
|
except:
|
|
|
|
|
# error, but we could work without sound
|
|
|
|
|
logging.error("cannot initialise alarm sound")
|
|
|
|
|
logging.debug("cannot initialise alarm sound", exc_info=True)
|
|
|
|
|
pass
|
|
|
|
|
|
2015-07-15 16:02:36 +02:00
|
|
|
globals.startTime = int(time.time())
|
2015-07-17 20:26:38 +02:00
|
|
|
logging.info("alarmMonitor started - on standby")
|
2015-07-15 16:02:36 +02:00
|
|
|
|
2015-07-15 00:59:54 +02:00
|
|
|
#
|
|
|
|
|
# Main Program
|
|
|
|
|
# (Threads will set abort to True if an error occurs)
|
|
|
|
|
#
|
|
|
|
|
while globals.abort == False:
|
|
|
|
|
# accept connections from outside
|
|
|
|
|
(clientsocket, address) = sock.accept()
|
|
|
|
|
logging.debug("connected client: %s", address)
|
|
|
|
|
|
|
|
|
|
# recv message as json_string
|
|
|
|
|
json_string = clientsocket.recv( 4096 ) # buffer size is 1024 bytes
|
|
|
|
|
try:
|
|
|
|
|
# parsing jason
|
|
|
|
|
parsed_json = json.loads(json_string)
|
|
|
|
|
logging.debug("parsed message: %s", parsed_json)
|
|
|
|
|
except ValueError:
|
|
|
|
|
# we will ignore waste in json_string
|
|
|
|
|
logging.warning("No JSON object could be decoded: %s", json_string)
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
try:
|
2015-07-15 07:47:44 +02:00
|
|
|
logging.debug("Alarmmessage arrived")
|
|
|
|
|
logging.debug("-- ric: %s", parsed_json['ric'])
|
|
|
|
|
logging.debug("-- functionChar: %s", parsed_json['functionChar'])
|
2015-07-15 16:02:36 +02:00
|
|
|
|
|
|
|
|
# current time for this loop:
|
|
|
|
|
curtime = int(time.time())
|
|
|
|
|
|
2015-07-15 00:59:54 +02:00
|
|
|
# keep alive calculation with additional RICs
|
2015-07-15 16:02:36 +02:00
|
|
|
if int(parsed_json['ric']) in keepAliveRICs:
|
2015-07-15 00:59:54 +02:00
|
|
|
logging.info("POCSAG is alive")
|
2015-07-15 16:02:36 +02:00
|
|
|
globals.lastAlarm = curtime
|
|
|
|
|
globals.countKeepAlive += 1
|
2015-07-15 00:59:54 +02:00
|
|
|
|
|
|
|
|
# (test) alarm processing
|
2015-07-15 16:02:36 +02:00
|
|
|
elif int(parsed_json['ric']) in alarmRICs:
|
2015-07-15 00:59:54 +02:00
|
|
|
if parsed_json['functionChar'] in functionCharTestAlarm:
|
2015-07-17 20:26:38 +02:00
|
|
|
logging.info("--> Probealarm: %s", parsed_json['ric'])
|
2015-07-15 00:59:54 +02:00
|
|
|
globals.screenBackground = pygame.Color(globals.config.get("AlarmMonitor","colourYellow"))
|
2015-07-15 16:02:36 +02:00
|
|
|
globals.countTestAlarm += 1
|
2015-07-15 00:59:54 +02:00
|
|
|
elif parsed_json['functionChar'] in functionCharAlarm:
|
2015-07-17 20:26:38 +02:00
|
|
|
logging.info("--> Alarm: %s", parsed_json['ric'])
|
2015-07-15 00:59:54 +02:00
|
|
|
globals.screenBackground = pygame.Color(globals.config.get("AlarmMonitor","colourRed"))
|
2015-07-15 16:02:36 +02:00
|
|
|
globals.countAlarm += 1
|
2015-07-15 00:59:54 +02:00
|
|
|
|
|
|
|
|
# forward data to alarmMonitor
|
|
|
|
|
globals.data = parsed_json
|
2015-07-15 16:02:36 +02:00
|
|
|
globals.data['timestamp'] = curtime
|
2015-07-17 20:26:38 +02:00
|
|
|
logging.debug("-- data: %s", parsed_json)
|
2015-07-15 16:02:36 +02:00
|
|
|
# save 5 alarm history entries
|
|
|
|
|
globals.alarmHistory.append(globals.data)
|
|
|
|
|
if len(globals.alarmHistory) > 5:
|
|
|
|
|
globals.alarmHistory.pop(0)
|
2015-07-15 00:59:54 +02:00
|
|
|
# update lastAlarm for keep alive calculation
|
2015-07-15 16:02:36 +02:00
|
|
|
globals.lastAlarm = curtime
|
2015-07-15 00:59:54 +02:00
|
|
|
# enable display for n seconds:
|
2015-07-15 16:02:36 +02:00
|
|
|
globals.enableDisplayUntil = curtime + globals.config.getint("AlarmMonitor","showAlarmTime")
|
2015-07-15 00:59:54 +02:00
|
|
|
# tell alarm-thread to turn on the display
|
2015-07-15 16:02:36 +02:00
|
|
|
globals.navigation = "alarmPage"
|
2015-07-15 00:59:54 +02:00
|
|
|
globals.showDisplay = True;
|
|
|
|
|
|
2015-07-15 19:59:06 +02:00
|
|
|
# play alarmSound...
|
|
|
|
|
if not alarmSound == False:
|
|
|
|
|
# ... but only one per time...
|
|
|
|
|
if pygame.mixer.get_busy() == False:
|
|
|
|
|
alarmSound.play()
|
|
|
|
|
logging.debug("sound started")
|
|
|
|
|
|
2015-07-15 00:59:54 +02:00
|
|
|
except KeyError:
|
|
|
|
|
# we will ignore waste in json_string
|
|
|
|
|
logging.warning("No RIC found: %s", json_string)
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
logging.warning("Keyboard Interrupt")
|
2015-07-17 20:26:38 +02:00
|
|
|
exit(0)
|
|
|
|
|
except SystemExit:
|
|
|
|
|
logging.warning("SystemExit received")
|
|
|
|
|
exit(0)
|
2015-07-15 00:59:54 +02:00
|
|
|
except:
|
|
|
|
|
logging.exception("unknown error")
|
|
|
|
|
finally:
|
|
|
|
|
try:
|
|
|
|
|
logging.info("socketServer shuting down")
|
|
|
|
|
globals.running = False
|
|
|
|
|
sock.close()
|
|
|
|
|
logging.debug("socket closed")
|
2015-07-17 20:26:38 +02:00
|
|
|
if not alarmSound == False:
|
|
|
|
|
pygame.mixer.quit()
|
|
|
|
|
logging.debug("mixer closed")
|
|
|
|
|
if connection:
|
|
|
|
|
connection.close()
|
|
|
|
|
logging.debug("MySQL closed")
|
|
|
|
|
time.sleep(0.5)
|
2015-07-15 00:59:54 +02:00
|
|
|
logging.debug("exiting socketServer")
|
|
|
|
|
except:
|
|
|
|
|
logging.warning("failed in clean-up routine")
|
|
|
|
|
finally:
|
|
|
|
|
# Close Logging
|
|
|
|
|
logging.debug("close Logging")
|
|
|
|
|
logging.info("socketServer exit()")
|
|
|
|
|
logging.shutdown()
|
|
|
|
|
ch.close()
|