mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-01-31 12:49:34 +00:00
2acb538cbb
EBUS: Verschoben in contrib/pahenning Roomba: Verschoben in contrib/pahenning git-svn-id: https://svn.fhem.de/fhem/trunk@26128 2b470e98-0d58-463d-a4d8-8e2adae1ed80
386 lines
16 KiB
Python
386 lines
16 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
logger.debug("%s loaded", __name__)
|
|
|
|
import threading
|
|
import time # used by: fire_event_synchron
|
|
from inspect import isfunction, ismethod # used by: register_action
|
|
import string, random # used by event_id
|
|
|
|
import sqlite3
|
|
import os
|
|
|
|
from base import SingleAction
|
|
import doorpi
|
|
|
|
class EnumWaitSignalsClass():
|
|
WaitToFinish = True
|
|
WaitToEnd = True
|
|
sync = True
|
|
syncron = True
|
|
|
|
DontWaitToFinish = False
|
|
DontWaitToEnd = False
|
|
async = False
|
|
asyncron = False
|
|
EnumWaitSignals = EnumWaitSignalsClass()
|
|
|
|
ONTIME = 'OnTime'
|
|
|
|
def id_generator(size = 6, chars = string.ascii_uppercase + string.digits):
|
|
return ''.join(random.choice(chars) for _ in range(size))
|
|
|
|
class EventLog(object):
|
|
|
|
_db = False
|
|
|
|
#doorpi.DoorPi().conf.get_string_parsed('DoorPi', 'eventlog', '!BASEPATH!/conf/eventlog.db')
|
|
def __init__(self, file_name):
|
|
|
|
if not file_name: return
|
|
try:
|
|
if not os.path.exists(os.path.dirname(file_name)):
|
|
logger.info('Path %s does not exist - creating it now', os.path.dirname(file_name))
|
|
os.makedirs(os.path.dirname(file_name))
|
|
#https://docs.python.org/2/library/sqlite3.html#sqlite3.connect
|
|
self._db = sqlite3.connect(
|
|
database = file_name,
|
|
timeout = 1,
|
|
check_same_thread = False
|
|
)
|
|
|
|
self.execute_sql('''
|
|
CREATE TABLE IF NOT EXISTS event_log (
|
|
event_id TEXT,
|
|
fired_by TEXT,
|
|
event_name TEXT,
|
|
start_time REAL,
|
|
additional_infos TEXT
|
|
);'''
|
|
)
|
|
self.execute_sql('''
|
|
CREATE TABLE IF NOT EXISTS action_log (
|
|
event_id TEXT,
|
|
action_name TEXT,
|
|
start_time REAL,
|
|
action_result TEXT
|
|
);'''
|
|
)
|
|
except:
|
|
logger.error('error to create event_db')
|
|
|
|
#-- CODE by PAH
|
|
def purge_logs(self, period = 1):
|
|
try:
|
|
limit=int(time.time()-period*86400)
|
|
logger.info('purge event and action log older than %s days => limit = %s',period,limit)
|
|
sql_statement = '''
|
|
DELETE FROM event_log
|
|
WHERE (start_time < '{limit}');'''.format(limit = limit)
|
|
self.execute_sql(sql_statement)
|
|
sql_statement = '''
|
|
DELETE FROM action_log
|
|
WHERE (start_time < '{limit}');'''.format(limit = limit)
|
|
self.execute_sql(sql_statement)
|
|
self._db.commit()
|
|
return "0"
|
|
except Exception as exp:
|
|
logger.exception(exp)
|
|
return "-1"
|
|
#-- END CODE by PAH
|
|
|
|
def get_event_log_entries_count(self, filter = ''):
|
|
logger.debug('request event logs count with filter %s', filter)
|
|
try:
|
|
return self.execute_sql('''
|
|
SELECT COUNT(*)
|
|
FROM event_log
|
|
WHERE event_id LIKE '%{filter}%'
|
|
OR fired_by LIKE '%{filter}%'
|
|
OR event_name LIKE '%{filter}%'
|
|
OR start_time LIKE '%{filter}%'
|
|
'''.format(filter = filter)).fetchone()[0]
|
|
except Exception as exp:
|
|
logger.exception(exp)
|
|
return "-1"
|
|
|
|
def get_event_log_entries(self, max_count = 100, filter = ''):
|
|
logger.debug('request last %s event logs with filter %s', max_count, filter)
|
|
return_object = []
|
|
sql_statement = '''
|
|
SELECT
|
|
event_id,
|
|
fired_by,
|
|
event_name,
|
|
start_time,
|
|
additional_infos
|
|
FROM event_log
|
|
WHERE event_id LIKE '%{filter}%'
|
|
OR fired_by LIKE '%{filter}%'
|
|
OR event_name LIKE '%{filter}%'
|
|
OR start_time LIKE '%{filter}%'
|
|
ORDER BY start_time DESC
|
|
LIMIT {max_count}'''.format(max_count = max_count, filter = filter)
|
|
|
|
for single_row in self.execute_sql(sql_statement):
|
|
return_object.append({
|
|
'event_id': single_row[0],
|
|
'fired_by': single_row[1],
|
|
'event_name': single_row[2],
|
|
'start_time': single_row[3],
|
|
'additional_infos': single_row[4]
|
|
})
|
|
return return_object
|
|
|
|
def execute_sql(self, sql):
|
|
if not self._db: return
|
|
#logger.trace('fire sql: %s', sql)
|
|
return self._db.execute(sql)
|
|
|
|
def insert_event_log(self, event_id, fired_by, event_name, start_time, additional_infos):
|
|
sql_statement = '''
|
|
INSERT INTO event_log VALUES (
|
|
"{event_id}","{fired_by}","{event_name}",{start_time},"{additional_infos}"
|
|
);
|
|
'''.format(
|
|
event_id = event_id,
|
|
fired_by = fired_by.replace('"', "'"),
|
|
event_name = event_name.replace('"', "'"),
|
|
start_time = start_time,
|
|
additional_infos = str(additional_infos).replace('"', "'")
|
|
)
|
|
self.execute_sql(sql_statement)
|
|
#-- CODE by PAH
|
|
try: self._db.commit()
|
|
except: pass
|
|
|
|
def insert_action_log(self, event_id, action_name, start_time, action_result):
|
|
sql_statement = '''
|
|
INSERT INTO action_log VALUES (
|
|
"{event_id}","{action_name}",{start_time},"{action_result}"
|
|
);
|
|
'''.format(
|
|
event_id = event_id,
|
|
action_name = action_name.replace('"', "'"),
|
|
start_time = start_time,
|
|
action_result = str(action_result).replace('"', "'")
|
|
)
|
|
self.execute_sql(sql_statement)
|
|
#-- CODE by PAH
|
|
#try: self._db.commit()
|
|
#except: pass
|
|
|
|
def update_event_log(self):
|
|
pass
|
|
|
|
def destroy(self):
|
|
try: self._db.close()
|
|
except: pass
|
|
|
|
__del__ = destroy
|
|
|
|
class EventHandler:
|
|
|
|
__Sources = [] # Auflistung Sources
|
|
__Events = {} # Zuordnung Event zu Sources (1 : n)
|
|
__Actions = {} # Zuordnung Event zu Actions (1: n)
|
|
|
|
__additional_informations = {}
|
|
|
|
@property
|
|
def event_history(self): return self.db.get_event_log_entries()
|
|
|
|
@property
|
|
def sources(self): return self.__Sources
|
|
@property
|
|
def events(self): return self.__Events
|
|
@property
|
|
def events_by_source(self):
|
|
events_by_source = {}
|
|
for event in self.events:
|
|
for source in self.events[event]:
|
|
if source in events_by_source:
|
|
events_by_source[source].append(event)
|
|
else:
|
|
events_by_source[source] = [event]
|
|
return events_by_source
|
|
@property
|
|
def actions(self): return self.__Actions
|
|
@property
|
|
def threads(self): return threading.enumerate()
|
|
@property
|
|
def idle(self): return len(self.threads) - 1 is 0
|
|
@property
|
|
def additional_informations(self): return self.__additional_informations
|
|
|
|
def __init__(self):
|
|
db_path = doorpi.DoorPi().config.get_string_parsed('DoorPi', 'eventlog', '!BASEPATH!/conf/eventlog.db')
|
|
self.db = EventLog(db_path)
|
|
|
|
__destroy = False
|
|
|
|
def destroy(self, force_destroy = False):
|
|
self.__destroy = True
|
|
self.db.destroy()
|
|
|
|
def register_source(self, event_source):
|
|
if event_source not in self.__Sources:
|
|
self.__Sources.append(event_source)
|
|
logger.debug("event_source %s was added", event_source)
|
|
|
|
def register_event(self, event_name, event_source):
|
|
silent = ONTIME in event_name
|
|
if not silent: logger.trace("register Event %s from %s ", event_name, event_source)
|
|
self.register_source(event_source)
|
|
if event_name not in self.__Events:
|
|
self.__Events[event_name] = [event_source]
|
|
if not silent: logger.trace("added event_name %s and registered source %s", event_name, event_source)
|
|
elif event_source not in self.__Events[event_name]:
|
|
self.__Events[event_name].append(event_source)
|
|
if not silent: logger.trace("added event_source %s to existing event %s", event_source, event_name)
|
|
else:
|
|
if not silent: logger.trace("nothing to do - event %s from source %s is already known", event_name, event_source)
|
|
|
|
def fire_event(self, event_name, event_source, syncron = False, kwargs = None):
|
|
if syncron is False: return self.fire_event_asynchron(event_name, event_source, kwargs)
|
|
else: return self.fire_event_synchron(event_name, event_source, kwargs)
|
|
|
|
def fire_event_asynchron(self, event_name, event_source, kwargs = None):
|
|
silent = ONTIME in event_name
|
|
if self.__destroy and not silent: return False
|
|
if not silent: logger.trace("fire Event %s from %s asyncron", event_name, event_source)
|
|
return threading.Thread(
|
|
target = self.fire_event_synchron,
|
|
args = (event_name, event_source, kwargs),
|
|
name = "%s from %s" % (event_name, event_source)
|
|
).start()
|
|
|
|
def fire_event_asynchron_daemon(self, event_name, event_source, kwargs = None):
|
|
logger.trace("fire Event %s from %s asyncron and as daemons", event_name, event_source)
|
|
t = threading.Thread(
|
|
target = self.fire_event_synchron,
|
|
args = (event_name, event_source, kwargs),
|
|
name = "daemon %s from %s" % (event_name, event_source)
|
|
)
|
|
t.daemon = True
|
|
t.start()
|
|
|
|
def fire_event_synchron(self, event_name, event_source, kwargs = None):
|
|
silent = ONTIME in event_name
|
|
if self.__destroy and not silent: return False
|
|
|
|
event_fire_id = id_generator()
|
|
start_time = time.time()
|
|
if not silent: self.db.insert_event_log(event_fire_id, event_source, event_name, start_time, kwargs)
|
|
|
|
if event_source not in self.__Sources:
|
|
logger.warning('source %s unknown - skip fire_event %s', event_source, event_name)
|
|
return "source unknown"
|
|
if event_name not in self.__Events:
|
|
logger.warning('event %s unknown - skip fire_event %s from %s', event_name, event_name, event_source)
|
|
return "event unknown"
|
|
if event_source not in self.__Events[event_name]:
|
|
logger.warning('source %s unknown for this event - skip fire_event %s from %s', event_name, event_name, event_source)
|
|
return "source unknown for this event"
|
|
if event_name not in self.__Actions:
|
|
if not silent: logger.debug('no actions for event %s - skip fire_event %s from %s', event_name, event_name, event_source)
|
|
return "no actions for this event"
|
|
|
|
if kwargs is None: kwargs = {}
|
|
kwargs.update({
|
|
'last_fired': str(start_time),
|
|
'last_fired_from': event_source,
|
|
'event_fire_id': event_fire_id
|
|
})
|
|
|
|
self.__additional_informations[event_name] = kwargs
|
|
if 'last_finished' not in self.__additional_informations[event_name]:
|
|
self.__additional_informations[event_name]['last_finished'] = None
|
|
|
|
if 'last_duration' not in self.__additional_informations[event_name]:
|
|
self.__additional_informations[event_name]['last_duration'] = None
|
|
|
|
if not silent: logger.debug("[%s] fire for event %s this actions %s ", event_fire_id, event_name, self.__Actions[event_name])
|
|
for action in self.__Actions[event_name]:
|
|
if not silent: logger.trace("[%s] try to fire action %s", event_fire_id, action)
|
|
try:
|
|
result = action.run(silent)
|
|
if not silent: self.db.insert_action_log(event_fire_id, action.name, start_time, result)
|
|
if action.single_fire_action is True: del action
|
|
except SystemExit as exp:
|
|
logger.info('[%s] Detected SystemExit and shutdown DoorPi (Message: %s)', event_fire_id, exp)
|
|
doorpi.DoorPi().destroy()
|
|
except KeyboardInterrupt as exp:
|
|
logger.info("[%s] Detected KeyboardInterrupt and shutdown DoorPi (Message: %s)", event_fire_id, exp)
|
|
doorpi.DoorPi().destroy()
|
|
except:
|
|
logger.exception("[%s] error while fire action %s for event_name %s", event_fire_id, action, event_name)
|
|
if not silent: logger.trace("[%s] finished fire_event for event_name %s", event_fire_id, event_name)
|
|
self.__additional_informations[event_name]['last_finished'] = str(time.time())
|
|
self.__additional_informations[event_name]['last_duration'] = str(time.time() - start_time)
|
|
return True
|
|
|
|
def unregister_event(self, event_name, event_source, delete_source_when_empty = True):
|
|
try:
|
|
logger.trace("unregister Event %s from %s ", event_name, event_source)
|
|
if event_name not in self.__Events: return "event unknown"
|
|
if event_source not in self.__Events[event_name]: return "source not know for this event"
|
|
self.__Events[event_name].remove(event_source)
|
|
if len(self.__Events[event_name]) is 0:
|
|
del self.__Events[event_name]
|
|
logger.debug("no more sources for event %s - remove event too", event_name)
|
|
if delete_source_when_empty: self.unregister_source(event_source)
|
|
logger.trace("event_source %s was removed for event %s", event_source, event_name)
|
|
return True
|
|
except Exception as exp:
|
|
logger.error('failed to unregister event %s with error message %s', event_name, exp)
|
|
return False
|
|
|
|
def unregister_source(self, event_source, force_unregister = False):
|
|
try:
|
|
logger.trace("unregister Eventsource %s and force_unregister is %s", event_source, force_unregister)
|
|
if event_source not in self.__Sources: return "event_source %s unknown" % (event_source)
|
|
for event_name in self.__Events.keys():
|
|
if event_source in self.__Events[event_name] and force_unregister:
|
|
self.unregister_event(event_name, event_source, False)
|
|
elif event_source in self.__Events[event_name] and not force_unregister:
|
|
return "couldn't unregister event_source %s because it is used for event %s" % (event_source, event_name)
|
|
if event_source in self.__Sources:
|
|
# sollte nicht nötig sein, da es entfernt wird, wenn das letzte Event dafür gelöscht wird
|
|
self.__Sources.remove(event_source)
|
|
logger.trace("event_source %s was removed", event_source)
|
|
return True
|
|
except Exception as exp:
|
|
logger.exception('failed to unregister source %s with error message %s', event_source, exp)
|
|
return False
|
|
|
|
def register_action(self, event_name, action_object, *args, **kwargs):
|
|
if ismethod(action_object) and callable(action_object):
|
|
action_object = SingleAction(action_object, *args, **kwargs)
|
|
elif isfunction(action_object) and callable(action_object):
|
|
action_object = SingleAction(action_object, *args, **kwargs)
|
|
elif not isinstance(action_object, SingleAction):
|
|
action_object = SingleAction.from_string(action_object)
|
|
|
|
if action_object is None:
|
|
logger.error('action_object is None')
|
|
return False
|
|
|
|
if 'single_fire_action' in kwargs.keys() and kwargs['single_fire_action'] is True:
|
|
action_object.single_fire_action = True
|
|
del kwargs['single_fire_action']
|
|
|
|
if event_name in self.__Actions:
|
|
self.__Actions[event_name].append(action_object)
|
|
logger.trace("action %s was added to event %s", action_object, event_name)
|
|
else:
|
|
self.__Actions[event_name] = [action_object]
|
|
logger.trace("action %s was added to new evententry %s", action_object, event_name)
|
|
|
|
return action_object
|
|
|
|
__call__ = fire_event_asynchron
|