diff options
Diffstat (limited to 'lib/taurus/core/tango')
-rw-r--r-- | lib/taurus/core/tango/__init__.py | 77 | ||||
-rw-r--r-- | lib/taurus/core/tango/__taurus_plugin__ | 0 | ||||
-rw-r--r-- | lib/taurus/core/tango/enums.py | 96 | ||||
-rw-r--r-- | lib/taurus/core/tango/img/__init__.py | 41 | ||||
-rw-r--r-- | lib/taurus/core/tango/img/img.py | 146 | ||||
-rw-r--r-- | lib/taurus/core/tango/search.py | 123 | ||||
-rw-r--r-- | lib/taurus/core/tango/starter.py | 234 | ||||
-rwxr-xr-x | lib/taurus/core/tango/tangoattribute.py | 531 | ||||
-rw-r--r-- | lib/taurus/core/tango/tangoconfiguration.py | 436 | ||||
-rw-r--r-- | lib/taurus/core/tango/tangodatabase.py | 380 | ||||
-rw-r--r-- | lib/taurus/core/tango/tangodevice.py | 265 | ||||
-rw-r--r-- | lib/taurus/core/tango/tangofactory.py | 747 |
12 files changed, 3076 insertions, 0 deletions
diff --git a/lib/taurus/core/tango/__init__.py b/lib/taurus/core/tango/__init__.py new file mode 100644 index 00000000..1b350514 --- /dev/null +++ b/lib/taurus/core/tango/__init__.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +############################################################################# +## +## This file is part of Taurus +## +## http://taurus-scada.org +## +## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +## Taurus is free software: you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## Taurus is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public License +## along with Taurus. If not, see <http://www.gnu.org/licenses/>. +## +############################################################################# + +""" +Tango extension for taurus core mode. +The Tango extension implements :mod:`taurus.core` objects that connect to Tango +objects. The official scheme name is, obviously, 'tango'. As Tango is the default +taurus scheme, when specifing a tango model name, the scheme prefix ('tango://') can +be omited. + +You should never create objects of tango classes directly. Instead you +should use the :class:`taurus.core.taurusmanager.TaurusManager` and :class:`taurus.core.taurusfactory.TaurusFactory` APIs +to access all elements. + +For example, to get a reference to the Tango attribute my/tango/device/state you +should do something like:: + + >>> import taurus + >>> my_state = taurus.Attribute('tango://my/tango/device/state') + +In fact, because the taurus default extension is Tango, you could omit the 'tango://' +prefix from the previous code:: + + >>> import taurus + >>> my_state = taurus.Attribute('my/tango/device/state') + +The same is applied to a device:: + + >>> import taurus + >>> my_device = taurus.Device('my/tango/device') + +...to a database:: + + >>> import taurus + >>> db = taurus.Database('homer:10000') + +...and an attribute configuration:: + + >>> import taurus + >>> me_state_config = taurus.Attribute('my/tango/device/state?configuration') + +The way to get access to the Tango factory is:: + + >>> import taurus + >>> factory = taurus.Factory() +""" + +__docformat__ = "restructuredtext" + +from enums import * +from tangodatabase import * +from tangodevice import * +from tangofactory import * +from tangoattribute import * +from tangoconfiguration import * diff --git a/lib/taurus/core/tango/__taurus_plugin__ b/lib/taurus/core/tango/__taurus_plugin__ new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/lib/taurus/core/tango/__taurus_plugin__ diff --git a/lib/taurus/core/tango/enums.py b/lib/taurus/core/tango/enums.py new file mode 100644 index 00000000..08fc9c8f --- /dev/null +++ b/lib/taurus/core/tango/enums.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python + +############################################################################# +## +## This file is part of Taurus +## +## http://taurus-scada.org +## +## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +## Taurus is free software: you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## Taurus is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public License +## along with Taurus. If not, see <http://www.gnu.org/licenses/>. +## +############################################################################# + +"""This module contains all basic tango enumerations""" + +__all__ = ["TangoObjectType", "EVENT_TO_POLLING_EXCEPTIONS", + "FROM_TANGO_TO_NUMPY_TYPE", "FROM_TANGO_TO_STR_TYPE"] + +__docformat__ = "restructuredtext" + +from taurus.core.taurusbasetypes import SubscriptionState +from taurus.core.util.enumeration import Enumeration + +TangoObjectType = Enumeration("TangoObjectType", + ["Database", "Server", "Class", "Device", + "Attribute","Property","Configuration", + "Object"]) + +import numpy +import PyTango + +# The exception reasons that will force switching from events to polling +# API_AttributePollingNotStarted - the attribute does not support events. +# Don't try to resubscribe. +# API_DSFailedRegisteringEvent - same exception then the one above but higher +# in the stack +# API_NotificationServiceFailed - Problems in notifd, it was not able to +# register the event. +# API_EventChannelNotExported - the notifd is not running +# API_EventTimeout - after a successfull register the the device server +# and/or notifd shuts down/crashes +# API_CommandNotFound - Added on request from ESRF (Matias Guijarro). They have +# a DS in java (doesn't have events) and the only way they +# found to fix the event problem was to add this exception +# type here. Maybe in future this will be solved in a better +# way +# API_BadConfigurationProperty - the device server is not running +EVENT_TO_POLLING_EXCEPTIONS = ('API_AttributePollingNotStarted', + 'API_DSFailedRegisteringEvent', + 'API_NotificationServiceFailed', + 'API_EventChannelNotExported', + 'API_EventTimeout', + 'API_EventPropertiesNotSet', + 'API_CommandNotFound', +) +# 'API_BadConfigurationProperty') + +FROM_TANGO_TO_NUMPY_TYPE = { + PyTango.DevBoolean : numpy.bool8, + PyTango.DevUChar : numpy.ubyte, + PyTango.DevShort : numpy.short, + PyTango.DevUShort : numpy.ushort, + PyTango.DevLong : numpy.int32, + PyTango.DevULong : numpy.uint32, + PyTango.DevLong64 : numpy.int64, + PyTango.DevULong64 : numpy.uint64, + PyTango.DevString : numpy.str, + PyTango.DevDouble : numpy.float64, + PyTango.DevFloat : numpy.float32, +} + +FROM_TANGO_TO_STR_TYPE = { + PyTango.DevBoolean : 'bool8', + PyTango.DevUChar : 'ubyte', + PyTango.DevShort : 'short', + PyTango.DevUShort : 'ushort', + PyTango.DevLong : 'int32', + PyTango.DevULong : 'uint32', + PyTango.DevLong64 : 'int64', + PyTango.DevULong64 : 'uint64', + PyTango.DevString : 'str', + PyTango.DevDouble : 'float64', + PyTango.DevFloat : 'float32', +} diff --git a/lib/taurus/core/tango/img/__init__.py b/lib/taurus/core/tango/img/__init__.py new file mode 100644 index 00000000..eb95ef43 --- /dev/null +++ b/lib/taurus/core/tango/img/__init__.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +############################################################################# +## +## This file is part of Taurus +## +## http://taurus-scada.org +## +## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +## Taurus is free software: you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## Taurus is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public License +## along with Taurus. If not, see <http://www.gnu.org/licenses/>. +## +############################################################################# + +"""The img package. It contains specific part of tango devices dedicated to +images (CCDs, detectors, etc)""" + +__docformat__ = 'restructuredtext' + +from img import * + +def registerExtensions(): + """Registers the image extensions in the :class:`taurus.core.tango.TangoFactory`""" + import taurus + factory = taurus.Factory('tango') + factory.registerDeviceClass('PyImageViewer', PyImageViewer) + factory.registerDeviceClass('ImgGrabber', ImgGrabber) + factory.registerDeviceClass('ImgBeamAnalyzer', ImgBeamAnalyzer) + factory.registerDeviceClass('CCDPVCAM', CCDPVCAM) + factory.registerDeviceClass('Falcon', Falcon)
\ No newline at end of file diff --git a/lib/taurus/core/tango/img/img.py b/lib/taurus/core/tango/img/img.py new file mode 100644 index 00000000..da2df08a --- /dev/null +++ b/lib/taurus/core/tango/img/img.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python + +############################################################################# +## +## This file is part of Taurus +## +## http://taurus-scada.org +## +## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +## Taurus is free software: you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## Taurus is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public License +## along with Taurus. If not, see <http://www.gnu.org/licenses/>. +## +############################################################################# + +"""The img submodule. It contains specific device implementation for CCDs and +2D detectors""" + +__all__ = ['ImageDevice', 'ImageCounterDevice', 'PyImageViewer', 'ImgGrabber', + 'CCDPVCAM', 'ImgBeamAnalyzer', 'Falcon'] + +__docformat__ = 'restructuredtext' + + +from taurus.core.taurusbasetypes import TaurusEventType +from taurus.core.tango import TangoDevice +from taurus.core.util.containers import CaselessDict, CaselessList + +class ImageDevice(TangoDevice): + """A class encapsulating a generic image device""" + + def __init__(self, name, image_name='image', **kw): + self.call__init__(TangoDevice, name, **kw) + self.setImageAttrName(image_name) + + def addImageAttrName(self, attr_name): + if attr_name in self._image_attr_names: + return + self._image_attr_names.append(attr_name) + + def setImageAttrName(self, attr_name): + self._image_attr_names = CaselessList() + self.addImageAttrName(attr_name) + + def getImageAttrName(self, idx=0): + return self._image_attr_names[0] + + def getImageAttrNames(self): + return self._image_attr_names + + +class ImageCounterDevice(ImageDevice): + """A class encapsulating a generic image device that has an image counter + attribute""" + + def __init__(self, name, image_name='image', **kw): + self._image_data = CaselessDict() + self.call__init__(ImageDevice, name, **kw) + self._image_id_attr = self.getAttribute(self.getImageIDAttrName()) + self._image_id_attr.addListener(self) + + def _setDirty(self, names=None): + names = names or self.getImageAttrNames() + for n in names: + d = self._image_data.get(n, (True, None)) + self._image_data[n] = True, d[1] + + def _getDirty(self, names=None): + names = names or self.getImageAttrNames() + dirty = [] + for name in names: + d = self._image_data.get(name) + if d is None or d[0] == True: + dirty.append(name) + return names + + def getImageIDAttrName(self): + return 'imagecounter' + + def eventReceived(self, evt_src, evt_type, evt_value): + if evt_src == self._image_id_attr: + if evt_type == TaurusEventType.Change: + self._setDirty() + self.fireEvent(evt_type, evt_value) + else: + ImageDevice.eventReceived(self, evt_src, evt_type, evt_value) + + def getImageData(self, names=None): + if names is None: + names = self.getImageAttrNames() + elif isinstance(names, str): + names = (names,) + + fetch = self._getDirty(names) + + try: + data = self.read_attributes(fetch) + for d in data: + self._image_data[d.name] = False, d + except: + pass + return self._image_data + +PyImageViewer = ImageCounterDevice +ImgGrabber = ImageCounterDevice +CCDPVCAM = ImageCounterDevice + +class Falcon(ImageCounterDevice): + + def __init__(self, name, image_name='image', **kw): + self._color = False + self.call__init__(ImageCounterDevice, name, image_name=image_name, **kw) + self.imgFormat_Attr = self.getAttribute("imageformat") + self.imgFormat_Attr.addListener(self) + + def eventReceived(self, evt_src, evt_type, evt_value): + if evt_src == self.getAttribute("imageformat"): + if evt_type in (TaurusEventType.Change, TaurusEventType.Periodic): + self._color = evt_value.value.lower() == "rgb24" + return + ImageCounterDevice.eventReceived(self, evt_src, evt_type, evt_value) + + def getImageData(self, names=None): + data = ImageCounterDevice.getImageData(self, names=names) + if self._color: + for k, v in data.items(): + s = v[1].value.shape + v[1].value = v[1].value.reshape((s[0], s[1]/3, 3)) + return data + + +class ImgBeamAnalyzer(ImageCounterDevice): + + def __init__(self, name, image_name='roiimage', **kw): + self.call__init__(ImageCounterDevice, name, image_name, **kw) + diff --git a/lib/taurus/core/tango/search.py b/lib/taurus/core/tango/search.py new file mode 100644 index 00000000..0cc84286 --- /dev/null +++ b/lib/taurus/core/tango/search.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python + +############################################################################# +## +## This file is part of Taurus +## +## http://taurus-scada.org +## +## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +## Taurus is free software: you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## Taurus is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public License +## along with Taurus. If not, see <http://www.gnu.org/licenses/>. +## +############################################################################# + +""" +search.py: methods for getting matching device/attribute/alias names from Tango database + +These methods have been borrowed from fandango modules. +""" + +import re +import taurus + +############################################################################### +# Utils + +def searchCl(regexp,target): + return re.search(extend_regexp(regexp).lower(),target.lower()) + +def matchCl(regexp,target): + return re.match(extend_regexp(regexp).lower(),target.lower()) + +def is_regexp(s): + return any(c in s for c in '.*[]()+?') + +def extend_regexp(s): + s = str(s).strip() + if '.*' not in s: + s = s.replace('*','.*') + if '.*' not in s: + if ' ' in s: s = s.replace(' ','.*') + if '/' not in s: s = '.*'+s+'.*' + else: + if not s.startswith('^'): s = '^'+s + if not s.endswith('$'): s = s+'$' + return s + +def isString(s): + typ = s.__class__.__name__.lower() + return not hasattr(s,'__iter__') and 'str' in typ and 'list' not in typ + +def isCallable(obj): + return hasattr(obj,'__call__') + +def isMap(obj): + return hasattr(obj,'has_key') or hasattr(obj,'items') + +def isDictionary(obj): + return isMap(obj) + +def isSequence(obj): + typ = obj.__class__.__name__.lower() + return (hasattr(obj,'__iter__') or 'list' in typ) and not isString(obj) and not isMap(obj) + +def split_model_list(modelNames): + '''convert str to list if needed (commas and whitespace are considered as separators)''' + if isString(modelNames): #isinstance(modelNames,(basestring,Qt.QString)): + modelNames = str(modelNames).replace(',',' ') + modelNames = modelNames.split() + if isSequence(modelNames): #isinstance(modelNames,(list.Qt.QStringList)): + modelNames = [str(s) for s in modelNames] + return modelNames + +def get_matching_devices(expressions,limit=0,exported=False): + """ + Searches for devices matching expressions, if exported is True only running devices are returned + """ + db = taurus.Database() + all_devs = [s.lower() for s in db.get_device_name('*','*')] + #This code is used to get data from multiples hosts + #if any(not fun.matchCl(rehost,expr) for expr in expressions): all_devs.extend(get_all_devices(exported)) + #for expr in expressions: + #m = fun.matchCl(rehost,expr) + #if m: + #host = m.groups()[0] + #print 'get_matching_devices(%s): getting %s devices ...'%(expr,host) + #odb = PyTango.Database(*host.split(':')) + #all_devs.extend('%s/%s'%(host,d) for d in odb.get_device_name('*','*')) + result = [e for e in expressions if e.lower() in all_devs] + expressions = [extend_regexp(e) for e in expressions if e not in result] + result.extend(filter(lambda d: any(matchCl(extend_regexp(e),d) for e in expressions),all_devs)) + return result + +def get_device_for_alias(alias): + db = taurus.Database() + try: return db.get_device_alias(alias) + except Exception,e: + if 'no device found' in str(e).lower(): return None + return None #raise e + +def get_alias_for_device(dev): + db = taurus.Database() + try: + result = db.get_alias(dev) #.get_database_device().DbGetDeviceAlias(dev) + return result + except Exception,e: + if 'no alias found' in str(e).lower(): return None + return None #raise e + +def get_alias_dict(exp='*'): + tango = taurus.Database() + return dict((k,tango.get_device_alias(k)) for k in tango.get_device_alias_list(exp))
\ No newline at end of file diff --git a/lib/taurus/core/tango/starter.py b/lib/taurus/core/tango/starter.py new file mode 100644 index 00000000..65b129e9 --- /dev/null +++ b/lib/taurus/core/tango/starter.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python + +############################################################################# +## +## This file is part of Taurus +## +## http://taurus-scada.org +## +## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +## Taurus is free software: you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## Taurus is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public License +## along with Taurus. If not, see <http://www.gnu.org/licenses/>. +## +############################################################################# + +"""This module provides a very simple API for starting and killing device +servers + +It is not a replacement of the Tango Starter Device Server since this is much +more limited in scope. +""" + +__docformat__ = 'restructuredtext' + + +import os +import time +import subprocess +import PyTango + +from taurus.core.util.log import Logger + +_log = Logger('Starter') + +class Starter(object): + '''Abstract class for managing (starting, stopping, registering and + removing) a Tango Device Server. + + Derived classes should provide the methods for starting and stopping a + device. + ''' + + def __init__(self, ds_name): + ''' + :param ds_name: (str) Device Server name in the form "server/instance" + ''' + self.ds_name = ds_name + self.dserver_name = 'dserver/%s' % ds_name + try: + self.dserver = PyTango.DeviceProxy(self.dserver_name) + self.serverExisted = True + except PyTango.DevFailed: #not registered? + self.dserver = None + self.serverExisted = False + self._addedDevices = [] + + def hardKill(self): + raise NotImplementedError('hardKill method is mandatory') + + def terminate(self): + raise NotImplementedError('terminate method is mandatory') + + def start(self): + raise NotImplementedError('start method is mandatory') + + def stopDs(self, synch=True, hard_kill=False, wait_seconds=10): + if hard_kill: + _log.info('Hard killing server %s...' % self.ds_name) + self.hardKill() + else: + _log.info('Stopping server %s...' % self.ds_name) + self.terminate() + if not synch: + return + for i in range(wait_seconds): + _log.debug('Waiting for server %s to get stopped. Iteration: %d'% \ + (self.ds_name, i)) + if self.isRunning(): + time.sleep(1) + else: + time.sleep(3) #TODO: figure out why we have to wait here + _log.info('Server %s has been stopped' % self.ds_name) + return + _log.warning('Server %s did not stop within %d seconds'% + (self.ds_name, wait_seconds)) + + def startDs(self, synch=True, wait_seconds=10): + if self.isRunning(): + _log.warning('Server already running') + return + _log.info('Starting server %s...' % self.ds_name) + self.start() + if not synch: + return + for i in range(wait_seconds): + _log.debug('Waiting for server %s to get started... %d'% \ + (self.ds_name, i)) + if self.isRunning(): + _log.info('Server %s has been started' % self.ds_name) + time.sleep(3) #TODO: figure out why we have to wait here + return + else: + time.sleep(1) + _log.warning('Server %s did not start within %d seconds'% \ + (self.ds_name, wait_seconds)) + + def addNewDevice(self, device, klass=None): + """ + Register a device of this server in the DB (register the server if + not present) + e.g. to create Starter in an init script:: + + addNewDevice('sys/tg_test/foobar', klass='TangoTest') + + :param klass: class name. If None passed, it defaults to the server + name (without instance name) + """ + if device in self._addedDevices: + _log.warning('%s already added. Skipping'%device) + return + if klass is None: + klass = self.ds_name.split('/')[0] + # in case the device is already defined, skipping... + db = PyTango.Database() + try: + db.import_device(device) + _log.warning('%s already exists. Skipping' % device) + return + except: + pass + # register the device, + # in case the server did not exist before this will define it + dev_info = PyTango.DbDevInfo() + dev_info.name = device + dev_info.klass = klass + dev_info.server = self.ds_name + db.add_device(dev_info) + #create proxy to dserver + self.dserver = PyTango.DeviceProxy(self.dserver_name) + #keep track of added devices + self._addedDevices.append(device) + + def cleanDb(self, force=False): + '''removes devices which have been added by :meth:`addNewDevice` + and then removes the server if it was registered by this starter + (or, if force is True, it removes the server in any case) + + :param force: (bool) force removing of the Server even if it was + not registered within this starter + ''' + for device in self._addedDevices: + PyTango.Database().delete_device(device) + _log.info('Deleted device %s'%device) + if (self.serverExisted or len(self._addedDevices)==0) and not force: + msg = ('%s was not registered by this starter. Not removing. '+ + 'Use %s.cleanDb(force=True) to force cleanup')% \ + (self.ds_name, self.__class__.__name__) + _log.warning(msg) + else: + self.stopDs(hard_kill=True) + PyTango.Database().delete_server(self.ds_name) + _log.info('Deleted Server %s'%self.ds_name) + + + def isRunning(self): + if self.dserver is None: + return False + try: + self.dserver.ping() + except PyTango.DevFailed: + return False + return True + + +class ProcessStarter(Starter): + '''A :class:`Starter` which uses subprocess to start and stop a device + server. + ''' + + def __init__(self, execname, ds_name): + ''' + :param execname: (str) path to the executable to launch the server + :param ds_name: (str) Device Server name in the form "server/instance" + ''' + super(ProcessStarter, self).__init__(ds_name) + self.ds_instance = ds_name.split('/')[1] + self.exec_name = os.path.abspath(execname) + self.process = None + + def start(self): + dev_null = open(os.devnull, 'wb') + args = [self.exec_name, self.ds_instance] + self.process = subprocess.Popen(args, stdout=dev_null, stderr=dev_null) + + def terminate(self): + if self.process: + self.process.terminate() + else: + _log.warning('Process not started, cannot terminate it.') + + def hardKill(self): + if self.process: + self.process.kill() + else: + _log.warning('Process not started, cannot terminate it.') + + +if __name__ == '__main__': + + from taurus.test.resource import getResourcePath + timeoutExec = getResourcePath('taurus.qt.qtgui.button.test.res', 'Timeout') + s = ProcessStarter(timeoutExec, 'Timeout/test_removeme') + devname = 'testing/timeout/temp-1' + s.addNewDevice(devname, klass='Timeout') + s.startDs() + try: + print 'Is running:', s.isRunning() + print "ping:", PyTango.DeviceProxy(devname).ping() + except Exception, e: + print e + s.stopDs() + s.cleanDb(force=False) + + diff --git a/lib/taurus/core/tango/tangoattribute.py b/lib/taurus/core/tango/tangoattribute.py new file mode 100755 index 00000000..db53e0bc --- /dev/null +++ b/lib/taurus/core/tango/tangoattribute.py @@ -0,0 +1,531 @@ +#!/usr/bin/env python + +############################################################################# +## +## This file is part of Taurus +## +## http://taurus-scada.org +## +## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +## Taurus is free software: you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## Taurus is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public License +## along with Taurus. If not, see <http://www.gnu.org/licenses/>. +## +############################################################################# + +"""This module contains all taurus tango attribute""" + +__all__ = ["TangoAttribute", "TangoStateAttribute", "TangoAttributeEventListener"] + +__docformat__ = "restructuredtext" + +# -*- coding: utf-8 -*- +import time +import threading +import PyTango +import numpy + +from taurus import Factory, Manager +from taurus.core.taurusattribute import TaurusAttribute, TaurusStateAttribute +from taurus.core.taurusbasetypes import TaurusEventType, TaurusSerializationMode, \ + SubscriptionState +from taurus.core.taurusoperation import WriteAttrOperation +from taurus.core.util.event import EventListener +from .enums import EVENT_TO_POLLING_EXCEPTIONS + +DataType = PyTango.CmdArgType + +class TangoAttribute(TaurusAttribute): + + # helper class property that stores a reference to the corresponding factory + _factory = None + + def __init__(self, name, parent, **kwargs): + + # the last attribute value + self.__attr_value = None + + # the last attribute error + self.__attr_err = None + + # the last time the attribute was read + self.__attr_timestamp = 0 + + # the change event identifier + self.__chg_evt_id = None + + # reference to the configuration object + self.__attr_config = None + + # current event subscription state + self.__subscription_state = SubscriptionState.Unsubscribed + self.__subscription_event = threading.Event() + + self.call__init__(TaurusAttribute, name, parent, **kwargs) + + def __getattr__(self, name): + return getattr(self._getRealConfig(), name) + + def _getRealConfig(self): + """ Returns the current configuration of the attribute.""" + + if self.__attr_config is None: + cfg_name = "%s?configuration" % self.getFullName() + from taurus.core.tango import TangoConfiguration # @todo... + self.__attr_config = TangoConfiguration(cfg_name, self) + return self.__attr_config + + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # TaurusModel necessary overwrite + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + @classmethod + def factory(cls): + if cls._factory is None: + cls._factory = Factory(scheme='tango') + return cls._factory + + def getNewOperation(self, value): + attr_value = PyTango.AttributeValue() + attr_value.name = self.getSimpleName() + attr_value.value = self.encode(value) + op = WriteAttrOperation(self, attr_value) + return op + + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # PyTango connection + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + + def isNumeric(self, inc_array=False): + cfg = self._getRealConfig() + if not cfg: + self.warning("attribute does not contain information") + return False + if inc_array and not self.isScalar(): + return False + + type = cfg.getType() + return PyTango.is_numerical_type(type, inc_array=inc_array) + + def isInteger(self, inc_array=False): + cfg = self._getRealConfig() + if not cfg: + self.warning("attribute does not contain information") + return False + if inc_array and not self.isScalar(): + return False + + type = cfg.getType() + return PyTango.is_int_type(type, inc_array=inc_array) + + def isFloat(self, inc_array=False): + cfg = self._getRealConfig() + if not cfg: + self.warning("attribute does not contain information") + return False + if inc_array and not self.isScalar(): + return False + + type = cfg.getType() + return PyTango.is_float_type(type, inc_array=inc_array) + + def isBoolean(self, inc_array=False): + cfg = self._getRealConfig() + if not cfg: + self.warning("attribute does not contain information") + return False + if inc_array and not self.isScalar(): + return False + + type = cfg.getType() + if inc_array: + return type in (DataType.DevBoolean, DataType.DevVarBooleanArray) + else: + return type == DataType.DevBoolean + + def isState(self): + cfg = self._getRealConfig() + if not cfg: + self.warning("attribute does not contain information") + return False + return cfg.getType() == DataType.DevState + + def getDisplayValue(self, cache=True): + attrvalue = self.getValueObj(cache=cache) + if not attrvalue: + return None + v = attrvalue.value + + return self.displayValue(v) + + def encode(self, value): + """Translates the given value into a tango compatible value according to + the attribute data type""" + + attrvalue = None + + if self._getRealConfig() is None: + self.warning("attribute does not contain information") + return value + + fmt = self.getDataFormat() + type = self.getType() + if fmt == PyTango.SCALAR: + if type == DataType.DevDouble: + attrvalue = float(value) + elif type == DataType.DevFloat: + # We encode to float, but rounding to Tango::DevFloat precision + # see: http://sf.net/p/sardana/tickets/162 + attrvalue = float(numpy.float32(value)) + elif PyTango.is_int_type(type): + #attrvalue = int(value) + attrvalue = long(value) #changed as a partial workaround to a problem in PyTango writing to DevULong64 attributes (see ALBA RT#29793) + elif type == DataType.DevBoolean: + try: + attrvalue = bool(int(value)) + except: + attrvalue = str(value).lower() == 'true' + elif type == DataType.DevUChar: + attrvalue = chr(value) + elif type == DataType.DevState or type == DataType.DevEncoded: + attrvalue = value + else: + attrvalue = str(value) + elif fmt in (PyTango.SPECTRUM, PyTango.IMAGE): + attrvalue = value + else: + attrvalue = str(value) + return attrvalue + + def decode(self, attr_value): + """Decodes a value that was received from PyTango into the expected + representation""" + return attr_value + + def write(self, value, with_read=True): + """ Write the value in the Tango Device Attribute """ + try: + dev = self.getParentObj() + name, value = self.getSimpleName(), self.encode(value) + if self.isUsingEvents() or not self.isReadWrite(): + with_read = False + if with_read: + try: + result = dev.write_read_attribute(name, value) + except AttributeError: + # handle old PyTango + dev.write_attribute(name, value) + result = dev.read_attribute(name) + except PyTango.DevFailed, df: + for err in df: + # Handle old device servers + if err.reason == 'API_UnsupportedFeature': + dev.write_attribute(name, value) + result = dev.read_attribute(name) + break; + else: + raise df + result = self.decode(result) + self.poll(single=False, value=result, time=time.time()) + return result + else: + dev.write_attribute(name, value) + except PyTango.DevFailed, df: + err = df[0] + self.error("[Tango] write failed (%s): %s" % (err.reason, err.desc)) + raise df + except Exception, e: + self.error("[Tango] write failed: %s" % str(e)) + raise e + + def poll(self, **kwargs): + """ Notify listeners when the attribute has been polled""" + single = kwargs.get('single', True) + try: + if single: + self.read(cache=False) + else: + self.__attr_value = self.decode(kwargs.get('value')) + self.__attr_err = kwargs.get('error') + self.__attr_timestamp = kwargs.get('time', time.time()) + if self.__attr_err: + raise self.__attr_err + except PyTango.DevFailed, df: + self.__subscription_event.set() + self.debug("Error polling: %s" % df[0].desc) + self.traceback() + self.fireEvent(TaurusEventType.Error, self.__attr_err) + except Exception, e: + self.__subscription_event.set() + self.debug("Error polling: %s" % str(e)) + self.fireEvent(TaurusEventType.Error, self.__attr_err) + else: + self.__subscription_event.set() + self.fireEvent(TaurusEventType.Periodic, self.__attr_value) + + def read(self, cache=True): + """ Returns the current value of the attribute. + if cache is set to True (default) or the attribute has events + active then it will return the local cached value. Otherwise it will + read the attribute value from the tango device.""" + curr_time = time.time() + + if cache: + dt = (curr_time - self.__attr_timestamp) * 1000 + if dt < self.getPollingPeriod(): + if self.__attr_value is not None: + return self.__attr_value + elif self.__attr_err is not None: + raise self.__attr_err + + if not cache or (self.__subscription_state in (SubscriptionState.PendingSubscribe, SubscriptionState.Unsubscribed) and not self.isPollingActive()): + try: + dev = self.getParentObj() + v = dev.read_attribute(self.getSimpleName()) + self.__attr_value, self.__attr_err, self.__attr_timestamp = self.decode(v), None, curr_time + return self.__attr_value + except PyTango.DevFailed, df: + self.__attr_value, self.__attr_err, self.__attr_timestamp = None, df, curr_time + err = df[0] + self.debug("[Tango] read failed (%s): %s", err.reason, err.desc) + raise df + except Exception, e: + self.__attr_value, self.__attr_err = None, e + self.debug("[Tango] read failed: %s", e) + raise e + elif self.__subscription_state in (SubscriptionState.Subscribing, SubscriptionState.PendingSubscribe): + self.__subscription_event.wait() + + + if self.__attr_err is not None: + raise self.__attr_err + return self.__attr_value + + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # API for listeners + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + + def __fireRegisterEvent(self, listener): + #fire a first configuration event + #cfg = self._getRealConfig().getValueObj() + #if cfg: + # self.fireEvent(TaurusEventType.Config, cfg, listener) + #else: + + #fire a first change event + try: + v = self.read() + self.fireEvent(TaurusEventType.Change, v, listener) + except: + self.fireEvent(TaurusEventType.Error, self.__attr_err, listener) + + def addListener(self, listener): + """ Add a TaurusListener object in the listeners list. + If it is the first element and Polling is enabled starts the + polling mechanism. + If the listener is already registered nothing happens.""" + cfg = self._getRealConfig() + cfg.addListener(listener) + + listeners = self._listeners + initial_subscription_state = self.__subscription_state + + ret = TaurusAttribute.addListener(self, listener) + if not ret: + return ret + + assert len(listeners) >= 1 + + if self.__subscription_state == SubscriptionState.Unsubscribed and len(listeners) == 1: + self._subscribeEvents() + + #if initial_subscription_state == SubscriptionState.Subscribed: + if len(listeners) > 1 and (initial_subscription_state == SubscriptionState.Subscribed or self.isPollingActive()): + sm = self.getSerializationMode() + if sm == TaurusSerializationMode.Concurrent: + Manager().addJob(self.__fireRegisterEvent, None, (listener,)) + else: + self.__fireRegisterEvent((listener,)) + return ret + + def removeListener(self, listener): + """ Remove a TaurusListener from the listeners list. If polling enabled + and it is the last element the stop the polling timer. + If the listener is not registered nothing happens.""" + ret = TaurusAttribute.removeListener(self, listener) + + cfg = self._getRealConfig() + cfg.removeListener(listener) + + if not ret: + return ret + + if self.hasListeners(): + return ret + + if self.__subscription_state != SubscriptionState.Unsubscribed: + self._unsubscribeEvents() + + return ret + + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # API for attribute configuration + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + + def setConfigEx(self, config): + self.getParentObj().set_attribute_config([config]) + + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # PyTango event handling (private) + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + + def isUsingEvents(self): + return self.__subscription_state == SubscriptionState.Subscribed + + def _process_event_exception(self, ex): + pass + + def _subscribeEvents(self): + """ Enable subscription to the attribute events. If change events are + not supported polling is activated """ + + # We have to register for configuration events before registering for + # change events because when a change event occurs we need to have + # configuration info in order to know how to decode the value + self._getRealConfig().addListener(self) + + + self.trace("Subscribing to change events...") + + dev = self.getParentObj() + if dev is None: + self.debug("failed to subscribe change events: device is None") + return + dev = dev.getHWObj() + if dev is None: + self.debug("failed to subscribe change events: HW is None") + return + + self.__subscription_event = threading.Event() + + try: + self.__subscription_state = SubscriptionState.Subscribing + self.__chg_evt_id = dev.subscribe_event(self.getSimpleName(), + PyTango.EventType.CHANGE_EVENT, + self, []) + + except: + self.__subscription_state = SubscriptionState.PendingSubscribe + self._activatePolling() + self.__chg_evt_id = dev.subscribe_event(self.getSimpleName(), + PyTango.EventType.CHANGE_EVENT, + self, [], True) + + def _unsubscribeEvents(self): + # Careful in this method: This is intended to be executed in the cleanUp + # so we should not access external objects from the factory, like the + # parent object + self._getRealConfig().removeListener(self) + if not self.__chg_evt_id is None and not self._dev_hw_obj is None: + self.trace("Unsubscribing to change events (ID=%d)" % self.__chg_evt_id) + try: + self._dev_hw_obj.unsubscribe_event(self.__chg_evt_id) + self.__chg_evt_id = None + except PyTango.DevFailed, df: + if len(df.args) and df[0].reason == 'API_EventNotFound': + # probably tango shutdown as been initiated before and it + # unsubscribed from events itself + pass + else: + self.debug("Failed: %s" % df[0].desc) + self.trace(str(df)) + self._deactivatePolling() + self.__subscription_state = SubscriptionState.Unsubscribed + + def push_event(self, event): + """Method invoked by the PyTango layer when a change event occurs. + Default implementation propagates the event to all listeners.""" + + curr_time = time.time() + manager = Manager() + sm = self.getSerializationMode() + if not event.err: + self.__attr_value, self.__attr_err, self.__attr_timestamp = self.decode(event.attr_value), None, curr_time + self.__subscription_state = SubscriptionState.Subscribed + self.__subscription_event.set() + if not self.isPollingForced(): + self._deactivatePolling() + listeners = tuple(self._listeners) + if sm == TaurusSerializationMode.Concurrent: + manager.addJob(self.fireEvent, None, TaurusEventType.Change, + self.__attr_value, listeners=listeners) + else: + self.fireEvent(TaurusEventType.Change, self.__attr_value, + listeners=listeners) + elif event.errors[0].reason in EVENT_TO_POLLING_EXCEPTIONS: + if self.isPollingActive(): + return + self.info("Activating polling. Reason: %s", event.errors[0].reason) + self.__subscription_state = SubscriptionState.PendingSubscribe + self._activatePolling() + else: + self.__attr_value, self.__attr_err = None, PyTango.DevFailed(*event.errors) + self.__subscription_state = SubscriptionState.Subscribed + self.__subscription_event.set() + self._deactivatePolling() + listeners = tuple(self._listeners) + if sm == TaurusSerializationMode.Concurrent: + manager.addJob(self.fireEvent, None, TaurusEventType.Error, + self.__attr_err, listeners=listeners) + else: + self.fireEvent(TaurusEventType.Error, self.__attr_err, + listeners=listeners) + + def isInformDeviceOfErrors(self): + return False + + +class TangoStateAttribute(TangoAttribute, TaurusStateAttribute): + def __init__(self, name, parent, **kwargs): + self.call__init__(TangoAttribute, name, parent, **kwargs) + self.call__init__(TaurusStateAttribute, name, parent, **kwargs) + + +class TangoAttributeEventListener(EventListener): + """A class that listens for an event with a specific value + + Note: Since this class stores for each event value the last timestamp when + it occured, it should only be used for events for which the event value + domain (possible values) is limited and well known (ex: an enum)""" + + def __init__(self, attr): + EventListener.__init__(self) + self.attr = attr + attr.addListener(self) + + def eventReceived(self, s, t, v): + if t not in (TaurusEventType.Change, TaurusEventType.Periodic): + return + self.fireEvent(v.value) + + + +def test1(): + import numpy + from taurus import Attribute + a = Attribute('sys/tg_test/1/ulong64_scalar') + + a.write(numpy.uint64(88)) + +if __name__ == "__main__": + test1() + diff --git a/lib/taurus/core/tango/tangoconfiguration.py b/lib/taurus/core/tango/tangoconfiguration.py new file mode 100644 index 00000000..23808929 --- /dev/null +++ b/lib/taurus/core/tango/tangoconfiguration.py @@ -0,0 +1,436 @@ +#!/usr/bin/env python + +############################################################################# +## +## This file is part of Taurus +## +## http://taurus-scada.org +## +## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +## Taurus is free software: you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## Taurus is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public License +## along with Taurus. If not, see <http://www.gnu.org/licenses/>. +## +############################################################################# + +"""This module contains all taurus tango attribute configuration""" + +__all__ = ["TangoConfiguration"] + +__docformat__ = "restructuredtext" + +# -*- coding: utf-8 -*- +import threading +import weakref +import time + +import PyTango + +from taurus import Factory, Manager +from taurus.core.taurusbasetypes import TaurusEventType +from taurus.core.taurusconfiguration import TaurusConfiguration +from .enums import EVENT_TO_POLLING_EXCEPTIONS + +class TangoConfiguration(TaurusConfiguration): + + def __init__(self, name, parent, storeCallback = None): + self._events_working = False + self.call__init__(TaurusConfiguration, name, parent, storeCallback) + + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # TaurusModel necessary overwrite + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + + # helper class property that stores a reference to the corresponding factory + _factory = None + + @classmethod + def factory(cls): + if cls._factory is None: + cls._factory = Factory("tango") + return cls._factory + + def __getattr__(self, name): + if self._attr_info is None: + return + try: + return getattr(self._attr_info,name) + except: + try: + return getattr(self._attr_info.alarms,name) + except: + try: + return getattr(self._attr_info.events,name) + except AttributeError: + raise AttributeError + + def isWrite(self, cache=True): + return self.getWritable(cache) == PyTango.AttrWriteType.WRITE + + def isReadOnly(self, cache=True): + return self.getWritable(cache) == PyTango.AttrWriteType.READ + + def isReadWrite(self, cache=True): + return self.getWritable(cache) == PyTango.AttrWriteType.READ_WRITE + + def isScalar(self, cache=True): + return self.getDataFormat(cache) == PyTango.AttrDataFormat.SCALAR + + def isSpectrum(self, cache=True): + return self.getDataFormat(cache) == PyTango.AttrDataFormat.SPECTRUM + + def isImage(self, cache=True): + return self.getDataFormat(cache) == PyTango.AttrDataFormat.IMAGE + + def encode(self, value): + """Translates the given value into a tango compatible value according to + the attribute data type + value must be a valid """ + return value + + def getValueObj(self, cache=True): + """ Returns the current configuration for the attribute. + if cache is set to True (default) and the the configuration has + events active then it will return the local cached value. Otherwise + it will read from the tango layer.""" + if cache and self._attr_info is not None: + return self._attr_info + + curr_time = time.time() + + dt = (curr_time - self._attr_timestamp)*1000 + if dt < TangoConfiguration.DftTimeToLive: + return self._attr_info + + self._attr_timestamp = curr_time + try: + dev = self._getDev() + v = dev.attribute_query(self._getAttrName()) + self._attr_info = self.decode(v) + except PyTango.DevFailed, df: + err = df[0] + self.debug("[Tango] read configuration failed (%s): %s" % (err.reason, err.desc)) + except Exception, e: + self.debug("[Tango] read configuration failed: %s" % str(e)) + return self._attr_info + + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # API for listeners + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + + def __fireRegisterEvent(self, listener): + value = self.getValueObj() + if value is not None: + self.fireEvent(TaurusEventType.Config, value, listener) + + def addListener(self, listener): + """ Add a TaurusListener object in the listeners list. + If the listener is already registered nothing happens.""" + ret = TaurusConfiguration.addListener(self, listener) + if not ret: + return ret + + #fire a first configuration event + #if len(self._listeners) > 1 or not self._events_working: + Manager().addJob(self.__fireRegisterEvent, None, (listener,)) + return ret + + def removeListener(self, listener): + """ Remove a TaurusListener from the listeners list. + If it is the last listener, unsubscribe from events.""" + ret = TaurusConfiguration.removeListener(self, listener) + if not ret: + return ret + if not self.hasListeners(): + self._unsubscribeEvents() + return ret + + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # PyTango event handling (private) + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + + def _subscribeEvents(self): + """ Enable subscription to the attribute configuration events.""" + self.trace("Subscribing to configuration events...") + dev = self._getDev() + if dev is None: + self.debug("failed to subscribe config events: device is None") + return + dev = dev.getHWObj() + if dev is None: + self.debug("failed to subscribe config events: HW is None") + return + + attrname = self._getAttrName() + try: + self._cfg_evt_id = dev.subscribe_event(attrname, + PyTango.EventType.ATTR_CONF_EVENT, + self, [], True) + except PyTango.DevFailed, e: + self.debug("Unexpected exception trying to subscribe to CONFIGURATION events.") + self.traceback() + # Subscription failed either because event mechanism is not available + # or because the device server is not running. + # The first possibility is assumed so an attempt to get the configuration + # manually is done + try: + self.getValueObj(cache=False) + except: + self.debug("Error getting attribute configuration") + self.traceback() + + def _unsubscribeEvents(self): + # Careful in this method: This is intended to be executed in the cleanUp + # so we should not access external objects from the factory, like the + # parent object + if self._cfg_evt_id and not self._dev_hw_obj is None: + self.trace("Unsubscribing to configuration events (ID=%s)" % str(self._cfg_evt_id)) + try: + self._dev_hw_obj.unsubscribe_event(self._cfg_evt_id) + self._cfg_evt_id = None + except PyTango.DevFailed, e: + self.debug("Exception trying to unsubscribe configuration events") + self.trace(str(e)) + + def decode(self, i): + if i is None: + return i + + i.climits = [i.min_value, i.max_value] + i.calarms = [i.min_alarm, i.max_alarm] + i.cwarnings = [i.alarms.min_warning, i.alarms.max_warning] + i.cranges = [i.min_value, i.min_alarm, i.alarms.min_warning, + i.alarms.max_warning, i.max_alarm, i.max_value] + i.range = [i.min_value, i.max_value] + i.alarm = [i.min_alarm, i.max_alarm] + i.warning = [i.alarms.min_warning, i.alarms.max_warning] + # add dev_name, dev_alias, attr_name, attr_full_name + i.dev_name = self._getDev().getNormalName() + i.dev_alias = self._getDev().getSimpleName() + try: + attr = self._getAttr() + if attr is not None: + i.attr_fullname = self._getAttr().getNormalName() + i.attr_name = self._getAttr().getSimpleName() + else: + self.debug(('TangoConfiguration.decode(%s/%s): ' + + 'self._getAttr() returned None (failed detach?)'), + i.dev_name, i.name) + except: + import traceback + self.warning('at TangoConfiguration.decode(%s/%s)', i.dev_name, i.name) + self.warning(traceback.format_exc()) + i.attr_name = i.attr_fullname = '' + + # %6.2f is the default value that Tango sets when the format is + # unassigned. This is only good for float types! So for other + # types I am changing this value. + # There's a bug about this in the core TangoC++ project, so + # this code may become useless someday. + if i.format == '%6.2f': + if PyTango.is_float_type(i.data_type, inc_array=True): + pass + elif PyTango.is_int_type(i.data_type, inc_array=True): + i.format = '%d' + elif i.data_type in (PyTango.CmdArgType.DevString, PyTango.CmdArgType.DevVarStringArray): + i.format = '%s' + return i + + def push_event(self, event): + if event.err: + if event.errors[0].reason not in EVENT_TO_POLLING_EXCEPTIONS: + self._attr_timestamp = time.time() + self._events_working = True + else: + self._events_working = False + return + if self._getAttr() is None and not self._listeners: + #=================================================================== + # This is a safety net to catch "zombie" TangoConfiguration objects + # when they get called. + # If you get here, there is some bug elsewhere which should be + # investigated. + # Without this safety net, you would get exceptions. + # We assume that a TangoConfiguration object which has no listeners + # and which is not associated to a TangoAttribute, is a "zombie". + self.warning('"Zombie" object (%s) received an event. Unsubscribing it.', repr(self)) + self._unsubscribeEvents() + return + #=================================================================== + self._events_working = True + self._attr_timestamp = time.time() + self._attr_info = self.decode(event.attr_conf) + listeners = tuple(self._listeners) + #Manager().addJob(self._push_event, None, event) + Manager().addJob(self.fireEvent, None, TaurusEventType.Config, self._attr_info, listeners=listeners) + + + #=========================================================================== + # Some methods reimplemented from TaurusConfiguration + #=========================================================================== + + def getMaxDimX(self, cache=True): + c = self.getValueObj(cache=cache) + if c: + return c.max_dim_x + return None + + def getMaxDimY(self, cache=True): + c = self.getValueObj(cache=cache) + if c: + return c.max_dim_y + return None + + def getType(self, cache=True): + c = self.getValueObj(cache=cache) + if c: + return c.data_type + return None + + def getRange(self, cache=True): + return self.getLimits(cache=cache) + + def getLimits(self, cache=True): + c = self.getValueObj(cache=cache) + if c: + return c.climits + return None + + def getRanges(self, cache=True): + c = self.getValueObj(cache=cache) + if c: + return list(c.cranges) + return None + + def getMinAlarm(self, cache=True): + c = self.getValueObj(cache=cache) + if c: + return c.min_alarm + return None + + def getMaxAlarm(self, cache=True): + c = self.getValueObj(cache=cache) + if c: + return c.max_alarm + return None + + def getAlarms(self, cache=True): + c = self.getValueObj(cache=cache) + if c: + return list(c.calarms) + return None + + def getMinWarning(self, cache=True): + c = self.getValueObj(cache=cache) + if c: + return c.alarms.min_warning + return None + + def getMaxWarning(self, cache=True): + c = self.getValueObj(cache=cache) + if c: + return c.alarms.max_warning + return None + + def getWarnings(self, cache=True): + c = self.getValueObj(cache=cache) + if c: + return list(c.cwarnings) + return None + + def getParam(self, param_name): + config = self.getValueObj() + if config: + if param_name.endswith('warning') or param_name.endswith('alarm'): + config = config.alarms + try: + return getattr(config, param_name) + except: + return None + + def setParam(self, param_name, value): + config = self.getValueObj() + if config is None: + return + if param_name.endswith('warning') or param_name.endswith('alarm'): + config = config.alarms + setattr(config, param_name, value) + self._applyConfig() + + def setDescription(self,descr): + config = self.getValueObj() + if config: + config.description = descr + self._applyConfig() + + def setLabel(self,lbl): + config = self.getValueObj() + if config: + config.label = lbl + self._applyConfig() + + def setUnit(self,unit): + config = self.getValueObj() + if config: + config.unit = unit + self._applyConfig() + + def setStandardUnit(self,standard_unit): + config = self.getValueObj() + if config: + config.standard_unit = standard_unit + self._applyConfig() + + def setDisplayUnit(self,display_unit): + config = self.getValueObj() + if config: + config.display_unit = display_unit + self._applyConfig() + + def setFormat(self,fmt): + config = self.getValueObj() + if config: + config.format = fmt + self._applyConfig() + + def setLimits(self,low, high): + config = self.getValueObj() + if config: + l_str, h_str = str(low), str(high) + config.cranges[0] = config.min_value = l_str + config.cranges[5] = config.max_value = h_str + config.climits = [l_str, h_str] + self._applyConfig() + + def setWarnings(self,low, high): + config = self.getValueObj() + if config: + l_str, h_str = str(low), str(high) + config.cranges[2] = config.alarms.min_warning = l_str + config.cranges[3] = config.alarms.max_warning = h_str + config.cwarnings = [l_str, h_str] + self._applyConfig() + + def setAlarms(self,low, high): + config = self.getValueObj() + if config: + l_str, h_str = str(low), str(high) + config.cranges[1] = config.min_alarm = config.alarms.min_alarm = l_str + config.cranges[4] = config.max_alarm = config.alarms.max_alarm = h_str + config.calarms = [l_str, h_str] + self._applyConfig() + + def _applyConfig(self): + config = self.getValueObj() + if config: + self.getParentObj().setConfigEx(config) diff --git a/lib/taurus/core/tango/tangodatabase.py b/lib/taurus/core/tango/tangodatabase.py new file mode 100644 index 00000000..f6c501b7 --- /dev/null +++ b/lib/taurus/core/tango/tangodatabase.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python + +############################################################################# +## +## This file is part of Taurus +## +## http://taurus-scada.org +## +## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +## Taurus is free software: you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## Taurus is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public License +## along with Taurus. If not, see <http://www.gnu.org/licenses/>. +## +############################################################################# + +"""This module contains all taurus tango database""" + +__all__ = ["TangoDevInfo", "TangoServInfo", "TangoDatabaseCache", + "TangoDatabase" ] + +__docformat__ = "restructuredtext" + +import os + +import PyTango +import PyTango.utils + +from taurus import Factory +from taurus.core.taurusbasetypes import TaurusSWDevHealth +from taurus.core.taurusdatabase import TaurusDatabaseCache, TaurusDevInfo, \ + TaurusAttrInfo, TaurusServInfo, TaurusDevClassInfo, TaurusDevTree, \ + TaurusServerTree +from taurus.core.util.containers import CaselessDict +from taurus.core.taurusdatabase import TaurusDatabase + +InvalidAlias = "nada" + + +class TangoDevInfo(TaurusDevInfo): + + def __init__(self, container, name=None, full_name=None, alias=None, server=None, klass=None, exported=False, host=None): + super(TangoDevInfo, self).__init__(container, name=name, full_name=full_name, alias=alias, server=server, klass=klass, exported=exported, host=host) + self._alivePending = False + + def attributes(self): + if self._attributes is None or len(self._attributes) == 0: + self.refreshAttributes() + return self._attributes + + def getHWObj(self): + db = self.container().db + name = self.name() + full_name = db.getFullName() + "/" + name + dev = None + try: + dev = db.factory().getDevice(full_name).getHWObj() + except: + pass + return dev + + def alive(self): + if self._alive is None: + if self._alivePending: + return False + self._alivePending = True + try: + dev = self.getHWObj() + state = dev.state() + self._alive = True + except: + self._alive = False + self._alivePending = False + return self._alive + + def health(self): + """Overwrite health so it doesn't call 'alive()' since it can take + a long time for devices that are declared as exported but are in fact + not running (crached, network error, power cut, etc)""" + if not self._health is None: + return self._health + exported = self.exported() + if exported: + self._health = TaurusSWDevHealth.Exported + else: + self._health = TaurusSWDevHealth.NotExported + return self._health + + def refreshAttributes(self): + attrs = [] + try: + dev = self.getHWObj() + if dev is None: + raise PyTango.DevFailed() # @todo: check if this is the right exception to throw + attr_info_list = dev.attribute_list_query_ex() + for attr_info in attr_info_list: + full_name = "%s/%s" % (self.fullName(), attr_info.name) + attr_obj = TaurusAttrInfo(self.container(), + name=attr_info.name.lower(), full_name=full_name.lower(), + device=self, info=attr_info) + attrs.append(attr_obj) + attrs = sorted(attrs, key=lambda attr : attr.name()) + except PyTango.DevFailed as df: + if self.health() == TaurusSWDevHealth.Exported: + self._health = TaurusSWDevHealth.ExportedNotAlive + self.setAttributes(attrs) + + +class TangoServInfo(TaurusServInfo): + + def __init__(self, container, name=None, full_name=None): + super(TangoServInfo, self).__init__(container, name=name, full_name=full_name) + self._alivePending = False + + def alive(self): + if self._alive is None: + if self._alivePending: + return False + try: + self._alivePending = True + alive = True + for d in self.devices().values(): + alive = d.alive() + if not alive: + break + self._alive = alive + except Exception,e: + print "except",e + self._alive = False + self._alivePending = False + return self._alive + + +class TangoDatabaseCache(TaurusDatabaseCache): + + def refresh(self): + db = self.db + + query = "SELECT name, alias, exported, host, server, class FROM device" + + r = db.command_inout("DbMySqlSelect", query) + row_nb, column_nb = r[0][-2], r[0][-1] + results, data = r[0][:-2], r[1] + assert row_nb == len(data) / column_nb + + CD = CaselessDict + #CD = dict + dev_dict, serv_dict, klass_dict, alias_dict = CD(), {}, {}, CD() + + for i in xrange(0, len(data), column_nb): + name, alias, exported, host, server, klass = data[i:i+column_nb] + if name.count("/") != 2: continue # invalid/corrupted entry: just ignore it + if server.count("/") != 1: continue # invalid/corrupted entry: just ignore it + if not len(alias): alias = None + + serv_dict[server] = si = serv_dict.get(server, + TangoServInfo(self, name=server, + full_name=server)) + + klass_dict[klass] = dc = klass_dict.get(klass, + TaurusDevClassInfo(self, + name=klass, + full_name=klass)) + + full_name = "tango://%s/%s" % (db.getFullName(), name) + dev_dict[name] = di = TangoDevInfo(self, name=name, full_name=full_name, + alias=alias, server=si, klass=dc, + exported=exported, host=host) + + si.addDevice(di) + dc.addDevice(di) + if alias is not None: + alias_dict[alias] = di + + self._devices = dev_dict + self._device_tree = TaurusDevTree(dev_dict) + self._server_tree = TaurusServerTree(serv_dict) + self._servers = serv_dict + self._klasses = klass_dict + self._aliases = alias_dict + + def refreshAttributes(self, device): + attrs = [] + try: + db = self.db + name = device.name() + full_name = db.getFullName() + "/" + name + taurus_dev = db.factory().getExistingDevice(full_name) + if taurus_dev is None: + dev = PyTango.DeviceProxy(full_name) + else: + dev = taurus_dev.getHWObj() + attr_info_list = dev.attribute_list_query_ex() + for attr_info in attr_info_list: + full_attr_name = "%s/%s" % (full_name, attr_info.name) + attr_obj = TaurusAttrInfo(self, name=attr_info.name, + full_name=full_attr_name, + device=device, info=attr_info) + attrs.append(attr_obj) + attrs = sorted(attrs, key=lambda attr : attr.name().lower()) + except PyTango.DevFailed as df: + pass + device.setAttributes(attrs) + + +def get_home(): + """ + Find user's home directory if possible. Otherwise raise error. + + :return: user's home directory + :rtype: str + + New in PyTango 7.2.0 + """ + path='' + try: + path=os.path.expanduser("~") + except: + pass + if not os.path.isdir(path): + for evar in ('HOME', 'USERPROFILE', 'TMP'): + try: + path = os.environ[evar] + if os.path.isdir(path): + break + except: pass + if path: + return path + else: + raise RuntimeError('please define environment variable $HOME') + +def get_env_var(env_var_name): + """ + Returns the value for the given environment name + A backup method for old Tango/PyTango versions which don't implement + :meth:`PyTango.ApiUtil.get_env_var` + + Search order: + + * a real environ var + * HOME/.tangorc + * /etc/tangorc + + :param env_var_name: the environment variable name + :type env_var_name: str + :return: the value for the given environment name + :rtype: str + """ + + if env_var_name in os.environ: + return os.environ[env_var_name] + + fname = os.path.join(get_home(), '.tangorc') + if not os.path.exists(fname): + if os.name == 'posix': + fname = "/etc/tangorc" + if not os.path.exists(fname): + return None + + for line in file(fname): + strippedline = line.split('#',1)[0].strip() + + if not strippedline: + #empty line + continue + + tup = strippedline.split('=',1) + if len(tup) !=2: + # illegal line! + continue + + key, val = map(str.strip, tup) + if key == env_var_name: + return val + + +class TangoDatabase(TaurusDatabase): + + def __init__(self,host=None,port=None,parent=None): + pars = () + if host is None or port is None: + try: + host, port = TangoDatabase.get_default_tango_host().rsplit(':', 1) + pars = host, port + except Exception, e: + print "Error getting env TANGO_HOST:", str(e) + else: + pars = host, port + self.dbObj = PyTango.Database(*pars) + self._dbProxy = None + self._dbCache = None + + complete_name = "%s:%s" % (host, port) + self.call__init__(TaurusDatabase, complete_name, parent) + + try: + self.get_class_for_device(self.dev_name()) + except: + # Ok, old tango database. + self.get_class_for_device = self.__get_class_for_device + + @staticmethod + def get_default_tango_host(): + if hasattr(PyTango.ApiUtil, "get_env_var"): + f = PyTango.ApiUtil.get_env_var + else: + f = get_env_var + return f("TANGO_HOST") + + def __get_class_for_device(self, dev_name): + """Backup method when connecting to tango 5 database device server""" + # Ok, old tango database. + serv_name = self.command_inout("DbGetDeviceInfo",dev_name)[1][3] + devs = self.get_device_class_list(serv_name) + dev_name_lower = dev_name.lower() + for i in xrange(len(devs)/2): + idx = i*2 + if devs[idx].lower() == dev_name_lower: + return devs[idx+1] + return None + + def get_device_attribute_list(self, dev_name, wildcard): + return self.command_inout("DbGetDeviceAttributeList", (dev_name, wildcard)) + + # Export the PyTango.Database interface into this object. + # This way we can call for example get_attribute_property on an object of this class + def __getattr__(self, name): + if not self.dbObj is None: + return getattr(self.dbObj,name) + return None + + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # TaurusModel implementation + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + + # helper class property that stores a reference to the corresponding factory + _factory = None + + @classmethod + def factory(cls): + if cls._factory is None: + cls._factory = Factory(scheme='tango') + return cls._factory + + def getValueObj(self,cache=True): + return self.dbObj + + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # Mandatory methods to overwrite from TaurusDatabase + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + + def cache(self): + if self._dbCache is None: + self._dbCache = TangoDatabaseCache(self) + return self._dbCache + + def getElementAlias(self, full_name): + try: + alias = self.getValueObj().get_alias(full_name) + if alias and alias.lower() == InvalidAlias: + alias = None + except: + alias = None + return alias + + def getElementFullName(self, alias): + try: + return self.getValueObj().get_device_alias(alias) + except: + pass + return None diff --git a/lib/taurus/core/tango/tangodevice.py b/lib/taurus/core/tango/tangodevice.py new file mode 100644 index 00000000..2a7114c2 --- /dev/null +++ b/lib/taurus/core/tango/tangodevice.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python + +############################################################################# +## +## This file is part of Taurus +## +## http://taurus-scada.org +## +## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +## Taurus is free software: you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## Taurus is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public License +## along with Taurus. If not, see <http://www.gnu.org/licenses/>. +## +############################################################################# + +"""This module contains all taurus tango database""" + +__all__ = ["TangoDevice"] + +__docformat__ = "restructuredtext" + +import time +import PyTango + +from taurus import Factory +from taurus.core.taurusdevice import TaurusDevice +from taurus.core.taurusbasetypes import TaurusSWDevState, TaurusLockInfo, LockStatus + +DFT_TANGO_DEVICE_DESCRIPTION = "A TANGO device" + +class _TangoInfo(object): + + def __init__(self): + self.dev_class = self.dev_type = 'TangoDevice' + self.doc_url = 'http://www.esrf.fr/computing/cs/tango/tango_doc/ds_doc/' + self.server_host = 'Unknown' + self.server_id = 'Unknown' + self.server_version = 1 + +class TangoDevice(TaurusDevice): + def __init__(self, name, **kw): + """Object initialization.""" + self.call__init__(TaurusDevice, name, **kw) + + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # TaurusModel necessary overwrite + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # helper class property that stores a reference to the corresponding factory + _factory = None + + @classmethod + def factory(cls): + if cls._factory is None: + cls._factory = Factory(scheme='tango') + return cls._factory + + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # TaurusDevice necessary overwrite + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + def _createHWObject(self): + try: + return PyTango.DeviceProxy(self.getFullName()) + except PyTango.DevFailed, e: + self.warning('Could not create HW object: %s' % (e[0].desc)) + self.traceback() + + def isValidDev(self): + '''see: :meth:`TaurusDevice.isValid`''' + return self._deviceObj is not None + + def lock(self, force=False): + li = self.getLockInfo() + if force: + if self.getLockInfo().status == TaurusLockInfo.Locked: + self.unlock(force=True) + return self.getHWObj().lock() + + def unlock(self, force=False): + return self.getHWObj().unlock(force) + + def getLockInfo(self, cache=False): + lock_info = self._lock_info + if cache and lock_info.status != LockStatus.Unknown: + return lock_info + try: + dev = self.getHWObj() + li = PyTango.LockerInfo() + locked = dev.get_locker(li) + msg = "%s " % self.getSimpleName() + if locked: + lock_info.id = pid = li.li + lock_info.language = li.ll + lock_info.host = host = li.locker_host + lock_info.klass = li.locker_class + if dev.is_locked_by_me(): + status = LockStatus.LockedMaster + msg += "is locked by you!" + else: + status = LockStatus.Locked + msg += "is locked by PID %s on %s" % (pid, host) + else: + lock_info.id = None + lock_info.language = None + lock_info.host = host = None + lock_info.klass = None + status = LockStatus.Unlocked + msg += "is not locked" + lock_info.status = status + lock_info.status_msg = msg + except: + self._lock_info = lock_info = TaurusLockInfo() + return lock_info + + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + # Protected implementation + #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- + + def _server_state(self): + state = None + try: + self.dev.ping() + state = TaurusSWDevState.Running + except: + try: + if self.dev.import_info().exported: + state = TaurusSWDevState.Crash + else: + state = TaurusSWDevState.Shutdown + except: + state = TaurusSWDevState.Shutdown + return state + + def decode(self, event_value): + if isinstance(event_value, PyTango.DeviceAttribute): + new_sw_state = TaurusSWDevState.Running + elif isinstance(event_value, PyTango.DevFailed): + new_sw_state = self._handleExceptionEvent(event_value) + elif isinstance(event_value, int): # TaurusSWDevState + new_sw_state = event_value + else: + self.info("Unexpected value to decode: %s" % str(event_value)) + new_sw_state = TaurusSWDevState.Crash + + value = PyTango.DeviceAttribute() + value.value = new_sw_state + + return value + + def _handleExceptionEvent(self, event_value): + """Handles the tango error event and returns the proper SW state.""" + + new_sw_state = TaurusSWDevState.Uninitialized + reason = event_value[0].reason + # API_EventTimeout happens when: + # 1 - the server where the device is running shuts down/crashes + # 2 - the notifd shuts down/crashes + if reason == 'API_EventTimeout': + if not self._deviceSwState in self.SHUTDOWNS: + serv_state = self._server_state() + # if the device is running it means that it must have been + # the event system that failed + if serv_state == TaurusSWDevState.Running: + new_sw_state = TaurusSWDevState.EventSystemShutdown + else: + new_sw_state = serv_state + else: + # Keep the old state + new_sw_state = self._deviceSwState + + # API_BadConfigurationProperty happens when: + # 1 - at client startup the server where the device is is not + # running. + elif reason == 'API_BadConfigurationProperty': + assert(self._deviceSwState != TaurusSWDevState.Running) + new_sw_state = TaurusSWDevState.Shutdown + + # API_EventChannelNotExported happens when: + # 1 - at client startup the server is running but the notifd + # is not + elif reason == 'API_EventChannelNotExported': + new_sw_state = TaurusSWDevState.EventSystemShutdown + return new_sw_state + + def _getDefaultDescription(self): + return DFT_TANGO_DEVICE_DESCRIPTION + + def __pollResult(self, attrs, ts, result, error=False): + if error: + for attr in attrs.values(): + attr.poll(single=False, value=None, error=result, time=ts) + return + + for da in result: + if da.has_failed: + v, err = None, PyTango.DevFailed(*da.get_err_stack()) + else: + v, err = da, None + attr = attrs[da.name] + attr.poll(single=False, value=v, error=err, time=ts) + + def __pollAsynch(self, attrs): + ts = time.time() + try: + req_id = self.read_attributes_asynch(attrs.keys()) + except PyTango.DevFailed as e: + return False, e, ts + return True, req_id, ts + + def __pollReply(self, attrs, req_id, timeout=None): + ok, req_id, ts = req_id + if not ok: + self.__pollResult(attrs, ts, req_id, error=True) + return + + if timeout is None: + timeout = 0 + timeout = int(timeout*1000) + result = self.read_attributes_reply(req_id, timeout) + self.__pollResult(attrs, ts, result) + + def poll(self, attrs, asynch=False, req_id=None): + '''optimized by reading of multiple attributes in one go''' + if req_id is not None: + return self.__pollReply(attrs, req_id) + + if asynch: + return self.__pollAsynch(attrs) + + error = False + ts = time.time() + try: + result = self.read_attributes(attrs.keys()) + except PyTango.DevFailed as e: + error = True + result = e + self.__pollResult(attrs, ts, result, error=error) + + def _repr_html_(self): + try: + info = self.getHWObj().info() + except: + info = _TangoInfo() + txt = """\ +<table> + <tr><td>Short name</td><td>{simple_name}</td></tr> + <tr><td>Standard name</td><td>{normal_name}</td></tr> + <tr><td>Full name</td><td>{full_name}</td></tr> + <tr><td>Device class</td><td>{dev_class}</td></tr> + <tr><td>Server</td><td>{server_id}</td></tr> + <tr><td>Documentation</td><td><a target="_blank" href="{doc_url}">{doc_url}</a></td></tr> +</table> +""".format(simple_name=self.getSimpleName(), normal_name=self.getNormalName(), + full_name=self.getFullName(), dev_class=info.dev_class, + server_id=info.server_id, doc_url=info.doc_url) + return txt diff --git a/lib/taurus/core/tango/tangofactory.py b/lib/taurus/core/tango/tangofactory.py new file mode 100644 index 00000000..a9ada49e --- /dev/null +++ b/lib/taurus/core/tango/tangofactory.py @@ -0,0 +1,747 @@ +#!/usr/bin/env python + +############################################################################# +## +## This file is part of Taurus +## +## http://taurus-scada.org +## +## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain +## +## Taurus is free software: you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## Taurus is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public License +## along with Taurus. If not, see <http://www.gnu.org/licenses/>. +## +############################################################################# + +"""This module contains all taurus tango attribute configuration""" + +__all__ = ["TangoFactory"] + +__docformat__ = "restructuredtext" + +import sys +import os +import threading +import PyTango + +from taurus.core.taurusfactory import TaurusFactory +from taurus.core.taurusbasetypes import OperationMode, MatchLevel +from taurus.core.taurusexception import TaurusException, DoubleRegistration +from taurus.core.tauruspollingtimer import TaurusPollingTimer +from taurus.core.util.enumeration import Enumeration +from taurus.core.util.log import Logger +from taurus.core.util.singleton import Singleton +from taurus.core.util.containers import CaselessWeakValueDict, CaselessDict + +from .tangodatabase import TangoDatabase, InvalidAlias +from .tangoattribute import TangoAttribute, TangoStateAttribute +from .tangodevice import TangoDevice +from .tangoconfiguration import TangoConfiguration + +_Database = TangoDatabase +_Attribute = TangoAttribute +_StateAttribute = TangoStateAttribute +_Device = TangoDevice +_Configuration = TangoConfiguration + + +class TangoFactory(Singleton, TaurusFactory, Logger): + """A Singleton class designed to provide Tango related objects. + + The TangoFactory model containning the Factory for the Tango scheme + + Tango Factory uses the Taurus object naming (URI based):: + + foo://username:password@example.com:8042/over/there/index.dtb;type=animal?name=ferret#nose + \_/ \________________/\_________/ \__/\_________/ \___/ \_/ \_________/ \_________/ \__/ + | | | | | | | | | | + scheme userinfo hostname port path filename extension parameter(s) query fragment + \____________________________/ + | + authority + + For Tango: + + - The 'scheme' must be the string "tango" (lowercase mandatory) + - The 'authority' is the Tango database (<hostname> and <port> mandatory) + - The 'path' is the Tango object, which can be a Device or Attribute. + For device it must have the format _/_/_ or alias + For attribute it must have the format _/_/_/_ or devalias/_ + - The 'filename' and 'extension' are always empty + - The 'parameter' is always empty + - The 'the query' is valid when the 'path' corresponds to an Attribute. Valid + queries must have the format configuration=<config param>. Valid + configuration parameters are: label, format, description, unit, display_unit, + standard_unit, max_value, min_value, max_alarm, min_alarm, + max_warning, min_warning. in this case the Tango object is a Configuration + """ + + #: the list of schemes that this factory supports. For this factory: 'tango' + #: is the only scheme + schemes = ("tango",) + + def __init__(self): + """ Initialization. Nothing to be done here for now.""" + pass + + def init(self, *args, **kwargs): + """Singleton instance initialization. + **For internal usage only**""" + name = self.__class__.__name__ + self.call__init__(Logger, name) + self.call__init__(TaurusFactory) + self._polling_enabled = True + self.reInit() + + def reInit(self): + """Reinitialize the singleton""" + self._default_tango_host = None + self.operation_mode = OperationMode.ONLINE + self.dft_db = None + self.tango_db = CaselessWeakValueDict() + self.tango_db_queries = CaselessWeakValueDict() + self.tango_configs = CaselessWeakValueDict() + self.tango_attrs = CaselessWeakValueDict() + self.tango_devs = CaselessWeakValueDict() + self.tango_dev_queries = CaselessWeakValueDict() + self.tango_alias_devs = CaselessWeakValueDict() + self.polling_timers = {} + + # Plugin device classes + self.tango_dev_klasses = {} + + # Plugin attribute classes + self.tango_attr_klasses = CaselessDict() + self.tango_attr_klasses["state"] = _StateAttribute + + def cleanUp(self): + """Cleanup the singleton instance""" + self.trace("[TangoFactory] cleanUp") + for k,v in self.tango_attrs.items(): v.cleanUp() + for k,v in self.tango_configs.items(): v.cleanUp() + for k,v in self.tango_dev_queries.items(): v.cleanUp() + for k,v in self.tango_devs.items(): v.cleanUp() + self.dft_db = None + for k,v in self.tango_db_queries.items(): v.cleanUp() + for k,v in self.tango_db.items(): v.cleanUp() + self.reInit() + + def getExistingAttributes(self): + """Returns a new dictionary will all registered attributes on this factory + + :return: dictionary will all registered attributes on this factory + :rtype: dict""" + return dict(self.tango_attrs) + + def getExistingDevices(self): + """Returns a new dictionary will all registered devices on this factory + + :return: dictionary will all registered devices on this factory + :rtype: dict""" + return dict(self.tango_devs) + + def getExistingDatabases(self): + """Returns a new dictionary will all registered databases on this factory + + :return: dictionary will all registered databases on this factory + :rtype: dict""" + return dict(self.tango_db) + + def getExistingConfigurations(self): + """Returns a new dictionary will all registered configurations on this factory + + :return: dictionary will all registered configurations on this factory + :rtype: dict""" + return dict(self.tango_configs) + + def set_default_tango_host(self, tango_host): + """Sets the new default tango host. + + :param tango_host: (str) the new tango host + """ + self._default_tango_host = tango_host + self.dft_db = None + + def registerAttributeClass(self, attr_name, attr_klass): + """Registers a new attribute class for the attribute name. + + :param attr_name: (str) attribute name + :param attr_klass: (taurus.core.tango.TangoAttribute) the new class that + will handle the attribute + """ + self.tango_attr_klasses[attr_name] = attr_klass + + def unregisterAttributeClass(self, attr_name): + """Unregisters the attribute class for the given attribute + If no class was registered before for the given attribute, this call + as no effect + + :param attr_name: (str) attribute name + """ + if self.tango_attr_klasses.has_key(attr_name): + del self.tango_attr_klasses[attr_name] + + def registerDeviceClass(self, dev_klass_name, dev_klass): + """Registers a new python class to handle tango devices of the given tango class name + + :param dev_klass_name: (str) tango device class name + :param dev_klass: (taurus.core.tango.TangoDevice) the new class that will + handle devices of the given tango class name + """ + self.tango_dev_klasses[dev_klass_name] = dev_klass + + def unregisterDeviceClass(self, dev_klass_name): + """Unregisters the class for the given tango class name + If no class was registered before for the given attribute, this call + as no effect + + :param dev_klass_name: (str) tango device class name + """ + if self.tango_dev_klasses.has_key(dev_klass_name): + del self.tango_dev_klasses[dev_klass_name] + + def findObjectClass(self,absolute_name): + """ + Obtain the class object corresponding to the given name. + + :param absolute_name: (str) the object absolute name string + + :return: (taurus.core.taurusmodel.TaurusModel) a class object that should be a subclass of a taurus.core.taurusmodel.TaurusModel + :raise: (taurus.core.taurusexception.TaurusException) if the given name is invalid. + """ + objType = None + try: + if _Database.isValid(absolute_name, MatchLevel.NORMAL_COMPLETE): + objType = _Database + elif _Device.isValid(absolute_name, MatchLevel.NORMAL_COMPLETE): + objType = _Device + elif _Attribute.isValid(absolute_name, MatchLevel.NORMAL_COMPLETE): + objType = _Attribute + elif _Configuration.isValid(absolute_name, MatchLevel.NORMAL_COMPLETE): + objType = _Configuration + elif _Database.isValid(absolute_name, MatchLevel.SHORT): + objType = _Database + elif _Device.isValid(absolute_name, MatchLevel.SHORT): + objType = _Device + elif _Attribute.isValid(absolute_name, MatchLevel.SHORT): + objType = _Attribute + elif _Configuration.isValid(absolute_name, MatchLevel.SHORT): + objType = _Configuration + except: + self.debug("Not able to find Object class for %s" % absolute_name, exc_info=1) + return objType + + def getDatabase(self, db_name = None): + """ + Obtain the object corresponding to the given database name or the + default database if db_name is None. + If the corresponding database object already exists, the existing + instance is returned. Otherwise a new instance is stored and returned. + + :param db_name: (str) database name string alias. If None, the + default database is used + + :return: (taurus.core.tangodatabase.TangoDatabase) database object + :raise: (taurus.core.taurusexception.TaurusException) if the given alias is invalid. + """ + ret = None + if db_name is None: + if self.dft_db is None: + try: + if self._default_tango_host is None: + self.dft_db = _Database() + else: + db_name = self._default_tango_host + validator = _Database.getNameValidator() + params = validator.getParams(db_name) + if params is None: + raise TaurusException("Invalid default Tango database name %s" % db_name) + host, port = params.get('host'),params.get('port') + self.dft_db = _Database(host,port) + except: + self.debug("Could not create Database", exc_info=1) + raise + db_name = self.dft_db.getFullName() + self.tango_db[db_name] = self.dft_db + ret = self.dft_db + else: + ret = self.tango_db.get(db_name) + if not ret is None: + return ret + validator = _Database.getNameValidator() + params = validator.getParams(db_name) + if params is None: + raise TaurusException("Invalid Tango database name %s" % db_name) + host, port = params.get('host'),params.get('port') + try: + ret = _Database(host,port) + except: + self.debug("Could not create Database %s:%s", host, port, exc_info=1) + + self.tango_db[db_name] = ret + return ret + + def getDevice(self,dev_name,**kw): + """Obtain the object corresponding to the given tango device name. + If the corresponding device already exists, the existing instance + is returned. Otherwise a new instance is stored and returned. + + :param dev_name: (str) tango device name or tango alias for the device. + It should be formed like: <host>:<port>/<tango device name> + - If <host>:<port> is ommited then it will use the + default database. + - <tango device name> can be full tango device name + (_/_/_) or a device alias. + + :return: (taurus.core.tango.TangoDevice) a device object + :raise: (taurus.core.taurusexception.TaurusException) if the given dev_name is invalid. + """ + d = self.tango_devs.get(dev_name) + if d is None: + d = self.tango_alias_devs.get(dev_name) + if d is not None: + return d + + # Simple approach did not work. Lets build a proper device name + if dev_name.lower().startswith("tango://"): + dev_name = dev_name[8:] + + validator = _Device.getNameValidator() + params = validator.getParams(dev_name) + + if params is None: + raise TaurusException("Invalid Tango device name '%s'" % dev_name) + + host,port = params.get('host'),params.get('port') + db = None + if host is None or port is None: + db = self.getDatabase() + host, port = db.get_db_host(), db.get_db_port() + else: + db_name = "%s:%s" % (host,port) + db = self.getDatabase(db_name) + + dev_name = params.get('devicename') + alias = params.get('devalias') + + if dev_name: + try: + alias = db.get_alias(dev_name) + if alias and alias.lower() == InvalidAlias: + alias = None + except: + alias = None + else: + try: + dev_name = db.get_device_alias(alias) + except: + raise TaurusException("Device %s is not defined in %s." % (alias,db.getFullName())) + + full_dev_name = db.getFullName() + "/" + dev_name + if not alias is None: + alias = db.getFullName() + "/" + alias + + d = self.tango_devs.get(full_dev_name) + + if d is None: + try: + dev_klass = self._getDeviceClass(db=db, dev_name=dev_name) + kw['storeCallback'] = self._storeDevice + kw['parent'] = db + d = dev_klass(full_dev_name, **kw) + # device objects will register themselves in this factory + # so there is no need to do it here + except DoubleRegistration: + d = self.tango_devs.get(full_dev_name) + except: + self.debug("Error creating device %s", dev_name, exc_info=1) + raise + return d + + def getAttribute(self,attr_name, **kwargs): + """Obtain the object corresponding to the given attribute name. + If the corresponding attribute already exists, the existing instance + is returned. Otherwise a new instance is stored and returned. + + :param attr_name: (str) attribute name + + :return: (taurus.core.tangoattribute.TangoAttribute) attribute object + :raise: (taurus.core.taurusexception.TaurusException) if the given alias is invalid. + """ + attr = self.tango_attrs.get(attr_name) + + if not attr is None: + return attr + + # Simple approach did not work. Lets build a proper device name + if attr_name.lower().startswith("tango://"): + attr_name = attr_name[8:] + validator = _Attribute.getNameValidator() + params = validator.getParams(attr_name) + + if params is None: + raise TaurusException("Invalid Tango attribute name '%s'" % attr_name) + + host,port = params.get('host'),params.get('port') + + db = None + if host is None or port is None: + db = self.getDatabase() + host, port = db.get_db_host(), db.get_db_port() + else: + db_name = "%s:%s" % (host,port) + db = self.getDatabase(db_name) + + dev_name = params.get('devicename') + + if dev_name is None: + dev = self.getDevice(params.get('devalias')) + dev_name = dev.getFullName() + else: + dev_name = db.getFullName() + "/" + dev_name + + attr_name = params.get('attributename') + full_attr_name = dev_name + "/" + attr_name + + attr = self.tango_attrs.get(full_attr_name) + + if attr is None: + try: + dev = self.getDevice(dev_name) + if dev is not None: + # Do another try in case the Device object created the attribute + # itself. This happens for the 'state' attribute + attr = self.tango_attrs.get(full_attr_name) + if attr is not None: + return attr + try: + attr_klass = self._getAttributeClass(attr_name=attr_name) + kwargs['storeCallback'] = self._storeAttribute + if not kwargs.has_key('pollingPeriod'): + kwargs['pollingPeriod'] = self.getDefaultPollingPeriod() + attr = attr_klass(full_attr_name, dev, **kwargs) + # attribute objects will register themselves in this factory + # so there is no need to do it here + except DoubleRegistration: + attr = self.tango_attrs.get(full_attr_name) + except: + self.debug("Error creating attribute %s", attr_name, exc_info=1) + raise + return attr + + def getAttributeInfo(self,full_attr_name): + """Deprecated: Use :meth:`taurus.core.tango.TangoFactory.getConfiguration` instead. + + Obtain attribute information corresponding to the given attribute name. + If the corresponding attribute info already exists, the existing information + is returned. Otherwise a new information instance is stored and returned. + + :param full_attr_name: (str) attribute name in format: <tango device name>'/'<attribute name> + + :return: (taurus.core.tango.TangoConfiguration) configuration object + """ + self.deprecated("Use getConfiguration(full_attr_name) instead") + attr = self.getAttribute(full_attr_name) + return attr.getConfig() + + def getConfiguration(self,param): + """Obtain the object corresponding to the given attribute or full name. + If the corresponding configuration already exists, the existing instance + is returned. Otherwise a new instance is stored and returned. + + :param param: (taurus.core.taurusattribute.TaurusAttribute or str) attrubute object or full configuration name + + :return: (taurus.core.tango.TangoConfiguration) configuration object + """ + if isinstance(param, str): + return self._getConfigurationFromName(param) + return self._getConfigurationFromAttribute(param) + + def _getAttributeClass(self, **params): + attr_name = params.get("attr_name") + attr_klass = self.tango_attr_klasses.get(attr_name, _Attribute) + return attr_klass + + def _getDeviceClass(self, **params): + db, dev_name = params.get("db"), params.get("dev_name") + if db is None or dev_name is None or len(self.tango_dev_klasses) == 0: + return _Device + else: + tango_dev_klass = db.get_class_for_device(dev_name) + return self.tango_dev_klasses.get(tango_dev_klass, _Device) + + def _getConfigurationFromName(self,cfg_name): + cfg = self.tango_configs.get(cfg_name) + + if cfg is not None: + return cfg + + # Simple approach did not work. Lets build a proper configuration name + if cfg_name.lower().startswith("tango://"): + cfg_name = cfg_name[8:] + + validator = _Configuration.getNameValidator() + params = validator.getParams(cfg_name) + + if params is None: + raise TaurusException("Invalid Tango configuration name %s" % cfg_name) + + host,port = params.get('host'),params.get('port') + db = None + if host is None or port is None: + db = self.getDatabase() + host, port = db.get_db_host(), db.get_db_port() + else: + db_name = "%s:%s" % (host,port) + db = self.getDatabase(db_name) + + dev_name = params.get('devicename') or db.get_device_alias(params.get('devalias')) + dev_name = db.getFullName() + "/" + dev_name + attr_name = params.get('attributename') + attr_name = dev_name + "/" + attr_name + cfg_name = attr_name + "?configuration" + + cfg = self.tango_configs.get(cfg_name) + + if cfg is None: + attrObj = self.getAttribute(attr_name) + cfg = self._getConfigurationFromAttribute(attrObj) + return cfg + + def _getConfigurationFromAttribute(self,attrObj): + cfg = attrObj.getConfig() + cfg_name = attrObj.getFullName() + "?configuration" + self.tango_configs[cfg_name] = cfg + return cfg + + def _storeDevice(self, dev): + name, alias = dev.getFullName(), dev.getSimpleName() + exists = self.tango_devs.get(name) + if not exists is None: + if exists == dev: + msg = "%s has already been registered before" % name + else: + msg = "%s has already been registered before with a different object!" % name + self.debug(msg) + raise DoubleRegistration(msg) + self.tango_devs[name] = dev + if not alias is None and len(alias): + self.tango_alias_devs[alias] = dev + + def _storeAttribute(self, attr): + name = attr.getFullName() + exists = self.tango_attrs.get(name) + if not exists is None: + if exists == attr: + msg = "%s has already been registered before" % name + else: + msg = "%s has already been registered before with a different object!" % name + self.debug(msg) + raise DoubleRegistration(msg) + self.tango_attrs[name] = attr + + def getExistingAttribute(self, attr_name): + """Returns a registered attribute or None if the corresponding attribute + as not been registered. This is used mainly to avoid recursion between + two objects supplied by this factory which can ask for the other object + in the constructor. + + :param attr_name: (str) attribute name + :return: (taurus.core.tango.TangoAttribute or None) attribute object or None + """ + attr = self.tango_attrs.get(attr_name) + + if attr is not None: + return attr + + # Simple approach did not work. Lets build a proper device name + if attr_name.lower().startswith("tango://"): + attr_name = attr_name[8:] + validator = _Attribute.getNameValidator() + params = validator.getParams(attr_name) + + if params is None: + raise TaurusException("Invalid Tango attribute name %s" % attr_name) + + host,port = params.get('host'),params.get('port') + + db = None + if host is None or port is None: + db = self.getDatabase() + host, port = db.get_db_host(), db.get_db_port() + else: + db_name = "%s:%s" % (host,port) + db = self.getDatabase(db_name) + + dev_name = params.get('devicename') + + if dev_name is None: + dev = self.getDevice(params.get('devalias')) + dev_name = dev.getFullName() + else: + dev_name = db.getFullName() + "/" + dev_name + + attr_name = params.get('attributename') + full_attr_name = dev_name + "/" + attr_name + + attr = self.tango_attrs.get(full_attr_name) + return attr + + def getExistingDevice(self, dev_name): + """Returns a registered device or None if the corresponding device + as not been registered. This is used mainly to avoid recursion between + two objects supplied by this factory which can ask for the other object + in the constructor. + + :param dev_name: (str) tango device name or tango alias for the device. + It should be formed like: <host>:<port>/<tango device name> + - If <host>:<port> is ommited then it will use the + default database. + - <tango device name> can be full tango device name + (_/_/_) or a device alias. + :return: (taurus.core.tango.TangoDevice or None) device object or None + """ + + d = self.tango_devs.get(dev_name) + if d is None: + d = self.tango_alias_devs.get(dev_name) + if d is not None: + return d + + # Simple approach did not work. Lets build a proper device name + if dev_name.lower().startswith("tango://"): + dev_name = dev_name[8:] + + validator = _Device.getNameValidator() + params = validator.getParams(dev_name) + + if params is None: + raise TaurusException("Invalid Tango device name %s" % dev_name) + + host,port = params.get('host'),params.get('port') + db = None + if host is None or port is None: + db = self.getDatabase() + host, port = db.get_db_host(), db.get_db_port() + else: + db_name = "%s:%s" % (host,port) + db = self.getDatabase(db_name) + + dev_name = params.get('devicename') + alias = params.get('devalias') + + if dev_name: + try: + alias = db.get_alias(dev_name) + if alias and alias.lower() == InvalidAlias: + alias = None + except: + alias = None + else: + try: + dev_name = db.get_device_alias(alias) + except: + raise TaurusException("Device %s is not defined in %s." % (alias,db.getFullName())) + + full_dev_name = db.getFullName() + "/" + dev_name + if not alias is None: + alias = db.getFullName() + "/" + alias + + return self.tango_devs.get(full_dev_name) + + def removeExistingDevice(self, dev_or_dev_name): + """Removes a previously registered device. + + :param dev_or_dev_name: (str or TangoDevice) device name or device object + """ + if isinstance(dev_or_dev_name, _Device): + dev = dev_or_dev_name + else: + dev = self.getExistingDevice(dev_or_dev_name) + if dev is None: + raise KeyError("Device %s not found" % dev_or_dev_name) + dev.cleanUp() + full_name = dev.getFullName() + if self.tango_devs.has_key(full_name): + del self.tango_devs[full_name] + simp_name = dev.getSimpleName() + if self.tango_alias_devs.has_key(simp_name): + del self.tango_alias_devs[simp_name] + + def removeExistingAttribute(self, attr_or_attr_name): + """Removes a previously registered attribute. + + :param attr_or_attr_name: (str or TangoAttribute) attribute name or attribute object + """ + if isinstance(attr_or_attr_name, _Attribute): + attr = attr_or_attr_name + else: + attr = self.getExistingAttribute(attr_or_attr_name) + if attr is None: + raise KeyError("Attribute %s not found" % attr_or_attr_name) + attr.cleanUp() + full_name = attr.getFullName() + if self.tango_attrs.has_key(full_name): + del self.tango_attrs[full_name] + + def addAttributeToPolling(self, attribute, period, unsubscribe_evts = False): + """Activates the polling (client side) for the given attribute with the + given period (seconds). + + :param attribute: (taurus.core.tango.TangoAttribute) attribute name. + :param period: (float) polling period (in seconds) + :param unsubscribe_evts: (bool) whether or not to unsubscribe from events + """ + tmr = self.polling_timers.get(period, TaurusPollingTimer(period)) + self.polling_timers[period] = tmr + tmr.addAttribute(attribute, self.isPollingEnabled()) + + def removeAttributeFromPolling(self, attribute): + """Deactivate the polling (client side) for the given attribute. If the + polling of the attribute was not previously enabled, nothing happens. + + :param attribute: (str) attribute name. + """ + p = None + for period,timer in self.polling_timers.iteritems(): + if timer.containsAttribute(attribute): + timer.removeAttribute(attribute) + if timer.getAttributeCount() == 0: + p = period + break + if p: + del self.polling_timers[period] + + def isPollingEnabled(self): + """Tells if the local tango polling is enabled + + :return: (bool) wheter or not the polling is enabled + """ + return self._polling_enabled + + def disablePolling(self): + """Disable the application tango polling""" + if not self.isPollingEnabled(): + return + self._polling_enabled = False + for period,timer in self.polling_timers.iteritems(): + timer.stop() + + def enablePolling(self): + """Enable the application tango polling""" + if self.isPollingEnabled(): + return + for period,timer in self.polling_timers.iteritems(): + timer.start() + self._polling_enabled = True + |