frappy_psi.bridge: changes proposed by claude.ai
Change-Id: Id392d3e5cd8b0f3048d35d6b4146f4193bb2dcc9
This commit is contained in:
1 parent
574fdabf36
commit
71495aa934
1 file changed
+80
-32
+80
-32
@@ -21,8 +21,9 @@ import re
|
||||
from bisect import bisect
|
||||
from frappy.core import StringIO, HasIO, Readable, \
|
||||
Parameter, FloatRange, IntRange, EnumType, \
|
||||
Property, Attached, IDLE, ERROR, WARN
|
||||
Property, Attached, Command, IDLE, ERROR, WARN
|
||||
from frappy.lib import clamp
|
||||
from frappy.errors import RangeError
|
||||
|
||||
|
||||
def string_to_value(value):
|
||||
@@ -31,7 +32,7 @@ def string_to_value(value):
|
||||
:param value: value
|
||||
:return: float value without units
|
||||
"""
|
||||
value_with_unit = re.compile(r'(\d+)([pnumkMG]?)')
|
||||
value_with_unit = re.compile(r'(\d+(?:\.\d+)?)([pnumkMG]?)')
|
||||
value, pfx = value_with_unit.match(value).groups()
|
||||
pfx_dict = {'p': 1e-12, 'n': 1e-9, 'u': 1e-6, 'm': 1e-3, 'k': 1e3, 'M': 1e6, 'G': 1e9}
|
||||
if pfx in pfx_dict:
|
||||
@@ -41,20 +42,22 @@ def string_to_value(value):
|
||||
|
||||
def find_idx(list_of_values, target):
|
||||
"""
|
||||
Search for the nearest value and index from the given range for the given target.
|
||||
Search for the nearest value (from above) in the given list for the given target.
|
||||
:param list_of_values: range of values
|
||||
:param target: target
|
||||
:return: closest index and closest value
|
||||
:return: (closest index, closest value), or (None, None) if nothing in the
|
||||
list is >= target
|
||||
"""
|
||||
target = float(target)
|
||||
cl_idx = None
|
||||
cl_value = float('inf')
|
||||
cl_value = None
|
||||
best_diff = float('inf')
|
||||
|
||||
for idx, value in enumerate(list_of_values):
|
||||
if value >= target:
|
||||
diff = value - target
|
||||
|
||||
if diff < cl_value:
|
||||
if diff < best_diff:
|
||||
best_diff = diff
|
||||
cl_value = value
|
||||
cl_idx = idx
|
||||
|
||||
@@ -87,8 +90,9 @@ class Base(HasIO):
|
||||
|
||||
|
||||
class Resistance(Base, Readable):
|
||||
value = Parameter('resistance', datatype=FloatRange, unit='Ohm')
|
||||
output_offset = Parameter('resistance deviation', datatype=FloatRange, unit='Ohm', readonly=False)
|
||||
value = Parameter('resistance', datatype=FloatRange(unit='Ohm'))
|
||||
output_offset = Parameter('resistance setpoint (RSET), subtracted to form the deviation (RDEV)',
|
||||
datatype=FloatRange(unit='Ohm'), readonly=False)
|
||||
phase_hold = Parameter('phase hold', EnumType('phase hold', off=0, on=1))
|
||||
|
||||
RES_RANGE = ['20mOhm', '200mOhm', '2Ohm', '20Ohm', '200Ohm', '2kOhm', '20kOhm', '200kOhm',
|
||||
@@ -96,31 +100,62 @@ class Resistance(Base, Readable):
|
||||
irange = Parameter('resistance range index', EnumType('resistance range index',
|
||||
{name: idx for idx, name in enumerate(RES_RANGE)}),
|
||||
readonly=False)
|
||||
range = Parameter('resistance range value', FloatRange(2e-2, 2e7), unit='Ohm', readonly=False)
|
||||
range = Parameter('resistance range value', FloatRange(2e-2, 2e7, unit='Ohm'), readonly=False)
|
||||
|
||||
TIME_CONST = ['0.3s', '1s', '3s', '10s', '30s', '100s', '300s']
|
||||
itc = Parameter('time constant index',
|
||||
EnumType('time const. index range',
|
||||
{name: value for value, name in enumerate(TIME_CONST)}), readonly=False)
|
||||
tc = Parameter('time constant value', FloatRange(1e-1, 3e2), unit='s', readonly=False)
|
||||
tc = Parameter('time constant value', FloatRange(1e-1, 3e2, unit='s'), readonly=False)
|
||||
|
||||
EXCT_RANGE = ['0', '3uV', '10uV', '30uV', '100uV', '300uV', '1mV', '3mV', '10mV', '30mV']
|
||||
iexct = Parameter('excitation index',
|
||||
EnumType('excitation index range', {name: idx for idx, name in enumerate(EXCT_RANGE, start=-1)}),
|
||||
readonly=False)
|
||||
exct = Parameter('excitation value', FloatRange(0, 3e-2), unit='s', default=300, readonly=False)
|
||||
exct = Parameter('excitation value', FloatRange(0, 3e-2, unit='V'), default=1e-4, readonly=False)
|
||||
|
||||
vexc = Parameter("excitation voltage real()", datatype=FloatRange, unit='V', default=0, readonly=True)
|
||||
|
||||
autorange = Parameter('autorange_on', EnumType('autorange', off=0, on=1),
|
||||
readonly=False, default=1)
|
||||
|
||||
# Excitation mode (MODE command). Passive is intentionally not supported:
|
||||
# RVAL? is not a trustworthy closed-loop measurement in passive mode (see
|
||||
# write_excitation_mode below), so we simply refuse to select it.
|
||||
EXC_MODE = {'passive': 0, 'current': 1, 'voltage': 2, 'power': 3}
|
||||
excitation_mode = Parameter('excitation mode', EnumType('excitation mode', **EXC_MODE),
|
||||
value=2, readonly=False)
|
||||
|
||||
RES_RANGE_values = [string_to_value(value) for value in RES_RANGE]
|
||||
TIME_CONST_values = [string_to_value(value) for value in TIME_CONST]
|
||||
EXCT_RANGE_values = [string_to_value(value) for value in EXCT_RANGE]
|
||||
|
||||
ioClass = BridgeIO
|
||||
|
||||
# TODO sensor-break detection.
|
||||
# This was removed for now: doing it properly does not work purely from
|
||||
# current/voltage mode. As in the original Tcl driver (sim921::upd /
|
||||
# sim921::wrmode), it needs to:
|
||||
# 1. Temporarily switch excitation_mode to passive (bypassing the
|
||||
# public write_excitation_mode guard below -- e.g. send
|
||||
# `self.query('mode 0;mode?')` directly internally, since we still
|
||||
# want to keep passive off-limits as a user-selectable target).
|
||||
# 2. Read IEXC? (excitation current) and compute an approximate raw
|
||||
# resistance, e.g. rraw = exref * K1 / iexc - K2 * rref, where exref
|
||||
# is the nominal excitation voltage for the current EXCI setting and
|
||||
# rref is half the current range's full scale. See sim921::upd in
|
||||
# the Tcl driver for the reference implementation; K1=18.2, K2=22
|
||||
# there appear to be instrument-specific calibration figures we
|
||||
# were not able to derive independently from the public manual.
|
||||
# 3. Flag "no sensor" if rraw is far beyond the plausible resistance
|
||||
# for the configured range (e.g. > 10x range, at the top range),
|
||||
# with a debounce counter across several checks so a single noisy
|
||||
# sample doesn't trip it.
|
||||
# 4. Switch back to voltage/current mode afterwards.
|
||||
# This disturbs whatever measurement is in progress each time it runs,
|
||||
# so it should run on a slow cadence (e.g. every N polls, or only when
|
||||
# something already looks suspicious), not on every poll.
|
||||
|
||||
def doPoll(self):
|
||||
super().doPoll()
|
||||
if self.autorange == 1:
|
||||
@@ -177,10 +212,9 @@ class Resistance(Base, Readable):
|
||||
return int(self.query('rang?'))
|
||||
|
||||
def write_irange(self, idx):
|
||||
value = int(idx)
|
||||
self.query(f'rang {value}; rang?')
|
||||
self.query(f'rang {idx}; rang?')
|
||||
self.read_range()
|
||||
return value
|
||||
return idx
|
||||
|
||||
def read_range(self):
|
||||
"""value of the resistance range"""
|
||||
@@ -194,7 +228,7 @@ class Resistance(Base, Readable):
|
||||
return cl_value
|
||||
|
||||
def read_output_offset(self):
|
||||
"""Output offset, can be set by user. This is the value subtracted from the measured value"""
|
||||
"""Setpoint (RSET) subtracted from the measured value to form the deviation (RDEV)."""
|
||||
return self.query('rset?')
|
||||
|
||||
def write_output_offset(self, output_offset):
|
||||
@@ -202,12 +236,11 @@ class Resistance(Base, Readable):
|
||||
|
||||
def read_itc(self):
|
||||
"""index of the temperature constant value according to the range"""
|
||||
return self.query('tcon?')
|
||||
return int(self.query('tcon?'))
|
||||
|
||||
def write_itc(self, itc):
|
||||
self.read_itc()
|
||||
value = int(itc)
|
||||
return self.query(f'tcon {value}; tcon?')
|
||||
return self.query(f'tcon {itc}; tcon?')
|
||||
|
||||
def read_tc(self):
|
||||
idx = self.read_itc()
|
||||
@@ -219,20 +252,12 @@ class Resistance(Base, Readable):
|
||||
self.query(f'tcon {cl_idx};tcon?')
|
||||
return cl_value
|
||||
|
||||
def read_autorange(self):
|
||||
return self.autorange
|
||||
|
||||
def write_autorange(self, value):
|
||||
self.query(f'agai {value:d};agai?')
|
||||
return value
|
||||
|
||||
def read_iexct(self):
|
||||
"""index of the excitation value according to the range"""
|
||||
return int(self.query('exci?'))
|
||||
|
||||
def write_iexct(self, iexct):
|
||||
value = int(iexct)
|
||||
return self.query(f'exci {value};exci?')
|
||||
return self.query(f'exci {iexct};exci?')
|
||||
|
||||
def write_exct(self, target):
|
||||
target = float(target)
|
||||
@@ -248,7 +273,10 @@ class Resistance(Base, Readable):
|
||||
cl_value = value
|
||||
cl_idx = idx
|
||||
|
||||
self.write_iexct(cl_idx)
|
||||
# EXCT_RANGE_values is indexed 0..9 (list position), but the
|
||||
# instrument's EXCI codes run -1..8 (index 0 == "off" == code -1;
|
||||
# see `iexct`'s EnumType above, and read_exct below).
|
||||
self.write_iexct(cl_idx - 1)
|
||||
return cl_value
|
||||
|
||||
def read_exct(self):
|
||||
@@ -269,13 +297,33 @@ class Resistance(Base, Readable):
|
||||
|
||||
def read_vexc(self):
|
||||
reply = self.communicate('vexc?')
|
||||
value = float(reply.strip())
|
||||
return float(reply.strip())
|
||||
|
||||
def read_excitation_mode(self):
|
||||
return int(self.query('mode?'))
|
||||
|
||||
def write_excitation_mode(self, value):
|
||||
if value == 'passive':
|
||||
raise RangeError('passive excitation mode is not supported by this driver')
|
||||
self.query(f'mode {value};mode?')
|
||||
return value
|
||||
|
||||
@Command()
|
||||
def autogain(self):
|
||||
"""Trigger a one-shot autogain cycle (AGAI 1).
|
||||
|
||||
This re-optimizes the internal preamp/servo gains for the signal
|
||||
levels currently present; it takes a few seconds to complete on the
|
||||
instrument. It is unrelated to range autoranging (see the
|
||||
`autorange` parameter, which is a pure software flag consumed by
|
||||
doPoll and never touches AGAI).
|
||||
"""
|
||||
self.communicate('agai 1;agai?')
|
||||
|
||||
|
||||
class Phase(Readable):
|
||||
resistance = Attached()
|
||||
value = Parameter('phase', FloatRange, default=0, unit='deg')
|
||||
value = Parameter('phase', FloatRange(unit='deg'), default=0)
|
||||
|
||||
def read_value(self):
|
||||
return self.resistance.query('phas?')
|
||||
@@ -283,7 +331,7 @@ class Phase(Readable):
|
||||
|
||||
class Deviation(Readable):
|
||||
resistance = Attached()
|
||||
value = Parameter('resistance deviation', FloatRange(), unit='Ohm')
|
||||
value = Parameter('resistance deviation', FloatRange(unit='Ohm'))
|
||||
|
||||
def read_value(self):
|
||||
return self.resistance.query('rdev?')
|
||||
Reference in new issue
Block a user