#!/usr/bin/env python # ***************************************************************************** # 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 # ***************************************************************************** """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,.*')] default_settings = {'port': 7777, 'baudrate': 57600, 'parity': 'O', 'bytesize': 7} 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 further 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(unit='K', min=0, max=1500)) 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}') self.communicate(f'SETP {self.loop},{target};*OPC?') self._driving = True # Setting the status attribute triggers an update message for the SECoP status # parameter. This has to be done before returning from this method! self.status = BUSY, 'target changed' return target 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: # within tolerance: simple convergence criterion self._driving = False return IDLE, '' return ERROR, text