Commit 584c975c authored by Matias Guijarro's avatar Matias Guijarro
Browse files

Merge branch 'icepap' into 'master'

Icepap

See merge request !434
parents 910f56ef a9e05a61
......@@ -97,49 +97,3 @@ class Actuator:
return "UNKNOWN"
return state
class Shutter:
def __init__(self, open=None, close=None, state=None):
self.__open = open
self.__close = close
self.__state = state
self.__opened = False
self.__closed = False
def open(self,timeout=5):
# this is to know which command was asked for,
# in case we don't have a return
self.__opened = True
self.__closed = False
try:
with gevent.Timeout(timeout):
while True:
self.__open()
if self.state() == 'OPENED':
break
else:
gevent.sleep(0.5)
finally:
dispatcher.send("state", self, self.state())
def close(self, timeout=5):
self.__opened = False
self.__closed = True
try:
with gevent.Timeout(timeout):
while True:
self.__close()
if self.state() == 'CLOSED':
break
else:
gevent.sleep(0.5)
finally:
dispatcher.send("state", self, self.state())
def state(self):
if self.__state is not None:
return self.__state()
else:
if self.__opened:
return "OPENED"
elif self.__closed:
return "CLOSED"
else:
return "UNKNOWN"
......@@ -40,6 +40,7 @@ import gevent
import re
import types
import functools
import numpy
#: Default polling time
DEFAULT_POLLING_TIME = 0.02
......@@ -1030,6 +1031,18 @@ class Axis(object):
self.limits(*self.limits(from_config=True))
@lazy_init
def set_event_positions(self, positions):
dial_positions = self.user2dial(numpy.array(positions,dtype=numpy.float))
step_positions = dial_positions * self.steps_per_unit
return self.__controller.set_event_positions(self,step_positions)
@lazy_init
def get_event_positions(self):
step_positions = numpy.array(self.__controller.get_event_positions(self),
dtype=numpy.float)
dial_positions = self.dial2user(step_positions)
return dial_positions / self.steps_per_unit
class AxisRef(object):
"""Object representing a named reference to an :class:`Axis`."""
......
......@@ -62,3 +62,11 @@ class Encoder(object):
self.controller.set_encoder(self, new_value*self.steps_per_unit)
return self.read()
@lazy_init
def set_event_positions(self, positions):
return self.__controller.set_event_positions(self,positions)
@lazy_init
def get_event_positions(self, positions):
return self.__controller.get_event_positions(self)
# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
# Copyright (c) 2017 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
import functools
from gevent import lock
from bliss.config.conductor.client import Lock
from bliss.config.channels import Cache
from bliss.config.settings import HashObjSetting
class Shutter(object):
MANUAL,EXTERNAL,CONFIGURATION = range(3) # modes
MODE2STR = {MANUAL: ("MANUAL", "Manual mode"),
EXTERNAL: ("EXTERNAL", "External trigger mode"),
CONFIGURATION: ("CONFIGURATION", "Configuration mode"),
}
OPEN,CLOSED,UNKNOWN = range(3) # state
STATE2STR = { OPEN: ("OPEN", "Shutter is open"),
CLOSED: ("CLOSED", "Shutter is closed"),
UNKNOWN: ("UNKNOWN", "Unknown shutter state"),
}
"""
Generic shutter object
This interface should be used for all type of shutter (motor,fast...)
You may want to link this shutter with an external
control i.e: wago,musst.... in that case you have to put
in configuration **external-control** with the object reference.
This external control should be compatible with the Switch object
and have an OPEN/CLOSED states.
"""
def lazy_init(func):
@functools.wraps(func)
def func_wrapper(self,*args,**kwargs):
self.init()
with Lock(self):
return func(self,*args,**kwargs)
return func_wrapper
def __init__(self,name,config):
self.__name = name
self.__config = config
self._external_ctrl = config.get('external-control')
self.__settings = HashObjSetting('shutter:%s' % name)
self.__initialized_hw = Cache(self,"initialized",
default_value = False)
self.__state = Cache(self,"state",
default_value = Shutter.UNKNOWN)
self._init_flag = False
self.__lock = lock.Semaphore()
def init(self):
"""
initialize the shutter in the current mode.
this is method is called by lazy_init
"""
if self._external_ctrl is not None:
# Check if the external control is compatible
# with a switch object and if it has open/close state
ext_ctrl = self._external_ctrl
name = ext_ctrl.name if hasattr(ext_ctrl,'name') else "unknown"
try:
states = ext_ctrl.states_list()
ext_ctrl.set
ext_ctrl.get
except AttributeError:
raise ValueError('external-ctrl : {0} is not compatible '
'with a switch object'.format(name))
else:
if(not 'OPEN' in states or
not 'CLOSED' in states):
raise ValueError("external-ctrl : {0} doesn't"
" have 'OPEN' and 'CLOSED' states".format(name))
if not self._init_flag:
self._init_flag = True
try:
self._init()
with Lock(self):
with self.__lock:
if not self.__initialized_hw.value:
self._initialize_hardware()
self.__initialized_hw.value = True
except:
self._init_flag = False
raise
def _init(self):
"""
This method should contains all software initialization
like communication, internal state...
"""
raise NotImplementedError
def _initialize_hardware(self):
"""
This method should contains all commands needed to
initialize the hardware.
It's will be call only once (by the first client).
"""
pass
@property
def name(self):
return self.__name
@property
def config(self):
return self.__config
@property
def settings(self):
return self.__settings
@property
def mode(self):
"""
shutter mode can be MANUAL,EXTERNAL,CONFIGURATION
In CONFIGURATION mode, shutter can't be open/close.
**CONFIGURATION** could mean that the shutter is in tuning mode
i.e: changing open/close position in case of a motor.
In EXTERNAL mode, the shutter will be controlled
through the external-control handler.
If no external control is configured open/close
wont be authorized.
"""
return self.__settings.get('mode',Shutter.MANUAL)
@mode.setter
def mode(self,value):
if value not in self.MODE2STR:
raise ValueError("Mode can only be: %s" %\
','.join((x[0] for x in self.MODE2STR.values())))
self.init()
self._set_mode(value)
if value in (self.CONFIGURATION,self.EXTERNAL):
# Can't cache the state if external or configuration
self.__state.value = self.UNKNOWN
self.__settings['mode'] = value
@property
def state(self):
self.init()
mode = self.mode
if mode == self.MANUAL and self.__state.value == self.UNKNOWN:
return_state = self._state()
self.__state.value = return_state
return return_state
else:
if mode == self.EXTERNAL:
if self.external_control is not None:
switch_state = self.external_control.get()
return self.OPEN if switch_state == "OPEN" else self.CLOSED
else:
return self.UNKNOWN
elif mode == self.CONFIGURATION:
return self.UNKNOWN
return self.__state.value
def _state(self):
raise NotImplementedError
@property
def state_string(self):
state = self.state()
return self.STATE2STR.get(state,self.STATE2STR[self.UNKNOWN])
@property
def external_control(self):
return self._external_ctrl
@lazy_init
def opening_time(self):
"""
Return the opening time if available or None
"""
return self._opening_time()
def _opening_time(self):
return self.__settings.get('opening_time')
@lazy_init
def closing_time(self):
"""
Return the closing time if available or None
"""
return self._closing_time()
def _closing_time(self):
return self.__settings.get('closing_time')
def measure_open_close_time(self):
"""
This small procedure will in basic usage do an open and close
of the shutter to measure the opening and closing time.
Those timing will be register into the settings.
returns (opening,closing) time
"""
previous_mode = self.mode()
try:
if previous_mode != self.MANUAL:
self.mode(self.MANUAL)
opening_time,closing_time = self._measure_open_close_time()
self.__settings['opening_time'] = opening_time
self.__settings['closing_time'] = closing_time
return open_time,close_time
finally:
if previous_mode != self.MANUAL:
self.mode(previous_mode)
def _measure_open_close_time(self):
"""
This method can be overloaded if needed.
Basic timing on
"""
self.close() # ensure it's closed
start_time = time.time()
self.open()
opening_time = time.time() - start_time
start_time = time.time()
self.close()
closing_time = time.time() - start_time
return opening_time,closing_time
@lazy_init
def open(self):
mode = self.mode
if mode == self.EXTERNAL:
if self._external_ctrl is None:
raise RuntimeError("Can't open the shutter because no "
"external-control is configured")
else:
return self._external_ctrl.set("OPEN")
elif mode != self.MANUAL:
raise RuntimeError("Can't open the shutter, in %s" %\
self.MODE2STR.get(mode,"Unknown"))
return self._open()
def _open(self):
raise NotImplementedError
@lazy_init
def close(self):
mode = self.mode
if mode == self.EXTERNAL:
if self._external_ctrl is None:
raise RuntimeError("Can't close the shutter because no "
"external-control is configured")
else:
return self._external_ctrl.set("CLOSED")
elif mode != self.MANUAL:
raise RuntimeError("Can't close the shutter, in %s" %\
self.MODE2STR.get(mode,"Unknown"))
return self._close()
def _close(self):
raise NotImplementedError
......@@ -15,6 +15,7 @@ __all__ = ['wa', 'wm', 'sta', 'mv', 'umv', 'mvr', 'umvr', 'move',
import inspect
import logging
import functools
import gevent
from six import print_
from gevent import sleep
......@@ -94,13 +95,20 @@ def wa(**kwargs):
print_("Current Positions (user, dial)")
header, pos, dial = [], [], []
tables = [(header, pos, dial)]
tasks = list()
def request(axis):
return axis.name,get(axis, "position"),get(axis, "dial")
for axis in __get_axes_iter():
tasks.append(gevent.spawn(request,axis))
for task in tasks:
axis_name,position,dial_position = task.get()
if len(header) == max_cols:
header, pos, dial = [], [], []
tables.append((header, pos, dial))
header.append(axis.name)
pos.append(get(axis, "position"))
dial.append(get(axis, "dial"))
header.append(axis_name)
pos.append(position)
dial.append(dial_position)
for table in tables:
print_()
......
# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
# Copyright (c) 2017 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
import functools
from gevent import lock
from bliss.config.conductor.client import Lock
from bliss.config.channels import Cache
class Switch(object):
"""
Generic interface for switch object.
"""
def lazy_init(func):
@functools.wraps(func)
def func_wrapper(self,*args,**kwargs):
self.init()
with Lock(self):
return func(self,*args,**kwargs)
return func_wrapper
def __init__(self,name,config):
self.__name = name
self.__config = config
self.__initialized_hw = Cache(self,"initialized",
default_value = False)
self._init_flag = False
self.__lock = lock.Semaphore()
@property
def name(self):
return self.__name
@property
def config(self):
return self.__config
def init(self):
"""
initialize the switch object
"""
if not self._init_flag:
self._init_flag = True
try:
self._init()
with Lock(self):
with self.__lock:
if not self.__initialized_hw.value:
self._initialize_hardware()
self.__initialized_hw.value = True
except:
self._init_flag = False
raise
def _init(self):
"""
This method should contains all software initialization
"""
pass
def _initialize_hardware(self):
"""
This method should contains all commands needed to
initialize the hardware.
It will be called only once (by the first client).
"""
pass
@lazy_init
def set(self,state):
return self._set(state.upper())
def _set(self,state):
raise NotImplementedError
@lazy_init
def get(self):
return self._get().upper()
def _get(self):
raise NotImplementedError
@lazy_init
def states_list(self):
return [x.upper() for x in self._states_list()]
def _states_list(self):
raise NotImplementedError
......@@ -9,6 +9,7 @@ from __future__ import absolute_import
import os
import sys
import pkgutil
import weakref
from bliss.common.axis import Axis, AxisRef
from bliss.common.encoder import Encoder
......@@ -335,6 +336,10 @@ def create_objects_from_config_node(config, node):
axes_names = list()
encoders = list()
encoders_names = list()
switches = list()
switches_names = list()
shutters = list()
shutters_names = list()
for axis_config in node.get('axes'):
axis_name = axis_config.get("name")
if axis_name.startswith("$"):
......@@ -348,37 +353,60 @@ def create_objects_from_config_node(config, node):
axis_class = getattr(controller_module, axis_class_name)
axes_names.append(axis_name)
axes.append((axis_name, axis_class, axis_config))
for encoder_config in node.get('encoders', []):
encoder_name = encoder_config.get("name")
encoder_class_name = encoder_config.get("class")
if encoder_class_name is None:
encoder_class = Encoder
else:
encoder_class = getattr(controller_module, encoder_class_name)
encoders_names.append(encoder_name)
encoders.append((encoder_name, encoder_class, encoder_config))
for objects,objects_names,default_class,default_class_name,objects_config in\
((encoders,encoders_names,Encoder,'',node.get('encoders',[])),
(shutters,shutters_names,None,'Shutter',node.get('shutters',[])),
(switches,switches_names,None,'Switch',node.get('switches',[])),
):
for object_config in objects_config:
object_name = object_config.get("name")
object_class_name = object_config.get("class")
object_config = _checkref(config,object_config)
if object_class_name is None:
object_class = default_class
if object_class is None:
try:
object_class = getattr(controller_module, default_class_name)
except AttributeError:
pass
else:
object_class = getattr(controller_module, object_class_name)
objects_names.append(object_name)
objects.append((object_name, object_class, object_config))
controller = controller_class(controller_name, node, axes, encoders)
controller = controller_class(controller_name, node, axes,
encoders, shutters, switches)
controller._update_refs(config)
controller.initialize()
all_names = axes_names + encoders_names
all_names = axes_names + encoders_names + switches_names + shutters_names
cache_dict = dict(zip(all_names, [controller]*len(all_names)))
if obj_name in axes_names:
cache_dict.pop(obj_name)
return { controller_name: controller, obj_name: controller.get_axis(obj_name) }, cache_dict
elif obj_name in encoders_names:
cache_dict.pop(obj_name)
return {controller_name: controller, obj_name: controller.get_encoder(obj_name) }, cache_dict
ctrl = cache_dict.pop(obj_name,None)
if ctrl is not None:
obj = create_object_from_cache(None, obj_name, controller)
return { controller_name: controller, obj_name: obj }, cache_dict
else:
return {controller_name: controller }, cache_dict
return {controller_name: controller }, cache_dict
def create_object_from_cache(config, name, controller):
try:
o = controller.get_axis(name)
except KeyError:
o = controller.get_encoder(name)
return o
for func in (controller.get_axis,
controller.get_encoder,
controller.get_switch,
controller.get_shutter):
try:
return func(name)
except KeyError:
pass
raise KeyError(name)
def _checkref(config,cfg):
obj_cfg = cfg.deep_copy()
for key,value in obj_cfg.iteritems():
if isinstance(value,str) and value.startswith('$'):
# convert reference to item from config
obj = weakref.proxy(config.get(value))
obj_cfg[key] = obj
return obj_cfg
......@@ -30,7 +30,7 @@ class Controller(object):
:ref:`bliss-how-to-motor-controller`
'''
def __init__(self, name, config, axes, encoders):
def __init__(self, name, config, axes, encoders, shutters, switches):
self.__name = name
self.__config = StaticConfig(config)
self.__initialized_axis = dict()
......@@ -39,6 +39,8 @@ class Controller(object):
self.__initialized_hw_axis = dict()
self._axes = dict()
self._encoders = dict()
self._shutters = dict()
self._switches = dict()
self.__initialized_encoder = dict()
self._tagged = dict()
......@@ -69,6 +71,12 @@ class Controller(object):