state as of 6.7.2026
- use new function matches_with_string_precision - chk_crvpts instead of get_crvpts and then check Change-Id: I724f23edfa6b5e269950073641b6e852cfa03713
This commit is contained in:
@@ -3,9 +3,11 @@ Node('ls370test.psi.ch',
|
||||
interface='tcp://5000',
|
||||
)
|
||||
|
||||
### temperature monitoring lakeshore (tmon) ###
|
||||
IO('io_treg', 'dil2-ts:3001')
|
||||
IO('io_tmon', 'dil2-ts:3003')
|
||||
|
||||
IO('io_tmon', 'dil3-ts:3003')
|
||||
|
||||
### temperature monitoring lakeshore (tmon) ###
|
||||
|
||||
Mod('tmon',
|
||||
'frappy_psi.lakeshore370.Device',
|
||||
@@ -88,8 +90,6 @@ Mod('onek',
|
||||
|
||||
### temperature regulating lakeshore (treg) ###
|
||||
|
||||
IO('io_treg', 'dil3-ts:3001')
|
||||
|
||||
Mod('treg',
|
||||
'frappy_psi.lakeshore370.Device',
|
||||
'regulating lakeshore 370',
|
||||
@@ -161,4 +161,4 @@ Mod('relais',
|
||||
# io = 'io',
|
||||
# channel = 5,
|
||||
# switcher = 'switcher',
|
||||
# )
|
||||
# )
|
||||
|
||||
@@ -32,6 +32,7 @@ import traceback
|
||||
from configparser import ConfigParser
|
||||
from os import environ, path
|
||||
from pathlib import Path
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
SECoP_DEFAULT_PORT = 10767
|
||||
@@ -496,3 +497,14 @@ def delayed_import(modname):
|
||||
except Exception:
|
||||
return _Raiser(modname)
|
||||
return module
|
||||
|
||||
|
||||
def matches_with_string_precision(floatstring, value: float) -> bool:
|
||||
"""check if floatstring is equal to value within precision derived from floatstring
|
||||
|
||||
the result is tolerant to any deviation less than one unit of the last given digit
|
||||
created with the help of Claude AI
|
||||
"""
|
||||
target = Decimal(floatstring)
|
||||
quantizer = Decimal(1).scaleb(target.as_tuple().exponent)
|
||||
return abs(Decimal(str(value)) - target) < quantizer
|
||||
@@ -38,7 +38,7 @@ switcher=sw
|
||||
import time
|
||||
|
||||
from frappy.datatypes import IntRange, BoolType, FloatRange
|
||||
from frappy.core import Attached, Property, Drivable, Parameter, Readable
|
||||
from frappy.core import Attached, Property, Drivable, Parameter, Readable, ERROR
|
||||
|
||||
|
||||
class ChannelSwitcher(Drivable):
|
||||
@@ -116,8 +116,9 @@ class ChannelSwitcher(Drivable):
|
||||
self.status = 'IDLE', 'measure'
|
||||
self.value = self.target
|
||||
self._start_measure = self._last_measure = now
|
||||
chan.read_value()
|
||||
chan.read_status()
|
||||
if chan.status[0] < ERROR:
|
||||
chan.read_value()
|
||||
if self.measure_delay > self._time_tol:
|
||||
return self.status
|
||||
else:
|
||||
|
||||
+67
-46
@@ -15,6 +15,7 @@
|
||||
# Module authors:
|
||||
# Oksana Shliakhtun <oksana.shliakhtun@psi.ch>
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
# Anik Stark <anik.stark@psi.ch>
|
||||
# *****************************************************************************
|
||||
"""base classes for various lakeshore temperature monitors/controllers"""
|
||||
|
||||
@@ -31,11 +32,11 @@ from frappy.datatypes import IntRange, FloatRange, StringType, \
|
||||
from frappy.errors import CommunicationFailedError, ConfigError, \
|
||||
HardwareError, DisabledError, ImpossibleError, secop_error, SECoPError
|
||||
from frappy.lib.units import NumberWithUnit, format_with_unit
|
||||
from frappy.lib import formatStatusBits
|
||||
from frappy.lib import formatStatusBits, matches_with_string_precision
|
||||
from frappy.lib.enum import EnumMember
|
||||
from frappy_psi.calcurve import CalCurve
|
||||
from frappy_psi.convergence import HasConvergence
|
||||
from frappy.mixins import HasOutputModule, HasControlledBy
|
||||
from frappy.ctrlby import HasOutputModule, HasControlledBy
|
||||
from frappy.extparams import StructParam
|
||||
|
||||
|
||||
@@ -269,18 +270,13 @@ class Device(HasLscIO, Module):
|
||||
"""preliminary check: check if the ends of the curve are matching the stored ones"""
|
||||
npnt = len(request.points)
|
||||
numbers = [1, npnt]
|
||||
points = [request.points[0], request.points[npnt-1]]
|
||||
if npnt < self.max_curve_length:
|
||||
numbers.append(npnt + 1)
|
||||
points.append((0, 0))
|
||||
stored_points = self.get_crvpts(no, *numbers)
|
||||
for pairs in zip(points, stored_points):
|
||||
if not self.is_equal(*pairs):
|
||||
self.log.info('not equal %r', pairs)
|
||||
self.log.info('requested points %r', points)
|
||||
self.log.info('stored points %r', stored_points)
|
||||
return False
|
||||
return True
|
||||
mismatch = self.chk_crvpts(no, numbers, request.points)
|
||||
if mismatch is None:
|
||||
return True
|
||||
self.log.info('%s', *mismatch)
|
||||
return False
|
||||
|
||||
def find_curve(self, request):
|
||||
"""try to find curve and return required action
|
||||
@@ -341,21 +337,20 @@ class Device(HasLscIO, Module):
|
||||
sensors = request.new_sensors
|
||||
request.new_sensors = set()
|
||||
request.install_sensors(sensors)
|
||||
given = request.points[request.pointer:request.pointer + self.cmds_per_line]
|
||||
first = request.pointer + 1
|
||||
num1 = request.pointer + 1
|
||||
ngiven = list(range(num1, min(len(request.points) + 1, num1 + self.cmds_per_line)))
|
||||
no = request.curve_no
|
||||
for n, (pt, ptg) in enumerate(zip(self.get_crvpts(no, *range(first, first + len(given))), given)):
|
||||
if not self.is_equal(pt, ptg):
|
||||
self.log.info('reply (%g, %g) does not match given point %d (%g,%g)',
|
||||
*pt, first + n, *ptg)
|
||||
request.invalidate = no
|
||||
request.set_curve_no(self.get_empty())
|
||||
self.log.info('%s has changed, create #%d', request.crvhdr.sn, request.curve_no)
|
||||
request.loading = True
|
||||
return self.start_load
|
||||
|
||||
request.pointer += self.cmds_per_line
|
||||
return self.check_points
|
||||
mismatch = self.chk_crvpts(no, ngiven, request.points)
|
||||
if not mismatch:
|
||||
request.pointer += self.cmds_per_line
|
||||
# continue soon
|
||||
return self.check_points
|
||||
self.log.info('%s', mismatch)
|
||||
request.invalidate = no
|
||||
request.set_curve_no(self.get_empty())
|
||||
self.log.info('%s has changed, create #%d', request.crvhdr.sn, request.curve_no)
|
||||
request.loading = True
|
||||
return self.start_load
|
||||
|
||||
def start_load(self, request):
|
||||
"""start loading a curve
|
||||
@@ -436,7 +431,7 @@ class Device(HasLscIO, Module):
|
||||
return abs(v1 - v2) < eps
|
||||
return True
|
||||
|
||||
def get_crvpts(self, curve_no, *numbers):
|
||||
def get_crvpts(self, curve_no, *numbers): # TODO: remove as obsolete
|
||||
"""read curve points
|
||||
|
||||
:param curve_no: curve number
|
||||
@@ -449,6 +444,29 @@ class Device(HasLscIO, Module):
|
||||
replies.extend(self.communicate(';'.join(cmds)).split(';'))
|
||||
return [[float(v) for v in xy.split(',')] for xy in replies]
|
||||
|
||||
def chk_crvpts(self, curve_no, numbers, values):
|
||||
"""check curve points
|
||||
|
||||
:param curve_no: curve number
|
||||
:param numbers: pint numbers of points to check (starting at 1)
|
||||
:param values: list or numpy array of points (x, y) (indices starting at 0)
|
||||
:return: None on success, first mismatch (idx, strvalue, value) on failure
|
||||
|
||||
Note: for checking the point after the end of the table to be (0,0) an
|
||||
index == len(values) may be given
|
||||
"""
|
||||
for i in range(0, len(numbers), self.cmds_per_line):
|
||||
pntno = numbers[i:i+self.cmds_per_line]
|
||||
cmds = [f'CRVPT?{curve_no},{n}' for n in pntno]
|
||||
replies = self.communicate(';'.join(cmds)).split(';')
|
||||
for pair, no in zip(replies, pntno):
|
||||
given = values[no - 1] if no <= len(values) else (0, 0)
|
||||
for strvalue, value in zip(pair.split(','), given):
|
||||
if not matches_with_string_precision(strvalue, value):
|
||||
x, y = given
|
||||
return f'point {no}: {pair} does not match given point ({x:g},{y:g})'
|
||||
return None
|
||||
|
||||
def get_headers(self):
|
||||
"""get all headers
|
||||
|
||||
@@ -689,27 +707,23 @@ class Sensor(SensorBase):
|
||||
return rdgst, raw, value
|
||||
|
||||
def read_status(self):
|
||||
status, self.value, self.raw = self.get_data()
|
||||
if self._raw_error:
|
||||
self.announceUpdate('raw', err=self._raw_error)
|
||||
if self._value_error:
|
||||
self.announceUpdate('value', err=self._value_error)
|
||||
status, value, raw = self.get_data()
|
||||
self.announceUpdate('raw', raw, self._raw_error)
|
||||
self.announceUpdate('value', value, self._value_error)
|
||||
return status
|
||||
|
||||
@nopoll
|
||||
def read_raw(self):
|
||||
self.status, self.value, raw = self.get_data()
|
||||
if self._value_error:
|
||||
self.announceUpdate('value', err=self._value_error)
|
||||
self.status, value, raw = self.get_data()
|
||||
self.announceUpdate('value', value, self._value_error)
|
||||
if self._raw_error:
|
||||
raise self._raw_error
|
||||
return raw
|
||||
|
||||
@nopoll
|
||||
def read_value(self):
|
||||
self.status, value, self.raw = self.get_data()
|
||||
if self._raw_error:
|
||||
self.announceUpdate('raw', err=self._raw_error)
|
||||
self.status, value, raw = self.get_data()
|
||||
self.announceUpdate('raw', raw, err=self._raw_error)
|
||||
if self._value_error:
|
||||
raise self._value_error
|
||||
return value
|
||||
@@ -783,7 +797,6 @@ class Output(Base, HasControlledBy, Writable):
|
||||
sorted_factors = None
|
||||
errorstatus = None
|
||||
_desired_max_power = None
|
||||
_control_loop = None # a loop module object when controlled, None when in manual mode
|
||||
power_offset = 0 # offset for closed_loop
|
||||
|
||||
def configure(self):
|
||||
@@ -793,6 +806,10 @@ class Output(Base, HasControlledBy, Writable):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_control_loop(self):
|
||||
"""get controlling module object or None"""
|
||||
return None if self.controlled_by == 'self' else self.secNode.modules[self.controlled_by.name]
|
||||
|
||||
def fix_heater_range(self):
|
||||
"""switch on heater range, if off"""
|
||||
|
||||
@@ -897,8 +914,10 @@ class MainOutput(Output):
|
||||
self.heater_ranges[irng] / self.resistance)))
|
||||
self._power_scale = user_current ** 2 * self.heater_ranges[irng] / 1e4
|
||||
self.command(f'HTRSET {self.output_no}', 1 if self.resistance < 50 else 2, 0, user_current, 1)
|
||||
control_loop = self.get_control_loop()
|
||||
self.log.info('configure %r', control_loop)
|
||||
# self.command(f'CDISP {self.output_no}', 1, self.resistance, 1, 0)
|
||||
if self._control_loop is None:
|
||||
if control_loop is None:
|
||||
mode = self.query(f'OUTMODE?{self.output_no}', int)
|
||||
if mode != 3: # open loop
|
||||
self.command(f'OUTMODE {self.output_no}', 3) # control off
|
||||
@@ -906,9 +925,9 @@ class MainOutput(Output):
|
||||
self.command(f'RANGE {self.output_no}', self._htr_range)
|
||||
self.put_manual_power(self.target)
|
||||
else:
|
||||
self.command(f'OUTMODE {self.output_no}', 1, self._control_loop.channel, 0) # control on
|
||||
self.command(f'OUTMODE {self.output_no}', 1, control_loop.channel, 0) # control on
|
||||
self.command(f'RANGE {self.output_no}', self._htr_range)
|
||||
self.put_manual_power(self._control_loop.power_offset)
|
||||
self.put_manual_power(control_loop.power_offset)
|
||||
|
||||
def read_max_power(self):
|
||||
curidx, self._user_current = self.query(f'HTRSET? {self.output_no}', int, int, float)[1:3] # ! cmd not found in ls370 manual (HTRRNG?)
|
||||
@@ -962,14 +981,15 @@ class AnalogOutput(Output):
|
||||
self.imax ** 2 / self.resistance)
|
||||
|
||||
def configure(self):
|
||||
if self._control_loop is None:
|
||||
control_loop = self.get_control_loop()
|
||||
if control_loop is None:
|
||||
# 3: open loop, 0: powerup enable off
|
||||
self.command(f'OUTMODE {self.output_no}', 3, 0, 0)
|
||||
self.put_manual_power(self.target)
|
||||
else:
|
||||
# 1: closed loop, 0: powerup enable off
|
||||
self.command(f'OUTMODE {self.output_no}', 1, self._control_loop.channel, 0)
|
||||
self.put_manual_power(self._control_loop.power_offset)
|
||||
self.command(f'OUTMODE {self.output_no}', 1, control_loop.channel, 0)
|
||||
self.put_manual_power(control_loop.power_offset)
|
||||
|
||||
def read_htr(self):
|
||||
return self.query(f'AOUT?{self.output_no}', float)
|
||||
@@ -1017,7 +1037,8 @@ class Loop(HasConvergence, HasOutputModule, Sensor):
|
||||
def get_target(self):
|
||||
return self.query(f'SETP?{self.loop()}', float)
|
||||
|
||||
def set_control_active(self, active):
|
||||
def write_control_active(self, active):
|
||||
self.log.info('control_active %r', active)
|
||||
if active:
|
||||
controlled_by = self.name
|
||||
if self.output_module.controlled_by != controlled_by:
|
||||
|
||||
@@ -199,6 +199,7 @@ class SensorBase(Ls370, ls.SensorBase, Channel):
|
||||
inset_params = 'enabled', 'pause', 'dwell'
|
||||
_curve = None
|
||||
tempco = None
|
||||
STATUS_BIT_LABELS = 'cs_ovl vcm_ovl vmix_ovl vdif_ovl r_over r_under t_over t_under'.split()
|
||||
|
||||
def initModule(self):
|
||||
# take io from switcher
|
||||
@@ -411,6 +412,8 @@ class TemperatureLoop(ls.Loop, Sensor, Drivable):
|
||||
# return 0
|
||||
|
||||
def set_target(self, target):
|
||||
self.log.info('set_target %g out.controlled_by %s %s %r', target, self.output_module.name,
|
||||
self.output_module.controlled_by, self.channel)
|
||||
outmode = 1
|
||||
prev = self.query(f'CMODE?', int)
|
||||
if outmode != prev:
|
||||
@@ -427,6 +430,7 @@ class TemperatureLoop(ls.Loop, Sensor, Drivable):
|
||||
|
||||
HEATER_RANGES = {8 - i: 10 ** -i for i in range(8)}
|
||||
|
||||
|
||||
class MainOutput(ls.MainOutput):
|
||||
ioClass = IO
|
||||
model = 370
|
||||
@@ -461,7 +465,8 @@ class MainOutput(ls.MainOutput):
|
||||
icurrent, htr_range = self.get_best_power_idx(self._desired_max_power, 1.1)
|
||||
self._power_scale = self.max_currents[icurrent] ** 2 * self.heater_ranges[htr_range] / 1e4
|
||||
self._htr_range = htr_range
|
||||
if self._control_loop is None:
|
||||
control_loop = self.get_control_loop()
|
||||
if control_loop is None:
|
||||
mode = self.query(f'CMODE?', int)
|
||||
if mode != 3: # open loop
|
||||
self.command(f'CSET', '0', 1, 1, 1, 1, self._htr_range, self.resistance) # control off
|
||||
@@ -469,10 +474,11 @@ class MainOutput(ls.MainOutput):
|
||||
self.command('HTRRNG', self._htr_range)
|
||||
self.put_manual_power(self._manual_output)
|
||||
else:
|
||||
self.command(f'CSET', self._control_loop.channel, 1, 1, 1, 1, self._htr_range, self.resistance) # control on
|
||||
self.log.info('control_loop %r, %r', control_loop, control_loop.channel)
|
||||
self.command(f'CSET', control_loop.channel, 1, 1, 1, 1, self._htr_range, self.resistance) # control on
|
||||
self.command(f'CMODE', 1) # pid
|
||||
self.command('HTRRNG', self._htr_range)
|
||||
self.put_manual_power(self._control_loop.power_offset)
|
||||
self.put_manual_power(control_loop.power_offset)
|
||||
|
||||
def fix_heater_range(self):
|
||||
# switch heater range on, if needed
|
||||
|
||||
Reference in New Issue
Block a user