core: Add Acquisition Interface
+ Adds first implementation for the Acquisition interface, split into Controller and Channel Modules + frappy_demo: adds an example simulation + new property AttachedDict for a collection of attached modules + move Attach and AttachDict to a new file frappy/attached.py + interface_classes creation changed. includes now also Acquisition Change-Id: I198a96065a65bb28f73e468ce0465fca2d8734d7
This commit is contained in:
40
cfg/acquisition_cfg.py
Normal file
40
cfg/acquisition_cfg.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
Node('measure.frappy.demo',
|
||||||
|
'''Measureable demo''',
|
||||||
|
'tcp://10770',
|
||||||
|
)
|
||||||
|
Mod('control',
|
||||||
|
'frappy_demo.acquisition.Controller',
|
||||||
|
'simple demo controller',
|
||||||
|
channels = {'first': 'chan1', 'second': 'chan2', 'third': 'chan3'},
|
||||||
|
pollinterval = 1,
|
||||||
|
)
|
||||||
|
Mod('chan1',
|
||||||
|
'frappy_demo.acquisition.Channel',
|
||||||
|
'simple channel demo',
|
||||||
|
goal = 50,
|
||||||
|
goal_enable = True,
|
||||||
|
pollinterval = 1,
|
||||||
|
)
|
||||||
|
Mod('chan2',
|
||||||
|
'frappy_demo.acquisition.Channel',
|
||||||
|
'simple channel demo',
|
||||||
|
pollinterval = 1,
|
||||||
|
)
|
||||||
|
Mod('chan3',
|
||||||
|
'frappy_demo.acquisition.Channel',
|
||||||
|
'simple channel demo',
|
||||||
|
pollinterval = 1,
|
||||||
|
)
|
||||||
|
Mod('single',
|
||||||
|
'frappy_demo.acquisition.SimpleAcquisition',
|
||||||
|
'Acquisition demo',
|
||||||
|
pollinterval = 1,
|
||||||
|
goal = 20,
|
||||||
|
goal_enable=True,
|
||||||
|
acquisition_key='single',
|
||||||
|
)
|
||||||
|
Mod('ng',
|
||||||
|
'frappy_demo.acquisition.NoGoalAcquisition',
|
||||||
|
'Acquisition demo',
|
||||||
|
pollinterval = 5,
|
||||||
|
)
|
||||||
131
frappy/attached.py
Normal file
131
frappy/attached.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program 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 General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||||
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||||
|
# Alexander Zaft <a.zaft@fz-juelich.de>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
from frappy.errors import ConfigError
|
||||||
|
from frappy.modulebase import Module
|
||||||
|
from frappy.datatypes import StringType, ValueType
|
||||||
|
from frappy.properties import Property
|
||||||
|
|
||||||
|
|
||||||
|
class Attached(Property):
|
||||||
|
"""a special property, defining an attached module
|
||||||
|
|
||||||
|
assign a module name to this property in the cfg file,
|
||||||
|
and the server will create an attribute with this module
|
||||||
|
|
||||||
|
When mandatory is set to False, and there is no value or an empty string
|
||||||
|
given in the config file, the value of the attribute will be None.
|
||||||
|
"""
|
||||||
|
def __init__(self, basecls=Module, description='attached module', mandatory=True):
|
||||||
|
self.basecls = basecls
|
||||||
|
super().__init__(description, StringType(), mandatory=mandatory)
|
||||||
|
|
||||||
|
def __get__(self, obj, owner):
|
||||||
|
if obj is None:
|
||||||
|
return self
|
||||||
|
modobj = obj.attachedModules.get(self.name)
|
||||||
|
if not modobj:
|
||||||
|
modulename = super().__get__(obj, owner)
|
||||||
|
if not modulename:
|
||||||
|
return None # happens when mandatory=False and modulename is not given
|
||||||
|
modobj = obj.secNode.get_module(modulename)
|
||||||
|
if not modobj:
|
||||||
|
raise ConfigError(f'attached module {self.name}={modulename!r} '
|
||||||
|
f'does not exist')
|
||||||
|
if not isinstance(modobj, self.basecls):
|
||||||
|
raise ConfigError(f'attached module {self.name}={modobj.name!r} '
|
||||||
|
f'must inherit from {self.basecls.__qualname__!r}')
|
||||||
|
obj.attachedModules[self.name] = modobj
|
||||||
|
return modobj
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return Attached(self.basecls, self.description, self.mandatory)
|
||||||
|
|
||||||
|
|
||||||
|
class DictWithFlag(dict):
|
||||||
|
flag = False
|
||||||
|
|
||||||
|
|
||||||
|
class AttachDictType(ValueType):
|
||||||
|
"""a custom datatype for a dict <key> of names or modules"""
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(DictWithFlag)
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return AttachDictType()
|
||||||
|
|
||||||
|
def export_value(self, value):
|
||||||
|
"""export either names or the name attribute
|
||||||
|
|
||||||
|
to treat bare names and modules the same
|
||||||
|
"""
|
||||||
|
return {k: getattr(v, 'name', v) for k, v in value.items()}
|
||||||
|
|
||||||
|
|
||||||
|
class AttachedDict(Property):
|
||||||
|
def __init__(self, description='attached modules', elements=None, optional=None, basecls=None,
|
||||||
|
**kwds):
|
||||||
|
"""a mapping of attached modules
|
||||||
|
|
||||||
|
:param elements: None or a dict <key> of <basecls> for mandatory elements
|
||||||
|
:param optional: None or a dict <key> of <basecls> for optional elements
|
||||||
|
:param basecls: None or a base class for arbitrary keys
|
||||||
|
if not given, only keys given in parameters 'elements' and 'optional' are allowed
|
||||||
|
:param description: the property description
|
||||||
|
|
||||||
|
<key> might also be a number or any other immutable
|
||||||
|
"""
|
||||||
|
self.elements = elements or {}
|
||||||
|
self.basecls = basecls
|
||||||
|
self.baseclasses = {**self.elements, **(optional or {})}
|
||||||
|
super().__init__(description, AttachDictType(), default={}, **kwds)
|
||||||
|
|
||||||
|
def __get__(self, obj, owner):
|
||||||
|
if obj is None:
|
||||||
|
return self
|
||||||
|
attach_dict = super().__get__(obj, owner) or DictWithFlag({})
|
||||||
|
if attach_dict.flag:
|
||||||
|
return attach_dict
|
||||||
|
|
||||||
|
for key, modulename in attach_dict.items():
|
||||||
|
basecls = self.baseclasses.get(key, self.basecls)
|
||||||
|
if basecls is None:
|
||||||
|
raise ConfigError(f'unknown key {key!r} for attached modules {self.name}')
|
||||||
|
modobj = obj.secNode.get_module(modulename)
|
||||||
|
if modobj is None:
|
||||||
|
raise ConfigError(f'attached modules {self.name}: '
|
||||||
|
f'{key}={modulename!r} does not exist')
|
||||||
|
if not isinstance(modobj, basecls):
|
||||||
|
raise ConfigError(f'attached modules {self.name}: '
|
||||||
|
f'module {key}={modulename!r} must inherit '
|
||||||
|
f'from {basecls.__qualname__!r}')
|
||||||
|
obj.attachedModules[self.name, key] = attach_dict[key] = modobj
|
||||||
|
missing_keys = set(self.elements) - set(attach_dict)
|
||||||
|
if missing_keys:
|
||||||
|
raise ConfigError(f'attached modules {self.name}: '
|
||||||
|
f"missing {', '.join(missing_keys)} ")
|
||||||
|
attach_dict.flag = True
|
||||||
|
return attach_dict
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return AttachedDict(self.elements, self.baseclasses, self.basecls, self.description)
|
||||||
@@ -29,8 +29,8 @@ from frappy.datatypes import ArrayOf, BLOBType, BoolType, EnumType, \
|
|||||||
FloatRange, IntRange, ScaledInteger, StringType, StructOf, TupleOf, StatusType
|
FloatRange, IntRange, ScaledInteger, StringType, StructOf, TupleOf, StatusType
|
||||||
from frappy.lib.enum import Enum
|
from frappy.lib.enum import Enum
|
||||||
from frappy.modulebase import Done, Module, Feature
|
from frappy.modulebase import Done, Module, Feature
|
||||||
from frappy.modules import Attached, Communicator, \
|
from frappy.modules import Communicator, Drivable, Readable, Writable
|
||||||
Drivable, Readable, Writable
|
from frappy.attached import Attached, AttachedDict
|
||||||
from frappy.params import Command, Parameter, Limit
|
from frappy.params import Command, Parameter, Limit
|
||||||
from frappy.properties import Property
|
from frappy.properties import Property
|
||||||
from frappy.proxy import Proxy, SecNode, proxy_class
|
from frappy.proxy import Proxy, SecNode, proxy_class
|
||||||
|
|||||||
@@ -320,6 +320,9 @@ class Module(HasAccessibles):
|
|||||||
pollInfo = None
|
pollInfo = None
|
||||||
triggerPoll = None # trigger event for polls. used on io modules and modules without io
|
triggerPoll = None # trigger event for polls. used on io modules and modules without io
|
||||||
__poller = None # the poller thread, if used
|
__poller = None # the poller thread, if used
|
||||||
|
SECoP_CLASS = None
|
||||||
|
SECoP_BASE_CLASSES = [] # predefined SECoP base classes
|
||||||
|
SECoP_CLASSES = [] # all predefined SECoP interface classes
|
||||||
|
|
||||||
def __init__(self, name, logger, cfgdict, srv):
|
def __init__(self, name, logger, cfgdict, srv):
|
||||||
# remember the secnode for interacting with other modules and the
|
# remember the secnode for interacting with other modules and the
|
||||||
|
|||||||
@@ -22,16 +22,19 @@
|
|||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""Define base classes for real Modules implemented in the server"""
|
"""Define base classes for real Modules implemented in the server"""
|
||||||
|
|
||||||
|
from frappy.datatypes import BoolType, FloatRange, StatusType, StringType
|
||||||
from frappy.datatypes import FloatRange, \
|
|
||||||
StatusType, StringType
|
|
||||||
from frappy.errors import ConfigError, ProgrammingError
|
from frappy.errors import ConfigError, ProgrammingError
|
||||||
from frappy.lib.enum import Enum
|
from frappy.lib.enum import Enum
|
||||||
from frappy.params import Command, Parameter
|
|
||||||
from frappy.properties import Property
|
|
||||||
from frappy.logging import HasComlog
|
from frappy.logging import HasComlog
|
||||||
|
from frappy.params import Command, Parameter
|
||||||
|
|
||||||
from .modulebase import Module
|
from .modulebase import Module
|
||||||
|
from .attached import AttachedDict
|
||||||
|
|
||||||
|
# import compatibility:
|
||||||
|
# pylint: disable=unused-import
|
||||||
|
from .properties import Property
|
||||||
|
from .attached import Attached
|
||||||
|
|
||||||
|
|
||||||
class Readable(Module):
|
class Readable(Module):
|
||||||
@@ -98,6 +101,77 @@ class Drivable(Writable):
|
|||||||
"""not implemented - this is a no-op"""
|
"""not implemented - this is a no-op"""
|
||||||
|
|
||||||
|
|
||||||
|
class AcquisitionChannel(Readable):
|
||||||
|
"""A Readable which is part of a data acquisition."""
|
||||||
|
interface_classes = ['AcquisitionChannel', 'Readable']
|
||||||
|
# copy Readable.status and extend it with BUSY
|
||||||
|
status = Parameter(datatype=StatusType(Readable, 'BUSY'))
|
||||||
|
goal = Parameter('stops the data acquisition when it is reached',
|
||||||
|
FloatRange(), default=0, readonly=False, optional=True)
|
||||||
|
goal_enable = Parameter('enable goal', BoolType(), readonly=False,
|
||||||
|
default=False, optional=True)
|
||||||
|
|
||||||
|
# clear is no longer part of the proposed spec, so it does not appear
|
||||||
|
# as optional command here. however, a subclass may still implement it
|
||||||
|
|
||||||
|
|
||||||
|
class AcquisitionController(Module):
|
||||||
|
"""Controls other modules.
|
||||||
|
|
||||||
|
Controls the data acquisition from AcquisitionChannels.
|
||||||
|
"""
|
||||||
|
interface_classes = ['AcquisitionController']
|
||||||
|
# channels might be configured to an arbitrary number of channels with arbitrary roles
|
||||||
|
# - to forbid the use fo arbitrary roles, override base=None
|
||||||
|
# - to restrict roles and base classes override elements={<key>: <basecls>}
|
||||||
|
# and/or optional={<key>: <basecls>}
|
||||||
|
channels = AttachedDict('mapping of role to module name for attached channels',
|
||||||
|
elements=None, optional=None,
|
||||||
|
basecls=AcquisitionChannel,
|
||||||
|
extname='acquisition_channels')
|
||||||
|
status = Drivable.status
|
||||||
|
isBusy = Drivable.isBusy
|
||||||
|
# add pollinterval parameter to enable faster polling of the status
|
||||||
|
pollinterval = Readable.pollinterval
|
||||||
|
|
||||||
|
def doPoll(self):
|
||||||
|
self.read_status()
|
||||||
|
|
||||||
|
@Command()
|
||||||
|
def go(self):
|
||||||
|
"""Start the acquisition. No-op if the controller is already Busy."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@Command(optional=True)
|
||||||
|
def prepare(self):
|
||||||
|
"""Prepare the hardware so 'go' can trigger immediately."""
|
||||||
|
|
||||||
|
@Command(optional=True)
|
||||||
|
def hold(self):
|
||||||
|
"""Pause the operation.
|
||||||
|
|
||||||
|
The next go will continue without clearing any channels or resetting hardware."""
|
||||||
|
|
||||||
|
@Command(optional=True)
|
||||||
|
def stop(self):
|
||||||
|
"""Stop the data acquisition or operation."""
|
||||||
|
|
||||||
|
|
||||||
|
class Acquisition(AcquisitionController, AcquisitionChannel): # pylint: disable=abstract-method
|
||||||
|
"""Combines AcquisitionController and AcquisitionChannel into one Module
|
||||||
|
|
||||||
|
for the special case where there is only one channel.
|
||||||
|
remark: when using multiple inheritance, Acquisition must appear
|
||||||
|
before any base class inheriting from AcquisitionController
|
||||||
|
"""
|
||||||
|
interface_classes = ['Acquisition', 'Readable']
|
||||||
|
channels = None # remove property
|
||||||
|
acquisition_key = Property('acquisition role (equivalent to NICOS preset name)',
|
||||||
|
StringType(), export=True, default='')
|
||||||
|
|
||||||
|
doPoll = Readable.doPoll
|
||||||
|
|
||||||
|
|
||||||
class Communicator(HasComlog, Module):
|
class Communicator(HasComlog, Module):
|
||||||
"""basic abstract communication module"""
|
"""basic abstract communication module"""
|
||||||
interface_classes = ['Communicator']
|
interface_classes = ['Communicator']
|
||||||
@@ -110,38 +184,3 @@ class Communicator(HasComlog, Module):
|
|||||||
:return: the reply
|
:return: the reply
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class Attached(Property):
|
|
||||||
"""a special property, defining an attached module
|
|
||||||
|
|
||||||
assign a module name to this property in the cfg file,
|
|
||||||
and the server will create an attribute with this module
|
|
||||||
|
|
||||||
When mandatory is set to False, and there is no value or an empty string
|
|
||||||
given in the config file, the value of the attribute will be None.
|
|
||||||
"""
|
|
||||||
def __init__(self, basecls=Module, description='attached module', mandatory=True):
|
|
||||||
self.basecls = basecls
|
|
||||||
super().__init__(description, StringType(), mandatory=mandatory)
|
|
||||||
|
|
||||||
def __get__(self, obj, owner):
|
|
||||||
if obj is None:
|
|
||||||
return self
|
|
||||||
modobj = obj.attachedModules.get(self.name)
|
|
||||||
if not modobj:
|
|
||||||
modulename = super().__get__(obj, owner)
|
|
||||||
if not modulename:
|
|
||||||
return None # happens when mandatory=False and modulename is not given
|
|
||||||
modobj = obj.secNode.get_module(modulename)
|
|
||||||
if not modobj:
|
|
||||||
raise ConfigError(f'attached module {self.name}={modulename!r} '
|
|
||||||
f'does not exist')
|
|
||||||
if not isinstance(modobj, self.basecls):
|
|
||||||
raise ConfigError(f'attached module {self.name}={modobj.name!r} '
|
|
||||||
f'must inherit from {self.basecls.__qualname__!r}')
|
|
||||||
obj.attachedModules[self.name] = modobj
|
|
||||||
return modobj
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
return Attached(self.basecls, self.description, self.mandatory)
|
|
||||||
|
|||||||
241
frappy_demo/acquisition.py
Normal file
241
frappy_demo/acquisition.py
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program 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 General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Alexander Zaft <a.zaft@fz-juelich.de>
|
||||||
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import threading
|
||||||
|
from frappy.lib import clamp, mkthread
|
||||||
|
from frappy.core import IntRange, Parameter, ArrayOf, TupleOf, FloatRange, \
|
||||||
|
IDLE, ERROR, BUSY
|
||||||
|
from frappy.modules import AcquisitionController, AcquisitionChannel, Acquisition
|
||||||
|
from frappy.params import Command
|
||||||
|
|
||||||
|
|
||||||
|
class AcquisitionSimulation:
|
||||||
|
def __init__(self, keys):
|
||||||
|
self.values = {k: 0 for k in keys}
|
||||||
|
self.err = None
|
||||||
|
self._stopflag = threading.Event()
|
||||||
|
self.run_acquisition = threading.Event()
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.need_reset = False
|
||||||
|
self._thread = None
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
if self.need_reset:
|
||||||
|
self.reset()
|
||||||
|
if self._thread is None:
|
||||||
|
self._thread = mkthread(self.threadfun)
|
||||||
|
|
||||||
|
def threadfun(self):
|
||||||
|
self.sim_interval = 1
|
||||||
|
self.err = None
|
||||||
|
try:
|
||||||
|
self.__sim()
|
||||||
|
except Exception as e:
|
||||||
|
self.err = str(e)
|
||||||
|
# the thread stops here, but will be restarted with the go command
|
||||||
|
self._thread = None
|
||||||
|
|
||||||
|
def __sim(self):
|
||||||
|
timestamp = time.time()
|
||||||
|
delay = 0
|
||||||
|
while not self._stopflag.wait(delay):
|
||||||
|
self.run_acquisition.wait()
|
||||||
|
t = time.time()
|
||||||
|
diff = t - timestamp
|
||||||
|
if diff < self.sim_interval:
|
||||||
|
delay = clamp(0.1, self.sim_interval, 10)
|
||||||
|
continue
|
||||||
|
delay = 0
|
||||||
|
with self.lock:
|
||||||
|
self.values = {k: v + max(0., random.normalvariate(4., 1.))
|
||||||
|
for k, v in self.values.items()}
|
||||||
|
timestamp = t
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
with self.lock:
|
||||||
|
for key in self.values:
|
||||||
|
self.values[key] = 0
|
||||||
|
self.need_reset = False
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
# unblock thread:
|
||||||
|
self._stopflag.set()
|
||||||
|
self.run_acquisition.set()
|
||||||
|
if self._thread and self._thread.is_alive():
|
||||||
|
self._thread.join()
|
||||||
|
|
||||||
|
|
||||||
|
class Controller(AcquisitionController):
|
||||||
|
_status = None # for sticky status values
|
||||||
|
|
||||||
|
def init_ac(self):
|
||||||
|
self.ac = AcquisitionSimulation(m.name for m in self.channels.values())
|
||||||
|
self.ac.reset()
|
||||||
|
for key, channel in self.channels.items():
|
||||||
|
self.log.debug('register %s: %s', key, channel.name)
|
||||||
|
channel.register_acq(self.ac)
|
||||||
|
|
||||||
|
def initModule(self):
|
||||||
|
super().initModule()
|
||||||
|
self.init_ac()
|
||||||
|
|
||||||
|
def read_status(self):
|
||||||
|
with self.ac.lock:
|
||||||
|
if self.ac.err:
|
||||||
|
status = self.Status.ERROR, self.ac.err
|
||||||
|
elif self.ac.run_acquisition.is_set():
|
||||||
|
status = self.Status.BUSY, 'running acquisition'
|
||||||
|
else:
|
||||||
|
status = self._status or (self.Status.IDLE, '')
|
||||||
|
for chan in self.channels.values():
|
||||||
|
chan.read_status()
|
||||||
|
return status
|
||||||
|
|
||||||
|
def go(self):
|
||||||
|
self.ac.start() # restart sim thread if it failed
|
||||||
|
self.ac.run_acquisition.set()
|
||||||
|
self._status = None
|
||||||
|
self.read_status()
|
||||||
|
|
||||||
|
def hold(self):
|
||||||
|
self.ac.run_acquisition.clear()
|
||||||
|
self._status = IDLE, 'paused'
|
||||||
|
self.read_status()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.ac.run_acquisition.clear()
|
||||||
|
self.ac.need_reset = True
|
||||||
|
self._status = IDLE, 'stopped'
|
||||||
|
|
||||||
|
@Command()
|
||||||
|
def clear(self):
|
||||||
|
"""clear all channels"""
|
||||||
|
self.ac.reset()
|
||||||
|
|
||||||
|
def shutdownModule(self):
|
||||||
|
self.ac.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
class Channel(AcquisitionChannel):
|
||||||
|
_status = None # for sticky status values
|
||||||
|
# activate optional parameters:
|
||||||
|
goal = Parameter()
|
||||||
|
goal_enable = Parameter()
|
||||||
|
|
||||||
|
def register_acq(self, ac):
|
||||||
|
self.ac = ac
|
||||||
|
|
||||||
|
def read_value(self):
|
||||||
|
with self.ac.lock:
|
||||||
|
try:
|
||||||
|
ret = self.ac.values[self.name]
|
||||||
|
except KeyError:
|
||||||
|
return -1
|
||||||
|
if self.goal_enable and self.goal < ret:
|
||||||
|
if self.ac.run_acquisition.is_set():
|
||||||
|
self.ac.run_acquisition.clear()
|
||||||
|
self.ac.need_reset = True
|
||||||
|
self._status = IDLE, 'hit goal'
|
||||||
|
else:
|
||||||
|
self._status = None
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def read_status(self):
|
||||||
|
if self.ac.err:
|
||||||
|
return ERROR, self.ac.err
|
||||||
|
if self.ac.run_acquisition.is_set():
|
||||||
|
return BUSY, 'running acquisition'
|
||||||
|
return self._status or (IDLE, '')
|
||||||
|
|
||||||
|
@Command()
|
||||||
|
def clear(self):
|
||||||
|
"""clear this channel"""
|
||||||
|
with self.ac.lock:
|
||||||
|
try:
|
||||||
|
self.ac.values[self.name] = 0.
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
self.read_value()
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleAcquisition(Acquisition, Controller, Channel):
|
||||||
|
def init_ac(self):
|
||||||
|
self.channels = {}
|
||||||
|
self.ac = AcquisitionSimulation([self.name])
|
||||||
|
self.ac.reset()
|
||||||
|
|
||||||
|
|
||||||
|
class NoGoalAcquisition(Acquisition):
|
||||||
|
_value = 0
|
||||||
|
_deadline = 0
|
||||||
|
|
||||||
|
def read_value(self):
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
def read_status(self):
|
||||||
|
if self.status[0] == BUSY:
|
||||||
|
overtime = time.time() - self._deadline
|
||||||
|
if overtime < 0:
|
||||||
|
return BUSY, ''
|
||||||
|
self.setFastPoll(False)
|
||||||
|
self._value = overtime
|
||||||
|
self.read_value()
|
||||||
|
return IDLE, ''
|
||||||
|
|
||||||
|
def go(self):
|
||||||
|
self._value = 0
|
||||||
|
self.status = BUSY, 'started'
|
||||||
|
self.setFastPoll(True, 0.1)
|
||||||
|
self._deadline = time.time() + 1
|
||||||
|
self.read_status()
|
||||||
|
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
class MatrixChannel(AcquisitionChannel):
|
||||||
|
roi = Parameter('region of interest',
|
||||||
|
ArrayOf(TupleOf(IntRange(), IntRange()), 0, 1),
|
||||||
|
default=[], readonly=False)
|
||||||
|
|
||||||
|
def initModule(self):
|
||||||
|
self.data = [0.] * 128
|
||||||
|
|
||||||
|
def read_value(self):
|
||||||
|
# mean of data or roi
|
||||||
|
if self.roi:
|
||||||
|
b, e = self.roi[0]
|
||||||
|
else:
|
||||||
|
b, e = 0, len(self.data) - 1
|
||||||
|
return self.data[b:e] / (e - b)
|
||||||
|
|
||||||
|
def write_roi(self, roi):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@Command(result=ArrayOf(FloatRange()))
|
||||||
|
def get_data(self):
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
# axes
|
||||||
|
# binning
|
||||||
|
def clear(self):
|
||||||
|
raise NotImplementedError()
|
||||||
@@ -53,6 +53,7 @@ class WithAtt(Readable):
|
|||||||
def read_value(self):
|
def read_value(self):
|
||||||
return self.att.read_value()
|
return self.att.read_value()
|
||||||
|
|
||||||
|
|
||||||
class LN2(Readable):
|
class LN2(Readable):
|
||||||
"""Just a readable.
|
"""Just a readable.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user