コード例 #1
0
class ReadonlySwitcher(MappedReadable):
    """Same as the `Switcher`, but for read-only underlying devices."""

    attached_devices = {
        'readable': Attach('The continuous device which is read', Readable),
    }

    parameters = {
        'precision':
        Param('Precision for comparison', type=floatrange(0), default=0),
    }

    parameter_overrides = {
        'fallback': Override(userparam=False,
                             type=none_or(str),
                             mandatory=False),
    }

    hardware_access = False

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

    def _mapReadValue(self, pos):
        prec = self.precision
        for name, value in iteritems(self.mapping):
            if prec:
                if abs(pos - value) <= prec:
                    return name
            elif pos == value:
                return name
        if self.fallback is not None:
            return self.fallback
        raise PositionError(self,
                            'unknown position of %s' % self._attached_readable)

    def doStatus(self, maxage=0):
        # if the underlying device is moving or in error state,
        # reflect its status
        move_status = self._attached_readable.status(maxage)
        if move_status[0] not in (status.OK, status.WARN):
            return move_status
        # otherwise, we have to check if we are at a known position,
        # and otherwise return an error status
        try:
            if self.read(maxage) == self.fallback:
                return status.NOTREACHED, 'unconfigured position of %s or '\
                    'still moving' % self._attached_readable
        except PositionError as e:
            return status.NOTREACHED, str(e)
        return status.OK, ''

    def doReset(self):
        self._attached_readable.reset()
コード例 #2
0
ファイル: virtual.py プロジェクト: umithardal/nicos
class VirtualReferenceMotor(CanReference, VirtualMotor):
    """Virtual motor device with reference capability."""

    parameters = {
        'refpos':
        Param('Reference position if given',
              type=none_or(float),
              settable=False,
              default=None,
              unit='main'),
        'addr':
        Param('Bus address of the motor', type=intrange(32, 255), default=71),
        'refswitch':
        Param('Type of the reference switch',
              type=oneof('high', 'low', 'ref'),
              default='high',
              settable=False),
    }

    def doReference(self, *args):
        refswitch = args[0] if args and isinstance(args[0], string_types) \
            else None
        self.log.debug('reference: %s', refswitch)
        self._setrefcounter()
        if self.refpos is not None:
            ret = self.read(0)
            self.log.debug('%s %r', self.name, self.isAtReference())
            return ret
        return self.refpos

    def _setrefcounter(self):
        self.log.debug('in setrefcounter')
        if self.refpos is not None:
            self.setPosition(self.refpos)
            self._setROParam('target', self.refpos)
            self.log.debug('%r %r', self.refpos, self.target)
            session.delay(0.1)
        if not self.isAtReference():
            raise UsageError('cannot set reference counter, not at reference '
                             'point')

    def isAtReference(self, refswitch=None):
        if self.refpos is None:
            return False
        pos = self.read(0)
        is_at_refpos = abs(self.refpos - self.read(0)) <= self.precision
        if refswitch == 'low':
            return is_at_refpos and (abs(self.abslimits[0] - pos) <
                                     abs(self.abslimits[1] - pos))
        elif refswitch == 'high':
            return is_at_refpos and (abs(self.abslimits[0] - pos) >
                                     abs(self.abslimits[1] - pos))
        return is_at_refpos
コード例 #3
0
class Sample(NicosSample):
    """A special device to represent a sample.

    This has the MLZ-specific sample ID from the sample tracker.
    """

    parameters = {
        'sampleid':     Param('Sample ID from the sample tracker',
                              type=none_or(int), settable=True,
                              category='sample'),
    }

    def clear(self):
        """Clear experiment-specific information."""
        NicosSample.clear(self)
        self.sampleid = None

    def _applyParams(self, number, parameters):
        """Apply sample parameters."""
        NicosSample._applyParams(self, number, parameters)
        self.sampleid = parameters.get('id')
コード例 #4
0
ファイル: pvaccess.py プロジェクト: umithardal/nicos
class EpicsDevice(DeviceMixinBase):
    """
    Basic EPICS device.
    """

    hardware_access = True
    valuetype = anytype

    parameters = {
        'epicstimeout': Param('Timeout for getting EPICS PVs',
                              type=none_or(floatrange(0.1, 60)),
                              default=1.0),
        'usepva': Param('If true, PVAcess is used instead of ChannelAccess',
                        type=bool, default=False, preinit=True)
    }

    # A set of all parameters that indicate PV names.  Since PVs are very
    # limited, an EpicsDevice is expected to use many different PVs a lot
    # of times.
    pv_parameters = set()

    pv_cache_relations = {}

    # This will store PV objects for each PV param.
    _pvs = {}
    _pvctrls = {}

    def doPreinit(self, mode):
        # Don't create PVs in simulation mode
        self._pvs = {}
        self._pvctrls = {}

        if mode != SIMULATION:
            # When there are standard names for PVs (see motor record), the PV names
            # may be derived from some prefix. To make this more flexible, the pv_parameters
            # are obtained via a method that can be overridden in subclasses.
            pv_parameters = self._get_pv_parameters()

            # For cases where for example readpv and writepv are the same, this dict makes
            # sure that only one Channel object is created per PV.
            pv_names = {}

            for pvparam in pv_parameters:
                # Retrieve the actual PV-name from (potentially overridden) method
                pv_name = self._get_pv_name(pvparam)

                try:
                    pv = pv_names.setdefault(pv_name, self._create_pv(pv_name))
                    self._pvs[pvparam] = pv

                    pv.setTimeout(self.epicstimeout)

                    self._pvctrls[pvparam] = pv.get('display').toDict().get('display')
                    if self._pvctrls[pvparam] is None:
                        self._pvctrls[pvparam] = pv.get('control').toDict().get('control', {})

                except pvaccess.PvaException:
                    raise CommunicationError(self, 'could not connect to PV %r'
                                             % pv_name)
        else:
            for pvparam in self._get_pv_parameters():
                self._pvs[pvparam] = HardwareStub(self)
                self._pvctrls[pvparam] = {}

    def doInit(self, mode):
        if mode != SIMULATION:
            self._register_pv_callbacks()

    def _create_pv(self, pv_name):
        return pvaccess.Channel(pv_name, pvaccess.PVA if self.usepva else pvaccess.CA)

    def _get_pv_parameters(self):
        # The default implementation of this method simply returns the pv_parameters set
        return self.pv_parameters

    def _get_pv_name(self, pvparam):
        # In the default case, the name of a PV-parameter is stored in a parameter.
        # This method can be overridden in subclasses in case the name can be derived
        # using some other information.
        return getattr(self, pvparam)

    def doStatus(self, maxage=0):
        return status.OK, ''

    def _setMode(self, mode):
        super(EpicsDevice, self)._setMode(mode)
        # remove the PVs on entering simulation mode, to prevent
        # accidental access to the hardware
        if mode == SIMULATION:
            for key in self._pvs:
                self._pvs[key] = HardwareStub(self)

    def _get_pv(self, pvparam, field='value', as_string=False):
        """
        Uses pvaccess to obtain a field from a PV. The default field is value,
        so that

            val = self._get_pv('readpv')

        retrieves the value of the PV. To obtain alarm or other status information,
        the field parameter can be specified:

            alarm = self._get_pv('readpv', field='alarm')

        Args:
            pvparam: The PV parameter to be queried. Is translated to a PV name internally.
            field: Field of the PV to obtain, default is value.

        Returns: Value of the queried PV field.
        """
        # result = self._pvs[pvparam].get(field).toDict().get(field)
        result = _pvget(self._pvs[pvparam], field, as_string)

        if result is None:  # timeout or channel not connected
            raise CommunicationError(self, 'timed out getting PV %r from EPICS'
                                     % self._get_pv_name(pvparam))

        return result

    def _get_pvctrl(self, pvparam, ctrl, default=None, update=False):
        if update:
            self._pvctrls[pvparam] = self._pvs[pvparam].get('display').toDict().get('display')
            if self._pvctrls[pvparam] is None:
                self._pvctrls[pvparam] = self._pvs[pvparam].get('control').toDict().get(
                    'control', {})
        return self._pvctrls[pvparam].get(ctrl, default)

    def _get_pv_datatype(self, pvparam):
        pv_data = self._pvs[pvparam].get().getStructureDict()['value']
        if not isinstance(pv_data, list):
            return FTYPE_TO_VALUETYPE.get(pv_data, anytype)
        else:
            return [FTYPE_TO_VALUETYPE.get(dt, anytype) for dt in pv_data]

    def _put_pv(self, pvparam, value, wait=True):
        self._pvs[pvparam].put(value)

    def _put_pv_blocking(self, pvparam, value, update_rate=0.1):
        # TODO: figure out why putGet segfaults
        self._put_pv(pvparam, value)

    def _register_pv_callbacks(self):
        """
        If this is a poller session, monitor the PVs specified in the
        ``pv_cache_relations`` member for updates and put the values
        into the cache. This happens in addition to polling, but makes sure that
        values get inserted into the cache immediately when they are available.

        This example would map the value of readpv to the ``value`` of the device:

            pv_cache_relations = {
                'readpv': 'value',
            }

        This method has to be called explicitly in ``doInit``, should it be re-implemented.
        """
        if session.sessiontype == POLLER:
            for pvparam in self._get_pv_parameters():
                corresponding_cache_key = self.pv_cache_relations.get(pvparam)
                if corresponding_cache_key is not None:
                    self._register_pv_update_callback(pvparam, corresponding_cache_key)

    def _register_pv_update_callback(self, pvparam, cache_key, pv_field='value'):
        """
        Subscribes to a PV monitor that updates the cache whenever the PV is updated
        via ChannelAccess.

        Args:
            pvparam: The pvparam to subscribe to, for example readpv or writepv
            cache_key: The cache key that corresponds to the PV's value
            pv_field: Field of the PV to obtain, default is value.

        """
        self.log.info('Registering callback for %s (PV: %s)', pvparam,
                      self._get_pv_name(pvparam))

        def update_callback(pv_object, obj=self, key=cache_key):
            if isinstance(obj, Readable):
                if key == 'value' or key == 'status':
                    ret = obj.poll()
                    ct = currenttime()
                    obj._cache.put(self, 'status', ret[0], ct, self.maxage)
                    obj._cache.put(self, 'value', ret[1], ct, self.maxage)
                else:
                    obj._pollParam(key)

        pv = self._pvs[pvparam]
        pv.setMonitorMaxQueueLength(10)
        pv.subscribe('_'.join((self.name, pvparam, cache_key, 'poller')), update_callback)

        #if not pv.isMonitorActive():
        pv.startMonitor('')

    def _get_mapped_epics_status(self):
        # Checks the status and severity of all the associated PVs.
        # Returns the worst status (error prone first) and
        # a list of all associated pvs having that error
        status_map = {}
        for name in self._pvs:
            epics_status = self._get_pvctrl(name, 'status', update=True)
            epics_severity = self._get_pvctrl(name, 'severity')

            mapped_status = STAT_TO_STATUS.get(epics_status, None)

            if mapped_status is None:
                mapped_status = SEVERITY_TO_STATUS.get(
                    epics_severity, status.UNKNOWN)

            status_map.setdefault(mapped_status, []).append(
                self._get_pv_name(name))

        return max(status_map.items())
コード例 #5
0
ファイル: pyepics.py プロジェクト: umithardal/nicos
class EpicsDevice(DeviceMixinBase):
    """
    Basic EPICS device.
    """

    hardware_access = True
    valuetype = anytype

    parameters = {
        'epicstimeout': Param('Timeout for getting EPICS PVs',
                              type=none_or(floatrange(0.1, 60)),
                              default=1.0),
    }

    # A set of all parameters that indicate PV names.  Since PVs are very
    # limited, an EpicsDevice is expected to use many different PVs a lot
    # of times.
    pv_parameters = set()

    # This will store PV objects for each PV param.
    _pvs = {}
    _pvctrls = {}

    def doPreinit(self, mode):
        # Don't create PVs in simulation mode
        self._pvs = {}
        self._pvctrls = {}
        if mode != SIMULATION:
            # in case we get started in a thread, make sure to use the global
            # CA context in that thread
            if epics.ca.current_context() is None:
                epics.ca.use_initial_context()

            # When there are standard names for PVs (see motor record), the PV
            # names may be derived from some prefix. To make this more flexible,
            # the pv_parameters are obtained via a method that can be overridden
            # in subclasses.
            pv_parameters = self._get_pv_parameters()
            for pvparam in pv_parameters:

                # Retrieve the actual PV-name from (potentially overridden) method
                pvname = self._get_pv_name(pvparam)
                if not pvname:
                    raise ConfigurationError(self, 'PV for parameter %s was '
                                                   'not found!' % pvparam)
                pv = self._pvs[pvparam] = epics.pv.PV(
                    pvname, connection_timeout=self.epicstimeout)
                pv.connect()
                if not pv.wait_for_connection(timeout=self.epicstimeout):
                    raise CommunicationError(self, 'could not connect to PV %r'
                                             % pvname)

                self._pvctrls[pvparam] = pv.get_ctrlvars() or {}
        else:
            for pvparam in self._get_pv_parameters():
                self._pvs[pvparam] = HardwareStub(self)
                self._pvctrls[pvparam] = {}

    def _get_pv_parameters(self):
        # The default implementation of this method simply returns the
        # pv_parameters set
        return self.pv_parameters

    def _get_pv_name(self, pvparam):
        # In the default case, the name of a PV-parameter is stored in ai
        # parameter. This method can be overridden in subclasses in case the
        # name can be derived using some other information.
        return getattr(self, pvparam)

    def doStatus(self, maxage=0):
        # Return the status and the affected pvs in case the status is not OK
        mapped_status, affected_pvs = self._get_mapped_epics_status()

        status_message = 'Affected PVs: ' + ', '.join(
            affected_pvs) if mapped_status != status.OK else ''
        return mapped_status, status_message

    def _get_mapped_epics_status(self):
        # Checks the status and severity of all the associated PVs.
        # Returns the worst status (error prone first) and
        # a list of all associated pvs having that error
        if epics.ca.current_context() is None:
            epics.ca.use_initial_context()

        status_map = {}
        for name in self._pvs:
            epics_status = self._get_pvctrl(name, 'status', update=True)
            epics_severity = self._get_pvctrl(name, 'severity')

            mapped_status = STAT_TO_STATUS.get(epics_status, None)

            if mapped_status is None:
                mapped_status = SEVERITY_TO_STATUS.get(
                    epics_severity, status.UNKNOWN)

            status_map.setdefault(mapped_status, []).append(
                self._get_pv_name(name))

        return max(status_map.items())

    def _setMode(self, mode):
        super(EpicsDevice, self)._setMode(mode)
        # remove the PVs on entering simulation mode, to prevent
        # accidental access to the hardware
        if mode == SIMULATION:
            for key in self._pvs:
                self._pvs[key] = HardwareStub(self)

    def _get_pv(self, pvparam, as_string=False):
        # since NICOS devices can be accessed from any thread, we have to
        # ensure that the same context is set on every thread
        if epics.ca.current_context() is None:
            epics.ca.use_initial_context()
        result = self._pvs[pvparam].get(timeout=self.epicstimeout,
                                        as_string=as_string)
        if result is None:  # timeout
            raise CommunicationError(self, 'timed out getting PV %r from EPICS'
                                     % self._get_pv_name(pvparam))
        return result

    def _get_pvctrl(self, pvparam, ctrl, default=None, update=False):
        if update:
            if epics.ca.current_context() is None:
                epics.ca.use_initial_context()

            self._pvctrls[pvparam] = self._pvs[pvparam].get_ctrlvars()

        result = self._pvctrls[pvparam]
        if result is None:
            return default
        return result.get(ctrl, default)

    def _put_pv(self, pvparam, value, wait=False):
        if epics.ca.current_context() is None:
            epics.ca.use_initial_context()

        self._pvs[pvparam].put(value, wait=wait, timeout=self.epicstimeout)

    def _put_pv_blocking(self, pvparam, value, update_rate=0.1, timeout=60):
        if epics.ca.current_context() is None:
            epics.ca.use_initial_context()

        pv = self._pvs[pvparam]

        pv.put(value, use_complete=True)

        start = currenttime()
        while not pv.put_complete:
            if currenttime() - start > timeout:
                raise CommunicationError('Timeout in setting %s' % pv.pvname)
            session.delay(update_rate)
コード例 #6
0
class Coder(NicosCoder):
    """This class supports both IPC absolute and incremental coder cards.

    It can be used with the `nicos.devices.generic.Axis` class.
    """

    parameters = {
        'addr':
        Param('Bus address of the coder',
              type=intrange(32, 255),
              mandatory=True),
        'confbyte':
        Param('Configuration byte of the coder',
              settable=True,
              type=intrange(0, 255),
              prefercache=False),
        'zerosteps':
        Param('Coder steps for physical zero',
              type=float,
              unit='steps',
              settable=True),
        'slope':
        Param('Coder slope',
              type=float,
              default=1.0,
              unit='steps/main',
              settable=True),
        'firmware':
        Param('Firmware version', type=int),
        'steps':
        Param('Current coder position in steps', type=int, settable=False),
        'circular':
        Param(
            'Wrap-around value for circular coders, if negative'
            ' use it as +/-, else as 0..value, None disables '
            'this',
            type=none_or(float),
            settable=True,
            default=None),
        'readings':
        Param(
            'Number of readings to average over '
            'when determining current position',
            type=int,
            default=1,
            settable=True),
    }

    attached_devices = {
        'bus': Attach('The communication bus', IPCModBus),
    }

    def doInit(self, mode):
        if mode != SIMULATION:
            self._attached_bus.ping(self.addr)
            try:
                actual_confbyte = self.doReadConfbyte()
            except NicosError:
                actual_confbyte = -1
            if self.confbyte != actual_confbyte:
                self.doWriteConfbyte(self.confbyte)
                self.log.warning(
                    'Confbyte mismatch between setup and card, '
                    'overriding card value to 0x%02x', self.confbyte)
        self._lasterror = None

    def doVersion(self):
        return [('IPC encoder card, %s' % self._hwtype, str(self.firmware))]

    def doReadFirmware(self):
        return self._attached_bus.get(self.addr, 151)

    def doReadConfbyte(self):
        return self._attached_bus.get(self.addr, 152)

    def doWriteConfbyte(self, byte):
        self._attached_bus.send(self.addr, 154, byte, 3)

    def doUpdateConfbyte(self, byte):
        try:
            self._type = self._getcodertype(byte)
            self._resolution = byte & 31
        except Exception:
            self._type = None
            self._resolution = None

    @lazy_property
    def _hwtype(self):
        """Returns 'analog' or 'digital', used for features that only one of the
        card types supports. 'analog' type is for potis and 'digital' is for
        rotary encoders.
        """
        firmware = self._attached_bus.get(self.addr, 151)
        confbyte = self._attached_bus.get(self.addr, 152)
        if confbyte < 4:
            return 'digital'
        if confbyte == 16:
            if firmware <= 6:
                return 'analog'  # wild guess for resolvers
        if confbyte & 0xe0 == 0x20:
            if firmware < 20:  # wild guess, but seems to work...
                return 'analog'
        return 'digital'

    def _getcodertype(self, byte):
        """Extract coder type from configuration byte."""
        if byte < 4:
            return 'incremental encoder, 24bit, ' + \
                ['no reset', 'reset once', 'reset always',
                 'reset once to halfrange'][byte]
        if self._hwtype == 'analog':
            if byte == 16:
                return 'resolver, 16bit'
            return 'potentiometer, %dbit' % (byte & 0x1F)
        proto = byte & 128 and 'endat' or 'ssi'
        coding = byte & 64 and 'gray' or 'binary'
        parity = byte & 32 and 'no parity' or 'even parity'
        return 'absolute encoder, %s-protocol, %s-coded, %s, %dbit' % \
            (proto, coding, parity, byte & 31)

    def doReset(self):
        self._lasterror = None
        try:
            self._attached_bus.send(self.addr, 153)
        except NicosError:
            pass
        else:
            session.delay(0.5)

    def _fromsteps(self, value):
        return float((value - self.zerosteps) / self.slope)

    def doReadSteps(self):
        try:
            try:
                value = self._attached_bus.get(self.addr, 150)
            except NicosError:
                self._endatclearalarm()
                session.delay(1)
                # try again
                value = self._attached_bus.get(self.addr, 150)
        except NicosError as e:
            # record last error to return it from doStatus()
            self._lasterror = str(e)
            raise
        self._lasterror = None
        self.log.debug('value is %d steps', value)
        return value

    def doRead(self, maxage=0):
        # make sure to ask hardware, don't use cached value of steps
        steps = sum(self.doReadSteps() for _ in range(self.readings))
        steps = int(steps / float(self.readings))
        self._params['steps'] = steps
        if self._cache:  # save last valid position in cache
            self._cache.put(self, 'steps', steps)
        pos = self._fromsteps(steps)
        if self.circular is not None:
            # make it wrap around
            pos = pos % abs(self.circular)
            # if we want +/- instead of 0 to x and value is >x/2
            if self.circular < 0 and pos > -0.5 * self.circular:
                # subtract x to make it -x/2..0..x/2 (circular is negative
                # here)
                pos += self.circular
        self.log.debug('position is %s', self.format(pos))
        return pos

    def doStatus(self, maxage=0):
        if self._lasterror:
            return status.ERROR, self._lasterror
        return status.OK, ''

    def doSetPosition(self, target):
        raise NicosError('setPosition not implemented for IPC coders')

    def _endatclearalarm(self):
        """Clear alarm for a binary-endat encoder."""
        if self._type is not None and 'endat-protocol' not in self._type:
            return
        try:
            self._attached_bus.send(self.addr, 155, 185, 3)
            session.delay(0.5)
            self._attached_bus.send(self.addr, 157, 0, 3)
            session.delay(0.5)
            self.doReset()
        except Exception as err:
            raise CommunicationError(self,
                                     'cannot clear alarm for encoder') from err
コード例 #7
0
class ReferenceMotor(CanReference, Motor1):
    """IPC stepper card motor with reference capability."""

    parameters = {
        'refswitch':
        Param('Type of the reference switch',
              type=oneof('high', 'low', 'ref'),
              mandatory=True,
              settable=False),
        'maxtries':
        Param('Number of tries to reach the target',
              type=int,
              default=3,
              settable=True),
        'parkpos':
        Param('Position to move after reaching reference switch',
              type=none_or(float),
              unit='main',
              settable=False,
              default=None),
        'refpos':
        Param('Number of steps at reference position',
              type=intrange(0, 999999),
              settable=False,
              default=500000),
        'refspeed':
        Param('Speed value during the reference move',
              type=intrange(0, 255),
              settable=False),
        'refstep':
        Param('Steps to move away from reference switch',
              type=intrange(0, 999999),
              settable=False,
              default=2000),
        'refmove':
        Param('Steps to move to the reference switch',
              type=intrange(0, 10000),
              settable=False,
              default=100),
        'refdirection':
        Param(
            'Direction of the reference move'
            'to "lower" or "upper" step values',
            type=oneof('lower', 'upper'),
            settable=False,
            default='lower'),
    }

    parameters_override = {
        'timeout': Override(default=600.),
    }

    def doInit(self, mode):
        Motor1.doInit(self, mode)
        self._stoprequest = 0
        self._refcontrol = None
        if mode != SIMULATION:
            try:
                current_val = self.doReadSpeed()
                if self.speed != current_val:
                    self.doWriteSpeed(self.speed)
            except NicosError:
                self.log.warning("Could not write 'speed' value")
            try:
                current_val = self.doReadAccel()
                if self.accel != current_val:
                    self.doWriteAccel(self.accel)
            except NicosError:
                self.log.warning("Could not write 'accel' value")
            try:
                current_val = self.doReadMicrostep()
                if self.microstep != current_val:
                    self.doWriteMicrostep(self.microstep)
            except NicosError:
                self.log.warning("Could not write 'microstep' value")
        self._lasterror = None

    def doStart(self, target):
        try:
            Motor1.doStart(self, target)
        except Exception as e:
            r = self._attached_bus.get(self.addr, STATUS)
            if not (r & 0x20 or r & 0x40 or r & 100 or r & 200):
                raise
            self.log.info('Ignoring due to limit switch hitting: %r', e)

    def doStop(self):
        self._stoprequest = 1
        Motor1.doStop(self)
        if self._refcontrol and self._refcontrol.is_alive():
            self._refcontrol.join()
        self._refcontrol = None

    def doStatus(self, maxage=0):
        """Return the status of the motor controller."""
        if self._mode == SIMULATION:
            return (status.OK, '')
        elif self._refcontrol and self._refcontrol.is_alive():
            return (status.BUSY, 'referencing')
        return Motor1.doStatus(self, maxage)

    def doReference(self, *args):
        refswitch = args[0] if args and isinstance(args[0], str) else None
        if self.doStatus()[0] == status.BUSY:
            self.stop()
            self.wait()

        # The sixfold and triple cards will be reset as whole card, so values
        # of all channels will reset !!!
        # self.reset()
        # self.wait()

        if self.doStatus()[0] == status.OK:
            if self._refcontrol and self._refcontrol.is_alive():
                self._refcontrol.join()
            self._refcontrol = None

            if self._refcontrol is None:
                threadname = 'referencing %s' % self
                self._refcontrol = createThread(threadname,
                                                self._reference,
                                                args=(refswitch, ))
                session.delay(0.2)
        else:
            raise NicosError(self, 'in error or busy state')

    def doWriteSteps(self, value):
        self.log.debug('setting new steps value: %s', value)
        self._attached_bus.send(self.addr, SET_CURR_POS, value, 6)
        ret = self._attached_bus.get(self.addr, CURR_POS)
        self.log.debug('set new steps value: %s', ret)
        return ret

    def _start(self, target):
        target = self._tosteps(target)
        pos = self._tosteps(self.read(0))
        self.log.debug('target is %d steps, pos is %d steps', target, pos)
        diff = target - pos
        if diff:
            softlimit = self._tosteps(self.absmin if self.refdirection ==
                                      'lower' else self.absmax)
            self.log.debug('Diff: %d %d', softlimit, pos)
            if abs(softlimit - pos) < abs(diff):
                savediff = diff
                diff = softlimit - pos
                if not diff:
                    return
                self.log.info('Distance to softlimit too short: %d -> %d',
                              savediff, diff)
            self._attached_bus.send(self.addr,
                                    DIR_NEG if diff < 0 else DIR_POS)
            try:
                self._attached_bus.send(self.addr, MOVE_REL, abs(diff), 6)
            except Exception as e:
                r = self._attached_bus.get(self.addr, STATUS)
                self.log.debug('status: 0x%04X', r)
                if not (r & 0x20 or r & 0x40 or r & 100 or r & 200):
                    raise
                self.log.info('Ignoring due to limit switch hitting: %r', e)
            session.delay(0.1)
            # hw_wait will not work here, since the status of the device is
            # always busy, but only the state of the motor is important
            while Motor1.doStatus(self, 0)[0] == status.BUSY and \
                    not self._stoprequest:
                session.delay(self._base_loop_delay)
            # motor must be stopped, the hardware will go again if any of the
            # limit switches is released again, this leads to unpredictable
            # results!
            Motor1.doStop(self)
            while Motor1.doStatus(self, 0)[0] == status.BUSY:
                session.delay(self._base_loop_delay)
            self._stoprequest = 0

    def _reference(self, refswitch=None):
        """Drive motor to reference switch."""
        self.log.debug('referencing: %s', refswitch)

        # init referencing
        self._stoprequest = 0
        # if reference switch is explicitly given only drive to given limit
        # switch
        fullref = refswitch is None
        if refswitch is None:
            refswitch = self.refswitch
        if refswitch == self.refswitch:
            refdirection = self.refdirection
        else:  # refdirection must be opposite from configured
            if self.refdirection == 'upper':
                refdirection = 'lower'
            else:
                refdirection = 'upper'
            self.log.debug('switch %s, direction %s, %s', refswitch,
                           self.refdirection, refdirection)
        try:
            _min, _max, motspeed = self.min, self.max, self.speed
            self._resetlimits()
            self._drive_to_limit_switch(refswitch, refdirection)
            if fullref:
                if self.isAtReference():
                    self._move_away_from_reference(refswitch, refdirection)
                self.resetTimeout(0)
                self._move_until_referenced()
                if self.isAtReference():
                    if self.parkpos is not None:
                        self._start(self.parkpos)
                if self._stoprequest == 1:
                    raise NicosError(self, 'reference stopped by user')
        except NicosTimeoutError as e:
            self.log.error('%s occured during referencing', e)
        except NicosError as e:
            self.log.error('%s: occured during referencing', e)
        except Exception as e:
            self.log.error('%s: occured during referencing', e)
        finally:
            self.log.debug('stoprequest: %d', self._stoprequest)
            self.speed = motspeed
            self.min = _min
            self.max = _max
            if fullref:
                try:
                    temp = self.read(0)
                    self.log.info('new position of %s is now %.3f %s',
                                  self.name, temp, self.unit)
                    if (self.abslimits[0] - self.precision) <= temp <= \
                       (self.abslimits[1] + self.precision):
                        self._resetlimits()
                    else:
                        self.log.warning('in _referencing limits not restored '
                                         'after positioning')
                except NicosError as e:
                    self.log.warning('error catched in finally positioning %s',
                                     e)

    def _drive_to_limit_switch(self, switch, direction):
        self.log.debug('driving to %r switch towards %r', switch, direction)
        if not self.isAtReference(switch):
            self.log.debug('in _drive_to_limitswitch checkrefswitch')
            self._drive_to_reference(self.refspeed, switch, direction)
            if not self.isAtReference(switch):
                if self._stoprequest == 1:
                    raise NicosError(self, 'limitswitch drive stopped by user')

    def isAtReference(self, refswitch=None):
        """Check whether configured reference switch is active."""
        self.log.debug('in isAtReference function')
        if refswitch is None:
            refswitch = self.refswitch
        return (refswitch == 'high' and self._isAtHighlimit()) or \
               (refswitch == 'low' and self._isAtLowlimit()) or \
               (refswitch == 'ref' and self._isAtReferenceSwitch())

    def _read_status(self):
        return self._attached_bus.get(self.addr, STATUS)

    def _isAtHighlimit(self):
        val = self._read_status()
        if self._hwtype == 'sixfold':
            if self.firmware < 63:
                return bool(val & 0x20)
        return bool(val & 0x40)

    def _isAtLowlimit(self):
        val = self._read_status()
        if self._hwtype == 'sixfold':
            if self.firmware < 63:
                return bool(val & 0x40)
        return bool(val & 0x20)

    def _isAtReferenceSwitch(self):
        val = self._read_status()
        return bool(val & 0x80)

    def _setrefcounter(self, raise_error=True):
        self.log.debug('in setrefcounter')
        if not self.isAtReference():
            if raise_error:
                raise UsageError('cannot set reference counter, not at '
                                 'reference point')
        self.steps = self.refpos

    def _resetlimits(self):
        alim = self.abslimits
        if isinstance(self, HasOffset):
            newlim = (alim[0] - self.offset, alim[1] - self.offset)
        else:
            newlim = alim
        if self.userlimits != newlim:
            self.userlimits = newlim

    def _drive_to_reference(self, refspeed, refswitch, refdirection):
        self.log.debug('reference: switch %s, direction %s', refswitch,
                       refdirection)
        if refswitch in ['high', 'low']:
            if refdirection == 'lower':
                if self.slope > 0:
                    stop, start = self._hw_limits
                else:
                    start, stop = self._hw_limits
            else:
                if self.slope > 0:
                    start, stop = self._hw_limits
                else:
                    stop, start = self._hw_limits
            # if start > stop:
            #     start, stop = stop, start
            while not self.isAtReference():
                self.setPosition(start)
                self.log.debug('move to %f from %f', stop, start)
                self._start(stop)
                self.log.debug('finished at %f', self.read(0))
                if self._stoprequest:
                    raise NicosError(self, 'reference drive stopped by user')

    def _move_away_from_reference(self, refswitch, refdirection):
        self.log.debug('%s limit switch active', refswitch)
        self.steps = self.refpos
        if self.refstep != 0:
            d = abs(self.refstep / self.slope)
            if refdirection == 'lower':
                d = -d
            self.log.debug('move away from reference switch %f', d)
            self._start(self.read(0) - d)
        if self._stoprequest:
            raise NicosError(self, 'move away from reference stopped by user')

    def _move_until_referenced(self):
        # calculate the step size for each reference move
        d = abs(self.refmove / self.slope)
        if self.refdirection == 'lower':
            d = -d
        while not self.isAtReference():
            p = self.read(0)
            t = p + d
            self.log.debug('move to %s limit switch %r -> %r', self.refswitch,
                           p, t)
            self._start(t)
            if self._stoprequest:
                raise NicosError(self, 'move until reference stopped by user')
            if self.isTimedOut():
                raise NicosTimeoutError(
                    self, 'timeout occured during '
                    ' reference drive')
        self._setrefcounter(False)
        # avoid message 'target not reached' in status message
        self._setROParam('target', self.doRead(0))

    @property
    def _hw_limits(self):
        return (self._fromsteps(self.min), self._fromsteps(self.max))
コード例 #8
0
class RefAxis(Axis):
    parameters = {
        'refpos':
        Param('Reference position', type=none_or(float), default=None),
        'autoref':
        Param(
            'Number of movements before autoreferencing, '
            'None/0=disable, >0:count all moves, <0:count only '
            'negative moves',
            type=none_or(int),
            default=None,
            settable=True),
        'refspeed':
        Param(
            'Motorspeed when referencing or None/0 to use normal'
            'speed setting',
            type=none_or(float),
            default=None),
    }

    _moves = 0
    _referencing = False

    def doStart(self, target):
        if target < self.read():
            self._moves += 1
        elif self.autoref and self.autoref > 0:
            self._moves += 1

        if self.autoref and self._moves > abs(self.autoref) and \
           not self._referencing:
            self.log.info('self.autoref limit reached => referencing NOW')
            self.reference(target)  # WARNING: This takes a while !

        return Axis.doStart(self, target)  # handles offset as well

    def doReference(self, gotopos=None):  # pylint: disable=W0221
        """references this axis by finding the reference switch and then
        setting current position to refpos.
        1) Finding the refswitch by going backwards until the refswitch
           (=negative limit switch) fires,
        2) then go forward a little until the switch is not active,
        3) then crawl SLOWLY backwards to hit it again.
        4) current position is set to self.refpos (e.g. the reference is
           stored, the referencing done)
        If an axis can't go reliably backwards (e.g. blocking) in step 1)
        then this fails!!!

        the refpos must be within motor.abslimits!
        """

        # Check initial conditions
        if self.refpos is None:
            self.log.error('Can\'t reference, no refpos specified!')
            return
        if self._mode not in [MASTER, MAINTENANCE]:
            if self._mode == SIMULATION:
                self.log.debug('would reference')
            else:
                self.log.error('Can\'t reference if not in master or '
                               'maintenance mode!')
            return

        try:
            # helper for DRY: check for ANY Refswitch
            def refsw(motor):
                return motor.doStatus()[1].lower().find('limit switch') > -1

            # helper: wait until the motor HW is no longer busy
            def wait_for_motor(m):
                while m.doStatus()[0] == status.BUSY:
                    session.delay(m._base_loop_delay)
                m.poll()

            self.stop()  # make sure the axis code does not interfere
            self._referencing = True
            m = self._attached_motor
            oldspeed = m.speed

            # figure out the final position (=current position or gotopos, if
            # gotopos is given)
            oldpos = self.doRead() if gotopos is None else gotopos

            # Step 1) Try to hit the refswitch by turning backwards in a fast
            # way
            self.log.info('Referencing: FAST Mode: find refswitch')
            try:
                # ignore Userlimits! -> use doStart
                m.doStart(m.abslimits[0])
            except NicosError:
                # if refswitch is already active, doStart gives an exception
                pass
            # wait until a) refswitch fires or b) movement finished
            wait_for_motor(m)
            if not refsw(m) and self._checkTargetPosition(
                    self.read(0), self.abslimits[0], error=False):
                self.log.error('Referencing: No refswitch found!!! Exiting')
                self.start(oldpos)
                return

            # Step 2) Try find a position without refswitch active, but close
            # to it.
            self.log.info('Referencing: FAST Mode: looking for inactive '
                          'refswitch')
            steps = [0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100, 200]
            for stepsize in steps:
                self.log.debug('trying %s', self.format(stepsize, unit=True))
                m.doStart(m.doRead() + stepsize)
                wait_for_motor(m)
                if not refsw(m):
                    break
            else:
                self.log.error(
                    'Referencing: RefSwitch still active after '
                    '%.1f %s, exiting!', sum(steps), self.unit)
                self.start(oldpos)
                return

            # Step 3) Now SLOWLY crawl onto the refswitch
            if self.refspeed:
                m.speed = self.refspeed
            tries = 7
            self.log.info('Referencing: SLOW Mode: find refswitch')
            while not (refsw(m)) and tries > 0:
                self.log.debug('Another %d slots left to try', tries)
                try:
                    m.doStart(m.doRead() - stepsize / 3.)  # pylint: disable=W0631
                except NicosError:
                    # if refswitch is already active, doStart gives an
                    # exception
                    pass
                wait_for_motor(m)
                tries -= 1
            m.stop()
            m.speed = oldspeed
            if tries == 0:
                self.log.error(
                    'Referencing: RefSwitch still not active after '
                    '%.1f %s, exiting!', self.wraparound, self.unit)
                self.start(oldpos)
                return

            # Step 4) We are _at_ refswitch, motor stopped
            # => we are at refpos, communicate this to the motor
            self.poll()
            self.log.info(
                'Found Refswitch at %.2f, should have been at %.2f, '
                'lost %.3f %s', m.doRead(), self.refpos,
                m.doRead() - self.refpos, self.unit)

            for d in devIter(self._adevs):
                if hasattr(d, 'setPosition'):
                    try:
                        d.setPosition(self.refpos)
                    except NicosError as e:
                        self.log.error(str(e))
            self.poll()

            self.log.info('Referenced, moving to position (%.2f)...', oldpos)
            self.start(oldpos)
            self._moves = 0
        finally:
            m.speed = oldspeed
            # if gotopos was given, do not wait...
            if gotopos is None:
                self.wait()
            self._referencing = False
コード例 #9
0
ファイル: system.py プロジェクト: umithardal/nicos
class FreeSpace(Readable):
    """This is a readable device that returns the free space on a filesystem.

    It is useful to record this in the cache, for example to enable warnings
    about low free space before data files cannot be saved anymore.

    The device status is `OK` until free space is below the value set by the
    `minfree` parameter.
    """

    parameters = {
        'path':
        Param(
            'The path to the filesystem mount point (or "None") '
            'to check the experiment data directory).',
            type=none_or(str),
            default=None),
        'minfree':
        Param('Minimum free space for "ok" status',
              unit='GiB',
              default=5,
              settable=True),
    }

    parameter_overrides = {
        'unit': Override(default='GiB', mandatory=False),
        'pollinterval': Override(default=300),  # every 5 minutes is sufficient
        'maxage': Override(default=330),
    }

    def doRead(self, maxage=0):
        if self.path is None:
            path = session.experiment.dataroot
        else:
            path = self.path
        try:
            if os.name == 'nt':
                free = ctypes.c_ulonglong(0)
                ret = ctypes.windll.kernel32.GetDiskFreeSpaceExW(
                    ctypes.c_wchar_p(path), None, None, ctypes.pointer(free))
                if ret == 0:
                    raise OSError('GetDiskFreeSpaceExW call failed')
                return free.value / self._factor
            else:
                st = os.statvfs(path)
                return (st.f_frsize * st.f_bavail) / self._factor
        except OSError as err:
            raise NicosError(self, 'could not determine free space: %s' % err)

    def doStatus(self, maxage=0):
        free = self.read()
        munit = self.parameters['minfree'].unit
        mfactor = units.get(munit, (1024**3))
        if free * self._factor < self.minfree * mfactor:
            return status.ERROR, 'free space %(free).2f %(unit)s below ' \
                '%(minfree).2f %(munit)s' \
                % {'free': free,
                   'minfree': self.minfree,
                   'unit': self.unit,
                   'munit': munit}
        return status.OK, '%.2f %s free' % (free, self.unit)

    def doUpdateMinfree(self, value):
        if self._cache:
            self._cache.invalidate(self, 'status')

    def doUpdateUnit(self, unit):
        factor = units.get(unit, None)
        if factor is None:
            raise ConfigurationError('Unsupported unit, allowed: %s' %
                                     ','.join(units))
        self._factor = factor
コード例 #10
0
ファイル: switcher.py プロジェクト: ess-dmsc/nicos
class MultiSwitcher(MappedMoveable):
    """The multi-switcher generalizes the `Switcher` so that for a state change
    multiple underlying moveable devices can be controlled.

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

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

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

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

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

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

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

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

    hardware_access = False

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

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

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

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

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

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

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

    def doStop(self):
        multiStop(self._adevs)
コード例 #11
0
class VirtualRealTemperature(HasWindowTimeout, HasLimits, Moveable):
    """A virtual temperature regulation device with a realistic simulation
    of a sample in a cryostat, with a PID-controlled heater.
    """

    parameters = {
        'jitter':    Param('Jitter of the read-out value', default=0,
                           unit='main'),
        'regulation': Param('Current temperature (regulation)', settable=False,
                            unit='main', default=2.),
        'sample':    Param('Current temperature (sample)', settable=False,
                           unit='main', default=2.),
        'curstatus': Param('Current status', type=tupleof(int, str),
                           settable=True, default=(status.OK, 'idle'),
                           no_sim_restore=True),
        'ramp':      Param('Ramping speed of the setpoint', settable=True,
                           type=none_or(floatrange(0, 1000)), unit='main/min'),
        'loopdelay': Param('Cycle time for internal thread', default=1,
                           settable=True, unit='s', type=floatrange(0.2, 10)),
        'setpoint':  Param('Current setpoint', settable=True, unit='main',
                           category='general', default=2.),
        'heater':    Param('Simulated heater output power in percent',
                           settable=True, unit='%'),
        'heaterpower': Param('Simulated heater output power in Watt',
                             settable=False, unit='W'),
        'maxpower':  Param('Max heater power in W', settable=True, unit='W',
                           default=100),
        'p':         Param('P-value for regulation', settable=True,
                           default=100, unit='%/main'),
        'i':         Param('I-value for regulation', settable=True,
                           default=10, unit='%/mains'),
        'd':         Param('D-value for regulation', settable=True,
                           default=1, unit='%s/main'),
        'mode':      Param('PID control or open loop heater mode',
                           settable=True, default='manualpid',
                           type=oneof('manualpid', 'manual', 'openloop')),
        'speedup':   Param('Speed up simulation by a factor', settable=True,
                           default=1, unit='', type=floatrange(0.01, 100)),
    }

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

    _thread = None
    _window = None
    _starttime = 0
    _stopflag = False

    def doInit(self, mode):
        if mode == SIMULATION:
            return
        if self.curstatus[0] < status.OK:  # clean up old status values
            self._setROParam('curstatus', (status.OK, ''))
        if session.sessiontype != POLLER:  # dont run in the poller!
            self._window = []
            self._statusLock = threading.Lock()
            self._thread = createThread('cryo simulator %s' % self, self.__run)

    def doShutdown(self):
        self._stopflag = True

    def doStart(self, pos):
        # do nothing more, its handled in the thread...
        with self._statusLock:
            # insert target position into history
            # if target is far away -> loop goes busy
            # else loop sets to stable again....
            currtime = time.time()
            self._window.append((currtime, pos))
            self._starttime = currtime
            self.curstatus = status.BUSY, 'ramping setpoint'

    def doRead(self, maxage=0):
        return self.regulation + self.jitter * (0.5 - random.random())

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

    def doStop(self):
        self.start(self.setpoint)

    def doPoll(self, nr, maxage):
        self._pollParam('setpoint', 1)
        self._pollParam('curvalue', 1)
        self._pollParam('curstatus', 1)

    #
    # Parameters
    #
    def doWriteMaxpower(self, newpower):
        self.heater = clamp(self.heater * self.maxpower / float(newpower),
                            0, 100)

    def doReadTarget(self):
        # Bootstrapping helper, called at most once.
        # Start target at the initial current temperature, to avoid going into
        # BUSY state right away.
        return self.parameters['regulation'].default

    #
    # calculation helpers
    #
    def __coolerPower(self, temp):
        """returns cooling power in W at given temperature"""
        # quadratic up to 42K, is linear from 40W@42K to 100W@600K
        # return clamp((temp-2)**2 / 32., 0., 40.) + temp * 0.1
        return clamp(15 * atan(temp * 0.01) ** 3, 0., 40.) + temp * 0.1 - 0.2

    def __coolerCP(self, temp):
        """heat capacity of cooler at given temp"""
        return 75 * atan(temp / 50)**2 + 1

    def __heatLink(self, coolertemp, sampletemp):
        """heatflow from sample to cooler. may be negative..."""
        flow = (sampletemp - coolertemp) * \
               ((coolertemp + sampletemp) ** 2)/400.
        cp = clamp(self.__coolerCP(coolertemp) * self.__sampleCP(sampletemp),
                   1, 10)
        return clamp(flow, -cp, cp)

    def __sampleCP(self, temp):
        return 3 * atan(temp / 30) + \
            12 * temp / ((temp - 12.)**2 + 10) + 0.5

    def __sampleLeak(self, temp):
        return 0.02/temp

    #
    # Model is a cooling source with __coolingPower and __coolerCP capacity
    # here we have THE heater and the regulation thermometer
    # this is connected via a __heatLink to a sample with __heatCapacity and
    # here we have the sample thermometer
    #
    def __run(self):
        try:
            self.__moving()
        except Exception as e:
            if not self._stopflag:
                self.log.exception(e)
                self.curstatus = status.ERROR, str(e)

    def __moving(self):
        # complex thread handling:
        # a) simulation of cryo (heat flow, thermal masses,....)
        # b) optional PID temperature controller with windup control
        # c) generating status+updated value+ramp
        # this thread is not supposed to exit!

        # local state keeping:
        regulation = self.regulation
        sample = self.sample
        timestamp = time.time()
        heater = 0
        lastflow = 0
        last_heaters = (0, 0)
        delta = 0
        I = D = 0
        lastD = 0
        damper = 1
        lastmode = self.mode
        while not self._stopflag:
            t = time.time()
            h = t - timestamp
            if h < self.loopdelay / damper:
                time.sleep(clamp(self.loopdelay / damper - h, 0.1, 60))
                continue
            h *= self.speedup
            # a)
            sample = self.sample
            regulation = self.regulation
            heater = self.heater

            heatflow = self.__heatLink(regulation, sample)
            self.log.debug('sample = %.5f, regulation = %.5f, heatflow = %.5g',
                           sample, regulation, heatflow)
            newsample = max(0,
                            sample + (self.__sampleLeak(sample) - heatflow) /
                            self.__sampleCP(sample) * h)
            # avoid instabilities due to too small CP
            newsample = clamp(newsample, sample, regulation)
            regdelta = (heater * 0.01 * self.maxpower + heatflow -
                        self.__coolerPower(regulation))
            newregulation = max(0, regulation +
                                regdelta / self.__coolerCP(regulation) * h)

            # b) see
            # http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-introduction/
            if self.mode != 'openloop':
                # fix artefacts due to too big timesteps
                # actually i would prefer reducing loopdelay, but i have no
                # good idea on when to increase it back again
                if heatflow * lastflow != -100:
                    if (newregulation - newsample) * (regulation - sample) < 0:
                        # newregulation = (newregulation + regulation) / 2
                        # newsample = (newsample + sample) / 2
                        damper += 1
                lastflow = heatflow

                error = self.setpoint - newregulation
                # use a simple filter to smooth delta a little
                delta = (delta + regulation - newregulation) / 2.

                kp = self.p / 10.             # LakeShore P = 10*k_p
                ki = kp * abs(self.i) / 500.  # LakeShore I = 500/T_i
                kd = kp * abs(self.d) / 2.    # LakeShore D = 2*T_d

                P = kp * error
                I += ki * error * h
                D = kd * delta / h

                # avoid reset windup
                I = clamp(I, 0., 100.)  # I is in %

                # avoid jumping heaterpower if switching back to pid mode
                if lastmode != self.mode:
                    # adjust some values upon switching back on
                    I = self.heater - P - D

                v = P + I + D
                # in damping mode, use a weighted sum of old + new heaterpower
                if damper > 1:
                    v = ((damper ** 2 - 1) * self.heater + v) / damper ** 2

                # damp oscillations due to D switching signs
                if D * lastD < -0.2:
                    v = (v + heater) / 2.
                # clamp new heater power to 0..100%
                heater = clamp(v, 0., 100.)
                lastD = D

                self.log.debug('PID: P = %.2f, I = %.2f, D = %.2f, '
                               'heater = %.2f', P, I, D, heater)

                # check for turn-around points to detect oscillations ->
                # increase damper
                x, y = last_heaters
                if (x + 0.1 < y and y > heater + 0.1) or \
                   (x > y + 0.1 and y + 0.1 < heater):
                    damper += 1
                last_heaters = (y, heater)

            else:
                # self.heaterpower is set manually, not by pid
                heater = self.heater
                last_heaters = (0, 0)

            heater = round(heater, 3)
            sample = newsample
            regulation = newregulation
            lastmode = self.mode

            # c)
            if self.setpoint != self.target:
                if self.ramp == 0:
                    maxdelta = 10000
                else:
                    maxdelta = self.ramp / 60. * h
                try:
                    self.setpoint = round(self.setpoint +
                                          clamp(self.target - self.setpoint,
                                                -maxdelta, maxdelta), 3)
                    self.log.debug('setpoint changes to %r (target %r)',
                                   self.setpoint, self.target)
                except (TypeError, ValueError):
                    # self.target might be None
                    pass

            # keep max self.window seconds long history
            self._cacheCB('value', regulation, t)

            # temperature is stable when all recorded values in the window
            # differ from setpoint by less than tolerance
            with self._statusLock:
                if self.setpoint == self.target:
                    self._setROParam('curstatus', (status.OK, ''))
                    damper -= (damper - 1) / 10.  # max value for damper is 11
                else:
                    self._setROParam('curstatus',
                                     (status.BUSY, 'ramping setpoint'))
            damper -= (damper - 1) / 20.
            self._setROParam('regulation', round(regulation, 3))
            self._setROParam('sample', round(sample, 3))
            self._setROParam('heaterpower',
                             round(heater * self.maxpower * 0.01, 3))
            self._setROParam('heater', heater)
            timestamp = t
コード例 #12
0
ファイル: sample.py プロジェクト: ess-dmsc/nicos
class Sample(Moveable):
    """A special device to represent a sample.

    An instance of this class is used as the *sample* attached device of the
    `Experiment` object.  It can be subclassed to add special sample
    properties, such as lattice and orientation calculations, or more
    parameters describing the sample.

    The device stores the collection of all currently defined samples in
    its `samples` parameter.  When changing samples, it will overwrite the
    device's other parameters with these values.
    """

    parameters = {
        'samplename':
        Param('Current sample name',
              type=str,
              settable=True,
              category='sample'),
        'samplenumber':
        Param(
            'Current sample number: e.g. the position in '
            'a sample changer or the index of the sample '
            'among all defined samples',
            type=none_or(int),
            settable=True),
        'samples':
        Param('Information about all defined samples',
              type=dictof(int, dictof(str, anytype)),
              settable=True,
              internal=True,
              preinit=True),
    }

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

    valuetype = str

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

    def doStatus(self, maxage=0):
        return status.OK, ''

    def doStart(self, target):
        self.select(target)

    def doIsAtTarget(self, pos, target):
        # never warn about self.target mismatch
        return True

    @property
    def filename(self):
        return safeName(self.samplename)

    def doWriteSamplename(self, name):
        if name:
            session.elogEvent('sample', name)

    def clear(self):
        """Clear experiment-specific information."""
        self.samplename = ''
        self.samplenumber = None
        self.samples = {}

    def new(self, parameters):
        """Create and select a new sample."""
        # In this simple base class, we expect the user to use only NewSample,
        # so we remove stored sample information every time to avoid a buildup
        # of unused sample information.
        self.samples = {0: parameters}
        self.select(0)

    def set(self, number, parameters):
        """Set sample information for sample no. *number*."""
        if number is None:
            raise InvalidValueError(self, 'cannot use None as sample number')
        info = self.samples.copy()
        if number in info:
            self.log.warning('overwriting parameters for sample %s (%s)',
                             number, info[number]['name'])
        info[number] = parameters
        self.samples = info

    def select(self, number_or_name):
        """Select sample with given number or name."""
        number = self._findIdent(number_or_name)
        try:
            parameters = self.samples[number]
        except KeyError:
            raise InvalidValueError(
                self, 'cannot find sample with number or name %r' %
                number_or_name) from None
        self._applyParams(number, parameters)
        session.experiment.newSample(parameters)
        self.poll()

    def _findIdent(self, number_or_name):
        """Find sample number.  Can be overridden in subclasses."""
        # look by number
        if number_or_name in self.samples:
            return number_or_name
        # look by name
        found = None
        for (number, parameters) in self.samples.items():
            if parameters['name'] == number_or_name:
                if found is not None:
                    # two samples with same name found...
                    raise InvalidValueError(
                        self, 'two samples with name %r '
                        'were found, please use the '
                        'sample number (%s or %s)' %
                        (number_or_name, found, number))
                found = number
        return found

    def _applyParams(self, number, parameters):
        """Apply sample parameters.  Override in subclasses.

        All parameters beside the name should be treated as optional by
        subclasses, since they will not be provided for the empty sample
        created by NewExperiment.
        """
        self.samplenumber = number
        self.samplename = parameters['name']
        self._setROParam('target', parameters['name'])

    def doUpdateSamples(self, info):
        self.valuetype = oneof(*(info[n]['name'] for n in sorted(info)))
コード例 #13
0
class CARESSDevice(HasCommunication):
    """The CARESS base device."""

    _orb = None

    _kind = None

    _used_counter = 0

    _caressObject = None

    _initialized = False

    _caress_name = ''

    _caress_maps = {}

    _caress_initialized = False

    parameters = {
        'config':
        Param(
            'Device configuration/setup string',
            type=str,
            mandatory=True,
            settable=False,
        ),
        'nameserver':
        Param(
            'Computer name running the CORBA name service',
            type=none_or(str),
            mandatory=False,
            default=None,
        ),
        'objname':
        Param(
            'Name of the CORBA object',
            type=none_or(str),
            mandatory=False,
            default=None,
        ),
        'caresspath':
        Param(
            'Directory of the CARESS installation',
            type=absolute_path,
            default='/opt/caress/parameter',
            settable=False,
        ),
        'toolpath':
        Param(
            'Path to the dump_u1 program',
            type=absolute_path,
            default='/opt/caress',
            settable=False,
        ),
        'absdev':
        Param('CORBA object is a the legacy absdev device',
              type=bool,
              default=True,
              settable=False),
        'loadblock':
        Param('Additional init block', type=str, settable=False, default=''),
        'cid':
        Param('CARESS device ID',
              type=int,
              settable=False,
              internal=True,
              default=0),
    }

    parameter_overrides = {
        'comtries': Override(default=5),
        'comdelay': Override(default=0.2),
    }

    def _initORB(self, args):
        if not self._orb:
            self._orb = CORBA.ORB_init(args, CORBA.ORB_ID)

    def _getCID(self, device):
        if session.sessiontype == POLLER:
            while not self.cid:
                session.delay(0.5)
            return self.cid
        self.log.debug('get CARESS device ID: %r', device)
        answer = createSubprocess(
            'cd %s && %s/dump_u1 -n %s' %
            (self.caresspath, self.toolpath, device),
            shell=True,
            stdout=subprocess.PIPE,
            universal_newlines=True,
        ).communicate()[0]
        self._caress_name = device
        if answer in ('', None):
            if not CARESSDevice._caress_maps:
                CARESSDevice._caress_maps[device] = 4096
            elif device not in CARESSDevice._caress_maps:
                CARESSDevice._caress_maps[device] = 1 + \
                    max(CARESSDevice._caress_maps.values())
            res = CARESSDevice._caress_maps[device]
        else:
            res = int(answer.split('=')[1])
        self.log.debug('get CARESS device ID: %r', res)
        return res

    def _is_corba_device(self):
        return self._device_kind() == CORBA_DEVICE and (not self.absdev)

    def _device_kind(self):
        if not self._kind:
            tmp = self.config.split(None, 2)
            self._kind = int(tmp[1]) if len(tmp) > 1 else 0
        return self._kind

    def _initObject(self):
        if not self._orb:
            raise ProgrammingError(self, 'Programmer forgot to call _initORB')

        obj = self._orb.resolve_initial_references('NameService')
        _root_context = obj._narrow(CosNaming.NamingContext)

        if not _root_context:
            raise CommunicationError(
                self, 'Failed to narrow the root naming'
                ' context')
        if self._is_corba_device():
            try:
                tmp = self.objname.split('.') if self.objname else \
                    self.config.split()[2].split('.')
                if len(tmp) < 2:
                    tmp.append('caress_object')
                self.log.debug('%r', tmp)
                obj = _root_context.resolve(
                    [CosNaming.NameComponent(tmp[0], tmp[1])])
            except CosNaming.NamingContext.NotFound as ex:
                raise ConfigurationError(self,
                                         'Name not found: %s' % (ex, )) from ex
            self._caressObject = obj._narrow(CARESS.CORBADevice)
        else:
            try:
                self._caressObject = \
                    self._orb.string_to_object('corbaname::%s#%s.context/'
                                               'caress.context/'
                                               'server.context/absdev.object' %
                                               (self.nameserver, self.objname))
            except CORBA.BAD_PARAM as ex:
                raise ConfigurationError(self,
                                         'Name not found: %s' % (ex, )) from ex

        if CORBA.is_nil(self._caressObject):
            raise CommunicationError(self, 'Could not create a CARESS device')

        if hasattr(self._caressObject, 'init_module_orb'):
            self._caressObject.init_module = self._caressObject.init_module_orb

    def _normalized_config(self):
        tmp = self.config.split()
        if tmp[2].count(':') and not self.absdev:
            tmp[2] = tmp[2].split(':/')[1]
            return ' '.join(tmp)
        else:
            return self.config

    def _name_server(self):
        tmp = self.config.split()
        if tmp[2].count(':') and not self.absdev:
            return tmp[2].split(':')[0]
        elif self.nameserver:
            return self.nameserver
        else:
            raise ConfigurationError(
                self, 'No name server configured. Please '
                'use the "nameserver" parameter or put it'
                'into the "config" parameter.')

    def _init(self, cid):
        try:
            if session.sessiontype != POLLER:
                if hasattr(self._caressObject, 'init_system_orb'):
                    if not CARESSDevice._caress_initialized:
                        self.log.debug('initialize the CARESS absdev '
                                       'container')
                        if self._caressObject.init_system_orb(0)[0] in \
                           (0, CARESS.OK):
                            CARESSDevice._caress_initialized = True
                        else:
                            raise CommunicationError(
                                self, 'could not '
                                'initialize CARESS absdev'
                                ' container')

            _config = self._normalized_config()

            res = self._caressObject.init_module(INIT_CONNECT, cid, _config)
            self.log.debug('INIT_CONNECT: %r', res)
            if res[0] in (0, CARESS.OK):
                if res[1] == OFF_LINE:
                    res = self._caressObject.init_module(
                        INIT_REINIT, cid, _config)
            else:
                res = self._caressObject.init_module(INIT_NORMAL, cid, _config)
            self.log.debug('init module (Connect): %r', res)
            if res[0] not in (0, CARESS.OK) or res[1] == OFF_LINE:
                raise NicosError(
                    self, 'Could not initialize module! (%r) %d' %
                    ((res, ), self._device_kind()))
            # res = self._caressObject.init_module(INIT_REINIT, cid, _config)
            # self.log.debug('Init module (Re-Init): %r', res)
            # if res not in[(0, ON_LINE), (CARESS.OK, ON_LINE)]:
            #     self.log.error('Init module (Re-Init): %r (%d, %s)',
            #                    res, cid, self.config)
            if self._device_kind() == CORBA_DEVICE:
                if self.absdev:
                    res = self._caressObject.char_loadblock_module_orb(
                        0, cid, 1, len(self.loadblock), 16, self.loadblock)
                else:
                    val = CARESS.Value(ab=self.loadblock.encode())
                    res = self._caressObject.loadblock_module(
                        0, cid, 1, len(self.loadblock), val)  # 16, val)
            self._initialized = True
            if not self._is_corba_device():
                CARESSDevice._used_counter += 1

        except CORBA.TRANSIENT as err:
            raise CommunicationError(
                self, 'could not init CARESS module %r (%d: %s)' %
                (err, cid, self.config)) from err

    def doInit(self, mode):
        if mode == SIMULATION:
            return
        if not omniORB:
            raise ConfigurationError(self, 'There is no CORBA module found')
        self._initORB(
            ['-ORBInitRef',
             'NameService=corbaname::%s' % self._name_server()])
        self._initObject()
        _cid = self._getCID(self.config.split(None, 2)[0])
        self._init(_cid)
        if session.sessiontype != POLLER:
            # omniORB.setClientCallTimeout(self._caressObject, 0)
            self._setROParam('cid', _cid)
            if self._cache:
                self._cache.invalidate(self, 'cid')

    def doShutdown(self):
        if session.mode == SIMULATION:
            return
        if session.sessiontype != POLLER:
            if self._caressObject and hasattr(self._caressObject,
                                              'release_system_orb'):
                if CARESSDevice._used_counter:
                    CARESSDevice._used_counter -= 1
                    if not CARESSDevice._used_counter:
                        if self._caressObject.release_system_orb(0) in \
                           (0, CARESS.OK):
                            CARESSDevice._caress_maps.clear()
                            CARESSDevice._caress_initialized = False
                        else:
                            raise NicosError(self, 'Could not release CARESS')
            self._setROParam('cid', 0)
            if self._cache:
                self._cache.invalidate(self, 'cid')
        self._orb = None
        self._initialized = False

    def _read(self):
        if not self.cid:
            raise InvalidValueError(self, 'Connection lost to CARESS')
        if hasattr(self._caressObject, 'read_module'):
            # result = self._caressObject.read_module(0x80000000, self.cid)
            result, state, val = self._caressObject.read_module(0, self.cid)
            if result != CARESS.OK:
                raise CommunicationError(
                    self, 'Could not read the CARESS module: %d' % self.cid)
            if hasattr(val, 'f'):
                return (state, val.f)
            return (
                state,
                val.l,
            )
        else:
            _ = ()
            self.log.debug('read module: %d', self.cid)
            l, result = self._caressObject.read_module_orb(0, self.cid, _)
            self.log.debug('read_module: %d, %r', l, result)
            if l != 0:
                raise CommunicationError(
                    self, 'Could not read the CARESS module: %d' % self.cid)
            if result[0].value() != self.cid:
                raise CommunicationError(
                    self,
                    'Answer from wrong module!: %d %r' % (self.cid, result[0]))
            state = result[1].value()
            if state == OFF_LINE:
                raise NicosError(self, 'Module is off line!')
            if result[2].value() < 1:
                raise InvalidValueError(self, 'No position in data')
            return state, result[4].value()

    def doRead(self, maxage=0):
        try:
            return self._caress_guard(self._read)[1]
        except (InvalidValueError, CommunicationError, NicosError):
            if session.sessiontype == POLLER:
                return None
            raise

    def doStatus(self, maxage=0):
        try:
            state = self._caress_guard(self._read)[0]
            if state == OFF_LINE:
                return status.ERROR, 'device is offline'
            elif state in (ACTIVE, ACTIVE1, COMBO_ACTIVE):
                return status.BUSY, 'moving or in manual mode'
            elif state == DONE:
                return status.OK, 'idle or paused'
            elif state == LOADED:
                return status.OK, 'loaded'
            elif state == NOT_ACTIVE:
                return status.OK, 'device is not active'
            self.log.warning('Unhandled status : %r', state)
            return status.OK, 'idle'
        except (InvalidValueError, CommunicationError, NicosError) as e:
            return status.ERROR, str(e)

    def _caress_guard_nolog(self, function, *args):

        if not self._initialized or not self._caressObject:
            CARESSDevice.doInit(self, self._mode)

#       self._com_lock.aquire()
        try:
            return function(*args)
        except (CORBA.COMM_FAILURE, CORBA.TRANSIENT) as err:
            tries = self.comtries - 1
            while True and tries > 0:
                self.log.warning('Remaining tries: %d', tries)
                session.delay(self.comdelay)
                if isinstance(err, CORBA.TRANSIENT):
                    CARESSDevice.doShutdown(self)
                    CARESSDevice.doInit(self, self._mode)
                session.delay(self.comdelay)
                try:
                    return function(*args)
                except (CORBA.COMM_FAILURE, CORBA.TRANSIENT) as err:
                    tries -= 1
            raise CommunicationError(
                self, 'CARESS error: %s%r: %s' %
                (function.__name__, args, err)) from err
        finally:
            pass
#           self._com_lock.release()

    _caress_guard = _caress_guard_nolog
コード例 #14
0
class DetectorPosSwitcher(DetectorPosSwitcherMixin, SequencerMixin,
                          MappedMoveable):

    hardware_access = False

    attached_devices = {
        'det_z': Attach('Large detector Z axis', Moveable),
        'bs_x': Attach('Large detector beamstop X axis', Moveable),
        'bs_y': Attach('Large detector beamstop Y axis', Moveable),
    }

    parameters = {
        'presets':
        Param('Presets that determine the mappings',
              type=dictof(str, dictof(str, dictwith(x=float, y=float,
                                                    z=float))),
              mandatory=True),
        'offsets':
        Param(
            'Offsets to correct TOF chopper-detector length '
            'for the errors in the det_z axis value',
            type=dictof(float, float),
            mandatory=True),
        'mapkey':
        Param('Last selector position for mapping',
              type=str,
              settable=True,
              internal=True),
        'beamstopsettlepos':
        Param('Settling position for beamstop y axis',
              type=none_or(float),
              settable=True,
              default=400),
    }

    parameter_overrides = {
        'mapping': Override(mandatory=False, settable=True, internal=True),
        'fallback': Override(userparam=False, type=str, mandatory=True),
    }

    def doInit(self, mode):
        # check that an offset is defined for each z distance
        for _selpos, selpresets in self.presets.items():
            for _pname, preset in selpresets.items():
                if preset['z'] not in self.offsets:
                    raise ConfigurationError(
                        self, 'no detector offset found in configuration '
                        'for detector distance of %.2f m' % preset['z'])
        MappedMoveable.doInit(self, mode)
        # apply mapping of last selector pos in case it changed
        if mode == MASTER:
            self._updateMapping(self.mapkey)

    def _updateMapping(self, selpos):
        self.log.debug('updating the detector mapping for selector '
                       'setting %s' % selpos)
        try:
            pos = self.presets.get(selpos, {})
            new_mapping = {
                k: [v['x'], v['y'], v['z']]
                for (k, v) in pos.items()
            }
            self.mapping = new_mapping
            self.mapkey = selpos
            self.valuetype = oneof_detector(*sorted(new_mapping, key=num_sort))
            if self._cache:
                self._cache.invalidate(self, 'value')
                self._cache.invalidate(self, 'status')
        except Exception:
            self.log.warning('could not update detector mapping', exc=1)

    def _startRaw(self, pos):
        if self._seq_is_running():
            if self._mode == SIMULATION:
                self._seq_thread.join()
                self._seq_thread = None
            else:
                raise MoveError(
                    self, 'Cannot start device, sequence is still '
                    'running (at %s)!' % self._seq_status[1])

        det_z = self._attached_det_z
        seq = []
        seq.append(SeqDev(self._attached_bs_y, pos[1], stoppable=True))
        seq.append(SeqDev(self._attached_bs_x, pos[0], stoppable=True))
        seq.append(SeqDev(det_z, pos[2], stoppable=True))

        # if z has to move, reposition beamstop y afterwards by going to
        # some other value (damping vibrations) and back
        if self.beamstopsettlepos is not None and \
           abs(det_z.read(0) - pos[2]) > det_z.precision:
            seq.append(
                SeqDev(self._attached_bs_y,
                       self.beamstopsettlepos,
                       stoppable=True))
            seq.append(SeqSleep(30))
            seq.append(SeqDev(self._attached_bs_y, pos[1], stoppable=True))

        self._startSequence(seq)

    def _readRaw(self, maxage=0):
        return {
            n: (d.read(maxage), getattr(d, 'precision', 0))
            for (n, d) in self._adevs.items()
        }

    def _mapReadValue(self, pos):
        def eq(posname, val):
            return abs(pos[posname][0] - val) <= pos[posname][1]

        for name, values in self.mapping.items():
            if eq('det_z', values[2]) and eq('bs_x', values[0]) and \
               eq('bs_y', values[1]):
                return name
        return self.fallback

    def doStatus(self, maxage=0):
        seq_status = SequencerMixin.doStatus(self, maxage)
        if seq_status[0] not in (status.OK, status.WARN):
            return seq_status
        return MappedMoveable.doStatus(self, maxage)

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

    def doStop(self):
        multiStop(self._adevs)
コード例 #15
0
ファイル: monochromator.py プロジェクト: umithardal/nicos
class Monochromator(HasLimits, Moveable):
    """Monochromator device of antares.

    Used to tune the double monochromator to a wavelength between 1.4 and 6.0
    Angstroms.  Can be moved to None to get a white beam.

    Experimental version.
    CHECK THE FORMULAS!
    """
    attached_devices = {
        'phi1':        Attach('monochromator rotation 1', Moveable),
        'phi2':        Attach('monochromator rotation 2', Moveable),
        'translation': Attach('monochromator translation', Moveable),
        'inout':       Attach('monochromator inout device', Moveable),
    }

    parameters = {
        'dvalue1':    Param('Lattice constant of Mono1', type=float,
                            settable=True, mandatory=True),
        'dvalue2':    Param('Lattice constant of Mono2', type=float,
                            settable=True, mandatory=True),
        'distance':   Param('Parallactic distance of monos', type=float,
                            settable=True, mandatory=True),
        'tolphi':     Param('Max deviation of phi1 or phi2 from calculated '
                            'value',
                            type=float, settable=True, default=0.01),
        'toltrans':   Param('Max deviation of translation from calculated '
                            'value',
                            type=float, settable=True, default=0.01),
        'parkingpos': Param('Monochromator parking position',
                            type=dictof(oneof(*attached_devices.keys()),
                                        anytype),
                            mandatory=True),
    }

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

    valuetype = none_or(floatrange(1.4, 6.0))

    hardware_access = False

    @lazy_property
    def devices(self):
        return list(self._adevs[i] for i in 'inout phi1 phi2 translation'.split())

    def _from_lambda(self, lam):
        """Return 3 values used for phi1, phi2 and translation."""
        phi1 = degrees(asin(lam / float(2 * self.dvalue1)))
        phi2 = degrees(asin(lam / float(2 * self.dvalue2)))
        trans = self.distance / tan(2*radians(phi1))
        return phi1, phi2, trans

    def _to_lambda(self, phi1, phi2, trans):
        """Calculate lambda from phi1, phi2, trans. May raise a PositionError.
        Not necessarily all arguments are used.

        Next iteration could evaluate all 3 args and return an average value...
        """
        try:
            return abs(2 * self.dvalue1 * sin(radians(phi1)))
        except Exception:
            raise PositionError('can not determine lambda!')

    def _moveToParkingpos(self):
        for dev, target in self.parkingpos.items():
            self._adevs[dev].start(target)

    def doStart(self, target):
        if target is None:
            self.log.debug('None given; Moving to parking position')
            self._moveToParkingpos()
            return

        if self.devices[0].read() == 'out':
            self.log.debug('moving monochromator into beam')

        for d, v in zip(self.devices, ['in'] + list(self._from_lambda(target))):
            self.log.debug('sending %s to %r', d.name, v)
            d.start(v)

    def doStatus(self, maxage=0):
        st = multiStatus(listitems(self._adevs), maxage)
        if st[0] == status.OK:
            # check position
            try:
                self.doRead(maxage)
            except PositionError as e:
                return status.NOTREACHED, str(e)
        return st

    def doRead(self, maxage=0):
        pos = [d.read(maxage) for d in self.devices]

        # Are we in the beam?
        if pos[0] == 'out':
            return None

        # calculate lambda from phi1 and then check the other positions
        # for consistency...
        lam = self._to_lambda(*pos[1:])
        self.log.debug('lambda seems to be %.4f Angstroms', lam)
        compare_pos = self._from_lambda(lam)
        tol = [self.tolphi, self.tolphi, self.toltrans]
        for d, p, t, c in zip(self.devices[1:], pos[1:], tol, compare_pos):
            self.log.debug('%s is at %s and should be at %s for %.4f '
                           'Angstroms', d, d.format(p), d.format(c), lam)
            if abs(p-c) > t:
                raise PositionError('%s is too far away for %.4f Angstroms' %
                                    (d, lam))
        return lam
コード例 #16
0
ファイル: epics_devices.py プロジェクト: ess-dmsc/nicos
class EpicsDevice(DeviceMixinBase):
    hardware_access = True
    valuetype = anytype

    parameters = {
        'epicstimeout':
        Param('Timeout for getting EPICS PVs',
              type=none_or(floatrange(0.1, 60)),
              userparam=False,
              mandatory=False,
              default=1.0),
    }

    parameter_overrides = {
        # Hide the parameters that are irrelevant when using monitors.
        'maxage': Override(userparam=False, settable=False),
        'pollinterval': Override(userparam=False, settable=False),
    }

    # This will store PV objects for each PV param.
    _pvs = {}
    _epics_wrapper = None
    _record_fields = {}

    def doPreinit(self, mode):
        self._epics_wrapper = PvaWrapper()

        # Don't create PVs in simulation mode
        self._pvs = {}

        if mode != SIMULATION:
            for pvparam in self._get_pv_parameters():
                # Retrieve the actual PV name
                pvname = self._get_pv_name(pvparam)
                if not pvname:
                    raise ConfigurationError(
                        self, 'PV for parameter %s was '
                        'not found!' % pvparam)
                # Check pv exists - throws if cannot connect
                self._epics_wrapper.connect_pv(pvname, self.epicstimeout)
                self._pvs[pvparam] = pvname
            self._register_pv_callbacks()
        else:
            for pvparam in self._get_pv_parameters():
                self._pvs[pvparam] = HardwareStub(self)

    def _register_pv_callbacks(self):
        pass

    def _get_pv_parameters(self):
        return set(self._record_fields.keys())

    def _get_pv_name(self, pvparam):
        if hasattr(self, pvparam):
            return getattr(self, pvparam)
        stem = getattr(self, 'readpv')
        return '.'.join([stem, self._record_fields.get(pvparam, '')])

    def doStatus(self, maxage=0):
        # For most devices we only care about the status of the read PV
        pvname = self._get_pv_name('readpv')
        severity, msg = self._epics_wrapper.get_alarm_status(
            pvname, timeout=self.epicstimeout)
        if severity in [status.ERROR, status.WARN]:
            return severity, msg
        return status.OK, msg

    def _setMode(self, mode):
        # remove the PVs on entering simulation mode, to prevent
        # accidental access to the hardware
        if mode == SIMULATION:
            for key in self._pvs:
                self._pvs[key] = HardwareStub(self)

    def _get_limits(self, pvparam):
        return self._epics_wrapper.get_limits(self._get_pv_name(pvparam),
                                              timeout=self.epicstimeout)

    def _get_pv(self, pvparam, as_string=False):
        cache_key = self._get_cache_relation(pvparam) or pvparam
        if cache_key in self._values:
            return self._values[cache_key]
        return self._epics_wrapper.get_pv_value(self._pvs[pvparam],
                                                timeout=self.epicstimeout,
                                                as_string=as_string)

    def _put_pv(self, pvparam, value, wait=False):
        self._epics_wrapper.put_pv_value(self._pvs[pvparam],
                                         value,
                                         wait=wait,
                                         timeout=self.epicstimeout)

    def _put_pv_blocking(self, pvparam, value, update_rate=0.1, timeout=60):
        self._epics_wrapper.put_pv_value_blocking(self._pvs[pvparam], value,
                                                  update_rate, timeout)
コード例 #17
0
ファイル: epics_devices.py プロジェクト: ess-dmsc/nicos
class EpicsMoveable(EpicsMonitorMixin, EpicsDevice, Moveable):
    """
    Handles EPICS devices which can set and read a value.
    """
    parameters = {
        'readpv':
        Param('PV for reading device value',
              type=pvname,
              mandatory=True,
              userparam=False),
        'writepv':
        Param('PV for writing device target',
              type=pvname,
              mandatory=True,
              userparam=False),
        'targetpv':
        Param('Optional target readback PV.',
              type=none_or(pvname),
              mandatory=False,
              userparam=False)
    }

    parameter_overrides = {
        # Units are set by EPICS, so cannot be changed
        'unit': Override(mandatory=False, settable=False),
        'target': Override(volatile=True),
    }

    _cache_relations = {
        'readpv': 'value',
        'writepv': 'target',
    }

    def _get_pv_parameters(self):
        if self.targetpv:
            return {'readpv', 'writepv', 'targetpv'}

        return {'readpv', 'writepv'}

    def doInit(self, mode):
        if mode == SIMULATION:
            return

        in_type = self._epics_wrapper.get_pv_type(self._pvs['readpv'],
                                                  self.epicstimeout)
        out_type = self._epics_wrapper.get_pv_type(self._pvs['writepv'],
                                                   self.epicstimeout)
        if in_type != self.valuetype:
            raise ConfigurationError(
                self, 'Input PV %r does not have the '
                'correct data type' % self.readpv)
        if out_type != self.valuetype:
            raise ConfigurationError(
                self, 'Output PV %r does not have the '
                'correct data type' % self.writepv)
        if self.targetpv:
            target_type = self._epics_wrapper.get_pv_type(
                self._pvs['targetpv'], self.epicstimeout)
            if target_type != self.valuetype:
                raise ConfigurationError(
                    self, 'Target PV %r does not have the '
                    'correct data type' % self.targetpv)

    def doReadTarget(self):
        if self.targetpv:
            return self._get_pv('targetpv')
        else:
            return self._get_pv('writepv')

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

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

    def doStop(self):
        self.doStart(self.doRead())
コード例 #18
0
ファイル: switcher.py プロジェクト: ess-dmsc/nicos
class Switcher(MappedMoveable):
    """The switcher is a device that maps switch states onto discrete values of
    a continuously moveable device.

    This is useful if you have for example a motor that only every moves to
    certain discrete positions, e.g. a monochromator changer.  Then you can
    control it using ::

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

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

        move(changer, 14.55)
        move(changer, 51.3)

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

    attached_devices = {
        'moveable': Attach('The continuous device which is controlled',
                           Moveable),
    }

    parameters = {
        'precision':    Param('Precision for comparison', mandatory=True),
        'blockingmove': Param('Should we wait for the move to finish?',
                              type=bool, default=True, settable=True),
    }

    parameter_overrides = {
        'fallback':  Override(userparam=False, type=none_or(str),
                              mandatory=False),
    }

    hardware_access = False

    def _startRaw(self, target):
        """Initiate movement of the moveable to the translated raw value."""
        self._attached_moveable.start(target)
        if self.blockingmove:
            self._attached_moveable.wait()

    def _readRaw(self, maxage=0):
        """Return raw position value of the moveable."""
        return self._attached_moveable.read(maxage)

    def _mapReadValue(self, pos):
        """Override default inverse mapping to allow a
        deviation <= precision.
        """
        prec = self.precision
        for name, value in self.mapping.items():
            if prec:
                if abs(pos - value) <= prec:
                    return name
            elif pos == value:
                return name
        if self.fallback is not None:
            return self.fallback
        if self.relax_mapping:
            return self._attached_moveable.format(pos, True)
        raise PositionError(self, 'unknown position of %s: %s' %
                            (self._attached_moveable,
                             self._attached_moveable.format(pos, True))
                            )

    def doStatus(self, maxage=0):
        # if the underlying device is moving or in error state,
        # reflect its status
        move_status = self._attached_moveable.status(maxage)
        if move_status[0] not in (status.OK, status.WARN):
            return move_status
        # otherwise, we have to check if we are at a known position,
        # and otherwise return an error status
        try:
            r = self.read(maxage)
            if r not in self.mapping:
                if self.fallback:
                    return (status.UNKNOWN, 'unconfigured position of %s, '
                            'using fallback' % self._attached_moveable)
                return (status.NOTREACHED, 'unconfigured position of %s or '
                        'still moving' % self._attached_moveable)
        except PositionError as e:
            return status.NOTREACHED, str(e)
        return status.OK, ''

    def doReset(self):
        self._attached_moveable.reset()

    def doStop(self):
        self._attached_moveable.stop()
コード例 #19
0
ファイル: sequence.py プロジェクト: ess-dmsc/nicos
class LockedDevice(BaseSequencer):
    """A "locked" device, where each movement of the underlying device must be
    surrounded by moving another device (the "lock") to some value and back
    after the movement of the main device.

    The "lock" is moved to the `unlockvalue` before moving the main device.
    After the main device has moved successfully, the lock is moved either back
    to its previous value, or if `lockvalue` is not ``None``, to the
    `lockvalue`.

    If an error occurs while moving the main device, the lock is not moved back
    to "locked" position.  The error must be resolved first to restore
    integrity of the device arrangement.
    """

    attached_devices = {
        'device': Attach('Moveable device which is protected by the lock',
                         Moveable),
        'lock': Attach('The lock, protecting the device', Moveable),
    }

    parameters = {
        'unlockvalue': Param('The value for the lock to unlock the moveable',
                             mandatory=True, type=anytype),
        'keepfixed': Param('Whether to fix lock device if not moving',
                           default=False, type=bool),
        'lockvalue': Param('Value for the lock after movement, default None'
                           ' goes to previous value',
                           default=None, type=none_or(anytype)),
    }

    def _generateSequence(self, target):
        device = self._attached_device
        lock = self._attached_lock
        seq = []

        if self.keepfixed:
            # release lock first
            seq.append(SeqMethod(lock, 'release'))

        # now move lock to unlockvalue
        seq.append(SeqDev(lock, self.unlockvalue))

        if self.keepfixed:
            # fix lock again
            seq.append(SeqMethod(lock, 'fix', 'fixed unless %s moves' % self))

        seq.append(SeqDev(device, target))

        if self.keepfixed:
            # release lock again
            seq.append(SeqMethod(lock, 'release'))

        # now move lock to lockvalue
        seq.append(SeqDev(lock,
                          self.lockvalue or lock.target or lock.doRead(0)))

        if self.keepfixed:
            # fix lock again
            seq.append(SeqMethod(lock, 'fix', 'fixed unless %s moves' % self))

        return seq

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

    def doIsAllowed(self, target):
        return self._attached_device.isAllowed(target)
コード例 #20
0
class NiagShutter(HasTimeout, MappedMoveable):
    """ Shutter device for the NIAG beam lines.

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

    An additional digital input indicates whether the shutter is enabled

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

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

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

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

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

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

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

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

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

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

    # only allow to start if the enable bit is active

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

    def doReset(self):
        multiReset(self._adevs)
コード例 #21
0
ファイル: detector.py プロジェクト: umithardal/nicos
class Detector(MeasureSequencer):
    """SPODI specific detector.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def arrayInfo(self):
        return self._arraydesc,

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

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

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

    def _cleanUp(self):
        if self._seq_was_stopped:
            self._seq_was_stopped = False
            self._set_seq_status(status.OK, 'idle')
コード例 #22
0
# Module authors:
#   Georg Brandl <*****@*****.**>
#
# *****************************************************************************
"""KWS-3 flexible temperature controller."""

from __future__ import absolute_import, division, print_function

from nicos.core import Param, dictof, none_or, oneof, tangodev, tupleof
from nicos.devices.generic.paramdev import ParamDevice
from nicos.devices.tango import TemperatureController
from nicos.pycompat import iteritems

# out-dev, (in-dev, min-out, max-out, init-pid) if software-regulated
entry = tupleof(tangodev,
                none_or(tupleof(tangodev, float, float, float, float, float)))


class FlexRegulator(TemperatureController):
    """Temperature controller with varying setup for software and hardware
    regulation."""

    parameters = {
        'dummy':
        Param('Dummy input device', type=tangodev, mandatory=True),
        'configs':
        Param('Possible setups', type=dictof(str, entry), mandatory=True),
        'config':
        Param('Current configuration', type=str, volatile=True, settable=True),
    }
コード例 #23
0
ファイル: __init__.py プロジェクト: umithardal/nicos
class PushVersionInfo(BaseCacheClient):
    r"""Pushes the version info to a storage service.

    The version infos are pushed as a JSON array.  The content is not
    defined, but normally the following keys can be available::

      instrument
      host
      nicos_root
      nicos_version
      custom_version
      service [opt.]
      base_version  \  these two are only posted
      base_host     /  from this service

    Setting of the token can be done:
        a) with the nicos-keystore tool:

          `nicos-keystore add <tokenid>`

        b) with the keyring tool from th keyring package (this may require
           additional dependencies to be installed):

           `keyring -b keyrings.alt.file.EncryptedKeyring set nicos <tokenid>`

    """

    parameters = {
        'update_uri':
        Param(
            'URI to send version information to, or None to '
            'disable. The access token and Version info is '
            'directly appended to the URI, encoded as a '
            'query parameter.',
            type=none_or(str),
            mandatory=True,
            userparam=False),
        'tokenid':
        Param('Id used in the keystore for the update token',
              type=str,
              default='frm2jenkins'),
        'infokey':
        Param('URI parameter key for the info dict', type=str, mandatory=True),
    }

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

    def doInit(self, mode):
        if self.update_uri is None:
            self.log.warning('No update URI configured, updates will not be '
                             'sent')
        if not nicoskeystore.getCredential(self.tokenid):
            self.log.warning('No token %s found in keystore, updates will not '
                             'be sent' % self.tokenid)
        BaseCacheClient.doInit(self, mode)

    def _connect_action(self):
        BaseCacheClient._connect_action(self)
        self.sendUpdate()

    def _handle_msg(self, time, ttlop, ttl, tsop, key, op, value):
        if not key.startswith(self.prefix):
            return
        if op in [OP_TELL, OP_TELLOLD]:
            self.processSysinfo(key, value, time, tell=False)

    def processSysinfo(self, key, value, time, tell):
        if not tell:
            infodict = dict(cache_load(value))
            if time:
                dt = datetime.fromtimestamp(float(time))
                infodict['time'] = dt.strftime(TIME_FMT)
            self.sendUpdate(infodict)

    def getDaemonInfo(self):
        instrument = config.instrument
        base_host = getfqdn()

        # collect data to send, data should be strings!
        infodict = dict(
            time=datetime.now().strftime(TIME_FMT),
            instrument=instrument,
            base_host=base_host,
            base_version=nicos_version,
            nicos_root=config.nicos_root,
            custom_path=config.setup_package_path,
            custom_version=get_custom_version(),
        )
        return infodict

    def sendUpdate(self, infodict=None, time=None):
        # make json
        if not self.update_uri:
            return

        token = nicoskeystore.getCredential(self.tokenid)
        if not token:
            return
        if infodict is None:
            infodict = self.getDaemonInfo()

        paramdict = {self.infokey: json.dumps(infodict), 'token': token}
        append = ''
        if '?' not in self.update_uri:
            append = '?'
        elif not self.update_uri[-1] == '&':
            append = '&'

        update_string = self.update_uri + append
        update_string += urllib.parse.urlencode(paramdict)
        try:
            urllib.request.urlopen(update_string)
            self.log.debug('update sent successfully for %s',
                           infodict.get('service', 'base'))
        except Exception:
            self.log.debug('cannot send version information! (tried:\n%r\n)',
                           update_string,
                           exc=True)
コード例 #24
0
class EpicsMoveable(EpicsDevice, Moveable):
    """
    Handles EPICS devices which can set and read a value.
    """

    parameters = {
        'readpv':
        Param('PV for reading device value',
              type=pvname,
              mandatory=True,
              userparam=False),
        'writepv':
        Param('PV for writing device target',
              type=pvname,
              mandatory=True,
              userparam=False),
        'targetpv':
        Param('Optional target readback PV.',
              type=none_or(pvname),
              mandatory=False,
              userparam=False)
    }

    parameter_overrides = {
        'unit': Override(mandatory=False),
        'target': Override(volatile=True),
    }

    def _get_pv_parameters(self):
        """
        Overriden from EpicsDevice. If the targetpv parameter is specified,
        the PV-object should be created accordingly. Otherwise, just return
        the mandatory pv_parameters.
        """
        if self.targetpv:
            return {'readpv', 'writepv', 'targetpv'}

        return {'readpv', 'writepv'}

    def doInit(self, mode):
        if mode == SIMULATION:
            return
        intype = FTYPE_TO_VALUETYPE.get(self._pvs['readpv'].ftype, anytype)
        outtype = FTYPE_TO_VALUETYPE.get(self._pvs['writepv'].ftype, anytype)
        if intype is not anytype and intype != self.valuetype:
            raise ConfigurationError(
                self, 'Input PV %r does not have the '
                'correct data type' % self.readpv)
        if outtype is not anytype and outtype != self.valuetype:
            raise ConfigurationError(
                self, 'Output PV %r does not have the '
                'correct data type' % self.writepv)

        if self.targetpv:
            target_type = FTYPE_TO_VALUETYPE.get(self._pvs['targetpv'].ftype,
                                                 anytype)

            if target_type is not anytype and target_type != self.valuetype:
                raise ConfigurationError(
                    self, 'Target PV %r does not have the '
                    'correct data type' % self.targetpv)

    def doReadUnit(self):
        return self._get_pvctrl('readpv', 'units', '')

    def doReadTarget(self):
        """
        IOCs commonly provide a read-back of the set-point according to the
        device (targetpv). When this read-back is not present then the writepv
        should be read instead.
        """
        if self.targetpv:
            return self._get_pv('targetpv')

        return self._get_pv('writepv')

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

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

    def doStop(self):
        self.doStart(self.doRead())
コード例 #25
0
class GenericLimaCCD(PyTangoDevice, ImageChannelMixin, PassiveChannel):
    """
    This device class can be used together with the LIMA TANGO server
    to control all common parameters and actions of the supported cameras.

    For hardware specific additions, have a look at the particular class.
    """
    parameters = {
        'hwdevice':         Param('Hardware specific tango device', type=tangodev,
                                  default='', preinit=True),
        'imagewidth':       Param('Image width',
                                  type=int, volatile=True, category='general'),
        'imageheight':      Param('Image height',
                                  type=int, volatile=True, category='general'),
        'roi':              Param('Region of interest',
                                  type=tupleof(int, int, int, int), settable=True,
                                  default=(0, 0, 0, 0), volatile=True,
                                  category='general'),
        'bin':              Param('Binning (x,y)',
                                  type=tupleof(int, int), settable=True,
                                  default=(1, 1), volatile=True, category='general'),
        'flip':             Param('Flipping (x,y)',
                                  type=tupleof(bool, bool), settable=True,
                                  default=(False, False), category='general'),
        'rotation':         Param('Rotation',
                                  type=oneof(0, 90, 180, 270), settable=True,
                                  default=0, category='general'),
        'expotime':         Param('Exposure time',
                                  type=float, settable=False, volatile=True,
                                  category='general'),
        'cameramodel':      Param('Camera type/model',
                                  type=str, settable=False,
                                  volatile=True,  # Necessary?
                                  category='general'),
        'shutteropentime':  Param('Shutter open time',
                                  type=none_or(float), settable=True, default=0,
                                  volatile=False, category='general'),
        'shutterclosetime': Param('Shutter open time',
                                  type=none_or(float), settable=True, default=0,
                                  volatile=False, category='general'),
        'shuttermode':      Param('Shutter mode',
                                  type=none_or(oneof('always_open',
                                                     'always_closed',
                                                     'auto')),
                                  settable=True, default='auto', volatile=True,
                                  category='general'),
        '_starttime':   Param('Cached counting start time',
                              type=float, default=0, settable=False,
                              internal=True),
        # some cached values are necessary as hw params are volatile on request
        '_curexpotime': Param('Cached exposure time for current acquisition',
                              type=float,
                              default=0,
                              settable=False,
                              internal=True),
        '_curshutteropentime':  Param('Cached shutter open time for current'
                                      ' acquisition',
                                      type=float,
                                      default=0,
                                      settable=False,
                                      internal=True),
        '_curshutterclosetime': Param('Cached shutter close time for current'
                                      ' acquisition',
                                      type=float,
                                      default=0,
                                      settable=False,
                                      internal=True),

    }

    _hwDev = None

    def doPreinit(self, mode):
        PyTangoDevice.doPreinit(self, mode)

        # Create hw specific device if given
        if self.hwdevice:
            self._hwDev = HwDevice(self.name + '._hwDev',
                                   tangodevice=self.hwdevice, lowlevel=True)

        # optional components
        self._shutter = None

        if mode != SIMULATION:
            self._initOptionalComponents()

            if self._dev.camera_model.startswith('SIMCAM'):
                self.log.warning('Using lima simulation camera! If that\'s not'
                                 ' intended, please check the cables and '
                                 'restart the camera and the lima server')

            # set some dummy roi to avoid strange lima rotation behaviour
            # (not at 0, 0 to avoid possible problems with strides)
            self._writeRawRoi((8, 8, 8, 8))
            # ensure NO rotation
            self._dev.image_rotation = 'NONE'
            # set full detector size as roi
            self._writeRawRoi((0, 0, 0, 0))
            # cache full detector size
            self._width_height = (self.imagewidth, self.imageheight)
        else:
            # some dummy shape for simulation
            self._width_height = (2048, 1536)

    def doInit(self, mode):
        # Determine image type
        self.arraydesc = ArrayDesc('data', self._width_height[::-1],
                                   self._getImageType())

    def doShutdown(self):
        if self._hwDev:
            self._hwDev.shutdown()

    def doInfo(self):
        for p in ('imagewidth', 'imageheight', 'roi', 'bin', 'expotime',
                  'cameramodel', 'shutteropentime', 'shutterclosetime',
                  'shuttermode'):
            self._pollParam(p)
        return []

    def valueInfo(self):
        # no readresult by default
        return ()

    def doStart(self):
        # ignore prep in time calc
        self._dev.prepareAcq()

        self._setROParam('_starttime', time.time())
        self._setROParam('_curexpotime', self.expotime)

        if self._shutter is not None:
            self._setROParam('_curshutteropentime', self.shutteropentime)
            self._setROParam('_curshutterclosetime', self.shutterclosetime)

        self._dev.startAcq()

    def doFinish(self):
        self._dev.stopAcq()

    def doStop(self):
        self.doFinish()

    def doStatus(self, maxage=0):
        statusMap = {
            'Ready': status.OK,
            'Running': status.BUSY,
            'Fault': status.ERROR,
        }

        limaStatus = self._dev.acq_status
        nicosStatus = statusMap.get(limaStatus, status.UNKNOWN)

        if nicosStatus == status.BUSY:
            deltaTime = time.time() - self._starttime

            if self._shutter and deltaTime <= self._curshutteropentime:
                limaStatus += ' (Opening shutter)'
            elif deltaTime <= (self._curexpotime):
                remaining = self._curexpotime - deltaTime
                limaStatus += ' (Exposing; Remaining: %.2f s)' % remaining
            elif self._shutter and deltaTime <= (self._curshutteropentime +
                                                 self._curexpotime +
                                                 self._curshutterclosetime):
                limaStatus += ' (Closing shutter)'
            else:
                limaStatus += ' (Readout)'

        return (nicosStatus, limaStatus)

    def doReadImagewidth(self):
        return self._dev.image_width

    def doReadImageheight(self):
        return self._dev.image_height

    def doReadRoi(self):
        rawRoi = self._readRawRoi()
        return self._convRoiFromLima(rawRoi, self.rotation, self.flip)

    def doWriteRoi(self, value):
        value = self._convRoiToLima(value, self.rotation, self.flip)
        self._writeRawRoi(value)

    def doReadBin(self):
        return tuple(self._dev.image_bin.tolist())

    def doWriteBin(self, value):
        self._dev.image_bin = value

    def doWriteFlip(self, value):
        roi = self.doReadRoi()
        roi = self._convRoiToLima(roi, self.rotation, value)
        self._writeRawRoi(roi)

    def doWriteRotation(self, value):
        roi = self.doReadRoi()
        roi = self._convRoiToLima(roi, value, self.flip)
        self._writeRawRoi(roi)

    def doReadExpotime(self):
        return self._dev.acq_expo_time

    def doReadCameramodel(self):
        camType = self._dev.camera_type
        camModel = self._dev.camera_model

        return '%s (%s)' % (camType, camModel)

    def doReadShutteropentime(self):
        if self._shutter:
            return self._shutter.doReadShutteropentime()
        return None  # will be overwritten

    def doWriteShutteropentime(self, value):
        if self._shutter:
            return self._shutter.doWriteShutteropentime(value)
        raise HardwareError('Not supported')

    def doReadShutterclosetime(self):
        if self._shutter:
            return self._shutter.doReadShutterclosetime()
        return None

    def doWriteShutterclosetime(self, value):
        if self._shutter:
            return self._shutter.doWriteShutterclosetime(value)
        raise HardwareError('Not supported')

    def doReadShuttermode(self):
        if self._shutter:
            return self._shutter.doReadShuttermode()
        return None

    def doWriteShuttermode(self, value):
        if self._shutter:
            return self._shutter.doWriteShuttermode(value)
        raise HardwareError('Not supported')

    def doReadArray(self, quality):
        if quality == INTERRUPTED:
            return None

        response = self._dev.readImage(0)

        img_data_str = response[1]  # response is a tuple (type name, data)

        dt = numpy.dtype(self._getImageType())
        dt = dt.newbyteorder('<')

        img_data = numpy.frombuffer(img_data_str, dt, offset=64)
        img_data = numpy.reshape(img_data, (self.imageheight, self.imagewidth))
        img_data = numpy.rot90(img_data, self.rotation // 90)
        if self.flip[0]:
            img_data = numpy.fliplr(img_data)
        if self.flip[1]:
            img_data = numpy.flipud(img_data)

        return img_data

    def _initOptionalComponents(self):
        try:
            self._shutter = LimaShutter(self._dev, self._hwDev)
        except NicosError:
            pass

    def _getImageType(self):
        if self._mode == SIMULATION:
            return numpy.uint32

        imageType = self._dev.image_type

        mapping = {
            'Bpp8': numpy.uint8,
            'Bpp8S': numpy.int8,
            'Bpp16': numpy.uint16,
            'Bpp16S': numpy.int16,
            'Bpp32': numpy.uint32,
            'Bpp32S': numpy.int32,
        }

        return mapping.get(imageType, numpy.uint32)

    def _convRoiToLima(self, roi, rotation, flip):
        if roi == (0, 0, 0, 0):
            return (0, 0) + self._width_height

        roi = self._flipRoi(roi, rotation, flip)
        roi = self._unrotateRoi(roi, rotation)

        return roi

    def _convRoiFromLima(self, roi, rotation, flip):
        if roi == ((0, 0) + self._width_height):
            return (0, 0, 0, 0)

        roi = self._rotateRoi(roi, rotation)
        roi = self._flipRoi(roi, rotation, flip)

        return roi

    def _unrotateRoi(self, roi, rotation):
        self.log.debug('*un*rotate roi %r by %r', roi, rotation)
        w, h = self._width_height[0] - 1, self._width_height[1] - 1

        # transformation matrix for no rotation
        transmat = numpy.matrix([
            [1, 0, 0],
            [0, 1, 0],
            [0, 0, 1]
        ])

        if rotation == 90:
            transmat = numpy.matrix([
                [0, -1, w],
                [1,  0, 0],
                [0,  0, 1]
            ])
        elif rotation == 180:
            transmat = numpy.matrix([
                [-1,  0, w],
                [ 0, -1, h],
                [ 0,  0, 1]
            ])
        elif rotation == 270:
            transmat = numpy.matrix([
                [ 0, 1, 0],
                [-1, 0, h],
                [ 0, 0, 1]])

        result = self._transformRoi(roi, transmat)
        self.log.debug('\t=> %r', result)
        return result

    def _rotateRoi(self, roi, rotation):
        self.log.debug('rotate roi %r from %r', roi, rotation)
        w, h = self._width_height[0] - 1, self._width_height[1] - 1

        # transformation matrix for no rotation
        transmat = numpy.matrix([
            [1, 0, 0],
            [0, 1, 0],
            [0, 0, 1]
        ])

        if rotation == 90:
            transmat = numpy.matrix([
                [ 0, 1, 0],
                [-1, 0, w],
                [ 0, 0, 1]
            ])
        elif rotation == 180:
            transmat = numpy.matrix([
                [-1,  0, w],
                [ 0, -1, h],
                [ 0,  0, 1]
            ])
        elif rotation == 270:
            transmat = numpy.matrix([
                [0, -1, h],
                [1,  0, 0],
                [0,  0, 1]])

        result = self._transformRoi(roi, transmat)
        self.log.debug('\t=> %r', result)
        return result

    def _flipRoi(self, roi, rotation, flip):
        self.log.debug('flip roi %r by %r', roi, flip)
        w, h = self._width_height

        if rotation in [90, 270]:
            w, h = h, w

        x_bot_left, y_bot_left, rw, rh = roi
        x_top_right = x_bot_left + rw - 1
        y_top_right = y_bot_left + rh - 1

        if flip[0]:
            x_bot_left = (w - 1) - x_bot_left
            x_top_right = (w - 1) - x_top_right
        if flip[1]:
            y_bot_left = (h - 1) - y_bot_left
            y_top_right = (h - 1) - y_top_right

        x_bot_left = min(x_bot_left, x_top_right)
        y_bot_left = min(y_bot_left, y_top_right)

        result = (x_bot_left, y_bot_left, rw, rh)
        self.log.debug('\t=> %r', result)
        return result

    def _transformRoi(self, roi, transmat):
        x, y, roi_width, roi_height = roi

        topleft = numpy.matrix([
            [x],
            [y],
            [1]
        ])

        bottomright = numpy.matrix([
            [x+roi_width - 1],
            [y+roi_height - 1],
            [1]
        ])

        topleft = transmat * topleft
        bottomright = transmat * bottomright

        x_max = max(topleft.item(0), bottomright.item(0))
        x_min = min(topleft.item(0), bottomright.item(0))
        y_max = max(topleft.item(1), bottomright.item(1))
        y_min = min(topleft.item(1), bottomright.item(1))

        return (x_min, y_min, x_max - x_min + 1 , y_max - y_min + 1)

    def _readRawRoi(self):
        return tuple(self._dev.image_roi.tolist())

    def _writeRawRoi(self, value):
        self._dev.image_roi = value
コード例 #26
0
class Regulator(Moveable):
    """Device to regulate a attached moveable by monitoring an attached
    readable. Will be used for monitoring and regulating the amplitude of the
    frequency generators for the resonating circuits.

    The regulation is done, assuming that the movable is (approximitely)
    directly proportional to the sensor."""

    attached_devices = {
        'moveable': Attach('Device to regulate', Moveable),
        'sensor': Attach('Device to evaluate', Readable),
    }

    parameters = {
        'stepfactor':
        Param('Factor of regulation steps',
              type=float,
              settable=True,
              mandatory=False,
              default=0.5),
        'minstep':
        Param('Minimum stepsize if adjusting',
              type=float,
              settable=True,
              mandatory=False,
              default=0.001),
        'deadbandwidth':
        Param('Width of the dead band',
              type=float,
              settable=True,
              mandatory=False,
              default=0.05),
        'loopdelay':
        Param('Sleep time when waiting',
              type=float,
              unit='s',
              default=1.0,
              settable=True),
        'maxstep':
        Param('Maximum step size',
              type=none_or(float),
              settable=True,
              mandatory=False,
              default=None),
        'curstatus':
        Param('Store the current device status',
              internal=True,
              type=tupleof(int, str),
              settable=True),
    }

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

    hardware_access = False

    def doInit(self, mode):
        self._regulation_thread = None
        self._stop_request = False
        self.valuetype = self._attached_moveable.valuetype

    def doShutdown(self):
        self.doStop()

    def doStart(self, target):
        self._stop_request = False
        if self._regulation_thread is None and session.sessiontype != POLLER:
            # no regulation thread (yet), but running in daemon -> start one
            self._regulation_thread = createThread(
                'regulation thread %s' % self, self._regulate)
        self.curstatus = status.BUSY, 'regulating'
        self.poll()

    def doStop(self):
        self._stop_request = True
        if self._regulation_thread:
            self._regulation_thread.join()
            self.poll()

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

    def doStatus(self, maxage=0):
        if session.sessiontype != POLLER:  # XXX!
            if self._regulation_thread and self._regulation_thread.is_alive():
                # self.curstatus = status.BUSY, 'regulating'
                pass
            else:
                self.curstatus = status.OK, 'idle'
        return self.curstatus

    def _regulate(self):
        while not self._stop_request:
            try:
                read_val = self._attached_sensor.read(0)
                self.log.debug('Readable value: %s', read_val)

                diff = abs(self.target - read_val)
                self.log.debug('Difference to the target: %s', diff)
                if diff > self.deadbandwidth:
                    self.curstatus = status.BUSY, 'regulating'
                if diff > self.deadbandwidth / 2:
                    cur_write_val = self._attached_moveable.read(0)
                    step = self.stepfactor * (diff - self.deadbandwidth / 2)
                    if step < self.minstep:
                        step = self.minstep
                    maxstep = self.maxstep or step
                    sign = 1 if read_val < self.target else -1

                    step = min(step, maxstep) * sign
                    new_target = cur_write_val + step

                    self.log.debug(
                        'Regulation necessary, move attached movable:'
                        '%s -> %s', cur_write_val, new_target)

                    if hasattr(self._attached_moveable, 'absmax'):
                        if new_target > self._attached_moveable.absmax:
                            new_target = self._attached_moveable.absmax
                    if hasattr(self._attached_moveable, 'usermax'):
                        if new_target > self._attached_moveable.usermax:
                            new_target = self._attached_moveable.usermax

                    self._attached_moveable.start(new_target)
                    # TODO: wait?
                else:
                    self.curstatus = status.OK, 'stable'

            except NicosError as e:
                self.log.warning('Skip regulation: %s', e)

            time.sleep(self.loopdelay)
        self._regulation_thread = None
        self._stop_request = False
コード例 #27
0
class Sans1ColliMotor(Sans1ColliBase, CanReference, SequencerMixin, HasTimeout,
                      Motor):
    """
    Device object for a digital output device via a Beckhoff modbus interface.
    Minimum Parameter Implementation.
    Relevant Parameters need to be configured in the setupfile or in the
    Beckhoff PLC.
    """

    relax_mapping = True

    parameters = {
        # provided by parent class: speed, unit, fmtstr, warnlimits, abslimits,
        #                           userlimits, precision and others
        'address':
        Param('Starting offset of Motor control Block in words',
              type=MOTOR_VALIDATOR,
              mandatory=True,
              settable=False,
              userparam=False),
        'slope':
        Param('Slope of the Motor in FULL steps per physical '
              'unit',
              type=float,
              default=1.,
              unit='steps/main',
              userparam=False,
              settable=True),
        'microsteps':
        Param('Microstepping for the motor',
              type=oneof(1, 2, 4, 8, 16, 32, 64),
              default=1,
              userparam=False,
              settable=False),
        'autozero':
        Param(
            'Maximum distance from referencepoint for forced '
            'referencing before moving, or None',
            type=none_or(float),
            default=10,
            unit='main',
            settable=False),
        'autopower':
        Param('Automatically disable Drivers if motor is idle',
              type=oneofdict({
                  0: 'off',
                  1: 'on'
              }),
              default='on',
              settable=False),
        'refpos':
        Param('Position of reference switch',
              unit='main',
              type=float,
              mandatory=True,
              settable=False,
              prefercache=False),
    }

    parameter_overrides = {
        'timeout': Override(mandatory=False, default=300),
    }
    _busy_until = 0

    def doInit(self, mode):
        Sans1ColliBase.doInit(self, mode)
        if mode != SIMULATION:
            if self.autopower == 'on':
                self._HW_disable()

    #
    # access-helpers for accessing the fields inside the MotorControlBlock
    #
    def _readControlBit(self, bit):
        self.log.debug('_readControlBit %d', bit)
        value = uint16(self._dev.ReadOutputWord(self.address))
        return (value & (1 << int(bit))) >> int(bit)

    def _writeControlBit(self, bit, value):
        self._busy_until = time.time() + 3
        self.log.debug('_writeControlBit %r, %r', bit, value)
        tmpval = uint16(self._dev.ReadOutputWord(self.address))
        tmpval &= ~(1 << int(bit))
        tmpval |= (int(value) << int(bit))
        self._dev.WriteOutputWord((self.address, uint16(tmpval)))
        session.delay(0.5)  # work around race conditions inside plc....

    def _writeDestination(self, value):
        self.log.debug('_writeDestination %r', value)
        self._dev.WriteOutputDword((self.address + 2, uint32(value)))

    def _readStatusWord(self):
        value = uint16(self._dev.ReadOutputWord(self.address + 4))
        self.log.debug('_readStatusWord %04x', value)
        return value

    def _readErrorWord(self):
        value = uint16(self._dev.ReadOutputWord(self.address + 5))
        self.log.debug('_readErrorWord %04x', value)
        return value

    def _readPosition(self):
        value = int32(self._dev.ReadOutputDword(self.address + 6))
        self.log.debug('_readPosition: -> %d steps', value)
        return value

    def _readUpperControlWord(self):
        self.log.error('_readUpperControlWord')
        return uint16(self._dev.ReadOutputWord(self.address + 1))

    def _writeUpperControlWord(self, value):
        self.log.debug('_writeUpperControlWord 0x%04x', value)
        value = uint16(value)
        self._dev.WriteOutputWord((self.address + 1, value))

    def _readDestination(self):
        value = int32(self._dev.ReadOutputDword(self.address + 2))
        self.log.debug('_readDestination: -> %d steps', value)
        return value

    def _readReturn(self):
        value = int32(self._dev.ReadOutputDword(self.address + 8))
        self.log.debug('_readReturn: -> %d (0x%08x)', value, value)
        return value

    #
    # math: transformation of position and speed:
    #       µsteps(/s) <-> phys. unit(/s)
    #
    def _steps2phys(self, steps):
        value = steps / float(self.microsteps * self.slope)
        self.log.debug('_steps2phys: %r steps -> %s', steps,
                       self.format(value, unit=True))
        return value

    def _phys2steps(self, value):
        steps = int(value * float(self.microsteps * self.slope))
        self.log.debug('_phys2steps: %s -> %r steps',
                       self.format(value, unit=True), steps)
        return steps

    def _speed2phys(self, speed):
        # see manual
        return speed / float(self.microsteps * self.slope * 1.6384e-2)

    def _phys2speed(self, value):
        # see manual
        return int(value * self.slope * self.microsteps * 1.6384e-2)

    #
    # Hardware abstraction: which actions do we want to do...
    #
    def _HW_enable(self):
        self._writeControlBit(0, 1)  # docu: bit0 = 1: enable

    def _HW_disable(self):
        self._writeControlBit(0, 0)  # docu: bit0 = 1: enable

    def _HW_start(self, target):
        self._writeDestination(self._phys2steps(target))
        # docu: bit2 = go to absolute position, autoresets
        self._writeControlBit(2, 1)

    def _HW_reference(self):
        """Do the referencing and update position to refpos"""
        self._writeControlBit(4, 1)  # docu: bit4 = reference, autoresets
        # according to docu, the refpos is (also) a parameter of the KL....

    def doSetPosition(self, value):
        for _ in range(100):
            if self._readStatusWord() & (1 << 7):
                continue
            break
        else:
            raise UsageError(
                self, 'Can not set position while motor is '
                'moving, please stop it first!')

        was_on = self._readControlBit(0)
        if was_on:
            self._HW_disable()

        # wait for inactive ACK/NACK
        for _ in range(1000):
            if self._readStatusWord() & (3 << 14) == 0:
                break
        else:
            raise CommunicationError(
                self, 'HW still busy, can not set '
                'position, please retry later.')

        loops = 10
        for loop in range(loops):
            self.log.debug('setPosition: loop %d of %d', loop, loops)
            self._writeDestination(self._phys2steps(value))
            # index=1: update current position
            self._writeUpperControlWord((1 << 8) | 1)

            # Wait for ACK/NACK bits
            for _ in range(100):
                last_sw = self._readStatusWord()
                if last_sw & (3 << 14) != 0:
                    break
            else:
                self.log.warning(
                    'SetPosition command not recognized, retrying')

            if last_sw & (2 << 14) != 0:
                self.log.debug('setPosition: got ACK')
                break
            elif last_sw & (1 << 14):
                self.log.debug('setPosition: got NACK')
                raise CommunicationError(
                    self, 'Setting position failed, '
                    'got a NACK!')
        else:
            raise CommunicationError(
                self, 'setPosition command not '
                'recognized by HW, please retry later.')

        if was_on:
            self._HW_enable()

    def _HW_stop(self):
        self._writeControlBit(6, 1)  # docu: bit6 = stop, autoresets

    def _HW_wait_while_BUSY(self):
        # XXX timeout?
        while not self._seq_stopflag:
            session.delay(0.1)
            statval = self._readStatusWord()
            # if motor moving==0 and target reached==1 -> break
            if (statval & (1 << 7) == 0) and (statval & (1 << 6)):
                break
            if statval & (7 << 10):  # limit switch triggered or stop issued
                session.delay(0.1)
                break

    def _HW_status(self):
        """ used Status bits:
        0-2 : Load-angle (0 good, 7 bad)
        3   : limit switch -
        4   : limit switch +
        5   : moving in pos. direction
        6   : target reached
        7   : motor moving
        8   : driver on and ready
        9   : Overtemperature
        10  : Target NOT reached, but a limit switch triggered
        11  : Target NOT reached due PowerOff or Stop
        12  : Can not move towards requested position, command ignored
        14  : N_ACK (last set/get command was unsuccessful),
              auto clears after 1s
        15  : ACK (last get/set command was successful,
              value in RETURN is valid), auto clears after 1s
        """
        statval = self._readStatusWord()
        errval = self._readErrorWord()
        code, msg = status.ERROR, ['Unknown Status value 0x%04x!' % statval]

        # work around buggy SPS code:
        # sometimes we get 0x0105..7, which should never happen
        # as the lowest 3 bits are not relevant,
        # check only the others and return BUSY
        # also ignore the limit switch bits
        # if statval & (0xfff8) == 0x0100:
        if statval & (0x7fe0) == 0x0100:
            return status.BUSY, '0x010x!'

        # status Stuff
        if statval & (1 << 7):
            code, msg = status.BUSY, ['busy']
        elif statval & (1 << 6):
            code, msg = status.OK, ['Target reached']
        elif ~statval & (1 << 8):
            code, msg = status.OK, ['Disabled']
        elif statval & (1 << 9):
            code, msg = status.ERROR, ['Overtemperature!']
        # check any of bit 10, 11, 12 at the same time!
        elif statval & (7 << 10):
            code, msg = status.OK, ['Can not reach Target!']
        if errval:
            code, msg = status.ERROR, ['Error']
            if errval & (1 << 0):
                msg.append('Control voltage too low')
            if errval & (1 << 1):
                msg.append('Motor driving voltage too low')
            if errval & (1 << 2):
                msg.append('Overcurrent or short in winding A')
            if errval & (1 << 3):
                msg.append('Overcurrent or short in winding B')
            if errval & (1 << 4):
                msg.append('Open load or broken wire in winding A')
            if errval & (1 << 5):
                msg.append('Open load or broken wire in winding B')
            if errval & (1 << 7):
                msg.append('Overtemperature (T>125 degC)')
            if errval & 0b1111111101000000:
                msg.append('Unknown Error 0x%04x' % errval)

        # informational stuff
        if statval & (1 << 4):
            msg.append('limit switch +')
        if statval & (1 << 3):
            msg.append('limit switch -')
        if statval & (1 << 8):
            msg.append('driver on and ready')
        if statval & (1 << 7):
            msg.append('load=%d' % (statval & 0x0007))

        msg = ', '.join(msg)
        self.log.debug('_HW_Status returns %r', (code, msg))

        if self._busy_until > time.time():
            code = max(code, status.BUSY)
            msg = 'timed busy, %s' % msg

        return code, msg

    #
    # Sequencing stuff
    #
    def _gen_move_sequence(self, target):
        # now generate a sequence of commands to execute in order
        seq = []

        # always enable before doing anything
        seq.append(SeqMethod(self, '_HW_enable'))

        # check autoreferencing feature
        if self.autozero is not None:
            currentpos = self.read(0)
            mindist = min(abs(currentpos - self.refpos),
                          abs(target - self.refpos))
            if mindist < self.autozero:
                seq.extend(self._gen_ref_sequence())

        # now just go where commanded....
        seq.append(SeqMethod(self, '_HW_start', target))
        seq.append(SeqMethod(self, '_HW_wait_while_BUSY'))

        if self.autopower == 'on':
            # disable if requested.
            seq.append(SeqMethod(self, '_HW_disable'))

        return seq

    def _gen_ref_sequence(self):
        seq = []
        # try to mimic anatel: go to 5mm before refpos and then to the negative limit switch
        seq.append(SeqMethod(self, '_HW_enable'))
        seq.append(SeqMethod(self, '_HW_start', self.refpos + 5.))
        seq.append(SeqMethod(self, '_HW_wait_while_BUSY'))
        seq.append(
            SeqMethod(
                self, '_HW_start',
                self.absmin if self.absmin < self.refpos else self.refpos -
                100))
        seq.append(SeqMethod(self, '_HW_wait_while_BUSY'))
        seq.append(SeqMethod(self, '_HW_reference'))
        seq.append(SeqMethod(self, '_HW_wait_while_BUSY'))
        seq.append(SeqMethod(self, 'doSetPosition', self.refpos))
        return seq

    #
    # nicos methods
    #
    def doRead(self, maxage=0):
        return self._steps2phys(self._readPosition())

    def doStart(self, target):
        if self._seq_is_running():
            raise MoveError(self, 'Cannot start device, it is still moving!')
        self._startSequence(self._gen_move_sequence(target))

    def doStop(self):
        if self._honor_stop:
            self._seq_stopflag = True
        self._HW_stop()

    def doReset(self):
        self._writeControlBit(7, 1)  # docu: bit7 = ERROR-ACK, autoresets
        self._set_seq_status(status.OK, 'idle')

    def doStatus(self, maxage=0):
        """returns highest statusvalue"""
        if self._mode == SIMULATION:
            stati = [(status.OK, 'simulation'), self._seq_status]
        else:
            stati = [self._HW_status(), self._seq_status]
        # sort inplace by first element, i.e. status code
        stati.sort(key=lambda st: st[0])
        # select highest (worst) status
        # if no status is 'worse' then _seq_status, this is _seq_status
        _status = stati[-1]
        if self._seq_is_running():
            return max(status.BUSY, _status[0]), _status[1]
        return _status

    @requires(level='admin')
    def doReference(self):
        if self._seq_is_running():
            raise MoveError(self, 'Cannot reference a moving device!')
        seq = self._gen_ref_sequence()
        if self.autopower == 'on':
            # disable if requested.
            seq.append(SeqMethod(self, '_HW_disable'))
        self._startSequence(seq)
コード例 #28
0
ファイル: detector.py プロジェクト: ess-dmsc/nicos
class Detector(Measurable):
    """Detector using multiple (synchronized) channels.

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

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

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

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

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

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

    hardware_access = False
    multi_master = True

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def doInfo(self):
        ret = []
        if self._user_comment:
            ret.append(('usercomment', self._user_comment, self._user_comment,
                        '', 'general'))
        presets = []
        for (_dev, devpresets) in self._channel_presets.items():
            for (key, value) in devpresets:
                presets.append((self._presetkeys[key][1], value))
        if len(presets) > 1:
            mode = ' or '.join(p[0] for p in presets)
            ret.append(('mode', mode, mode, '', 'presets'))
            for (mode, value) in presets:
                ret.append(
                    ('preset_%s' % mode, value, str(value), '', 'presets'))
        elif presets:
            mode, value = presets[0]
            return ret + [('mode', mode, mode, '', 'presets'),
                          ('preset', value, str(value), '', 'presets')]
        return ret
コード例 #29
0
ファイル: pvaccess.py プロジェクト: umithardal/nicos
class EpicsMoveable(EpicsDevice, Moveable):
    """
    Handles EPICS devices which can set and read a value.
    """

    parameters = {
        'readpv': Param('PV for reading device value',
                        type=pvname, mandatory=True, preinit=True),
        'writepv': Param('PV for writing device target',
                         type=pvname, mandatory=True, preinit=True),
        'targetpv': Param('Optional target readback PV.',
                          type=none_or(pvname), mandatory=False, preinit=True)
    }

    parameter_overrides = {
        'unit': Override(mandatory=False),
        'target': Override(volatile=True),
    }

    pv_parameters = {'readpv', 'writepv'}

    pv_cache_relations = {
        'readpv': 'value',
        'targetpv': 'target',
    }

    def _get_pv_parameters(self):
        """
        Overriden from EpicsDevice. If the targetpv parameter is specified,
        the PV-object should be created accordingly. Otherwise, just return
        the mandatory pv_parameters.
        """
        if self.targetpv:
            return self.pv_parameters | {'targetpv'}

        return self.pv_parameters

    def doInit(self, mode):
        if mode == SIMULATION:
            return
        intype = self._get_pv_datatype('readpv')
        outtype = self._get_pv_datatype('writepv')
        if intype != self.valuetype:
            raise ConfigurationError(self, 'Input PV %r does not have the '
                                           'correct data type' % self.readpv)
        if outtype != self.valuetype:
            raise ConfigurationError(self, 'Output PV %r does not have the '
                                           'correct data type' % self.writepv)

        if self.targetpv:
            target_type = self._get_pv_datatype('targetpv')

            if target_type != self.valuetype:
                raise ConfigurationError(
                    self, 'Target PV %r does not have the '
                          'correct data type' % self.targetpv)

        self._register_pv_callbacks()

    def doReadUnit(self):
        return self._get_pvctrl('readpv', 'units', '')

    def doReadTarget(self):
        """
        In many cases IOCs provide a readback of the setpoint, here represented
        as targetpv. Since this is not provided everywhere, it should still be
        possible to get the target.
        """
        if self.targetpv:
            return self._get_pv('targetpv')

        value = self._params.get('target')
        return value if value is not None else self._config.get('target')

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

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

    def doStop(self):
        self.doStart(self.doRead())
コード例 #30
0
ファイル: __init__.py プロジェクト: umithardal/nicos
class Monitor(BaseCacheClient):
    """
    A graphical window showing values of cache keys.

    For cache keys that correspond to NICOS devices, not only the devicevalue,
    but also the device status and unit are shown.
    """

    # server and prefix parameters come from BaseCacheClient
    parameters = {
        'title':
        Param('Title of status window', type=str, default='Status'),
        'layout':
        Param('Status monitor layout', type=list, mandatory=True),
        'font':
        Param('Font name for the window', type=str, default='Luxi Sans'),
        'valuefont':
        Param('Font name for the value displays', type=str),
        'fontsize':
        Param('Basic font size', type=int, default=12, settable=True),
        'timefontsize':
        Param('Time headline font size',
              type=none_or(int),
              default=None,
              settable=True),
        'padding':
        Param('Padding for the display fields',
              type=int,
              default=2,
              settable=True),
        'geometry':
        Param('Geometry for status window',
              type=str,
              settable=True,
              ext_desc='For the  allowed settings see '
              ':option:`--geometry`.'),
        'resizable':
        Param('Whether the window is resizable', type=bool, default=True),
        'colors':
        Param('Color scheme for value displays (dark or light '
              'background)',
              type=oneof('dark', 'light')),
        'showwatchdog':
        Param('Whether to show watchdog warnings', type=bool, default=True),
        'expectmaster':
        Param(
            'Whether a message should indicate that no '
            'NICOS master is active',
            type=bool,
            default=True),
    }

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

    # methods to be implemented in concrete implementations

    def initGui(self):
        raise NotImplementedError('Implement initGui() in subclasses')

    def mainLoop(self):
        raise NotImplementedError('Implement mainLoop() in subclasses')

    def closeGui(self):
        raise NotImplementedError('Implement closeGui() in subclasses')

    def signalKeyChange(self, field, *args):
        raise NotImplementedError('Implement signalKeyChange() in subclasses')

    def switchWarnPanel(self, on):
        raise NotImplementedError('Implement switchWarnPanel() in subclasses')

    def reconfigureBoxes(self):
        raise NotImplementedError('Implement reconfigureBoxes() in subclasses')

    def updateTitle(self, text):
        raise NotImplementedError('Implement updateTitle() in subclasses')

    # methods implemented here

    _keys_expired = False  # whether on disconnect all keys have been expired

    def start(self, options):  # pylint: disable=W0221
        self.log.info('monitor starting up, creating main window')

        self._fontsize = options.fontsize or self.fontsize
        self._fontsizebig = int(self._fontsize * 1.2)
        self._timefontsize = (options.timefontsize or self.timefontsize
                              or (self._fontsizebig + self._fontsize))
        self._padding = options.padding or self.padding
        self._geometry = options.geometry or self.geometry

        if self._geometry and self._geometry != 'fullscreen':
            try:
                m = re.match(r'(?:(\d+)x(\d+))?\+(\d+)\+(\d+)', self._geometry)
                w, h, x, y = m.groups()
                if w is None:
                    w = h = 0
                else:
                    w, h = int(w), int(h)
                x, y = int(x), int(y)
                self._geometry = (w, h, x, y)
            except Exception:
                self.log.warning('invalid geometry %s', self._geometry)
                self._geometry = None

        # timeout for select() call
        self._selecttimeout = 0.2
        # maps keys to field-dicts defined in self.layout (see above)
        self._keymap = {}
        # blocks to hide conditionally
        self._onlyblocks = []
        # fields to hide conditionally (only if not in a block)
        self._onlyfields = []
        # remembers loaded setups
        self._setups = set()
        # master active?
        self._masteractive = False
        # currently shown warnings
        self._currwarnings = ''

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

        self.initGui()

        # now start the worker thread
        self._worker.start()

        self.log.info('starting main loop')
        try:
            self.mainLoop()
        except KeyboardInterrupt:
            pass
        self._stoprequest = True

    def _checker(self):
        setupname = session.explicit_setups[0]
        fn = session._setup_info[setupname]['filenames']
        watchFileContent(fn, self.log)
        self.log.info('setup file changed; restarting monitor process')
        reexecProcess()

    def wait(self):
        self.log.info('monitor quitting')
        self._worker.join()
        self.log.info('done')

    def quit(self, *ignored, **kwds):
        self.closeGui()
        self._stoprequest = True

    def _connect_action(self):
        self._keys_expired = False
        BaseCacheClient._connect_action(self)
        if self.showwatchdog:
            # also ask for and subscribe to all watchdog events
            self._socket.sendall(to_utf8('@watchdog/%s\n' % OP_WILDCARD))
            self._socket.sendall(to_utf8('@watchdog/%s\n' % OP_SUBSCRIBE))

        # use appname to distinguish between different instances
        self.storeSysInfo(session.appname)

    # called between connection attempts
    def _wait_retry(self):
        if not self._keys_expired:
            time = currenttime()
            for key in self._keymap:
                for obj in self._keymap[key]:
                    self.signalKeyChange(obj, key, None, time, True)
            self._keys_expired = True
        self.updateTitle('Disconnected (%s)' % strftime('%d.%m.%Y %H:%M:%S'))
        sleep(1)

    # called while waiting for data
    def _wait_data(self):
        # update current time
        self.updateTitle('%s (%s)%s' %
                         (self.title, strftime('%d.%m.%Y %H:%M:%S'),
                          '' if self._masteractive or
                          (not self.expectmaster) else ', no master active'))

    def register(self, widget, key):
        """API used by NicosListener widgets to register keys for callback."""
        key = self._prefix + key.lower()
        self._keymap.setdefault(key, []).append(widget)
        return key

    # called to handle an incoming protocol message
    def _handle_msg(self, time, ttlop, ttl, tsop, key, op, value):
        if op not in (OP_TELL, OP_TELLOLD):
            return
        try:
            time = float(time)
        except (ValueError, TypeError):
            time = currenttime()
        try:
            value = cache_load(value)
        except ValueError:
            value = None

        if key == 'watchdog/warnings' and self.showwatchdog:
            self._process_warnings(value)
            return

        # self.log.debug('processing %s', [time, ttl, key, op, value])

        if key == self._prefix + 'session/master':
            self._masteractive = value and op != OP_TELLOLD

        if key == self._prefix + 'session/mastersetup':
            self._setups = set(value)
            # reconfigure displayed blocks
            self.reconfigureBoxes()
            self.log.info('reconfigured display for setups %s',
                          ', '.join(self._setups))

        expired = value is None or op == OP_TELLOLD

        # now check if we need to update something
        objs = self._keymap.get(key, [])
        for obj in objs:
            self.signalKeyChange(obj, key, value, time, expired)

    def _process_warnings(self, warnings):
        # self.log.debug('new warnings: %s', warnings)
        self._currwarnings = warnings
        self.switchWarnPanel(bool(warnings))

    def _resolve_block(self, block):
        # exchange SetupBlock objects by their definition, or raise
        # a ConfigurationError if it doesn't exist.
        if not isinstance(block, SetupBlock):
            return block
        setup, bname = block._setupname, block._blockname
        setupinfo = session.getSetupInfo()
        if setup not in setupinfo:
            raise ConfigurationError(
                self, 'Setup "%s" required by '
                'SetupBlock() does not exist' % setup)
        blocks = setupinfo[setup]['monitor_blocks']
        if bname not in blocks:
            raise ConfigurationError(
                self, 'Setup "%s" does not define a  '
                'monitor block called "%s"' % (setup, bname))
        return blocks[bname]