some more merges from gerrit
- removed files - modified drivers - fixed READE.md Change-Id: I47ae486df4dde3d60cc5e0e328194718dd396d87
This commit is contained in:
parent
57e0a2cc72
commit
10018b8cad
@ -38,7 +38,7 @@ changes are done, eventually a sync step should happen:
|
|||||||
1) ideally, this is done when work and wip match
|
1) ideally, this is done when work and wip match
|
||||||
2) make sure branches mlz, master, wip and work are in sync with remote, push/pull otherwise
|
2) make sure branches mlz, master, wip and work are in sync with remote, push/pull otherwise
|
||||||
3) cherry-pick commits from mlz to master
|
3) cherry-pick commits from mlz to master
|
||||||
4) make sure master and mlz branches match (git diff --name-only master..wip should only return README.md)
|
4) make sure master and mlz branches match (git diff --name-only master..mlz should only return README.md)
|
||||||
5) create branch new_work from master
|
5) create branch new_work from master
|
||||||
6) go through commits in wip and sort out:
|
6) go through commits in wip and sort out:
|
||||||
- core commits already pushed through gerrit are skipped
|
- core commits already pushed through gerrit are skipped
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# *****************************************************************************
|
|
||||||
#
|
|
||||||
# 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>
|
|
||||||
#
|
|
||||||
# *****************************************************************************
|
|
||||||
"""Pathes. how to find what and where..."""
|
|
||||||
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
basepath = path.abspath(path.join(sys.path[0], '..'))
|
|
||||||
etc_path = path.join(basepath, 'etc')
|
|
||||||
pid_path = path.join(basepath, 'pid')
|
|
||||||
log_path = path.join(basepath, 'log')
|
|
||||||
sys.path[0] = path.join(basepath, 'src')
|
|
278
secop/poller.py
278
secop/poller.py
@ -1,278 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# *****************************************************************************
|
|
||||||
#
|
|
||||||
# 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:
|
|
||||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
|
||||||
#
|
|
||||||
# *****************************************************************************
|
|
||||||
"""general, advanced frappy poller
|
|
||||||
|
|
||||||
Usage examples:
|
|
||||||
any Module which want to be polled with a specific Poller must define
|
|
||||||
the pollerClass class variable:
|
|
||||||
|
|
||||||
class MyModule(Readable):
|
|
||||||
...
|
|
||||||
pollerClass = poller.Poller
|
|
||||||
...
|
|
||||||
|
|
||||||
modules having a parameter 'iodev' with the same value will share the same poller
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
|
||||||
from heapq import heapify, heapreplace
|
|
||||||
from threading import Event
|
|
||||||
|
|
||||||
from secop.errors import ProgrammingError
|
|
||||||
from secop.lib import mkthread
|
|
||||||
|
|
||||||
# poll types:
|
|
||||||
AUTO = 1 #: equivalent to True, converted to REGULAR, SLOW or DYNAMIC
|
|
||||||
SLOW = 2 #: polling with low priority and increased poll interval (used by default when readonly=False)
|
|
||||||
REGULAR = 3 #: polling with standard interval (used by default for read only parameters except status and value)
|
|
||||||
DYNAMIC = 4 #: polling with shorter poll interval when BUSY (used by default for status and value)
|
|
||||||
|
|
||||||
|
|
||||||
class PollerBase:
|
|
||||||
|
|
||||||
startup_timeout = 30 # default timeout for startup
|
|
||||||
name = 'unknown' # to be overridden in implementors __init__ method
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def add_to_table(cls, table, module):
|
|
||||||
"""sort module into poller table
|
|
||||||
|
|
||||||
table is a dict, with (<pollerClass>, <name>) as the key, and the
|
|
||||||
poller as value.
|
|
||||||
<name> is module.iodev or module.name, if iodev is not present
|
|
||||||
"""
|
|
||||||
# for modules with the same iodev, a common poller is used,
|
|
||||||
# modules without iodev all get their own poller
|
|
||||||
name = getattr(module, 'iodev', module.name)
|
|
||||||
poller = table.get((cls, name), None)
|
|
||||||
if poller is None:
|
|
||||||
poller = cls(name)
|
|
||||||
table[(cls, name)] = poller
|
|
||||||
poller.add_to_poller(module)
|
|
||||||
|
|
||||||
def start(self, started_callback):
|
|
||||||
"""start poller thread
|
|
||||||
|
|
||||||
started_callback to be called after all poll items were read at least once
|
|
||||||
"""
|
|
||||||
mkthread(self.run, started_callback)
|
|
||||||
return self.startup_timeout
|
|
||||||
|
|
||||||
def run(self, started_callback):
|
|
||||||
"""poller thread function
|
|
||||||
|
|
||||||
started_callback to be called after all poll items were read at least once
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""stop polling"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
"""is there any poll item?"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '%s(%r)' % (self.__class__.__name__, self.name)
|
|
||||||
|
|
||||||
|
|
||||||
class Poller(PollerBase):
|
|
||||||
"""a standard poller
|
|
||||||
|
|
||||||
parameters may have the following polltypes:
|
|
||||||
|
|
||||||
- REGULAR: by default used for readonly parameters with poll=True
|
|
||||||
- SLOW: by default used for readonly=False parameters with poll=True.
|
|
||||||
slow polls happen with lower priority, but at least one parameter
|
|
||||||
is polled with regular priority within self.module.pollinterval.
|
|
||||||
Scheduled to poll every slowfactor * module.pollinterval
|
|
||||||
- DYNAMIC: by default used for 'value' and 'status'
|
|
||||||
When busy, scheduled to poll every fastfactor * module.pollinterval
|
|
||||||
"""
|
|
||||||
|
|
||||||
DEFAULT_FACTORS = {SLOW: 4, DYNAMIC: 0.25, REGULAR: 1}
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
"""create a poller"""
|
|
||||||
self.queues = {polltype: [] for polltype in self.DEFAULT_FACTORS}
|
|
||||||
self._event = Event()
|
|
||||||
self._stopped = False
|
|
||||||
self.maxwait = 3600
|
|
||||||
self.name = name
|
|
||||||
self.modules = [] # used for writeInitParams only
|
|
||||||
|
|
||||||
def add_to_poller(self, module):
|
|
||||||
self.modules.append(module)
|
|
||||||
factors = self.DEFAULT_FACTORS.copy()
|
|
||||||
try:
|
|
||||||
factors[DYNAMIC] = module.fast_pollfactor
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
factors[SLOW] = module.slow_pollfactor
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
self.maxwait = min(self.maxwait, getattr(module, 'max_polltestperiod', 10))
|
|
||||||
try:
|
|
||||||
self.startup_timeout = max(self.startup_timeout, module.startup_timeout)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
handlers = set()
|
|
||||||
# at the beginning, queues are simple lists
|
|
||||||
# later, they will be converted to heaps
|
|
||||||
for pname, pobj in module.parameters.items():
|
|
||||||
polltype = pobj.poll
|
|
||||||
if not polltype:
|
|
||||||
continue
|
|
||||||
if not hasattr(module, 'pollinterval'):
|
|
||||||
raise ProgrammingError("module %s must have a pollinterval"
|
|
||||||
% module.name)
|
|
||||||
if pname == 'is_connected':
|
|
||||||
if hasattr(module, 'registerReconnectCallback'):
|
|
||||||
module.registerReconnectCallback(self.name, self.trigger_all)
|
|
||||||
else:
|
|
||||||
module.log.warning("%r has 'is_connected' but no 'registerReconnectCallback'" % module)
|
|
||||||
if polltype == AUTO: # covers also pobj.poll == True
|
|
||||||
if pname in ('value', 'status'):
|
|
||||||
polltype = DYNAMIC
|
|
||||||
elif pobj.readonly:
|
|
||||||
polltype = REGULAR
|
|
||||||
else:
|
|
||||||
polltype = SLOW
|
|
||||||
if polltype not in factors:
|
|
||||||
raise ProgrammingError("unknown poll type %r for parameter '%s'"
|
|
||||||
% (polltype, pname))
|
|
||||||
if pobj.handler:
|
|
||||||
if pobj.handler in handlers:
|
|
||||||
continue # only one poller per handler
|
|
||||||
handlers.add(pobj.handler)
|
|
||||||
# placeholders 0 are used for due, lastdue and idx
|
|
||||||
self.queues[polltype].append(
|
|
||||||
(0, 0, (0, module, pobj, pname, factors[polltype])))
|
|
||||||
|
|
||||||
def poll_next(self, polltype):
|
|
||||||
"""try to poll next item
|
|
||||||
|
|
||||||
advance in queue until
|
|
||||||
- an item is found which is really due to poll. return 0 in this case
|
|
||||||
- or until the next item is not yet due. return next due time in this case
|
|
||||||
"""
|
|
||||||
queue = self.queues[polltype]
|
|
||||||
if not queue:
|
|
||||||
return float('inf') # queue is empty
|
|
||||||
now = time.time()
|
|
||||||
done = False
|
|
||||||
while not done:
|
|
||||||
due, lastdue, pollitem = queue[0]
|
|
||||||
if now < due:
|
|
||||||
return due
|
|
||||||
_, module, pobj, pname, factor = pollitem
|
|
||||||
|
|
||||||
if polltype == DYNAMIC and not module.isBusy():
|
|
||||||
interval = module.pollinterval # effective interval
|
|
||||||
mininterval = interval * factor # interval for calculating next due
|
|
||||||
else:
|
|
||||||
interval = module.pollinterval * factor
|
|
||||||
mininterval = interval
|
|
||||||
if due == 0:
|
|
||||||
due = now # do not look at timestamp after trigger_all
|
|
||||||
else:
|
|
||||||
due = max(lastdue + interval, pobj.timestamp + interval * 0.5)
|
|
||||||
if now >= due:
|
|
||||||
module.pollOneParam(pname)
|
|
||||||
done = True
|
|
||||||
lastdue = due
|
|
||||||
due = max(lastdue + mininterval, now + min(self.maxwait, mininterval * 0.5))
|
|
||||||
# replace due, lastdue with new values and sort in
|
|
||||||
heapreplace(queue, (due, lastdue, pollitem))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def trigger_all(self):
|
|
||||||
for _, queue in sorted(self.queues.items()):
|
|
||||||
for idx, (_, lastdue, pollitem) in enumerate(queue):
|
|
||||||
queue[idx] = (0, lastdue, pollitem)
|
|
||||||
self._event.set()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def run(self, started_callback):
|
|
||||||
"""start poll loop
|
|
||||||
|
|
||||||
To be called as a thread. After all parameters are polled once first,
|
|
||||||
started_callback is called. To be called in Module.start_module.
|
|
||||||
|
|
||||||
poll strategy:
|
|
||||||
Slow polls are performed with lower priority than regular and dynamic polls.
|
|
||||||
If more polls are scheduled than time permits, at least every second poll is a
|
|
||||||
dynamic poll. After every n regular polls, one slow poll is done, if due
|
|
||||||
(where n is the number of regular parameters).
|
|
||||||
"""
|
|
||||||
if not self:
|
|
||||||
# nothing to do (else time.sleep(float('inf')) might be called below
|
|
||||||
started_callback()
|
|
||||||
return
|
|
||||||
# if writeInitParams is not yet done, we do it here
|
|
||||||
for module in self.modules:
|
|
||||||
module.writeInitParams()
|
|
||||||
# do all polls once and, at the same time, insert due info
|
|
||||||
for _, queue in sorted(self.queues.items()): # do SLOW polls first
|
|
||||||
for idx, (_, _, (_, module, pobj, pname, factor)) in enumerate(queue):
|
|
||||||
lastdue = time.time()
|
|
||||||
module.pollOneParam(pname)
|
|
||||||
due = lastdue + min(self.maxwait, module.pollinterval * factor)
|
|
||||||
# in python 3 comparing tuples need some care, as not all objects
|
|
||||||
# are comparable. Inserting a unique idx solves the problem.
|
|
||||||
queue[idx] = (due, lastdue, (idx, module, pobj, pname, factor))
|
|
||||||
heapify(queue)
|
|
||||||
started_callback() # signal end of startup
|
|
||||||
nregular = len(self.queues[REGULAR])
|
|
||||||
while not self._stopped:
|
|
||||||
due = float('inf')
|
|
||||||
for _ in range(nregular):
|
|
||||||
due = min(self.poll_next(DYNAMIC), self.poll_next(REGULAR))
|
|
||||||
if due:
|
|
||||||
break # no dynamic or regular polls due
|
|
||||||
due = min(due, self.poll_next(DYNAMIC), self.poll_next(SLOW))
|
|
||||||
delay = due - time.time()
|
|
||||||
if delay > 0:
|
|
||||||
self._event.wait(delay)
|
|
||||||
self._event.clear()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self._event.set()
|
|
||||||
self._stopped = True
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
"""is there any poll item?"""
|
|
||||||
return any(self.queues.values())
|
|
||||||
|
|
||||||
|
|
||||||
class BasicPoller(PollerBase):
|
|
||||||
"""basic poller
|
|
||||||
|
|
||||||
this is just a dummy, the poller thread is started in Readable.startModule
|
|
||||||
"""
|
|
||||||
# pylint: disable=abstract-method
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def add_to_table(cls, table, module):
|
|
||||||
pass
|
|
@ -20,7 +20,7 @@
|
|||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""Andeen Hagerling capacitance bridge"""
|
"""Andeen Hagerling capacitance bridge"""
|
||||||
|
|
||||||
from secop.core import Done, FloatRange, HasIodev, Parameter, Readable, StringIO
|
from secop.core import Done, FloatRange, HasIO, Parameter, Readable, StringIO, nopoll
|
||||||
|
|
||||||
|
|
||||||
class Ah2700IO(StringIO):
|
class Ah2700IO(StringIO):
|
||||||
@ -28,19 +28,19 @@ class Ah2700IO(StringIO):
|
|||||||
timeout = 5
|
timeout = 5
|
||||||
|
|
||||||
|
|
||||||
class Capacitance(HasIodev, Readable):
|
class Capacitance(HasIO, Readable):
|
||||||
|
|
||||||
value = Parameter('capacitance', FloatRange(unit='pF'), poll=True)
|
value = Parameter('capacitance', FloatRange(unit='pF'))
|
||||||
freq = Parameter('frequency', FloatRange(unit='Hz'), readonly=False, default=0)
|
freq = Parameter('frequency', FloatRange(unit='Hz'), readonly=False, default=0)
|
||||||
voltage = Parameter('voltage', FloatRange(unit='V'), readonly=False, default=0)
|
voltage = Parameter('voltage', FloatRange(unit='V'), readonly=False, default=0)
|
||||||
loss = Parameter('loss', FloatRange(unit='deg'), default=0)
|
loss = Parameter('loss', FloatRange(unit='deg'), default=0)
|
||||||
|
|
||||||
iodevClass = Ah2700IO
|
ioClass = Ah2700IO
|
||||||
|
|
||||||
def parse_reply(self, reply):
|
def parse_reply(self, reply):
|
||||||
if reply.startswith('SI'): # this is an echo
|
if reply.startswith('SI'): # this is an echo
|
||||||
self.sendRecv('SERIAL ECHO OFF')
|
self.communicate('SERIAL ECHO OFF')
|
||||||
reply = self.sendRecv('SI')
|
reply = self.communicate('SI')
|
||||||
if not reply.startswith('F='): # this is probably an error message like "LOSS TOO HIGH"
|
if not reply.startswith('F='): # this is probably an error message like "LOSS TOO HIGH"
|
||||||
self.status = [self.Status.ERROR, reply]
|
self.status = [self.Status.ERROR, reply]
|
||||||
return
|
return
|
||||||
@ -59,32 +59,35 @@ class Capacitance(HasIodev, Readable):
|
|||||||
if lossunit == 'DS':
|
if lossunit == 'DS':
|
||||||
self.loss = loss
|
self.loss = loss
|
||||||
else: # the unit was wrong, we want DS = tan(delta), not NS = nanoSiemens
|
else: # the unit was wrong, we want DS = tan(delta), not NS = nanoSiemens
|
||||||
reply = self.sendRecv('UN DS').split() # UN DS returns a reply similar to SI
|
reply = self.communicate('UN DS').split() # UN DS returns a reply similar to SI
|
||||||
try:
|
try:
|
||||||
self.loss = reply[7]
|
self.loss = reply[7]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass # don't worry, loss will be updated next time
|
pass # don't worry, loss will be updated next time
|
||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
self.parse_reply(self.sendRecv('SI')) # SI = single trigger
|
self.parse_reply(self.communicate('SI')) # SI = single trigger
|
||||||
return Done
|
return Done
|
||||||
|
|
||||||
|
@nopoll
|
||||||
def read_freq(self):
|
def read_freq(self):
|
||||||
self.read_value()
|
self.read_value()
|
||||||
return Done
|
return Done
|
||||||
|
|
||||||
|
@nopoll
|
||||||
def read_loss(self):
|
def read_loss(self):
|
||||||
self.read_value()
|
self.read_value()
|
||||||
return Done
|
return Done
|
||||||
|
|
||||||
def read_volt(self):
|
@nopoll
|
||||||
|
def read_voltage(self):
|
||||||
self.read_value()
|
self.read_value()
|
||||||
return Done
|
return Done
|
||||||
|
|
||||||
def write_freq(self, value):
|
def write_freq(self, value):
|
||||||
self.parse_reply(self.sendRecv('FR %g;SI' % value))
|
self.parse_reply(self.communicate('FR %g;SI' % value))
|
||||||
return Done
|
return Done
|
||||||
|
|
||||||
def write_volt(self, value):
|
def write_voltage(self, value):
|
||||||
self.parse_reply(self.sendRecv('V %g;SI' % value))
|
self.parse_reply(self.communicate('V %g;SI' % value))
|
||||||
return Done
|
return Done
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"""drivers for CCU4, the cryostat control unit at SINQ"""
|
"""drivers for CCU4, the cryostat control unit at SINQ"""
|
||||||
# the most common Frappy classes can be imported from secop.core
|
# the most common Frappy classes can be imported from secop.core
|
||||||
from secop.core import EnumType, FloatRange, \
|
from secop.core import EnumType, FloatRange, \
|
||||||
HasIodev, Parameter, Readable, StringIO
|
HasIO, Parameter, Readable, StringIO
|
||||||
|
|
||||||
|
|
||||||
class CCU4IO(StringIO):
|
class CCU4IO(StringIO):
|
||||||
@ -34,14 +34,13 @@ class CCU4IO(StringIO):
|
|||||||
identification = [('cid', r'CCU4.*')]
|
identification = [('cid', r'CCU4.*')]
|
||||||
|
|
||||||
|
|
||||||
# inheriting the HasIodev mixin creates us a private attribute *_iodev*
|
# inheriting HasIO allows us to use the communicate method for talking with the hardware
|
||||||
# for talking with the hardware
|
|
||||||
# Readable as a base class defines the value and status parameters
|
# Readable as a base class defines the value and status parameters
|
||||||
class HeLevel(HasIodev, Readable):
|
class HeLevel(HasIO, Readable):
|
||||||
"""He Level channel of CCU4"""
|
"""He Level channel of CCU4"""
|
||||||
|
|
||||||
# define the communication class to create the IO module
|
# define the communication class to create the IO module
|
||||||
iodevClass = CCU4IO
|
ioClass = CCU4IO
|
||||||
|
|
||||||
# define or alter the parameters
|
# define or alter the parameters
|
||||||
# as Readable.value exists already, we give only the modified property 'unit'
|
# as Readable.value exists already, we give only the modified property 'unit'
|
||||||
@ -71,7 +70,7 @@ class HeLevel(HasIodev, Readable):
|
|||||||
for changing a parameter
|
for changing a parameter
|
||||||
:returns: the (new) value of the parameter
|
:returns: the (new) value of the parameter
|
||||||
"""
|
"""
|
||||||
name, txtvalue = self._iodev.communicate(cmd).split('=')
|
name, txtvalue = self.communicate(cmd).split('=')
|
||||||
assert name == cmd.split('=')[0] # check that we got a reply to our command
|
assert name == cmd.split('=')[0] # check that we got a reply to our command
|
||||||
return txtvalue # Frappy will automatically convert the string to the needed data type
|
return txtvalue # Frappy will automatically convert the string to the needed data type
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user