fetched mlz version
- before some chamges in the gerrit pipline Change-Id: I33eb2d75f83345a7039d0fb709e66defefb1c3e0
This commit is contained in:
183
frappy_psi/channelswitcher.py
Normal file
183
frappy_psi/channelswitcher.py
Normal file
@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- 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>
|
||||
# *****************************************************************************
|
||||
"""channel switcher Mixin
|
||||
|
||||
Example Config File:
|
||||
|
||||
[sw]
|
||||
description=the switcher for blabla channels
|
||||
class=frappy_facility.module.YourChannelSwitcher
|
||||
uri=...
|
||||
|
||||
[chan1]
|
||||
description=channel 1
|
||||
class=frappy_facility.module.YourChannel
|
||||
switcher=sw
|
||||
|
||||
[chan2]
|
||||
...
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from frappy.datatypes import IntRange, BoolType, FloatRange
|
||||
from frappy.core import Attached, Property, Drivable, Parameter, Readable
|
||||
|
||||
|
||||
class ChannelSwitcher(Drivable):
|
||||
"""base class for the channel switcher
|
||||
|
||||
minimum implementation:
|
||||
- override :meth:`set_active_channel`
|
||||
|
||||
- <channel>.read_value() and <channel>.read_status() is called periodically
|
||||
every <channel>.pollinterval on the active channel only
|
||||
- <channel>.is_switching(...) is called every <switcher>.pollinterval
|
||||
during switching period, until it is returning False
|
||||
"""
|
||||
value = Parameter('the current channel number', IntRange(), needscfg=False)
|
||||
target = Parameter('channel to select', IntRange(), needscfg=False)
|
||||
status = Parameter(update_unchanged='never')
|
||||
autoscan = Parameter('whether to scan automatically',
|
||||
BoolType(), readonly=False, default=True)
|
||||
pollinterval = Parameter(default=1, export=False)
|
||||
switch_delay = Parameter('the time needed to switch between channels',
|
||||
FloatRange(0, None), readonly=False, default=5)
|
||||
measure_delay = Parameter('the time for staying at a channel',
|
||||
FloatRange(0, None), readonly=False, default=2)
|
||||
|
||||
fast_poll = 0.1
|
||||
channels = None # dict <channel no> of <module object>
|
||||
_start_measure = 0
|
||||
_last_measure = 0
|
||||
_start_switch = 0
|
||||
_time_tol = 0.5
|
||||
_first_channel = None
|
||||
|
||||
def earlyInit(self):
|
||||
super().earlyInit()
|
||||
self.channels = {}
|
||||
|
||||
def register_channel(self, mod):
|
||||
"""register module"""
|
||||
if not self.channels:
|
||||
self._first_channel = mod
|
||||
self.channels[mod.channel] = mod
|
||||
|
||||
def set_active_channel(self, chan):
|
||||
"""tell the HW the active channel
|
||||
|
||||
:param chan: a channel object
|
||||
|
||||
to be implemented
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def next_channel(self, channelno):
|
||||
next_channel = channelno
|
||||
first_channel = None
|
||||
for ch, mod in self.channels.items():
|
||||
if mod.enabled:
|
||||
if first_channel is None:
|
||||
first_channel = ch
|
||||
if next_channel == ch:
|
||||
next_channel = None
|
||||
elif next_channel is None:
|
||||
next_channel = ch
|
||||
break
|
||||
else:
|
||||
next_channel = first_channel
|
||||
return next_channel
|
||||
|
||||
def read_status(self):
|
||||
now = time.monotonic()
|
||||
if self.status[0] == 'BUSY':
|
||||
chan = self.channels[self.target]
|
||||
if chan.is_switching(now, self._start_switch, self.switch_delay):
|
||||
return self.status
|
||||
self.setFastPoll(False)
|
||||
self.status = 'IDLE', 'measure'
|
||||
self.value = self.target
|
||||
self._start_measure = self._last_measure = now
|
||||
chan.read_value()
|
||||
chan.read_status()
|
||||
if self.measure_delay > self._time_tol:
|
||||
return self.status
|
||||
else:
|
||||
chan = self.channels.get(self.value, self._first_channel)
|
||||
self.read_value() # this might modify autoscan or deadline!
|
||||
if chan.enabled:
|
||||
if self.target != self.value: # may happen after startup
|
||||
self.target = self.value
|
||||
next_measure = self._last_measure + chan.pollinterval
|
||||
if now + self._time_tol > next_measure:
|
||||
chan.read_value()
|
||||
chan.read_status()
|
||||
self._last_measure = next_measure
|
||||
if not self.autoscan or now + self._time_tol < self._start_measure + self.measure_delay:
|
||||
return self.status
|
||||
next_channel = self.next_channel(self.value)
|
||||
if next_channel == self.value:
|
||||
return 'IDLE', 'single channel'
|
||||
if next_channel is None:
|
||||
return 'ERROR', 'no enabled channel'
|
||||
self.write_target(next_channel)
|
||||
return self.status
|
||||
|
||||
def write_pollinterval(self, value):
|
||||
self._time_tol = min(1, value) * 0.5
|
||||
return value
|
||||
|
||||
def write_target(self, channel):
|
||||
if channel not in self.channels:
|
||||
raise ValueError(f'{channel!r} is no valid channel')
|
||||
if channel == self.target and self.channels[channel].enabled:
|
||||
return channel
|
||||
chan = self.channels[channel]
|
||||
chan.enabled = True
|
||||
self.set_active_channel(chan)
|
||||
self._start_switch = time.monotonic()
|
||||
self.status = 'BUSY', 'change channel'
|
||||
self.setFastPoll(True, self.fast_poll)
|
||||
return channel
|
||||
|
||||
|
||||
class Channel(Readable):
|
||||
"""base class for channels
|
||||
|
||||
you should override the datatype of the channel property,
|
||||
in order to match the datatype of the switchers value
|
||||
"""
|
||||
switcher = Attached()
|
||||
channel = Property('channel number', IntRange())
|
||||
enabled = Parameter('enabled flag', BoolType(), default=True)
|
||||
value = Parameter(needscfg=False)
|
||||
|
||||
def initModule(self):
|
||||
super().initModule()
|
||||
self.switcher.register_channel(self)
|
||||
|
||||
def doPoll(self):
|
||||
"""value and status are polled by switcher"""
|
||||
|
||||
def is_switching(self, now, last_switch, switch_delay):
|
||||
"""returns True when switching is done"""
|
||||
return now + self.switcher._time_tol < last_switch + switch_delay
|
Reference in New Issue
Block a user