def doPreinit(self, mode): tango.TemperatureController.doPreinit(self, mode) if mode != SIMULATION: self._controller = self._createPyTangoDevice(self.controller) else: self._controller = HardwareStub(self)
def doPreinit(self, mode): if self.loglevel == 'debug': self._taco_guard = self._taco_guard_log if self.taco_class is None: raise ProgrammingError('missing taco_class attribute in class ' + self.__class__.__name__) if mode != SIMULATION: self._dev = self._create_client() else: self._dev = HardwareStub(self)
def _setMode(self, mode): super()._setMode(mode) # remove the TACO device on entering simulation mode, to prevent # accidental access to the hardware if mode == SIMULATION: # keep the device instance around to avoid destruction (which can # mess with the TACO connections in the main process if simulation # has been forked off) self._orig_dev = self._dev self._dev = HardwareStub(self)
def doInit(self, mode): NamedDigitalOutput.doInit(self, mode) # Don't create PyTango device in simulation mode if mode != SIMULATION: self._remote = self._createPyTangoDevice(devname=self.remote) self._readback = self._createPyTangoDevice(devname=self.readback) self._error = self._createPyTangoDevice(devname=self.error) self._sleeptime = 0.1 else: self._remote = HardwareStub(self) self._readback = HardwareStub(self) self._error = HardwareStub(self)
def doPreinit(self, mode): # Wrap PyTango client creation (so even for the ctor, logging and # exception mapping is enabled). self._createPyTangoDevice = self._applyGuardToFunc( self._createPyTangoDevice, 'constructor') self._dev = None # Don't create PyTango device in simulation mode if mode != SIMULATION: self._dev = self._createPyTangoDevice(self.tangodevice) else: self._dev = HardwareStub(self)
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 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] = {}
class PressureController(tango.TemperatureController): """Device to be able to set the pressure manually. Pressure is set via the controller, which is supposed to handle the limits within which setting pressure is allowed. """ parameters = { 'controller': Param('SEController device name', type=tangodev, mandatory=True, preinit=True), 'pressuretolerance': Param('Tolerance for the adjustment of the ' 'pressure', settable=True, volatile=True) } def doPreinit(self, mode): tango.TemperatureController.doPreinit(self, mode) if mode != SIMULATION: self._controller = self._createPyTangoDevice(self.controller) else: self._controller = HardwareStub(self) def doStart(self, value): self._controller.setPressure(value) def doStop(self): pass def doReadPressuretolerance(self): return self._dev.pressure_tolerance def doWritePressuretolerance(self, value): self._dev.pressure_tolerance = value
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 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] = {}
class TacoDevice(HasCommunication): """Mixin class for TACO devices. Use it in concrete device classes like this:: class Counter(TacoDevice, Measurable): taco_class = IO.Counter # more overwritten methods i.e., put TacoDevice first in the base class list. TacoDevice provides the following methods already: * `.doVersion` (returns TACO device version) * `.doPreinit` (creates the TACO device from the `tacodevice` parameter) * `.doRead` (reads the TACO device) * `.doStatus` (returns status.OK for ON and DEVICE_NORMAL, ERROR otherwise) * `.doReset` (resets the TACO device) * `.doReadUnit` (reads the unit parameter from the TACO device if not configured in setup) You can however override them and provide your own specialized implementation. TacoDevice subclasses will automatically log all calls to TACO if their loglevel is DEBUG. TacoDevice also has the following class attributes, which can be overridden in derived classes: * `taco_class` -- the Python class to use for the TACO client * `taco_resetok` -- a boolean value indicating if the device can be reset during connection if it is in error state * `taco_errorcodes` -- a dictionary mapping TACO error codes to NICOS exception classes The following utility methods are provided: .. automethod:: _taco_guard .. automethod:: _taco_update_resource .. automethod:: _create_client """ parameters = { 'tacodevice': Param('TACO device name', type=tacodev, mandatory=True, preinit=True), 'tacotimeout': Param('TACO network timeout for this process', unit='s', type=floatrange(0.0, 1200), default=3, settable=True, preinit=True), } parameter_overrides = { # the unit isn't mandatory -- TACO usually knows it already 'unit': Override(mandatory=False), } _TACO_STATUS_MAPPING = { # OK states TACOStates.ON: status.OK, TACOStates.DEVICE_NORMAL: (status.OK, 'idle'), TACOStates.POSITIVE_ENDSTOP: (status.OK, 'limit switch +'), TACOStates.NEGATIVE_ENDSTOP: (status.OK, 'limit switch -'), TACOStates.STOPPED: (status.OK, 'idle or paused'), TACOStates.PRESELECTION_REACHED: status.OK, TACOStates.DISABLED: status.OK, # BUSY states # explicit ramp string as there seem to be some inconsistencies TACOStates.RAMP: (status.BUSY, 'ramping'), TACOStates.MOVING: status.BUSY, TACOStates.STOPPING: status.BUSY, TACOStates.INIT: (status.BUSY, 'initializing taco device / hardware'), TACOStates.RESETTING: status.BUSY, TACOStates.STOP_REQUESTED: status.BUSY, TACOStates.COUNTING: status.BUSY, TACOStates.STARTED: status.BUSY, # NOTREACHED states TACOStates.UNDEFINED: status.NOTREACHED, # WARN states TACOStates.ALARM: status.WARN, # ERROR states TACOStates.FAULT: status.ERROR, TACOStates.BLOCKED: status.ERROR, TACOStates.TRIPPED: status.ERROR, TACOStates.OVERFLOW: status.ERROR, TACOStates.OFF: status.ERROR, TACOStates.DEVICE_OFF: status.ERROR, TACOStates.ON_NOT_REACHED: status.ERROR, } # the TACO client class to instantiate taco_class = None # whether to call deviceReset() if the initial switch-on fails taco_resetok = True # additional TACO error codes mapping to Nicos exception classes taco_errorcodes = {} # TACO device instance _dev = None def doPreinit(self, mode): if self.loglevel == 'debug': self._taco_guard = self._taco_guard_log if self.taco_class is None: raise ProgrammingError('missing taco_class attribute in class ' + self.__class__.__name__) if mode != SIMULATION: self._dev = self._create_client() else: self._dev = HardwareStub(self) def doShutdown(self): if self._dev: self._dev.disconnectClient() del self._dev def _setMode(self, mode): super()._setMode(mode) # remove the TACO device on entering simulation mode, to prevent # accidental access to the hardware if mode == SIMULATION: # keep the device instance around to avoid destruction (which can # mess with the TACO connections in the main process if simulation # has been forked off) self._orig_dev = self._dev self._dev = HardwareStub(self) def doVersion(self): return [(self.tacodevice, self._taco_guard(self._dev.deviceVersion))] def doRead(self, maxage=0): return self._taco_guard(self._dev.read) def doStatus(self, maxage=0): for i in range(self.comtries or 1): if i: session.delay(self.comdelay) tacoState = self._taco_guard(self._dev.deviceState) if tacoState != TACOStates.FAULT: break state = self._TACO_STATUS_MAPPING.get(tacoState, status.ERROR) if isinstance(state, tuple): return state statusStr = self._taco_guard(self._dev.deviceStatus) return (state, statusStr) def doReset(self): self._taco_reset(self._dev) def doReadUnit(self): # explicitly configured unit has precendence if 'unit' in self._config: return self._config['unit'] if hasattr(self._dev, 'unit'): return self._taco_guard(self._dev.unit) return self.parameters['unit'].default def doWriteUnit(self, value): if hasattr(self._dev, 'setUnit'): self._taco_guard(self._dev.setUnit, value) if 'unit' in self._config: if self._config['unit'] != value: self.log.warning( 'configured unit %r in configuration differs ' 'from current unit %r', self._config['unit'], value) def doUpdateTacotimeout(self, value): if not self._sim_intercept and self._dev: if value != 3.0: self.log.warning( '%r: client network timeout changed to: ' '%.2f s', self.tacodevice, value) self._taco_guard(self._dev.setClientNetworkTimeout, value) def doUpdateLoglevel(self, value): super().doUpdateLoglevel(value) self._taco_guard = value == 'debug' and self._taco_guard_log or \ self._taco_guard_nolog # internal utilities def _create_client(self, devname=None, class_=None, resetok=None, timeout=None): """Create a new TACO client to the device given by *devname*, using the Python class *class_*. Initialize the device in a consistent state, handling eventual errors. If no arguments are given, the values of *devname*, *class_*, *resetok* and *timeout* are taken from the class attributes *taco_class* and *taco_resetok* as well as the device parameters *tacodevice* and *tacotimeout*. This is done during `.doPreinit`, so that you usually don't have to call this method in TacoDevice subclasses. You can use this method to create additional TACO clients in a device implementation that uses more than one TACO device. """ if devname is None: devname = self.tacodevice if class_ is None: class_ = self.taco_class if resetok is None: resetok = self.taco_resetok if timeout is None: timeout = self.tacotimeout self.log.debug('creating %s TACO device', class_.__name__) try: dev = class_(devname) self._check_server_running(dev) except TACOError as err: self._raise_taco( err, 'Could not connect to device %r; make sure ' 'the device server is running' % devname) try: if timeout != 0: if timeout != 3.0: self.log.warning( 'client network timeout changed to: ' '%.2f s', timeout) dev.setClientNetworkTimeout(timeout) except TACOError as err: self.log.warning( 'Setting TACO network timeout failed: ' '[TACO %d] %s', err.errcode, err) try: if dev.isDeviceOff(): dev.deviceOn() except TACOError as err: self.log.warning( 'Switching TACO device %r on failed: ' '[TACO %d] %s', devname, err.errcode, err) try: if dev.deviceState() == TACOStates.FAULT: if resetok: dev.deviceReset() dev.deviceOn() except TACOError as err: self._raise_taco( err, 'Switching device %r on after ' 'reset failed' % devname) return dev def _check_server_running(self, dev): dev.deviceVersion() def _taco_guard_log(self, function, *args): """Like _taco_guard(), but log the call.""" self.log.debug('TACO call: %s%r', function.__name__, args) if not self._dev: raise NicosError(self, 'TACO Device not initialised') self._com_lock.acquire() try: ret = function(*args) except TACOError as err: # for performance reasons, starting the loop and querying # self.comtries only triggers in the error case if self.comtries > 1 or err == DevErr_RPCTimedOut: tries = 2 if err == DevErr_RPCTimedOut and self.comtries == 1 \ else self.comtries - 1 self.log.warning('TACO %s failed, retrying up to %d times', function.__name__, tries, exc=1) while True: session.delay(self.comdelay) tries -= 1 try: if self.taco_resetok and \ self._dev.deviceState() == TACOStates.FAULT: self._dev.deviceInit() session.delay(self.comdelay) ret = function(*args) self.log.debug('TACO return: %r', ret) return ret except TACOError as err: if tries == 0: break # and fall through to _raise_taco self.log.warning('TACO %s failed again', function.__name__, exc=True) self.log.debug('TACO exception: %r', err) self._raise_taco(err) else: self.log.debug('TACO return: %r', ret) return ret finally: self._com_lock.release() def _taco_guard_nolog(self, function, *args): """Try running the TACO function, and raise a NicosError on exception. A more specific NicosError subclass is chosen if appropriate. For example, database-related errors are converted to `.CommunicationError`. A TacoDevice subclass can add custom error code to exception class mappings by using the `.taco_errorcodes` class attribute. If the `comtries` parameter is > 1, the call is retried accordingly. """ if not self._dev: raise NicosError(self, 'TACO device not initialised') self._com_lock.acquire() try: return function(*args) except TACOError as err: # for performance reasons, starting the loop and querying # self.comtries only triggers in the error case if self.comtries > 1 or err == DevErr_RPCTimedOut: tries = 2 if err == DevErr_RPCTimedOut and self.comtries == 1 \ else self.comtries - 1 self.log.warning('TACO %s failed, retrying up to %d times', function.__name__, tries) while True: session.delay(self.comdelay) tries -= 1 try: return function(*args) except TACOError as err: if tries == 0: break # and fall through to _raise_taco self._raise_taco(err, '%s%r' % (function.__name__, args)) finally: self._com_lock.release() _taco_guard = _taco_guard_nolog def _taco_update_resource(self, resname, value): """Update the TACO resource *resname* to *value* (both must be strings), switching the device off and on. """ if not self._dev: raise NicosError(self, 'TACO device not initialised') self._com_lock.acquire() try: self.log.debug('TACO resource update: %s %s', resname, value) self._dev.deviceOff() self._dev.deviceUpdateResource(resname, value) self._dev.deviceOn() self.log.debug('TACO resource update successful') except TACOError as err: self._raise_taco(err, 'While updating %s resource' % resname) finally: self._com_lock.release() def _raise_taco(self, err, addmsg=None): """Raise a suitable NicosError for a given TACOError instance.""" tb = sys.exc_info()[2] code = err.errcode cls = NicosError if code in self.taco_errorcodes: cls = self.taco_errorcodes[code] elif code < 50: # error numbers 0-50: RPC call errors cls = CommunicationError elif 400 <= code < 500: # error number 400-499: database system error messages cls = CommunicationError elif code == DevErr_RangeError: cls = LimitError elif code in (DevErr_InvalidValue, DevErr_ExecutionDenied): cls = InvalidValueError elif code in (DevErr_IOError, DevErr_InternalError, DevErr_RuntimeError, DevErr_SystemError): cls = CommunicationError msg = '[TACO %d] %s' % (err.errcode, err) if addmsg is not None: msg = addmsg + ': ' + msg exc = cls(self, msg, tacoerr=err.errcode) raise exc.with_traceback(tb) def _taco_reset(self, client, resetcall='deviceReset'): try: hostname = client.getServerHost() servername = client.getServerProcessName() personalname = client.getServerPersonalName() self.log.info( 'Resetting TACO device; if this does not help try ' 'restarting the %s named %s on host %s.', servername, personalname, hostname) except AttributeError: # older version without these API calls self.log.info('Resetting TACO device; if this does not help try ' 'restarting the server.') try: if resetcall == 'deviceReset': self._taco_guard(client.deviceReset) else: self._taco_guard(client.deviceInit) except Exception as err: self.log.warning('%s failed with %s', resetcall, err) if self._taco_guard(client.isDeviceOff): self._taco_guard(client.deviceOn)
def _setMode(self, mode): super(PyTangoDevice, self)._setMode(mode) # remove the Tango device on entering simulation mode, to prevent # accidental access to the hardware if mode == SIMULATION: self._dev = HardwareStub(self)
class PyTangoDevice(HasCommunication): """ Basic PyTango device. The PyTangoDevice uses an internal PyTango.DeviceProxy but wraps command execution and attribute operations with logging and exception mapping. """ hardware_access = True parameters = { 'tangodevice': Param('Tango device name', type=tangodev, mandatory=True, preinit=True), 'tangotimeout': Param('TANGO network timeout for this process', unit='s', type=floatrange(0.0, 1200), default=3, settable=True, preinit=True), } parameter_overrides = { 'unit': Override(mandatory=False), } tango_status_mapping = { PyTango.DevState.ON: status.OK, PyTango.DevState.ALARM: status.WARN, PyTango.DevState.OFF: status.DISABLED, PyTango.DevState.FAULT: status.ERROR, PyTango.DevState.MOVING: status.BUSY, } # Since each DeviceProxy leaks a few Python objects, we can't just # drop them when the device fails to initialize, and create another one. # It is also not required since they reconnect automatically. proxy_cache = {} def doPreinit(self, mode): # Wrap PyTango client creation (so even for the ctor, logging and # exception mapping is enabled). self._createPyTangoDevice = self._applyGuardToFunc( self._createPyTangoDevice, 'constructor') self._dev = None # Don't create PyTango device in simulation mode if mode != SIMULATION: self._dev = self._createPyTangoDevice(self.tangodevice) else: self._dev = HardwareStub(self) def doStatus(self, maxage=0): # Map Tango state to NICOS status nicosState = self.tango_status_mapping.get(self._dev.State(), status.UNKNOWN) return (nicosState, self._dev.Status()) def _hw_wait(self): """Wait until hardware status is not BUSY.""" while PyTangoDevice.doStatus(self, 0)[0] == status.BUSY: session.delay(self._base_loop_delay) def doVersion(self): return [(self.tangodevice, self._dev.version)] def doReset(self): self._dev.Reset() while self._dev.State() == PyTango.DevState.INIT: session.delay(self._base_loop_delay) def _setMode(self, mode): super(PyTangoDevice, self)._setMode(mode) # remove the Tango device on entering simulation mode, to prevent # accidental access to the hardware if mode == SIMULATION: self._dev = HardwareStub(self) def _getProperty(self, name, dev=None): """ Utility function for getting a property by name easily. """ if dev is None: dev = self._dev # return value is: [name, value, name, value, ...] props = dev.GetProperties() propnames = props[::2] return props[2*propnames.index(name) + 1] \ if name in propnames else None def doReadUnit(self): """For devices with a unit attribute.""" attrInfo = self._dev.attribute_query('value') # prefer configured unit if nothing is set on the Tango device if attrInfo.unit == 'No unit': return self._config.get('unit', '') return attrInfo.unit def _createPyTangoDevice(self, address): # pylint: disable=E0202 """ Creates the PyTango DeviceProxy and wraps command execution and attribute operations with logging and exception mapping. """ check_tango_host_connection(self.tangodevice, self.tangotimeout) proxy_key = (self._name, address) if proxy_key not in PyTangoDevice.proxy_cache: PyTangoDevice.proxy_cache[proxy_key] = PyTango.DeviceProxy(address) device = PyTangoDevice.proxy_cache[proxy_key] device.set_timeout_millis(int(self.tangotimeout * 1000)) # detect not running and not exported devices early, because that # otherwise would lead to attribute errors later try: device.State except AttributeError: raise NicosError(self, 'connection to Tango server failed, ' 'is the server running?') return self._applyGuardsToPyTangoDevice(device) def _applyGuardsToPyTangoDevice(self, dev): """ Wraps command execution and attribute operations of the given device with logging and exception mapping. """ dev.command_inout = self._applyGuardToFunc(dev.command_inout) dev.write_attribute = self._applyGuardToFunc(dev.write_attribute, 'attr_write') dev.read_attribute = self._applyGuardToFunc(dev.read_attribute, 'attr_read') dev.attribute_query = self._applyGuardToFunc(dev.attribute_query, 'attr_query') return dev def _applyGuardToFunc(self, func, category='cmd'): """ Wrap given function with logging and exception mapping. """ def wrap(*args, **kwds): info = category + ' ' + args[0] if args else category # handle different types for better debug output if category == 'cmd': self.log.debug('[Tango] command: %s%r', args[0], args[1:]) elif category == 'attr_read': self.log.debug('[Tango] read attribute: %s', args[0]) elif category == 'attr_write': self.log.debug('[Tango] write attribute: %s => %r', args[0], args[1:]) elif category == 'attr_query': self.log.debug('[Tango] query attribute properties: %s', args[0]) elif category == 'constructor': self.log.debug('[Tango] device creation: %s', args[0]) try: result = func(*args, **kwds) return self._com_return(result, info) except Exception as err: self._com_raise(err, info) elif category == 'internal': self.log.debug('[Tango integration] internal: %s%r', func.__name__, args) else: self.log.debug('[Tango] call: %s%r', func.__name__, args) return self._com_retry(info, func, *args, **kwds) # hide the wrapping wrap.__name__ = func.__name__ return wrap def _com_return(self, result, info): # explicit check for loglevel to avoid expensive reprs if self.loglevel == 'debug': if isinstance(result, PyTango.DeviceAttribute): the_repr = repr(result.value)[:300] else: # This line explicitly logs '=> None' for commands which # does not return a value. This indicates that the command # execution ended. the_repr = repr(result)[:300] self.log.debug('\t=> %s', the_repr) return result def _tango_exc_desc(self, err): exc = str(err) if err.args: exc = err.args[0] # Can be str or DevError if isinstance(exc, PyTango.DevError): return describe_dev_error(exc) return exc def _tango_exc_reason(self, err): if err.args and isinstance(err.args[0], PyTango.DevError): return err.args[0].reason.strip() return '' def _com_warn(self, retries, name, err, info): if self._tango_exc_reason(err) in FATAL_REASONS: self._com_raise(err, info) if retries == self.comtries - 1: self.log.warning('%s failed, retrying up to %d times: %s', info, retries, self._tango_exc_desc(err)) def _com_raise(self, err, info): reason = self._tango_exc_reason(err) exclass = REASON_MAPPING.get( reason, EXC_MAPPING.get(type(err), NicosError)) fulldesc = self._tango_exc_desc(err) self.log.debug('[Tango] error: %s', fulldesc) raise exclass(self, fulldesc)
def _setMode(self, mode): IPCModBus._setMode(self, mode) if mode == SIMULATION: self._connection = HardwareStub(self)