Esempio n. 1
0
class DelayBox(Moveable):
    """Toni TOFTOF-type programmable delay box."""

    attached_devices = {
        'bus': Attach('Toni communication bus', ToniBus),
    }

    parameters = {
        'addr':
        Param('Bus address of the supply controller',
              type=intrange(0xF0, 0xFF),
              mandatory=True),
    }

    parameter_overrides = {
        'fmtstr': Override(default='%d'),
    }

    valuetype = int

    def doRead(self, maxage=0):
        return self._attached_bus.communicate('D?', self.addr, expect_hex=4)

    def doStart(self, target):
        self._attached_bus.communicate('D=%04X' % target,
                                       self.addr,
                                       expect_ok=True)

    def doIsAllowed(self, target):
        if 0 <= target <= 65535:
            return True, ''
        else:
            return False, '%r is not in the allowed range [0, 65535], please '\
                          'check your delay calculation' % (target,)

    def doStatus(self, maxage=0):
        return status.OK, ''
Esempio n. 2
0
class WindowMoveable(HasLimits, EpicsMoveable):
    """
    Some devices do not have a way to determine their status. The only way
    to test for completion is to read the value back and test if it is
    within a certain window of the target. This is done here.
    """

    parameters = {
        'window':
        Param('Tolerance used for testing for completion',
              type=float,
              mandatory=True)
    }

    parameter_overrides = {
        'target': Override(settable=True),
    }

    valuetype = float
    _driveTarget = None

    def doStart(self, value):
        # I have to use my private _driveTarget as the target
        # attribute is marked volatile in EpicsMoveable and is
        # not holding the real target.
        self._driveTarget = value
        EpicsMoveable.doStart(self, value)

    def doStatus(self, maxage=0):
        pos = self.doRead(0)
        if self._driveTarget:
            if abs(pos - self._driveTarget) < self.window:
                self._driveTarget = None
                return status.OK, 'Done'
            else:
                return status.BUSY, 'Moving'
        return status.OK, 'Done'
Esempio n. 3
0
class UsesFastshutter(DeviceMixinBase):
    """
    Adds the ability to open a fast shutter before the
    acquisition.

    The given shutter devices MUST BE of type <nicos.devices.generic.Switcher>
    and MUST HAVE the values 'open' and 'closed'.  This state is enforced to
    avoid setups that configure almighty monster-detectors.
    """

    attached_devices = {
        'fastshutter': Attach('Fast shutter switcher device', Switcher),
    }

    parameters = {
        'openfastshutter':
        Param(
            'Open fast shutter before the acquisition. '
            'Caution: It has to be closed manually',
            type=bool,
            settable=True,
            default=True),
    }

    def openFastshutter(self):
        # open fastshutter automatically if desired
        if self.openfastshutter:
            # reset fast shutter if in error state (the shutter sometimes goes
            # into error state because it couldn't be opened, but it works
            # again after reset on the next try
            fastshutter = self._attached_fastshutter
            if fastshutter.status(0)[0] == status.ERROR:
                self.log.warning('resetting fast shutter before opening: it is'
                                 ' in error state')
                fastshutter.reset()
            waitForCompletion(fastshutter)
            fastshutter.maw('open')
Esempio n. 4
0
class Authenticator(BaseAuthenticator):
    """Authenticates against PAM.

    This unfortunately only works against the local shadow database if the
    daemon runs as the root user.

    The access level info can be put into the "gecos" field.

    Example:

    John Doe,access=20

    where 20 is the 'ADMIN' level. (see `nicos.core.utils.py` file)
    """

    parameters = {
        'defaultlevel': Param('Default user level if not in PAM settings',
                              settable=False, userparam=False,
                              type=oneof(GUEST, USER, ADMIN), default=GUEST),
    }

    def authenticate(self, username, password):
        try:
            pam.authenticate(username, password, resetcred=0)
            entry = pwd.getpwnam(username)
            idx = access_re.search(entry.pw_gecos)
            if idx:
                access = int(idx.group('level'))
                if access in (GUEST, USER, ADMIN):
                    return User(username, access)
            return User(username, self.defaultlevel)
        except pam.PAMError as err:
            raise AuthenticationError(
                'PAM authentication failed: %s' % err) from None
        except Exception as err:
            raise AuthenticationError(
                'exception during PAM authentication: %s' % err) from None
Esempio n. 5
0
class TASIndex(AutoDevice, Moveable):
    """
    "Partial" devices for the H, K, L, E indices of the TAS instrument.
    """

    parameters = {
        'index': Param('The index into the TAS value', type=int),
    }

    attached_devices = {
        'tas': Attach('The spectrometer to control', TAS),
    }

    valuetype = float

    hardware_access = False

    def doRead(self, maxage=0):
        return self._attached_tas.read(maxage)[self.index]

    def doStart(self, pos):
        current = list(self._attached_tas.read(0.5))
        current[self.index] = pos
        self._attached_tas.start(current)
Esempio n. 6
0
class AsymmetricMagnet(HasTimeout, Actuator):
    """Class for the asymmetric ccmsans.

    Provides the ability to set the current field, and the asymmetry ratio.
    """

    parameters = {
        'asymmetry':
        Param('Asymmetry ratio',
              type=oneof(0, 11, 25, 39, 53, 70),
              settable=True,
              volatile=True),
    }

    parameter_overrides = {
        # max range * max ramp + 5'
        'timeout': Override(mandatory=False, default=5400 + 300)
    }

    def doReadAsymmetry(self):
        return self._dev.asymmetry

    def doWriteAsymmetry(self, value):
        self._dev.asymmetry = value
Esempio n. 7
0
class Ratemeter(Readable):
    """Toni ratemeter inside a "crate"."""

    attached_devices = {
        'bus': Attach('Toni communication bus', ModBus),
    }

    parameters = {
        'addr':
        Param('Bus address of crate',
              type=intrange(0xF0, 0xFF),
              mandatory=True),
    }

    def doRead(self, maxage=0):
        bus = self._attached_bus
        self._cachelock_acquire()
        try:
            # ratemeter is on channel 2
            bus.communicate('C2', self.addr, expect_ok=True)
            # send command (T = transmit, X = anything for input buffer update
            bus.communicate('TX', self.addr, expect_ok=True)
            # wait until response is ready
            rlen = -1
            t = 0
            while 1:
                sleep(0.05)
                ret = bus.communicate('R?', self.addr)
                if rlen == -1 or len(ret) == rlen:
                    return ret
                t += 1
                if t == 10:
                    raise CommunicationError('timeout while waiting for '
                                             'response')
        finally:
            self._cachelock_release()
Esempio n. 8
0
class DigitalInput(Moveable):
    """A test DigitalInput."""

    parameters = {
        '_value':
        Param('Simulated value',
              type=intrange(0, 0xFFFFFFFF),
              default=0,
              settable=False,
              internal=True),
    }

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

    valuetype = intrange(0, 0xFFFFFFFF)

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

    def doStatus(self, maxage=0):
        return status.OK, 'idle'
Esempio n. 9
0
class VirtualScanningDetector(SubscanMeasurable):
    """A virtual detector whose data acquisition consists of scanning a
    moveable device, and taking data points with another detector.
    """

    attached_devices = {
        'scandev':  Attach('Current device to scan', Moveable),
        'detector': Attach('Detector to scan', Measurable),
    }

    parameters = {
        'positions': Param('Positions to scan over', type=listof(float))
    }

    def doInit(self, mode):
        self._last = [0, '']
        self._preset = None

    def doSetPreset(self, **preset):
        self._preset = preset

    def doStart(self):
        positions = [[p] for p in self.positions]
        dataset = Scan([self._adevs['scandev']], positions, positions,
                       detlist=[self._adevs['detector']], preset=self._preset,
                       subscan=True).run()
        # process the values...
        yvalues = [subset.detvaluelist[0] for subset in dataset.subsets]
        self._last = [sum(yvalues) / float(len(yvalues)), dataset.filenames[0]]

    def valueInfo(self):
        return (Value(self.name + '.mean', unit='cts', fmtstr='%.1f'),
                Value(self.name + '.file', unit='', type='info'))

    def doRead(self, maxage=0):
        return self._last
Esempio n. 10
0
class AmorSlitLogicalMotor(AmorLogicalMotor):
    """ Class to represent the logical slit motors in AMOR.
    """
    parameters = {
        'motortype':
        Param('Type of motor %s' % ','.join(motortypes),
              type=oneof(*motortypes),
              mandatory=True),
    }

    parameter_overrides = {
        'unit': Override(mandatory=False, default='degree'),
        'target': Override(volatile=True),
        'abslimits': Override(mandatory=False, default=(-3.0, 3.0)),
        'userlimits': Override(mandatory=False, default=(-3.0, 3.0))
    }

    attached_devices = {
        'controller': Attach('Controller for the logical motors',
                             AmorSlitHandler)
    }

    def doRead(self, maxage=0):
        return self._attached_controller.doRead(maxage)
Esempio n. 11
0
class VSTiltMotor(EpicsMotor):
    """
    The tilt motor for a velocity selector can only be moved when
    the selector is standing. This class ensures just that.
    """
    attached_devices = {
        'vs_rotation': Attach('Velcocity Selector Rotation', Moveable),
    }

    parameters = {
        'limit':
        Param('Limit below which the rotation is considered standing',
              type=float,
              mandatory=True)
    }

    def doIsAllowed(self, target):
        if self._attached_vs_rotation.read(0) > self.limit:
            return False, \
                   'Velocity Selector must be stopped before moving tilt'
        return True, ''

    def doStart(self, pos):
        EpicsMotor.doStart(self, pos)
Esempio n. 12
0
class HistogramConfBank(HistogramConfElement):
    """ The Bank configuration element of the XML file. The bank has
    it's associated id which is to be used when fetching data from
    this bank. The bank can also have several axes attached. The rank
    of the bank (number of dimensions of the bank) is deduced from the
    number of axes attached to this bank.
    """
    parameters = {
        'bankid': Param('Integer id of the bank', type=int, mandatory=True)
    }

    attached_devices = {
        'axes': Attach('Axis for this bank', HistogramConfAxis, multiple=True)
    }

    @property
    def arrays(self):
        """ Provides a set of of all the arrays that appear in this
        bank.
        """
        # Collect all the arrays that occur in axis
        return {ax._attached_array for ax in self.axes if ax._attached_array}

    @property
    def axes(self):
        return self._attached_axes

    @property
    def shape(self):
        return [ax.length for ax in self.axes]

    def getXml(self):
        elem = etree.Element('bank', rank=str(len(self.axes)))
        for ax in self.axes:
            elem.append(ax.getXml())
        return elem
Esempio n. 13
0
class LiveViewSink(ImageSink):
    """A DataSink that sends images to attached clients for "live" preview.

    This sinks sends any data it receives in putResults, and also notifies
    clients about data filenames at the end of a measurement.

    For this sink to actually send "live" data, i.e. while counting is in
    progress, the detector(s) must return LIVE in their `duringMeasureHook`.
    Whenever it does, the NICOS acquire loop will read out data immediately and
    push it to the sink.  Returning INTERMEDIATE is also possible, but designed
    for use by data sinks that want to save data as "checkpoints" while
    counting, not just for live display.

    The frequency of the hook returning something other than None determines
    how often live data is updated.

    For the `nicos.devices.generic.Detector`, this is controlled by its
    "liveinterval" and "saveintervals" parameters.

    "datasets" is a list of the amount of data sets of each sent image.
    If more images are sent then data sets are listed the last value is
    set for all images exceeding the list length.
    """

    parameters = {
        'datasets': Param('Amount of datasets in each liveimage.',
                          type=listof(int), default=[1])
    }

    parameter_overrides = {
        # this is fixed string for labeling cached live data
        'filenametemplate': Override(mandatory=False, userparam=False,
                                     default=['<Live>@%d']),
    }

    handlerclass = LiveViewSinkHandler
Esempio n. 14
0
class EpicsMappedMoveable(MappedMoveable, EpicsDigitalMoveableEss):
    """
    EPICS based implementation of MappedMoveable. Useful for PVs that contain
    enums or bools.
    """

    parameters = {
        'ignore_stop':
        Param('Whether to do anything when stop is called',
              type=bool,
              default=False,
              userparam=False),
    }

    def doInit(self, mode):
        EpicsDigitalMoveableEss.doInit(self, mode)
        MappedMoveable.doInit(self, mode)

    def doReadTarget(self):
        target_value = EpicsDigitalMoveableEss.doReadTarget(self)

        # If this is from EPICS, it needs to be mapped, otherwise not
        if self.targetpv:
            return self._mapReadValue(target_value)

        return target_value

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

    def _startRaw(self, target):
        EpicsDigitalMoveableEss.doStart(self, target)

    def doStop(self):
        if not self.ignore_stop:
            EpicsDigitalMoveableEss.doStop(self)
Esempio n. 15
0
class VSForbiddenMoveable(WindowMoveable):
    """
    Velocity selectors have forbidden regions in which they are
    not supposed to run for reason of excessive vibration. This class
    checks for this
    """

    parameters = {
        'forbidden_regions':
        Param('List of forbidden regions',
              type=listof(tupleof(float, float)),
              mandatory=True)
    }

    valuetype = float

    def doIsAllowed(self, value):
        for region in self.forbidden_regions:
            if region[0] < value < region[1]:
                return False, \
                       'Desired value value is within ' \
                       'forbidden region %f to %f' \
                       % (region[0], region[1])
        return True, ''
Esempio n. 16
0
class AmorSingleDetectorImageChannel(HistogramImageChannel):
    """ The three single detectors in AMOR write the data on second
    bank in the histogram memory with each row representing the TOF
    data from a particular detector
    """
    parameters = {
        'detectorid': Param('ID of the single detector', type=int),
    }

    def _dimDesc(self):
        desc = HistogramImageChannel._dimDesc(self)
        return [desc[1]]

    @property
    def startid(self):
        return self.detectorid * self.bank.shape[1]

    @property
    def endid(self):
        return (self.detectorid + 1) * self.bank.shape[1]

    @property
    def shape(self):
        return [self.bank.shape[1]]
Esempio n. 17
0
class SecopDevice(Readable):
    # based on Readable instead of Device, as we want to have a status
    attached_devices = {
        'secnode': Attach('sec node', SecNodeDevice),
    }
    parameters = {
        'secop_module': Param('SECoP module', type=str, settable=False, userparam=False),
    }
    parameter_overrides = {
        # do not force to give unit in setup file (take from SECoP description)
        'unit': Override(default='', mandatory=False),
    }
    _status = (SecopStatus.ERROR, 'disconnected')
    STATUS_MAP = {
        0: status.DISABLED,
        1: status.OK,
        2: status.WARN,
        3: status.BUSY,
        4: status.ERROR,
    }
    _maintype = staticmethod(anytype)
    _defunct = False

    @classmethod
    def makeDevClass(cls, name, **config):
        """create a class with the needed doRead/doWrite methods

        for accessing the assigned SECoP module
        """
        secnodedev = session.getDevice(config['secnode'])
        # make a copy, as we will modify later
        params_override = config.pop('params_cfg', None)
        setup_info = secnodedev.get_setup_info()
        if name in setup_info:
            devcfg = dict(setup_info[name][1])
            params_cfg = devcfg.pop('params_cfg')
        else:
            devcfg, params_cfg = {}, {}
        if params_override is not None:
            params_cfg = dict(params_cfg)
            for pname, pold in list(params_cfg.items()):
                pnew = params_override.get(pname)
                if pnew is not None:
                    params_cfg[pname] = dict(pold, **pnew)
                elif pname not in cls.parameters:
                    params_cfg.pop(pname)  # remove parameters not mentioned
        devcfg.update(config)

        parameters = {}
        # create parameters and methods
        attrs = dict(parameters=parameters, __module__=cls.__module__)
        if 'valuetype' in config:
            # this is in fact the target value type
            attrs['valuetype'] = config.pop('valuetype')
        if 'maintype' in config:
            attrs['_maintype'] = staticmethod(config.pop('maintype'))
        for pname, kwargs in params_cfg.items():
            typ = kwargs['type']
            if 'fmtstr' not in kwargs and (typ is float or isinstance(typ, floatrange)):
                # the fmtstr default differs in SECoP and NICOS
                kwargs = dict(kwargs, fmtstr='%g')  # copy kwargs as it may be read only
            parameters[pname] = Param(volatile=True, **kwargs)

            def do_read(self, maxage=None, pname=pname, validator=typ):
                return self._read(pname, maxage, validator)

            attrs['doRead%s' % pname.title()] = do_read

            if kwargs.get('settable', False):
                def do_write(self, value, pname=pname, validator=typ):
                    return self._write(pname, value, validator)

                attrs['doWrite%s' % pname.title()] = do_write

        classname = cls.__name__ + '_' + name
        # create a new class extending SecopDevice, apply DeviceMeta in order to
        # include the added parameters
        newclass = DeviceMeta.__new__(DeviceMeta, classname, (cls,), attrs)  # pylint: disable=too-many-function-args
        newclass._modified_config = devcfg  # store temporarily for __init__
        return newclass

    def __new__(cls, name, **config):
        """called when an instance of the class is created but before __init__

        instead of returning a SecopDevice, we create an object of an extended class here
        """
        newclass = cls.makeDevClass(name, **config)
        return Readable.__new__(newclass)

    def __init__(self, name, **config):
        """apply modified config"""
        Readable.__init__(self, name, **self._modified_config)
        del self.__class__._modified_config

    def replaceClass(self, config):
        """replace the class on the fly

        happens when the structure fo the device has changed
        """
        cls = self.__class__.__bases__[0]
        newclass = cls.makeDevClass(self.name, **config)
        bad_attached = False
        for dev, cls in get_attaching_devices(self):
            if issubclass(newclass, cls):
                self.log.warning('reattach %s to %s' % (dev.name, self.name))
            else:
                self.log.error('can not attach %s to %s' % (dev.name, self.name))
                bad_attached = True
        if bad_attached:
            raise ConfigurationError('device class mismatch')
        for dev, cls in get_aliases(self):
            if issubclass(newclass, cls):
                self.log.warning('redirect alias %s to %s' % (dev.name, self.name))
            else:
                self.log.error('release alias %s from %s' % (dev.name, self.name))
                dev.alias = ''
        self.__class__ = newclass
        # as we do not go through self.__init__ again, we have to update self._config
        self._config = dict((name.lower(), value) for (name, value) in config.items())
        for aname in self.attached_devices:
            self._config.pop(aname, None)

    def doPreinit(self, mode):
        if mode != SIMULATION:
            self._attached_secnode.registerDevice(self)

    def _update(self, module, parameter, value, timestamp, readerror):
        if parameter not in self.parameters:
            return
        if readerror:
            return  # do not know how to indicate an error on a parameter
        try:
            # ignore timestamp for now
            self._setROParam(parameter, value)
        except Exception as err:
            self.log.error(repr(err))
            self.log.error('can not set %s:%s to %r' % (module, parameter, value))

    def _raise_defunct(self):
        if session.devices.get(self.name) == self:
            raise DefunctDevice('SECoP device %s no longer available' % self.name)
        raise DefunctDevice('refers to a replaced defunct SECoP device %s' % self.name)

    def _read(self, param, maxage, validator):
        try:
            secnode = self._attached_secnode._secnode
        except AttributeError:
            self._raise_defunct()
        value, timestamp, _ = secnode.cache[self.secop_module, param]
        if maxage is not None and time.time() > (timestamp or 0) + maxage:
            value = secnode.getParameter(self.secop_module, param)[0]
        if value is not None:
            value = validator(value)
        return value

    def _write(self, param, value, validator):
        try:
            value = validator(value)
            self._attached_secnode._secnode.setParameter(self.secop_module, param, value)
            return value
        except AttributeError:
            self._raise_defunct()

    def setDefunct(self):
        if self._defunct:
            self.log.error('device is already defunct')
            return
        self.updateSecopStatus((SecopStatus.ERROR, 'defunct'))
        self._defunct = True
        if self._attached_secnode is not None:
            self._attached_secnode.unregisterDevice(self)
            # make defunct
            self._attached_secnode = None
            self._cache = None

    def setAlive(self, secnode):
        self._defunct = False
        self._cache = secnode._cache
        self._attached_secnode = secnode
        secnode.registerDevice(self)
        # clear defunct status
        self.updateSecopStatus((SecopStatus.IDLE, ''))

    def doShutdown(self):
        if not self._defunct:
            self.setDefunct()

    def doRead(self, maxage=0):
        """dummy, as there is no value"""
        return ''

    def updateSecopStatus(self, value):
        """update status from SECoP status value"""
        self._status = value
        if self._cache:
            self._cache.put(self, 'status', self.doStatus())

    def _update_status(self, module, parameter, value, timestamp, readerror):
        if value is not None:
            self.updateSecopStatus(tuple(value))

    def doStatus(self, maxage=0):
        code, text = self._status
        if 390 <= code < 400:  # SECoP status finalizing
            return status.OK, text
        # treat SECoP code 401 (unknown) as error - should be distinct from NICOS status unknown
        return self.STATUS_MAP.get(code // 100, status.UNKNOWN), text
Esempio n. 18
0
class SecNodeDevice(Readable):
    """SEC node device

    want to have a status -> based on Readable
    """

    parameters = {
        'prefix': Param("Prefix for the generated devices\n\n'$' will be replaced by the equipment id",
                        type=str, default='$_', settable=True),
        'uri': Param('tcp://<host>:<port>', type=str, settable=True),
        'auto_create': Param('flag for automatic creation of devices', type=bool,
                             settable=False, prefercache=False, default=False, userparam=False),
        'setup_info': Param('setup info', type=anytype, default={}, settable=True),
    }
    parameter_overrides = {
        'unit': Override(default='', mandatory=False),
    }

    valuetype = str
    _secnode = None
    _value = ''
    _status = status.OK, 'unconnected'
    _devices = {}

    def doPreinit(self, mode):
        self._devices = {}

    def doInit(self, mode):
        if mode == MASTER:
            if self.uri:
                try:
                    self._connect()
                except Exception:
                    pass
        elif mode == SIMULATION:
            setup_info = self.get_setup_info()
            if self.auto_create:
                self.makeDynamicDevices(setup_info)
            else:
                self._setROParam('setup_info', setup_info)

    def get_setup_info(self):
        if self._mode == SIMULATION:
            db = session.getSyncDb()
            return db.get('%s/setup_info' % self.name.lower())
        return self.setup_info

    def doRead(self, maxage=0):
        if self._secnode:
            if self._secnode.online:
                self._value = self._secnode.nodename
        else:
            self._value = ''
        return self._value

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

    def _set_status(self, code, text):
        self._status = code, text
        if self._cache:
            self._cache.put(self, 'status', self._status)
            self._cache.put(self, 'value', self.doRead())

    def doWriteUri(self, value):
        """change uri and reconnect"""
        self._setROParam('uri', value)  # make sure uri is set before reconnect
        if self.uri:
            self._connect()
        else:
            self._disconnect()
        return value

    def _connect(self):
        """try to connect

        called on creation and on uri change,
        but NOT on automatic reconnection
        """
        if not self.uri:
            self._disconnect()
        if self._secnode:
            self._secnode.disconnect()
        self._secnode = SecopClient(self.uri, log=self.log)
        self._secnode.register_callback(None, self.nodeStateChange, self.descriptiveDataChange)
        try:
            self._secnode.connect()
            self._set_status(status.OK, 'connected')
            self.createDevices()
            return
        except Exception as e:
            if not isinstance(e, CommunicationFailedError):
                raise
            self.log.warning('can not connect to %s (%s), retry in background' % (self.uri, e))
            self._set_status(status.ERROR, 'try connecting')
            start_event = Event()
            self._secnode.spawn_connect(start_event.set)

    def _disconnect(self):
        if not self._secnode:
            return
        self.removeDevices()
        self._secnode.disconnect()
        self._set_status(status.OK, 'unconnected')
        self._secnode = None

    def descriptiveDataChange(self, module, description):
        """called when descriptive data changed after an automatic reconnection"""
        self.log.warning('node description changed')
        self.createDevices()

    def get_status(self, online, state):
        if not online:
            return status.ERROR, state
        return status.OK if state == 'connected' else status.WARN, state

    def nodeStateChange(self, online, state):
        """called when the state of the connection changes

        'online' is True when connected or reconnecting, False when disconnected or connecting
        'state' is the connection state as a string
        """
        if online and state == 'connected':
            self._set_status(status.OK, 'connected')
        elif not online:
            self._set_status(status.ERROR, 'reconnecting')
            for device in self._devices.values():
                device.updateSecopStatus((400, 'disconnected'))
        else:
            self._set_status(status.WARN, state)

    def doShutdown(self):
        self._disconnect()
        if self._devices:
            self.log.error('can not remove devices %r' % list(self._devices))

    def _get_prefix(self):
        if not self._secnode:
            return None
        equipment_name = clean_identifier(self._secnode.nodename).lower()
        return self.prefix.replace('$', equipment_name)

    @usermethod
    def showModules(self):
        """show modules of the connected SECoP server

        and intended devices names using the given prefix
        """
        prefix = self._get_prefix()
        if prefix is None:
            self.log.error('secnode is not connected')
            return
        items = [(prefix + m, m, mod_desc.get('properties', {}).get('description', '').split('\n')[0])
                 for m, mod_desc in self._secnode.modules.items()]
        printTable(['foreseen device name', 'SECoP module', 'description'], items, self.log.info)

    def registerDevice(self, device):
        if not self._secnode:
            raise IOError('unconnected')
        self.log.debug('register %s on %s' % (device, self))
        self._devices[device.name] = device
        module = device.secop_module
        if module not in self._secnode.modules:
            raise ConfigurationError('no module %r found on this SEC node' % module)
        for parameter in self._secnode.modules[module]['parameters']:
            updatefunc = getattr(device, '_update_' + parameter, device._update)
            self._secnode.register_callback((module, parameter), updateEvent=updatefunc)
            try:
                data = self._secnode.cache[module, parameter]
                if data:
                    updatefunc(module, parameter, *data)
                else:
                    self.log.warning('No data for %s:%s' % (module, parameter))
            except KeyError:
                self.log.warning('No cache for %s:%s' % (module, parameter))

    def unregisterDevice(self, device):
        self.log.debug('unregister %s from %s' % (device, self))
        session.configured_devices.pop(device.name, None)
        if self._devices.pop(device.name, None) is None:
            self.log.info('device %s already removed' % device.name)
            return
        module = device.secop_module
        try:
            moddesc = self._secnode.modules[module]
        except KeyError:  # do not complain again about missing module
            return
        for parameter in moddesc['parameters']:
            updatefunc = getattr(device, '_update_' + parameter, device._update)
            self._secnode.unregister_callback((module, parameter), updateEvent=updatefunc)

    def createDevices(self):
        """create drivers and devices

        for the devices to be created from the connected SECoP server
        """
        if not self._secnode:
            self.log.error('secnode is not connected')
            return
        modules = self._secnode.modules
        prefix = self._get_prefix()
        setup_info = {}
        for module, mod_desc in modules.items():
            params_cfg = {}
            module_properties = mod_desc.get('properties', None)
            for ifclass in (module_properties.get('interface_classes', []) or
                            module_properties.get('interface_class', [])):
                try:
                    clsname = IFCLASSES[ifclass.title()]
                    break
                except KeyError:
                    continue
            else:
                clsname = 'SecopDevice'
            kwds = {}
            for pname, props in mod_desc['parameters'].items():
                datatype = props['datatype']
                typ = get_validator(**datatype.export_datatype())
                pargs = dict(type=typ, description=props['description'])
                if not props.get('readonly', True) and pname != 'target':
                    pargs['settable'] = True
                unit = ''
                fmtstr = None
                if isinstance(datatype, FloatRange):
                    fmtstr = getattr(datatype, 'fmtstr', '%g')
                    unit = getattr(datatype, 'unit', '')
                elif isinstance(datatype, ScaledInteger):
                    fmtstr = getattr(datatype, 'fmtstr',
                                     '%%%df' % max(0, -floor(log10(props['scale']))))
                    unit = getattr(datatype, 'unit', '')
                if unit:
                    pargs['unit'] = unit
                if pname == 'status':
                    continue
                if pname == 'value':
                    try:
                        kwds['unit'] = datatype.unit
                    except AttributeError:
                        pass
                    if fmtstr is not None:
                        kwds['fmtstr'] = fmtstr
                    else:
                        kwds['fmtstr'] = '%r'
                    kwds['maintype'] = typ
                    continue
                if pname == 'target':
                    kwds['valuetype'] = typ
                if fmtstr is not None and fmtstr != '%g':
                    pargs['fmtstr'] = fmtstr
                params_cfg[pname] = pargs
            if clsname != 'SecopDevice':
                kwds.setdefault('unit', '')  # unit is mandatory on Readables
            desc = dict(secnode=self.name,
                        description=mod_desc.get('properties', {}).get('description', ''),
                        secop_module=module,
                        params_cfg=params_cfg,
                        **kwds)
            setup_info[prefix + module] = ('nicos.devices.secop.%s' % clsname, desc)
        if not setup_info:
            self.log.info('creating devices for %s skipped' % self.name)
            return
        if self.auto_create:
            self.makeDynamicDevices(setup_info)
        else:
            self._setROParam('setup_info', setup_info)

    def removeDevices(self):
        self.makeDynamicDevices({})

    def makeDynamicDevices(self, setup_info):
        """create and destroy dynamic devices

        create devices from setup_info, and store the name of the setup
        creating the creator device in session.creator_devices for
        session.getSetupInfo()
        Based on the previous setup_info from self.setup_info,
        devices are created, recreated, destroyed or remain unchanged.
        If setup_info is empty, destroy all devices.
        """
        prevdevices = set(self.setup_info.keys())
        self._setROParam('setup_info', setup_info)

        # find setup of this secnode
        result = session.getSetupInfo()
        for setupname in session.loaded_setups:
            info = result.get(setupname, None)
            if info and self.name in info['devices']:
                break
        else:
            raise ConfigurationError('can not find setup')
        # add new or changed devices
        for devname, devcfg in setup_info.items():
            prevdevices.discard(devname)
            dev = session.devices.get(devname, None)
            if dev:
                if not isinstance(dev, SecopDevice) or (dev._attached_secnode and
                                                        dev._attached_secnode != self):
                    self.log.error('device %s already exists' % devname)
                    continue
                base = dev.__class__.__bases__[0]
                prevcfg = base.__module__ + '.' + base.__name__, dict(secnode=self.name, **dev._config)
            else:
                prevcfg = None
            if prevcfg != devcfg:
                session.configured_devices[devname] = devcfg
                session.dynamic_devices[devname] = setupname  # pylint: disable=undefined-loop-variable
                if dev is None:
                    # add new device
                    session.createDevice(devname, recreate=True, explicit=True)
                    dev = session.devices[devname]
                else:
                    # modify existing device
                    if dev._attached_secnode:
                        dev._attached_secnode.unregisterDevice(dev)
                    session.configured_devices[devname] = devcfg
                    session.dynamic_devices[devname] = setupname  # pylint: disable=undefined-loop-variable
                    try:
                        dev.replaceClass(devcfg[1])
                        dev.setAlive(self)
                    except ConfigurationError:
                        # above failed because an alias or attaching device requires a specific class
                        # make old device defunct and replace by a new device
                        session.destroyDevice(dev)
                        session.dynamic_devices.pop(devname, None)
                        session.createDevice(devname, recreate=True, explicit=True)
                        prevdevices.discard(devname)
                        dev = session.devices[devname]
                if not isinstance(dev, SecopReadable):
                    # we will not get status updates for these
                    dev.updateSecopStatus((SecopStatus.IDLE, ''))

        defunct = set()
        # defunct devices no longer available
        for devname in prevdevices:
            dev = session.devices.get(devname)
            if dev is None or dev._attached_secnode != self:
                continue
            if dev._sdevs:
                self.log.warning('defunct device is attached to %s' % ', '.join(dev._sdevs))
            dev.setDefunct()
            defunct.add(devname)

        # inform client that setups have changed
        session.setupCallback(list(session.loaded_setups), list(session.explicit_setups))
Esempio n. 19
0
class Andor2LimaCCD(GenericLimaCCD):
    """
    This device class is an extension to the GenericLimaCCD that adds the
    hardware specific functionality for all Andor SDK2 based cameras.
    """

    HSSPEEDS = [5, 3, 1, 0.05]  # Values from sdk manual
    VSSPEEDS = [16, 38.55, 76.95]  # Values from sdk manual
    PGAINS = [1, 2, 4]  # Values from sdk manual

    HSSPEED_RE = re.compile(r'ADC0_(\d+\.\d+|\d+)MHZ')
    VSSPEED_RE = re.compile(r'(\d+(?:\.\d+)?)USEC')
    PGAIN_RE = re.compile(r'X(\d)')

    parameters = {
        'hsspeed':
        Param('Horizontal shift speed',
              type=oneof(*HSSPEEDS),
              settable=True,
              default=5,
              unit='MHz',
              volatile=True,
              category='general'),
        'vsspeed':
        Param('Vertical shift speed',
              type=oneof(*VSSPEEDS),
              settable=True,
              default=76.95,
              unit='ms/shift',
              volatile=True,
              category='general'),
        'pgain':
        Param('Preamplifier gain',
              type=oneof(*PGAINS),
              settable=True,
              default=4,
              volatile=True,
              category='general'),
    }

    parameter_overrides = {
        'hwdevice': Override(mandatory=True),
    }

    def doInfo(self):
        for p in ('hsspeed', 'vsspeed', 'pgain'):
            self._pollParam(p)
        return []

    def doReadHsspeed(self):
        val = float(self.HSSPEED_RE.match(self._hwDev._dev.adc_speed).group(1))
        return val

    def doWriteHsspeed(self, value):
        self._hwDev._dev.adc_speed = 'ADC0_%sMHZ' % value

    def doReadVsspeed(self):
        val = float(self.VSSPEED_RE.match(self._hwDev._dev.vs_speed).group(1))
        return val

    def doWriteVsspeed(self, value):
        self._hwDev._dev.vs_speed = '%gUSEC' % value

    def doReadPgain(self):
        val = float(self.PGAIN_RE.match(self._hwDev._dev.p_gain).group(1))
        return val

    def doWritePgain(self, value):
        self._hwDev._dev.p_gain = 'X%s' % value
Esempio n. 20
0
class McStasImage(ImageChannelMixin, PassiveChannel):
    """Image channel based on McStas simulation."""

    _mythread = None

    _process = None

    parameters = {
        'size':
        Param(
            'Detector size in pixels (x, y)',
            settable=False,
            type=tupleof(intrange(1, 8192), intrange(1, 8192)),
            default=(1, 1),
        ),
        'mcstasprog':
        Param('Name of the McStas simulation executable',
              type=str,
              settable=False),
        'mcstasdir':
        Param('Directory where McStas stores results',
              type=str,
              default='singlecount',
              settable=False),
        'mcstasfile':
        Param('Name of the McStas data file', type=str, settable=False),
        'mcsiminfo':
        Param('Name for the McStas Siminfo file',
              settable=False,
              type=str,
              default='mccode.sim'),
        'ci':
        Param('Constant ci applied to simulated intensity I',
              settable=False,
              type=floatrange(0.),
              default=1e3)
    }

    def doInit(self, mode):
        self.arraydesc = ArrayDesc(self.name, self.size, '<u4')
        self._workdir = os.getcwd()

    def doReadArray(self, quality):
        self.log.debug('quality: %s', quality)
        if quality == LIVE:
            self._send_signal(SIGUSR2)
        elif quality == FINAL:
            if self._mythread and self._mythread.is_alive():
                self._mythread.join(1.)
                if self._mythread.is_alive():
                    self.log.exception("Couldn't join readout thread.")
                else:
                    self._mythread = None
        self._readpsd(quality == LIVE)
        return self._buf

    def _prepare_params(self):
        """Return a list of key=value strings.

        Each entry defines a parameter setting for the mcstas simulation call.

        examples:
            param=10
        """
        raise NotImplementedError('Please implement _prepare_params method')

    def doPrepare(self):
        self._mcstas_params = ' '.join(self._prepare_params())
        self.log.debug('McStas parameters: %s', self._mcstas_params)
        self._buf = np.zeros(self.size[::-1])
        self.readresult = [0]

    def valueInfo(self):
        return (Value(self.name + '.sum',
                      unit='cts',
                      type='counter',
                      errors='sqrt',
                      fmtstr='%d'), )

    def doStart(self):
        self._mythread = createThread('detector %s' % self, self._run)

    def doStatus(self, maxage=0):
        if self._mythread and self._mythread.is_alive():
            return status.BUSY, 'busy'
        return status.OK, 'idle'

    def doFinish(self):
        self.log.debug('finish')
        self._send_signal(SIGTERM)

    def _send_signal(self, sig):
        if self._process and self._process.is_running():
            self._process.send_signal(sig)
            # wait for mcstas releasing fds
            datafile = path.join(self._workdir, self.mcstasdir,
                                 self.mcstasfile)
            siminfo = path.join(self._workdir, self.mcstasdir, self.mcsiminfo)
            try:
                while self._process and self._process.is_running():
                    fnames = [f.path for f in self._process.open_files()]
                    if siminfo not in fnames and datafile not in fnames:
                        break
                    session.delay(.01)
            except (AccessDenied, NoSuchProcess):
                self.log.debug(
                    'McStas process already terminated in _send_signal(%r)',
                    sig)
            self.log.debug('McStas process has written file on signal (%r)',
                           sig)

    def _run(self):
        """Run McStas simulation executable.

        The current settings of the instrument parameters will be transferred
        to it.
        """
        try:
            shutil.rmtree(self.mcstasdir)
        except (IOError, OSError):
            self.log.info('could not remove old data')
        command = '%s -n 1e8 -d %s %s' % (self.mcstasprog, self.mcstasdir,
                                          self._mcstas_params)
        self.log.debug('run %s', command)
        try:
            self._process = Popen(command.split(),
                                  stdout=PIPE,
                                  stderr=PIPE,
                                  cwd=self._workdir)
            out, err = self._process.communicate()
            if out:
                self.log.debug('McStas output:')
                for line in out.splitlines():
                    self.log.debug('[McStas] %s', line)
            if err:
                self.log.warning('McStas found some problems:')
                for line in err.splitlines():
                    self.log.warning('[McStas] %s', line)
        except OSError as e:
            self.log.error('Execution failed: %s', e)
        self._process.wait()
        self._process = None

    def _readpsd(self, ignore_error=False):
        try:
            with open(
                    path.join(self._workdir, self.mcstasdir, self.mcstasfile),
                    'r') as f:
                lines = f.readlines()[-3 * (self.size[0] + 1):]
            if lines[0].startswith('# Data') and self.mcstasfile in lines[0]:
                self._buf = (
                    np.loadtxt(lines[1:self.size[0] + 1], dtype=np.float32) *
                    self.ci).astype(np.uint32)
                self.readresult = [self._buf.sum()]
            elif not ignore_error:
                raise IOError('Did not find start line: %s' % lines[0])
        except IOError:
            if not ignore_error:
                self.log.exception('Could not read result file')
Esempio n. 21
0
class EpicsMotor(CanDisable, CanReference, HasOffset, EpicsAnalogMoveableEss,
                 Motor):
    """
    This device exposes some of the functionality provided by the EPICS motor
    record. The PV names for the fields of the record (readback, speed, etc.)
    are derived by combining the motorpv-parameter with the predefined field
    names.

    The errorbitpv and reseterrorpv can be provided optionally in case the
    controller supports reporting errors and a reset-mechanism that tries to
    recover from certain errors. If present, these are used when calling the
    reset()-method.

    Another optional PV is the errormsgpv, which contains an error message that
    may originate from the motor controller or the IOC. If it is present,
    doStatus uses it for some of the status messages.
    """
    parameters = {
        'motorpv':
        Param('Name of the motor record PV.',
              type=pvname,
              mandatory=True,
              settable=False,
              userparam=False),
        'errormsgpv':
        Param('Optional PV with error message.',
              type=pvname,
              mandatory=False,
              settable=False,
              userparam=False),
        'errorbitpv':
        Param('Optional PV with error bit.',
              type=pvname,
              mandatory=False,
              settable=False,
              userparam=False),
        'reseterrorpv':
        Param('Optional PV with error reset switch.',
              type=pvname,
              mandatory=False,
              settable=False,
              userparam=False),
    }

    parameter_overrides = {
        # readpv and writepv are determined automatically from the base PV
        'readpv': Override(mandatory=False, userparam=False, settable=False),
        'writepv': Override(mandatory=False, userparam=False, settable=False),

        # speed, limits and offset may change from outside, can't rely on cache
        'speed': Override(volatile=True),
        'offset': Override(volatile=True, chatty=False),
        'abslimits': Override(volatile=True),
    }

    # Fields of the motor record for which an interaction via Channel Access
    # is required.
    def _get_record_fields(self):
        return {
            'readpv': 'RBV',
            'writepv': 'VAL',
            'stop': 'STOP',
            'donemoving': 'DMOV',
            'moving': 'MOVN',
            'miss': 'MISS',
            'homeforward': 'HOMF',
            'homereverse': 'HOMR',
            'speed': 'VELO',
            'offset': 'OFF',
            'highlimit': 'HLM',
            'lowlimit': 'LLM',
            'softlimit': 'LVIO',
            'lowlimitswitch': 'LLS',
            'highlimitswitch': 'HLS',
            'enable': 'CNEN',
            'set': 'SET',
            'foff': 'FOFF',
        }

    def _get_pv_parameters(self):
        """
        Implementation of inherited method to automatically account for fields
        present in motor record.

        :return: List of PV aliases.
        """
        pvs = set(self._record_fields)

        if self.errormsgpv:
            pvs.add('errormsgpv')

        if self.errorbitpv:
            pvs.add('errorbitpv')

        if self.reseterrorpv:
            pvs.add('reseterrorpv')

        return pvs

    def _get_pv_name(self, pvparam):
        """
        Implementation of inherited method that translates between PV aliases
        and actual PV names. Automatically adds a prefix to the PV name
        according to the motorpv parameter.

        :param pvparam: PV alias.
        :return: Actual PV name.
        """
        motor_record_prefix = getattr(self, 'motorpv')
        motor_field = self._record_fields.get(pvparam)

        if motor_field is not None:
            return '.'.join((motor_record_prefix, motor_field))

        return getattr(self, pvparam)

    def doReadSpeed(self):
        return self._get_pv('speed')

    def doWriteSpeed(self, newValue):
        speed = self._get_valid_speed(newValue)

        if speed != newValue:
            self.log.warning(
                'Selected speed %s is outside the parameter '
                'limits, using %s instead.', newValue, speed)

        self._put_pv('speed', speed)

    def doReadOffset(self):
        return self._get_pv('offset')

    def doWriteOffset(self, value):
        # In EPICS, the offset is defined in following way:
        # USERval = HARDval + offset

        if self.offset != value:
            diff = value - self.offset

            # Set the offset in motor record
            self._put_pv_blocking('offset', value)

            # Read the absolute limits from the device as they have changed.
            self.abslimits  # pylint: disable=pointless-statement

            # Adjust user limits
            self.userlimits = (self.userlimits[0] + diff,
                               self.userlimits[1] + diff)

            self.log.info('The new user limits are: ' + str(self.userlimits))

    def doAdjust(self, oldvalue, newvalue):
        diff = oldvalue - newvalue
        # For EPICS the offset sign convention differs to that of the base
        # implementation.
        self.offset -= diff

    def _get_valid_speed(self, newValue):
        min_speed = self._get_pvctrl('speed', 'lower_ctrl_limit', 0.0)
        max_speed = self._get_pvctrl('speed', 'upper_ctrl_limit', 0.0)

        valid_speed = newValue
        if min_speed != 0.0:
            valid_speed = max(min_speed, valid_speed)

        if max_speed != 0.0:
            valid_speed = min(max_speed, valid_speed)

        return valid_speed

    def doRead(self, maxage=0):
        return self._get_pv('readpv')

    def doStart(self, pos):
        self._put_pv('writepv', pos)

    def doReadTarget(self):
        return self._get_pv('writepv')

    def doStatus(self, maxage=0):
        general_epics_status, _ = self._get_mapped_epics_status()
        message = self._get_status_message()

        if general_epics_status == status.ERROR:
            return status.ERROR, message or 'Unknown problem in record'

        done_moving = self._get_pv('donemoving')
        moving = self._get_pv('moving')
        if done_moving == 0 or moving != 0:
            return status.BUSY, message or 'Motor is moving to target...'

        miss = self._get_pv('miss')
        if miss != 0:
            return (status.NOTREACHED, message
                    or 'Did not reach target position.')

        high_limitswitch = self._get_pv('highlimitswitch')
        if high_limitswitch != 0:
            return status.WARN, message or 'At high limit switch.'

        low_limitswitch = self._get_pv('lowlimitswitch')
        if low_limitswitch != 0:
            return status.WARN, message or 'At low limit switch.'

        limit_violation = self._get_pv('softlimit')
        if limit_violation != 0:
            return status.WARN, message or 'Soft limit violation.'

        return status.OK, message

    def _get_status_message(self):
        """
        Get the status message from the motor if the PV exists.

        :return: The status message if it exists, otherwise an empty string.
        """
        if not self.errormsgpv:
            return ''

        return self._get_pv('errormsgpv', as_string=True)

    def doStop(self):
        self._put_pv('stop', 1, False)

    def _checkLimits(self, limits):
        # Called by doReadUserlimits and doWriteUserlimits
        low, high = self.abslimits
        if low == 0 and high == 0:
            # No limits defined in IOC.
            # Could be a rotation stage for example.
            return

        if limits[0] < low or limits[1] > high:
            raise ConfigurationError('cannot set user limits outside of '
                                     'absolute limits (%s, %s)' % (low, high))

    def doReadAbslimits(self):
        absmin = self._get_pv('lowlimit')
        absmax = self._get_pv('highlimit')
        return absmin, absmax

    def doReference(self):
        self._put_pv_blocking('homeforward', 1)

    def doReset(self):
        if self.errorbitpv and self.reseterrorpv:
            error_bit = self._get_pv('errorbitpv')
            if error_bit == 0:
                self.log.warning(
                    'Error bit is not set, can not reset error state.')
            else:
                self._put_pv('reseterrorpv', 1)

    def _enable(self, on):
        what = 1 if on else 0
        self._put_pv('enable', what, False)

    def doSetPosition(self, pos):
        self._put_pv('set', 1)
        self._put_pv('foff', 1)
        self._put_pv('writepv', pos)
        self._put_pv('set', 0)
        self._put_pv('foff', 0)
Esempio n. 22
0
class BaseCacheClient(Device):
    """
    An extensible read/write client for the NICOS cache.
    """

    parameters = {
        'cache':
        Param('"host[:port]" of the cache instance to connect to',
              type=host(defaultport=DEFAULT_CACHE_PORT),
              mandatory=True),
        'prefix':
        Param('Cache key prefix', type=str, mandatory=True),
    }

    remote_callbacks = True
    _worker = None
    _startup_done = None

    def doInit(self, mode):
        # Should the worker connect or disconnect?
        self._should_connect = True
        # this event is set as soon as:
        # * the connection is established and the connect_action is done, or
        # * the initial connection failed
        # this prevents devices from polling parameter values before all values
        # from the cache have been received
        self._startup_done = threading.Event()
        self._connected = False
        self._socket = None
        self._secsocket = None
        self._sec_lock = threading.RLock()
        self._prefix = self.prefix.strip('/')
        if self._prefix:
            self._prefix += '/'
        self._selecttimeout = CYCLETIME  # seconds
        self._do_callbacks = self.remote_callbacks
        self._disconnect_warnings = 0
        # maps newprefix -> oldprefix without self._prefix prepended
        self._inv_rewrites = {}
        # maps oldprefix -> set of new prefixes without self._prefix prepended
        self._rewrites = {}
        self._prefixcallbacks = {}

        self._stoprequest = False
        self._queue = queue.Queue()
        self._synced = True

        # create worker thread, but do not start yet, leave that to subclasses
        self._worker = createThread('CacheClient worker',
                                    self._worker_thread,
                                    start=False)

    def _getCache(self):
        return None

    def doShutdown(self):
        self._stoprequest = True
        if self._worker and self._worker.is_alive():
            self._worker.join()

    def _connect(self):
        self._do_callbacks = False
        self._startup_done.clear()
        self.log.debug('connecting to %s', self.cache)
        try:
            self._socket = tcpSocket(self.cache,
                                     DEFAULT_CACHE_PORT,
                                     timeout=5,
                                     keepalive=10)
        except Exception as err:
            self._disconnect('unable to connect to %s: %s' % (self.cache, err))
        else:
            self.log.info('now connected to %s', self.cache)
            self._connected = True
            self._disconnect_warnings = 0
            try:
                self._connect_action()
            except Exception as err:
                self._disconnect('unable to init connection to %s: %s' %
                                 (self.cache, err))
        self._startup_done.set()
        self._do_callbacks = self.remote_callbacks

    def _disconnect(self, why=''):
        self._connected = False
        self._startup_done.clear()
        if why:
            if self._disconnect_warnings % 10 == 0:
                self.log.warning(why)
            self._disconnect_warnings += 1
        if self._socket:
            closeSocket(self._socket)
            self._socket = None
        # close secondary socket
        with self._sec_lock:
            if self._secsocket:
                closeSocket(self._secsocket)
                self._secsocket = None
        self._disconnect_action()

    def _wait_retry(self):
        sleep(self._long_loop_delay)

    def _wait_data(self):
        pass

    def _connect_action(self):
        # send request for all keys and updates....
        # (send a single request for a nonexisting key afterwards to
        # determine the end of data)
        msg = '@%s%s\n%s%s\n' % (self._prefix, OP_WILDCARD, END_MARKER, OP_ASK)
        self._socket.sendall(to_utf8(msg))

        # read response
        data, n = b'', 0
        sentinel = to_utf8(END_MARKER + OP_TELLOLD + '\n')
        while not data.endswith(sentinel) and n < 1000:
            data += self._socket.recv(BUFSIZE)
            n += 1

        # send request for all updates
        msg = '@%s%s\n' % (self._prefix, OP_SUBSCRIBE)
        self._socket.sendall(to_utf8(msg))
        for prefix in self._prefixcallbacks:
            msg = '@%s%s\n' % (prefix, OP_SUBSCRIBE)
            self._socket.sendall(to_utf8(msg))

        self._process_data(data)

    def _disconnect_action(self):
        pass

    def _handle_msg(self, time, ttlop, ttl, tsop, key, op, value):
        raise NotImplementedError('implement _handle_msg in subclasses')

    def _process_data(self,
                      data,
                      sync_str=to_utf8(SYNC_MARKER + OP_TELLOLD),
                      lmatch=line_pattern.match,
                      mmatch=msg_pattern.match):
        # n = 0
        i = 0  # avoid making a string copy for every line
        match = lmatch(data, i)
        while match:
            line = match.group(1)
            i = match.end()
            if sync_str in line:
                self.log.debug('process data: received sync: %r', line)
                self._synced = True
            else:
                msgmatch = mmatch(from_utf8(line))
                # ignore invalid lines
                if msgmatch:
                    # n += 1
                    try:
                        self._handle_msg(**msgmatch.groupdict())
                    except Exception:
                        self.log.exception('error handling message %r',
                                           msgmatch.group())
            # continue loop
            match = lmatch(data, i)
        # self.log.debug('processed %d items', n)
        return data[i:]

    def _worker_thread(self):
        while True:
            try:
                self._worker_inner()
            except Exception:
                self.log.exception('exception in cache worker thread; '
                                   'restarting (please report a bug)')
                if self._stoprequest:
                    break  # ensure we do not restart during shutdown
            else:
                # normal termination
                break

    def _worker_inner(self):
        data = b''
        process = self._process_data

        while not self._stoprequest:
            if self._should_connect:
                if not self._socket:
                    self._connect()
                    if not self._socket:
                        self._wait_retry()
                        continue
            else:
                if self._socket:
                    self._disconnect()
                self._wait_retry()
                continue

            # process data so far
            data = process(data)

            # wait for a whole line of data to arrive
            while b'\n' not in data and self._socket and self._should_connect \
                  and not self._stoprequest:

                # optionally do some action while waiting
                self._wait_data()

                if self._queue.empty():
                    # NOTE: the queue.empty() check is not 100% reliable, but
                    # that is not important here: all we care is about not
                    # having the select always return immediately for writing
                    writelist = []
                else:
                    writelist = [self._socket]

                # read or write some data
                while 1:
                    try:
                        res = select.select([self._socket], writelist, [],
                                            self._selecttimeout)
                    except EnvironmentError as e:
                        if e.errno == errno.EINTR:
                            continue
                        raise
                    except TypeError:
                        # socket was None, let the outer loop handle that
                        res = ([], [], [])
                    break

                if res[1]:
                    # determine if something needs to be sent
                    tosend = ''
                    itemcount = 0
                    try:
                        # bunch a few messages together, but not unlimited
                        for _ in xrange(10):
                            tosend += self._queue.get(False)
                            itemcount += 1
                    except queue.Empty:
                        pass
                    # write data
                    try:
                        self._socket.sendall(to_utf8(tosend))
                    except Exception:
                        self._disconnect('disconnect: send failed')
                        # report data as processed, but then re-queue it to send
                        # after reconnect
                        for _ in range(itemcount):
                            self._queue.task_done()
                        data = b''
                        self._queue.put(tosend)
                        break
                    for _ in range(itemcount):
                        self._queue.task_done()
                if res[0]:
                    # got some data
                    try:
                        newdata = self._socket.recv(BUFSIZE)
                    except Exception:
                        newdata = b''
                    if not newdata:
                        # no new data from blocking read -> abort
                        self._disconnect('disconnect: recv failed')
                        data = b''
                        break
                    data += newdata

        if self._socket:
            # send rest of data
            tosend = ''
            itemcount = 0
            try:
                while 1:
                    tosend += self._queue.get(False)
                    itemcount += 1
            except queue.Empty:
                pass
            try:
                self._socket.sendall(to_utf8(tosend))
            except Exception:
                self.log.debug('exception while sending last batch of updates',
                               exc=1)
                # no reraise, we'll disconnect below anyways
            for _ in range(itemcount):
                self._queue.task_done()

        # end of while loop
        self._disconnect()

    def _single_request(self, tosend, sentinel=b'\n', retry=2, sync=False):
        """Communicate over the secondary socket."""
        if not self._socket:
            self._disconnect('single request: no socket')
            if not self._socket:
                raise CacheError('cache not connected')
        if sync:
            # sync has to be false for lock requests, as these occur during startup
            self._queue.join()
        with self._sec_lock:
            if not self._secsocket:
                try:
                    self._secsocket = tcpSocket(self.cache, DEFAULT_CACHE_PORT)
                except Exception as err:
                    self.log.warning(
                        'unable to connect secondary socket '
                        'to %s: %s', self.cache, err)
                    self._secsocket = None
                    self._disconnect('secondary socket: could not connect')
                    raise CacheError('secondary socket could not be created')

            try:
                # write request
                # self.log.debug("get_explicit: sending %r", tosend)
                self._secsocket.sendall(to_utf8(tosend))

                # give 10 seconds time to get the whole reply
                timeout = currenttime() + 10
                # read response
                data = b''
                while not data.endswith(sentinel):
                    newdata = self._secsocket.recv(BUFSIZE)  # blocking read
                    if not newdata:
                        raise socket.error('cache closed connection')
                    if currenttime() > timeout:
                        # do not just break, we need to reopen the socket
                        raise socket.error('getting response took too long')
                    data += newdata
            except socket.error:
                self.log.warning('error during cache query', exc=1)
                closeSocket(self._secsocket)
                self._secsocket = None
                if retry:
                    for m in self._single_request(tosend, sentinel, retry - 1):
                        yield m
                    return
                raise

        lmatch = line_pattern.match
        mmatch = msg_pattern.match
        i = 0
        # self.log.debug("get_explicit: data =%r", data)
        match = lmatch(data, i)
        while match:
            line = match.group(1)
            i = match.end()
            msgmatch = mmatch(from_utf8(line))
            if not msgmatch:
                # ignore invalid lines
                continue
            # self.log.debug('line processed: %r', line)
            yield msgmatch
            match = lmatch(data, i)

    def waitForStartup(self, timeout):
        self._startup_done.wait(timeout)

    def flush(self):
        """wait for empty output queue"""
        self._synced = False
        self._queue.put('%s%s\n' % (SYNC_MARKER, OP_ASK))
        self._queue.join()
        for _ in range(100):
            # self.log.debug('flush; waiting for sync...')
            if self._synced:
                break
            sleep(CYCLETIME)

    def addPrefixCallback(self, prefix, function):
        """Add a "prefix" callback, which is called for every key and value
        that does not match the prefix parameter of the client, but matches
        the prefix given to this function.
        """
        if prefix not in self._prefixcallbacks:
            self._queue.put('@%s%s\n' % (prefix, OP_SUBSCRIBE))
        self._prefixcallbacks[prefix] = function

    def removePrefixCallback(self, prefix):
        """Remove a "prefix" callback.

        This removes the callback previously installed by addPrefixCallback.
        If prefix is unknown, then do nothing.
        """
        if prefix in self._prefixcallbacks:
            self._queue.put('@%s%s\n' % (prefix, OP_UNSUBSCRIBE))
            del self._prefixcallbacks[prefix]

    # methods to make this client usable as the main device in a simple session

    def start(self, *args):
        self._connect()
        self._worker.start()

    def wait(self):
        while not self._stoprequest:
            sleep(self._long_loop_delay)
        if self._worker and self._worker.is_alive():
            self._worker.join()

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

    def lock(self, key, ttl=None, unlock=False, sessionid=None):
        """Locking/unlocking: opens a separate connection."""
        tosend = '%s%s%s%s%s\n' % (self._prefix, key.lower(), OP_LOCK,
                                   unlock and OP_LOCK_UNLOCK or OP_LOCK_LOCK,
                                   sessionid or session.sessionid)
        if ttl is not None:
            tosend = ('+%s@' % ttl) + tosend
        for msgmatch in self._single_request(tosend, sync=False):
            if msgmatch.group('value'):
                raise CacheLockError(msgmatch.group('value'))
            return
        # no response received; let's assume standalone mode
        self.log.warning('allowing lock/unlock operation without cache '
                         'connection')

    def unlock(self, key, sessionid=None):
        return self.lock(key, ttl=None, unlock=True, sessionid=sessionid)

    def storeSysInfo(self, service):
        """Store info about the service in the cache."""
        if not self._socket:
            return
        try:
            key, res = getSysInfo(service)
            msg = '%s@%s%s%s\n' % (currenttime(), key, OP_TELL,
                                   cache_dump(res))
            self._socket.sendall(to_utf8(msg))
        except Exception:
            self.log.exception('storing sysinfo failed')
Esempio n. 23
0
class CenteredDistanceDevice(HasLimits, CanReference, Moveable):
    """
    Device that controls the distance between attached devices A and B that can be moved
    symmetrically with respect to their center.

    It is assumed that the distance between A and B is 0 when both devices are at 0. When
    the coordinates parameter is 'opposite', when both devices' coordinates increase with distance from
    the center. For 'equal', device A is assumed to move in negative direction from the center,
    device B in the positive direction.

    This borrows heavily from Slit, essentially it is a one-axis slit in centered mode.
    """

    attached_devices = {
        'a': Attach('Device A', HasPrecision),
        'b': Attach('Device B', HasPrecision),
    }

    parameters = {
        'coordinates':
        Param('Coordinate convention for device A and B',
              default='equal',
              type=oneof('equal', 'opposite')),
    }

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

    hardware_access = False

    def doInit(self, mode):
        self._axes = [self._attached_a, self._attached_b]
        self._axnames = ['A', 'B']

    def doReadAbslimits(self):
        limits_a = self._attached_a.abslimits
        limits_b = self._attached_b.abslimits

        if self.coordinates == 'equal':
            return limits_b[0] - limits_a[1], limits_b[1] - limits_a[0]

        return limits_b[0] + limits_a[0], limits_b[1] + limits_a[1]

    def doStart(self, target):
        self._doStartPositions(self._getPositions(target))

    def _doStartPositions(self, positions):
        for ax, pos in zip(self._axes, positions):
            ax.move(pos)

    def _getPositions(self, target):
        half_distance = target / 2.

        if self.coordinates == 'equal':
            return (-half_distance, half_distance)

        return (half_distance, half_distance)

    def doReset(self):
        multiReset(self._axes)
        multiWait(self._axes)

    def doReference(self):
        for ax in self._axes:
            if isinstance(ax, CanReference):
                self.log.info('referencing %s...', ax)
                ax.reference()
            else:
                self.log.warning('%s cannot be referenced', ax)

    def doRead(self, maxage=0):
        pos_a = self._attached_a.read(maxage)
        pos_b = self._attached_b.read(maxage)

        if self.coordinates == 'equal':
            return pos_b - pos_a

        return pos_b + pos_a

    def doStatus(self, maxage=0):
        return multiStatus(list(zip(self._axnames, self._axes)))

    def doReadUnit(self):
        return self._attached_a.unit
Esempio n. 24
0
class PixelmanUDPChannel(ActiveChannel):
    """
    Trigger detector controlled by Pixelman software via UDP service

    One of the detectors at V20 can be triggered through a simple UDP
    service which listens on the computer that controls the detector.
    It expects a keyword to start acquisition and then sends a keyword
    back once it's done. The service expects a new connection for each
    data acquisition, so the connection is established in the doStart
    method and removed in doFinish. In order to recover from
    inconsistent states, the socket is also torn down in doStop,
    although that won't stop the detector, just reset the connection.

    At the moment it's not possible to obtain count information from
    the service, but that may change in the future.
    """

    parameters = {
        'host':
        Param('IP and port for Pixelman Detector UDP interface.', type=host()),
        'acquire':
        Param('Keyword to send for starting the acquisition', type=str),
        'finished':
        Param(
            'Keyword to wait for to determine '
            'whether the acquisition is done',
            type=str),
        'acquiring':
        Param('Internal parameter to synchronise between processes.',
              type=bool,
              internal=True,
              default=False,
              mandatory=False,
              settable=False)
    }

    parameter_overrides = {'ismaster': Override(default=True, settable=True)}

    def valueInfo(self):
        return Value(self.name,
                     unit=self.unit,
                     type='other',
                     fmtstr=self.fmtstr),

    def doInit(self, mode):
        self._socket = None

    def doStart(self):
        if self._socket is None:
            self.log.debug('Socket is None, creating socket.')
            pm_host, pm_port = self.host.split(':')
            self.log.debug('Connection: ' + self.host)
            self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self._socket.connect((pm_host, int(pm_port)))
            self.log.debug('Sending Keyword: ' + self.acquire)
            self._socket.sendall(self.acquire)
            self._socket.setblocking(0)

            self._setROParam('acquiring', True)

            self.log.debug('Acquisition started')
        else:
            self.log.info(
                'Socket already exists, starting again has no effect.')

    def doFinish(self):
        self.log.debug('Finishing...')
        if self._socket is not None:
            self.log.debug('Actually shutting down...')
            self._socket.close()
            self._socket = None

        self._setROParam('acquiring', False)

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

    def doStop(self):
        self.doFinish()

    def doStatus(self, maxage=0):
        if not self._check_complete():
            return status.BUSY, 'Acquiring...'

        return status.OK, 'Idle'

    def duringMeasureHook(self, elapsed):
        return None

    def _check_complete(self):
        self.log.debug('Checking completion...')

        if session.sessiontype != POLLER:
            if self._socket is not None:
                self.log.debug('Actually performing check...')
                try:
                    data = self._socket.recv(1024)

                    self.log.debug('Got data: ' + data)

                    return data == self.finished
                except socket.error:
                    return False

        self.log.debug('Falling back to Cache...')

        return not self.acquiring
Esempio n. 25
0
class SingleSlit(PseudoNOK, HasOffset, Moveable):
    """Slit using one axis."""

    hardware_access = False

    attached_devices = {
        'motor': Attach('moving motor', Moveable),
    }

    parameters = {
        'mode':
        Param('Beam mode',
              type=oneof(*MODES),
              settable=True,
              userparam=True,
              default='slit',
              category='general'),
        '_offsets':
        Param('List of offsets per mode position',
              settable=False,
              internal=True,
              type=dictof(str, float),
              default={}),
        'opmode':
        Param('Mode of operation for the slit',
              type=oneof(CENTERED),
              userparam=True,
              settable=True,
              default=CENTERED,
              category='experiment'),
    }

    parameter_overrides = {
        'masks':
        Override(type=dictwith(**{name: float
                                  for name in MODES}),
                 unit='',
                 mandatory=True),
    }

    valuetype = float

    def doWriteOffset(self, value):
        HasOffset.doWriteOffset(self, value)
        # deep copy is need to be able to change the values
        d = self._offsets.copy()
        d[self.mode] = value
        self._setROParam('_offsets', d)

    def doRead(self, maxage=0):
        return self._attached_motor.read(maxage) - self.masks[self.mode] - \
            self.offset

    def doIsAllowed(self, target):
        return self._attached_motor.isAllowed(target + self.masks[self.mode])

    def doStop(self):
        self._attached_motor.stop()

    def doStart(self, target):
        self._attached_motor.start(target + self.masks[self.mode] +
                                   self.offset)

    def doWriteMode(self, mode):
        self._attached_motor.start(
            self._attached_motor.read(0) + self.masks[mode] -
            self.masks[self.mode])
        # update the offset parameter from offset mapping
        self._setROParam('offset', self._offsets.get(mode, 0.))
        self.log.debug('New offset is now: %f', self.offset)
Esempio n. 26
0
class DoubleSlit(PseudoNOK, Moveable):
    """Double slit using two SingleSlits."""

    hardware_access = False

    attached_devices = {
        'slit_r': Attach('Reactor side single slit', SingleSlit),
        'slit_s': Attach('Sample side single slit', SingleSlit),
    }

    parameters = {
        'mode':
        Param('Modus of Beam',
              type=oneof(*MODES),
              settable=True,
              userparam=True,
              default='slit',
              category='experiment'),
        'maxheight':
        Param('Max opening of the slit',
              type=floatrange(0),
              settable=False,
              default=12.),
        'opmode':
        Param(
            'Mode of operation for the slit',
            type=oneof(CENTERED),  # '2blades' is possible
            userparam=True,
            settable=True,
            default=CENTERED,
            category='experiment'),
    }

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

    def doInit(self, mode):
        # Even if the slit could not be become closer then 0 and not more
        # opened the maxheight the instrument scientist want to scan over
        # the limits to find out the 'open' and 'closed' point for the neutrons
        self.valuetype = tupleof(floatrange(-1, self.maxheight + 1), float)
        # generate auto devices
        for name, idx, opmode in [('height', 0, CENTERED),
                                  ('center', 1, CENTERED)]:
            self.__dict__[name] = SingleSlitAxis('%s.%s' % (self.name, name),
                                                 slit=self,
                                                 unit=self.unit,
                                                 lowlevel=True,
                                                 index=idx,
                                                 opmode=opmode)
        self._motors = [self._attached_slit_r, self._attached_slit_s]

    def doStatus(self, maxage=0):
        st = Moveable.doStatus(self, maxage)
        if st[0] == status.OK:
            return st[0], self.name  # display device name
        return st

    def doWriteMode(self, mode):
        for d in self._adevs.values():
            d.mode = mode

    def _calculate_slits(self, arg, direction):
        self.log.debug('calculate slits: dir:%s mode:%s arg %s', direction,
                       self.mode, str(arg))
        if direction:
            reactor, sample = arg
            opening = self.maxheight - (sample - reactor)
            height = (sample + reactor) / 2.0
            res = [opening, height]
        else:
            opening, height = arg
            reactor = height - (self.maxheight - opening) / 2.0
            sample = height + (self.maxheight - opening) / 2.0
            res = [reactor, sample]
        self.log.debug('res %s', res)
        return res

    def doRead(self, maxage=0):
        return self._calculate_slits([
            self._attached_slit_r.read(maxage),
            self._attached_slit_s.read(maxage)
        ], True)

    def doIsAllowed(self, targets):
        self.log.debug('DoubleSlit doIsAllowed %s', targets)
        why = []
        try:
            self.valuetype((targets[0], 0))
        except ValueError as e:
            why.append('%s' % e)
        for dev, pos in zip([self._attached_slit_r, self._attached_slit_s],
                            self._calculate_slits(targets, False)):
            ok, _why = dev.isAllowed(pos)
            if not ok:
                why.append('%s: requested position %.3f %s out of limits; %s' %
                           (dev, pos, dev.unit, _why))
            else:
                self.log.debug('%s: requested position %.3f %s allowed', dev,
                               pos, dev.unit)
        if why:
            return False, '; '.join(why)
        return True, ''

    # def doIsAtTarget(self, targets):
    #     # check precision, only move if needed!
    #     self.log.debug('DoubleSlit doIsAtTarget %s', targets)
    #     targets = self.rechnen_motor(targets, False, 'doIsAtTarget')
    #     self.log.debug('%s', targets)
    #     traveldists = [target - dev.doRead(0)
    #                    for target, dev in zip(targets, self._devices)]
    #     return max(abs(v) for v in traveldists) <= self.precision

    def doStop(self):
        for dev in self._adevs.values():
            dev.stop()

    def doStart(self, targets):
        """Generate and start a sequence if none is running."""
        for dev, target in zip([self._attached_slit_r, self._attached_slit_s],
                               self._calculate_slits(targets, False)):
            dev.start(target)

    def doReadNok_Start(self):
        return self._attached_slit_r.nok_start

    def doReadNok_End(self):
        return self._attached_slit_s.nok_end

    def doPoll(self, n, maxage):
        # also poll sub-AutoDevices we created
        for dev in devIter(self.__dict__, baseclass=AutoDevice):
            dev.poll(n, maxage)

    def valueInfo(self):
        return Value('%s.height' % self, unit=self.unit, fmtstr='%.2f'), \
               Value('%s.center' % self, unit=self.unit, fmtstr='%.2f')
Esempio n. 27
0
class HasSwitchPv(DeviceMixinBase):
    """
    A mixin that can be used with EPICS based devices.

    Devices that inherit this mixin get a new property that indicates
    whether the device is switched on (that may mean different things
    in different devices):

        dev.isSwitchedOn

    To switch the device on or off, use the provided methods:

        dev.switchOn()
        dev.switchOff()

    The link to EPICS is configured via the switchpvs and switchstates
    parameters. The former defines which PV to read for the status
    information as well as which one to write to when using the methods.
    The latter defines what values the PV accepts for on and off
    respectively.
    """

    parameters = {
        'switchstates':
        Param('Map of boolean switch states to underlying type',
              type=dictwith(on=anytype, off=anytype),
              userparam=False),
        'switchpvs':
        Param('Read and write pv for switching device on and off.',
              type=dictwith(read=pvname, write=pvname),
              userparam=False)
    }

    def _get_pv_parameters(self):
        # Use colon prefix to prevent name clashes with
        # PVs specified in EpicsDevice.param
        switch_pvs = {'switchpv:' + pv for pv in self.switchpvs}

        return super(HasSwitchPv, self)._get_pv_parameters() | switch_pvs

    def _get_pv_name(self, pvparam):
        components = pvparam.split(':', 1)

        if len(components) == 2 and components[0] == 'switchpv':
            return self.switchpvs[components[1]]

        return super(HasSwitchPv, self)._get_pv_name(pvparam)

    @property
    def isSwitchedOn(self):
        """
        True if the device is switched on.
        """
        raw_value = self._get_pv('switchpv:read')

        if raw_value not in self.switchstates.values():
            self.log.warning('State by attached switch device not recognized. '
                             'Returning raw value.')

            return raw_value

        return raw_value == self.switchstates['on']

    @usermethod
    def switchOn(self):
        """
        Switch the device on (writes the 'on' of switchstates map to the
        write-pv specified in switchpvs).
        """
        if not self.isSwitchedOn:
            self._put_pv('switchpv:write', self.switchstates['on'])
        else:
            self.log.info('Device is already switched on')

    @usermethod
    def switchOff(self):
        """
        Switch the device off (writes the 'off' of switchstates map to the
        write-pv specified in switchpvs).
        """
        if self.isSwitchedOn:
            self._put_pv('switchpv:write', self.switchstates['off'])
        else:
            self.log.info('Device is already switched off')
Esempio n. 28
0
class SamplePos(Moveable):
    """Control selector speed via SPS I/O devices."""

    attached_devices = {
        'active_ap': Attach('Alias for active aperture', DeviceAlias),
        'active_x': Attach('Alias for active x translation', DeviceAlias),
        'active_y': Attach('Alias for active y translation', DeviceAlias),
    }

    parameters = {
        'alloweddevs':
        Param('List of allowed devices for presets', type=listof(str)),
        'presets':
        Param('Presets for sample position switching',
              type=dictof(str, dictof(str, anytype))),
    }

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

    def doInit(self, mode):
        self.valuetype = oneof(*sorted(self.presets, key=num_sort))
        self._waitdevs = []
        self._aliases = {}
        self._devpos = {}
        for setting, values in self.presets.items():
            values = dict(values)
            try:
                self._aliases[setting] = (values.pop('active_ap'),
                                          values.pop('active_x'),
                                          values.pop('active_y'))
            except KeyError:
                raise ConfigurationError(
                    self, 'setting %r needs active_ap, active_x and active_y '
                    'settings' % setting) from None
            try:
                for name in self._aliases[setting]:
                    session.getDevice(name)
            except NicosError as exc:
                raise ConfigurationError(
                    self,
                    'could not create/find alias targets for setting %r' %
                    setting) from exc
            for key in values:
                if key not in self.alloweddevs:
                    raise ConfigurationError(
                        self, 'device %s is not allowed '
                        'to be moved by sample_pos' % key)
            self._devpos[setting] = values

    def doRead(self, maxage=0):
        current_targets = (
            self._attached_active_ap.alias,
            self._attached_active_x.alias,
            self._attached_active_y.alias,
        )
        setting = None
        for setting, targets in self._aliases.items():
            if targets == current_targets:
                break
        else:
            return 'unknown'
        ok = True
        for devname, devpos in self._devpos[setting].items():
            dev = session.getDevice(devname)
            devval = dev.read(maxage)
            if isinstance(dev, HasPrecision):
                ok &= abs(devval - devpos) <= dev.precision
            elif isinstance(dev, Slit):
                ok &= all(abs(v - w) <= 0.1 for (v, w) in zip(devval, devpos))
            else:
                ok &= devval == devpos
        if ok:
            return setting
        return 'unknown'

    def doStart(self, target):
        aliases = self._aliases[target]
        self._attached_active_ap.alias = aliases[0]
        self._attached_active_x.alias = aliases[1]
        self._attached_active_y.alias = aliases[2]
        self._waitdevs = []
        for dev, devpos in self._devpos[target].items():
            dev = session.getDevice(dev)
            dev.move(devpos)
            self._waitdevs.append(dev)

    def _getWaiters(self):
        if self._waitdevs:
            return self._waitdevs
        return self._adevs
Esempio n. 29
0
class FPGAChannelBase(PyTangoDevice, ActiveChannel):
    """Basic Tango Device for ZEA-2 Counter Card."""

    parameters = {
        'extmode':
        Param('Arm for external start instead of starting',
              type=bool,
              default=False,
              settable=True),
        'extmask':
        Param('Bitmask of the inputs to use for external start',
              type=int,
              default=0),
        'extwait':
        Param(
            'If nonzero, we are waiting for external start '
            'since that timestamp',
            type=float,
            default=0,
            settable=True,
            internal=True),
    }

    def _setPreselection(self):
        """This method must be present and should set the the preselection
        value for the card before start.
        """
        raise NotImplementedError

    def doStart(self):
        if self.ismaster:
            self._dev.DevFPGACountReset()
            # preselection has to be set here and not in doWritePreset
            # because `DevFPGACountReset()` resets all values.
            self._setPreselection()
            if self.extmode:
                self.extwait = currenttime()
                self._dev.DevFPGACountArmForExternalStart(self.extmask)
            else:
                self._dev.DevFPGACountStart()

    def doFinish(self):
        self.extwait = 0
        self._dev.DevFPGACountStop()

    def doStop(self):
        self.doFinish()

    def doPause(self):
        if self.extmode:
            return False
        self.finish()
        return True

    def doResume(self):
        self._dev.DevFPGACountStart()

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

    def doStatus(self, maxage=0):
        # Workaround self._dev.State() does not return DevState.MOVING
        if self._dev.DevFPGACountGateStatus():
            return (status.BUSY, 'counting')
        else:
            return (status.OK, '')

    def doReset(self):
        if self.status(0)[0] == status.BUSY:
            self.finish()
        self._dev.DevFPGACountReset()
Esempio n. 30
0
class HttpConnector(HasCommunication, Readable):
    """ Device to connect to the HTTP Server using HTTP Basic Authentication.
    *baseurl* provided in the parameters is prepended while connecting to the
    server using GET or POST. Parameter *base64auth* provides a way to
    authenticate the connection.
    """
    parameters = {
        'baseurl':
        Param('Base request URL to be used', type=str, mandatory=True),
        'base64auth':
        Param('HTTP authentication encoded in base64',
              type=str,
              mandatory=True,
              userparam=False),
        'byteorder':
        Param('Endianness of the raw data on server(big/little)',
              type=oneof('big', 'little'),
              default='little'),
        'curstatus':
        Param('Current status of the connection (readonly)',
              type=tupleof(int, str),
              settable=True,
              internal=True)
    }

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

    status_code_msg = {
        400: 'Bad request',
        403: 'Authentication did not work..',
        404: 'Somehow, address was not found!',
        500: 'Internal server error',
    }

    def doInit(self, mode):
        # Check if the base url is available
        self._com_retry(None,
                        requests.get,
                        self.baseurl,
                        headers=self._get_auth())

    def _get_auth(self):
        return {"Authorization": "Basic %s" % self.base64auth}

    def _com_return(self, result, info):
        # Check if the communication was successful
        response = result.status_code
        if response in self.status_code_msg:
            raise CommunicationError(self.status_code_msg.get(response))
        elif response != 200:
            raise CommunicationError('Error while connecting to server!')
        self._setROParam('curstatus', (status.OK, ''))
        return result

    def _com_raise(self, err, info):
        self._setROParam('curstatus', (status.ERROR, 'Communication Error!'))
        HasCommunication._com_raise(self, err, info)

    def _com_warn(self, retries, name, err, info):
        self._com_raise(err, info)

    def doRead(self, maxage=0):
        return ''

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

    def get(self, name='', params=()):
        """Connect to *baseurl/name* using the GET protocol
        :param name: String to be appended to the *baseurl*
        :param params: GET parameters to be passed
        :return: (requests.Response) response
        """
        return self._com_retry(None,
                               requests.get,
                               self.baseurl + '/' + name,
                               headers=self._get_auth(),
                               params=params)

    def post(self, name='', data=()):
        """Connect to *baseurl/name* using the POST protocol
        :param name: String to be appended to the *baseurl*
        :param data: POST parameters to be passed
        :return: (requests.Response) response
        """
        return self._com_retry(None,
                               requests.post,
                               self.baseurl + '/' + name,
                               headers=self._get_auth(),
                               data=data)