diff --git a/frappy_psi/channelswitcher.py b/frappy_psi/channelswitcher.py index f266b31..9e76207 100644 --- a/frappy_psi/channelswitcher.py +++ b/frappy_psi/channelswitcher.py @@ -109,7 +109,7 @@ class ChannelSwitcher(Drivable): def read_status(self): now = time.monotonic() - if self.status[0] == 'BUSY': + if self.isBusy(): chan = self.channels[self.target] if chan.is_switching(now, self._start_switch, self.switch_delay): return self.status diff --git a/frappy_psi/ls372.py b/frappy_psi/ls372.py index 7b0c313..4899b2d 100644 --- a/frappy_psi/ls372.py +++ b/frappy_psi/ls372.py @@ -202,6 +202,7 @@ class ResChannel(Channel): _toggle_autorange = 'init' _prev_rdgrng = (1, 1) # last read values for icur and exc _last_range_change = 0 + _value = None # if not None, a fresh value rdgrng_params = 'range', 'iexc', 'vexc' inset_params = 'enabled', 'pause', 'dwell' STATUS_MASK = 0x3f # mask T_OVER and T_UNDER @@ -221,7 +222,7 @@ class ResChannel(Channel): def read_status(self): if not self.enabled: return [self.Status.DISABLED, 'disabled'] - if not self.channel == self.switcher.value == self.switcher.target: + if self.switcher.isBusy(): return self.status result = int(self.communicate('RDGST?%d' % self.channel)) & self.STATUS_MASK statustext = ' '.join(formatStatusBits(result, STATUS_BIT_LABELS)) @@ -259,15 +260,19 @@ class ResChannel(Channel): return result def read_value(self): - if self.channel == self.switcher.value == self.switcher.target: - return self._read_value() or self.value - return self.value if self.enabled else 0 + if self.switcher.isBusy(): + return self.value if self.enabled else 0 + value = self._read_value() if self._value is None else self._value + self._value = None + return self.value if value is None else value def is_switching(self, now, last_switch, switch_delay): - last_switch = max(last_switch, self._last_range_change) - if now + 0.5 > last_switch + self.pause: - self._read_value() # adjust range only - return super().is_switching(now, last_switch, switch_delay) + result = super().is_switching(now, last_switch, switch_delay) + if not result: # the time since last switch has passed + self._value = self._read_value() # for autorange + # now check for the time since last range change + result = super().is_switching(now, self._last_range_change, switch_delay) + return result @CommonReadHandler(rdgrng_params) def read_rdgrng(self): @@ -377,7 +382,6 @@ class TemperatureLoop(HasConvergence, TemperatureChannel, Drivable): HTRRNG = {n: i for i, n in enumerate(['off', '30uA', '100uA', '300uA', '1mA', '3mA', '10mA', '30mA', '100mA'])} htrrng = Parameter('', EnumType(HTRRNG), readonly=False) _control_active = False - _underflow = False @Command def control_off(self): @@ -399,16 +403,6 @@ class TemperatureLoop(HasConvergence, TemperatureChannel, Drivable): if newhtr: self.log.info('switched heater on %d', newhtr) self.communicate(f'RANGE {self.loop},{newhtr};RANGE?{self.loop}') - if self.minheater: - self.log.info('underflow open loop') - self._underflow = True - self.communicate(f'OUTMODE {self.loop},2,{self.channel},0,0,1,3;*OPC?') - self.communicate(f'MOUT {self.loop}{self.min_percent()};*OPC?') - elif self._underflow: - self.log.info('switch to control after underflow') - self._underflow = False - self.communicate(f'OUTMODE {self.loop},5,{self.channel},0,0,1,3;*OPC?') - self.communicate(f'SETP {self.loop},{self.target};*OPC?') def read_control_active(self): self.set_htrrng() @@ -416,7 +410,7 @@ class TemperatureLoop(HasConvergence, TemperatureChannel, Drivable): def read_target(self): if self._control_active: - return float(self.communicate('SETP?{self.loop}')) + return float(self.communicate(f'SETP?{self.loop}')) return 0 def write_htrrng(self, value): @@ -428,15 +422,15 @@ class TemperatureLoop(HasConvergence, TemperatureChannel, Drivable): outmode = (5, self.channel, 0, 0, 1, 3) prev = parse(self.communicate(f'OUTMODE?{self.loop}')) if outmode != prev: - self.communicate(f'OUTMODE {self.loop}, %g,%g,%g,%g,%g,%g;*OPC?' % tuple(outmode)) + self.communicate(f'OUTMODE {self.loop},%g,%g,%g,%g,%g,%g;*OPC?' % tuple(outmode)) self.communicate(f'MOUT {self.loop},{self.min_percent()};*OPC?') for chan in self.switcher.channels.values(): chan._control_active = False self._control_active = True self.read_control_active() self.convergence_start() - # do not resturn the readback value, as it might not yet be correct - self.communicate(f'SETP {self.loop},{target};SETP?{self.loop}') + # do not return the readback value, as it might not yet be correct + self.communicate(f'SETP {self.loop},{target};*OPC?') return target #def write_ctrlpars(self, ctrlpars): diff --git a/frappy_psi/parmod.py b/frappy_psi/parmod.py index 338fc11..ca84503 100644 --- a/frappy_psi/parmod.py +++ b/frappy_psi/parmod.py @@ -134,7 +134,12 @@ def get_value(obj, default): """get the value of given module. if not valid, return the limit (min_high or max_low)""" if not getattr(obj, 'enabled', True): return default - return obj.value if IDLE <= obj.status[0] < ERROR else default + # consider also that a value 0 is invalid + return (obj.value if IDLE <= obj.status[0] < ERROR else 0) or default + + +LOW = 0 +HIGH = 1 class SwitchDriv(HasConvergence, Drivable): @@ -143,14 +148,32 @@ class SwitchDriv(HasConvergence, Drivable): min_high = Parameter('minimum high target', FloatRange(unit='$'), readonly=False) max_low = Parameter('maximum low target', FloatRange(unit='$'), readonly=False) # disable_other = Parameter('whether to disable unused channel', BoolType(), readonly=False) - selected = Parameter('selected module', EnumType(low=0, high=1), readonly=False, default=0) + selected = Parameter('selected module', EnumType(low=LOW, high=HIGH), readonly=False, default=0) + _switch_target = None # if not None, switch to selection mhen mid range is reached # TODO: copy units from attached module # TODO: callbacks for updates def doPoll(self): super().doPoll() - if not self.isBusy(): + if self.isBusy(): + if self._switch_target is not None: + mid = (self.min_high + self.max_low) * 0.5 + if self._switch_target == HIGH: + low = get_value(self.low, mid) # returns mid when low is invalid + if low > mid: + self.value = self.low.value + self._switch_target = None + self.write_target(self.target) + return + else: + high = get_value(self.high, mid) # return mid then high is invalid + if high < mid: + self.value = self.high.value + self._switch_target = None + self.write_target(self.target) + return + else: low = get_value(self.low, self.max_low) high = get_value(self.high, self.min_high) low_valid = low < self.max_low @@ -167,28 +190,36 @@ class SwitchDriv(HasConvergence, Drivable): set_enabled(self.high, True) def read_value(self): - return self.low.value if self.selected == self.selected.low else self.high.value + return self.low.value if self.selected == LOW else self.high.value def read_status(self): - status = self.low.status if self.selected == self.selected.low else self.high.status + status = self.low.status if self.selected == LOW else self.high.status if status[0] >= ERROR: return status return super().read_status() # convergence status - def read_target(self): - if self.selected == self.selected.low: - return self.low.target - return self.high.target - def write_target(self, target): this, other = self.low, self.high selected = self.selected + target1 = target + self._switch_target = None if target > self.max_low: - this, other = other, this - selected = self.selected.high + if self.value < self.min_high: + target1 = self.max_low + self._switch_target = HIGH + selected = LOW + else: + this, other = other, this + selected = HIGH elif target < self.min_high: - selected = self.selected.low - elif self.selected == self.selected.high: + if self.value > self.max_low: + target1 = self.min_high + self._switch_target = LOW + this, other = other, this + selected = HIGH + else: + selected = LOW + elif self.selected == HIGH: this, other = other, this if hasattr(other, 'control_off'): other.control_off() @@ -197,4 +228,5 @@ class SwitchDriv(HasConvergence, Drivable): self.write_selected(selected) self.convergence_start() self.log.info('target=%g (%s)', target, this.name) - return this.write_target(target) + this.write_target(target1) + return target