#!/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 defines the TangoDevice object
"""
import time
from PyTango import DeviceProxy, DevFailed, LockerInfo, DevState
from taurus.core.taurusdevice import TaurusDevice
from taurus.core.taurusbasetypes import (
TaurusDevState,
TaurusLockInfo,
LockStatus,
TaurusEventType,
)
from taurus.core.util.log import taurus4_deprecation
from taurus.core.tango.enums import DevState as TaurusTangoDevState
__docformat__ = "restructuredtext"
class _TangoInfo(object):
pass
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
[docs]
class TangoDevice(TaurusDevice):
"""A Device object representing an abstraction of the PyTango.DeviceProxy
object in the taurus.core.tango scheme
"""
# helper class property that stores a reference to the corresponding
# factory
_factory = None
_scheme = "tango"
_description = "A Tango Device"
def __init__(self, name="", **kw):
"""Object initialization."""
self.call__init__(TaurusDevice, name, **kw)
self._deviceObj = self._createHWObject()
self._lock_info = TaurusLockInfo()
self._deviceStateObj = None
self._deviceState = TaurusDevState.Undefined
# Export the DeviceProxy interface into this object.
# This way we can call for example read_attribute on an object of this
# class
def __getattr__(self, name):
if name != "_deviceObj" and self._deviceObj is not None:
return getattr(self._deviceObj, name)
cls_name = self.__class__.__name__
raise AttributeError("'%s' has no attribute '%s'" % (cls_name, name))
# def __setattr__(self, name, value):
# if '_deviceObj' in self.__dict__ and self._deviceObj is not None:
# return setattr(self._deviceObj, name, value)
# super(TaurusDevice, self).__setattr__(name, value)
def __contains__(self, key):
"""delegate the contains interface to the device proxy"""
hw = self.getDeviceProxy()
if hw is None:
return False
return hw.__contains__(key)
def __getitem__(self, key):
"""read attribute value using key-indexing syntax (e.g. as in a dict)
on the device
"""
attr = self.getAttribute(key)
return attr.read()
def __setitem__(self, key, value):
"""set attribute value using key-indexing syntax (e.g. as in a dict)
on the device
"""
attr = self.getAttribute(key)
return attr.write(value)
[docs]
def getAttribute(self, attrname):
"""Returns the attribute object given its name"""
slashnb = attrname.count("/")
if slashnb == 0:
attrname = "%s/%s" % (self.getFullName(), attrname)
elif attrname[0] == "/":
attrname = "%s%s" % (self.getFullName(), attrname)
return self.factory().getAttribute(attrname)
@taurus4_deprecation(
alt=".stateObj.read().rvalue [Tango] or " + ".state [agnostic]"
)
def getState(self, cache=True):
stateAttrValue = self.stateObj.read(cache=cache)
if stateAttrValue is not None:
state_rvalue = stateAttrValue.rvalue
return DevState.values[state_rvalue.value]
return None
@taurus4_deprecation(
alt=".stateObj [Tango] or "
+ ".factory.getAttribute(state_full_name) [agnostic]"
)
def getStateObj(self):
return self.stateObj
@taurus4_deprecation(alt="state")
def getSWState(self, cache=True):
raise Exception("getSWState has been removed. Use state instead")
# return self.getValueObj().rvalue
@property
def state(self, cache=True):
"""Reimplemented from :class:`TaurusDevice` to use Tango's state
attribute for diagnosis of the current state. It supports a "cache"
kwarg
:param cache: If True (default), cache will be used when reading the
state attribute of this device
:type cache: bool
:return:
:rtype: TaurusDevState
"""
self._deviceState = TaurusDevState.Undefined
try:
taurus_tango_state = self.stateObj.read(cache).rvalue
except Exception:
return self._deviceState # Undefined
if taurus_tango_state in (
TaurusTangoDevState.FAULT,
TaurusTangoDevState.DISABLE,
TaurusTangoDevState.INIT,
TaurusTangoDevState.UNKNOWN,
):
self._deviceState = TaurusDevState.NotReady
else:
self._deviceState = TaurusDevState.Ready
return self._deviceState
@taurus4_deprecation(alt="state [agnostic] or stateObj.read [Tango]")
def getValueObj(self, cache=True):
"""Deprecated by TEP14.
..warning::
this bck-compat implementation is not perfect because the
rvalue of the returned TangoAttributeValue is now a member of
TaurusDevState instead of TaurusSWDevState
"""
if not cache:
self.warning("Ignoring argument `cache=False`to getValueObj()")
from taurus.core.tango.tangoattribute import TangoAttrValue
ret = TangoAttrValue()
ret.rvalue = self.state
return ret
[docs]
def getDisplayDescrObj(self, cache=True):
desc_obj = super(TangoDevice, self).getDisplayDescrObj(cache)
# extend the info on dev state
ret = []
for name, value in desc_obj:
if name.lower() == "device state" and self.stateObj is not None:
try:
tg_state = self.stateObj.read(cache).rvalue.name
value = "%s (%s)" % (value, tg_state)
except Exception:
value = "cannot read state"
ret.append((name, value))
return ret
[docs]
def cleanUp(self):
self.trace("[TangoDevice] cleanUp")
self._descr = None
if self._deviceStateObj is not None:
self._deviceStateObj.removeListener(self)
self._deviceStateObj = None
self._deviceObj = None
TaurusDevice.cleanUp(self)
@taurus4_deprecation(alt=".state.name")
def getDisplayValue(self, cache=True):
return self.stateObj.read(cache=cache).rvalue.name
def _createHWObject(self):
name = self.getFullName()
if name.startswith("tango-nodb:"):
# adapt the taurus full name to tango convention
name = "tango" + name[10:] + "#dbase=no"
else:
name = self.getSimpleName()
try:
return DeviceProxy(name)
except DevFailed as e:
self.warning("Could not create HW object: %s" % (e.args[0].desc))
self.traceback()
@taurus4_deprecation(alt="getDeviceProxy()")
def getHWObj(self):
return self.getDeviceProxy()
[docs]
def getDeviceProxy(self):
if self._deviceObj is None:
self._deviceObj = self._createHWObject()
return self._deviceObj
@taurus4_deprecation(alt="getDeviceProxy() is not None")
def isValidDev(self):
"""see: :meth:`TaurusDevice.isValid`"""
return self._deviceObj is not None
[docs]
def lock(self, force=False):
_ = self.getLockInfo() # TODO: is this needed? (only if side-effects)
if force:
if self.getLockInfo().status == TaurusLockInfo.Locked:
self.unlock(force=True)
return self.getDeviceProxy().lock()
[docs]
def unlock(self, force=False):
return self.getDeviceProxy().unlock(force)
[docs]
def getLockInfo(self, cache=False):
lock_info = self._lock_info
if cache and lock_info.status != LockStatus.Unknown:
return lock_info
try:
dev = self.getDeviceProxy()
li = 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 Exception:
self._lock_info = lock_info = TaurusLockInfo()
return lock_info
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
# Protected implementation
# -~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
[docs]
def removeListener(self, listener):
ret = TaurusDevice.removeListener(self, listener)
if not ret or self.hasListeners():
return ret # False, None or True
return self.stateObj.removeListener(self)
[docs]
def addListener(self, listener):
weWereListening = self.hasListeners()
ret = TaurusDevice.addListener(self, listener)
if not ret:
return ret
# We are only listening to State if someone is listening to us
if weWereListening:
# We were listening already, so we must fake an event to the new
# subscribed listener with the current value
try:
evt_value = self.__decode(self.stateObj.read())
except Exception:
# the value may not be available (e.g. if device is not ready)
self.debug("Cannot read state")
return ret
listeners = (
hasattr(listener, "__iter__") and listener or [listener]
)
self.fireEvent(TaurusEventType.Change, evt_value, listeners)
else:
# We were not listening to events, but now we have to
self.stateObj.addListener(self)
return ret
[docs]
def eventReceived(self, event_src, event_type, event_value):
if event_type == TaurusEventType.Config:
return
value = self.__decode(event_value)
new_state = value.rvalue
if new_state != self._deviceState:
msg = "Device State changed %s -> %s" % (
self._deviceState.name,
new_state.name,
)
self.debug(msg)
self._deviceState = new_state
self.fireEvent(TaurusEventType.Change, value)
def __decode(self, event_value):
"""Decode events from the state attribute into TangoAttrValues whose
rvalue is the Device state
"""
from taurus.core.tango.tangoattribute import TangoAttrValue
if isinstance(event_value, TangoAttrValue): # for change events (&co)
new_state = TaurusDevState.Ready
elif isinstance(event_value, DevFailed): # for error events
new_state = TaurusDevState.NotReady
else:
self.info("Unexpected event value: %r", event_value)
new_state = TaurusDevState.Undefined
from taurus.core.taurusbasetypes import TaurusModelValue
value = TaurusModelValue()
value.rvalue = new_state
return value
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, 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(list(attrs.keys()))
except 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)
_error = False
try:
result = self.read_attributes_reply(req_id, timeout)
except Exception as e:
result = e
_error = True
self.__pollResult(attrs, ts, result, error=_error)
[docs]
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(list(attrs.keys()))
except DevFailed as e:
error = True
result = e
self.__pollResult(attrs, ts, result, error=error)
def _repr_html_(self):
try:
info = self.getDeviceProxy().info()
except Exception:
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( # noqa
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
@taurus4_deprecation(alt=".description")
def getDescription(self, cache=True):
return self.description
@property
def description(self):
try:
self._description = self.getDeviceProxy().description()
except Exception:
pass
return self._description
@property
def stateObj(self):
if self._deviceStateObj is None:
self._deviceStateObj = self.getAttribute("state")
return self._deviceStateObj