summaryrefslogtreecommitdiff
path: root/lib/taurus/core/tango
diff options
context:
space:
mode:
Diffstat (limited to 'lib/taurus/core/tango')
-rw-r--r--lib/taurus/core/tango/__init__.py77
-rw-r--r--lib/taurus/core/tango/__taurus_plugin__0
-rw-r--r--lib/taurus/core/tango/enums.py96
-rw-r--r--lib/taurus/core/tango/img/__init__.py41
-rw-r--r--lib/taurus/core/tango/img/img.py146
-rw-r--r--lib/taurus/core/tango/search.py123
-rw-r--r--lib/taurus/core/tango/starter.py234
-rwxr-xr-xlib/taurus/core/tango/tangoattribute.py531
-rw-r--r--lib/taurus/core/tango/tangoconfiguration.py436
-rw-r--r--lib/taurus/core/tango/tangodatabase.py380
-rw-r--r--lib/taurus/core/tango/tangodevice.py265
-rw-r--r--lib/taurus/core/tango/tangofactory.py747
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
+