Example #1
0
class KafkaSubscriber(DeviceMixinBase):
    """ Receives messages from Kafka, can subscribe to a topic and get all
    new messages from the topic if required via a callback method
    *new_message_callback*.
    """

    parameters = {
        'brokers':
        Param('List of kafka hosts to be connected',
              type=listof(host(defaultport=9092)),
              default=['localhost'],
              preinit=True,
              userparam=False)
    }

    def doPreinit(self, mode):
        self._consumer = kafka.KafkaConsumer(
            bootstrap_servers=self.brokers,
            auto_offset_reset='latest'  # start at latest offset
        )

        # Settings for thread to fetch new message
        self._stoprequest = True
        self._updater_thread = None

    def doShutdown(self):
        if self._updater_thread is not None:
            self._stoprequest = True
            if self._updater_thread.is_alive():
                self._updater_thread.join()
            self._consumer.close()

    @property
    def consumer(self):
        return self._consumer

    def subscribe(self, topic):
        """ Create the thread that provides call backs on new messages
        """
        # Remove all the assigned topics
        self._consumer.unsubscribe()

        topics = self._consumer.topics()
        if topic not in topics:
            raise ConfigurationError('Provided topic %s does not exist' %
                                     topic)

        # Assign the partitions
        partitions = self._consumer.partitions_for_topic(topic)
        if not partitions:
            raise ConfigurationError('Cannot query partitions for %s' % topic)

        self._consumer.assign(
            [kafka.TopicPartition(topic, p) for p in partitions])
        self._stoprequest = False
        self._updater_thread = createThread('updater_' + topic,
                                            self._get_new_messages)
        self.log.debug('subscribed to updates from topic: %s' % topic)

    def _get_new_messages(self):
        while not self._stoprequest:
            sleep(self._long_loop_delay)

            messages = {}
            data = self._consumer.poll(5)
            for records in data.values():
                for record in records:
                    messages[record.timestamp] = record.value

            if messages:
                self.new_messages_callback(messages)
            else:
                self.no_messages_callback()
        self.log.debug("KafkaSubscriber thread finished")

    def new_messages_callback(self, messages):
        """This method is called whenever a new messages appear on
        the topic. The subclasses should define this method if
        a callback is required when new messages appear.
        :param messages: dict of timestamp and raw message
        """

    def no_messages_callback(self):
        """This method is called if no messages are on the topic.
Example #2
0
class Detector(Measurable):
    """Detector using multiple (synchronized) channels.

    Each channel can have a "preset" set, which means that measurement stops if
    the channel's value (or an element thereof, for channels with multiple
    read values) has reached the preset value.

    Passive channels can only stop the measurement via soft presets (presets
    that are checked by NICOS) during the countloop and therefore may be
    overshot by some nontrivial amount.  In contrast, the derived
    `ActiveChannel` is able to stop by itself, usually implemented in hardware,
    so that the preset is reached exactly, or overshot by very little.

    In the detector, channels with a preset are called "masters", while
    channels without are called "slaves".  Which channels are masters and
    slaves can change with every count cycle.
    """

    attached_devices = {
        'timers':
        Attach('Timer channel', PassiveChannel, multiple=True, optional=True),
        'monitors':
        Attach('Monitor channels',
               PassiveChannel,
               multiple=True,
               optional=True),
        'counters':
        Attach('Counter channels',
               PassiveChannel,
               multiple=True,
               optional=True),
        'images':
        Attach('Image channels',
               ImageChannelMixin,
               multiple=True,
               optional=True),
        'others':
        Attach('Channels that return e.g. filenames',
               PassiveChannel,
               multiple=True,
               optional=True),
    }

    parameters = {
        'liveinterval':
        Param(
            'Interval to read out live images (None '
            'to disable live readout)',
            type=none_or(float),
            unit='s',
            settable=True),
        'saveintervals':
        Param(
            'Intervals to read out intermediate images '
            '(empty to disable); [x, y, z] will read out '
            'after x, then after y, then every z seconds',
            type=listof(float),
            unit='s',
            settable=True),
        'postprocess':
        Param(
            'Post processing list containing tuples of '
            '(PostprocessPassiveChannel, '
            'ImageChannelMixin or PassiveChannel, ...)',
            type=listof(tuple)),
    }

    parameter_overrides = {
        'fmtstr': Override(volatile=True),
    }

    hardware_access = False
    multi_master = True

    _last_live = 0
    _last_save = 0
    _last_save_index = 0
    _last_preset = None
    _user_comment = ''

    def doInit(self, _mode):
        self._masters = []
        self._slaves = []
        self._channel_presets = {}
        self._postprocess = []
        self._postpassives = []

        for tup in self.postprocess:
            if tup[0] not in session.configured_devices:
                self.log.warning(
                    "device %r not found but configured in "
                    "'postprocess' parameter. No "
                    "post processing for this device. Please "
                    "check the detector setup.", tup[0])
                continue
            postdev = session.getDevice(tup[0])
            img_or_passive_devs = [session.getDevice(name) for name in tup[1:]]
            if not isinstance(postdev, PostprocessPassiveChannel):
                raise ConfigurationError("Device '%s' is not a "
                                         "PostprocessPassiveChannel" %
                                         postdev.name)
            if postdev not in self._channels:
                raise ConfigurationError("Device '%s' has not been configured "
                                         "for this detector" % postdev.name)
            for dev in img_or_passive_devs:
                if dev not in self._channels:
                    raise ConfigurationError("Device '%s' has not been "
                                             "configured for this detector" %
                                             dev.name)
                elif isinstance(dev, PassiveChannel):
                    self._postpassives.append(dev)
            self._postprocess.append((postdev, img_or_passive_devs))

    # allow overwriting in derived classes
    def _presetiter(self):
        """Yield (name, device, type) tuples for all 'preset-able' devices."""
        # a device may react to more than one presetkey....
        for i, dev in enumerate(self._attached_timers):
            if i == 0:
                yield ('t', dev, 'time')
            for preset in dev.presetInfo():
                yield (preset, dev, 'time')
        for dev in self._attached_monitors:
            for preset in dev.presetInfo():
                yield (preset, dev, 'monitor')
        for i, dev in enumerate(self._attached_counters):
            if i == 0:
                yield ('n', dev, 'counts')
            for preset in dev.presetInfo():
                yield (preset, dev, 'counts')
        for dev in self._attached_images + self._attached_others:
            for preset in dev.presetInfo():
                yield (preset, dev, 'counts')
        yield ('live', None, None)

    def doPreinit(self, mode):
        presetkeys = {}
        for name, dev, typ in self._presetiter():
            # later mentioned presetnames dont overwrite earlier ones
            presetkeys.setdefault(name, (dev, typ))
        self._channels = uniq(self._attached_timers + self._attached_monitors +
                              self._attached_counters + self._attached_images +
                              self._attached_others)
        self._presetkeys = presetkeys
        self._getMasters()

    def _getMasters(self):
        """Internal method to collect all masters."""
        masters = []
        slaves = []
        for ch in self._channels:
            if ch.ismaster:
                masters.append(ch)
            else:
                slaves.append(ch)
        self._masters, self._slaves = masters, slaves

    def _getPreset(self, preset):
        """Returns previous preset if no preset has been set."""
        if not preset and self._last_preset:
            return self._last_preset
        if 'live' not in preset:
            # do not store live as previous preset
            self._last_preset = preset
        return preset

    def doSetPreset(self, **preset):
        self._user_comment = preset.pop('info', '')
        preset = self._getPreset(preset)
        if not preset:
            # keep old settings
            return
        for master in self._masters:
            master.ismaster = False
        self._channel_presets = {}
        for (name, value) in iteritems(preset):
            if name in self._presetkeys and name != 'live':
                dev = self._presetkeys[name][0]
                dev.setChannelPreset(name, value)
                self._channel_presets.setdefault(dev, []).append((name, value))
        self._getMasters()
        if set(self._masters) != set(self._channel_presets):
            if not self._masters:
                self.log.warning('no master configured, detector may not stop')
            else:
                self.log.warning(
                    'master setting for devices %s ignored by '
                    'detector',
                    ', '.join(set(self._channel_presets) - set(self._masters)))
        self.log.debug("   presets: %s", preset)
        self.log.debug("presetkeys: %s", self._presetkeys)
        self.log.debug("   masters: %s", self._masters)
        self.log.debug("    slaves: %s", self._slaves)

    def doPrepare(self):
        for slave in self._slaves:
            slave.prepare()
        for master in self._masters:
            master.prepare()

    def doStart(self):
        # setting this to -interval, instead of 0, will send some live data at
        # the very start of each count, clearing the "live" data from last time
        self._last_live = -(self.liveinterval or 0)
        self._last_save = 0
        self._last_save_index = 0
        for slave in self._slaves:
            slave.start()
        for master in self._masters:
            master.start()

    def doTime(self, preset):
        self.doSetPreset(**preset)  # okay in simmode
        return self.doEstimateTime(0) or 0

    def doPause(self):
        success = True
        for slave in self._slaves:
            success &= slave.pause()
        for master in self._masters:
            success &= master.pause()
        return success

    def doResume(self):
        for slave in self._slaves:
            slave.resume()
        for master in self._masters:
            master.resume()

    def doFinish(self):
        for master in self._masters:
            master.finish()
        for slave in self._slaves:
            slave.finish()

    def doStop(self):
        for master in self._masters:
            master.stop()
        for slave in self._slaves:
            slave.stop()

    def doRead(self, maxage=0):
        ret = []
        for ch in self._channels:
            ret.extend(ch.read())
        return ret

    def doReadArrays(self, quality):
        arrays = [img.readArray(quality) for img in self._attached_images]
        results = [dev.read(0) for dev in self._postpassives]
        for postdev, img_or_passive_devs in self._postprocess:
            postarrays, postresults = [], []
            for dev in img_or_passive_devs:
                if isinstance(dev, ImageChannelMixin):
                    postarrays.append(arrays[self._attached_images.index(dev)])
                else:  # PassiveChannel
                    postresults.append(results[self._postpassives.index(dev)])
            postdev.setReadResult(postarrays, postresults, quality)
        return arrays

    def duringMeasureHook(self, elapsed):
        if self.liveinterval is not None:
            if self._last_live + self.liveinterval < elapsed:
                self._last_live = elapsed
                return LIVE
        intervals = self.saveintervals
        if intervals:
            if self._last_save + intervals[self._last_save_index] < elapsed:
                self._last_save_index = min(self._last_save_index + 1,
                                            len(intervals) - 1)
                self._last_save = elapsed
                return INTERMEDIATE
        return None

    def doSimulate(self, preset):
        self.doSetPreset(**preset)  # okay in simmode
        return self.doRead()

    def doStatus(self, maxage=0):
        st, text = multiStatus(self._getWaiters(), maxage)
        if st == status.ERROR:
            return st, text
        for master in self._masters:
            for (name, value) in self._channel_presets.get(master, ()):
                if master.presetReached(name, value, maxage):
                    return status.OK, text
        return st, text

    def doReset(self):
        for ch in self._channels:
            ch.reset()

    def valueInfo(self):
        ret = []
        for ch in self._channels:
            ret.extend(ch.valueInfo())
        return tuple(ret)

    def arrayInfo(self):
        return tuple(img.arraydesc for img in self._attached_images)

    def doReadFmtstr(self):
        return ', '.join('%s = %s' % (v.name, v.fmtstr)
                         for v in self.valueInfo())

    def presetInfo(self):
        return {'info'} | set(self._presetkeys)

    def doEstimateTime(self, elapsed):
        eta = {master.estimateTime(elapsed) for master in self._masters}
        eta.discard(None)
        if eta:
            # first master stops, so take min
            return min(eta)
        return None

    def doInfo(self):
        ret = []
        if self._user_comment:
            ret.append(('usercomment', self._user_comment, self._user_comment,
                        '', 'general'))
        presets = []
        for (_dev, devpresets) in iteritems(self._channel_presets):
            for (key, value) in devpresets:
                presets.append((self._presetkeys[key][1], value))
        if len(presets) > 1:
            mode = ' or '.join(p[0] for p in presets)
            ret.append(('mode', mode, mode, '', 'presets'))
            for (mode, value) in presets:
                ret.append(
                    ('preset_%s' % mode, value, str(value), '', 'presets'))
        elif presets:
            mode, value = presets[0]
            return ret + [('mode', mode, mode, '', 'presets'),
                          ('preset', value, str(value), '', 'presets')]
        return ret
Example #3
0
class GatedDetector(Detector):
    """A Detector which enables some 'gates' before the measurment
    and disables them afterwards
    """

    attached_devices = {
        'gates': Attach('Gating devices',
                        Moveable,
                        multiple=True,
                        optional=True),
    }

    parameters = {
        'enablevalues':
        Param('List of values to enable the gates',
              type=listof(anytype),
              default=[]),
        'disablevalues':
        Param('List of values to disable the gates',
              type=listof(anytype),
              default=[]),
    }

    def _enable_gates(self):
        self.log.debug('enabling gates')
        for dev, val in zip(self._attached_gates, self.enablevalues):
            dev.move(val)
        multiWait(self._attached_gates)
        self.log.debug('gates enabled')

    def _disable_gates(self):
        self.log.debug('disabling gates')
        for dev, val in zip(reversed(self._attached_gates),
                            reversed(self.disablevalues)):
            dev.move(val)
        multiWait(self._attached_gates)
        self.log.debug('gates disabled')

    def doStart(self):
        self._enable_gates()
        Detector.doStart(self)

    def doResume(self):
        # check first gate to see if we need to (re-)enable them
        if self._attached_gates[0].read(0) != self.enablevalues[0]:
            self._enable_gates()
        Detector.doResume(self)

    def doPause(self):
        res = Detector.doPause(self)
        if res:
            self._disable_gates()
        return res

    def doStop(self):
        Detector.doStop(self)
        self._disable_gates()

    def doFinish(self):
        Detector.doFinish(self)
        self._disable_gates()
Example #4
0
class KafkaCacheDatabase(MemoryCacheDatabase):
    """ Cache database that stores cache in Kafka topics without History.

    Current key and value pairs are stored in Kafka using log compaction.
    The `CacheEntry` values can be serialized using the attached device. This
    is then used to encode and decode entry instances while producing to
    the Kafka topic and while consuming from it.

    The data is stored in the Kafka topics using log compaction. Log
    compaction ensures that Kafka will always retain at least the last
    known value for each message key within the log of data for a single
    topic partition.

    The keys for partitioning are the keys received from cache server and
    the timestamp of the message is same as the time of the cache entry.
    If the provided `topic` does not exist, the cache database will not
    be able to proceed. Use the command line kafka tool to create the topic.

    This database will connect to the kafka and zookeeper services so
    they should be running in background on the provided `hosts`. For
    a basic getting started with kafka one can use the following guide:
    https://kafka.apache.org/quickstart

    Note that the minimum kafka broker version required for this
    database to run properly is 0.11.0. Before this version timestamps
    for messages could not be set.

    The default behavior of Kafka does not ensure that the topic will
    be cleaned up using the log compaction policy. For this to happen
    one of the following two is to be done:

    * Create topic by CLI Kafka tools using --config cleanup.policy=compact
    * Set in server.properties log.cleanup.policy=compact

    History is NOT supported with this database.
    """
    parameters = {
        'currenttopic':
        Param('Kafka topic where the current values of cache are streamed',
              type=str,
              mandatory=True),
        'brokers':
        Param('List of Kafka bootstrap servers.',
              type=listof(host(defaultport=9092)),
              default=['localhost']),
    }

    attached_devices = {
        'serializer':
        Attach('Device to serialize the cache entry values',
               CacheEntrySerializer,
               optional=False)
    }

    def doInit(self, mode):
        MemoryCacheDatabase.doInit(self, mode)

        # Create the producer
        self._producer = KafkaProducer(bootstrap_servers=self.brokers)

        # Create the consumer
        self._consumer = KafkaConsumer(
            bootstrap_servers=self.brokers,
            auto_offset_reset='earliest'  # start at earliest topic
        )

        # Give up if the topic does not exist
        if self.currenttopic not in self._consumer.topics():
            raise ConfigurationError(
                'Topic "%s" does not exit. Create this topic and restart.' %
                self.currenttopic)

        # Assign the partitions
        partitions = self._consumer.partitions_for_topic(self.currenttopic)
        self._consumer.assign(
            [TopicPartition(self.currenttopic, p) for p in partitions])

        # Cleanup thread configuration
        self._stoprequest = False
        self._cleaner = createThread('cleaner', self._clean, start=False)

    def doShutdown(self):
        self._consumer.close()
        self._producer.close()

        # Stop the cleaner thread
        self._stoprequest = True
        self._cleaner.join()

    def initDatabase(self):
        self.log.info('Reading messages from kafka topic - %s',
                      self.currenttopic)
        now = currenttime()
        message_count = 0
        end = self._consumer.end_offsets(list(self._consumer.assignment()))
        for partition in self._consumer.assignment():
            while self._consumer.position(partition) < end[partition]:
                msg = next(self._consumer)
                message_count += 1
                if msg.value is not None:
                    _, entry = self._attached_serializer.decode(msg.value)
                    if entry is not None and entry.value is not None:
                        # self.log.debug('%s (%s): %s -> %s', msg.offset,
                        #               msg.timestamp, msg.key, entry)
                        if entry.ttl and entry.time + entry.ttl < now:
                            entry.expired = True

                        self._db[msg.key] = [entry]

        self._cleaner.start()
        self.log.info('Processed %i messages.', message_count)

    def _clean(self):
        def cleanonce():
            with self._db_lock:
                for key, entries in iteritems(self._db):
                    entry = entries[-1]
                    if not entry.value or entry.expired:
                        continue
                    time = currenttime()
                    if entry.ttl and (entry.time + entry.ttl < time):
                        entry.expired = True
                        for client in self._server._connected.values():
                            client.update(key, OP_TELLOLD, entry.value, time,
                                          None)

        while not self._stoprequest:
            sleep(self._long_loop_delay)
            cleanonce()

    def _update_topic(self, key, entry):
        # This method is responsible to communicate and update all the
        # topics that should be updated. Subclasses can (re)implement it
        # if there are messages to be produced to other topics
        self.log.debug('Writing: %s -> %s', key, entry.value)

        # For the log-compacted topic key deletion happens when None is
        # passed as the value for the key
        value = None
        if entry.value is not None:
            # Only when the key deletion is not required
            value = self._attached_serializer.encode(key, entry)

        self._producer.send(topic=self.currenttopic,
                            value=value,
                            key=bytes(key),
                            timestamp_ms=int(entry.time * 1000))

        # clear all local buffers and produce pending messages
        self._producer.flush()

    def tell(self, key, value, time, ttl, from_client):
        if value is None:
            # deletes cannot have a TTL
            ttl = None
        send_update = True
        always_send_update = False
        # remove no-store flag
        if key.endswith(FLAG_NO_STORE):
            key = key[:-len(FLAG_NO_STORE)]
            always_send_update = True
        try:
            category, subkey = key.rsplit('/', 1)
        except ValueError:
            category = 'nocat'
            subkey = key
        newcats = [category]
        if category in self._rewrites:
            newcats.extend(self._rewrites[category])
        for newcat in newcats:
            key = newcat + '/' + subkey
            with self._db_lock:
                entries = self._db.setdefault(key, [])
                if entries:
                    lastent = entries[-1]
                    if lastent.value == value and not lastent.expired:
                        # not a real update
                        send_update = False
                thisent = CacheEntry(time, ttl, value)
                entries[:] = [thisent]
                if send_update:
                    self._update_topic(key, thisent)
            if send_update or always_send_update:
                for client in self._server._connected.values():
                    if client is not from_client and client.is_active():
                        client.update(key, OP_TELL, value or '', time, ttl)
Example #5
0
class DSPec(PyTangoDevice, Measurable):

    attached_devices = {
        'gates': Attach('Gating devices',
                        Moveable,
                        multiple=True,
                        optional=True),
    }

    parameters = {
        'prefix':
        Param('prefix for filesaving',
              type=str,
              settable=False,
              mandatory=True,
              category='general'),
        'ecalslope':
        Param('Energy calibration slope',
              type=float,
              mandatory=False,
              settable=True,
              volatile=True,
              default=0),
        'ecalintercept':
        Param('Energy calibration interception',
              type=float,
              mandatory=False,
              settable=True,
              volatile=True,
              default=0),
        'poll':
        Param('Polling time of the TANGO device driver',
              type=float,
              settable=False,
              volatile=True),
        'cacheinterval':
        Param('Interval to cache intermediate spectra',
              type=float,
              unit='s',
              settable=True,
              default=1800),
        'enablevalues':
        Param('List of values to enable the gates',
              type=listof(anytype),
              default=[],
              settable=True,
              category='general'),
        'disablevalues':
        Param('List of values to disable the gates',
              type=listof(anytype),
              default=[]),
    }

    def _enable_gates(self):
        self.log.debug('enabling gates')
        for dev, val in zip(self._attached_gates, self.enablevalues):
            dev.move(val)
        multiWait(self._attached_gates)
        self.log.debug('gates enabled')

    def _disable_gates(self):
        self.log.debug('disabling gates')
        for dev, val in zip(reversed(self._attached_gates),
                            reversed(self.disablevalues)):
            dev.move(val)
        multiWait(self._attached_gates)
        self.log.debug('gates disabled')

    # XXX: issues with ortec API -> workarounds and only truetime and livetime
    # working.

    def doReadEcalslope(self):
        ec = self._dev.EnergyCalibration.strip()
        return float(ec.split()[1])

    def doReadEcalintercept(self):
        ec = self._dev.EnergyCalibration.strip()
        return float(ec.split()[2])

    def doReadArrays(self, quality):
        spectrum = None
        try:
            spectrum = self._dev.Value
        except NicosError:
            # self._comment += 'CACHED'
            if self._read_cache is not None:
                self.log.warning('using cached spectrum')
                spectrum = self._read_cache
            else:
                self.log.warning('no spectrum cached')
        return [spectrum]

    def doRead(self, maxage=0):
        ret = [
            self._dev.TrueTime[0], self._dev.LiveTime[0],
            sum(self._dev.Value.tolist())
        ]
        return ret

    def doReadPoll(self):
        return self._dev.PollTime[0]

    def _clear(self):
        self._started = None
        self._lastread = 0
        self._comment = ''
        self._name_ = ''
        self._stop = None
        self._preset = {}
        self._dont_stop_flag = False
        self._read_cache = None

    def doReset(self):
        self._clear()
        self._dev.Init()

    def doInit(self, mode):
        self._clear()
        self.arraydesc = ArrayDesc('data', (1, 16384), np.uint32)

    def doFinish(self):
        self.doStop()
        # reset preset values
        self._clear()

    def doReadIsmaster(self):
        pass

    def presetInfo(self):
        return {
            'info', 'Filename', 'TrueTime', 'LiveTime', 'ClockTime', 'counts'
        }

    def doSetPreset(self, **preset):
        self._clear()

        if 'TrueTime' in preset:
            try:
                self._dev.SyncMode = 'RealTime'
                self._dev.SyncValue = preset['TrueTime'] * 1000
            except NicosError:
                try:
                    self.doStop()
                    self._dev.Init()
                except NicosError:
                    return
                self._dev.SyncMode = 'RealTime'
                self._dev.SyncValue = preset['TrueTime'] * 1000
        elif 'LiveTime' in preset:
            try:
                self._dev.SyncMode = 'LiveTime'
                self._dev.SyncValue = preset['LiveTime'] * 1000
            except NicosError:
                try:
                    self.doStop()
                    self._dev.Init()
                except NicosError:
                    return
                self._dev.SyncMode = 'LiveTime'
                self._dev.SyncValue = preset['LiveTime'] * 1000
        elif 'ClockTime' in preset:
            self._stop = preset['ClockTime']
        elif 'counts' in preset:
            pass

        self._preset = preset

    def doTime(self, preset):
        if 'TrueTime' in preset:
            return preset['TrueTime']
        elif 'LiveTime' in preset:
            return preset['LiveTime']
        elif 'ClockTime' in preset:
            return abs(float(preset['ClockTime']) - currenttime())
        elif 'counts' in preset:
            return 1
        return None

    def doEstimateTime(self, elapsed):
        if self.doStatus()[0] == status.BUSY:
            if 'TrueTime' in self._preset:
                return self._preset['TrueTime'] - elapsed
            elif 'LiveTime' in self._preset:
                return self._preset['LiveTime'] - elapsed
            elif 'ClockTime' in self._preset:
                return abs(float(self._preset['ClockTime']) - currenttime())
        return None

    def doStart(self):
        self._enable_gates()
        try:
            self._dev.Stop()
            self._dev.Clear()
            self._dev.Start()
        except NicosError:
            try:
                self._dev.Stop()
                self._dev.Init()
                self._dev.Clear()
                self._dev.Start()
            except NicosError:
                pass
        self._started = currenttime()
        self._lastread = currenttime()

    def doPause(self):
        self._dev.Stop()
        self._disable_gates()
        return True

    def doResume(self):
        # check first gate to see if we need to (re-)enable them
        if self._attached_gates[0].read(0) != self.enablevalues[0]:
            self._enable_gates()
        try:
            self._dev.Start()
        except NicosError:
            self._dev.Init()
            self._dev.Stop()
            self._dev.Start()

    def doStop(self):
        if self._dont_stop_flag:
            self._dont_stop_flag = False
            return
        try:
            self._dev.Stop()
        except NicosError:
            self._dev.Init()
            self._dev.Stop()
        self._disable_gates()

    def duringMeasureHook(self, elapsed):
        if (elapsed - self._lastread) > self.cacheinterval:
            try:
                self._read_cache = self._dev.Value
                self.log.debug('spectrum cached')
            except NicosError:
                self.log.warning('caching spectrum failed')
            finally:
                self._lastread = elapsed
            return LIVE
        return None

    def doSimulate(self, preset):
        for t in 'TrueTime', 'LiveTime', 'ClockTime':
            if t in preset:
                return [preset[t], preset[t], 0]
        if 'counts' in preset:
            return [1, 1, preset['counts']]
        return [0, 0, 0]

    def doIsCompleted(self):
        if self._started is None:
            return True
        if self._dont_stop_flag is True:
            return (currenttime() - self._started) >= self._preset['value']

        if self._stop is not None:
            if currenttime() >= self._stop:
                return True

        if 'TrueTime' in self._preset:
            return self._dev.TrueTime >= self._preset['TrueTime']
        elif 'LiveTime' in self._preset:
            return self._dev.LiveTime >= self._preset['LiveTime']
        elif 'counts' in self._preset:
            return self.doRead(0)[2] >= self._preset['counts']
        try:
            # self.log.warning('poll')
            stop = self.poll[0]
            return stop < 0
        except NicosError:
            self._dont_stop_flag = True
            # self.log.warning('read poll time failed, waiting for other '
            #                  'detector(s)...')
            return False
        return False

    def valueInfo(self):
        return (
            Value(name='truetim', type='time', fmtstr='%.3f', unit='s'),
            Value(name='livetim', type='time', fmtstr='%.3f', unit='s'),
            Value('DSpec', type='counter', fmtstr='%d', unit='cts'),
        )

    def arrayInfo(self):
        return (self.arraydesc, )
Example #6
0
class BaseCryopad(Moveable):
    """Controls the whole state of the cryopad.

    How to determine the wavelength and sample angles must be implemented
    in subclasses, via the _getInstr* methods.
    """

    attached_devices = {
        'nut_in': Attach('Incoming nutator axis', Moveable),
        'prec_in': Attach('Incoming precession current', Moveable),
        'nut_out': Attach('Outgoing nutator axis', Moveable),
        'prec_out': Attach('Outgoing precession current', Moveable),
    }

    parameters = {
        # 01/2017: [108.27, 0, 0, 80.73, 0, 0]
        'coefficients':
        Param('Coefficients for calculating currents',
              type=listof(float),
              settable=True,
              default=[108.46, 0, 0, 80.77, 0, 0]),
        'meissnercorrection':
        Param('Correct for curved Meissner shield?', type=bool, default=True),
    }

    parameter_overrides = {
        'fmtstr': Override(default='%.1f %.1f / %.1f %.1f'),
    }

    valuetype = tupleof(float, float, float, float)

    def _getWaiters(self):
        return [
            self._attached_nut_in, self._attached_nut_out,
            self._attached_prec_in, self._attached_prec_out
        ]

    def _getInstrPosition(self, maxage=None):
        """Return (lambda_in, lambda_out, gamma, sense) of the instrument.

        Wavelength must be in Angstrom, the angles must be in degrees.
        """
        raise NotImplementedError

    def _getInstrTarget(self):
        """Return targets (lambda_in, lambda_out, gamma, sense) of the
        instrument.
        """
        raise NotImplementedError

    def doRead(self, maxage=None):
        lam_in, lam_out, gamma, sense = self._getInstrPosition(maxage)
        prec_in = self._attached_prec_in.read(maxage)
        prec_out = self._attached_prec_out.read(maxage)
        theta_in = self._attached_nut_in.read(maxage)
        theta_out = self._attached_nut_out.read(maxage)

        if self.meissnercorrection:
            theta_out -= calc.curved_meissner_correction(theta_out)

        chi_in, chi_out = calc.currents_to_angles(self.coefficients, prec_in,
                                                  prec_out, lam_in, lam_out,
                                                  gamma)
        self.log.debug('chi_in=%s, chi_out=%s', chi_in, chi_out)
        xyz_in = calc.pol_in_from_angles(sense, theta_in, chi_in, lam_in,
                                         lam_out, gamma)
        xyz_out = calc.pol_out_from_angles(sense, theta_out, chi_out, lam_in,
                                           lam_out, gamma)
        self.log.debug('xyz_in=%s, xyz_out=%s', xyz_in, xyz_out)

        polar_in = calc.polar_angles_from_xyz(*xyz_in)
        polar_out = calc.polar_angles_from_xyz(*xyz_out)
        return polar_in + polar_out  # a 4-tuple

    def doStart(self, target):
        lam_in, lam_out, gamma, sense = self._getInstrTarget()
        polar_th_in, polar_phi_in, polar_th_out, polar_phi_out = target

        theta_in, chi_in = calc.cryopad_in(sense, lam_in, lam_out, gamma,
                                           polar_th_in, polar_phi_in)
        theta_in, chi_in = calc.optimize_angles(theta_in, chi_in)

        theta_out, chi_out = calc.cryopad_out(sense, lam_in, lam_out, gamma,
                                              polar_th_out, polar_phi_out)
        theta_out, chi_out = calc.optimize_angles(theta_out, chi_out)

        if self.meissnercorrection:
            theta_out += calc.curved_meissner_correction(theta_out)

        prec_in, prec_out = calc.angles_to_currents(self.coefficients, chi_in,
                                                    chi_out, lam_in, lam_out,
                                                    gamma)

        self._attached_nut_in.start(theta_in)
        self._attached_nut_out.start(theta_out)
        self._attached_prec_in.start(prec_in)
        self._attached_prec_out.start(prec_out)
Example #7
0
class Detector(MeasureSequencer):
    """SPODI specific detector.

    The detector moves around an axis (2theta) in a number of steps
    (`resosteps`) over a range of 2 degrees (given in `range`). The starting
    position is current position + (`resosteps` - 1) * `range` / `resosteps`.

    The detector data (image) will be stored in an accumulated image array:

        The first columns of each step will be stored in a sequence, the second
        columns will follow, and so on.

        At the end the image will be a refined picture of a single histogram.
    """

    attached_devices = {
        'motor': Attach('Axis to perform the reso(lution) steps', Moveable),
        'detector': Attach('Standard detector device', GenericDetector),
    }

    parameters = {
        'resosteps': Param('Number of steps performed by the motor to '
                           'accumulate a single spectrum',
                           type=oneof(1, 2, 4, 5, 8, 10, 20, 25, 40, 50, 80,
                                      100),
                           default=40, settable=True, userparam=True,
                           category='general',
                           ),
        'range': Param('Fullrange for the resosteps',
                       type=float, settable=False,
                       default=2.0, category='instrument',
                       ),
        'numinputs': Param('Number of detector channels',
                           type=int, default=80, settable=False,
                           category='general',
                           ),
        'liveinterval': Param('Interval to read out live images (None to '
                              'disable live readout)',
                              type=none_or(float), unit='s', settable=True,
                              default=0.5,
                              ),
        'rates': Param('The rates detected by the detector',
                       settable=False, type=listof(float), internal=True,
                       category='status',
                       ),
        '_startpos': Param('Store the starting position',
                           type=float, settable=True, internal=True,
                           category='instrument',
                           ),
        '_step': Param('Store the current step',
                       type=intrange(0, 100), settable=True, internal=True,
                       ),
    }

    parameter_overrides = {
        'fmtstr': Override(volatile=True),
    }

    _last_live = 0
    _step = 0
    _time_preset = 0
    _mon_preset = 0
    _arraydesc = None
    _det_run = False

    def doInit(self, mode):
        self._data = [0] * len(self._attached_detector.valueInfo())
        self._set_resosteps(self.resosteps)

    def doInfo(self):
        ret = self._attached_detector.doInfo()
        return ret

    def doPrepare(self):
        MeasureSequencer.doPrepare(self)
        self._attached_detector.doPrepare()

    def doStart(self):
        self._startpos = self._attached_motor.read() + \
            (self.resosteps - 1) * self._step_size
        self.log.debug('det._startpos: %r', self._startpos)
        self._setROParam('rates', [0., 0., 0.])
        session.experiment.data.updateMetainfo()

        self._last_live = 0
        self._step = 0
        self._array_data.fill(0)
        self._data = [0] * len(self._attached_detector.valueInfo())
        MeasureSequencer.doStart(self)

    def doSetPreset(self, **preset):
        self._tths = None
        if preset:
            self._time_preset = preset.get('t', 0)
            self._mon_preset = preset.get('mon1', preset.get('mon2', 0))
            if 'resosteps' in preset:
                self.resosteps = int(preset.pop('resosteps'))
            if 'tths' in preset:
                self._tths = float(preset.pop('tths'))
        self._attached_detector.doSetPreset(**preset)

    def _read_value(self):
        ret = self._attached_detector.read()
        self._data = [sum(x) for x in zip(self._data, ret)]
        # Detector is not busy anymore, but to early to consider it as
        # 'not busy'
        self._det_run = False
        imgret = self._attached_detector.readArrays(FINAL)[0].astype('<u4',
                                                                     order='F')
        # self.log.info('%r', imgret)
        if self._mode != SIMULATION:
            if imgret.shape[0] == self._array_data.shape[1]:
                self._array_data[self._step::self.resosteps] = np.transpose(
                    imgret)
            else:
                self._array_data[self._step::self.resosteps] = imgret

    def _incStep(self):
        if self._step < self.resosteps - 1:
            self._step += 1

    def _startDet(self):
        """Start the detector and mark it running.

        Since the detector is not really in BUSY mode after start, we need an
        additional flag to mark the detector started.
        """
        self._attached_detector.prepare()
        waitForCompletion(self._attached_detector)
        self._attached_detector.start()
        self._det_run = True

    def doReadArrays(self, quality):
        self.log.debug('doReadArrays: %d/%d: %d, %r',
                       self._step, self.resosteps, self._array_data.sum(),
                       self._array_data.shape)
        if quality == LIVE:
            imgret = self._attached_detector.readArrays(FINAL)[0].astype(
                '<u4', order='F')
            self.log.debug('Shapes:%r %r', imgret.shape, self._array_data.shape)
            if imgret.shape[0] == self._array_data.shape[1]:
                self._array_data[self._step::self.resosteps] = np.transpose(
                    imgret)
            else:
                self._array_data[self._step::self.resosteps] = imgret
        return [self._array_data]

    def _generateSequence(self):
        seq = []
        if self._tths:
            self._startpos = self._tths
            seq.append(SeqDev(self._attached_motor, self._startpos,
                              stoppable=True))
        for step in range(self.resosteps):
            pos = self._startpos - step * self._step_size
            seq.append(SeqDev(self._attached_motor, pos, stoppable=True))
            seq.append(SeqCall(self._startDet))
            seq.append(SeqWait(self._attached_detector))
            seq.append(SeqCall(self._read_value))
            seq.append(SeqCall(self._incStep))
        return seq

    def doRead(self, maxage=0):
        if self._step < self.resosteps:
            if self._attached_detector.status(0)[0] == status.BUSY \
               or self._det_run:
                ret = [self._step + 1] + \
                    [sum(x) for x in
                     zip(self._data, self._attached_detector.doRead(maxage))]
            else:
                if self._step == 1 and \
                   MeasureSequencer.status(self, 0)[0] != status.BUSY:
                    ret = [self._step] + self._data
                else:
                    ret = [self._step + 1] + self._data
        else:
            ret = [self._step] + self._data
        # ret = [step, meastime, mon1, mon2, counts]
        meastime = ret[1]
        if meastime > 0.:
            ctrrate = ret[-1] / meastime
            mon1rate = ret[2] / meastime
            mon2rate = 0
            if len(self._attached_detector._attached_monitors) > 1:
                mon2rate = ret[-2] / meastime
            self._setROParam('rates', [mon1rate, mon2rate, ctrrate])
        return ret

    def doReset(self):
        self._det_run = False
        self._last_live = 0
        self._step = 0
        self._array_data.fill(0)
        self._attached_detector.doReset()
        self._data = [0] * len(self._attached_detector.valueInfo())
        MeasureSequencer.doReset(self)
        # self._attached_motor.maw(self._startpos)

    def doPause(self):
        self._attached_detector.doPause()

    def doResume(self):
        self._attached_detector.doResume()

    def doFinish(self):
        self._attached_detector.doFinish()

    def doSimulate(self, preset):
        return [self.resosteps] + self._attached_detector.doSimulate(preset)

    def _set_resosteps(self, value):
        # TODO: shape should be (y, x) not (x, y)
        image = self._attached_detector._attached_images[0]
        if hasattr(image, 'size'):
            height = image.size[1]
        elif hasattr(image, 'sizes'):
            height = image.sizes[1]
        else:
            height = 256
        shape = (value * self.numinputs, height)

        self._step_size = self.range / value
        if not self._arraydesc:
            self._arraydesc = ArrayDesc('data', shape=shape, dtype='<u4')
            self._array_data = np.zeros(shape, dtype='<u4', order='F')
        else:
            self._arraydesc.shape = shape
            self._array_data = np.resize(self._array_data,
                                         shape).astype('<u4', order='F')
            self._array_data.fill(0)
        if self._mode != SIMULATION:
            self._cache.put(self, 'fmtstr', self._fmtstr(value))
        self.log.debug('%r', self._arraydesc)
        self.log.debug('stepsize: %f', self._step_size)

    def doWriteResosteps(self, value):
        self._set_resosteps(value)

    def _fmtstr(self, value):
        return 'step = %d' + '/%d, ' % value + \
            self._attached_detector.doReadFmtstr()

    def doReadFmtstr(self):
        return self._fmtstr(self.resosteps)

    def doEstimateTime(self, elapsed):
        # TODO calculate the estimated time better in case of monitor counting
        # the _time_preset value is only value for time counting mode
        mspeed = self._attached_motor.speed or 1.0
        steptime = (self.range / mspeed) / self.resosteps
        if MeasureSequencer.status(self, 0)[0] == status.BUSY:
            step = int(abs(self._attached_motor.read() - self._startpos) /
                       self._step_size)
            ret = (steptime + self._time_preset) * (self.resosteps - step)
        else:
            ret = (steptime + self._time_preset) * self.resosteps
        detTime = self._attached_detector.doEstimateTime(elapsed)
        ret += detTime if detTime is not None else 0.
        return ret

    def valueInfo(self):
        _val_info = Value('step', unit='', type='other', fmtstr='%d' + '/%d' %
                          self.resosteps),
        return _val_info + self._attached_detector.valueInfo()

    def arrayInfo(self):
        return self._arraydesc,

    def presetInfo(self):
        return {'resosteps', 'tths'} | self._attached_detector.presetInfo()

    def duringMeasureHook(self, elapsed):
        if self.liveinterval is not None:
            if self._last_live + self.liveinterval < elapsed:
                self._last_live = elapsed
                return LIVE
        return None

    def _stopAction(self, nr):
        self.log.debug('_stopAction at step: %d', nr)
        self._attached_detector.stop()

    def _cleanUp(self):
        if self._seq_was_stopped:
            self._seq_was_stopped = False
            self._set_seq_status(status.OK, 'idle')
Example #8
0
class Watchdog(BaseCacheClient):
    """Main device for running the watchdog service."""

    parameters = {
        'watch':
        Param('The configuration of things to watch',
              type=listof(dictof(str, anytype))),
        'mailreceiverkey':
        Param(
            'Cache key that updates the receivers for '
            'any mail notifiers we have configured',
            type=str),
        # these would be adevs, but adevs don't allow this flexible
        # configuration with dictionaries
        'notifiers':
        Param('Configures the notifiers for each warning type',
              type=dictof(str, listof(str))),
    }

    parameter_overrides = {
        'prefix': Override(mandatory=False, default='nicos/'),
    }

    def doInit(self, mode):
        BaseCacheClient.doInit(self, mode)
        # cache of all interesting keys with current values
        self._keydict = LCDict()
        # put status constants in key dict to simplify status conditions
        for stval, stname in status.statuses.items():
            self._keydict[stname.upper()] = stval
        # set to true during connect action
        self._process_updates = False
        # current setups
        self._setups = set()
        # mapping entry ids to entrys
        self._entries = {}
        # (mangled) key to update mail receivers
        self._mailreceiverkey = self.mailreceiverkey.replace('/', '_').lower()
        # mapping cache keys to entries that check this key
        self._keymap = {'session_mastersetup': set()}
        if self._mailreceiverkey:
            self._keymap[self._mailreceiverkey] = set()
        # current warnings: mapping entry ids to the string description
        self._warnings = OrderedDict()
        # current count loop pause reasons: mapping like self._warnings
        self._pausecount = OrderedDict()

        # create all notifier devices
        self._all_notifiers = []
        self._notifiers = {'': []}
        for key, devnames in iteritems(self.notifiers):
            self._notifiers[key] = notiflist = []
            for devname in devnames:
                dev = session.getDevice(devname, Notifier)
                notiflist.append(dev)
                self._all_notifiers.append(dev)

        # process entries in the default watchlist
        for entry_dict in self.watch:
            self._add_entry(entry_dict, 'watchdog')

        # start a thread checking for modification of the setup file
        createThread('refresh checker', self._checker)

    def _checker(self):
        setupname = session.explicit_setups[0]
        fn = session._setup_info[setupname]['filenames']
        watchFileContent(fn, self.log)
        self.log.info('setup file changed; restarting watchdog process')
        os.execv(sys.executable, [sys.executable] + sys.argv)

    def _add_entry(self, entryd, source):
        entryd = dict(entryd)  # convert back from readonlydict
        logprefix = 'entry %s from setup %r' % (entryd, source)

        # sanity-check the input
        if not entryd.get('condition'):
            self.log.warning('%s: missing "condition" key', logprefix)
            return
        if not entryd.get('message'):
            self.log.warning('%s: missing "message" key', logprefix)
            return
        if entryd.get('scriptaction') not in (None, 'pausecount', 'stop',
                                              'immediatestop'):
            self.log.warning(
                '%s: scriptaction is invalid, needs '
                "to be one of 'pausecount', 'stop' or "
                "'immediatestop'", logprefix)
            entryd.pop('scriptaction')

        entry = Entry(entryd)
        entry.from_setup = source

        if entry.id in self._entries:
            self.log.error('%s: duplicate entry, ignoring', logprefix)
            return

        if entry.type and entry.type not in self._notifiers:
            log_msg = (
                '%s: the condition type %r is not valid, must be '
                'one of %r' %
                (logprefix, entry.type, ', '.join(map(repr, self._notifiers))))
            if 'default' in self._notifiers:
                self.log.warning(log_msg + '; using default')
                entry.type = 'default'
            else:
                self.log.warning(log_msg + '; ignoring notifiers')
                entry.type = ''

        try:
            cond = Expression(self.log, entry.condition, entry.setup)
            if entry.gracetime:
                cond = DelayedTrigger(self.log, cond, entry.gracetime)
            if entry.precondition:
                precond = Expression(self.log, entry.precondition, entry.setup)
                if entry.precondtime:
                    precond = DelayedTrigger(self.log, precond,
                                             entry.precondtime)
                cond = Precondition(self.log, precond, cond)
            if not entry.enabled:
                cond.enabled = False
            entry.cond_obj = cond

        except Exception:
            self.log.error(
                '%s: could not construct condition, ignoring '
                'this condition', logprefix)
            return

        for key in cond.interesting_keys():
            self._keymap.setdefault(key, set()).add(entry)
        self._entries[entry.id] = entry

    def _remove_entry(self, eid):
        entry = self._entries.pop(eid, None)
        if entry:
            for key in entry.cond_obj.interesting_keys():
                self._keymap[key].discard(entry)

    # cache client API

    def _connect_action(self):
        # inhibit direct processing of updates
        self._process_updates = False
        try:
            BaseCacheClient._connect_action(self)
            # now process all keys we got
            time = currenttime()
            for key in list(self._keydict):
                try:
                    self._process_key(time, key, self._keydict[key])
                except Exception:
                    self.log.warning('error handling first update for key %s',
                                     key,
                                     exc=1)
        finally:
            self._process_updates = True
        self.storeSysInfo('watchdog')
        self._queue.put('watchdog/%s\n' % OP_SUBSCRIBE)
        self._publish_config()

    def _wait_data(self):
        t = currenttime()
        for entry in itervalues(self._entries):
            if entry.cond_obj.tick(t) or entry.cond_obj.is_expired(t):
                self._check_state(entry, t)

    def _handle_msg(self, time, ttlop, ttl, tsop, key, op, value):
        if key.startswith('watchdog/'):
            self._handle_control_msg(key, cache_load(value), time,
                                     op != OP_TELL)
            return

        key = key[len(self._prefix):].replace('/', '_').lower()

        # do we need the key for conditions?
        if key not in self._keymap:
            return
        expired = op == OP_TELLOLD or not value
        value = None if expired else cache_load(value)
        if not expired:
            self._keydict[key] = value
        else:
            self._keydict.pop(key, None)
        time = float(time)

        if self._process_updates:
            self._process_key(time, key, value)

    # internal watchdog API

    def _handle_control_msg(self, key, value, time, expired):
        if key == 'watchdog/enable':
            time = currenttime()
            for (eid, enabled) in value[1]:
                entry = self._entries.get(eid)
                if entry:
                    entry.cond_obj.enabled = enabled
                    entry.cond_obj.update(time, self._keydict)
                    self._check_state(entry, time)
            self.log.info('updated enabled conditions by user request')
        elif key == 'watchdog/reset':
            # reset all condition enables to their initial state
            # (e.g. due to NewExperiment)
            for entry in itervalues(self._entries):
                if entry.enabled != entry.cond_obj.enabled:
                    entry.cond_obj.enabled = entry.enabled
                    entry.cond_obj.update(time, self._keydict)
                    self._check_state(entry, time)
            self.log.info('enable status of all conditions reset')
        self._publish_config()

    def _publish_config(self):
        # publish current condition info in the cache
        self._put_message('configured', None,
                          [c.serialize() for c in itervalues(self._entries)])

    def _process_key(self, time, key, value):
        # check setups?
        if key == 'session_mastersetup' and value:
            self._setups_updated(time, set(value))
            return
        # update notification targets?
        if key == self._mailreceiverkey and value:
            self._update_mailreceivers(value)
            return
        for entry in self._keymap.get(key, ()):
            entry.cond_obj.update(time, self._keydict)
            self._check_state(entry, time)

    def _setups_updated(self, time, new_setups):
        prev_setups, self._setups = self._setups, new_setups
        # check if we need to remove some conditions
        for entry in listvalues(self._entries):
            if entry.from_setup != 'watchdog':
                if entry.from_setup not in self._setups:
                    self._remove_entry(entry.id)
        # check if we need to add some conditions
        session.readSetups()  # refresh setup info
        for new_setup in self._setups - prev_setups:
            info = session._setup_info.get(new_setup)
            if info and info['watch_conditions']:
                self.log.info('adding conditions from setup %s', new_setup)
                for entry_dict in info['watch_conditions']:
                    self._add_entry(entry_dict, new_setup)
        # trigger an update of all conditions
        for entry in itervalues(self._entries):
            entry.cond_obj.new_setups(self._setups)
            entry.cond_obj.update(time, self._keydict)
            self._check_state(entry, time)
        # update everyone els
        self._publish_config()
        self.log.info('new setups list: %s', ', '.join(self._setups))

    def _check_state(self, entry, time):
        """Check if the state of this entry changed and we need to
        emit a warning or a clear it.
        """
        eid = entry.id
        if entry.cond_obj.is_expired(time):
            if eid not in self._warnings or self._warnings[eid][0]:
                self._emit_expired_warning(entry)
        elif entry.cond_obj.triggered:
            if eid not in self._warnings or not self._warnings[eid][0]:
                self._emit_warning(entry)
        else:
            if eid in self._warnings:
                self._clear_warning(entry)
        self.log.debug('new conditions: %s', self._warnings)

    def _emit_warning(self, entry):
        """Emit a warning that this condition is now triggered."""
        self.log.info('got a new warning for %r', entry)
        warning_desc = strftime('%Y-%m-%d %H:%M') + ' -- ' + entry.message
        if entry.action:
            warning_desc += ' -- executing %r' % entry.action
        if entry.scriptaction == 'pausecount':
            self._pausecount[entry.id] = entry.message
            self._update_pausecount_str()
            warning_desc += ' -- counting paused'
        elif entry.scriptaction:
            self._put_message('scriptaction', entry,
                              (entry.scriptaction, entry.message))
        if entry.type:
            for notifier in self._notifiers[entry.type]:
                notifier.send('New warning from NICOS', warning_desc)
        self._put_message('warning', entry, entry.message)
        self._warnings[entry.id] = (True, warning_desc)
        self._update_warnings_str()
        if entry.action:
            self._put_message('action', entry, entry.action)
            self._spawn_action(entry.action)

    def _emit_expired_warning(self, entry):
        """Emit a warning that this condition has missing information."""
        self.log.info('value(s) missing for %r', entry)
        warning_desc = (strftime('%Y-%m-%d %H:%M') +
                        ' -- current value(s) missing, '
                        'cannot check condition %r' % entry.condition)
        # warn that we cannot check the condition anymore
        if entry.type:
            for notifier in self._notifiers[entry.type]:
                notifier.send('New warning from NICOS', warning_desc)
        self._put_message('warning', entry, warning_desc)
        self._warnings[entry.id] = (False, warning_desc)
        self._update_warnings_str()

    def _clear_warning(self, entry):
        """Clear a previously emitted warning for this condition."""
        self.log.info('condition %r normal again', entry)
        was_real_warning = self._warnings.pop(entry.id)[0]
        if entry.scriptaction == 'pausecount':
            self._pausecount.pop(entry.id, None)
            self._update_pausecount_str()
        self._update_warnings_str()

        if was_real_warning:
            self._put_message('resolved', entry, '')
            if entry.okmessage and entry.type:
                msg = strftime('%Y-%m-%d %H:%M -- ')
                msg += '%s\n\nWarning was: %s' % (entry.okmessage,
                                                  entry.message)
                for notifier in self._notifiers[entry.type]:
                    notifier.send('NICOS warning resolved', msg)
            if entry.okaction:
                self._put_message('action', entry, entry.okaction)
                self._spawn_action(entry.okaction)

    # internal helper methods

    def _put_message(self, msgtype, entry, message):
        if entry is not None:
            message = [currenttime(), message, entry.id]
        self._queue.put('watchdog/%s%s%s\n' %
                        (msgtype, OP_TELL, cache_dump(message)))

    def _update_mailreceivers(self, emails):
        self.log.info('updating any Mailer receivers to %s', emails)
        for notifier in self._all_notifiers:
            if isinstance(notifier, Mailer):
                # we're in slave mode, so _setROParam is necessary to set params
                notifier._setROParam('receivers', emails)

    def _update_warnings_str(self):
        self._put_message('warnings', None,
                          '\n'.join(v[1] for v in self._warnings.values()))

    def _update_pausecount_str(self):
        self._put_message('pausecount', None,
                          ', '.join(self._pausecount.values()))

    def _spawn_action(self, action):
        self.log.warning('will execute action %r', action)
        script = path.join(config.nicos_root, 'bin', 'nicos-script')
        createSubprocess([
            sys.executable,
            script,
            '-M',  # start in maintenance mode
            '-S',
            '60',  # abort after 60 seconds
            '-A',
            'watchdog-action',  # appname for the logfiles
            ','.join(self._setups),  # setups to load
            action
        ])  # code to execute
Example #9
0
class SatBox(HasTimeout, Moveable):
    """
    Device Object for PANDA's Attenuator

    controlled by a WUT-device via a ModBusTCP interface via a ModBus TACO
    Server.
    """

    valuetype = int

    attached_devices = {
        'input': Attach('Endswitch input', Readable),
        'output': Attach('Output', Moveable),
    }

    parameters = {
        'blades':
        Param('Thickness of the blades, starting with lowest bit',
              type=listof(floatrange(0, 1000)),
              mandatory=True),
        'readout':
        Param('Determine blade state from output or from switches',
              type=oneof('switches', 'outputs'),
              mandatory=True,
              default='output',
              chatty=True),
    }

    parameter_overrides = {
        'timeout': Override(default=5),
    }

    def _readOutputs(self, maxage=0):
        # just read back the OUTPUT values
        return bits(self._attached_output.read(), len(self.blades))

    def _readSwitches(self):
        # deduce blade state from switches
        state = bits(self._attached_input.read(), len(self.blades) * 2)
        realstate = []
        for i in range(0, len(state), 2):
            bladestate = state[i:i + 2]
            if bladestate == (0, 1):
                realstate.append(1)
            elif bladestate == (1, 0):
                realstate.append(0)
            else:
                realstate.append(None)
        return tuple(realstate)

    def doRead(self, maxage=0):
        bladestate = self._readSwitches() if self.readout == 'switches' else \
            self._readOutputs()
        # only sum up blades which are used for sure (0/None->ignore)
        return sum(b * r for b, r in zip(self.blades, bladestate) if r)

    def doStatus(self, maxage=0):
        if self.readout == 'outputs':
            return status.OK, ''
        if self._readSwitches() == self._readOutputs():
            return status.OK, ''
        return status.BUSY, 'moving'

    def doStart(self, rpos):
        if rpos > sum(self.blades):
            raise InvalidValueError(
                self,
                'Value %d too big!, maximum is %d' % (rpos, sum(self.blades)))
        which = 0
        pos = rpos
        # start with biggest blade and work downwards, ignoring disabled blades
        for bladewidth in reversed(self.blades):
            if bladewidth and pos >= bladewidth:
                which |= 1 << self.blades.index(bladewidth)
                pos -= bladewidth
        if pos != 0:
            self.log.warning('Value %d impossible, trying %d instead!', rpos,
                             rpos + 1)
            return self.start(rpos + 1)
        self._attached_output.move(which)
        if self.readout == 'output':
            # if we have no readback, give blades time to react
            session.delay(1)

    def doIsAllowed(self, target):
        if not (0 <= target <= sum(self.blades)):
            return False, 'Value outside range 0..%d' % sum(self.blades)
        return True, ''
Example #10
0
class Chopper(Moveable):
    """Switcher for the TOF setting.

    This controls the chopper phase and opening angle, as well as the TOF slice
    settings for the detector.  Presets depend on the target wavelength as well
    as the detector position.
    """

    hardware_access = False

    attached_devices = {
        'selector':    Attach('Selector preset switcher', SelectorSwitcher),
        'det_pos':     Attach('Detector preset switcher',
                              DetectorPosSwitcherMixin),
        'daq':         Attach('KWSDetector device', KWSDetector),
        'params':      Attach('Chopper param device', Moveable),
    }

    parameters = {
        'resolutions': Param('Possible values for the resolution', unit='%',
                             type=listof(float), mandatory=True),
        'channels':    Param('Desired number of TOF channels',
                             # TODO: max channels?
                             type=intrange(1, 1024), default=64,
                             settable=True),
        'shade':       Param('Desired overlap of spectrum edges',
                             type=floatrange(0.0, 1.0), default=0.0,
                             settable=True, category='general'),
        'tauoffset':   Param('Additional offset for time of flight',
                             type=floatrange(0.0), default=0.0, settable=True,
                             category='general'),
        'nmax':        Param('Maximum number of acquisition frames',
                             type=intrange(1, 128), default=25, settable=True),
        'calcresult':  Param('Last calculated setting',
                             type=tupleof(float, float, int), settable=True,
                             internal=True),
    }

    parameter_overrides = {
        'unit':        Override(default='', mandatory=False),
    }

    def doInit(self, mode):
        self.valuetype = oneof('off', 'manual',
                               *('%.1f%%' % v for v in self.resolutions))

    def _getWaiters(self):
        return [self._attached_params]

    def doUpdateChannels(self, value):
        # invalidate last calculated result when changing these parameters
        if self._mode == MASTER and 'channels' in self._params:
            self.calcresult = (0, 0, 0)

    def doUpdateShade(self, value):
        if self._mode == MASTER and 'shade' in self._params:
            self.calcresult = (0, 0, 0)

    def doUpdateTauoffset(self, value):
        if self._mode == MASTER and 'tauoffset' in self._params:
            self.calcresult = (0, 0, 0)

    def doUpdateNmax(self, value):
        if self._mode == MASTER and 'nmax' in self._params:
            self.calcresult = (0, 0, 0)

    def doStart(self, value):
        if value == 'off':
            if self._attached_daq.mode == 'tof':  # don't touch realtime
                self._attached_daq.mode = 'standard'
            self._attached_params.start((0, 0))
            return
        elif value == 'manual':
            if self._attached_daq.mode == 'standard':
                self._attached_daq.mode = 'tof'
            return
        reso = float(value.strip('%')) / 100.0

        sel_target = self._attached_selector.target
        det_target = self._attached_det_pos.target
        try:
            lam = self._attached_selector.presets[sel_target]['lam']
            spread = self._attached_selector.presets[sel_target]['spread']
            det_z = self._attached_det_pos.presets[sel_target][det_target]['z']
            det_offset = self._attached_det_pos.offsets[det_z]
        except KeyError:
            raise PositionError(self, 'cannot calculate chopper settings: '
                                'selector or detector device not at preset')

        self.log.debug('chopper calc inputs: reso=%f, lam=%f, spread=%f, '
                       'det_z=%f', reso, lam, spread, det_z)
        freq, opening = calculate(lam, spread, reso, self.shade,
                                  20.0 + det_z + det_offset,
                                  self.tauoffset, self.nmax)
        self.log.debug('calculated chopper settings: freq=%f, opening=%f',
                       freq, opening)
        interval = int(1000000.0 / (freq * self.channels))
        self.calcresult = freq, opening, interval

        self._attached_params.start((freq, opening))

        self._attached_daq.mode = 'tof'
        self._attached_daq.tofchannels = self.channels
        self._attached_daq.tofinterval = interval
        self._attached_daq.tofprogression = 1.0  # linear channel widths
        self._attached_daq.tofcustom = []  # no custom channels

    def doRead(self, maxage=0):
        if self.target == 'manual':
            return 'manual'
        params = self._attached_params.read(maxage)
        if params[0] < 1.0:
            return 'off'
        # TODO: take from isAtTarget
        if abs(params[0] - self.calcresult[0]) < self._attached_params._attached_freq1.precision and \
           abs(params[1] - self.calcresult[1]) < self._attached_params._attached_phase1.precision:
            if self._attached_daq.mode == 'tof' and \
               self._attached_daq.tofchannels == self.channels and \
               self._attached_daq.tofinterval == self.calcresult[2] and \
               self._attached_daq.tofprogression == 1.0 and \
               self._attached_daq.tofcustom == []:
                return self.target
        return 'unknown'

    def doStatus(self, maxage=0):
        move_status = self._attached_params.status(maxage)
        if move_status[0] not in (status.OK, status.WARN):
            return move_status
        r = self.read(maxage)
        if r == 'unknown':
            return (status.NOTREACHED, 'unconfigured chopper frequency/phase '
                    'or still moving')
        return status.OK, ''
Example #11
0
class Notifier(Device):
    """Base class for all notification systems.

    This class has a general notion of a "receiver" as an entity that
    notifications can be sent to.  If this is an address, a channel name,
    or something else, is determined by the specific subclass.

    Do not use directly.
    """

    parameters = {
        'minruntime':
        Param(
            'Minimum runtime of a command before a failure '
            'is sent over the notifier',
            type=float,
            unit='s',
            default=300,
            settable=True),
        'ratelimit':
        Param('Minimum time between sending two notifications',
              default=60,
              type=floatrange(30),
              unit='s',
              settable=True),
        'private':
        Param('True if notifier receivers are private, '
              'not user-settable',
              type=bool,
              default=False),
        'receivers':
        Param('Receiver addresses', type=listof(str), settable=True),
        'copies':
        Param(
            'Addresses that get a copy of messages, a list of '
            'tuples: (address, messagelevel: "all" or '
            '"important")',
            type=listof(tupleof(str, oneof('all', 'important'))),
            settable=True),
        'subject':
        Param('Subject prefix', type=str, default='NICOS'),
    }

    parameter_overrides = {
        'lowlevel': Override(default=True, mandatory=False),
    }

    _lastsent = 0

    def _checkRateLimit(self):
        """Implement rate limiting."""
        ct = currenttime()
        if ct - self._lastsent < self.ratelimit:
            self.log.info('rate limiting: not sending notification')
            return False
        self._lastsent = ct
        return True

    @usermethod
    def send(self, subject, body, what=None, short=None, important=True):
        """Send a notification."""
        raise NotImplementedError('send() must be implemented in subclasses')

    @usermethod
    def sendConditionally(self,
                          runtime,
                          subject,
                          body,
                          what=None,
                          short=None,
                          important=True):
        """Send a notification if the given runtime is large enough."""
        if runtime > self.minruntime:
            self.send(subject, body, what, short, important)

    def reset(self):
        """Reset experiment-specific configuration.  Does nothing by default."""

    def doWriteReceivers(self, value):
        if self.private and not session.checkAccess(ADMIN):
            raise AccessError(
                self, 'Only admins can change the receiver list '
                'for this notifier')
        return value

    def doWriteCopies(self, value):
        if self.private and not session.checkAccess(ADMIN):
            raise AccessError(
                self, 'Only admins can change the receiver list '
                'for this notifier')
        return value

    def _getAllRecipients(self, important):
        receivers = list(self.receivers)
        receivers.extend(addr for (addr, level) in self.copies
                         if level == 'all' or important)
        return receivers
Example #12
0
class CacheKafkaForwarder(ForwarderBase, Device):
    parameters = {
        'brokers': Param('List of kafka hosts to be connected',
                         type=listof(host(defaultport=9092)),
                         mandatory=True, preinit=True, userparam=False
                         ),
        'output_topic': Param('The topic to send data to',
                              type=str, userparam=False, settable=False,
                              mandatory=True,
                              ),
        'dev_ignore': Param('Devices to ignore; if empty, all devices are '
                            'accepted', default=[],
                            type=listof(str),
                            ),
        'update_interval': Param('Time interval (in secs.) to send regular updates',
                                 default=10.0, type=float, settable=False)

    }
    parameter_overrides = {
        # Key filters are irrelevant for this collector
        'keyfilters': Override(default=[], settable=False),
    }

    def doInit(self, mode):
        self._dev_to_value_cache = {}
        self._dev_to_status_cache = {}
        self._dev_to_timestamp_cache = {}
        self._producer = None
        self._lock = Lock()

        self._initFilters()
        self._queue = queue.Queue(1000)
        self._worker = createThread('cache_to_kafka', self._processQueue,
                                    start=False)
        self._regular_update_worker = createThread(
            'send_regular_updates', self._poll_updates, start=False)
        while not self._producer:
            try:
                self._producer = \
                    KafkaProducer(bootstrap_servers=self._config['brokers'])
            except Exception as error:
                self.log.error(
                    'Could not connect to Kafka - will try again soon: %s',
                    error)
                time.sleep(5)
        self.log.info('Connected to Kafka brokers %s', self._config['brokers'])

    def _startWorker(self):
        self._worker.start()
        self._regular_update_worker.start()

    def _poll_updates(self):
        while True:
            with self._lock:
                for dev_name in set(self._dev_to_value_cache.keys()).union(
                        self._dev_to_status_cache.keys()):
                    if self._relevant_properties_available(dev_name):
                        self._push_to_queue(
                            dev_name,
                            self._dev_to_value_cache[dev_name],
                            self._dev_to_status_cache[dev_name],
                            self._dev_to_timestamp_cache[dev_name])

            time.sleep(self.update_interval)

    def _checkKey(self, key):
        if key.endswith('/value') or key.endswith('/status'):
            return True
        return False

    def _checkDevice(self, name):
        if name not in self.dev_ignore:
            return True
        return False

    def _putChange(self, timestamp, ttl, key, value):
        if value is None:
            return
        dev_name = key[0:key.index('/')]
        if not self._checkKey(key) or not self._checkDevice(dev_name):
            return
        self.log.debug('_putChange %s %s %s', key, value, time)

        with self._lock:
            if key.endswith('value'):
                self._dev_to_value_cache[dev_name] = value
            else:
                self._dev_to_status_cache[dev_name] = convert_status(value)

            timestamp_ns = int(float(timestamp) * 10 ** 9)
            self._dev_to_timestamp_cache[dev_name] = timestamp_ns
            # Don't send until have at least one reading for both value and status
            if self._relevant_properties_available(dev_name):
                self._push_to_queue(
                    dev_name,
                    self._dev_to_value_cache[dev_name],
                    self._dev_to_status_cache[dev_name],
                    timestamp_ns,)

    def _relevant_properties_available(self, dev_name):
        return dev_name in self._dev_to_value_cache \
            and dev_name in self._dev_to_status_cache \
            and dev_name in self._dev_to_timestamp_cache

    def _push_to_queue(self, dev_name, value, status, timestamp):
        try:
            self._queue.put_nowait((dev_name, value, status, timestamp))
        except queue.Full:
            self.log.error('Queue full, so discarding older value(s)')
            self._queue.get()
            self._queue.put((dev_name, value, status, timestamp))
            self._queue.task_done()

    def _processQueue(self):
        while True:
            name, value, status, timestamp = self._queue.get()
            try:
                # Convert value from string to correct type
                value = cache_load(value)
                if not isinstance(value, str):
                    # Policy decision: don't send strings via f142
                    buffer = to_f142(name, value, status, timestamp)
                    self._send_to_kafka(buffer)
            except Exception as error:
                self.log.error('Could not forward data: %s', error)
            self._queue.task_done()

    def doShutdown(self):
        self._producer.close()

    def _send_to_kafka(self, buffer):
        self._producer.send(self.output_topic, buffer)
        self._producer.flush(timeout=3)
Example #13
0
class SXTalSample(Sample):
    parameters = {
        'a':
        Param('a', type=float, category='sample', settable=True),
        'b':
        Param('b', type=float, category='sample', settable=True),
        'c':
        Param('c', type=float, category='sample', settable=True),
        'alpha':
        Param('alpha',
              type=floatrange(1., 179.),
              category='sample',
              settable=True),
        'beta':
        Param('beta',
              type=floatrange(1., 179.),
              category='sample',
              settable=True),
        'gamma':
        Param('gamma',
              type=floatrange(1., 179.),
              category='sample',
              settable=True),
        'ubmatrix':
        Param('UB matrix',
              type=listof(float),
              category='sample',
              settable=True,
              userparam=True),
        'bravais':
        Param('Bravais lattice',
              type=oneof(*symmetry.Bravais.conditions),
              settable=True,
              default='P',
              category='sample'),
        'laue':
        Param('Laue group',
              type=oneof(*symmetry.symbols),
              settable=True,
              default='1',
              category='sample'),
        'reflist':
        Param('The name of the default reflection '
              'list to operate upon',
              type=str,
              userparam=True),
    }

    attached_devices = {
        'reflists':
        Attach('List of available reflection lists',
               devclass=ReflexList,
               multiple=True,
               optional=False),
    }

    def clear(self):
        """Clear experiment-specific information."""
        Sample.clear(self)
        self.ubmatrix = None
        for rfl in self._attached_reflists:
            rfl.clear()

    def new(self, parameters):
        self.clear()
        # pylint: disable=pointless-string-statement
        """Accepts several ways to spell new cell params."""
        lattice = parameters.pop('lattice', None)
        if lattice is not None:
            try:
                parameters['a'], parameters['b'], parameters['c'] = lattice
            except Exception:
                self.log.warning('invalid lattice spec ignored, should be '
                                 '[a, b, c]')
        angles = parameters.pop('angles', None)
        if angles is not None:
            try:
                parameters['alpha'], parameters['beta'], \
                    parameters['gamma'] = angles
            except Exception:
                self.log.warning('invalid angles spec ignored, should be '
                                 '[alpha, beta, gamma]')
        self.a = parameters.pop('a', None)
        if self.a is None:
            if 'cell' not in parameters:
                self.log.warning('using dummy lattice constant of 5 A')
            self.a = 5.0
        self.b = parameters.pop('b', self.a)
        self.c = parameters.pop('c', self.a)
        self.alpha = parameters.pop('alpha', 90.0)
        self.beta = parameters.pop('beta', 90.0)
        self.gamma = parameters.pop('gamma', 90.0)
        self.bravais = parameters.pop('bravais', 'P')
        self.laue = parameters.pop('laue', '1')
        Sample.new(self, parameters)

    def _applyParams(self, number, parameters):
        Sample._applyParams(self, number, parameters)

    def doWriteUbmatrix(self, ub):
        if not ub:
            return
        if not isinstance(ub, list):
            raise ValueError('Expecting list of 9 values')
        if len(ub) != 9:
            raise ValueError('Expected 9 values')

    def doWriteReflist(self, name):
        if name not in self._adevs:
            raise InvalidValueError('% is no known reflection list' % name)
        if not isinstance(self._adevs[name], ReflexList):
            raise InvalidValueError('%s is not a reflection list' % name)

    def getCell(self):
        return Cell(self.a, self.b, self.c, self.alpha, self.beta, self.gamma)

    def getUB(self):
        if self.ubmatrix:
            ub = np.array(self.ubmatrix, dtype='float64')
            ub = ub.reshape((3, 3))
            return ub
        # Return B instead when no UB available
        cell = self.getCell()
        ub = calculateBMatrix(cell)
        if isinstance(session.experiment, TASSXTal):
            return ub
        else:
            return ub / (2. * np.pi)

    def getRefList(self, name=None):
        if not name:
            name = self.reflist
        for reflist in self._attached_reflists:
            if reflist.name == name:
                return reflist
Example #14
0
class SXTalSample(Sample):
    parameters = {
        'cell':
        Param('Unit cell with matrix',
              type=SXTalCellType,
              settable=True,
              mandatory=False,
              default=SXTalCell.fromabc(5)),
        'a':
        Param('a', type=float, category='sample'),
        'b':
        Param('b', type=float, category='sample'),
        'c':
        Param('c', type=float, category='sample'),
        'alpha':
        Param('alpha', type=floatrange(1., 179.), category='sample'),
        'beta':
        Param('beta', type=floatrange(1., 179.), category='sample'),
        'gamma':
        Param('gamma', type=floatrange(1., 179.), category='sample'),
        'rmat':
        Param('rmat',
              type=listof(listof(float)),
              default=None,
              userparam=False),
        'ubmatrix':
        Param('UB matrix (rmat^T)',
              type=listof(listof(float)),
              category='sample'),
        'bravais':
        Param('Bravais lattice',
              type=oneof(*symmetry.Bravais.conditions),
              settable=True,
              default='P',
              category='sample'),
        'laue':
        Param('Laue group',
              type=oneof(*symmetry.symbols),
              settable=True,
              default='1',
              category='sample'),
        'peaklists':
        Param('Lists of peaks for scanning',
              internal=True,
              type=dictof(str, list),
              settable=True),
        'poslists':
        Param('Lists of positions for indexing',
              internal=True,
              type=dictof(str, list),
              settable=True),
    }

    def clear(self):
        """Clear experiment-specific information."""
        Sample.clear(self)
        self.cell = SXTalCell.fromabc(5)
        self.peaklists = {}
        self.poslists = {}

    def new(self, parameters):
        """Accepts several ways to spell new cell params."""
        lattice = parameters.pop('lattice', None)
        if lattice is not None:
            try:
                parameters['a'], parameters['b'], parameters['c'] = lattice
            except Exception:
                self.log.warning('invalid lattice spec ignored, should be '
                                 '[a, b, c]')
        angles = parameters.pop('angles', None)
        if angles is not None:
            try:
                parameters['alpha'], parameters['beta'], \
                    parameters['gamma'] = angles
            except Exception:
                self.log.warning('invalid angles spec ignored, should be '
                                 '[alpha, beta, gamma]')
        a = parameters.pop('a', None)
        if a is None:
            if 'cell' not in parameters:
                self.log.warning('using dummy lattice constant of 5 A')
            a = 5.0
        b = parameters.pop('b', a)
        c = parameters.pop('c', a)
        alpha = parameters.pop('alpha', 90.0)
        beta = parameters.pop('beta', 90.0)
        gamma = parameters.pop('gamma', 90.0)
        # TODO: map spacegroup/bravais/laue with triple-axis
        bravais = parameters.pop('bravais', 'P')
        laue = parameters.pop('laue', '1')
        if 'cell' not in parameters:
            parameters['cell'] = [a, b, c, alpha, beta, gamma, bravais, laue]
        Sample.new(self, parameters)

    def _applyParams(self, number, parameters):
        Sample._applyParams(self, number, parameters)
        if 'cell' in parameters:
            self.cell = parameters['cell']

    def doReadBravais(self):
        return self.cell.bravais.bravais

    def doWriteBravais(self, value):
        self.cell.bravais = symmetry.Bravais(value)

    def doReadLaue(self):
        return self.cell.laue.laue

    def doWriteLaue(self, value):
        self.cell.laue = symmetry.Laue(value)

    def doWriteCell(self, cell):
        params = cell.cellparams()
        self._setROParam('a', params.a)
        self._setROParam('b', params.b)
        self._setROParam('c', params.c)
        self._setROParam('alpha', params.alpha)
        self._setROParam('beta', params.beta)
        self._setROParam('gamma', params.gamma)
        self._setROParam('rmat', cell.rmat.tolist())
        self._setROParam('ubmatrix', cell.rmat.T.tolist())
        self._setROParam('bravais', cell.bravais.bravais)
        self._setROParam('laue', cell.laue.laue)

        self.log.info('New sample cell set. Parameters:')
        self.log.info('a = %8.3f  b = %8.3f  c = %8.3f', params.a, params.b,
                      params.c)
        self.log.info('alpha = %8.3f  beta = %8.3f  gamma = %8.3f',
                      params.alpha, params.beta, params.gamma)
        self.log.info('Bravais: %s  Laue: %s', cell.bravais.bravais,
                      cell.laue.laue)
        self.log.info('UB matrix:')
        for row in cell.rmat.T:
            self.log.info('%8.4f %8.4f %8.4f', *row)
Example #15
0
class NiagShutter(HasTimeout, MappedMoveable):
    """ Shutter device for the NIAG beam lines.

    The shutter devices operate using 4 digital IOs (2 outputs and 2 inputs):
        - The digital outputs are used to open and close the shutter by sending
          a pulse
        - The digital inputs indicate whether the shutter is opened, closed
          or in interstage (in the latter case, both inputs are 0)

    An additional digital input indicates whether the shutter is enabled

    Part of the code used for this class was copied or adapted from
    the MultiSwitcher class written by Georg Brandl and Enrico Faulhaber. """

    # the 5 digital IOs (2 pulse outputs, 3 inputs) used to control the shutter
    attached_devices = {
        'do_open': Attach('Output to open the shutter', Moveable),
        'do_close': Attach('Output to close the shutter', Moveable),
        'is_open': Attach('Input to check if shutter is open', Readable),
        'is_closed': Attach('Input to check if shutter is closed', Readable),
        'is_enabled': Attach('Input to check if shutter is enabled', Readable),
    }
    """ copied from the MultiSwitcher class """
    parameters = {
        'blockingmove':
        Param('Should we wait for the move to finish?',
              mandatory=False,
              default=True,
              settable=True,
              type=bool),
    }
    """ copied from the MultiSwitcher class """
    parameter_overrides = {
        'mapping':
        Override(description='Mapping of state names to N values '
                 'to move the moveables to',
                 type=dictof(anytype, listof(anytype))),
        'fallback':
        Override(userparam=False, type=none_or(anytype), mandatory=False),
    }
    """ copied from the MultiSwitcher class """
    hardware_access = False
    """ first value in target indicates whether to open the shutter, second
    value whether to close it """
    def _startRaw(self, target):
        self._attached_do_open.start(target[0])
        self._attached_do_close.start(target[1])

    # returns a tuple mad of the (opened?, closed?) values

    def _readRaw(self, maxage=0):
        return tuple([
            self._attached_is_open.read(maxage),
            self._attached_is_closed.read(maxage)
        ])

    # based on the method definition from the MultiSwitcher class, simplified
    # because there is no need to define the precision for digital inputs

    def _mapReadValue(self, pos):
        """maps a tuple to one of the configured values"""
        for name, values in self.mapping.items():
            if tuple(pos) == tuple(values):
                return name
        if self.fallback is not None:
            return self.fallback
        raise PositionError(
            self, 'unknown position of %s: %s' %
            (', '.join(str(d) for d in self._adevs), ', '.join(
                d.format(p) for (p, d) in zip(pos, self._adevs))))

    # completion is checked by verifying the feedback values, because
    # the "motion" of the outputs (pulses) is finished before the shutter
    # reaches its final position

    def doIsCompleted(self):
        reached = self.doRead() == self.target
        if reached:
            return True
        else:
            # if the position was not reached, consider that the motion
            # is not complete, unless an error is present """
            stat = self.doStatus()
            return stat[0] < status.BUSY

    # based on the definition of the MultiSwitcher class, extended for the
    # needs of the shutter control

    def doStatus(self, maxage=0):
        move_status = multiStatus(self._adevs, maxage)
        # if any of the underlying devices has an error, return it
        if move_status[0] > status.BUSY:
            return move_status
        try:
            # if the enable bit is not active, return "disabled" status
            if not self._attached_is_enabled.read(maxage):
                return status.DISABLED, 'disabled'
            r = self.doRead(maxage)
            # the the device is in the fallback position, it is considered
            # still moving
            if r == self.fallback:
                return status.BUSY, 'target not yet reached'
            return status.OK, 'idle'
        except PositionError as e:
            return status.NOTREACHED, str(e)

    # only allow to start if the enable bit is active

    def doStart(self, pos):
        if self._attached_is_enabled.read():
            return MappedMoveable.doStart(self, pos)
        raise MoveError(self, 'Device is disabled')

    def doReset(self):
        multiReset(self._adevs)
Example #16
0
class NicosDaemon(Device):
    """
    This class abstracts the main daemon process.
    """

    attached_devices = {
        'authenticators': Attach('The authenticator devices to use for '
                                 'validating users and passwords',
                                 Authenticator, multiple=True),
    }

    parameters = {
        'server':         Param('Address to bind to (host or host[:port])',
                                type=host(defaultport=DEFAULT_PORT),
                                mandatory=True,
                                ext_desc='The default port is ``1301``.'),
        'servercls':      Param('Server class used for creating transports '
                                'to each client',
                                type=str, mandatory=False,
                                default='nicos.services.daemon.proto.classic.'
                                'Server'),
        'serializercls':  Param('Serializer class used for serializing '
                                'messages transported from/to the server',
                                type=str, mandatory=False,
                                default='nicos.protocols.daemon.classic.'
                                'ClassicSerializer'),
        'maxlogins':      Param('Maximum number of simultaneous clients '
                                'served', type=int,
                                default=10),
        'updateinterval': Param('Interval for watch expressions checking and'
                                ' sending updates to the clients',
                                type=float, unit='s', default=0.2),
        'trustedhosts':   Param('A list of trusted hosts allowed to log in',
                                type=listof(str),
                                ext_desc='An empty list means all hosts are '
                                'allowed.'),
        'simmode':        Param('Whether to always start in dry run mode',
                                type=bool),
        'autosimulate':   Param('Whether to simulate scripts when running them',
                                type=bool, default=False)
    }

    def doInit(self, mode):
        # import server and serializer class
        servercls = importString(self.servercls)
        serialcls = importString(self.serializercls)

        self._stoprequest = False
        # the controller represents the internal script execution machinery
        if self.autosimulate and not config.sandbox_simulation:
            raise ConfigurationError('autosimulation configured but sandbox'
                                     ' deactivated')

        self._controller = ExecutionController(self.log, self.emit_event,
                                               'startup', self.simmode,
                                               self.autosimulate)

        # cache log messages emitted so far
        self._messages = []

        host, port = parseHostPort(self.server, DEFAULT_PORT)

        # create server (transport + serializer)
        self._server = servercls(self, (host, port), serialcls())

        self._watch_worker = createThread('daemon watch monitor',
                                          self._watch_entry)

    def _watch_entry(self):
        """
        This thread checks for watch value changes periodically and sends out
        events on changes.
        """
        # pre-fetch attributes for speed
        ctlr, intv, emit, sleep = self._controller, self.updateinterval, \
            self.emit_event, time.sleep
        lastwatch = {}
        while not self._stoprequest:
            sleep(intv)
            # new watch values?
            watch = ctlr.eval_watch_expressions()
            if watch != lastwatch:
                emit('watch', watch)
                lastwatch = watch

    def emit_event(self, event, data):
        """Emit an event to all handlers."""
        self._server.emit(event, data)

    def emit_event_private(self, event, data):
        """Emit an event to only the calling handler."""
        handler = self._controller.get_current_handler()
        if handler:
            self._server.emit(event, data, handler)

    def statusinfo(self):
        self.log.info('got SIGUSR2 - current stacktraces for each thread:')
        active = threading._active
        for tid, frame in listitems(sys._current_frames()):
            if tid in active:
                name = active[tid].getName()
            else:
                name = str(tid)
            self.log.info('%s: %s', name,
                          ''.join(traceback.format_stack(frame)))

    def start(self):
        """Start the daemon's server."""
        self.log.info('NICOS daemon v%s started, starting server on %s',
                      nicos_version, self.server)
        # startup the script thread
        self._controller.start_script_thread()
        self._worker = createThread('daemon server', self._server.start,
                                    args=(self._long_loop_delay,))

    def wait(self):
        while not self._stoprequest:
            time.sleep(self._long_loop_delay)
        self._worker.join()

    def quit(self, signum=None):
        self.log.info('quitting on signal %s...', signum)
        self._stoprequest = True
        self._server.stop()
        self._worker.join()
        self._server.close()

    def current_script(self):
        return self._controller.current_script

    def current_user(self):
        return getattr(self._controller.thread_data, 'user', system_user)

    def get_authenticators(self):
        return self._attached_authenticators
Example #17
0
class Stargate(tango.DigitalOutput):
    """Device for controlling the MIRA-Stargate blocks."""

    valuetype = listof(int)

    parameters = {
        'offset_in':  Param('Offset of digital input values',
                            type=int, mandatory=True),
        'offset_out': Param('Offset of digital output values',
                            type=int, mandatory=True),
        'chevron_att_angles': Param('att angle for shielding elements',
                                    type=listof(listof(int)),
                                    mandatory=True),
    }

    _started = 0

    def doRead(self, maxage=0):
        words = self._dev.ReadOutputWords([self.offset_in, 5])
        bitvals = [words[0], words[2], words[4]]
        chevrons = []

        for bitval in bitvals:
            for _ in range(4):
                chevrons.append(int(bitval & 0b11 == 0b01))
                bitval >>= 2

        return chevrons[:11]

    def doStatus(self, maxage=0):
        if self._started and self._started + 3 > currenttime():
            return status.BUSY, 'moving/waiting'
        return status.OK, ''

    def doStart(self, value):
        bitvals = [0, 0, 0]
        for curidx in range(len(value)):
            curval = value[curidx]

            byteidx = curidx // 4
            bitidx = (curidx % 4) * 2

            if curval:
                bitvals[byteidx] |= (1 << bitidx)
            else:
                bitvals[byteidx] |= (1 << (bitidx+1))

        self._dev.WriteOutputWords([self.offset_out] + bitvals)
        self._started = currenttime()

    def doIsAllowed(self, value):
        if len(value) != 11:
            raise InvalidValueError(self, 'list must have 11 entries')
        # map everything to 0 or 1
        value = [bool(v) for v in value]
        # check allowed positions
        if value == [True] * 11:
            # open everything is allowed
            return True, ''
        if sum(value) > 2:
            return False, 'cannot open more than 2 chevrons'
        if value[0] or value[10]:
            return False, 'cannot open first or last chevron'
        return True, ''

    def doReadFmtstr(self):
        return '[' + ', '.join(['%d'] * 11) + ']'

    def get_chevrons_for_att(self, att):
        chevrons = []

        for curidx in range(len(self.chevron_att_angles)):
            maxmin = self.chevron_att_angles[curidx]

            if len(maxmin) < 2:
                chevrons.append(0)
                continue

            if maxmin[1] < att < maxmin[0]:
                chevrons.append(1)
            else:
                chevrons.append(0)

        return chevrons
Example #18
0
class BoaTable(Device):
    parameters = {
        'standard_devices':
        Param('Standard Devices', type=listof(nicosdev), userparam=False),
        'setups':
        Param('setups on this table',
              type=listof(str),
              userparam=False,
              settable=True,
              default=[]),
        'additional_devices':
        Param('additional devices attached '
              'to the table',
              type=listof(nicosdev),
              default=[],
              settable=True,
              userparam=False),
    }

    def doInit(self, mode):
        self.hardware_access = False

    @usermethod
    def addSetup(self, name):
        if name not in session.loaded_setups:
            raise ValueError('%s not a loaded setup' % name)
        if name not in self.setups:
            tmp = list(self.setups)
            tmp.append(name)
            self.setups = tmp

    @usermethod
    def removeSetup(self, name):
        if name in self.setups:
            tmp = list(self.setups)
            tmp.remove(name)
            self.setups = tmp

    @usermethod
    def addDevice(self, name):
        if name not in session.configured_devices:
            raise ValueError('device %s is not available' % name)
        if name not in self.additional_devices:
            tmp = list(self.additional_devices)
            tmp.append(name)
            self.additional_devices = tmp

    @usermethod
    def removeDevice(self, name):
        if name in self.additional_devices:
            tmp = list(self.additional_devices)
            tmp.remove(name)
            self.additional_devices = tmp

    @usermethod
    def show(self):
        txt = 'Table %s Configuration:\n' % self.doReadName()
        txt += 'Standard Devices:\n'
        txt += '\t %s\n' % ', '.join(self.standard_devices)
        txt += 'Setups:\n'
        txt += '\t%s\n' % ', '.join(self.setups)
        txt += 'Additional Devices\n'
        txt += '\t%s\n' % ', '.join(self.additional_devices)
        txt += 'Total Devices\n'
        txt += '\t%s\n' % ', '.join(self.getTableDevices())
        session.log.info(txt)

    def getTableDevices(self):
        result = list(
            itertools.chain(self.standard_devices, self.additional_devices))
        setupInfo = session.getSetupInfo()
        for setup in self.setups:
            info = setupInfo[setup]
            result = list(itertools.chain(result, info['devices'].keys()))
        return result
Example #19
0
class Collimation(Moveable):
    """Controlling the collimation guides and slits together."""

    hardware_access = False

    attached_devices = {
        'guides': Attach('guides', Moveable),
        'slits': Attach('slit devices', CollimationSlit, multiple=True),
    }

    parameters = {
        'slitpos':
        Param('Positions of the attached slits',
              unit='m',
              type=listof(int),
              mandatory=True),
        'mapping':
        Param('Maps position name to guide and slit w/h',
              type=dictof(str, tupleof(int, float, float)),
              mandatory=True),
    }

    parameter_overrides = {
        'fmtstr': Override(default='%s'),
        'unit': Override(mandatory=False, default=''),
    }

    def doInit(self, mode):
        self.valuetype = oneof(*sorted(self.mapping, key=num_sort))
        if len(self._attached_slits) != len(self.slitpos):
            raise ConfigurationError(
                self, 'number of elements in slitpos '
                'parameter must match number of attached '
                'slit devices')

    def doRead(self, maxage=0):
        def matches(v1, v2):
            return abs(v1 - v2) < 1.0

        guidelen = self._attached_guides.read(maxage)
        if guidelen not in self.slitpos:
            return 'unknown'
        slitvals = [slit.read(maxage) for slit in self._attached_slits]
        for (posname, (pos_guidelen, pos_w, pos_h)) in self.mapping.items():
            if pos_guidelen != guidelen:
                continue
            ok = True
            for (slitpos, slit, (w, h)) in zip(self.slitpos,
                                               self._attached_slits, slitvals):
                if slitpos == pos_guidelen:
                    ok &= matches(w, pos_w) and matches(h, pos_h)
                else:
                    ok &= matches(w, slit.openpos[0]) and \
                        matches(h, slit.openpos[1])
            if ok:
                return posname
        return 'unknown'

    def doStart(self, target):
        pos_guidelen, pos_w, pos_h = self.mapping[target]
        self._attached_guides.start(pos_guidelen)
        for (slitpos, slit) in zip(self.slitpos, self._attached_slits):
            if slitpos == pos_guidelen:
                slit.start((pos_w, pos_h))
            else:
                slit.start(slit.openpos)
Example #20
0
class Image(BaseChannel, QMesyDAQImage):
    """Channel for QMesyDAQ that returns the last image."""

    parameters = {
        'readout':
        Param('Readout mode of the Detector',
              settable=True,
              type=oneof('raw', 'mapped', 'amplitude'),
              default='mapped',
              mandatory=False,
              chatty=True),
        'flipaxes':
        Param('Flip data along these axes after reading from det',
              type=listof(int),
              default=[],
              unit=''),
    }

    taco_class = Detector

    def doInit(self, mode):
        if mode == MASTER:
            self.readArray(FINAL)  # also set arraydesc

    def doStart(self):
        self.readresult = [0]
        BaseChannel.doStart(self)

    def doRead(self, maxage=0):
        return self.readresult

    def doReadArray(self, quality):
        # read data via taco and transform it
        res = self._taco_guard(self._dev.read)
        # first 3 values are sizes of dimensions
        # evaluate shape return correctly reshaped numpy array
        if (res[1], res[2]) in [(1, 1), (0, 1), (1, 0), (0, 0)]:  # 1D array
            self.arraydesc = ArrayDesc('data', shape=(res[0], ), dtype='<u4')
            data = np.fromiter(res[3:], '<u4', res[0])
            self.readresult = [data.sum()]
        elif res[2] in [0, 1]:  # 2D array
            self.arraydesc = ArrayDesc('data',
                                       shape=(res[0], res[1]),
                                       dtype='<u4')
            data = np.fromiter(res[3:], '<u4', res[0] * res[1])
            self.readresult = [data.sum()]
            data = data.reshape((res[0], res[1]), order='C')
        else:  # 3D array
            self.arraydesc = ArrayDesc('data',
                                       shape=(res[0], res[1], res[2]),
                                       dtype='<u4')
            data = np.fromiter(res[3:], '<u4', res[0] * res[1] * res[3])
            self.readresult = [data.sum()]
            data = data.reshape((res[0], res[1], res[2]), order='C')
        for axis in self.flipaxes:
            data = np.flip(data, axis)
        return data

    def doReadIsmaster(self):
        return False

    def doWriteListmode(self, value):
        self._taco_update_resource('writelistmode', '%s' % value)
        return self._taco_guard(self._dev.deviceQueryResource, 'writelistmode')

    def doWriteHistogram(self, value):
        self._taco_update_resource('writehistogram', '%s' % value)
        return self._taco_guard(self._dev.deviceQueryResource,
                                'writehistogram')

    def doWriteReadout(self, value):
        self._taco_update_resource('histogram', '%s' % value)
        return self._taco_guard(self._dev.deviceQueryResource, 'histogram')

    def doWriteListmodefile(self, value):
        self._taco_update_resource('lastlistfile', '%s' % value)
        return self._taco_guard(self._dev.deviceQueryResource, 'lastlistfile')

    def doReadConfigfile(self):
        return self._taco_guard(self._dev.deviceQueryResource, 'configfile')

    def doReadCalibrationfile(self):
        return self._taco_guard(self._dev.deviceQueryResource,
                                'calibrationfile')

#   def doReadListmodefile(self):
#       return self._taco_guard(self._dev.deviceQueryResource, 'lastlistfile')

    def doWriteHistogramfile(self, value):
        self._taco_update_resource('lasthistfile', '%s' % value)
        return self._taco_guard(self._dev.deviceQueryResource, 'lasthistfile')
Example #21
0
class CharmPowerSupply(StringIO, Readable):

    parameters = {
        'transitions':
        Param('transition choices as per entangle device configuration',
              type=listof(str)),
    }

    parameter_overrides = {
        'pollinterval': Override(default=5),  # every 5 seconds
    }

    # pylint: disable=unused-argument
    # pylint: disable=C:invalid-name
    def doInit(self, mode):
        n_items = 0
        x = super().status()
        if x[0] == status.OK:
            n_items = self.availablelines
        delays = []
        cmds = []
        availabletransitions = []
        for i in range(n_items):
            delays.append(0)
            cmd = "TR" + str(i)
            cmds.append(cmd)
            availabletransitions = self.multiCommunicate((delays, cmds))
            self._setROParam('transitions', availabletransitions)
        print(self.transitions)

    # pylint: disable=C:invalid-name
    def doShutdown(self):
        pass

    def read(self, maxage=0):
        s = super().status(maxage)
        sv = self.status_value(s[1])
        return sv[1]

    # pylint: disable=invalid-name
    def status_value(self, s1):
        val = ''
        sta = ''
        i = s1.rfind('[')
        if i >= 0:
            val = s1[i:]
            sta = s1[:i - 1]
            self._cache.put(self, 'value', val, currenttime(), self.maxage)
        return (sta, val)

    def doRead(self, maxage=0):
        return self.read(maxage)

    @usermethod
    def apply(self, *argv):
        """
        applies a transition from the 'transitions' parameter.
        Function argument is index to item in the list.
        """

        if len(argv) == 0:
            index = -1
        else:
            index = argv[0]
        if index == -1:
            msg = ''
            for i in range(len(self.transitions)):
                if i:
                    msg += " ,"
                msg += self.transitions[i] + " (" + str(i) + ")"

            print(msg)
            print('please use 0 based index  as parameter. No action taken.')
        else:
            if ((len(self.transitions) > index) and (index >= 0)):
                cmd = 'APPLY:' + self.transitions[index]
                print(cmd)
                self.write(cmd)
                print(self.transitions[index])
            else:
                print('index out of range. No action taken.')
Example #22
0
class MultiCounter(BaseChannel, PassiveChannel):
    """Channel for QMesyDAQ that allows to access selected channels in a
    multi-channel setup.
    """

    parameters = {
        'channels':
        Param('Tuple of active channels (1 based)',
              settable=True,
              type=listof(int)),
    }

    taco_class = Detector

    def doRead(self, maxage=0):
        if self._mode == SIMULATION:
            res = [0] * (max(self.channels) + 3)
        else:
            # read data via taco and transform it
            res = self._taco_guard(self._dev.read)
        expected = 3 + max(self.channels or [0])
        # first 3 values are sizes of dimensions
        if len(res) >= expected:
            data = res[3:]
            # ch is 1 based, data is 0 based
            total = sum([data[ch - 1] for ch in self.channels])
        else:
            self.log.warning(
                'not enough data returned, check config! '
                '(got %d elements, expected >=%d)', len(res), expected)
            data = None
            total = 0
        resultlist = [total]
        if data is not None:
            for ch in self.channels:
                # ch is 1 based, _data is 0 based
                resultlist.append(data[ch - 1])
        return resultlist

    def valueInfo(self):
        resultlist = [
            Value('ch.sum',
                  unit='cts',
                  errors='sqrt',
                  type='counter',
                  fmtstr='%d')
        ]
        for ch in self.channels:
            resultlist.append(
                Value('ch%d' % ch,
                      unit='cts',
                      errors='sqrt',
                      type='counter',
                      fmtstr='%d'))
        return tuple(resultlist)

    def doReadIsmaster(self):
        return False

    def doReadFmtstr(self):
        resultlist = ['sum %d']
        for ch in self.channels:
            resultlist.append('ch%d %%d' % ch)
        return ', '.join(resultlist)
Example #23
0
class Sans1HV(BaseSequencer):
    valuetype = float
    attached_devices = {
        'supply':
        Attach('NICOS Device for the highvoltage supply', Moveable),
        'discharger':
        Attach('Switch to activate the discharge resistors', Moveable),
        'interlock':
        Attach('Assume IDLE if set and Target is set to 0', Readable),
    }

    parameters = {
        'ramp':
        Param('Current ramp speed (volt per minute)',
              type=int,
              unit='main/min',
              settable=True,
              volatile=True),
        'lasthv':
        Param('When was hv applied last (timestamp)',
              type=float,
              internal=True,
              default=0.0,
              mandatory=False,
              settable=False),
        'maxofftime':
        Param('Maximum allowed Off-time for fast ramp-up',
              type=int,
              unit='s',
              default=4 * 3600),
        'slowramp':
        Param('Slow ramp-up speed (volt per minute)',
              type=int,
              unit='main/min',
              default=120),
        'fastramp':
        Param('Fast ramp-up speed (volt per minute)',
              type=int,
              unit='main/min',
              default=1200),
        'rampsteps':
        Param('Cold-ramp-up sequence (voltage, stabilize_minutes)',
              type=listof(tupleof(int, int)),
              unit='',
              default=[(70, 3), (300, 3), (500, 3), (800, 3), (1100, 3),
                       (1400, 3), (1500, 10)]),
    }

    parameter_overrides = {
        'abslimits': Override(default=(0, 1500), mandatory=False),
        'unit': Override(default='V', mandatory=False, settable=False),
    }

    def _generateSequence(self, target):
        hvdev = self._attached_supply
        disdev = self._attached_discharger
        seq = [SeqMethod(hvdev, 'stop'), SeqMethod(hvdev, 'wait')]

        now = currenttime()

        # below first rampstep is treated as poweroff
        if target <= self.rampsteps[0][0]:
            # fast ramp
            seq.append(SeqParam(hvdev, 'ramp', self.fastramp))
            seq.append(SeqDev(disdev, 1 if self.read() > target else 0))
            if self.read() > self.rampsteps[0][0]:
                seq.append(SeqDev(hvdev, self.rampsteps[0][0]))
            seq.append(SeqMethod(hvdev, 'start', target))
            return seq

        # check off time
        if self.lasthv and now - self.lasthv <= self.maxofftime:
            # short ramp up sequence
            seq.append(SeqParam(hvdev, 'ramp', self.fastramp))
            seq.append(SeqDev(disdev, 1 if self.read() > target else 0))
            seq.append(SeqDev(hvdev, target))
            # retry if target not reached
            seq.append(SeqMethod(hvdev, 'start', target))
            return seq

        # long sequence
        self.log.warning(
            'Voltage was down for more than %.2g hours, '
            'ramping up slowly, be patient!', self.maxofftime / 3600)

        self.log.info(
            'Voltage will be ready around %s',
            strftime('%X',
                     localtime(now + self.doTime(self.doRead(0), target))))

        seq.append(SeqParam(hvdev, 'ramp', self.slowramp))
        seq.append(SeqDev(disdev, 0))
        for voltage, minutes in self.rampsteps:
            # check for last point in sequence
            if target <= voltage:
                seq.append(SeqDev(hvdev, target))
                seq.append(
                    SeqSleep(minutes * 60,
                             'Stabilizing HV for %d minutes' % minutes))
                break
            else:  # append
                seq.append(SeqDev(hvdev, voltage))
                seq.append(
                    SeqSleep(minutes * 60,
                             'Stabilizing HV for %d minutes' % minutes))
        seq.append(SeqDev(hvdev, target))  # be sure...
        seq.append(SeqMethod(hvdev, 'poll'))  # force a read
        return seq

    def _waitFailed(self, step, action, exc_info):
        # signal single retry of the action
        return True

    def _runFailed(self, step, action, exc_info):
        # signal single retry of the action
        return 1

    def doRead(self, maxage=0):
        voltage = self._attached_supply.read(maxage)
        # everything below the last rampstep is no HV yet...
        # bugfix: avoid floating point rounding errors
        if voltage >= (self.rampsteps[-1][0] - 0.001):
            # just assigning does not work inside poller, but we want that!
            self._setROParam('lasthv', currenttime())
        return voltage

    def doIsAllowed(self, target):
        return self._attached_supply.isAllowed(target)

    def doTime(self, pos, target):
        # duration is in minutes...
        duration = abs(pos - target) / float(self.fastramp)
        if not self.lasthv or (currenttime() - self.lasthv > self.maxofftime):
            # cold start
            fromVolts = 0
            for volts, waiting in self.rampsteps:
                duration += waiting
                duration += (min(volts, target) - fromVolts) / float(
                    self.slowramp)
                fromVolts = volts
                if volts >= target:
                    break
        return duration * 60.

    # convenience stuff
    def doReadRamp(self):
        return self._attached_supply.ramp

    def doWriteRamp(self, value):
        self._attached_supply.ramp = value
        return self.doReadRamp()
Example #24
0
class Group(Readable):

    valuetype = bool

    parameters = {
        'bitlist': Param('Definition of a bit list',
                         type=listof(str), settable=False, userparam=False,
                         ),
        'okmask': Param('Mask to define the bits results OK status',
                        type=int, settable=False, userparam=False,
                        ),
    }

    attached_devices = {
        'shs': Attach('shs', Readable),
    }

    parameter_overrides = {
        'unit': Override(default='', volatile=True, mandatory=False),
        'fmtstr': Override(default='%s'),
    }

    def doReadUnit(self):
        return ''

    _register = {
        'Shutter':                              (0, 0),  # 0x0000
        'Ampeltest_inv':                        (3, 0),  # 0x0001
        'Betreten_Verboten_inv':                (3, 1),  # 0x0002
        'Hupentest_inv':                        (3, 2),  # 0x0004
        'Schluesselschalter_Wartung_inv':       (3, 3),  # 0x0008
        'Tuer_PO_auf':                          (3, 6),  # 0x0040
        'Tuer_PO_zu':                           (3, 7),  # 0x0080
        'Schnellschluss-Shutter_auf':           (3, 8),  # 0x0100
        'Schnellschluss-Shutter_zu':            (3, 9),  # 0x0200
        '6-fach-Shutter_auf':                   (3, 10),  # 0x0400
        '6-fach-Shutter_zu':                    (3, 11),  # 0x0800
        'Verbindung_zu_Warte_iO':               (3, 12),  # 0x1000
        'Freigabe_von_Warte_fuer_ESShutter':    (3, 13),  # 0x2000
        'Instrumentenverantwortlicher':         (3, 14),  # 0x4000
        'Not-Aus_Kreis_inv':                    (3, 15),  # 0x8000
        'Verbindungstuer':                      (4, 8),  # 0x0100
        'Tuer_SR_auf':                          (4, 10),  # 0x0400
        'Tuer_SR_zu':                           (4, 11),  # 0x0800
        'externer_User_Kontakt_A':              (5, 0),  # 0x0001
        'externer_User_Kontakt_B':              (5, 1),  # 0x0002
        'PO-Aus-Schalter_1':                    (5, 2),  # 0x0004
        'PO-Aus-Schalter_2':                    (5, 4),  # 0x0008
        'Drucksensor_CB':                       (6, 0),  # 0x0001
        'Drucksensor_SFK':                      (6, 1),  # 0x0002
        'Drucksensor_Tube':                     (6, 2),  # 0x0004
        'Chopper_Drehzahl':                     (6, 3),  # 0x0008
        'Druck_service_inv':                    (6, 4),  # 0x0010
        'Personenschluessel_Terminal':          (6, 11),  # 0x0800
        'Freigabe_Taster':                      (6, 12),  # 0x1000
        'Lampentest_inv':                       (6, 13),  # 0x2000
        'Endschalter_Ex_Shutter_inv':           (6, 14),  # 0x4000
        'Handbetrieb_tube_inv':                 (6, 15),  # 0x8000
        'Probenort_Geraeumt_inv':               (14, 2),  # 0x0004
        'Streurohr_Geraeumt_inv':               (14, 3),  # 0x0008
        'IV_key_1':                             (15, 8),  # 0x0100
        'IV_key_2':                             (15, 9),  # 0x0200
        'gelb_inv':                             (17, 3),  # 0x0008
        'Freigabe_EIN':                         (17, 10),  # 0x0400
        'rot_inv':                              (18, 8),  # 0x0100
        'Warnschilder':                         (18, 9),  # 0x0200
        'Keine_Freigabe_Hub_Streurohr':         (18, 10),  # 0x0400
        # nicht konzeptionell aber geht
        'Freigabe_Hub_Streurohr_inv':           (18, 10),  # 0x0400
        'shutterzustand':                       (18, 11),  # 0x0800
        'gruen_inv':                            (18, 12),  # 0x0800
    }

    def _do_read_bits(self):
        res = 0
        # take values from cache to avoid to many reads
        raw = self._attached_shs.read()
        for i, key in enumerate(self.bitlist):
            address, bit = self._register[key]
            res |= ((raw[address] & (1 << bit)) >> bit) << i
        return res

    def doRead(self, maxage=0):
        return int(self._do_read_bits() == self.okmask)

    def doStatus(self, maxage=0):
        bits = self._do_read_bits()
        if bits == self.okmask:
            return status.OK, ''
        return status.WARN, ', '.join(
            self.format_statusbits(bits ^ self.okmask, self.bitlist))

    def format_statusbits(self, sword, labels, start=0):
        """Return a list of labels according to bit state in `sword` starting
        with bit `start` and the first label in `labels`.
        """
        smsg = []
        for i, lbl in enumerate(labels, start):
            if sword & (1 << i) and lbl:
                smsg.append(lbl)
        return smsg
Example #25
0
class PumaMultiDetectorLayout(CanReference, HasTimeout, BaseSequencer):
    """PUMA multidetector arrangement device.

    There are 11 detector/blades(collimator) combinations moving on a circle.
    The detector tubes are mounted vertically and the blade can be moved around
    the detector, where the pivot point is the center of the detector tube.
    """

    _num_axes = 11

    attached_devices = {
        'rotdetector':
        Attach('Detector tube position devices', Moveable, multiple=_num_axes),
        'rotguide':
        Attach('Detector guide devices', Moveable, multiple=_num_axes),
        # 'man': Attach('multi analyzer', Moveable),
        'att':
        Attach('Coupled axes detector', Moveable),
    }

    valuetype = tupleof(*(float for i in range(2 * _num_axes)))

    parameters = {
        'general_reset':
        Param('', type=bool, settable=False, default=False),
        'raildistance':
        Param('', type=float, settable=False, default=20., unit='mm'),
        'detectorradius':
        Param('', type=float, settable=False, default=761.9, unit='mm'),
        'refgap':
        Param('Gap between detectors during the reference of the '
              'guides',
              type=floatrange(2.75, 4.1),
              settable=False,
              userparam=True,
              default=3.),
        'gapoffset':
        Param('Minimum gap for the det 1 from reference position',
              type=float,
              settable=False,
              userparam=False,
              default=4.),
        'parkpos':
        Param(
            'Detector and guide positions to park the '
            'multidetector in a safe position',
            type=listof(float),
            settable=False,
            userparam=False,
            default=[
                -14.9, -17.5, -20., -22.5, -25., -27.5, -30., -32.5, -35.,
                -37.5, -40., 3.5, 2.75, 2.5, 2.25, 2.0, 1.75, 1.5, 1.25, 1.0,
                0.75, 0.5
            ]),
        'tubediameter':
        Param('diameter of detector tubes',
              type=floatrange(0, None),
              unit='mm',
              category='general',
              default=25.4),
        'psdchannelwidth':
        Param('PSD channel width',
              type=floatrange(0, None),
              category='general',
              unit='mm',
              prefercache=False,
              default=0.7),
        'opmode':
        Param(
            'Operation mode, either "multi" for multi analysis or '
            ' "pa" for polarization analysis',
            type=oneof('multi', 'pa'),
            settable=True,
            default='multi'),
    }

    parameter_overrides = {
        'timeout': Override(default=600.),
        'unit': Override(mandatory=False, default='', settable=False),
        'fmtstr': Override(volatile=True),
    }

    hardware_access = False
    threadstate = None
    D2R = math.pi / 180
    R2D = 1. / D2R
    # [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]
    hortranslation = range(-100, 101, 20)
    anglis = [2.28, 2.45, 2.38, 2.35, 2.30, 2.43, 2.37, 2.43, 2.32, 2.36]

    @hiddenusercommand
    def park(self, blocking=True):
        """Move device to the ``park`` position.

        The park position is given by the ``parkposition`` parameter.
        It generate ands starts a sequence if none is running.

        The call blocks the daemon execution if ``blocking`` is set to True.
        """
        if self._seq_is_running():
            if self._mode == SIMULATION:
                self._seq_thread.join()
                self._seq_thread = None
            else:
                raise MoveError(
                    self, 'Cannot park device, sequence is still '
                    'running (at %s)!' % self._seq_status[1])
        self._startSequence([
            SeqMethod(self, '_move_guides_to_zero_pos'),
            SeqMethod(self, '_move_detectors', self.parkpos),
        ] + [
            SeqDev(d, p)
            for d, p in zip(self._rotguide1, self.parkpos[11:][::-1])
        ])
        if blocking:
            # block the move to be sure that the device has reached target
            # before it can be dismounted or the position sensitive detectors
            # can be used
            self.wait()

    def doInit(self, mode):
        self._rotdetector0 = self._attached_rotdetector
        self._rotdetector1 = self._rotdetector0[::-1]
        self._rotguide0 = self._attached_rotguide
        self._rotguide1 = self._rotguide0[::-1]

    def valueInfo(self):
        ret = []
        for dev in self._attached_rotdetector + self._attached_rotguide:
            ret.extend(dev.valueInfo())
        return tuple(ret)

    def doReadFmtstr(self):
        return ', '.join('%s = %s' % (v.name, v.fmtstr)
                         for v in self.valueInfo())

    def doIsAllowed(self, target):
        # check if requested position is allowed in principle
        why = []
        for dev, pos in zip(self._rotdetector0 + self._rotguide0, target):
            ok, _why = dev.isAllowed(pos)
            if ok:
                self.log.debug('%s: requested position %.3f deg allowed', dev,
                               pos)
            else:
                why.append(
                    '%s: requested position %.3f deg out of limits; %s' %
                    (dev, pos, _why))
        if why:
            return False, '; '.join(why)
        # now check detector and guide rotation allowed within single limits
        # and sequence limits
        return self._sequentialAngleLimit(target)

    def _move_guides_to_zero_pos(self):
        """Move all guides to a position '0.'.

        Starting at the most left  guide looking for the first guide where the
        position is >= 0 so the devices left from it may be moved without any
        problem to position 0. Repeat as long all none of the positions is < 0.
        The move all with positions > 0 to 0.
        """
        self.log.debug('move all guides to zero position')
        while min(d.read(0) for d in self._rotguide0) < 0:
            for d in self._rotguide0:
                if d.read(0) >= 0:
                    for d1 in self._rotguide1[self._rotguide0.index(d):]:
                        d1.maw(0)
            self.log.debug('%r', [d.read(0) for d in self._rotguide0])
        for d in self._rotguide0:
            if not d.isAtTarget(0):
                d.maw(0)

        # remove all remaining move commands on cards due to touching any limit
        # switch
        for d in self._rotguide0:
            d.stop()

    def _move_detectors(self, target):
        """Move detectors to their positions.

        The order of the movement is calculated to avoid clashes during the
        movement.
        """
        self.log.debug('Collision sorting')
        nstart = np.zeros((self._num_axes, self._num_axes), dtype=np.int8)
        rotpos = [d.read(0) for d in self._rotdetector0]
        for i in range(self._num_axes):
            for j in range(i + 1, self._num_axes):
                if target[i] < rotpos[j] + 2.5:
                    nstart[i][j] = 1
        self.log.debug('Collision sorting done: %r', nstart)

        istart = [0] * self._num_axes
        for _k in range(self._num_axes):
            if 0 not in istart:
                break
            # ready = 1
            # for i in range(self._num_axes):
            #     if istart[i] == 0:
            #         ready = 0
            # if ready != 0:
            #     break
            for i in range(self._num_axes):
                if istart[i] == 0:
                    if sum(nstart[i][i + 1:self._num_axes]) == 0:
                        istart[i] = 1
                        self.log.debug('Move detector #%d', i + 1)
                        self._rotdetector0[i].move(target[i])
                        session.delay(2)
                        for j in range(self._num_axes):
                            nstart[j][i] = 0
        self._hw_wait(self._rotdetector0)
        # remove all remaining move commands on cards due to touching any limit
        # switch
        for d in self._rotdetector0:
            d.stop()

    def _move_guides(self, target):
        """Move all guides to their final position.

        Starting with the most outer guides towards the inner ones all guides
        may started one after the other, since should have target positions
        increasing with their position numbers.
        """
        for n in [10, 0, 9, 1, 8, 2, 7, 3, 6, 4, 5]:
            self._rotguide0[n].move(target[n + self._num_axes])
            self.log.debug('Move guide #%d', n + 1)
        self._hw_wait(self._rotguide0)
        # remove all remaining move commands on cards due to touching any limit
        # switch
        for d in self._rotguide0:
            d.stop()

    def _generateSequence(self, target):
        """Move multidetector to correct scattering angle of multi analyzer.

        It takes account into the different origins of the analyzer blades.
        """
        # check if requested positions already reached within precision
        if self.isAtTarget(target):
            self.log.debug('device already at position, nothing to do!')
            return []

        return [
            SeqMethod(self, '_move_guides_to_zero_pos'),
            # The detectors can be  moved without any restriction.
            SeqMethod(self, '_move_detectors', target),
            SeqMethod(self, '_move_guides', target),
        ]

    def doRead(self, maxage=0):
        return [d.read(maxage) for d in self._rotdetector0 + self._rotguide0]

    def doReset(self):
        for dev in self._rotguide0 + self._rotdetector0:
            # one reset per card is sufficient, since the card will be reset
            # completely
            if dev.motor.addr in [71, 77, 83, 89]:
                dev.reset()

    def doReference(self, *args):
        # self.doReset()
        # remove all remaining move commands on cards due to touching
        # any limit switch
        self.stop()
        self.log.info('Compacting all elements')
        for d, g in zip(self._rotdetector0, self._rotguide0):
            self.log.debug('reference: %s, %s', d, g)
            self._reference_det_guide(d, g)
            session.delay(1.5)
        self.log.info('Spread elements to reference guides.')
        for i, (d, g) in enumerate(zip(self._rotdetector1, self._rotguide1)):
            d.userlimits = d.abslimits
            pos = d.read(0)
            d.move(self.gapoffset - (10 - i) * self.refgap)
            while abs(pos - d.read(0)) < (self.refgap - 0.2):
                session.delay(1)
            g.reference()
            self._hw_wait([d, g])
            g.maw(0)
        self.log.info('referencing of guides is finished')

    def _reference_det_guide(self, det, guide):
        """Drive 'det' and 'guide' devices to references.

        The 'det' device will be moved to its reference position, whereas the
        'guide' device will only moved to a position hitting the upper limit
        switch. So the 'det' and 'guide' devices are in a position all other
        pairs could be referenced too.

        If the 'guide' hits the upper limit switch and the 'det' is not at it's
        reference position it will be moved away in steps of 1 deg.
        """
        not_ref_switch = 'low' if guide.motor.refswitch == 'high' else 'high'
        try:
            det.reference()
            self._hw_wait([det])
            while guide.motor.isAtReference():
                if guide.motor.isAtReference(not_ref_switch):
                    self._clear_guide_reference(guide)
                while guide.motor.isAtReference():
                    self._step(guide)
                det.reference()
                self._hw_wait([det])
            while guide.motor.isAtReference():
                self._step(guide)
            while not guide.motor.isAtReference():
                guide.reference()
                self._hw_wait([guide])
        finally:
            pass

    def _step(self, guide, size=1):
        """Move device 'guide' a step 'size' away.

        The sign of the size gives the direction the value the distance.
        """
        p = guide.motor.read(0)
        if not guide.motor.isAllowed(p + size)[0]:
            guide.motor.setPosition(p - size)
            session.delay(1)
        guide.motor.maw(p + size)

    def _clear_guide_reference(self, guide):
        """Move all guides right from the 'guide' to free the limit switch.

        If there is a guide in between free, then free only left from this one
        """
        self.log.debug('clearing guide: %s', guide)
        while True:
            # find the first free guide from left side
            freeguides = self._rotguide1
            free = None
            for g in self._rotguide0[self._rotguide0.index(guide) + 1:]:
                not_ref_sw = 'low' if g.motor.refswitch == 'high' else 'high'
                if not g.motor.isAtReference(not_ref_sw):
                    free = g
                    freeguides = self._rotguide1[self._rotguide1.index(g):]
                    self.log.debug('found free guide: %s, %r', free,
                                   freeguides)
                    break

            # Try to free all left from found free guide if there was one
            for g in freeguides:
                for d in freeguides:
                    if d == g:
                        break
                    while d.motor.isAtReference():
                        self._step(d)
                    self._step(d)
                self._step(g)
                # if the found free is not free any more try the next round
                if free and free.motor.isAtReference():
                    break
                if g == guide:
                    return
            if free is None:
                self.log.warning(
                    "Can't free the guide: %s. Please check "
                    'manually why!', guide)

    def _hw_wait(self, devices):
        loops = 0
        final_exc = None
        devlist = devices[:]  # make a 'real' copy of the list
        while devlist:
            loops += 1
            for dev in devlist[:]:
                try:
                    done = dev.doStatus(0)[0]
                except Exception:
                    dev.log.exception('while waiting')
                    final_exc = filterExceptions(sys.exc_info(), final_exc)
                    # remove this device from the waiters - we might still
                    # have its subdevices in the list so that _hw_wait()
                    # should not return until everything is either OK or
                    # ERROR
                    devlist.remove(dev)
                if done == status.BUSY:
                    # we found one busy dev, normally go to next iteration
                    # until this one is done (saves going through the whole
                    # list of devices and doing unnecessary HW communication)
                    if loops % 10:
                        break
                    # every 10 loops, go through everything to get an accurate
                    # display in the action line
                    continue
                devlist.remove(dev)
            if devlist:
                session.delay(self._base_loop_delay)
        if final_exc:
            reraise(*final_exc)

    def _read_corr(self):
        """Read the physical unit of axis."""
        readraw0 = self._read_raw()

        temp1 = temp0 = self._correctAnglesRead(readraw0[:self._num_axes])

        self.log.debug('detector rotation corrected:       %r', temp0)
        self.log.debug('detector guide rotation corrected: %r', temp1)

        return [temp0, temp1]

    def _read_raw(self):
        """Read the physical unit of axis."""
        readraw0 = [d.read() for d in self._rotdetector0]
        readraw1 = [d.read() for d in self._rotguide0]
        self.log.debug('detector rotation raw:  %r', readraw0)
        self.log.debug('detector guide rotation raw: %r', readraw1)
        return readraw0 + readraw1

    def _printPos(self):
        out = []
        for i, dev in enumerate(self._attached_rotdetector):
            out.append('detector rotation %2d: %7.2f %s' %
                       (i, dev.read(), dev.unit))
        for i, dev in enumerate(self._attached_rotguide):
            out.append('guide rotation    %2d: %7.2f %s' %
                       (i, dev.read(), dev.unit))
        self.log.debug('%s', '\n'.join(out))

    def doIsAtTarget(self, target):
        self._printPos()
        return self._checkPositionReached(target, 'raw')

    def _checkPositionReached(self, target, mode):
        """Check whether requested position is reached within some limit."""
        self.log.debug('length of list: %d',
                       0 if target is None else len(target))
        if not target:
            return False

        if mode == 'raw':
            pos = self._read_raw()
        elif mode == 'cor':
            pos = self._read_cor()
        else:
            self.log.warning('not a valid mode given; corrected values or raw '
                             'values?')
            return False

        check = 0
        reached = []
        nonreached = []
        precs = [d.precision for d in self._rotdetector0 + self._rotguide0]
        for i, (t, p, prec) in enumerate(zip(target, pos, precs)):
            self.log.debug('%s %s', t, p)
            if abs(t - p) <= prec:
                reached.append(i)
                check += 1
            else:
                nonreached.append(i)
        self.log.debug('not reached: %s', nonreached)
        self.log.debug('reached    : %s', reached)
        self.log.debug('check      : %s', check)
        return check == len(target)

    def _readAnaTranslation(self):
        """Read the translation value of the individual analyzer blades.

        Needed for the calculation of real angles of detectors due to different
        origins
        """
        # [-125, -105, -85, -65, -45, -25, 0, 25, 45, 65, 85, 105, 125]
        anatranslist1 = list(range(-125, -5, 20)) + [0] + \
            list(range(25, 126, 20))
        return anatranslist1

    def _readZeroAna(self):
        """Read whether the whole analyser table is rotated.

        Needed for the calculation of real angles of detectors due to different
        origins
        """
        self.anarot = self._attached_att.read()
        return self.anarot

    def _correctZeroAna(self, position):
        return position - self._readZeroAna()

    def _correctAnglesRead(self, pos):
        trans = self._readAnaTranslation()
        read1 = []

        for i in range(len(self._rotdetector0)):
            angle = pos[i] * self.D2R
            read0 = (self.detectorradius + trans[i]) * math.tan(angle)  # b
            read0 += self.hortranslation[i]  # b corrected
            # b/a: a corrected detector radius
            temp = read0 / (self.detectorradius + trans[i])
            temp = math.atan(temp)  # calc angle radian
            temp = temp * self.R2D  # convert to degrees
            read1.append(temp)  # list append
        self.log.debug('corrected detector angles: %s', read1)
        return read1

    def _correctAnglesMove(self, pos):
        trans = self._readAnaTranslation()
        read1 = []

        for i in range(len(self._rotdetector0)):
            angle = pos[i] * self.D2R
            # b: without taking into account the horizontal shift
            read0 = math.tan(angle) * (self.detectorradius + trans[i])
            read0 -= self.hortranslation[i]  # b check if minus/plus?
            # b/a: a corrected detector radius
            temp = read0 / self.detectorradius
            temp = math.atan(temp)  # calc angle radian
            temp = temp * self.R2D  # convert to degrees
            read1.append(temp)  # list append
        self.log.debug('corrected detector angles: %s', read1)
        return read1

    def _checkLimitSwitches(self, what):
        lis = []
        if what in ['det', 'all']:
            lis += self._rotdetector0
        if what == ['guide', 'all']:
            lis += self._rotguide0

        ref = sum(dev.motor.isAtReference() for dev in lis)
        return ref == len(lis)

    def _sequentialAngleLimit(self, pos):
        """Check individual movement ranges allowed for detector or guide."""
        dtarget = pos[:self._num_axes]

        def is_reverse_sorted(l):
            l1, l2 = tee(l)
            next(l2, None)
            return all(a >= b for a, b in zip(l1, l2))

        def is_sorted(l):
            l1, l2 = tee(l)
            next(l2, None)
            return all(a <= b for a, b in zip(l1, l2))

        if not is_reverse_sorted(dtarget):
            return False, 'detector targets not a list of consecutive values'
        check = set(range(1, self._num_axes + 1))
        allowed = check.copy()
        self.log.debug('position: %s', pos)
        self.log.debug('anglis: %s', self.anglis)
        why = []

        for i in range(self._num_axes):
            self.log.debug('check position %s %s', i, pos[i])
            if i == 0:
                if abs(dtarget[i] - dtarget[i + 1]) < self.anglis[0]:
                    why.append('case 0: %s %s %s' %
                               (dtarget[i], dtarget[i + 1], self.anglis[0]))
                    allowed.discard(i + 1)
            elif i == 10:
                if abs(dtarget[i] - dtarget[i - 1]) < self.anglis[9]:
                    why.append('case 10: %s %s %s' %
                               (dtarget[i], dtarget[i - 1], self.anglis[9]))
                    allowed.discard(i + 1)
            else:
                if abs(dtarget[i] - dtarget[i + 1]) < self.anglis[i] or \
                   abs(dtarget[i] - dtarget[i - 1]) < self.anglis[i]:
                    why.append('%s %s %s %s' %
                               (dtarget[i - 1], dtarget[i], dtarget[i + 1],
                                self.anglis[i]))
                    allowed.discard(i)
        self.log.debug('movement allowed for the following detectors: %s',
                       ', '.join([str(i) for i in allowed]))
        notallowed = check - allowed
        if notallowed:
            self.log.warning(
                'movement not allowed for the following '
                'detectors: %s', ', '.join([str(i) for i in notallowed]))
            return False, '; '.join(why)

        allowed = check.copy()
        gtarget = pos[self._num_axes:]
        if self.opmode == 'multi' and not is_sorted(gtarget):
            return False, 'detector guide targets not a list of consecutive ' \
                'values %s' % (gtarget,)
        first = 0
        last = self._num_axes - 1
        rg1_min = -7.5
        if dtarget[first] < 4.2:
            rg1_min += 1.4 * (dtarget[first] - 4.2)
        rg11_max = 21.9
        if dtarget[last] < -23.5:
            rg11_max += 1.4 * (23.5 + dtarget[last - 1])
        if not (rg1_min <= gtarget[first] <= gtarget[first + 1]):
            why.append('rg1: %s %s %s' %
                       (rg1_min, gtarget[first], gtarget[first + 1]))
            allowed.discard(first + 1)
        if not (gtarget[last - 1] <= gtarget[last] <= rg11_max):
            why.append('rg11: %s %s %s' %
                       (gtarget[last - 1], gtarget[last], rg11_max))
            allowed.discard(last + 1)
        if self.opmode == 'multi':
            for i in range(1, last):
                if not (gtarget[i - 1] <= gtarget[i] <= gtarget[i + 1]):
                    why.append(
                        'rg%i: %s %s %s' %
                        (i + 1, gtarget[i - 1], gtarget[i], gtarget[i + 1]))
                    allowed.discard(i + 1)
        notallowed = check - allowed
        if notallowed:
            self.log.warning(
                'movement not allowed for the following guides: '
                '%s', ', '.join([str(i) for i in notallowed]))
            return False, why
        return True, ''
Example #26
0
class MultiSwitcher(MappedMoveable):
    """The multi-switcher generalizes the `Switcher` so that for a state change
    multiple underlying moveable devices can be controlled.

    This is useful if you have for example two motors that only ever move to
    certain discrete positions for selected 'configurations', e.g. a
    monochromator changer.  Then you can control both using ::

        move(changer_switch, 'up')
        move(changer_switch, 'down')

    instead of moving the axis to positions hard to understand or remember::

        move(changer1, 14.55, changer2, 8.15)
        move(changer1, 51.39, changer2, 3.14)

    and still have the underlying continuously moveable devices available for
    debugging purposes.

    """
    attached_devices = {
        'moveables': Attach('The N (continuous) devices which are'
                            ' controlled', Moveable, multiple=True),
        'readables': Attach('0 to N (continuous) devices which are'
                            ' used for read back only', Readable,
                            optional=True, multiple=True),
    }

    parameters = {
        'precision': Param('List of allowed deviations (1 or N) from target '
                           'position, or None to disable', mandatory=True,
                           type=none_or(listof(none_or(floatrange(0))))),
        'blockingmove': Param('Should we wait for the move to finish?',
                              mandatory=False, default=True, settable=True,
                              type=bool),
    }

    parameter_overrides = {
        'mapping':   Override(description='Mapping of state names to N values '
                              'to move the moveables to',
                              type=dictof(anytype, listof(anytype))),
        'fallback':  Override(userparam=False, type=none_or(anytype),
                              mandatory=False),
    }

    hardware_access = False

    @lazy_property
    def devices(self):
        return self._attached_moveables + self._attached_readables

    def doInit(self, mode):
        MappedMoveable.doInit(self, mode)
        for k, t in self.mapping.items():
            if len(t) != len(self.devices):
                raise ConfigurationError(self, 'Switcher state entry for key '
                                         '%r has different length than '
                                         'moveables list' % k)
        if self.precision:
            if len(self.precision) not in [1, len(self.devices)]:
                raise ConfigurationError(self, 'The precision list must either'
                                         ' contain only one element or have '
                                         'the same amount of elements as the '
                                         'moveables list')

    def _startRaw(self, target):
        """target is the raw value, i.e. a list of positions"""
        moveables = self._attached_moveables
        if not isinstance(target, (tuple, list)) or \
                len(target) < len(moveables):
            raise InvalidValueError(self, 'doStart needs a tuple of %d '
                                    'positions for this device!' %
                                    len(moveables))
        # only check and move the moveables, which are first in self.devices
        for d, t in zip(moveables, target):
            if not d.isAllowed(t):
                raise InvalidValueError(self, 'target value %r not accepted '
                                        'by device %s' % (t, d.name))
        for d, t in zip(moveables, target):
            self.log.debug('moving %r to %r', d, t)
            d.start(t)
        if self.blockingmove:
            multiWait(moveables)

    def _readRaw(self, maxage=0):
        if self._mode == SIMULATION and self.target is not None:
            # In simulation mode the values of the readables are assumed to be
            # given in the mapping table for the current target
            return tuple(d.read(maxage) for d in self._attached_moveables) + \
                   tuple(self.mapping[self.target][len(self._attached_moveables):])
        return tuple(d.read(maxage) for d in self.devices)

    def _mapReadValue(self, pos):
        """maps a tuple to one of the configured values"""
        hasprec = bool(self.precision)
        if hasprec:
            precisions = self.precision
            if len(precisions) == 1:
                precisions = [precisions[0]] * len(self.devices)
        for name, values in self.mapping.items():
            if hasprec:
                for p, v, prec in zip(pos, values, precisions):
                    if prec:
                        if abs(p - v) > prec:
                            break
                    elif p != v:
                        break
                else:  # if there was no break we end here...
                    return name
            else:
                if tuple(pos) == tuple(values):
                    return name
        if self.fallback is not None:
            return self.fallback
        raise PositionError(self, 'unknown position of %s: %s' % (
            ', '.join(str(d) for d in self.devices),
            ', '.join(d.format(p) for (p, d) in zip(pos, self.devices))))

    def doStatus(self, maxage=0):
        # if the underlying device is moving or in error state,
        # reflect its status
        move_status = multiStatus(self.devices, maxage)
        if move_status[0] not in (status.OK, status.WARN):
            return move_status
        return MappedReadable.doStatus(self, maxage)

    def doReset(self):
        multiReset(self._adevs)

    def doStop(self):
        multiStop(self._adevs)
Example #27
0
class PassiveChannel(Measurable):
    """Abstract base class for one channel of a aggregate detector.

    See the `Detector` documentation for an overview of channel types, and
    the difference between passive and active channels.

    Derived classes have to implement `doRead` and `valueInfo` methods.
    They can return an empty list and tuple, respectively, if the channel
    contributes no scalar values but one or more arrays, e.g. classes
    derived from `ImageChannelMixin`.
    """

    parameters = {
        'ismaster':
        Param('If this channel is an active master', type=bool, settable=True),
        'presetaliases':
        Param(
            'Aliases for setting a preset for the first '
            'scalar on this channel',
            type=listof(str)),
    }

    # preset handling is slightly different in the Detector

    def doSetPreset(self, **preset):
        raise UsageError(self, 'This channel cannot be used as a detector')

    # methods that can be overridden

    _presetmap = None

    def presetInfo(self):
        """Return the preset keys that this channel supports.

        The default implementation returns all time/monitor/counter/other
        value names from `self.valueInfo()`, with '.' replaced by '_' to
        form valid identifiers.

        It also adds entries from `self.presetaliases`.
        """
        if self._presetmap is not None:
            return set(self._presetmap)
        self._presetmap = {}
        for (i, value) in enumerate(self.valueInfo()):
            if value.type in ('counter', 'monitor', 'time', 'other'):
                self._presetmap[value.name.replace('.', '_')] = i
        if self._presetmap:
            for alias in self.presetaliases:
                self._presetmap[alias] = 0
        return set(self._presetmap)

    def setChannelPreset(self, name, value):
        """Set a preset for this channel.

        This can be ignored by passive channels since soft presets are checked
        explicitly.
        """
        self.ismaster = True

    def presetReached(self, name, value, maxage):
        """Return true if the soft preset for *name* has reached the given
        *value*.
        """
        if name in self._presetmap:
            return self.read(maxage)[self._presetmap[name]] >= value
        return False

    # methods to be implemented in concrete subclasses

    def doStart(self):
        pass

    def doFinish(self):
        pass

    def doStop(self):
        pass

    def doRead(self, maxage=0):
        raise NotImplementedError('implement doRead')

    def valueInfo(self):
        raise NotImplementedError('implement valueInfo')

    def doStatus(self, maxage=0):
        return status.OK, 'idle'
Example #28
0
class JustBinItDetector(Detector):
    """ A "detector" that reads image data from just-bin-it.

    Note: it only uses image channels.
    """
    parameters = {
        'brokers':
        Param('List of kafka hosts to be connected',
              type=listof(host(defaultport=9092)),
              mandatory=True,
              preinit=True,
              userparam=False),
        'command_topic':
        Param(
            'The topic to send just-bin-it commands to',
            type=str,
            userparam=False,
            settable=False,
            mandatory=True,
        ),
        'response_topic':
        Param(
            'The topic where just-bin-it responses appear',
            type=str,
            userparam=False,
            settable=False,
            mandatory=True,
        ),
        'ack_timeout':
        Param(
            'How long to wait for timeout on acknowledgement',
            type=int,
            default=5,
            unit='s',
            userparam=False,
            settable=False,
        ),
    }

    parameter_overrides = {
        'unit': Override(default='events', settable=False, mandatory=False),
        'fmtstr': Override(default='%d'),
        'liveinterval': Override(type=floatrange(0.5), default=1),
    }
    _last_live = 0
    _presets = {}
    _presetkeys = {'t'}
    _ack_thread = None
    _exit_thread = False

    def doPreinit(self, mode):
        self._command_sender = kafka.KafkaProducer(
            bootstrap_servers=self.brokers)
        # Set up the response message consumer
        self._response_consumer = kafka.KafkaConsumer(
            bootstrap_servers=self.brokers)
        self._response_topic = kafka.TopicPartition(self.response_topic, 0)
        self._response_consumer.assign([self._response_topic])
        self._response_consumer.seek_to_end()
        self.log.debug('Response topic consumer initial position = %s',
                       self._response_consumer.position(self._response_topic))

    def doInit(self, _mode):
        pass

    def doPrepare(self):
        for image_channel in self._attached_images:
            image_channel.doPrepare()

    def doStart(self):
        self._last_live = -(self.liveinterval or 0)

        # Generate a unique-ish id
        unique_id = 'nicos-{}-{}'.format(self.name, int(time.time()))
        self.log.debug('set unique id = %s', unique_id)

        count_interval = self._presets.get('t', None)
        config = self._create_config(count_interval, unique_id)

        if count_interval:
            self.log.info(
                'Requesting just-bin-it to start counting for %s seconds',
                count_interval)
        else:
            self.log.info('Requesting just-bin-it to start counting')

        self._send_command(self.command_topic, json.dumps(config).encode())

        # Tell the channels to start
        for image_channel in self._attached_images:
            image_channel.doStart()

        # Check for acknowledgement of the command being received
        self._exit_thread = False
        self._ack_thread = createThread("jbi-ack", self._check_for_ack,
                                        (unique_id, self.ack_timeout))

    def _check_for_ack(self, identifier, timeout_duration):
        timeout = int(time.time()) + timeout_duration
        acknowledged = False
        while not (acknowledged or self._exit_thread):
            messages = self._response_consumer.poll(timeout_ms=50)
            responses = messages.get(self._response_topic, [])
            for records in responses:
                msg = json.loads(records.value)
                if 'msg_id' in msg and msg['msg_id'] == identifier:
                    acknowledged = self._handle_message(msg)
                    break
            # Check for timeout
            if not acknowledged and int(time.time()) > timeout:
                err_msg = 'Count aborted as no acknowledgement received from ' \
                          'just-bin-it within timeout duration '\
                          f'({timeout_duration} seconds)'
                self.log.error(err_msg)
                break
        if not acknowledged:
            # Couldn't start histogramming, so stop the channels etc.
            self._stop_histogramming()
            for image_channel in self._attached_images:
                image_channel.doStop()

    def _handle_message(self, msg):
        if 'response' in msg and msg['response'] == 'ACK':
            self.log.info('Counting request acknowledged by just-bin-it')
            return True
        elif 'response' in msg and msg['response'] == 'ERR':
            self.log.error('just-bin-it could not start counting: %s',
                           msg['message'])
        else:
            self.log.error(
                'Unknown response message received from just-bin-it')
        return False

    def _send_command(self, topic, message):
        self._command_sender.send(topic, message)
        self._command_sender.flush()

    def _create_config(self, interval, identifier):
        histograms = []

        for image_channel in self._attached_images:
            histograms.append(image_channel.get_configuration())

        config_base = {
            'cmd': 'config',
            'msg_id': identifier,
            'histograms': histograms
        }

        if interval:
            config_base['interval'] = interval
        else:
            # If no interval then start open-ended count
            config_base['start'] = int(time.time()) * 1000
        return config_base

    def valueInfo(self):
        return tuple(info for channel in self._attached_images
                     for info in channel.valueInfo())

    def doRead(self, maxage=0):
        return [
            data for channel in self._attached_images
            for data in channel.read(maxage)
        ]

    def doReadArrays(self, quality):
        return [image.doReadArray(quality) for image in self._attached_images]

    def doFinish(self):
        self._stop_ack_thread()
        self._stop_histogramming()

    def _stop_histogramming(self):
        self._send_command(self.command_topic, b'{"cmd": "stop"}')

    def doShutdown(self):
        self._response_consumer.close()

    def doSetPreset(self, **presets):
        self._presets = presets

    def doStop(self):
        self._stop_ack_thread()
        self._stop_histogramming()

    def _stop_ack_thread(self):
        if self._ack_thread and self._ack_thread.is_alive():
            self._exit_thread = True
            self._ack_thread.join()
            self._ack_thread = None

    def doStatus(self, maxage=0):
        return multiStatus(self._attached_images, maxage)

    def doReset(self):
        pass

    def doInfo(self):
        return [
            data for channel in self._attached_images
            for data in channel.doInfo()
        ]

    def duringMeasureHook(self, elapsed):
        if elapsed > self._last_live + self.liveinterval:
            self._last_live = elapsed
            return LIVE
        return None

    def arrayInfo(self):
        return tuple(image.arrayInfo() for image in self._attached_images)
Example #29
0
class MagLock(Moveable):
    """Puma specific magnetic lock device."""

    attached_devices = {
        'magazine': Attach('The monochromator magazine', Moveable),
        'io_open': Attach('readout for the status', Readable),
        'io_closed': Attach('readout for the status', Readable),
        'io_set': Attach('output to set', Moveable),
    }

    valuetype = oneof('open', 'closed')

    parameters = {
        'states': Param('List of state names',
                        type=listof(str),
                        mandatory=True),
    }

    #    parameters = {
    #        'values':    Param('List of values to move to', type=listof(anytype),
    #                           mandatory=True),
    #        'io_values': Param('List of values to move to', type=listof(anytype),
    #                           mandatory=True),
    #        'precision': Param('Precision for comparison', mandatory=True),
    #    }

    def _bitmask(self, num):
        return 1 << num

    def doStart(self, position):
        magpos = self._magpos
        if magpos not in [0, 1, 2, 3]:
            raise NicosError(self, 'depot at unknown position')

        if position == self.read(0):
            return

        if position == 'closed':
            self._attached_io_set.move(0)
        elif position == 'open':
            self._attached_io_set.move(self._bitmask(magpos))
        else:
            self.log.info('Maglock: illegal input')
            return

        session.delay(2)  # XXX!

        if self.read(0) != position:
            raise NicosError(self, 'maglock returned wrong position!')
        else:
            self.log.info('Maglock: %s', self.read(0))

    def _read(self):
        """Return internal string repr. of the right sensing switches."""
        magpos = self._magpos

        #        if magpos == 4:
        #            raise NicosError(self, 'depot at unknown position')
        #            return
        bitmask = self._bitmask(magpos)
        val = list(
            map(
                str,
                [(self._attached_io_open.read(0) & bitmask) // bitmask,
                 (self._attached_io_closed.read(0) & bitmask) // bitmask],
            ))
        self.log.debug('Sensing switches are in State %s', val)
        return ''.join(val)

    def doRead(self, maxage=0):
        s = self._read()
        if s == '01':
            return 'closed'
        elif s == '10':
            return 'open'
        elif s == '00':
            return 'UNKNOWN'
        else:
            raise NicosError(
                self, 'Depot magnet switches in undefined status '
                '%s check switches' % s)

    def doStatus(self, maxage=0):
        s = self._read()
        if s == '01':
            return status.OK, 'idle'
        elif s == '10':
            return status.OK, 'idle'
        elif s == '00':
            return status.BUSY, 'Moving'  # '? or Error!'
        else:
            return status.ERROR, 'maglock is in error state'

    @property
    def _magpos(self):
        s = self._attached_magazine.read(0)
        for i, k in enumerate(self.states):
            if s == k:
                return i
        return 4
Example #30
0
class EchoTime(Moveable):
    """Reseda echo time device.
    Provides storage and access to tunewave tables which are used to determine
    the device setup for the particular echo time, considering the measurement
    mode and the current wavelength."""

    attached_devices = {
        'wavelength':
        Attach('Wavelength device', Readable),
        'dependencies':
        Attach('Echo time dependent devices', Readable, multiple=True),
    }

    parameters = {
        'zerofirst':
        Param(
            'mapping of Devices to preconfigured value to be '
            'set before applying the echo time',
            type=dictof(str, anytype),
            settable=True,
            userparam=False,
            default={}),
        'stopfirst':
        Param('list of Devices to stop before setting a new '
              'echotime',
              type=listof(str),
              settable=True,
              userparam=False,
              default=[]),
        'tables':
        Param('Tune wave tables',
              type=dictof(oneof('nrse', 'mieze'),
                          dictof(float, dictof(float, dictof(str, anytype)))),
              settable=True,
              internal=True),
        'currenttable':
        Param('Currently used tune wave tables',
              type=dictof(float, dictof(str, anytype)),
              settable=False,
              userparam=True,
              internal=True,
              volatile=True),
        'tunedevs':
        Param('Devices used for tuning',
              type=listof(str),
              settable=False,
              internal=True,
              volatile=True),
        'availtables':
        Param('Available tunewave tables',
              type=dictof(str, listof(float)),
              settable=False,
              internal=True,
              volatile=True),
        'wavelengthtolerance':
        Param('Wavelength tolerance for table'
              'determination',
              type=float,
              settable=True,
              default=0.1),
    }
    parameter_overrides = {
        'target': Override(category='instrument'),
    }

    valuetype = float

    def doPreinit(self, mode):
        # create an internal lookup dictionary for tunedevs ({name: dev})
        self._tunedevs = {
            entry.name: entry
            for entry in self._attached_dependencies
        }

    def doRead(self, maxage=0):
        # read all tuning devices
        devs = {
            key: value.read(maxage)
            for key, value in self._tunedevs.items()
        }

        # find correct echotime for current device setup in currently active
        # tunewave table
        for echotime, tunedevs in self.currenttable.items():
            self.log.debug('checking if we are at echotime %s', echotime)
            success = True
            for tunedev, value in tunedevs.items():
                # XXX: horrible hack
                if tunedev.endswith('reg_amp'):
                    continue
                # fuzzy matching necessary due to maybe oscillating devices
                prec = getattr(self._tunedevs[tunedev], 'precision', 0)
                if not self._fuzzy_match(value, devs.get(tunedev, None), prec):
                    self.log.debug(
                        '-> no, because %s is at %s not %s '
                        '(prec = %s)', tunedev, devs.get(tunedev, None), value,
                        prec)
                    success = False
                    break
            if success:
                self.log.debug('-> YES!')
                return echotime

        # return 0 as echotime and show additional info in status string
        # if the echo time could not be determined
        return 0.0

    def doStart(self, value):
        # filter unsupported echotimes
        # find best match from table
        for entry in self.currenttable:
            if abs(entry - value) < 1e-12:
                value = entry
                break
        else:
            raise InvalidValueError(
                'Given echo time not supported by current '
                'tunewave table (%s/%s) within %s' %
                (getattr(session.experiment, 'measurementmode', 'mieze'),
                 self._attached_wavelength.read(), self.wavelengthtolerance))

        # stop stopfirst devices
        for devname in self.stopfirst:
            dev = session.getDevice(devname)
            self.log.debug('stopping %s', str(dev))
            dev.stop()

        # move zerofirst devices to configured value
        self.log.debug('zeroing devices')
        wait_on = set()
        for devname, val in self.zerofirst.items():
            dev = session.getDevice(devname)
            self.log.debug('moving %s to zero', str(dev))
            dev.start(val)
            wait_on.add(dev)
        self.log.debug('waiting for devices to reach zero')
        multiWait(wait_on)
        self.log.debug('devices now at zero')

        # move all tuning devices at once without blocking
        wait_on = set()
        for tunedev, val in sorted(self.currenttable[value].items()):
            if tunedev in self.stopfirst:
                self.log.debug('skipping %s (will be set later)', tunedev)
                continue
            if tunedev in self._tunedevs:
                self.log.debug('setting %s to %s', tunedev, val)
                dev = self._tunedevs[tunedev]
                dev.start(val)
                wait_on.add(dev)
            else:
                self.log.warning('tune device %r from table not in tunedevs! '
                                 'movement to %r ignored !' % (tunedev, val))
        self.log.debug('waiting for devices...')
        multiWait(wait_on)
        self.log.debug('devices now at configured values')

        # activate stopfirst devices
        wait_on = set()
        for devname in self.stopfirst:
            dev = session.getDevice(devname)
            val = self.currenttable[value][devname]
            self.log.debug('moving %s to %s', devname, val)
            dev.move(val)
            wait_on.add(dev)
        self.log.debug('devices now at configured values')
        multiWait(wait_on)
        self.log.debug('all done. good luck!')

    def doReadTunedevs(self):
        return sorted(self._tunedevs)

    def doReadAvailtables(self):
        return {key: sorted(value) for key, value in self.tables.items()}

    def doReadCurrenttable(self):
        cur_wavelength = self._attached_wavelength.read()

        precision = self.wavelengthtolerance
        table = self.tables.get(
            getattr(session.experiment, 'measurementmode', 'mieze'), {})

        # determine current tunewave table by measurement mode and fuzzy
        # matched wavelength
        for wavelength, tunewavetable in table.items():
            if self._fuzzy_match(cur_wavelength, wavelength, precision):
                return self._validate_table(tunewavetable)

        return {}

    def getTable(self, measurement_mode, wavelength):
        """Get a specific tunewave table.

        Avoid transfering all tables for each access.
        """
        return self._validate_table(
            self.tables.get(measurement_mode, {}).get(wavelength, {}))

    def setTable(self, measurement_mode, wavelength, table):
        """Set a specific tunewave table. Avoid transfering all tables for each
        access."""

        # validate table structure and device values
        table = self._validate_table(table)

        # use ordinary dicts instead of readonlydicts to be able to change them
        tables = dict(self.tables)
        mode_table = dict(tables.setdefault(measurement_mode, {}))

        # ensure float for wavelength value due to later fuzzy matching
        mode_table[float(wavelength)] = table
        tables[measurement_mode] = mode_table

        self.tables = tables

    def deleteTable(self, measurement_mode, wavelength):
        """Delete a specific tunewave table. Avoid transfering all tables for
        each access."""

        # use ordinary dicts instead of readonlydicts to be able to change them
        tables = dict(self.tables)
        tables[measurement_mode] = dict(tables.get(measurement_mode, {}))
        del tables[measurement_mode][float(wavelength)]
        self.tables = tables

    def _fuzzy_match(self, value, setpoint, precision):
        """General fuzzy matching of values (used for float comparisons)."""
        return (setpoint - precision) <= value <= (setpoint + precision)

    def _validate_table(self, table):
        """Validates the structure of a single tunewave table and and all the
        included device values (using the valuetype of the particular device).
        """
        # Structure of a single tunewave table: {echotime: {tunedev : value}}

        result = {}
        for echotime, tunedevs in table.items():
            echotime = float(echotime)
            result[echotime] = {}
            for tunedev, value in tunedevs.items():
                result[echotime][tunedev] = self._tunedevs[tunedev].valuetype(
                    value)

        return result