diff --git a/README.md b/README.md index 45742f7..f59ef37 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ changes are done, eventually a sync step should happen: 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 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 6) go through commits in wip and sort out: - core commits already pushed through gerrit are skipped diff --git a/secop/paths.py b/secop/paths.py deleted file mode 100644 index d43cd69..0000000 --- a/secop/paths.py +++ /dev/null @@ -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 -# -# ***************************************************************************** -"""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') diff --git a/secop/poller.py b/secop/poller.py deleted file mode 100644 index 5a4c3b4..0000000 --- a/secop/poller.py +++ /dev/null @@ -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 -# -# ***************************************************************************** -"""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 (, ) as the key, and the - poller as value. - 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 diff --git a/secop_psi/ah2700.py b/secop_psi/ah2700.py index f726f0f..df403ee 100644 --- a/secop_psi/ah2700.py +++ b/secop_psi/ah2700.py @@ -20,7 +20,7 @@ # ***************************************************************************** """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): @@ -28,19 +28,19 @@ class Ah2700IO(StringIO): 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) voltage = Parameter('voltage', FloatRange(unit='V'), readonly=False, default=0) loss = Parameter('loss', FloatRange(unit='deg'), default=0) - iodevClass = Ah2700IO + ioClass = Ah2700IO def parse_reply(self, reply): if reply.startswith('SI'): # this is an echo - self.sendRecv('SERIAL ECHO OFF') - reply = self.sendRecv('SI') + self.communicate('SERIAL ECHO OFF') + reply = self.communicate('SI') if not reply.startswith('F='): # this is probably an error message like "LOSS TOO HIGH" self.status = [self.Status.ERROR, reply] return @@ -59,32 +59,35 @@ class Capacitance(HasIodev, Readable): if lossunit == 'DS': self.loss = loss 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: self.loss = reply[7] except IndexError: pass # don't worry, loss will be updated next time def read_value(self): - self.parse_reply(self.sendRecv('SI')) # SI = single trigger + self.parse_reply(self.communicate('SI')) # SI = single trigger return Done + @nopoll def read_freq(self): self.read_value() return Done + @nopoll def read_loss(self): self.read_value() return Done - def read_volt(self): + @nopoll + def read_voltage(self): self.read_value() return Done 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 - def write_volt(self, value): - self.parse_reply(self.sendRecv('V %g;SI' % value)) + def write_voltage(self, value): + self.parse_reply(self.communicate('V %g;SI' % value)) return Done diff --git a/secop_psi/ccu4.py b/secop_psi/ccu4.py index 0aaaa52..e263537 100644 --- a/secop_psi/ccu4.py +++ b/secop_psi/ccu4.py @@ -23,7 +23,7 @@ """drivers for CCU4, the cryostat control unit at SINQ""" # the most common Frappy classes can be imported from secop.core from secop.core import EnumType, FloatRange, \ - HasIodev, Parameter, Readable, StringIO + HasIO, Parameter, Readable, StringIO class CCU4IO(StringIO): @@ -34,14 +34,13 @@ class CCU4IO(StringIO): identification = [('cid', r'CCU4.*')] -# inheriting the HasIodev mixin creates us a private attribute *_iodev* -# for talking with the hardware +# inheriting HasIO allows us to use the communicate method for talking with the hardware # Readable as a base class defines the value and status parameters -class HeLevel(HasIodev, Readable): +class HeLevel(HasIO, Readable): """He Level channel of CCU4""" # define the communication class to create the IO module - iodevClass = CCU4IO + ioClass = CCU4IO # define or alter the parameters # as Readable.value exists already, we give only the modified property 'unit' @@ -71,7 +70,7 @@ class HeLevel(HasIodev, Readable): for changing a 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 return txtvalue # Frappy will automatically convert the string to the needed data type