diff --git a/secop/histwriter.py b/secop/histwriter.py new file mode 100644 index 0000000..251a878 --- /dev/null +++ b/secop/histwriter.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# ***************************************************************************** +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Module authors: +# Markus Zolliker +# ***************************************************************************** + +import time +from secop.datatypes import get_datatype, IntRange, FloatRange, ScaledInteger,\ + EnumType, BoolType, StringType, TupleOf, StructOf +import history.histwriter + + +class HistWriter(history.histwriter.HistWriter): + """extend writer to be used as an internal frappy connection""" + def __init__(self, directory, predefined_names, dispatcher): + super().__init__(directory) + self.predefined_names = predefined_names + self.parameters = {} # dict of (, ) + self.activated = False + self.dispatcher = dispatcher + self._init_time = None + print('HISTINIT') + + def init(self, msg): + action, _, description = msg + assert action == 'describing' + vars = [] + self._init_time = time.time() + + for modname, moddesc in description['modules'].items(): + for pname, pdesc in moddesc['accessibles'].items(): + ident = key = modname + ':' + pname + if pname.startswith('_') and pname[1:] not in self.predefined_names: + key = modname + ':' + pname[1:] + dt = get_datatype(pdesc['datainfo']) + + if pname == 'value': + continuous = isinstance(dt, (FloatRange, ScaledInteger)) + vars.append('%s|%s|%s||%d' % (key, dt.unit or '1', modname, continuous)) + elif pname == 'target': + vars.append('%s|%s|%s_target||0' % (key, dt.unit or '1', modname)) + self.parameters[ident] = dt, key + self.put(self._init_time, 'STR', 'vars', ' '.join(vars)) + self.dispatcher.handle_activate(self, None, None) + self._init_time = None + return + + def send_reply(self, msg): + action, ident, value = msg + if not action.endswith('update'): + print('unknown async message %r' % msg) + return + now = self._init_time or time.time() # on initialisation, make all timestamps equal + dt, key = self.parameters[ident] + if action == 'update': + + def convert(value, dt, key): + if isinstance(dt, (EnumType, IntRange, BoolType)): + return [('NUM', key, str(int(value)))] + if isinstance(dt, (FloatRange, ScaledInteger)): + return [('NUM', key, str(dt.import_value(value)))] + if isinstance(dt, StringType): + return [('STR', key, value)] + if isinstance(dt, TupleOf): + return sum((convert(value[i], d, '%s.%s' % (key, i)) for i, d in enumerate(dt.members)), []) + if isinstance(dt, StructOf): + return sum((convert(value[k], d, '%s.%s' % (key, k)) for d, k in dt.members.items()), []) + # ArrayType, BlobType and TextType are not considered: too much data, proabably not used + return [] + + # omit qualifiers. we do not use the timestamp here, as a potentially decreasing + # values might get the reader software into trouble + result = convert(value[0], dt, key) + for htype, key, strval in convert(value[0], dt, key): + self.put(now, htype, key, strval) + + else: # error_update + old = self.cache.get(key) + if old is None: + return # ignore if this key is not yet used + + def get_keys(dt, key): + if isinstance(dt, (IntRange, FloatRange, ScaledInteger, BoolType, EnumType)): + return [('NUM', key)] + if isinstance(dt, StringType): + return [('STR', key)] + if isinstance(dt, TupleOf): + return sum((get_keys(d, '%s.%s' % (key, i)) for i, d in enumerate(dt.members)), []) + if isinstance(dt, StructOf): + return sum((get_keys(d, '%s.%s' % (key, k)) for d, k in dt.members.items()), []) + return [] + + for htype, key in get_keys(dt, key): + self.put(now, htype, key, '') diff --git a/secop/server.py b/secop/server.py index 7a64739..d19b0ae 100644 --- a/secop/server.py +++ b/secop/server.py @@ -42,6 +42,7 @@ except ImportError: from secop.errors import ConfigError from secop.lib import formatException, get_class, getGeneralConfig from secop.modules import Attached +from secop.params import PREDEFINED_ACCESSIBLES try: import systemd.daemon @@ -126,7 +127,7 @@ class Server: else: filename = None if filename is None: - raise ConfigError("Couldn't find cfg file %r" % cfgfile) + raise ConfigError("Couldn't find cfg file %r in %s" % (cfgfile, cfg['confdir'])) self.log.debug('Parse config file %s ...' % filename) result = OrderedDict() parser = configparser.ConfigParser() @@ -270,3 +271,10 @@ class Server: if not event.wait(timeout=max(0, deadline - time.time())): self.log.info('WARNING: timeout when starting %s' % name) self.log.info('all modules and pollers started') + history_path = os.environ.get('FRAPPY_HISTORY') + if history_path: + from secop.histwriter import HistWriter + writer = HistWriter(history_path, PREDEFINED_ACCESSIBLES.keys(), self.dispatcher) + # treat writer as a connection + self.dispatcher.add_connection(writer) + writer.init(self.dispatcher.handle_describe(writer, None, None))