add lakeshore demo for hands-on workshop

- a simple LakeShore model 336 driver

a tutorial follows

Change-Id: I291a615efa5bd58a0dd908949210086d2f82c2ca
This commit is contained in:
2023-01-31 17:45:34 +01:00
parent 240c4f027b
commit 4ebb15ca6c
2 changed files with 126 additions and 0 deletions

18
cfg/ls336_cfg.py Normal file
View File

@ -0,0 +1,18 @@
Node('example_cryo.psi.ch', # a globally unique identification
'this is an example cryostat for the Frappy tutorial', # describes the node
interface='tcp://10767') # you might choose any port number > 1024
Mod('io', # the name of the module
'frappy_demo.lakeshore.LakeshoreIO', # the class used for communication
'communication to main controller', # a description
uri='tcp://129.129.138.78:7777', # the serial connection
)
Mod('T',
'frappy_demo.lakeshore.TemperatureLoop',
'Sample Temperature',
io='io',
channel='A', # the channel on the LakeShore for this module
loop=1, # the loop to be used
value=Param(max=470), # set the maximum expected T
target=Param(max=420), # set the maximum allowed target T
heater_range=3, # 5 for model 350
)

108
frappy_demo/lakeshore.py Normal file
View File

@ -0,0 +1,108 @@
#!/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>
# *****************************************************************************
"""LakeShore demo
demo example for tutorial
"""
from frappy.core import Readable, Parameter, FloatRange, HasIO, StringIO, Property, StringType, \
IDLE, BUSY, WARN, ERROR, Drivable, IntRange
class LakeshoreIO(StringIO):
wait_before = 0.05 # Lakeshore requires a wait time of 50 ms between commands
# Lakeshore commands (see manual)
# '*IDN?' is sent on connect, and the reply is checked to match the regexp 'LSCI,.*'
identification = [('*IDN?', 'LSCI,.*')]
class TemperatureSensor(HasIO, Readable):
"""a temperature sensor (generic for different models)"""
# internal property to configure the channel
channel = Property('the Lakeshore channel', datatype=StringType())
# 0, 1500 is the allowed range by the LakeShore controller
# this range should be restricted in the configuration (see below)
value = Parameter(datatype=FloatRange(0, 1500, unit='K'))
def read_value(self):
# the communicate method sends a command and returns the reply
reply = self.communicate(f'KRDG?{self.channel}')
return float(reply)
def read_status(self):
code = int(self.communicate(f'RDGST?{self.channel}'))
if code >= 128:
text = 'units overrange'
elif code >= 64:
text = 'units zero'
elif code >= 32:
text = 'temperature overrange'
elif code >= 16:
text = 'temperature underrange'
elif code % 2:
# ignore 'old reading', as this may happen in normal operation
text = 'invalid reading'
else:
return IDLE, ''
return ERROR, text
class TemperatureLoop(TemperatureSensor, Drivable):
# lakeshore loop number to be used for this module
loop = Property('lakeshore loop', IntRange(1, 2), default=1)
target = Parameter(datatype=FloatRange(min=0, max=1500, unit='K'))
heater_range = Property('heater power range', IntRange(0, 5)) # max. 3 on LakeShore 336
tolerance = Parameter('convergence criterion', FloatRange(0), default=0.1, readonly = False)
_driving = False
def write_target(self, target):
# reactivate heater in case it was switched off
# the command has to be changed in case of model 340 to f'RANGE {self.heater_range};RANGE?'
self.communicate(f'RANGE {self.loop},{self.heater_range};RANGE?{self.loop}')
reply = self.communicate(f'SETP {self.loop},{target};SETP? {self.loop}')
self._driving = True
self.status = BUSY, 'target changed'
return float(reply)
def read_target(self):
return float(self.communicate(f'SETP?{self.loop}'))
def read_status(self):
code = int(self.communicate(f'RDGST?{self.channel}'))
if code >= 128:
text = 'units overrange'
elif code >= 64:
text = 'units zero'
elif code >= 32:
text = 'temperature overrange'
elif code >= 16:
text = 'temperature underrange'
elif code % 2:
# ignore 'old reading', as this may happen in normal operation
text = 'invalid reading'
elif abs(self.target - self.value) > self.tolerance:
if self._driving:
return BUSY, 'approaching setpoint'
return WARN, 'temperature out of tolerance'
else:
self._driving = False
return IDLE, ''
return ERROR, text