From 6a3f6889e0a121dda0073cd36ecdf85ca29d05f7 Mon Sep 17 00:00:00 2001 From: Ferdi Franceschini Date: Fri, 2 Jul 2010 14:04:58 +1000 Subject: [PATCH] New lakeshore drivers separated out from the 3xx driver. r2974 | ffr | 2010-07-02 14:04:58 +1000 (Fri, 02 Jul 2010) | 2 lines --- .../temperature/sct_lakeshore_336.tcl | 2108 +++++++++++++++++ .../temperature/sct_lakeshore_340.tcl | 2108 +++++++++++++++++ 2 files changed, 4216 insertions(+) create mode 100644 site_ansto/instrument/config/environment/temperature/sct_lakeshore_336.tcl create mode 100644 site_ansto/instrument/config/environment/temperature/sct_lakeshore_340.tcl diff --git a/site_ansto/instrument/config/environment/temperature/sct_lakeshore_336.tcl b/site_ansto/instrument/config/environment/temperature/sct_lakeshore_336.tcl new file mode 100644 index 00000000..4f877aad --- /dev/null +++ b/site_ansto/instrument/config/environment/temperature/sct_lakeshore_336.tcl @@ -0,0 +1,2108 @@ +# Define procs in ::scobj::xxx namespace +# MakeSICSObj $obj SCT_ +# The MakeSICSObj cmd adds a /sics/$obj node. NOTE the /sics node is not browsable. + +## +# /*-------------------------------------------------------------------------- +# L A K E S H O R E 3 x x S E R I E S D R I V E R +# +# @file: This file contains the implementation of a driver for the +# Lakeshore 336 and 340 Temperature controller implemented as a scriptcontext +# object in TCL. +# +# @author: Arndt Meier, ANSTO, 2009-08-05 +# @brief: driver for Lakeshore 336 and 340 Temperature Controller (in TCL) +# @version: 20100520 for sics2_4 +# +# known bugs/limitations: lacks testing of ramping,alarm_limit compliance, +# some commands issued at initialisation are not honored, +# device and alarm reset not working correctly (nodes with no values). +# Namespace conflict: If both a model 336 and 340 are run at the same time, then +# the model 340 tries to access nodes such as outMode3 that only exist in the +# model 336 which segfaults SICServer. The problem is likely due to both models +# using the same namespace, but I have not figured out yet how to give the +# namespace a dynamic name specific to the model. +# Workaround: use 2 copies of this source code which are identical except for +# the literal name of the proc add_ls336 vs.340 and the +# namespace eval ::scobj::ls336 vs 340 +# ----------------------------------------------------------------------------*/ + +# Notes +# Hdb nodes which report data readings should have a "get" script attached to the +# "read" property of the node. This ensures that we can update the reading on demand +# which is necessary for logging data. +# +# Hdb nodes which report parameters which don't change after the driver has +# connected/reconnected to the device should be initialised with a call to the queue +# subcommand of the sct controller, Eg. +# sct queue /sics/path progress read +# +# If there are a large number of settings which need to be reported, and the device +# lets you get the read these setting with a single transaction then you can create +# a status polling object which fetches the settings and updates a given list of hdb +# nodes. + + +# Default temperature controller parameters +namespace eval ::scobj::ls336 { + # Some global variables that are useful as default initilisation values or for tracking + # Name of the scriptcontext object created from calling this driver - an instrument may use more than one + set ls336_sct_obj_name "UNKNOWN" + # What Lakeshore model are we dealing with? 336 or 340? + set ls336_LSmodel "UNKNOWN" + # Last query and write command sent to the device - for debugging and other tasks + set ls336_lastQueryCmd " " + set ls336_lastWriteCmd " " + # provide a global variable holding the path to the nodes + set ls336_path2nodes "/sample/tc1" + # terminator string for serial communication (empty for ls340, taken car of with the COMM command) + # obsolete - handled by sct object - set ls336_term "" + + # Variables that are identical to node names but are needed internally as well + # temperature difference between setpoint and actual temperature at which the + # heater changes from idle to driving + set ls336_driveTolerance1 2 + set ls336_driveTolerance2 2 + # Next 2 parameters are supported by LS model 340 only + # ctrl loop 1 settle parameter, threshold=allowable band around setpoint (0,..,100) + set ls340_settleThr 30 + # ctrl loop 1 settle parameter, settle time in seconds + set ls340_settleTime 30 + # default Heater Range (0,..,5) zero is off, hence the least dangerous + set ls336_range 0 + # upper and lower temperature limit in Kelvin + set ls336_upperlimit 500.0 + set ls336_lowerlimit 4.0 + # temperature units are Kelvin + set ls336_tempUnits "K" + # ls336 status byte + set ls336_statusByte -1 + # enable extra logging - can produce huge stout****.log file in /usr/local/sics/log/ + set ls336_verbose 0 + # a list of available sensors (not all may be connected/active) + set this_sensorlist [list A B C D] + # a list of controler loops + set this_controlerlist [list 1 2] + # set device ID to unknown + set this_sDeviceID "Unknown_Device" + # set self-test result to unknown + set this_selfTestResult -1 + # status of input channels - unknown at startup + set ls336_inputStatusA "UNKNOWN" + set ls336_inputStatusB "UNKNOWN" + set ls336_inputStatusC "UNKNOWN" + set ls336_inputStatusD "UNKNOWN" + set ls336_inpSetupA "UNKNOWN" + set ls336_inpSetupB "UNKNOWN" + set ls336_inpSetupC "UNKNOWN" + set ls336_inpSetupD "UNKNOWN" + set ls336_input4CtrlLp1 "0" + set ls336_input4CtrlLp2 "0" + set ls336_input4CtrlLp3 "0" + set ls336_input4CtrlLp4 "0" + set ls336_sampleSensor "UNKNOWN" + set timeInSecsSince2000 0 + + set alarm_Limit_LoA $ls336_lowerlimit + set alarm_Limit_LoB $ls336_lowerlimit + set alarm_Limit_LoC $ls336_lowerlimit + set alarm_Limit_LoD $ls336_lowerlimit + set alarm_Limit_HiA $ls336_upperlimit + set alarm_Limit_HiB $ls336_upperlimit + set alarm_Limit_HiC $ls336_upperlimit + set alarm_Limit_HiD $ls336_upperlimit + set checkAlarmLimitsA 1 + set checkAlarmLimitsB 1 + set checkAlarmLimitsC 1 + set checkAlarmLimitsD 1 + + set tc_dfltURL ca5-[instname] + array set moxaPortMap {1 4001 2 4002 3 4003 4 4004} + +########### Initialisation ############################# + +## +# Initialise the ls336: +# Checks the device ID, resets the device, checks the self-test, sets the communication +# protocol, sets the default alarm levels, heater range, settle threshold and tolerance. +# @param sct_controller the controller object created for this driver +# @param tc_root string variable holding the path to the object's base node in sics +# @return 0, always +proc ls336_init {sct_controller tc_root} { + if {[ catch { + # Define a constant time offset for later logging of data with timStamp + # counting from 1st Jan 2000 00:00hrs + # clock command does not work in tcl8.5 ?! + set ::scobj::ls336::timeInSecsSince2000 [clock scan 20000101T000000A] + # set ::scobj::ls336::timeInSecsSince2000 1000 + # set the communication protocol: terminator , bps 9600 baud, 7 data bits + + # 1 stop bit + odd parity bit + if { $::scobj::ls336::ls336_LSmodel == 340 } { + # puts "setting serial communication parameters" + hset $tc_root/other/cfgProtocol_comm "COMM 1,5,1" + } + # Query the device ID - are we talking to a Lakeshore 340 or 336? +# puts "sending: $sct_controller queue $tc_root/other/deviceID_idn progress read" +# $sct_controller queue $tc_root/other/deviceID_idn progress read + # !! Not working properly yet - needs fixing + #sct send "*IDN?" + #sct data [$sct_controller result] + #puts "rdValDrct(): result is $data" + #set data [hget $tc_root/other/deviceID_idn] +# puts "set deviceID_idn (hval $tc_root/other/deviceID_idn)" + #set data [hval $tc_root/other/deviceID_idn] + #set ::scobj::ls336::this_sDeviceID data +# puts "sct_lakeshore336.tcl: connected to device $::scobj::ls336::this_sDeviceID" + # reset the device to have it in a defined state +# hset $tc_root/other/reset_rst {*RST} + # Queue the Read Device Status-byte command so we can access the result in the + # corresponding node later +# puts "sending: $sct_controller queue $tc_root/other/statusByte progress read" +# $sct_controller queue $tc_root/other/statusByte progress read + if { $::scobj::ls336::ls336_LSmodel == 340 } { + hset $tc_root/other/cfgProtocol_comm "COMM 1,5,1" + } + # Was the self-test successful? + $sct_controller queue $tc_root/other/selftest progress read + set ::scobj::ls336::this_selfTestResult [hval $tc_root/other/selftest] + if {$::scobj::ls336::this_selfTestResult == 0} { + puts "sct_lakeshore336.tcl: Lakeshore $::scobj::ls336::ls336_LSmodel self-test ok." + } else { + puts "sct_lakeshore336.tcl: The Lakeshore $::scobj::ls336::ls336_LSmodel failed its self-test." + } + # Set the default upper and lower temperature alarm limits in Kelvin + foreach iSensor $::scobj::ls336::this_sensorlist { + hsetprop $tc_root/input/alarm_Limits_$iSensor units "K" + hsetprop $tc_root/input/alarm_Limits_$iSensor units "K" + } + # Set the default heater range (1,..,5) + if { $::scobj::ls336::ls336_LSmodel == 340 } { +# hset $tc_root/heater/heaterRange $::scobj::ls336::ls336_range + } else { +# hset $tc_root/heater/heaterRange_1 $::scobj::ls336::ls336_range +# hset $tc_root/heater/heaterRange_2 $::scobj::ls336::ls336_range + } + # Set the default settle parameters + if { $::scobj::ls336::ls336_LSmodel == 340 } { + hset $tc_root/control/settleThr_Loop_1 $::scobj::ls336::ls340_settleThr + hset $tc_root/control/settleTime_Loop_1 $::scobj::ls336::ls340_settleTime + hsetprop $tc_root/control/settleTime_Loop_1 units "s" + puts "Make sure INTERFACE : SERIAL : TERMINATOR is set correctly" + } + # Set the default tolerances for the setpoint temperatures + hset $tc_root/control/tolerance1 $::scobj::ls336::ls336_driveTolerance1 + hset $tc_root/control/tolerance2 $::scobj::ls336::ls336_driveTolerance2 + } message ]} { + return -code error "in ls336_init: $message" + } +} + +############# Reading polled nodes ################################### + +## +# @brief Sends a query command to the device via a read node formalism +# @param tc_root The path to the root of the node +# @param nextState The next function to call after this one (typically 'rdValue' +# to read the response from the device) +# @param cmd The query command to be send to the device (written to the +# node data value) +# @param idx indicates which control loop or which input channel +# the command belongs to +# @return nextState The next function to call after this one (typically 'rdValue') +proc getValue {tc_root nextState cmd idx} { + if {[ catch { + if [hpropexists [sct] geterror] { + hdelprop [sct] geterror + } + if { 0 == [string compare -length 7 $cmd "InpSample"] } { + # we are reading from a pseudo-node where there is no direct representation + # in the device + # puts "getValue(InpSample)" + set ::scobj::ls336::ls336_lastQueryCmd $cmd + #sct send $cmd$::scobj::ls336::ls336_term + } elseif { 0 == [string compare -length 7 $cmd "CRVHDR?"] } { + # In the case of calCurveHdr we need an extra parameter with the command sent + set nodename $tc_root/input/inpCalCurve_$idx + set whichCurve [hval $nodename] + if {$whichCurve < 1 | $whichCurve > 58} { + # Quering 'CRVHDR? 0' is an invalid curve index. The right answer is 'noCurve' + # However, cannot do the following 2 lines because then the nextState rdFunc has no value it can get + # set nodename $tc_root/input/calCurveHdr_$idx + # hset $nodename "noCurve" + # workaround: set whichCurve to 59 which would normally be empty and fix things up in rdValue + set whichCurve 59 + } + set ::scobj::ls336::ls336_lastQueryCmd "$cmd$whichCurve" + sct send $cmd$whichCurve + } else { + set ::scobj::ls336::ls336_lastQueryCmd "$cmd" + sct send $cmd + } + } message ]} { + return -code error "in getValue: $message. Last query command: $::scobj::ls336::ls336_lastQueryCmd" + } + return $nextState +} + +## +# @brief Reads the value of a read-node typically following a query command sent to the device +# rdValue is the default nextState for getValue() and setValue() +# @param idx indicates which control loop or which input channel the command belongs to +# @return idle Always returns system state idle - command sequence completed. + proc rdValue {idx} { + if {[ catch { + set data [sct result] + # puts "rdValue(): result is $data" + # broadcast rdValue "rdValue(): result is $data" + + # Check if an invalid curveHeader was queried and set the result to 'noCurve' + if { 0 == [string compare -length 10 $::scobj::ls336::ls336_lastQueryCmd "CRVHDR? 59"] } { + set data "noCurve" + if {$idx > 0 && $idx <= 4} { + switch $idx { + 1 {set idx "A"} + 2 {set idx "B"} + 3 {set idx "C"} + 4 {set idx "D"} + } + } + if {$idx == "A" || $idx == "B" || $idx == "C" || $idx == "D"} { + set nodename $::scobj::ls336::ls336_path2nodes/input/calCurveHdr_$idx + hset $nodename "noCurve" + } + } + # Continue as normal + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in rdValue: Last query command: $::scobj::ls336::ls336_lastQueryCmd" + sct geterror $data + } + default { + if {$data != [sct oldval]} { + sct oldval $data + sct update $data + sct utime readtime + + # We are done here - below are some special cases where we keep + # track of some global variables and stuff for the nodes in /sensor/ + + # Keep track of input Setup - tells us whether any channels are disabled + # Lakeshore Model 336 and 340 differ in that this information is requested via + # INTYPE? and INSET? commands, respectively, and with the LS336 not having + # the INSET? command + set mustUpdate 0 + if { 0 == [string compare -length 3 $::scobj::ls336::ls336_LSmodel "336"] && 0 == [string compare -length 7 $::scobj::ls336::ls336_lastQueryCmd "INTYPE?"] } { + set mustUpdate 1 + } + if {0 == [string compare -length 6 $::scobj::ls336::ls336_lastQueryCmd "INSET?"] || $mustUpdate == 1 } { + switch $idx { + "A" {set ::scobj::ls336::ls336_inpSetupA $data} + "B" {set ::scobj::ls336::ls336_inpSetupB $data} + "C" {set ::scobj::ls336::ls336_inpSetupC $data} + "D" {set ::scobj::ls336::ls336_inpSetupD $data} + } + } + + # Keep track of the time at which data was observed ** NXsensor allows float values only, not text + if {0 == [string compare -length 9 $::scobj::ls336::ls336_lastQueryCmd "DATETIME?"] } { + # DATETIME «MM»,«DD»,«YYYY»,«HH»,«mm»,«SS»,«sss» Configure Date and Time. + regsub -all {,} $data {:} data + # data=08:31:2009:12:58:58:910 + set separator {:} + set amonth [::scobj::ls336::getValFromString $data 0 $separator] + set aday [::scobj::ls336::getValFromString $data 1 $separator] + set ayear [::scobj::ls336::getValFromString $data 2 $separator] + set ahour [::scobj::ls336::getValFromString $data 3 $separator] + set amin [::scobj::ls336::getValFromString $data 4 $separator] + set asec [::scobj::ls336::getValFromString $data 5 $separator] + # use this code block to report time in seconds since 1/1/2000 + set aA "A" + set aT "T" + set timeString $ayear$amonth$aday$aT$ahour$amin$asec$aA + set timeInSecs [clock scan $timeString] + set timeInSecs [expr {$timeInSecs - $::scobj::ls336::timeInSecsSince2000}] + hset $::scobj::ls336::ls336_path2nodes/sensor/timStamp $timeInSecs + if {1==0} { + # if we could write a string value to NXsensor, then this date-time format would be nicer... + set sColon ":" + set sSpace " " + set isoTimeString $ayear$amonth$aday$sSpace$ahour$sColon$amin$sColon$asec + hset $::scobj::ls336::ls336_path2nodes/sensor/timStamp $isoTimeString + } + } + } + } + } + } message ]} { + return -code error "in rdValue: $message. Last query command: $::scobj::ls336::ls336_lastQueryCmd" + } + return idle + } + +## +# @brief Reads the values of the alarm_Limit_* nodes. Needs extra code compared to proc 'rdValue' +# because multiple variables are represented by only one concatenated command in the device command list. +# It is preceeded by a call to getValue() and a replacement for rdValue. +# @param idx indicates which control loop or which input channel the command belongs to +# @return idle Always returns system state idle - command sequence completed. +proc rdAlarmVal {iSensor} { + if {[ catch { + set data [sct result] + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in rdAlarmVal: Last query command: $::scobj::ls336::ls336_lastQueryCmd" + sct geterror $data + } + default { + if {$data != [sct oldval]} { + sct oldval $data + sct update $data + sct utime readtime + # ALARM? returns «off/on», «source», «high value», «low value», «latch enable», «relay» + # idx points to the position of the last coma in the string + # 1, 1, 500.00, 0.00, 0, 0 + #puts "rdAlarmVal: iSensor= $iSensor ;; data= $data " + set separator {,} + set onOff [::scobj::ls336::getValFromString $data 0 $separator] + if { $::scobj::ls336::ls336_LSmodel == 340 } { + #set source [::scobj::ls336::getValFromString $data 1 $separator] + set hiVal [::scobj::ls336::getValFromString $data 2 $separator] + set loVal [::scobj::ls336::getValFromString $data 3 $separator] + #set latch [::scobj::ls336::getValFromString $data 4 $separator] + #set relay [::scobj::ls336::getValFromString $data 5 $separator] + } else { + set hiVal [::scobj::ls336::getValFromString $data 1 $separator] + set loVal [::scobj::ls336::getValFromString $data 2 $separator] + #set deadbd [::scobj::ls336::getValFromString $data 3 $separator] + #set latch [::scobj::ls336::getValFromString $data 4 $separator] + #set audible [::scobj::ls336::getValFromString $data 5 $separator] + #set visible [::scobj::ls336::getValFromString $data 6 $separator] + } + #puts "alarm-data= $data ;; onOff= $onOff ;; source= $source ;; hiVal= $hiVal ;; loVal= $loVal ;; latch= $latch ;; relay= $relay" + + switch $iSensor { + "A" { + set ::scobj::ls336::checkAlarmLimitsA $onOff + if {$loVal >= 0} {set ::scobj::ls336::alarm_Limit_LoA $loVal} + if {$hiVal >= 0} {set ::scobj::ls336::alarm_Limit_HiA $hiVal} + } + "B" { + set ::scobj::ls336::checkAlarmLimitsB $onOff + if {$loVal >= 0} {set ::scobj::ls336::alarm_Limit_LoB $loVal} + if {$hiVal >= 0} {set ::scobj::ls336::alarm_Limit_HiB $hiVal} + } + "C" { + set ::scobj::ls336::checkAlarmLimitsC $onOff + if {$loVal >= 0} {set ::scobj::ls336::alarm_Limit_LoC $loVal} + if {$hiVal >= 0} {set ::scobj::ls336::alarm_Limit_HiC $hiVal} + } + "D" { + set ::scobj::ls336::checkAlarmLimitsD $onOff + if {$loVal >= 0} {set ::scobj::ls336::alarm_Limit_LoD $loVal} + if {$hiVal >= 0} {set ::scobj::ls336::alarm_Limit_HiD $hiVal} + } + } + } + } + } + } message ]} { + return -code error "in rdAlarmVal: $message. Last query command: $::scobj::ls336::ls336_lastQueryCmd" + } + return idle +} + + +## +# @brief rdCfgValue() does what rdValue() does but it replaces /sensor/ctrl_Loop_1 value +# with a more human readable version of the information from /control/config_Loop_1. This +# function is called for the CSET* and OUTMODE* commands. +# @param idx indicates which control channel the command acts upon +# @return idle Always returns system state idle - command sequence completed. + proc rdCfgValue {idx} { + if {[ catch { + set data [sct result] + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in rdCfgValue: Last query command: $::scobj::ls336::ls336_lastQueryCmd" + sct geterror $data + } + default { + set oval [sct oldval] + if { $data != [sct oldval] } { + # puts "rdCfgValue: idx:$idx Last query command: $::scobj::ls336::ls336_lastQueryCmd" + sct oldval $data + sct update $data + sct utime readtime + # update the /sensor/ctrl_Loop_1 and /sensor/ctrl_Loop_2 manual nodes which show + # DISABLED if the control channel is disabled + set ctrl_Loop_Txt "enabled, Input " + set input "A" + set separator {,} + if { $::scobj::ls336::ls336_LSmodel == 340 } { + # LS340, device command CSET* + set input [::scobj::ls336::getValFromString $data 0 $separator] + set units [::scobj::ls336::getValFromString $data 1 $separator] + set onOff [::scobj::ls336::getValFromString $data 2 $separator] + #set powerup [::scobj::ls336::getValFromString $data 3 $separator] + if {$onOff == 0} { + set ctrl_Loop_Txt "DISABLED, Input " + } + set in ", in " + switch -glob -- $units { + "1*" {set units "Kelvin"} + "2*" {set units "Celsius"} + "3*" {set units "sensor units"} + default {set units "UNKNOWN units"} + } + set ctrl_Loop_Txt $ctrl_Loop_Txt$input$in$units + # puts "ls340 rdCfgValue() idx: $idx, data: $data, units: $units, ctrl_Loop_Txt: $ctrl_Loop_Txt" + } else { + # LS 336, device command outMode_* + set mode [::scobj::ls336::getValFromString $data 0 $separator] + set input [::scobj::ls336::getValFromString $data 1 $separator] + # set powerup [::scobj::ls336::getValFromString $data 2 $separator] + if {$mode == 0} { + set ctrl_Loop_Txt " DISABLED, Input " + } + set myInp $input + switch -glob -- $myInp { + "0*" {set input "None"} + "1*" {set input "A"} + "2*" {set input "B"} + "3*" {set input "C"} + "4*" {set input "D"} + default {set input "UNKNOWN"} + } + append ctrl_Loop_Txt $input + set myMode $mode + switch -glob -- $myMode { + "0*" {set mode ", control off"} + "1*" {set mode ", PID closed loop"} + "2*" {set mode ", zone control"} + "3*" {set mode ", open loop"} + "4*" {set mode ", monitor out"} + "5*" {set mode ", warmup supply"} + default {set mode ", UNKNOWN control"} + } + append ctrl_Loop_Txt $mode + # puts "rdCfgValue() idx: $idx, data: $data, output: $idx, ctrl_Loop_Txt: $ctrl_Loop_Txt" + # puts "rdCfgValue setting ls336_input4CtrlLp idx:$idx, input:$input, myInp:$myInp" + } + set nodename $::scobj::ls336::ls336_path2nodes/sensor/ctrl_Loop_$idx + hset $nodename $ctrl_Loop_Txt + # Keep track of which inputs are used for the control loops + # puts "rdCfgValue() idx: $idx, data: $data, input:$input, ctrl_Loop_Txt: $ctrl_Loop_Txt" + switch $idx { + "1" {set ::scobj::ls336::ls336_input4CtrlLp1 $input} + "2" {set ::scobj::ls336::ls336_input4CtrlLp2 $input} + "3" {set ::scobj::ls336::ls336_input4CtrlLp3 $input} + "4" {set ::scobj::ls336::ls336_input4CtrlLp4 $input} + } + # puts "rdCfgValue ls336_input4CtrlLp:$::scobj::ls336::ls336_input4CtrlLp2 idx:$idx, input:$input, myInp:$myInp" + } + } + } + } message ]} { + return -code error "in rdCfgValue: $message. Last query command: $::scobj::ls336::ls336_lastQueryCmd" + } + return idle + } + + + +## +# @brief Reads the values of the RDGST* nodes. Needs extra code compared to proc 'rdValue' +# because it interprets the bit coded results and updates affected node values accordingly +# It is preceeded by a call to getValue() and is a replacement for rdValue(). +# @param iSensor indicates which input channel the command belongs to +# @return idle Always returns system state idle - command sequence completed. +proc rdBitValue {iSensor} { + if {[ catch { + set data [sct result] + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in rdBitValue: Last query command: $::scobj::ls336::ls336_lastQueryCmd" + sct geterror $data + } + default { + if {$data != [sct oldval]} { + sct oldval $data + sct update $data + sct utime readtime + # RDGST? A/B/C/D Read input status returns an integer with the following meaning + # Bit Weighting StatusIndicator + # 0 1 invalid reading + # 1 2 old reading (not an error, ignored in ls336) + # 4 16 temp underrange + # 5 32 temp overrange + # 6 64 sensor units zero + # 7 128 sensor units overrange + set sValue "" + # Remove any leading zeros from string 'data' which ought to represent an integer, + # ("001" => "1", "096" => "96") else the string may be misinterpreted as an octal number. + if {0 == [string compare -length 1 $data "0"] } { + set data [string range $data 1 5] + } + if {0 == [string compare -length 1 $data "0"] } { + set data [string range $data 1 5] + } + set i $data + # puts "rdBitValue(): iSensor:$iSensor, data:$data" + set bitValue [expr {$i & 1}] + if {$bitValue == 1} {set sValue "Invalid reading, "} + #set i [expr $i >> 1] + # set bitValue [expr $i & 1] + # if {$bitValue == 1} {set sValue "old reading"} + set i [expr {$i >> 4}] + set bitValue [expr {$i & 1}] + if {$bitValue == 1} { set sValue [append sValue "temp underrange, "] } + set i [expr {$i >> 1}] + set bitValue [expr {$i & 1}] + if {$bitValue == 1} { set sValue [append sValue "temp overrange, "] } + set i [expr {$i >> 1}] + set bitValue [expr {$i & 1}] + if {$bitValue == 1} { set sValue [append sValue "sensor units zero, "] } + set i [expr {$i >> 1}] + set bitValue [expr {$i & 1}] + if {$bitValue == 1} { set sValue [append sValue "sensor units overrange, "] } + if { [string length $sValue] < 4 } { + set sValue "ok" + } + #puts "rdBitValue(): iSensor:$iSensor, data:$data, sValue:$sValue " + switch $iSensor { + "A" {set ::scobj::ls336::ls336_inputStatusA $sValue} + "B" {set ::scobj::ls336::ls336_inputStatusB $sValue} + "C" {set ::scobj::ls336::ls336_inputStatusC $sValue} + "D" {set ::scobj::ls336::ls336_inputStatusD $sValue} + } + } + } + } + } message ]} { + return -code error "in rdBitValue: $message. Last query command: $::scobj::ls336::ls336_lastQueryCmd" + } + return idle +} + + +## +# @brief rdInpValue() does what rdValue() does but it replaces the input's Kelvin Reading +# with the message from RDGST? if the input is invalid. +# @param idx indicates which input channel the command acts upon +# @return idle Always returns system state idle - command sequence completed. + proc rdInpValue {idx} { + if {[ catch { + set data [sct result] + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in rdInpValue: Last query command: $::scobj::ls336::ls336_lastQueryCmd" + sct geterror $data + } + default { + if {$idx == "A"} { + # update only once per poll cycle needed + # Read the sics node sampleSensor to + # determine which input represents the sample temperature + set nodename $::scobj::ls336::ls336_path2nodes/sensor/sampleSensor + set ::scobj::ls336::ls336_sampleSensor [hval $nodename] + } + if { $data != [sct oldval] } { + sct oldval $data + sct update $data + sct utime readtime + # update the /sensor/ctrlLp1_value, /sensor/ctrlLp2_value, and /sensor/Tsample manual nodes which show + # zero or DISABLED if the input channel is disabled + + set inputStatus $::scobj::ls336::ls336_inputStatusA + set inpEnabled $::scobj::ls336::ls336_inpSetupA + switch $idx { + "B" {set inpEnabled $::scobj::ls336::ls336_inpSetupB} + "C" {set inpEnabled $::scobj::ls336::ls336_inpSetupC} + "D" {set inpEnabled $::scobj::ls336::ls336_inpSetupD} + } + if {0 == [string compare -length 1 $inpEnabled "0"] } { + # puts "Inp$idx is DISABLED" + set inputStatus "DISABLED" + } else { + # RDGST? if RDGST is NOT zero -- or else the Kelvin reading for that input. + switch $idx { + "A" {set inputStatus $::scobj::ls336::ls336_inputStatusA} + "B" {set inputStatus $::scobj::ls336::ls336_inputStatusB} + "C" {set inputStatus $::scobj::ls336::ls336_inputStatusC} + "D" {set inputStatus $::scobj::ls336::ls336_inputStatusD} + } + } + # RDGST? is 'ok' or 'UNKNOWN' -> it is okay to show the Kelvin reading we have + set value $data + if { [string length $inputStatus] >= 8 } { + # The status of the input channel meets an error condition - show the error message instead + # puts "rdInpValue() idx: $idx, data: $data, invalid: $inputStatus" + set value -1.0 + } + if {$idx == $::scobj::ls336::ls336_sampleSensor} { + hset $::scobj::ls336::ls336_path2nodes/sensor/Tsample $value + } + # Switch command did not work reliably below, hence a less elegant but + # functional if-elseif-else contruct is implemented instead + if {$idx == $::scobj::ls336::ls336_input4CtrlLp1} { + hset $::scobj::ls336::ls336_path2nodes/sensor/ctrlLp1_value $value + } elseif {$idx == $::scobj::ls336::ls336_input4CtrlLp2} { + hset $::scobj::ls336::ls336_path2nodes/sensor/ctrlLp2_value $value + } elseif {$idx == $::scobj::ls336::ls336_input4CtrlLp3} { + hset $::scobj::ls336::ls336_path2nodes/sensor/ctrlLp3_value $value + } elseif {$idx == $::scobj::ls336::ls336_input4CtrlLp4} { + hset $::scobj::ls336::ls336_path2nodes/sensor/ctrlLp4_value $value + } + } + } + } + } message ]} { + return -code error "in rdInpValue: $message. Last query command: $::scobj::ls336::ls336_lastQueryCmd" + } + return idle + } + + + +## +# @brief Does what rdValue() does plus it checks if the temperature is in tolerance. +# inTolerance is the default nextState after getValue() for read node other/deviceID_idn. +# If the LS336 is switched off, temperature is reported to be in tolerance so that +# slow return to ambient temperature can be carried out (see comments in Julabo driver +# for reasoning). +# @param CtrlLoopIdx indicates which control loop the command acts upon +# @return idle Always returns system state idle - command sequence completed. +proc inTolerance {CtrlLoopIdx} { + if {[ catch { + set tc_root $::scobj::ls336::ls336_path2nodes + set data [sct result] + set oldvalue [sct oldval] + # puts "inTolerance(): data=$data oldvalue=$oldvalue idx:$CtrlLoopIdx" + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in inTolerance: Last query command: $::scobj::ls336::ls336_lastQueryCmd" + sct geterror $data + } + default { + if {$data != $oldvalue} { + # timecheck is an internal node that is set to the current time at the + # start of measurement and it is reset to current time every time the tolerance + # test for a control loop decides that it is outside tolerance. With the + # ls366 we need 2 such variables, one for each control loop. + if {$oldvalue == "UNKNOWN"} { + sct utime timecheck + sct utime timecheck2 + sct utime currtime + } + sct oldval $data + sct update $data + sct utime readtime + } + } + } + sct utime currtime + # puts "inTolerance $::scobj::ls336::ls336_sct_obj_name CtrlLoopIdx:$CtrlLoopIdx data:$data" + # now update the manual nodes reporting whether the actual temperature + # is within tolerance of the corresponding setpoint + for {set CtrlLoopIdx 1} {$CtrlLoopIdx < 3} {incr CtrlLoopIdx 1} { + set cmpIdString [string compare -length 13 $data "LSCI,MODEL340"] + if {$cmpIdString != 0 } { + set cmpIdString [string compare -length 13 $data "LSCI,MODEL336"] + } + if {$cmpIdString == 0 } { + # set nodename $tc_root/control/config_Loop_$CtrlLoopIdx + # set iSensor [hval $nodename] + # set iSensor [string range $iSensor 0 0] + set iSensor [getCorrespondingInputSensor $CtrlLoopIdx] + # puts "inTolerance 2 $::scobj::ls336::ls336_sct_obj_name CtrlLoopIdx:$CtrlLoopIdx data:$data, iSensor:$iSensor" + #puts {set intol [checktol $tc_root [sct readtime] [sct timecheck] $CtrlLoopIdx $iSensor]} + if {[string length $iSensor] == 1} { + if {$CtrlLoopIdx == 1} { + set intol [checktol $tc_root [sct currtime] [sct timecheck] $CtrlLoopIdx $iSensor] + } else { + set intol [checktol $tc_root [sct currtime] [sct timecheck2] $CtrlLoopIdx $iSensor] + } + set nodename $tc_root/sensor/setpoint$CtrlLoopIdx + set setpt [hval $nodename] + set nodename $tc_root/sensor/sensorValue$iSensor + set temp [hval $nodename] + # puts "inTolerance(): comparing sensor/setpoint$CtrlLoopIdx=$setpt with actual sensorValue$iSensor=$temp" + set diff [expr {abs($setpt - $temp)}] + # $::scobj::ls336::ls336_driveTolerance = 0.2 Kelvin + set tol $::scobj::ls336::ls336_driveTolerance1 + if {$CtrlLoopIdx == 2} { + set tol $::scobj::ls336::ls336_driveTolerance2 + } + # puts "inTolerance(): diff=$diff tol=$tol" + # if $diff > $tol + if {$intol==0} { + set nodename $tc_root/emon/monMode_Lp$CtrlLoopIdx + hset $nodename "drive" + if {$CtrlLoopIdx == 1} { + hset $tc_root/status "busy" + if {$::scobj::ls336::ls336_verbose==1} {puts "hset $nodename drive; hset $tc_root/status busy"} + } else { + hset $tc_root/status_Ctrl_Lp2 "busy" + if {$::scobj::ls336::ls336_verbose==1} {puts "hset $nodename drive; hset $tc_root/status_Ctrl_Lp2 busy"} + } + } else { + set nodename $tc_root/sensor/setpoint$CtrlLoopIdx + hsetprop $nodename driving 0 + set nodename $tc_root/emon/monMode_Lp$CtrlLoopIdx + hset $nodename "monitor" + if {$CtrlLoopIdx == 1} { + hset $tc_root/status "idle" + if {$::scobj::ls336::ls336_verbose==1} {puts "hset $nodename idle; hset $tc_root/status monitor"} + } else { + hset $tc_root/status_Ctrl_Lp2 "idle" + if {$::scobj::ls336::ls336_verbose==1} {puts "hset $nodename idle; hset $tc_root/status_Ctrl_Lp2 monitor"} + } + } + } + } else { + # puts "inTolerance(): uuppss - should not go here" + hset $tc_root/emon/monMode_Lp1 "idle" + hset $tc_root/emon/monMode_Lp2 "idle" + hset $tc_root/emon/isInTolerance_Lp1 "inTolerance" + hset $tc_root/emon/isInTolerance_Lp2 "inTolerance" + hset $tc_root/status "idle" + hset $tc_root/status_Ctrl_Lp2 "idle" + } + } + # puts "inTolerance 4 $::scobj::ls336::ls336_sct_obj_name CtrlLoopIdx:$CtrlLoopIdx data:$data" + } message ]} { + return -code error "in inTolerance: $message. Last query command: $::scobj::ls336::ls336_lastQueryCmd" + } + # puts "Leaving inTolerance idx:$CtrlLoopIdx" + return idle +} + + +## +# @brief rdCalValue()) does what rdValue() does plus it resets the value /input/calCurveHdr_$idx forcing that node to refresh +# @param idx indicates which control loop or which input channel the command belongs to +# @return idle Always returns system state idle - command sequence completed. +proc rdCrvValue {idx} { + if {[ catch { + set data [sct result] + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in rdCrvValue: Last query command: $::scobj::ls336::ls336_lastQueryCmd" + sct geterror $data + } + default { + if {$data != [sct oldval]} { + sct oldval $data + sct update $data + sct utime readtime + # The calibration curve has changed. + # Set the curve header name to unknown, thus forcing the respective node to refresh + set tc_root $::scobj::ls336::ls336_path2nodes + set nodename $tc_root/input/calCurveHdr_$idx + hset $nodename "request refresh" + #puts "hset $nodename request refresh" + } + } + } + } message ]} { + return -code error "in rdCrvValue: $message. Last query command: $::scobj::ls336::ls336_lastQueryCmd" + } + return idle +} + + +################## Writing to nodes ########################################### + +## +# @brief Writes a new value to a node and sends the corresponding command to the device. +# @param tc_root string variable holding the path to the object's base node in sics +# @param nextState the next function to call after this one +# @param cmd the command to be send to the device (written to the node data value) +# @param idx indicates which control loop or which input channel the command belongs to +# @return nextState Is typically noResponse as the device does not acknowledge the write request +proc setValue {tc_root nextState cmd {idx ""}} { + # tc_root and idx are not being used - however, don't remove so we can use the + # same calling mask as for setPoint() or other $wrFunc + if {[ catch { + # Some commands have no argument and no matching read command, like reset_rst or alarmResetAll. + # Provide a default value for parameter 'idx' and initialize 'par' to an empty string because [sct target] + # is not an allowed operation in this case. + set par "" + if {$idx != ""} { + set par [sct target] + } + set ::scobj::ls336::ls336_lastWriteCmd "$cmd$par" + # Talking to a pseudo-node (no equivalent command in the device communication) + sct send "$cmd$par" + } message ]} { + return -code error "in setValue: $message. While sending command: $::scobj::ls336::ls336_lastWriteCmd" + } + return $nextState +} + + +## +# @brief Writes a new value to a node but does NOT send the corresponding command to the device. +# @param tc_root string variable holding the path to the object's base node in sics +# @param nextState the next function to call after this one +# @param cmd the command to be send to the device (written to the node data value) +# @param idx indicates which control loop or which input channel the command belongs to +# @return nextState Is typically noResponse as the device does not acknowledge the write request +proc setPseudoValue {tc_root nextState cmd {idx ""}} { + # tc_root and idx are not being used - however, don't remove so we can use the + # same calling mask as for setPoint() or other $wrFunc + if {[ catch { + # Some commands have no argument and no matching read command, like reset_rst or alarmResetAll. + # Provide a default value for parameter 'idx' and initialize 'par' to an empty string because [sct target] + # is not an allowed operation in this case. + set par "" + if {$idx != ""} { + set par [sct target] + } + set ::scobj::ls336::ls336_lastWriteCmd "$cmd$par" + # Talking to a pseudo-node (no equivalent command in the device communication) + # puts "setPseudoValue($tc_root $nextState $cmd $idx)" + #sct send "$cmd$par" + } message ]} { + return -code error "in setPseudoValue: $message. While processing command: $::scobj::ls336::ls336_lastWriteCmd" + } + return $nextState +} + + +## +# @brief Is an empty function just so we can define a next state function after a write operation. +# @return idle Returns the default system state indicating that the device is ready for the next command +proc noResponse {} { + return idle +} + + +## +# @brief Sets the desired temperature for the selected control loop: +# @param tc_root string variable holding the path to the object's base node in sics +# @param nextState next state of the device, typically that is 'noResponse' +# @param cmd string variable containing the device command to change the setpoint +# @param whichCtrlLoop specifies whether the setpoint is for control loop 1 or 2 +# @return nextState +proc setPoint {tc_root nextState cmd whichCtrlLoop} { + if {[ catch { + #puts "executing setPoint ($tc_root $nextState $cmd $whichCtrlLoop)" + #broadcast setPoint "executing setPoint ($tc_root $nextState $cmd $whichCtrlLoop)" + set ns ::scobj::lh45 + set par [sct target] + + # determine the corresponding input sensor + # set whichSensor [getCorrespondingInputSensor $whichCtrlLoop] + #hset $tc_root/status "busy" + set wrStatus [sct writestatus] + if {$wrStatus == "start"} { + # Called by drive adapter + # puts "setPoint(): driving set to 1" + set nodename $tc_root/sensor/setpoint$whichCtrlLoop + hsetprop $nodename driving 1 + } + #puts "setPoint(wrStatus=$wrStatus): sct send $cmd$par" + sct send "$cmd$par" + } message ]} { + return -code error "in setPoint: $message. Last write command: $::scobj::ls336::ls336_lastWriteCmd" + } + return $nextState +} + + +## +# @brief Writes a command directly to the device - device reset and alarm reset commands. +# @param tc_root string variable holding the path to the object's base node in sics +# @param nextState the next function to call after this one +# @param cmd the command to be send to the device (written to the node data value) +# @param idx indicates which control loop or which input channel the command belongs to +# @return nextState Is typically noResponse as the device does not acknowledge the write request +# NOT WORKING CORRECTLY YET +proc sendCmd {tc_root nextState cmd {idx ""}} { + # tc_root and idx are not being used - however, don't remove so we can use the + # same calling mask as for setPoint() or other $wrFunc + if {[ catch { + # Some commands have no argument and no matching read command, like reset_rst or alarmResetAll. + # Provide a default value for parameter 'idx' and initialize 'par' to an empty string because [sct target] + # is not an allowed operation in this case. + set par "" + if {$idx != ""} { + set par [sct target] + } + set ::scobj::ls336::ls336_lastWriteCmd "$cmd$par" + #sct send "$cmd$par" + if { 0 == [string compare -length 4 $cmd "*RST"]} { + sct_controller queue $tc_root/other/selftest progress read + } elseif { 0 == [string compare -length 6 $cmd "ALMRST"]} { + sct_controller queue $tc_root/input/alarmResetAll progress read + } + } message ]} { + return -code error "in setValue: $message. While sending command: $::scobj::ls336::ls336_lastWriteCmd" + } + return $nextState +} + + + +############# functions used with drivable ########################################## + +## +# @brief Implement the checkstatus command for the drivable interface +# +# NOTE: The drive adapter initially sets the writestatus to "start" and will +# only call this when writestatus!="start" + proc drivestatus {tc_root} { + if {[ catch { + if {[sct driving]} { + set retval busy + } else { + set retval idle + } + } message ]} { + return -code error "in drivestatus: $message. Last write command: $::scobj::ls336::ls336_lastWriteCmd" + } + return $retval + } + +## +# @brief Stops driving at current temperature. +# Sets the setpoint to the current temperature of the corresponding input +# @param tc_root string variable holding the path to the object's base node in sics +# @param whichCtrlLoop specifies whether the setpoint is for control loop 1 or 2 +# @return idle Indicates that the device is ready for the next command + proc halt {tc_root whichCtrlLoop} { + # stop driving at current temperature + # determine the corresponding input sensor +# ffr 2009-11-12 TODO what happens if whichSensor is none or UNKOWN, or if whichCtrlLoop > 2, we stay busy forever. + set whichSensor [getCorrespondingInputSensor $whichCtrlLoop] + if {[string length $whichSensor] == 1} { + set sensorValue $tc_root/sensor/sensorValue$whichSensor + if {$whichCtrlLoop <= 2} { + set nodename $tc_root/sensor/setpoint$whichCtrlLoop + hset $nodename [hval $sensorValue] + hsetprop $nodename driving 0 + } + } + return idle + } + +############# Auxiliary functions ########################################## + +## +# @brief Extracts elements from a coma (or other symbol) separated list of values provided in a string +# @param s The string holding a list of values separated by comas +# @param element Index of the element to extract (starts from zero) +# @param separator String holding the separator used in $s, e.g. a coma +# @return returnval String holding the extracted element. String is empty if operation failed. + proc getValFromString {s element separator} { + #puts "getValFromString $s $element $separator" + set startIdx 0 + set endIdx 0 + set eIdx $element + set idx 0 + while {$eIdx > 0} { + set idx [string first $separator $s $startIdx] + if {$idx > 0} { + if { " " == [string range $s $idx $idx]} { + incr idx 1 + } + set startIdx $idx + if {$startIdx > 0} { + incr startIdx 1 + } + } + incr eIdx -1 + } + # startIdx points to the first non-blank character of the value we are interested in + set returnval "" + if {$startIdx < 0} { + return $returnval + } + set endIdx [string first $separator $s $startIdx] + incr endIdx -1 + #puts "startIdx=$startIdx endIdx=$endIdx" + # endIdx points to one character before the next separator or is -1 if it is the + # last element in the string $s + if {$endIdx >= 0} { + set returnval [string range $s $startIdx $endIdx] + } else { + set returnval [string range $s $startIdx 555] + } + #puts "getValFromString $s, $element, $separator,\n returns: $returnval" + return $returnval + } + +## +# @brief determines which input sensor corresponds to this control loop or output +# @param CtrlLoopIdx the control loop (valid values 1,2) or output (1,2,3,4) +# @return iSensor returns A,B,C or D or None or Unknown +proc getCorrespondingInputSensor {CtrlLoopIdx} { + if { $::scobj::ls336::ls336_LSmodel == 340 } { + if {$CtrlLoopIdx < 1 || $CtrlLoopIdx > 2} { + set iSensor "UNKOWN" + return $iSensor + } + set nodename $::scobj::ls336::ls336_path2nodes/control/config_Loop_$CtrlLoopIdx + set iSensor [hval $nodename] + set iSensor [string range $iSensor 0 0] + } else { + # LS 336, device command outMode_* + if {$CtrlLoopIdx < 1 || $CtrlLoopIdx > 4} { + set iSensor "UNKOWN" + return $iSensor + } + set nodename $::scobj::ls336::ls336_path2nodes/control/outMode_$CtrlLoopIdx + set data [hval $nodename] + set input [::scobj::ls336::getValFromString $data 1 ","] + switch -glob -- $input { + "0*" {set iSensor "None"} + "1*" {set iSensor "A"} + "2*" {set iSensor "B"} + "3*" {set iSensor "C"} + "4*" {set iSensor "D"} + default {set iSensor "UNKNOWN"} + } + } + return $iSensor +} + + + +## +# @brief Checktol() checks whether the current temperature is within tolerance of the Setpoint. +# @param tc_root string variable holding the path to the object's base node in sics +# @param currtime current time +# @param timecheck time since last check(?) +# @param iLoop index of the control loop we are acting upon (is 1 or 2) +# @param iSensor indicates the corresponding input sensor (A,B,C or D) +# @return retVal returns 1 if in tolerance, 0 else. +proc checktol {tc_root currtime timecheck iLoop iSensor} { + if {[ catch { + set retVal 0 + set sensorValue $tc_root/sensor/sensorValue$iSensor + set temp [hval $sensorValue] + set isetp $tc_root/sensor/setpoint$iLoop + set setpt [hval $isetp] + set tol [hval $tc_root/control/tolerance1] + if {$iLoop == 2 } { + set tol [hval $tc_root/control/tolerance2] + } + set lotemp [expr {$setpt - $tol}] + set hitemp [expr {$setpt + $tol}] + if {$::scobj::ls336::ls336_verbose==1} { + puts "checktol(): setpt $isetp=$setpt lotemp=$lotemp, current $sensorValue=$temp, hitemp=$hitemp, tol=$tol, iLoop=$iLoop, timecheck=$timecheck, currtime=$currtime" + } + if { $temp < $lotemp || $temp > $hitemp} { + hset $tc_root/emon/isInTolerance_Lp$iLoop "outsideTolerance" + if {$::scobj::ls336::ls336_verbose==1} {puts "hset $tc_root/emon/isInTolerance_Lp$iLoop outsideTolerance"} + if {$iLoop==1} { sct utime timecheck } + if {$iLoop==2} { sct utime timecheck2 } + set retVal 0 + } else { + set timeout $::scobj::ls336::ls340_settleTime + if { $::scobj::ls336::ls336_LSmodel == 340 } { + set timeout [hval $tc_root/control/settleTime_Loop_1] + } + set elapsedTime [expr {$currtime - $timecheck}] + #puts "if (elapsedTime=$elapsedTime > timeout=$timeout) we are inTolerance" + if {$elapsedTime > $timeout} { + hset $tc_root/emon/isInTolerance_Lp$iLoop "inTolerance" + if {$::scobj::ls336::ls336_verbose==1} { + puts "hset $tc_root/emon/isInTolerance_Lp$iLoop inTolerance (elapsedTime=$elapsedTime greater than settleTime=$timeout)" + } + set retVal 1 + } else { + # Temperature has not been within tolerance for enough time - (overshoots, oscillations,..) + hset $tc_root/emon/isInTolerance_Lp$iLoop "outsideTolerance" + if {$::scobj::ls336::ls336_verbose==1} { + puts "hset $tc_root/emon/isInTolerance_Lp$iLoop outsideTolerance (elapsedTime=$elapsedTime less than settleTime=$timeout)" + } + set retVal 0 + } + } + } message ]} { + return -code error "in checktol: $message. Last query command: $::scobj::ls336::ls336_lastQueryCmd" + } + return $retVal +} + + +## +# @brief check() checks whether the the newly chosen Setpoint is inside the alarm levels +# @param tc_root string variable holding the path to the object's base node in sics +# @param whichCtrlLoop index of the control loop we are operating on (is 1 or 2) +# @return 'OK' if the new setpoint is within the alarm levels; else an error is reported +proc check {tc_root whichCtrlLoop} { + if {[ catch { + set setpoint [sct target] + #puts "check(): setpoint= $setpoint" + # determine the corresponding input sensor + set whichSensor [getCorrespondingInputSensor $whichCtrlLoop] + # puts "check(): whichCtrlLoop=$whichCtrlLoop whichSensor= $whichSensor" + set lolimit $::scobj::ls336::alarm_Limit_LoA + set hilimit $::scobj::ls336::alarm_Limit_HiA + set bCheckLimits $::scobj::ls336::checkAlarmLimitsA + switch $whichSensor { + "A" { + set lolimit $::scobj::ls336::alarm_Limit_LoA + set hilimit $::scobj::ls336::alarm_Limit_HiA + set bCheckLimits $::scobj::ls336::checkAlarmLimitsA + } + "B" { + set lolimit $::scobj::ls336::alarm_Limit_LoB + set hilimit $::scobj::ls336::alarm_Limit_HiB + set bCheckLimits $::scobj::ls336::checkAlarmLimitsB + } + "C" { + set lolimit $::scobj::ls336::alarm_Limit_LoC + set hilimit $::scobj::ls336::alarm_Limit_HiC + set bCheckLimits $::scobj::ls336::checkAlarmLimitsC + } + "D" { + set lolimit $::scobj::ls336::alarm_Limit_LoD + set hilimit $::scobj::ls336::alarm_Limit_HiD + set bCheckLimits $::scobj::ls336::checkAlarmLimitsD + } + default { + error "sct_ls336.tcl check(): Can't set setpoint. No valid input sensor specified for this output control loop." + } + } + # puts "check(): doCheck:$bCheckLimits lolimit=$lolimit setpoint=$setpoint hilimit=$hilimit" + if {$bCheckLimits == 1} { + if {$setpoint < $lolimit || $setpoint > $hilimit} { + error "sct_ls336.tcl: setpoint $tc_root/sensor/sensorValue$whichSensor violates set alarm limits" + } + } + } message ]} { + return -code error "in check(): $message. Last write command: $::scobj::ls336::ls336_lastWriteCmd" + } + return OK +} + + +## +# Provides online help for the driver commands available and the meaning of their +# options and parameters. Note that the help text is limited to +# - 500 bytes in total length. +# - 128 characters per line (if not, may get truncated on the display) +# - The following 5 special characters must be escape-coded for xml-compliance: +# Character Name Entity Reference Numeric Reference +# & Ampersand & &#38; +# < Left angle bracket < &#60; +# > Right angle bracket > > +# " Straight quotation mark " ' +# ' Apostrophe ' " +# Note that the xml file will be parsed more than once, meaning that escaping +# special characters may be insufficient and it may be necessary to avoid all +# xml special characters to avoid problems further downstream. It appears also +# that at present gumtree is truncating the help text when it comes across an +# equal sign '=' within the string. Multiple lines are not a problem. +# A later version of Gumtree may show this text as a tooltip - meanwhile it is +# available in the Properties tab of each node. The help text provided is taken +# from the user manual for the Lakeshore 336 and 340 but is shortened in some +# cases due to the restriction to max 500 characters. +proc helpNotes4user {scobj_hpath cmdGroup varName} { + if {[ catch { + set nodeName "$scobj_hpath/$cmdGroup/$varName" + # We presume that SICServer is running on Linux but Gumtree on Windows. + # Hence multi-line help texts should use CRLF instead of only LF + # Note that gumxml.tcl processes the node properties including this help text + # and may do strange things to it... + #set CR 0x0D + #set LF 0x0A + set CR "\r" + set LF "\n" + set CRLF $CR$LF + if {1 > [string length $cmdGroup]} { + set nodeName "$scobj_hpath/$varName" + } + set helptext "No help available" + #puts "helpNotes4user $scobj_hpath/$cmdGroup varName" + switch -glob $varName { + "sensorValue*" { + set h1 {KRDG? «input» Query Kelvin Reading for an Input} + set h2 {Returned: «kelvin value«. Format: nnn.nnn.} + set h3 {Remarks: Returns the kelvin reading for an input. «input» specifies which input to query.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "ctrlLp*" { + set h1 {KRDG? «input» Query Kelvin Reading for the sensor corresponding to this control loop} + set h2 {Returned: «kelvin value«. Format: nnn.nnn.} + set h3 {Remarks: Returns the kelvin reading for an input. «input» specifies which input to query.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "Tsample*" { + set helptext {Sample temperature: returns the kelvin reading for the sample input as defined by sampleSensor.} + } + "sampleSensor*" { + set helptext {Specifies the input channel that reads the temperature at the sample location.} + } + "setpoint*" { + set h1 {SETP «loop», «value» Configure Control Loop Setpoint.} + if { $::scobj::ls336::ls336_LSmodel == 336 } { + set h1 {SETP «output», «value» Set the Setpoint for the selected output's control loop.} + } + set h2 {«value» The value for the setpoint (in whatever units the setpoint is using).} + set h3 {Example: SETP 1, 122.5. Control Loop 1 setpoint is now 122.5 (based on its units).} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "device_busy*" { + set h1 {BUSY? Query Instrument Busy Status. Returned: «instrument busy status». Format: n.} + set h2 {Indicates that the instrument is busy performing a lengthy operation like generating a SoftCal curve, writing to the Flash chip, etc.} + set h3 {Commands that use the Instrument Busy Status say so in their description.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "cfgProtocol_comm*" { + set h1 {COMM [«terminator»], [«bps»], [«parity»] Configure Serial Interface Parameters.} + set h2 {«terminator» Specifies the terminator: 1: «CR»«LF», 2: «LF»«CR», 3: «CR», 4: «LF»} + set h3 {«bps» Specifies the bits per second (bps) rate. Valid entries: 1: 300, 2: 1200, 3: 2400, 4: 4800, 5: 9600, 6: 19200} + set h4 {«parity» Valid entries: 1: 7 data bits, 1 stop bit, odd parity; 2: 7 data bits, 1 stop bit, even parity;} + set h5 {3: 8 data bits, 1 stop bit, no parity.} + #Example: COMM 1, 6, 3[term]. After receiving the current terminator, the instrument responds at + #19,200 bps using 8 data bits, 1 stop bit, no parity, and «CR»«LF» as a terminator. + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "deviceID_idn*" { + set h1 {*IDN? Query Identification. Returned: «manufacturer», «model number», «serial number», «firmware date»} + set h2 {Format: LSCI,MODEL340,aaaaaa,nnnnnn. Remarks: Identifies the instrument model and software level.} + set helptext $h1$CRLF$h2 + } + "relayStatus*" { + set h1 {RELAYST? «high/low» Queries specified Relay Status} + set h2 {Returned: «status». Format: n} + set h3 {Returns specified relay status,: 0 : off, 1 : on} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "relayCtrlParmHi*" { + set h1 {RELAY «high/low», [«mode»], [«off/on»] } + set h2 {Configure Relay Control Parameters for the HIGH relay.} + set h3 {«mode» Specifies relay settings mode. Valid entries: 0 : off, 1 : alarms, 2 : manual.} + set h4 {«off/on» off:0, on:1 Specifies the manual relay settings if «mode» : 2.} + set h5 {Example: RELAY 1, 2, 1. Manually turns on the high relay} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + if { $::scobj::ls336::ls336_LSmodel == 336 } { + set h1 {RELAY «high/low», «mode», «input alarm», «alarm type» } + set h3 {«mode» Specifies relay settings mode. Valid entries: 0:off, 1:on, 2:alarms.} + set h4 {«input alarm» Specifies which input alarm activates the relay when it is in alarm mode (A-D).} + set h5 {«alarm type» Specifies the alarm type that activates the relay. 0:Low alarm, 1:High alarm, 2:Both alarms} + set h6 {Example: RELAY 1,2,B,0. Relay 1 (Hi) activates when input B low alarm activates.} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6 + } + } + "relayCtrlParmLo*" { + set h1 {RELAY «high/low», [«mode»], [«off/on»] } + set h2 {Configure Relay Control Parameters for the LOW relay} + set h3 {«mode» Specifies relay settings mode. Valid entries: 0 : off, 1 : alarms, 2 : manual.} + set h4 {«off/on» off:0, on:1 Specifies the manual relay settings if «mode» : 2.} + set h5 {Example: RELAY 1, 2, 1. Manually turns on the high relay} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + if { $::scobj::ls336::ls336_LSmodel == 336 } { + set h1 {RELAY «high/low», «mode», «input alarm», «alarm type» } + set h3 {«mode» Specifies relay settings mode. Valid entries: 0:off, 1:on, 2:alarms.} + set h4 {«input alarm» Specifies which input alarm activates the relay when it is in alarm mode (A-D).} + set h5 {«alarm type» Specifies the alarm type that activates the relay. 0:Low alarm, 1:High alarm, 2:Both alarms} + set h6 {Example: RELAY 2,2,B,0. Relay 2 (Lo) activates when input B low alarm activates.} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6 + } + } + "reset_rst*" { + set h1 {*RST Reset Instrument. Returned: Nothing.} + set h2 {Sets controller parameters to power_up settings.} + set helptext $h1$CRLF$h2 + } + "selftest*" { + set h1 {*TST? Query Self Test. Returned: 0 or 1. Format: n.} + set h2 {The Lakeshore 336 and 340 perform a selftest at power_up. 0 : no errors found, 1 : errors found.} + set helptext $h1$CRLF$h2 + } + "statusByte*" { + set h1 {*STB? Query Status Byte. The integer returned represents the sum of the bit weighting of the status flag bits that} + set h2 {are set in the Status Byte Register. Acts like a serial poll, but does not reset the register to all zeros.} + set h3 {Bit Bit Weighting Event Name} + set h4 {0 1 New A and B} + set h5 {1 2 New Option} + set h6 {2 4 Settle} + set h7 {3 8 Alarm} + set h8 {4 16 Error} + set h9 {5 32 ESB Event summary bit} + set hA {6 64 SRQ Service request bit} + set hB {7 128 Ramp Done} + if { $::scobj::ls336::ls336_LSmodel == 336 } { + set h4 {0 1 not used} + set h5 {1 2 not used} + set h6 {2 4 not used} + set h7 {3 8 not used} + set h8 {4 16 MAV Message available bit} + set h9 {5 32 ESB Event summary bit} + set hA {6 64 RQS Request service bit} + set hB {7 128 OSB Operation summary bit} + } + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8$CRLF$h9$CRLF$hA$CRLF$hB + } + "alarm_Limits_*" { + set h1 {ALARM «input», [«off/on»], [«source»], [«high value»], [«low value»], [«latch enable»], [«relay»]} + set h2 {Configures the alarm parameters for an input} + set h3 {«off/on» check the alarm for this input} + set h4 {«source» units for high/low values 1:Kelvin, 2:Celsius, 3:Sensor units, 4:Linear data} + set h5 {«high value» Limit to activate the high alarm} + set h6 {«low value» Limit to activate low alarm} + set h7 {«latch enable» enable latched alarm} + set h8 {«relay enable» 0:off, 1:on} + set h9 { } + #«relay enable» Specifies how the activated alarm affects relays. NOTE: Does not + #guarantee the alarm activates the relays. See the RELAY command. + #Example: ALARM A, 0. Turns off alarm checking for Input A. + #ALARM B, 1, 1, 270.0, ,1. Turns on alarm checking for input B, activates high alarm if + #kelvin reading is over 270, and latches the alarm when kelvin reading falls below 270 + if { $::scobj::ls336::ls336_LSmodel == 336 } { + set h1 {ALARM «input», [«off/on»], [«high value»], [«low value»], [«deadband»], [«latch enable»], [«audible»], [«visible»]} + set h2 {Configures alarm parameters for an input} + set h3 {«off/on» check the alarm for this input} + set h4 {«high value» Limit to activate the high alarm} + set h5 {«low value» Limit to activate low alarm} + set h6 {«deadband» sets the value the source must change} + set h7 {«latch enable» enable latched alarm} + set h8 {«audible» alarm 0:off,1:on} + set h9 {«visible» alarm 0:off,1:on} + } + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8$CRLF$h9 + } + "alarm_Limit_Lo*" { + set h1 {ALARM_Lo «input»,«low value»} + set h2 {«low value» Sets the value the source is checked against to activate low alarm.} + set helptext $h1$CRLF$h2 + } + "alarm_Limit_Hi*" { + set h1 {ALARM_Hi «input»,«high value»} + set h2 {«high value» Sets the value the source is checked against to activate the high alarm.} + set helptext $h1$CRLF$h2 + } + "alarmStatus*" { + set h1 {ALARMST? «input» Query Input Alarm Status.} + set h2 {Returned: «high status», «low status». Format: n,n.} + set h3 {Remarks: Returns the alarm status of an input. «input» specifies which input to query.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "calCurveHdr*" { + set h1 {CRVHDR «curve», [«name»], [«SN»], [«format»], [«limit value»], [«coefficient»]} + set h2 {Configures the user curve header} + set h3 {«curve» Specifies which curve to configure (21 - 59)} + set h4 {«name» Curve name (max 15 chars).} + set h5 {«SN» Curve serial number (max 10 chars)} + set h6 {«format» Curve data format (1:mV/K, 2:V/K, 3:Ω/K, 4:log Ω/K, 5:log Ω/log K)} + set h7 {«limit value» Curve temperature limit in kelvin.} + set h8 {«coefficient» Curves temperature coefficient (1:negative,2:positive)} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8 + } + "inpSetup*" { + set h1 {INSET «input», «enable», «compensation»} + set h2 {Configure hardware input setup parameters} + set h3 {«input» Specifies which input to configure (A,B,C,D)} + set h4 {«enable» Specifies whether the input is allowed to be read.} + set h5 {«compensation» For NTC resistors and special sensors only. 0: off, 1: on, 2: pause} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "inpCalCurve*" { + set h1 {INCRV «input», «curve number»} + set h2 {Specifies the curve an input uses for temperature conversion.} + set h3 {«input» Specifies which input to configure (A,B,C,D)} + set h4 {«curve number» Specifies which curve the input uses. If specified curve parameters do not match the input,} + set h5 {the curve number defaults to 0. 0: none, 1 to 20: standard curves, 21 to 59: user curves} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "alarmResetAll*" { + set helptext {ALMRST Clear Alarm Status for All Inputs} + } + "sensorType*" { + set h1 {INTYPE «input», [«type»], [«units»], [«coeff»], [«excitation»], [«range»]} + set h2 {Configure Input Type Parameters} + set h3 {«type» 0:Special,1:SiDiode,2:GaAlAsDiode,3:Platinum100 (250Ω),4:as 3 (500Ω),5:Platinum1000,6:RhodiumIron,7:CarbonGlass,} + set h4 {8:Cernox,9:RuOx,10:Ge,11:Capacitor,12:Thermocpl} + set h5 {«units» Sensor units 1:Volts,2:ohms} + set h6 {«coefficient» input coeff. 1:neg,2:pos} + set h7 {«excitation» 0:Off,1:30nA,2:100nA,3:300nA,..} + set h8 {«range» 1:1mV,2:2.5mV,..} + # Lakeshore 340 model only + # <excitation> 0:Off,1:30nA,2:100nA,3:300nA,4:1µA,5:3µA,6:10µA,7:30µA,8:100µA,9:300µA,10:1mA,11:10mV,12:1mV + # <range> 1:1 mV,2:2.5 mV,3:5 mV,4:10 mV,5:25 mV,6:50 mV,7:100 mV,8:250 mV,9:500 mV,10:1 V,11:2.5V,12:5 V,13:7.5 V + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8 + } + "inputType*" { + set h1 {INTYPE «input», [«type»], [«autorange»], [«range»], [«compensation»], [«units»]} + set h2 {Configure Input Type Parameters} + set h3 {«type» 0:Disabled,1:Diode,2:PlatinumRTD,3:NTC RTD,4:Thermocouple} + set h4 {«autorange» 0:off,1:on} + set h5 {«range» specifies range if autorange is off} + set h6 {«compensation» 0:off,1:on. Reversal for thermal EMF compensation if input} + set h7 { is resistive, room comp. if input is thermocpl. Always 0 if input is diode} + set h8 {«units» Sensor and setpoint units 1:Kelvin,2:Celsius,3:Sensor} + # Lakeshore 336 model only + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8 + } + "minMaxInpFunc*" { + set h1 {MNMX? «input» Query Minimum and Maximum Input Function Parameters} + set h2 {Returned: «on/pause», «source». Format: n,n} + set h3 {«on/pause» Turns on or Pauses the max/min function. Valid entries: 1 : on, 2 : paused.} + set h4 {«source» Specifies input data to process through max/min. Valid entries: 1 : Kelvin,} + set h5 {2 : Celsius, 3 : sensor units, 4 : linear data} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "sensorStatus*" { + set h1 {RDGST? «input» Query Input Status. Returned: «reading bit weighting»} + set h2 {The integer returned represents the sum of the bit weighting of the input status flag bits} + set h3 {Bit Weighting StatusIndicator} + set h4 {0 1 invalid reading} + set h5 {1 2 old reading} + set h6 {4 16 temp underrange} + set h7 {5 32 temp overrange} + set h8 {6 64 units zero} + set h9 {7 128 units overrange} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8$CRLF$h9 + } + "ctrl_Limit*" { + set h1 {CLIMIT «loop», [«SP limit value»],[«pos slope value»],[«neg slope value»],[«max current»],[«max range»]} + set h2 {Control Loop Limit Parameters} + set h3 {«SP limit value» limit at which the loop turns off output} + set h4 {«positive slope value» max pos change in output} + set h5 {«negative slope value» max neg change in output} + set h6 {«max current» Max current for loop 1 heater output (1:0.25A,2:0.5A,3:1.0A,4:2.0A,5:User)} + set h7 {«max range» Max loop 1 heater range (0 to 5)} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7 + } + "ctrl_Mode*" { + set h1 {CMODE «loop», «mode» Configure Control Loop Mode} + set h2 {«mode» Specifies the control mode. Valid entries: 1: Manual PID,2: Zone,3: Open Loop,4: AutoTune PID, 5: AutoTune PI, 6: AutoTune P} + set h3 {Example: CMODE 1, 4. Control Loop 1 uses PID AutoTuning.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "outMode_*" { + set h1 {OUTMODE «output», «mode» «input» «powerup enable» Output Mode Command} + set h2 {«mode» Specifies the control mode. Valid entries: 0:off, 1:Closed loop PID,2:Zone,3:Open Loop,4:Monitor out, 5:Warmup Supply} + set h3 {«input» which input to use for control. 0:none, 1:A, 2:B, 3:C, 4:D} + set h4 {«powerup enable» 0:output shuts off 1:output remains on after power cycle} + set h5 {Remarks: Modes 1 and 2 are only valid for heater outputs, and modes 4 and 5 are only valid for Monitor Out} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "config_Loop*" { + set h1 {CSET «loop», [«input»], [«units»], [«off/on»], [«powerup enable»] Configure Control Loop Parameters} + set h2 {«input» which input to control from. (A,B,..)} + set h3 {«units» Setpoint units. 1: Kelvin, 2: Celsius, 3: sensor units} + set h4 {«off/on» Control loop is on:1 or off:0} + set h5 {«powerup enable» Control loop is on or off after power_up} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "ctrl_Loop*" { + set h1 {Shows the Control Loop Parameters.} + set h2 {Use the /control/config_Loop_x entry to change settings} + if { $::scobj::ls336::ls336_LSmodel == 336 } { + set h1 {Shows the Output Control Parameters.} + set h2 {Use the /control/outMode_x entry to change settings} + } + set helptext $h1$CRLF$h2 + } + "pid_Loop*" { + set h1 {PID «loop», [«P value»], [«I value»], [«D value»] Configure Control Loop PID Values} + if { $::scobj::ls336::ls336_LSmodel == 336 } { + set h1 {PID «output», [«P value»], [«I value»], [«D value»] Configure Control Loop PID Values} + } + set h2 {«P value» The value for P (proportional)} + set h3 {«I value» The value for I (integral)} + set h4 {«D value» The value for D (derivative)} + set h5 {Example: PID 1, 10, 50. Control Loop 1 P is 10 and I is 50.} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "ramp_Loop*" { + set h1 {RAMP «loop», [«off/on»], [«rate value»] Configure Ramp Parameters for specified Control Loop} + if { $::scobj::ls336::ls336_LSmodel == 336 } { + set h1 {RAMP «output», [«off/on»], [«rate value»] Configure Ramp Parameters for specified output} + } + set h2 {«off/on» Specifies whether ramping is off or on} + set h3 {«rate value» Specifies how many kelvin per minute to ramp the setpoint (0.1 - 100)} + set h4 {Example: RAMP 1, 1, 10.5. When Control Loop 1 setpoint is changed, ramp the current setpoint to the target setpoint at 10.5 K/minute.} + if { $::scobj::ls336::ls336_LSmodel == 336 } { + set h4 {Example: RAMP 1, 1, 10.5. When the setpoint for output 1 is changed, ramp the current setpoint to the target setpoint at 10.5 K/minute.} + } + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4 + } + "rampStatus_Loop*" { + set h1 {RAMPST? «loop» Query Ramp Status for specified Control Loop.} + if { $::scobj::ls336::ls336_LSmodel == 336 } { + set h1 {RAMPST? «output» Query Ramp Status for specified output.} + } + set h2 {Returned: «ramp status». Format: n} + set h3 {Remarks: Returns 0 if setpoint is not ramping, and 1 if it is ramping. «loop» specifies loop to query.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "settle*" { + set h1 {SETTLE [«threshold»], [«time»] Sets Loop_1 Settle Parameters} + set h2 {«threshold» Specifies the allowable band around the setpoint. Valid entries: 0.0 to 100.00} + set h3 {«time» Specifies time in seconds the reading must stay within the band. Valid entries: 0 to 86400} + set h4 {Example: SETTLE 10.0, 10. The Control Loop 1 input readings must be within ±10 K of the setpoint for 10 seconds before the Within Control Limit flag is set.} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4 + } + "heaterOutp*" { + set h1 {HTR? Query Heater Output.} + set h2 {Returned: «heater value». Format: nnn.n.} + set h3 {Remarks: Returns the heater output in percent.} + set helptext $h1$CRLF$h2$CRLF$h3 + if { $::scobj::ls336::ls336_LSmodel == 336 } { + set h1 {HTR? «output» Query Heater Output.} + set h2 {«output» is 1 or 2; use command AOUT for output 3 and 4.} + set h3 {Returned: «heater value». Format: nnn.n.} + set h4 {Remarks: Returns the heater output in percent.} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4 + } + } + "manualOut_*" { + set h1 {MOUT «loop», «value» Manual Output Command.} + set h2 {«loop» specifies the control loop to configure: 1-4.} + if { $::scobj::ls336::ls336_LSmodel == 336 } { + set h1 {MOUT «output», «value» Manual Output Command.} + set h2 {«output» specifies output to configure: 1-4.} + } + set h3 {«value» Specifies value for manual output in percent (non-integer allowed).} + set h4 {Remarks: Requires closed-loop-PID, Zone, or Open-Loop mode.} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4 + } + "heaterStatus*" { + set h1 {HTRST? Query Heater Status. Returned: «error code». Format: nn.} + if { $::scobj::ls336::ls336_LSmodel == 336 } { + set h1 {HTRST? «output» Query Heater Status. Returned: «error code». Format: nn.} + } + set h2 {Remarks: Returns the heater error code (User Manual Paragraph 11.8).} + set h3 {A value of zero means that the system is happy.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "heaterRange*" { + if { $::scobj::ls336::ls336_LSmodel == 340 } { + set h1 {RANGE «range» Configure Heater Range} + set h2 {«range» specifies the heater range (0 to 5)} + set h3 {0 : heater switched off} + set h4 {1 : 5 mW} + set h5 {2 : 50 mW} + set h6 {3 : 500 mW} + set h7 {4 : 5 W} + set h8 {5 : 50 W} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8 + } else { + set h1 {RANGE «output» «range» Configure Heater Range} + set h2 {«output» specifies which output (1,2,3,4)} + set h3 {«range» specifies the heater range (0 to 3)} + set h4 {0 : heater switched off} + set h5 {1 : low} + set h6 {2 : medium} + set h7 {3 : high} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7 + } + } + "isInTolerance_Lp*" { + set h1 {A flag that indicates whether the actual temperature is within tolerance} + set h2 {of the setpoint temperature for that control loop.} + set helptext $h1$CRLF$h2 + } + "apply_tolerance*" { + set h1 {A flag that indicates whether the control loop should actively try} + set h2 {to keep the temperature within the set tolerance of the setpoint.} + set helptext $h1$CRLF$h2 + } + "tolerance*" { + set h1 {Tolerance specifies the temperature tolerance in Kelvin applicable to the setpoint.} + set helptext $h1 + } + "monMode_Lp*" { + set h1 {A flag that indicates whether heaters or other active components are attempting to} + set h2 {drive the temperature towards the setpoint or whether they are currently inactive} + set h3 {with the device just monitoring while the temperature is in tolerance with the setpoint.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "errhandler*" { + set h1 {Specifies the default action in case of a serious error.} + set h2 {The default action is always 'pause'.} + set helptext $h1$CRLF$h2 + } + "dateTime*" { + set h1 {DATETIME «MM»,«DD»,«YYYY»,«HH»,«mm»,«SS»,«sss» Configure Date and Time.} + set h2 {«MM» Month} + set h3 {«DD» Day of month} + set h4 {«YYYY» Year} + set h5 {«HH» Hours (0..23)} + set h6 {«mm» Minutes} + set h7 {«SS» Seconds} + set h8 {«sss» milliseconds} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8 + } + "timStamp*" { + set h1 {Time stamp. Seconds since 1st January 2000.} + set h2 {See node /other/dateTime for editing and formatted date/time display.} + set helptext $h1$CRLF$h2 + } + "status*" { + set helptext {Device status. Is 'idle' while in tolerance or 'busy' while driving or resetting} + } + default { + set helptext {Sorry mate. No help available.} + puts "No help info available for node $varName" + } + } + #set sLen [string bytelength $helptext] + #puts "helptext ($sLen bytes) $helptext" + hsetprop $nodeName help $helptext + } message ]} { + return -code error "in helpNotes4user: $message" + } +} + +## +# @brief createNode() creates a node for the given nodename with the properties and virtual +# function names provided +# @param scobj_hpath string variable holding the path to the object's base node in sics (/sample/tc1) +# @param sct_controller name of the ls336 scriptcontext object (typically sct_ls336_tc1 or tc2) +# @param cmdGroup subdirectory (below /sample/tc*/) in which the node is to be created +# @param varName name of the actual node typically representing one device command +# @param readable set to 1 if the node represents a query command, 0 if it is not +# @param writable set to 1 if the node represents a request for a change in settings sent to the device +# @param pollEnabled set to 1 if the node property pollable is to be enabled (node gets read every 5 secs) +# @param drivable if set to 1 it prepares the node to provide a drivable interface +# @param idx indicates which control loop or which input channel the command corresponds to +# @param ls340 set to 1 if this command is supported by device model-340 +# @param ls336 set to 1 if this command is supported by device model-336 +# @param dataType data type of the node, must be one of none, int, float, text +# @param permission defines what user group may read/write to this node (is one of spy, user, manager) +# @param rdCmd actual device query command to be sent to the device +# @param rdFunc nextState Function to be called after the getValue function, typically rdValue() +# @param wrCmd actual device write command to be sent to the device +# @param wrFunc Function to be called to send the wrCmd to the device, typically setValue() +# @param allowedValues allowed values for the node data - does not permit other +# @param klasse Nexus class name (?) +# @return OK +proc createNode {scobj_hpath sct_controller cmdGroup varName readable writable pollEnabled drivable idx ls340 ls336 dataType permission rdCmd rdFunc wrCmd wrFunc allowedValues klasse lsModel} { + #puts "createing node for: $scobj_hpath $cmdGroup $varName $readable $writable $pollEnabled $drivable $idx $dataType $permission $rdCmd $rdFunc $wrCmd $wrFunc" + #puts "createNode, lsModel = $lsModel, ls340=$ls340, ls336=$ls336, varName=$varName " + if {$lsModel == 340 && $ls340 == 0} { + # puts "Command node $cmdGroup/$varName not supported by Lakeshore $lsModel" + return OK + } + # Nothing to do + if {$lsModel == 336 && $ls336 == 0} { + # puts "Info: Command node $cmdGroup/$varName not supported by Lakeshore $lsModel" + return OK + } + # It is a command that is supported by the device + if {[ catch { + set ns ::scobj::ls336 + set nodeName "$scobj_hpath/$cmdGroup/$varName" + if {1 > [string length $cmdGroup]} { + set nodeName "$scobj_hpath/$varName" + } + hfactory $nodeName plain $permission $dataType + if {$readable == 1} { + hsetprop $nodeName read ${ns}::getValue $scobj_hpath $rdFunc $rdCmd $idx + } + if {$pollEnabled == 1} { + # puts "enabling polling for $nodeName" + $sct_controller poll $nodeName + } + hsetprop $nodeName $rdFunc ${ns}::$rdFunc $idx + if {$writable == 1} { + hsetprop $nodeName write ${ns}::$wrFunc $scobj_hpath noResponse $wrCmd $idx + hsetprop $nodeName writestatus UNKNOWN + hsetprop $nodeName noResponse ${ns}::noResponse + if {$pollEnabled == 1} { + $sct_controller write $nodeName + } + } + switch -exact $dataType { + "none" { } + "int" { hsetprop $nodeName oldval -1 } + "float" { hsetprop $nodeName oldval -1.0 } + default { hsetprop $nodeName oldval UNKNOWN } + } + if {1 < [string length $allowedValues]} { + hsetprop $nodeName values $allowedValues + } + # Drive adapter interface + if {$drivable == 1} { + hsetprop $nodeName check ${ns}::check $scobj_hpath 1 + hsetprop $nodeName driving 0 + hsetprop $nodeName checklimits ${ns}::check $scobj_hpath $idx + hsetprop $nodeName checkstatus ${ns}::drivestatus $scobj_hpath + hsetprop $nodeName halt ${ns}::halt $scobj_hpath $idx + } + } message ]} { + return -code error "in createNode $message" + } + helpNotes4user $scobj_hpath $cmdGroup $varName + return OK +} + + + ## + # @brief mk_sct_lakeshore_336() creates a scriptcontext object for a Lakeshore 336 or 340 temperature controller + # @param sct_controller name of the ls336 scriptcontext object (typically sct_ls336_tc1 or tc2) + # @param klasse Nexus class name (?), typically 'environment' + # @param tempobj short name for the temperature controller scriptcontext object (typ. tc1 or tc2) + # @param tol temperature tolerance in Kelvin (typ. 1) + # @return nothing (well, the sct object) + proc mk_sct_lakeshore_336 {sct_controller klasse tempobj LSmodel tol1 tol2 verbose} { + if {[ catch { + set ::scobj::ls336::ls336_driveTolerance1 $tol1 + set ::scobj::ls336::ls336_driveTolerance2 $tol2 + set ::scobj::ls336::ls336_LSmodel $LSmodel + set ::scobj::ls336::ls336_verbose $verbose + set ns ::scobj::ls336 + set ::scobj::ls336::ls336_sct_obj_name $tempobj + + # terminator string for serial communication (empty for ls340, taken care of with the COMM command) + #set CR "\r" + #set LF "\n" + #Wombat uses only CR not CRLF + #set ::scobj::ls336::ls336_term "" ! obsolete + + MakeSICSObj $tempobj SCT_OBJECT + sicslist setatt $tempobj klass $klasse + sicslist setatt $tempobj long_name $tempobj + # Create a base node for all the state machines of this sics object + set scobj_hpath /sics/$tempobj + set ::scobj::ls336::ls336_path2nodes $scobj_hpath + + # Create state machines for the following device commands (non-polled entries are place-holders + # for manually maintained nodes, like the selector for the input that provides the sample temperature + # 'sampleSensor', the sample tempreature reading 'Tsample', the input that provides for the controlLoop + # 'value', and the control loop parameters in human-readable form 'ctrl_Loop_x') + # Note that drivable nodes require the index of the control loop in their call to halt() + # Nodes appear in gumtree in the order in which they are created here. + # + # Initialise the model-dependent list of supported device commands + # RdWrPlDrIdx + # cmdGroup subdirectory (below /sample/tc*/) in which the node is to be created + # varName name of the actual node typically representing one device command + # readable set to 1 if the node represents a query command, 0 if it is not + # writable set to 1 if the node represents a request for a change in settings sent to the device + # pollEnabled set to 1 if the node property pollable is to be enabled (node gets read every 5 secs) + # drivable if set to 1 it prepares the node to provide a drivable interface + # idx indicates which control loop or which input channel the command corresponds to + # ls340 command supported by Lakeshore model 340 + # ls336 command supported by Lakeshore model 336 + # dataType data type of the node, must be one of none, int, float, text + # permission defines what user group may read/write to this node (is one of spy, user, manager) + # rdCmd actual device query command to be sent to the device + # rdFunc nextState Function to be called after the getValue function, typically rdValue() + # wrCmd actual device write command to be sent to the device + # wrFunc Function to be called to send the wrCmd to the device, typically setValue() + # allowedValues allowed values for the node data - does not permit other + set deviceCommandToplevel { + sensor sampleSensor 0 1 0 0 1 1 1 text user {CSET? 1} {rdValue} {InpSample } {setPseudoValue} {A,B,C,D} + sensor Tsample 1 0 0 0 1 1 1 float spy {KRDG? A} {rdValue} {} {setValue} {} + sensor ctrl_Loop_1 1 0 0 0 1 1 1 text user {CSET? 1} {rdValue} {} {setValue} {} + sensor ctrlLp1_value 1 0 0 0 1 1 1 float spy {KRDG? A} {rdValue} {} {setValue} {} + sensor setpoint1 1 1 1 1 1 1 1 float user {SETP? 1} {rdValue} {SETP 1,} {setPoint} {} + sensor ctrl_Loop_2 1 0 0 0 2 1 1 text user {CSET? 2} {rdValue} {} {setValue} {} + sensor ctrlLp2_value 1 0 0 0 2 1 1 float spy {KRDG? B} {rdValue} {} {setValue} {} + sensor setpoint2 1 1 1 1 2 1 1 float user {SETP? 2} {rdValue} {SETP 2,} {setPoint} {} + sensor ctrl_Loop_3 1 0 0 0 3 0 1 text user {CSET? 3} {rdValue} {} {setValue} {} + sensor ctrlLp3_value 1 0 0 0 3 0 1 float spy {KRDG? C} {rdValue} {} {setValue} {} + sensor setpoint3 1 1 1 1 3 0 1 float user {SETP? 3} {rdValue} {SETP 3,} {setPoint} {} + sensor ctrl_Loop_4 1 0 0 0 4 0 1 text user {CSET? 4} {rdValue} {} {setValue} {} + sensor ctrlLp4_value 1 0 0 0 4 0 1 float spy {KRDG? D} {rdValue} {} {setValue} {} + sensor setpoint4 1 1 1 1 4 0 1 float user {SETP? 4} {rdValue} {SETP 4,} {setPoint} {} + sensor sensorValueA 1 0 1 0 A 1 1 float spy {KRDG? A} {rdInpValue} {} {setValue} {} + sensor sensorValueB 1 0 1 0 B 1 1 float spy {KRDG? B} {rdInpValue} {} {setValue} {} + sensor sensorValueC 1 0 1 0 C 1 1 float spy {KRDG? C} {rdInpValue} {} {setValue} {} + sensor sensorValueD 1 0 1 0 D 1 1 float spy {KRDG? D} {rdInpValue} {} {setValue} {} + sensor timStamp 1 0 0 0 0 1 0 int user {DATETIME?} {rdValue} {} {setValue} {} + } + set deviceCommand { + input alarm_Limits_A 1 1 1 0 A 1 1 text spy {ALARM? A} {rdAlarmVal} {ALARM A,} {setValue} {} + input alarm_Limits_B 1 1 1 0 B 1 1 text spy {ALARM? B} {rdAlarmVal} {ALARM B,} {setValue} {} + input alarm_Limits_C 1 1 1 0 C 1 1 text spy {ALARM? C} {rdAlarmVal} {ALARM C,} {setValue} {} + input alarm_Limits_D 1 1 1 0 D 1 1 text spy {ALARM? D} {rdAlarmVal} {ALARM D,} {setValue} {} + input alarmStatusA 1 0 1 0 A 1 1 text spy {ALARMST? A} {rdValue} {} {setValue} {} + input alarmStatusB 1 0 1 0 B 1 1 text spy {ALARMST? B} {rdValue} {} {setValue} {} + input alarmStatusC 1 0 1 0 C 1 1 text spy {ALARMST? C} {rdValue} {} {setValue} {} + input alarmStatusD 1 0 1 0 D 1 1 text spy {ALARMST? D} {rdValue} {} {setValue} {} + input inpSetup_A 1 1 1 0 A 1 0 text user {INSET? A} {rdValue} {INSET A,} {setValue} {} + input inpSetup_B 1 1 1 0 B 1 0 text user {INSET? B} {rdValue} {INSET B,} {setValue} {} + input inpSetup_C 1 1 1 0 C 1 0 text user {INSET? C} {rdValue} {INSET C,} {setValue} {} + input inpSetup_D 1 1 1 0 D 1 0 text user {INSET? D} {rdValue} {INSET D,} {setValue} {} + input inpCalCurve_A 1 1 1 0 A 1 1 int user {INCRV? A} {rdCrvValue} {INCRV A,} {setValue} {} + input calCurveHdr_A 1 0 1 0 A 1 1 text user {CRVHDR? } {rdValue} {} {setValue} {} + input inpCalCurve_B 1 1 1 0 B 1 1 int user {INCRV? B} {rdCrvValue} {INCRV B,} {setValue} {} + input calCurveHdr_B 1 0 1 0 B 1 1 text user {CRVHDR? } {rdValue} {} {setValue} {} + input inpCalCurve_C 1 1 1 0 C 1 1 int user {INCRV? C} {rdCrvValue} {INCRV C,} {setValue} {} + input calCurveHdr_C 1 0 1 0 C 1 1 text user {CRVHDR? } {rdValue} {} {setValue} {} + input inpCalCurve_D 1 1 1 0 D 1 1 int user {INCRV? D} {rdCrvValue} {INCRV D,} {setValue} {} + input calCurveHdr_D 1 0 1 0 D 1 1 text user {CRVHDR? } {rdValue} {} {setValue} {} + input sensorTypeA 1 1 1 0 A 1 0 text user {INTYPE? A} {rdValue} {INTYPE A,} {setValue} {} + input sensorTypeB 1 1 1 0 B 1 0 text user {INTYPE? B} {rdValue} {INTYPE B,} {setValue} {} + input sensorTypeC 1 1 1 0 C 1 0 text user {INTYPE? C} {rdValue} {INTYPE C,} {setValue} {} + input sensorTypeD 1 1 1 0 D 1 0 text user {INTYPE? D} {rdValue} {INTYPE D,} {setValue} {} + input inputTypeA 1 1 1 0 A 0 1 text user {INTYPE? A} {rdValue} {INTYPE A,} {setValue} {} + input inputTypeB 1 1 1 0 B 0 1 text user {INTYPE? B} {rdValue} {INTYPE B,} {setValue} {} + input inputTypeC 1 1 1 0 C 0 1 text user {INTYPE? C} {rdValue} {INTYPE C,} {setValue} {} + input inputTypeD 1 1 1 0 D 0 1 text user {INTYPE? D} {rdValue} {INTYPE D,} {setValue} {} + input minMaxInpFuncA 1 0 1 0 A 1 0 int user {MNMX? A} {rdValue} {} {setValue} {} + input minMaxInpFuncB 1 0 1 0 B 1 0 int user {MNMX? B} {rdValue} {} {setValue} {} + input minMaxInpFuncC 1 0 1 0 C 1 0 int user {MNMX? C} {rdValue} {} {setValue} {} + input minMaxInpFuncD 1 0 1 0 D 1 0 int user {MNMX? D} {rdValue} {} {setValue} {} + input sensorStatusA 1 0 1 0 A 1 1 int spy {RDGST? A} {rdBitValue} {} {setValue} {} + input sensorStatusB 1 0 1 0 B 1 1 int spy {RDGST? B} {rdBitValue} {} {setValue} {} + input sensorStatusC 1 0 1 0 C 1 1 int spy {RDGST? C} {rdBitValue} {} {setValue} {} + input sensorStatusD 1 0 1 0 D 1 1 int spy {RDGST? D} {rdBitValue} {} {setValue} {} + control config_Loop_1 1 1 1 0 1 1 0 text user {CSET? 1} {rdCfgValue} {CSET 1,} {setValue} {} + control config_Loop_2 1 1 1 0 2 1 0 text user {CSET? 2} {rdCfgValue} {CSET 2,} {setValue} {} + control ctrl_Limit_1 1 1 1 0 1 1 0 text user {CLIMIT? 1} {rdValue} {CLIMIT 1,} {setValue} {} + control ctrl_Limit_2 1 1 1 0 2 1 0 text user {CLIMIT? 2} {rdValue} {CLIMIT 2,} {setValue} {} + control ctrl_Mode_1 1 1 1 0 1 1 0 int user {CMODE? 1} {rdValue} {CMODE 1,} {setValue} {} + control ctrl_Mode_2 1 1 1 0 2 1 0 int user {CMODE? 2} {rdValue} {CMODE 2,} {setValue} {} + control outMode_1 1 1 1 0 1 0 1 text user {OUTMODE? 1} {rdCfgValue} {OUTMODE 1,} {setValue} {} + control outMode_2 1 1 1 0 2 0 1 text user {OUTMODE? 2} {rdCfgValue} {OUTMODE 2,} {setValue} {} + control outMode_3 1 1 1 0 3 0 1 text user {OUTMODE? 3} {rdCfgValue} {OUTMODE 3,} {setValue} {} + control outMode_4 1 1 1 0 4 0 1 text user {OUTMODE? 4} {rdCfgValue} {OUTMODE 4,} {setValue} {} + control manualOut_1 1 1 1 0 1 1 1 text user {MOUT? 1} {rdValue} {MOUT 1,} {setValue} {} + control manualOut_2 1 1 1 0 2 1 1 text user {MOUT? 2} {rdValue} {MOUT 2,} {setValue} {} + control manualOut_3 1 1 1 0 3 0 1 text user {MOUT? 3} {rdValue} {MOUT 3,} {setValue} {} + control manualOut_4 1 1 1 0 4 0 1 text user {MOUT? 4} {rdValue} {MOUT 4,} {setValue} {} + control pid_Loop_1 1 1 1 0 1 1 1 text user {PID? 1} {rdValue} {PID 1,} {setValue} {} + control pid_Loop_2 1 1 1 0 2 1 1 text user {PID? 2} {rdValue} {PID 2,} {setValue} {} + control ramp_Loop_1 1 1 1 0 1 1 1 text user {RAMP? 1} {rdValue} {RAMP 1,} {setValue} {} + control ramp_Loop_2 1 1 1 0 2 1 1 text user {RAMP? 2} {rdValue} {RAMP 2,} {setValue} {} + control rampStatus_Loop_1 1 0 1 0 1 1 1 int spy {RAMPST? 1} {rdValue} {} {setValue} {} + control rampStatus_Loop_2 1 0 1 0 2 1 1 int spy {RAMPST? 2} {rdValue} {} {setValue} {} + control settleThr_Loop_1 1 1 1 0 1 1 0 float user {SETTLE?} {rdValue} {SETTLE } {setValue} {} + control settleTime_Loop_1 1 1 1 0 1 1 0 int user {SETTLE?} {rdValue} {SETTLE ,} {setValue} {} + heater heaterOutpPercent 1 1 1 0 0 1 0 float user {HTR?} {rdValue} {} {setValue} {} + heater heaterOutput_1 1 1 1 0 1 0 1 float user {HTR? 1} {rdValue} {} {setValue} {} + heater heaterOutput_2 1 1 1 0 2 0 1 float user {HTR? 2} {rdValue} {} {setValue} {} + heater heaterStatus 1 0 1 0 0 1 0 int spy {HTRST?} {rdValue} {} {setValue} {} + heater heaterStatus_1 1 0 1 0 1 0 1 int spy {HTRST? 1} {rdValue} {} {setValue} {} + heater heaterStatus_2 1 0 1 0 2 0 1 int spy {HTRST? 2} {rdValue} {} {setValue} {} + heater heaterRange 1 1 1 0 0 1 0 int user {RANGE?} {rdValue} {RANGE } {setValue} {0,1,2,3,4,5} + heater heaterRange_1 1 1 1 0 0 0 1 int user {RANGE? 1} {rdValue} {RANGE 1,} {setValue} {0,1,2,3} + heater heaterRange_2 1 1 1 0 0 0 1 int user {RANGE? 2} {rdValue} {RANGE 2,} {setValue} {0,1,2,3} + heater heaterRange_3 1 1 1 0 0 0 1 int user {RANGE? 3} {rdValue} {RANGE 3,} {setValue} {0,1} + heater heaterRange_4 1 1 1 0 0 0 1 int user {RANGE? 4} {rdValue} {RANGE 4,} {setValue} {0,1} + other dateTime 1 1 1 0 0 1 0 text user {DATETIME?} {rdValue} {DATETIME } {setValue} {} + other device_busy 1 0 1 0 0 1 0 int spy {BUSY?} {rdValue} {} {setValue} {} + other cfgProtocol_comm 1 1 0 0 0 1 0 text user {COMM?} {rdValue} {COMM } {setValue} {} + other deviceID_idn 1 0 1 0 0 1 1 text spy {*IDN?} {inTolerance} {} {setValue} {} + other selftest 1 0 0 0 0 1 1 int user {*TST?} {rdValue} {} {setValue} {} + other relayStatusHi 1 0 1 0 1 1 1 int spy {RELAYST? 1} {rdValue} {} {setValue} {} + other relayStatusLo 1 0 1 0 2 1 1 int spy {RELAYST? 2} {rdValue} {} {setValue} {} + other relayCtrlParmHi 1 1 1 0 0 1 1 int spy {RELAY? 1} {rdValue} {RELAY 1,} {setValue} {} + other relayCtrlParmLo 1 1 1 0 0 1 1 int spy {RELAY? 2} {rdValue} {RELAY 2,} {setValue} {} + other statusByte 1 0 1 0 0 1 1 int spy {*STB?} {rdValue} {} {setValue} {} + } + # The following 2 commands take no parameter - this makes them difficult to implement in a hipadaba structure + # because they would be nodes without values... + # input alarmResetAll 0 1 0 0 0 1 1 text user {} {rdValue} {ALMRST} {setValue} {} + # other reset_rst 0 1 0 0 0 1 1 text user {} {rdValue} {*RST} {setValue} {} + + hfactory $scobj_hpath/status plain spy text + hsetprop $scobj_hpath/status values busy,idle + hset $scobj_hpath/status "idle" + hfactory $scobj_hpath/status_Ctrl_Lp2 plain spy text + hsetprop $scobj_hpath/status_Ctrl_Lp2 values busy,idle + hset $scobj_hpath/status_Ctrl_Lp2 "idle" + helpNotes4user $scobj_hpath "" "status" + + hfactory $scobj_hpath/sensor plain spy none + # Flags ls340 and ls336 indicate whether this command is support by Lakeshore model ls340 and ls336, respectively + foreach {cmdGroup varName readable writable pollEnabled drivable idx ls340 ls336 dataType permission rdCmd rdFunc wrCmd wrFunc allowedValues} $deviceCommandToplevel { + createNode $scobj_hpath $sct_controller $cmdGroup $varName $readable $writable $pollEnabled $drivable $idx $ls340 $ls336 $dataType $permission $rdCmd $rdFunc $wrCmd $wrFunc $allowedValues $klasse $LSmodel + } + + # create a base node for each commandGroup element - these are all polled + hfactory $scobj_hpath/emon plain spy none + hfactory $scobj_hpath/control plain spy none + hfactory $scobj_hpath/heater plain spy none + hfactory $scobj_hpath/input plain spy none + hfactory $scobj_hpath/other plain spy none + + foreach {cmdGroup varName readable writable pollEnabled drivable idx ls340 ls336 dataType permission rdCmd rdFunc wrCmd wrFunc allowedValues} $deviceCommand { + createNode $scobj_hpath $sct_controller $cmdGroup $varName $readable $writable $pollEnabled $drivable $idx $ls340 $ls336 $dataType $permission $rdCmd $rdFunc $wrCmd $wrFunc $allowedValues $klasse $LSmodel + # helpNotes4user $scobj_hpath $cmdGroup $varName + } + + hsetprop $scobj_hpath/input/alarm_Limits_A units $::scobj::ls336::ls336_tempUnits + hsetprop $scobj_hpath/input/alarm_Limits_B units $::scobj::ls336::ls336_tempUnits + hsetprop $scobj_hpath/input/alarm_Limits_C units $::scobj::ls336::ls336_tempUnits + hsetprop $scobj_hpath/input/alarm_Limits_D units $::scobj::ls336::ls336_tempUnits + + # Create state machines for the following required nodes that do not correspond + # to device commands. So far we only provide one tolerance for both control loops. + hfactory $scobj_hpath/control/apply_tolerance plain user int + hsetprop $scobj_hpath/control/apply_tolerance values 0,1 + hset $scobj_hpath/control/apply_tolerance 1 + helpNotes4user $scobj_hpath "control" "apply_tolerance" + + hfactory $scobj_hpath/control/tolerance1 plain user float + hsetprop $scobj_hpath/control/tolerance1 units $::scobj::ls336::ls336_tempUnits + # hsetprop $scobj_hpath/control/tolerance units "K" + hset $scobj_hpath/control/tolerance1 $tol1 + helpNotes4user $scobj_hpath "control" "tolerance1" + + hfactory $scobj_hpath/control/tolerance2 plain user float + hsetprop $scobj_hpath/control/tolerance2 units $::scobj::ls336::ls336_tempUnits + hset $scobj_hpath/control/tolerance2 $tol2 + helpNotes4user $scobj_hpath "control" "tolerance2" + + # hfactory $scobj_hpath/lowerlimit plain mugger float + # hsetprop $scobj_hpath/lowerlimit units $::scobj::ls336::ls336_tempUnits + # hset $scobj_hpath/lowerlimit $::scobj::ls336::ls336_lowerlimit + # hfactory $scobj_hpath/upperlimit plain mugger float + # hsetprop $scobj_hpath/upperlimit units $::scobj::ls336::ls336_tempUnits + # hset $scobj_hpath/upperlimit $::scobj::ls336::ls336_upperlimit + + # environment monitoring flags: shows if setpoints of loop 1 and 2 are in tolerance + hfactory $scobj_hpath/emon/monMode_Lp1 plain user text + hsetprop $scobj_hpath/emon/monMode_Lp1 values idle,drive,monitor,error + hset $scobj_hpath/emon/monMode_Lp1 "idle" + helpNotes4user $scobj_hpath "emon" "monMode_Lp1" + hfactory $scobj_hpath/emon/monMode_Lp2 plain user text + hsetprop $scobj_hpath/emon/monMode_Lp2 values idle,drive,monitor,error + hset $scobj_hpath/emon/monMode_Lp2 "idle" + helpNotes4user $scobj_hpath "emon" "monMode_Lp2" + hfactory $scobj_hpath/emon/errhandler plain user text + hset $scobj_hpath/emon/errhandler "lazy" + helpNotes4user $scobj_hpath "emon" "errhandler" + + hfactory $scobj_hpath/emon/isInTolerance_Lp1 plain spy text + hsetprop $scobj_hpath/emon/isInTolerance_Lp1 values idle,drive,monitor,error + hset $scobj_hpath/emon/isInTolerance_Lp1 "inTolerance" + helpNotes4user $scobj_hpath "emon" "isInTolerance_Lp1" + hfactory $scobj_hpath/emon/isInTolerance_Lp2 plain spy text + hsetprop $scobj_hpath/emon/isInTolerance_Lp2 values idle,drive,monitor,error + hset $scobj_hpath/emon/isInTolerance_Lp2 "inTolerance" + helpNotes4user $scobj_hpath "emon" "isInTolerance_Lp2" + + # Temperature controllers must have at least the following nodes (for data logging?) + # /tempcont/setpoint + # /tempcont/sensor/ctrlLp1_value + + ::scobj::hinitprops $tempobj + hsetprop $scobj_hpath klass NXenvironment + ::scobj::set_required_props $scobj_hpath + # These are loggable parameters that need additional nexus properties + # Note: node names longer than 8 characters cause eror messages - avoid + set nxProperties " + $scobj_hpath sensor NXsensor spy + $scobj_hpath sensor/Tsample sensor user + $scobj_hpath sensor/ctrlLp1_value sensor user + $scobj_hpath sensor/ctrlLp2_value sensor user + $scobj_hpath sensor/timStamp sensor user + $scobj_hpath sensor/sensorValueA sensor user + $scobj_hpath sensor/sensorValueB sensor user + $scobj_hpath sensor/sensorValueC sensor user + $scobj_hpath sensor/sensorValueD sensor user + " + set aliasProperties " + $scobj_hpath Tsample sensor _sensor_Tsample + $scobj_hpath ctrlLp1_value sensor _sensor_ctrlLp1_value + $scobj_hpath ctrlLp2_value sensor _sensor_ctrlLp2_value + $scobj_hpath timStamp sensor _sensor_timStamp + $scobj_hpath sensorValueA sensor _sensor_sensorValueA + $scobj_hpath sensorValueB sensor _sensor_sensorValueB + $scobj_hpath sensorValueC sensor _sensor_sensorValueC + $scobj_hpath sensorValueD sensor _sensor_sensorValueD + " + # from nexus.dic file + # tc1_sensor_Tsample = /entry1,NXentry/sample,NXsample/tc1,NXenvironment/sensor,NXsensor/SDS Tsample -type NX_FLOAT32 -rank 1 -dim {-1} + # tc1_sensor_value = /entry1,NXentry/sample,NXsample/tc1,NXenvironment/sensor,NXsensor/SDS ctrlLp1_value -type NX_FLOAT32 -rank 1 -dim {-1} + if { $::scobj::ls336::ls336_LSmodel == 336 } { + # $scobj_hpath sensor/sampleSensor sensor user + set nxProperties " + $scobj_hpath sensor NXsensor spy + $scobj_hpath sensor/Tsample sensor user + $scobj_hpath sensor/ctrlLp1_value sensor user + $scobj_hpath sensor/ctrlLp2_value sensor user + $scobj_hpath sensor/sensorValueA sensor user + $scobj_hpath sensor/sensorValueB sensor user + $scobj_hpath sensor/sensorValueC sensor user + $scobj_hpath sensor/sensorValueD sensor user + " + # $scobj_hpath sampleSensor _sensor_sampleSensor + set aliasProperties " + $scobj_hpath Tsample sensor _sensor_Tsample + $scobj_hpath ctrlLp1_value sensor _sensor_ctrlLp1_value + $scobj_hpath ctrlLp2_value sensor _sensor_ctrlLp2_value + $scobj_hpath sensorValueA sensor _sensor_sensorValueA + $scobj_hpath sensorValueB sensor _sensor_sensorValueB + $scobj_hpath sensorValueC sensor _sensor_sensorValueC + $scobj_hpath sensorValueD sensor _sensor_sensorValueD + " + } + foreach {rootpath hpath klasse priv} $nxProperties { + hsetprop $rootpath/$hpath klass $klasse + hsetprop $rootpath/$hpath privilege $priv + hsetprop $rootpath/$hpath control true + hsetprop $rootpath/$hpath data true + hsetprop $rootpath/$hpath nxsave true + } + hsetprop $scobj_hpath type part + hsetprop $scobj_hpath/sensor type part + foreach {rootpath node groupy myalias} $aliasProperties { + hsetprop $scobj_hpath/$groupy/$node nxalias $tempobj$myalias + hsetprop $scobj_hpath/$groupy/$node mutable true + hsetprop $scobj_hpath/$groupy/$node sdsinfo ::nexus::scobj::sdsinfo + } + + hsetprop $scobj_hpath privilege spy + # call hinitprops from script_context_util.tcl which initialises the hdb properties required + # for generating the GumTree interface and saving data for script context objects (hdf file) + # @param scobj, name of script context object (path to a node) + # @param par, optional parameter (name of the node variable) + + # Changed ffr 20100625 due to change in SICServer + #::scobj::hinitprops $tempobj/sensor setpoint1 + #::scobj::hinitprops $tempobj/sensor setpoint2 + + # Problem: Does not write non-numbers to the hdf file unless told differently - but how? + # How can I make it write the NAME (or any character/string variable) to the hdf file? + # ::scobj::hinitprops $tempobj/sensor sampleSensor + + ansto_makesctdrive ${tempobj}_driveable $scobj_hpath/sensor/setpoint1 $scobj_hpath/sensor/ctrlLp1_value $sct_controller + ansto_makesctdrive ${tempobj}_driveable2 $scobj_hpath/sensor/setpoint2 $scobj_hpath/sensor/ctrlLp2_value $sct_controller + + # initialise the device + ls336_init $sct_controller $scobj_hpath + puts "Lakeshore $::scobj::ls336::ls336_LSmodel temperature controller ready at /sample/$tempobj (Driver 2010-06-25)" + } message ]} { + return -code error "in mk_sct_lakeshore_336 $message" + } + } + # endproc mk_sct_lakeshore_336 sct_controller klasse tempobj tol ls336_LSmodel + + namespace export mk_sct_lakeshore_336 +} +# end of namespace mk_sct_lakeshore_336 + +## +# @brief add_ls336() adds a scriptcontext object for a Lakeshore 336 o 340 temperature controller +# and makes it available to SICServer +# @param name short name for the temperature controller scriptcontext object (typ. tc1 or tc2) +# @param IP IP address of the device (e.g. IP of moxabox that hooks up to the Lakeshore 336) +# @param port port number on the moxabox (typ. 4001, 4002, 4003, or 4004) +# @param tol temperature tolerance in Kelvin (typ. 1) +# @return nothing (well, the sct object) +proc add_sct_ls336 {name IP port terminator {_tol1 1.0} {_tol2 1.0} {_verbose 0} } { + # ffr 2009-11-09, Don't create a temperature controller for the script validator, this causes the + # lakeshore to lock up. + # NOTE: I put this outside the catch block because "return" raises an exception + if {[SplitReply [environment_simulation]]} { + return + } + if {[ catch { + set _ls336_LSmodel 336 + puts "\nadd_ls336: makesctcontroller $name std ${IP}:$port for Lakeshore model $_ls336_LSmodel" + makesctcontroller sct_ls336_$name std ${IP}:$port $terminator + mk_sct_lakeshore_336 sct_ls336_$name environment $name $_ls336_LSmodel $_tol1 $_tol2 $_verbose + makesctemon $name /sics/$name/emon/monMode_Lp1 /sics/$name/emon/isInTolerance_Lp1 /sics/$name/emon/errhandler + # set m2 "_2" + # makesctemon $name$m2 /sics/$name/emon/monMode_Lp2 /sics/$name/emon/isInTolerance_Lp2 /sics/$name/emon/errhandler + } message ]} { + return -code error "in add_ls336: $message" + } +} + +namespace import ::scobj::ls336::* diff --git a/site_ansto/instrument/config/environment/temperature/sct_lakeshore_340.tcl b/site_ansto/instrument/config/environment/temperature/sct_lakeshore_340.tcl new file mode 100644 index 00000000..9724162f --- /dev/null +++ b/site_ansto/instrument/config/environment/temperature/sct_lakeshore_340.tcl @@ -0,0 +1,2108 @@ +# Define procs in ::scobj::xxx namespace +# MakeSICSObj $obj SCT_ +# The MakeSICSObj cmd adds a /sics/$obj node. NOTE the /sics node is not browsable. + +## +# /*-------------------------------------------------------------------------- +# L A K E S H O R E 3 x x S E R I E S D R I V E R +# +# @file: This file contains the implementation of a driver for the +# Lakeshore 336 and 340 Temperature controller implemented as a scriptcontext +# object in TCL. +# +# @author: Arndt Meier, ANSTO, 2009-08-05 +# @brief: driver for Lakeshore 336 and 340 Temperature Controller (in TCL) +# @version: 20100520 for sics2_4 +# +# known bugs/limitations: lacks testing of ramping,alarm_limit compliance, +# some commands issued at initialisation are not honored, +# device and alarm reset not working correctly (nodes with no values). +# Namespace conflict: If both a model 336 and 340 are run at the same time, then +# the model 340 tries to access nodes such as outMode3 that only exist in the +# model 336 which segfaults SICServer. The problem is likely due to both models +# using the same namespace, but I have not figured out yet how to give the +# namespace a dynamic name specific to the model. +# Workaround: use 2 copies of this source code which are identical except for +# the literal name of the proc add_ls336 vs.340 and the +# namespace eval ::scobj::ls336 vs 340 +# ----------------------------------------------------------------------------*/ + +# Notes +# Hdb nodes which report data readings should have a "get" script attached to the +# "read" property of the node. This ensures that we can update the reading on demand +# which is necessary for logging data. +# +# Hdb nodes which report parameters which don't change after the driver has +# connected/reconnected to the device should be initialised with a call to the queue +# subcommand of the sct controller, Eg. +# sct queue /sics/path progress read +# +# If there are a large number of settings which need to be reported, and the device +# lets you get the read these setting with a single transaction then you can create +# a status polling object which fetches the settings and updates a given list of hdb +# nodes. + + +# Default temperature controller parameters +namespace eval ::scobj::ls340 { + # Some global variables that are useful as default initilisation values or for tracking + # Name of the scriptcontext object created from calling this driver - an instrument may use more than one + set ls340_sct_obj_name "UNKNOWN" + # What Lakeshore model are we dealing with? 336 or 340? + set ls340_LSmodel "UNKNOWN" + # Last query and write command sent to the device - for debugging and other tasks + set ls340_lastQueryCmd " " + set ls340_lastWriteCmd " " + # provide a global variable holding the path to the nodes + set ls340_path2nodes "/sample/tc1" + # terminator string for serial communication (empty for ls340, taken car of with the COMM command) + # obsolete - handled by sct object - set ls340_term "" + + # Variables that are identical to node names but are needed internally as well + # temperature difference between setpoint and actual temperature at which the + # heater changes from idle to driving + set ls340_driveTolerance1 2 + set ls340_driveTolerance2 2 + # Next 2 parameters are supported by LS model 340 only + # ctrl loop 1 settle parameter, threshold=allowable band around setpoint (0,..,100) + set ls340_settleThr 30 + # ctrl loop 1 settle parameter, settle time in seconds + set ls340_settleTime 30 + # default Heater Range (0,..,5) zero is off, hence the least dangerous + set ls340_range 0 + # upper and lower temperature limit in Kelvin + set ls340_upperlimit 500.0 + set ls340_lowerlimit 4.0 + # temperature units are Kelvin + set ls340_tempUnits "K" + # ls340 status byte + set ls340_statusByte -1 + # enable extra logging - can produce huge stout****.log file in /usr/local/sics/log/ + set ls340_verbose 0 + # a list of available sensors (not all may be connected/active) + set this_sensorlist [list A B C D] + # a list of controler loops + set this_controlerlist [list 1 2] + # set device ID to unknown + set this_sDeviceID "Unknown_Device" + # set self-test result to unknown + set this_selfTestResult -1 + # status of input channels - unknown at startup + set ls340_inputStatusA "UNKNOWN" + set ls340_inputStatusB "UNKNOWN" + set ls340_inputStatusC "UNKNOWN" + set ls340_inputStatusD "UNKNOWN" + set ls340_inpSetupA "UNKNOWN" + set ls340_inpSetupB "UNKNOWN" + set ls340_inpSetupC "UNKNOWN" + set ls340_inpSetupD "UNKNOWN" + set ls340_input4CtrlLp1 "0" + set ls340_input4CtrlLp2 "0" + set ls340_input4CtrlLp3 "0" + set ls340_input4CtrlLp4 "0" + set ls340_sampleSensor "UNKNOWN" + set timeInSecsSince2000 0 + + set alarm_Limit_LoA $ls340_lowerlimit + set alarm_Limit_LoB $ls340_lowerlimit + set alarm_Limit_LoC $ls340_lowerlimit + set alarm_Limit_LoD $ls340_lowerlimit + set alarm_Limit_HiA $ls340_upperlimit + set alarm_Limit_HiB $ls340_upperlimit + set alarm_Limit_HiC $ls340_upperlimit + set alarm_Limit_HiD $ls340_upperlimit + set checkAlarmLimitsA 1 + set checkAlarmLimitsB 1 + set checkAlarmLimitsC 1 + set checkAlarmLimitsD 1 + + set tc_dfltURL ca5-[instname] + array set moxaPortMap {1 4001 2 4002 3 4003 4 4004} + +########### Initialisation ############################# + +## +# Initialise the ls340: +# Checks the device ID, resets the device, checks the self-test, sets the communication +# protocol, sets the default alarm levels, heater range, settle threshold and tolerance. +# @param sct_controller the controller object created for this driver +# @param tc_root string variable holding the path to the object's base node in sics +# @return 0, always +proc ls340_init {sct_controller tc_root} { + if {[ catch { + # Define a constant time offset for later logging of data with timStamp + # counting from 1st Jan 2000 00:00hrs + # clock command does not work in tcl8.5 ?! + set ::scobj::ls340::timeInSecsSince2000 [clock scan 20000101T000000A] + # set ::scobj::ls340::timeInSecsSince2000 1000 + # set the communication protocol: terminator , bps 9600 baud, 7 data bits + + # 1 stop bit + odd parity bit + if { $::scobj::ls340::ls340_LSmodel == 340 } { + # puts "setting serial communication parameters" + hset $tc_root/other/cfgProtocol_comm "COMM 1,5,1" + } + # Query the device ID - are we talking to a Lakeshore 340 or 336? +# puts "sending: $sct_controller queue $tc_root/other/deviceID_idn progress read" +# $sct_controller queue $tc_root/other/deviceID_idn progress read + # !! Not working properly yet - needs fixing + #sct send "*IDN?" + #sct data [$sct_controller result] + #puts "rdValDrct(): result is $data" + #set data [hget $tc_root/other/deviceID_idn] +# puts "set deviceID_idn (hval $tc_root/other/deviceID_idn)" + #set data [hval $tc_root/other/deviceID_idn] + #set ::scobj::ls340::this_sDeviceID data +# puts "sct_lakeshore340.tcl: connected to device $::scobj::ls340::this_sDeviceID" + # reset the device to have it in a defined state +# hset $tc_root/other/reset_rst {*RST} + # Queue the Read Device Status-byte command so we can access the result in the + # corresponding node later +# puts "sending: $sct_controller queue $tc_root/other/statusByte progress read" +# $sct_controller queue $tc_root/other/statusByte progress read + if { $::scobj::ls340::ls340_LSmodel == 340 } { + hset $tc_root/other/cfgProtocol_comm "COMM 1,5,1" + } + # Was the self-test successful? + $sct_controller queue $tc_root/other/selftest progress read + set ::scobj::ls340::this_selfTestResult [hval $tc_root/other/selftest] + if {$::scobj::ls340::this_selfTestResult == 0} { + puts "sct_lakeshore340.tcl: Lakeshore $::scobj::ls340::ls340_LSmodel self-test ok." + } else { + puts "sct_lakeshore340.tcl: The Lakeshore $::scobj::ls340::ls340_LSmodel failed its self-test." + } + # Set the default upper and lower temperature alarm limits in Kelvin + foreach iSensor $::scobj::ls340::this_sensorlist { + hsetprop $tc_root/input/alarm_Limits_$iSensor units "K" + hsetprop $tc_root/input/alarm_Limits_$iSensor units "K" + } + # Set the default heater range (1,..,5) + if { $::scobj::ls340::ls340_LSmodel == 340 } { +# hset $tc_root/heater/heaterRange $::scobj::ls340::ls340_range + } else { +# hset $tc_root/heater/heaterRange_1 $::scobj::ls340::ls340_range +# hset $tc_root/heater/heaterRange_2 $::scobj::ls340::ls340_range + } + # Set the default settle parameters + if { $::scobj::ls340::ls340_LSmodel == 340 } { + hset $tc_root/control/settleThr_Loop_1 $::scobj::ls340::ls340_settleThr + hset $tc_root/control/settleTime_Loop_1 $::scobj::ls340::ls340_settleTime + hsetprop $tc_root/control/settleTime_Loop_1 units "s" + puts "Make sure INTERFACE : SERIAL : TERMINATOR is set correctly" + } + # Set the default tolerances for the setpoint temperatures + hset $tc_root/control/tolerance1 $::scobj::ls340::ls340_driveTolerance1 + hset $tc_root/control/tolerance2 $::scobj::ls340::ls340_driveTolerance2 + } message ]} { + return -code error "in ls340_init: $message" + } +} + +############# Reading polled nodes ################################### + +## +# @brief Sends a query command to the device via a read node formalism +# @param tc_root The path to the root of the node +# @param nextState The next function to call after this one (typically 'rdValue' +# to read the response from the device) +# @param cmd The query command to be send to the device (written to the +# node data value) +# @param idx indicates which control loop or which input channel +# the command belongs to +# @return nextState The next function to call after this one (typically 'rdValue') +proc getValue {tc_root nextState cmd idx} { + if {[ catch { + if [hpropexists [sct] geterror] { + hdelprop [sct] geterror + } + if { 0 == [string compare -length 7 $cmd "InpSample"] } { + # we are reading from a pseudo-node where there is no direct representation + # in the device + # puts "getValue(InpSample)" + set ::scobj::ls340::ls340_lastQueryCmd $cmd + #sct send $cmd$::scobj::ls340::ls340_term + } elseif { 0 == [string compare -length 7 $cmd "CRVHDR?"] } { + # In the case of calCurveHdr we need an extra parameter with the command sent + set nodename $tc_root/input/inpCalCurve_$idx + set whichCurve [hval $nodename] + if {$whichCurve < 1 | $whichCurve > 58} { + # Quering 'CRVHDR? 0' is an invalid curve index. The right answer is 'noCurve' + # However, cannot do the following 2 lines because then the nextState rdFunc has no value it can get + # set nodename $tc_root/input/calCurveHdr_$idx + # hset $nodename "noCurve" + # workaround: set whichCurve to 59 which would normally be empty and fix things up in rdValue + set whichCurve 59 + } + set ::scobj::ls340::ls340_lastQueryCmd "$cmd$whichCurve" + sct send $cmd$whichCurve + } else { + set ::scobj::ls340::ls340_lastQueryCmd "$cmd" + sct send $cmd + } + } message ]} { + return -code error "in getValue: $message. Last query command: $::scobj::ls340::ls340_lastQueryCmd" + } + return $nextState +} + +## +# @brief Reads the value of a read-node typically following a query command sent to the device +# rdValue is the default nextState for getValue() and setValue() +# @param idx indicates which control loop or which input channel the command belongs to +# @return idle Always returns system state idle - command sequence completed. + proc rdValue {idx} { + if {[ catch { + set data [sct result] + # puts "rdValue(): result is $data" + # broadcast rdValue "rdValue(): result is $data" + + # Check if an invalid curveHeader was queried and set the result to 'noCurve' + if { 0 == [string compare -length 10 $::scobj::ls340::ls340_lastQueryCmd "CRVHDR? 59"] } { + set data "noCurve" + if {$idx > 0 && $idx <= 4} { + switch $idx { + 1 {set idx "A"} + 2 {set idx "B"} + 3 {set idx "C"} + 4 {set idx "D"} + } + } + if {$idx == "A" || $idx == "B" || $idx == "C" || $idx == "D"} { + set nodename $::scobj::ls340::ls340_path2nodes/input/calCurveHdr_$idx + hset $nodename "noCurve" + } + } + # Continue as normal + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in rdValue: Last query command: $::scobj::ls340::ls340_lastQueryCmd" + sct geterror $data + } + default { + if {$data != [sct oldval]} { + sct oldval $data + sct update $data + sct utime readtime + + # We are done here - below are some special cases where we keep + # track of some global variables and stuff for the nodes in /sensor/ + + # Keep track of input Setup - tells us whether any channels are disabled + # Lakeshore Model 336 and 340 differ in that this information is requested via + # INTYPE? and INSET? commands, respectively, and with the LS336 not having + # the INSET? command + set mustUpdate 0 + if { 0 == [string compare -length 3 $::scobj::ls340::ls340_LSmodel "336"] && 0 == [string compare -length 7 $::scobj::ls340::ls340_lastQueryCmd "INTYPE?"] } { + set mustUpdate 1 + } + if {0 == [string compare -length 6 $::scobj::ls340::ls340_lastQueryCmd "INSET?"] || $mustUpdate == 1 } { + switch $idx { + "A" {set ::scobj::ls340::ls340_inpSetupA $data} + "B" {set ::scobj::ls340::ls340_inpSetupB $data} + "C" {set ::scobj::ls340::ls340_inpSetupC $data} + "D" {set ::scobj::ls340::ls340_inpSetupD $data} + } + } + + # Keep track of the time at which data was observed ** NXsensor allows float values only, not text + if {0 == [string compare -length 9 $::scobj::ls340::ls340_lastQueryCmd "DATETIME?"] } { + # DATETIME «MM»,«DD»,«YYYY»,«HH»,«mm»,«SS»,«sss» Configure Date and Time. + regsub -all {,} $data {:} data + # data=08:31:2009:12:58:58:910 + set separator {:} + set amonth [::scobj::ls340::getValFromString $data 0 $separator] + set aday [::scobj::ls340::getValFromString $data 1 $separator] + set ayear [::scobj::ls340::getValFromString $data 2 $separator] + set ahour [::scobj::ls340::getValFromString $data 3 $separator] + set amin [::scobj::ls340::getValFromString $data 4 $separator] + set asec [::scobj::ls340::getValFromString $data 5 $separator] + # use this code block to report time in seconds since 1/1/2000 + set aA "A" + set aT "T" + set timeString $ayear$amonth$aday$aT$ahour$amin$asec$aA + set timeInSecs [clock scan $timeString] + set timeInSecs [expr {$timeInSecs - $::scobj::ls340::timeInSecsSince2000}] + hset $::scobj::ls340::ls340_path2nodes/sensor/timStamp $timeInSecs + if {1==0} { + # if we could write a string value to NXsensor, then this date-time format would be nicer... + set sColon ":" + set sSpace " " + set isoTimeString $ayear$amonth$aday$sSpace$ahour$sColon$amin$sColon$asec + hset $::scobj::ls340::ls340_path2nodes/sensor/timStamp $isoTimeString + } + } + } + } + } + } message ]} { + return -code error "in rdValue: $message. Last query command: $::scobj::ls340::ls340_lastQueryCmd" + } + return idle + } + +## +# @brief Reads the values of the alarm_Limit_* nodes. Needs extra code compared to proc 'rdValue' +# because multiple variables are represented by only one concatenated command in the device command list. +# It is preceeded by a call to getValue() and a replacement for rdValue. +# @param idx indicates which control loop or which input channel the command belongs to +# @return idle Always returns system state idle - command sequence completed. +proc rdAlarmVal {iSensor} { + if {[ catch { + set data [sct result] + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in rdAlarmVal: Last query command: $::scobj::ls340::ls340_lastQueryCmd" + sct geterror $data + } + default { + if {$data != [sct oldval]} { + sct oldval $data + sct update $data + sct utime readtime + # ALARM? returns «off/on», «source», «high value», «low value», «latch enable», «relay» + # idx points to the position of the last coma in the string + # 1, 1, 500.00, 0.00, 0, 0 + #puts "rdAlarmVal: iSensor= $iSensor ;; data= $data " + set separator {,} + set onOff [::scobj::ls340::getValFromString $data 0 $separator] + if { $::scobj::ls340::ls340_LSmodel == 340 } { + #set source [::scobj::ls340::getValFromString $data 1 $separator] + set hiVal [::scobj::ls340::getValFromString $data 2 $separator] + set loVal [::scobj::ls340::getValFromString $data 3 $separator] + #set latch [::scobj::ls340::getValFromString $data 4 $separator] + #set relay [::scobj::ls340::getValFromString $data 5 $separator] + } else { + set hiVal [::scobj::ls340::getValFromString $data 1 $separator] + set loVal [::scobj::ls340::getValFromString $data 2 $separator] + #set deadbd [::scobj::ls340::getValFromString $data 3 $separator] + #set latch [::scobj::ls340::getValFromString $data 4 $separator] + #set audible [::scobj::ls340::getValFromString $data 5 $separator] + #set visible [::scobj::ls340::getValFromString $data 6 $separator] + } + #puts "alarm-data= $data ;; onOff= $onOff ;; source= $source ;; hiVal= $hiVal ;; loVal= $loVal ;; latch= $latch ;; relay= $relay" + + switch $iSensor { + "A" { + set ::scobj::ls340::checkAlarmLimitsA $onOff + if {$loVal >= 0} {set ::scobj::ls340::alarm_Limit_LoA $loVal} + if {$hiVal >= 0} {set ::scobj::ls340::alarm_Limit_HiA $hiVal} + } + "B" { + set ::scobj::ls340::checkAlarmLimitsB $onOff + if {$loVal >= 0} {set ::scobj::ls340::alarm_Limit_LoB $loVal} + if {$hiVal >= 0} {set ::scobj::ls340::alarm_Limit_HiB $hiVal} + } + "C" { + set ::scobj::ls340::checkAlarmLimitsC $onOff + if {$loVal >= 0} {set ::scobj::ls340::alarm_Limit_LoC $loVal} + if {$hiVal >= 0} {set ::scobj::ls340::alarm_Limit_HiC $hiVal} + } + "D" { + set ::scobj::ls340::checkAlarmLimitsD $onOff + if {$loVal >= 0} {set ::scobj::ls340::alarm_Limit_LoD $loVal} + if {$hiVal >= 0} {set ::scobj::ls340::alarm_Limit_HiD $hiVal} + } + } + } + } + } + } message ]} { + return -code error "in rdAlarmVal: $message. Last query command: $::scobj::ls340::ls340_lastQueryCmd" + } + return idle +} + + +## +# @brief rdCfgValue() does what rdValue() does but it replaces /sensor/ctrl_Loop_1 value +# with a more human readable version of the information from /control/config_Loop_1. This +# function is called for the CSET* and OUTMODE* commands. +# @param idx indicates which control channel the command acts upon +# @return idle Always returns system state idle - command sequence completed. + proc rdCfgValue {idx} { + if {[ catch { + set data [sct result] + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in rdCfgValue: Last query command: $::scobj::ls340::ls340_lastQueryCmd" + sct geterror $data + } + default { + set oval [sct oldval] + if { $data != [sct oldval] } { + # puts "rdCfgValue: idx:$idx Last query command: $::scobj::ls340::ls340_lastQueryCmd" + sct oldval $data + sct update $data + sct utime readtime + # update the /sensor/ctrl_Loop_1 and /sensor/ctrl_Loop_2 manual nodes which show + # DISABLED if the control channel is disabled + set ctrl_Loop_Txt "enabled, Input " + set input "A" + set separator {,} + if { $::scobj::ls340::ls340_LSmodel == 340 } { + # LS340, device command CSET* + set input [::scobj::ls340::getValFromString $data 0 $separator] + set units [::scobj::ls340::getValFromString $data 1 $separator] + set onOff [::scobj::ls340::getValFromString $data 2 $separator] + #set powerup [::scobj::ls340::getValFromString $data 3 $separator] + if {$onOff == 0} { + set ctrl_Loop_Txt "DISABLED, Input " + } + set in ", in " + switch -glob -- $units { + "1*" {set units "Kelvin"} + "2*" {set units "Celsius"} + "3*" {set units "sensor units"} + default {set units "UNKNOWN units"} + } + set ctrl_Loop_Txt $ctrl_Loop_Txt$input$in$units + # puts "ls340 rdCfgValue() idx: $idx, data: $data, units: $units, ctrl_Loop_Txt: $ctrl_Loop_Txt" + } else { + # LS 336, device command outMode_* + set mode [::scobj::ls340::getValFromString $data 0 $separator] + set input [::scobj::ls340::getValFromString $data 1 $separator] + # set powerup [::scobj::ls340::getValFromString $data 2 $separator] + if {$mode == 0} { + set ctrl_Loop_Txt " DISABLED, Input " + } + set myInp $input + switch -glob -- $myInp { + "0*" {set input "None"} + "1*" {set input "A"} + "2*" {set input "B"} + "3*" {set input "C"} + "4*" {set input "D"} + default {set input "UNKNOWN"} + } + append ctrl_Loop_Txt $input + set myMode $mode + switch -glob -- $myMode { + "0*" {set mode ", control off"} + "1*" {set mode ", PID closed loop"} + "2*" {set mode ", zone control"} + "3*" {set mode ", open loop"} + "4*" {set mode ", monitor out"} + "5*" {set mode ", warmup supply"} + default {set mode ", UNKNOWN control"} + } + append ctrl_Loop_Txt $mode + # puts "rdCfgValue() idx: $idx, data: $data, output: $idx, ctrl_Loop_Txt: $ctrl_Loop_Txt" + # puts "rdCfgValue setting ls340_input4CtrlLp idx:$idx, input:$input, myInp:$myInp" + } + set nodename $::scobj::ls340::ls340_path2nodes/sensor/ctrl_Loop_$idx + hset $nodename $ctrl_Loop_Txt + # Keep track of which inputs are used for the control loops + # puts "rdCfgValue() idx: $idx, data: $data, input:$input, ctrl_Loop_Txt: $ctrl_Loop_Txt" + switch $idx { + "1" {set ::scobj::ls340::ls340_input4CtrlLp1 $input} + "2" {set ::scobj::ls340::ls340_input4CtrlLp2 $input} + "3" {set ::scobj::ls340::ls340_input4CtrlLp3 $input} + "4" {set ::scobj::ls340::ls340_input4CtrlLp4 $input} + } + # puts "rdCfgValue ls340_input4CtrlLp:$::scobj::ls340::ls340_input4CtrlLp2 idx:$idx, input:$input, myInp:$myInp" + } + } + } + } message ]} { + return -code error "in rdCfgValue: $message. Last query command: $::scobj::ls340::ls340_lastQueryCmd" + } + return idle + } + + + +## +# @brief Reads the values of the RDGST* nodes. Needs extra code compared to proc 'rdValue' +# because it interprets the bit coded results and updates affected node values accordingly +# It is preceeded by a call to getValue() and is a replacement for rdValue(). +# @param iSensor indicates which input channel the command belongs to +# @return idle Always returns system state idle - command sequence completed. +proc rdBitValue {iSensor} { + if {[ catch { + set data [sct result] + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in rdBitValue: Last query command: $::scobj::ls340::ls340_lastQueryCmd" + sct geterror $data + } + default { + if {$data != [sct oldval]} { + sct oldval $data + sct update $data + sct utime readtime + # RDGST? A/B/C/D Read input status returns an integer with the following meaning + # Bit Weighting StatusIndicator + # 0 1 invalid reading + # 1 2 old reading (not an error, ignored in ls336) + # 4 16 temp underrange + # 5 32 temp overrange + # 6 64 sensor units zero + # 7 128 sensor units overrange + set sValue "" + # Remove any leading zeros from string 'data' which ought to represent an integer, + # ("001" => "1", "096" => "96") else the string may be misinterpreted as an octal number. + if {0 == [string compare -length 1 $data "0"] } { + set data [string range $data 1 5] + } + if {0 == [string compare -length 1 $data "0"] } { + set data [string range $data 1 5] + } + set i $data + # puts "rdBitValue(): iSensor:$iSensor, data:$data" + set bitValue [expr {$i & 1}] + if {$bitValue == 1} {set sValue "Invalid reading, "} + #set i [expr $i >> 1] + # set bitValue [expr $i & 1] + # if {$bitValue == 1} {set sValue "old reading"} + set i [expr {$i >> 4}] + set bitValue [expr {$i & 1}] + if {$bitValue == 1} { set sValue [append sValue "temp underrange, "] } + set i [expr {$i >> 1}] + set bitValue [expr {$i & 1}] + if {$bitValue == 1} { set sValue [append sValue "temp overrange, "] } + set i [expr {$i >> 1}] + set bitValue [expr {$i & 1}] + if {$bitValue == 1} { set sValue [append sValue "sensor units zero, "] } + set i [expr {$i >> 1}] + set bitValue [expr {$i & 1}] + if {$bitValue == 1} { set sValue [append sValue "sensor units overrange, "] } + if { [string length $sValue] < 4 } { + set sValue "ok" + } + #puts "rdBitValue(): iSensor:$iSensor, data:$data, sValue:$sValue " + switch $iSensor { + "A" {set ::scobj::ls340::ls340_inputStatusA $sValue} + "B" {set ::scobj::ls340::ls340_inputStatusB $sValue} + "C" {set ::scobj::ls340::ls340_inputStatusC $sValue} + "D" {set ::scobj::ls340::ls340_inputStatusD $sValue} + } + } + } + } + } message ]} { + return -code error "in rdBitValue: $message. Last query command: $::scobj::ls340::ls340_lastQueryCmd" + } + return idle +} + + +## +# @brief rdInpValue() does what rdValue() does but it replaces the input's Kelvin Reading +# with the message from RDGST? if the input is invalid. +# @param idx indicates which input channel the command acts upon +# @return idle Always returns system state idle - command sequence completed. + proc rdInpValue {idx} { + if {[ catch { + set data [sct result] + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in rdInpValue: Last query command: $::scobj::ls340::ls340_lastQueryCmd" + sct geterror $data + } + default { + if {$idx == "A"} { + # update only once per poll cycle needed + # Read the sics node sampleSensor to + # determine which input represents the sample temperature + set nodename $::scobj::ls340::ls340_path2nodes/sensor/sampleSensor + set ::scobj::ls340::ls340_sampleSensor [hval $nodename] + } + if { $data != [sct oldval] } { + sct oldval $data + sct update $data + sct utime readtime + # update the /sensor/ctrlLp1_value, /sensor/ctrlLp2_value, and /sensor/Tsample manual nodes which show + # zero or DISABLED if the input channel is disabled + + set inputStatus $::scobj::ls340::ls340_inputStatusA + set inpEnabled $::scobj::ls340::ls340_inpSetupA + switch $idx { + "B" {set inpEnabled $::scobj::ls340::ls340_inpSetupB} + "C" {set inpEnabled $::scobj::ls340::ls340_inpSetupC} + "D" {set inpEnabled $::scobj::ls340::ls340_inpSetupD} + } + if {0 == [string compare -length 1 $inpEnabled "0"] } { + # puts "Inp$idx is DISABLED" + set inputStatus "DISABLED" + } else { + # RDGST? if RDGST is NOT zero -- or else the Kelvin reading for that input. + switch $idx { + "A" {set inputStatus $::scobj::ls340::ls340_inputStatusA} + "B" {set inputStatus $::scobj::ls340::ls340_inputStatusB} + "C" {set inputStatus $::scobj::ls340::ls340_inputStatusC} + "D" {set inputStatus $::scobj::ls340::ls340_inputStatusD} + } + } + # RDGST? is 'ok' or 'UNKNOWN' -> it is okay to show the Kelvin reading we have + set value $data + if { [string length $inputStatus] >= 8 } { + # The status of the input channel meets an error condition - show the error message instead + # puts "rdInpValue() idx: $idx, data: $data, invalid: $inputStatus" + set value -1.0 + } + if {$idx == $::scobj::ls340::ls340_sampleSensor} { + hset $::scobj::ls340::ls340_path2nodes/sensor/Tsample $value + } + # Switch command did not work reliably below, hence a less elegant but + # functional if-elseif-else contruct is implemented instead + if {$idx == $::scobj::ls340::ls340_input4CtrlLp1} { + hset $::scobj::ls340::ls340_path2nodes/sensor/ctrlLp1_value $value + } elseif {$idx == $::scobj::ls340::ls340_input4CtrlLp2} { + hset $::scobj::ls340::ls340_path2nodes/sensor/ctrlLp2_value $value + } elseif {$idx == $::scobj::ls340::ls340_input4CtrlLp3} { + hset $::scobj::ls340::ls340_path2nodes/sensor/ctrlLp3_value $value + } elseif {$idx == $::scobj::ls340::ls340_input4CtrlLp4} { + hset $::scobj::ls340::ls340_path2nodes/sensor/ctrlLp4_value $value + } + } + } + } + } message ]} { + return -code error "in rdInpValue: $message. Last query command: $::scobj::ls340::ls340_lastQueryCmd" + } + return idle + } + + + +## +# @brief Does what rdValue() does plus it checks if the temperature is in tolerance. +# inTolerance is the default nextState after getValue() for read node other/deviceID_idn. +# If the LS340 is switched off, temperature is reported to be in tolerance so that +# slow return to ambient temperature can be carried out (see comments in Julabo driver +# for reasoning). +# @param CtrlLoopIdx indicates which control loop the command acts upon +# @return idle Always returns system state idle - command sequence completed. +proc inTolerance {CtrlLoopIdx} { + if {[ catch { + set tc_root $::scobj::ls340::ls340_path2nodes + set data [sct result] + set oldvalue [sct oldval] + # puts "inTolerance(): data=$data oldvalue=$oldvalue idx:$CtrlLoopIdx" + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in inTolerance: Last query command: $::scobj::ls340::ls340_lastQueryCmd" + sct geterror $data + } + default { + if {$data != $oldvalue} { + # timecheck is an internal node that is set to the current time at the + # start of measurement and it is reset to current time every time the tolerance + # test for a control loop decides that it is outside tolerance. With the + # ls366 we need 2 such variables, one for each control loop. + if {$oldvalue == "UNKNOWN"} { + sct utime timecheck + sct utime timecheck2 + sct utime currtime + } + sct oldval $data + sct update $data + sct utime readtime + } + } + } + sct utime currtime + # puts "inTolerance $::scobj::ls340::ls340_sct_obj_name CtrlLoopIdx:$CtrlLoopIdx data:$data" + # now update the manual nodes reporting whether the actual temperature + # is within tolerance of the corresponding setpoint + for {set CtrlLoopIdx 1} {$CtrlLoopIdx < 3} {incr CtrlLoopIdx 1} { + set cmpIdString [string compare -length 13 $data "LSCI,MODEL340"] + if {$cmpIdString != 0 } { + set cmpIdString [string compare -length 13 $data "LSCI,MODEL336"] + } + if {$cmpIdString == 0 } { + # set nodename $tc_root/control/config_Loop_$CtrlLoopIdx + # set iSensor [hval $nodename] + # set iSensor [string range $iSensor 0 0] + set iSensor [getCorrespondingInputSensor $CtrlLoopIdx] + # puts "inTolerance 2 $::scobj::ls340::ls340_sct_obj_name CtrlLoopIdx:$CtrlLoopIdx data:$data, iSensor:$iSensor" + #puts {set intol [checktol $tc_root [sct readtime] [sct timecheck] $CtrlLoopIdx $iSensor]} + if {[string length $iSensor] == 1} { + if {$CtrlLoopIdx == 1} { + set intol [checktol $tc_root [sct currtime] [sct timecheck] $CtrlLoopIdx $iSensor] + } else { + set intol [checktol $tc_root [sct currtime] [sct timecheck2] $CtrlLoopIdx $iSensor] + } + set nodename $tc_root/sensor/setpoint$CtrlLoopIdx + set setpt [hval $nodename] + set nodename $tc_root/sensor/sensorValue$iSensor + set temp [hval $nodename] + # puts "inTolerance(): comparing sensor/setpoint$CtrlLoopIdx=$setpt with actual sensorValue$iSensor=$temp" + set diff [expr {abs($setpt - $temp)}] + # $::scobj::ls340::ls340_driveTolerance = 0.2 Kelvin + set tol $::scobj::ls340::ls340_driveTolerance1 + if {$CtrlLoopIdx == 2} { + set tol $::scobj::ls340::ls340_driveTolerance2 + } + # puts "inTolerance(): diff=$diff tol=$tol" + # if $diff > $tol + if {$intol==0} { + set nodename $tc_root/emon/monMode_Lp$CtrlLoopIdx + hset $nodename "drive" + if {$CtrlLoopIdx == 1} { + hset $tc_root/status "busy" + if {$::scobj::ls340::ls340_verbose==1} {puts "hset $nodename drive; hset $tc_root/status busy"} + } else { + hset $tc_root/status_Ctrl_Lp2 "busy" + if {$::scobj::ls340::ls340_verbose==1} {puts "hset $nodename drive; hset $tc_root/status_Ctrl_Lp2 busy"} + } + } else { + set nodename $tc_root/sensor/setpoint$CtrlLoopIdx + hsetprop $nodename driving 0 + set nodename $tc_root/emon/monMode_Lp$CtrlLoopIdx + hset $nodename "monitor" + if {$CtrlLoopIdx == 1} { + hset $tc_root/status "idle" + if {$::scobj::ls340::ls340_verbose==1} {puts "hset $nodename idle; hset $tc_root/status monitor"} + } else { + hset $tc_root/status_Ctrl_Lp2 "idle" + if {$::scobj::ls340::ls340_verbose==1} {puts "hset $nodename idle; hset $tc_root/status_Ctrl_Lp2 monitor"} + } + } + } + } else { + # puts "inTolerance(): uuppss - should not go here" + hset $tc_root/emon/monMode_Lp1 "idle" + hset $tc_root/emon/monMode_Lp2 "idle" + hset $tc_root/emon/isInTolerance_Lp1 "inTolerance" + hset $tc_root/emon/isInTolerance_Lp2 "inTolerance" + hset $tc_root/status "idle" + hset $tc_root/status_Ctrl_Lp2 "idle" + } + } + # puts "inTolerance 4 $::scobj::ls340::ls340_sct_obj_name CtrlLoopIdx:$CtrlLoopIdx data:$data" + } message ]} { + return -code error "in inTolerance: $message. Last query command: $::scobj::ls340::ls340_lastQueryCmd" + } + # puts "Leaving inTolerance idx:$CtrlLoopIdx" + return idle +} + + +## +# @brief rdCalValue()) does what rdValue() does plus it resets the value /input/calCurveHdr_$idx forcing that node to refresh +# @param idx indicates which control loop or which input channel the command belongs to +# @return idle Always returns system state idle - command sequence completed. +proc rdCrvValue {idx} { + if {[ catch { + set data [sct result] + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in rdCrvValue: Last query command: $::scobj::ls340::ls340_lastQueryCmd" + sct geterror $data + } + default { + if {$data != [sct oldval]} { + sct oldval $data + sct update $data + sct utime readtime + # The calibration curve has changed. + # Set the curve header name to unknown, thus forcing the respective node to refresh + set tc_root $::scobj::ls340::ls340_path2nodes + set nodename $tc_root/input/calCurveHdr_$idx + hset $nodename "request refresh" + #puts "hset $nodename request refresh" + } + } + } + } message ]} { + return -code error "in rdCrvValue: $message. Last query command: $::scobj::ls340::ls340_lastQueryCmd" + } + return idle +} + + +################## Writing to nodes ########################################### + +## +# @brief Writes a new value to a node and sends the corresponding command to the device. +# @param tc_root string variable holding the path to the object's base node in sics +# @param nextState the next function to call after this one +# @param cmd the command to be send to the device (written to the node data value) +# @param idx indicates which control loop or which input channel the command belongs to +# @return nextState Is typically noResponse as the device does not acknowledge the write request +proc setValue {tc_root nextState cmd {idx ""}} { + # tc_root and idx are not being used - however, don't remove so we can use the + # same calling mask as for setPoint() or other $wrFunc + if {[ catch { + # Some commands have no argument and no matching read command, like reset_rst or alarmResetAll. + # Provide a default value for parameter 'idx' and initialize 'par' to an empty string because [sct target] + # is not an allowed operation in this case. + set par "" + if {$idx != ""} { + set par [sct target] + } + set ::scobj::ls340::ls340_lastWriteCmd "$cmd$par" + # Talking to a pseudo-node (no equivalent command in the device communication) + sct send "$cmd$par" + } message ]} { + return -code error "in setValue: $message. While sending command: $::scobj::ls340::ls340_lastWriteCmd" + } + return $nextState +} + + +## +# @brief Writes a new value to a node but does NOT send the corresponding command to the device. +# @param tc_root string variable holding the path to the object's base node in sics +# @param nextState the next function to call after this one +# @param cmd the command to be send to the device (written to the node data value) +# @param idx indicates which control loop or which input channel the command belongs to +# @return nextState Is typically noResponse as the device does not acknowledge the write request +proc setPseudoValue {tc_root nextState cmd {idx ""}} { + # tc_root and idx are not being used - however, don't remove so we can use the + # same calling mask as for setPoint() or other $wrFunc + if {[ catch { + # Some commands have no argument and no matching read command, like reset_rst or alarmResetAll. + # Provide a default value for parameter 'idx' and initialize 'par' to an empty string because [sct target] + # is not an allowed operation in this case. + set par "" + if {$idx != ""} { + set par [sct target] + } + set ::scobj::ls340::ls340_lastWriteCmd "$cmd$par" + # Talking to a pseudo-node (no equivalent command in the device communication) + # puts "setPseudoValue($tc_root $nextState $cmd $idx)" + #sct send "$cmd$par" + } message ]} { + return -code error "in setPseudoValue: $message. While processing command: $::scobj::ls340::ls340_lastWriteCmd" + } + return $nextState +} + + +## +# @brief Is an empty function just so we can define a next state function after a write operation. +# @return idle Returns the default system state indicating that the device is ready for the next command +proc noResponse {} { + return idle +} + + +## +# @brief Sets the desired temperature for the selected control loop: +# @param tc_root string variable holding the path to the object's base node in sics +# @param nextState next state of the device, typically that is 'noResponse' +# @param cmd string variable containing the device command to change the setpoint +# @param whichCtrlLoop specifies whether the setpoint is for control loop 1 or 2 +# @return nextState +proc setPoint {tc_root nextState cmd whichCtrlLoop} { + if {[ catch { + #puts "executing setPoint ($tc_root $nextState $cmd $whichCtrlLoop)" + #broadcast setPoint "executing setPoint ($tc_root $nextState $cmd $whichCtrlLoop)" + set ns ::scobj::lh45 + set par [sct target] + + # determine the corresponding input sensor + # set whichSensor [getCorrespondingInputSensor $whichCtrlLoop] + #hset $tc_root/status "busy" + set wrStatus [sct writestatus] + if {$wrStatus == "start"} { + # Called by drive adapter + # puts "setPoint(): driving set to 1" + set nodename $tc_root/sensor/setpoint$whichCtrlLoop + hsetprop $nodename driving 1 + } + #puts "setPoint(wrStatus=$wrStatus): sct send $cmd$par" + sct send "$cmd$par" + } message ]} { + return -code error "in setPoint: $message. Last write command: $::scobj::ls340::ls340_lastWriteCmd" + } + return $nextState +} + + +## +# @brief Writes a command directly to the device - device reset and alarm reset commands. +# @param tc_root string variable holding the path to the object's base node in sics +# @param nextState the next function to call after this one +# @param cmd the command to be send to the device (written to the node data value) +# @param idx indicates which control loop or which input channel the command belongs to +# @return nextState Is typically noResponse as the device does not acknowledge the write request +# NOT WORKING CORRECTLY YET +proc sendCmd {tc_root nextState cmd {idx ""}} { + # tc_root and idx are not being used - however, don't remove so we can use the + # same calling mask as for setPoint() or other $wrFunc + if {[ catch { + # Some commands have no argument and no matching read command, like reset_rst or alarmResetAll. + # Provide a default value for parameter 'idx' and initialize 'par' to an empty string because [sct target] + # is not an allowed operation in this case. + set par "" + if {$idx != ""} { + set par [sct target] + } + set ::scobj::ls340::ls340_lastWriteCmd "$cmd$par" + #sct send "$cmd$par" + if { 0 == [string compare -length 4 $cmd "*RST"]} { + sct_controller queue $tc_root/other/selftest progress read + } elseif { 0 == [string compare -length 6 $cmd "ALMRST"]} { + sct_controller queue $tc_root/input/alarmResetAll progress read + } + } message ]} { + return -code error "in setValue: $message. While sending command: $::scobj::ls340::ls340_lastWriteCmd" + } + return $nextState +} + + + +############# functions used with drivable ########################################## + +## +# @brief Implement the checkstatus command for the drivable interface +# +# NOTE: The drive adapter initially sets the writestatus to "start" and will +# only call this when writestatus!="start" + proc drivestatus {tc_root} { + if {[ catch { + if {[sct driving]} { + set retval busy + } else { + set retval idle + } + } message ]} { + return -code error "in drivestatus: $message. Last write command: $::scobj::ls340::ls340_lastWriteCmd" + } + return $retval + } + +## +# @brief Stops driving at current temperature. +# Sets the setpoint to the current temperature of the corresponding input +# @param tc_root string variable holding the path to the object's base node in sics +# @param whichCtrlLoop specifies whether the setpoint is for control loop 1 or 2 +# @return idle Indicates that the device is ready for the next command + proc halt {tc_root whichCtrlLoop} { + # stop driving at current temperature + # determine the corresponding input sensor +# ffr 2009-11-12 TODO what happens if whichSensor is none or UNKOWN, or if whichCtrlLoop > 2, we stay busy forever. + set whichSensor [getCorrespondingInputSensor $whichCtrlLoop] + if {[string length $whichSensor] == 1} { + set sensorValue $tc_root/sensor/sensorValue$whichSensor + if {$whichCtrlLoop <= 2} { + set nodename $tc_root/sensor/setpoint$whichCtrlLoop + hset $nodename [hval $sensorValue] + hsetprop $nodename driving 0 + } + } + return idle + } + +############# Auxiliary functions ########################################## + +## +# @brief Extracts elements from a coma (or other symbol) separated list of values provided in a string +# @param s The string holding a list of values separated by comas +# @param element Index of the element to extract (starts from zero) +# @param separator String holding the separator used in $s, e.g. a coma +# @return returnval String holding the extracted element. String is empty if operation failed. + proc getValFromString {s element separator} { + #puts "getValFromString $s $element $separator" + set startIdx 0 + set endIdx 0 + set eIdx $element + set idx 0 + while {$eIdx > 0} { + set idx [string first $separator $s $startIdx] + if {$idx > 0} { + if { " " == [string range $s $idx $idx]} { + incr idx 1 + } + set startIdx $idx + if {$startIdx > 0} { + incr startIdx 1 + } + } + incr eIdx -1 + } + # startIdx points to the first non-blank character of the value we are interested in + set returnval "" + if {$startIdx < 0} { + return $returnval + } + set endIdx [string first $separator $s $startIdx] + incr endIdx -1 + #puts "startIdx=$startIdx endIdx=$endIdx" + # endIdx points to one character before the next separator or is -1 if it is the + # last element in the string $s + if {$endIdx >= 0} { + set returnval [string range $s $startIdx $endIdx] + } else { + set returnval [string range $s $startIdx 555] + } + #puts "getValFromString $s, $element, $separator,\n returns: $returnval" + return $returnval + } + +## +# @brief determines which input sensor corresponds to this control loop or output +# @param CtrlLoopIdx the control loop (valid values 1,2) or output (1,2,3,4) +# @return iSensor returns A,B,C or D or None or Unknown +proc getCorrespondingInputSensor {CtrlLoopIdx} { + if { $::scobj::ls340::ls340_LSmodel == 340 } { + if {$CtrlLoopIdx < 1 || $CtrlLoopIdx > 2} { + set iSensor "UNKOWN" + return $iSensor + } + set nodename $::scobj::ls340::ls340_path2nodes/control/config_Loop_$CtrlLoopIdx + set iSensor [hval $nodename] + set iSensor [string range $iSensor 0 0] + } else { + # LS 336, device command outMode_* + if {$CtrlLoopIdx < 1 || $CtrlLoopIdx > 4} { + set iSensor "UNKOWN" + return $iSensor + } + set nodename $::scobj::ls340::ls340_path2nodes/control/outMode_$CtrlLoopIdx + set data [hval $nodename] + set input [::scobj::ls340::getValFromString $data 1 ","] + switch -glob -- $input { + "0*" {set iSensor "None"} + "1*" {set iSensor "A"} + "2*" {set iSensor "B"} + "3*" {set iSensor "C"} + "4*" {set iSensor "D"} + default {set iSensor "UNKNOWN"} + } + } + return $iSensor +} + + + +## +# @brief Checktol() checks whether the current temperature is within tolerance of the Setpoint. +# @param tc_root string variable holding the path to the object's base node in sics +# @param currtime current time +# @param timecheck time since last check(?) +# @param iLoop index of the control loop we are acting upon (is 1 or 2) +# @param iSensor indicates the corresponding input sensor (A,B,C or D) +# @return retVal returns 1 if in tolerance, 0 else. +proc checktol {tc_root currtime timecheck iLoop iSensor} { + if {[ catch { + set retVal 0 + set sensorValue $tc_root/sensor/sensorValue$iSensor + set temp [hval $sensorValue] + set isetp $tc_root/sensor/setpoint$iLoop + set setpt [hval $isetp] + set tol [hval $tc_root/control/tolerance1] + if {$iLoop == 2 } { + set tol [hval $tc_root/control/tolerance2] + } + set lotemp [expr {$setpt - $tol}] + set hitemp [expr {$setpt + $tol}] + if {$::scobj::ls340::ls340_verbose==1} { + puts "checktol(): setpt $isetp=$setpt lotemp=$lotemp, current $sensorValue=$temp, hitemp=$hitemp, tol=$tol, iLoop=$iLoop, timecheck=$timecheck, currtime=$currtime" + } + if { $temp < $lotemp || $temp > $hitemp} { + hset $tc_root/emon/isInTolerance_Lp$iLoop "outsideTolerance" + if {$::scobj::ls340::ls340_verbose==1} {puts "hset $tc_root/emon/isInTolerance_Lp$iLoop outsideTolerance"} + if {$iLoop==1} { sct utime timecheck } + if {$iLoop==2} { sct utime timecheck2 } + set retVal 0 + } else { + set timeout $::scobj::ls340::ls340_settleTime + if { $::scobj::ls340::ls340_LSmodel == 340 } { + set timeout [hval $tc_root/control/settleTime_Loop_1] + } + set elapsedTime [expr {$currtime - $timecheck}] + #puts "if (elapsedTime=$elapsedTime > timeout=$timeout) we are inTolerance" + if {$elapsedTime > $timeout} { + hset $tc_root/emon/isInTolerance_Lp$iLoop "inTolerance" + if {$::scobj::ls340::ls340_verbose==1} { + puts "hset $tc_root/emon/isInTolerance_Lp$iLoop inTolerance (elapsedTime=$elapsedTime greater than settleTime=$timeout)" + } + set retVal 1 + } else { + # Temperature has not been within tolerance for enough time - (overshoots, oscillations,..) + hset $tc_root/emon/isInTolerance_Lp$iLoop "outsideTolerance" + if {$::scobj::ls340::ls340_verbose==1} { + puts "hset $tc_root/emon/isInTolerance_Lp$iLoop outsideTolerance (elapsedTime=$elapsedTime less than settleTime=$timeout)" + } + set retVal 0 + } + } + } message ]} { + return -code error "in checktol: $message. Last query command: $::scobj::ls340::ls340_lastQueryCmd" + } + return $retVal +} + + +## +# @brief check() checks whether the the newly chosen Setpoint is inside the alarm levels +# @param tc_root string variable holding the path to the object's base node in sics +# @param whichCtrlLoop index of the control loop we are operating on (is 1 or 2) +# @return 'OK' if the new setpoint is within the alarm levels; else an error is reported +proc check {tc_root whichCtrlLoop} { + if {[ catch { + set setpoint [sct target] + #puts "check(): setpoint= $setpoint" + # determine the corresponding input sensor + set whichSensor [getCorrespondingInputSensor $whichCtrlLoop] + # puts "check(): whichCtrlLoop=$whichCtrlLoop whichSensor= $whichSensor" + set lolimit $::scobj::ls340::alarm_Limit_LoA + set hilimit $::scobj::ls340::alarm_Limit_HiA + set bCheckLimits $::scobj::ls340::checkAlarmLimitsA + switch $whichSensor { + "A" { + set lolimit $::scobj::ls340::alarm_Limit_LoA + set hilimit $::scobj::ls340::alarm_Limit_HiA + set bCheckLimits $::scobj::ls340::checkAlarmLimitsA + } + "B" { + set lolimit $::scobj::ls340::alarm_Limit_LoB + set hilimit $::scobj::ls340::alarm_Limit_HiB + set bCheckLimits $::scobj::ls340::checkAlarmLimitsB + } + "C" { + set lolimit $::scobj::ls340::alarm_Limit_LoC + set hilimit $::scobj::ls340::alarm_Limit_HiC + set bCheckLimits $::scobj::ls340::checkAlarmLimitsC + } + "D" { + set lolimit $::scobj::ls340::alarm_Limit_LoD + set hilimit $::scobj::ls340::alarm_Limit_HiD + set bCheckLimits $::scobj::ls340::checkAlarmLimitsD + } + default { + error "sct_ls340.tcl check(): Can't set setpoint. No valid input sensor specified for this output control loop." + } + } + # puts "check(): doCheck:$bCheckLimits lolimit=$lolimit setpoint=$setpoint hilimit=$hilimit" + if {$bCheckLimits == 1} { + if {$setpoint < $lolimit || $setpoint > $hilimit} { + error "sct_ls340.tcl: setpoint $tc_root/sensor/sensorValue$whichSensor violates set alarm limits" + } + } + } message ]} { + return -code error "in check(): $message. Last write command: $::scobj::ls340::ls340_lastWriteCmd" + } + return OK +} + + +## +# Provides online help for the driver commands available and the meaning of their +# options and parameters. Note that the help text is limited to +# - 500 bytes in total length. +# - 128 characters per line (if not, may get truncated on the display) +# - The following 5 special characters must be escape-coded for xml-compliance: +# Character Name Entity Reference Numeric Reference +# & Ampersand & &#38; +# < Left angle bracket < &#60; +# > Right angle bracket > > +# " Straight quotation mark " ' +# ' Apostrophe ' " +# Note that the xml file will be parsed more than once, meaning that escaping +# special characters may be insufficient and it may be necessary to avoid all +# xml special characters to avoid problems further downstream. It appears also +# that at present gumtree is truncating the help text when it comes across an +# equal sign '=' within the string. Multiple lines are not a problem. +# A later version of Gumtree may show this text as a tooltip - meanwhile it is +# available in the Properties tab of each node. The help text provided is taken +# from the user manual for the Lakeshore 336 and 340 but is shortened in some +# cases due to the restriction to max 500 characters. +proc helpNotes4user {scobj_hpath cmdGroup varName} { + if {[ catch { + set nodeName "$scobj_hpath/$cmdGroup/$varName" + # We presume that SICServer is running on Linux but Gumtree on Windows. + # Hence multi-line help texts should use CRLF instead of only LF + # Note that gumxml.tcl processes the node properties including this help text + # and may do strange things to it... + #set CR 0x0D + #set LF 0x0A + set CR "\r" + set LF "\n" + set CRLF $CR$LF + if {1 > [string length $cmdGroup]} { + set nodeName "$scobj_hpath/$varName" + } + set helptext "No help available" + #puts "helpNotes4user $scobj_hpath/$cmdGroup varName" + switch -glob $varName { + "sensorValue*" { + set h1 {KRDG? «input» Query Kelvin Reading for an Input} + set h2 {Returned: «kelvin value«. Format: nnn.nnn.} + set h3 {Remarks: Returns the kelvin reading for an input. «input» specifies which input to query.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "ctrlLp*" { + set h1 {KRDG? «input» Query Kelvin Reading for the sensor corresponding to this control loop} + set h2 {Returned: «kelvin value«. Format: nnn.nnn.} + set h3 {Remarks: Returns the kelvin reading for an input. «input» specifies which input to query.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "Tsample*" { + set helptext {Sample temperature: returns the kelvin reading for the sample input as defined by sampleSensor.} + } + "sampleSensor*" { + set helptext {Specifies the input channel that reads the temperature at the sample location.} + } + "setpoint*" { + set h1 {SETP «loop», «value» Configure Control Loop Setpoint.} + if { $::scobj::ls340::ls340_LSmodel == 336 } { + set h1 {SETP «output», «value» Set the Setpoint for the selected output's control loop.} + } + set h2 {«value» The value for the setpoint (in whatever units the setpoint is using).} + set h3 {Example: SETP 1, 122.5. Control Loop 1 setpoint is now 122.5 (based on its units).} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "device_busy*" { + set h1 {BUSY? Query Instrument Busy Status. Returned: «instrument busy status». Format: n.} + set h2 {Indicates that the instrument is busy performing a lengthy operation like generating a SoftCal curve, writing to the Flash chip, etc.} + set h3 {Commands that use the Instrument Busy Status say so in their description.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "cfgProtocol_comm*" { + set h1 {COMM [«terminator»], [«bps»], [«parity»] Configure Serial Interface Parameters.} + set h2 {«terminator» Specifies the terminator: 1: «CR»«LF», 2: «LF»«CR», 3: «CR», 4: «LF»} + set h3 {«bps» Specifies the bits per second (bps) rate. Valid entries: 1: 300, 2: 1200, 3: 2400, 4: 4800, 5: 9600, 6: 19200} + set h4 {«parity» Valid entries: 1: 7 data bits, 1 stop bit, odd parity; 2: 7 data bits, 1 stop bit, even parity;} + set h5 {3: 8 data bits, 1 stop bit, no parity.} + #Example: COMM 1, 6, 3[term]. After receiving the current terminator, the instrument responds at + #19,200 bps using 8 data bits, 1 stop bit, no parity, and «CR»«LF» as a terminator. + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "deviceID_idn*" { + set h1 {*IDN? Query Identification. Returned: «manufacturer», «model number», «serial number», «firmware date»} + set h2 {Format: LSCI,MODEL340,aaaaaa,nnnnnn. Remarks: Identifies the instrument model and software level.} + set helptext $h1$CRLF$h2 + } + "relayStatus*" { + set h1 {RELAYST? «high/low» Queries specified Relay Status} + set h2 {Returned: «status». Format: n} + set h3 {Returns specified relay status,: 0 : off, 1 : on} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "relayCtrlParmHi*" { + set h1 {RELAY «high/low», [«mode»], [«off/on»] } + set h2 {Configure Relay Control Parameters for the HIGH relay.} + set h3 {«mode» Specifies relay settings mode. Valid entries: 0 : off, 1 : alarms, 2 : manual.} + set h4 {«off/on» off:0, on:1 Specifies the manual relay settings if «mode» : 2.} + set h5 {Example: RELAY 1, 2, 1. Manually turns on the high relay} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + if { $::scobj::ls340::ls340_LSmodel == 336 } { + set h1 {RELAY «high/low», «mode», «input alarm», «alarm type» } + set h3 {«mode» Specifies relay settings mode. Valid entries: 0:off, 1:on, 2:alarms.} + set h4 {«input alarm» Specifies which input alarm activates the relay when it is in alarm mode (A-D).} + set h5 {«alarm type» Specifies the alarm type that activates the relay. 0:Low alarm, 1:High alarm, 2:Both alarms} + set h6 {Example: RELAY 1,2,B,0. Relay 1 (Hi) activates when input B low alarm activates.} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6 + } + } + "relayCtrlParmLo*" { + set h1 {RELAY «high/low», [«mode»], [«off/on»] } + set h2 {Configure Relay Control Parameters for the LOW relay} + set h3 {«mode» Specifies relay settings mode. Valid entries: 0 : off, 1 : alarms, 2 : manual.} + set h4 {«off/on» off:0, on:1 Specifies the manual relay settings if «mode» : 2.} + set h5 {Example: RELAY 1, 2, 1. Manually turns on the high relay} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + if { $::scobj::ls340::ls340_LSmodel == 336 } { + set h1 {RELAY «high/low», «mode», «input alarm», «alarm type» } + set h3 {«mode» Specifies relay settings mode. Valid entries: 0:off, 1:on, 2:alarms.} + set h4 {«input alarm» Specifies which input alarm activates the relay when it is in alarm mode (A-D).} + set h5 {«alarm type» Specifies the alarm type that activates the relay. 0:Low alarm, 1:High alarm, 2:Both alarms} + set h6 {Example: RELAY 2,2,B,0. Relay 2 (Lo) activates when input B low alarm activates.} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6 + } + } + "reset_rst*" { + set h1 {*RST Reset Instrument. Returned: Nothing.} + set h2 {Sets controller parameters to power_up settings.} + set helptext $h1$CRLF$h2 + } + "selftest*" { + set h1 {*TST? Query Self Test. Returned: 0 or 1. Format: n.} + set h2 {The Lakeshore 336 and 340 perform a selftest at power_up. 0 : no errors found, 1 : errors found.} + set helptext $h1$CRLF$h2 + } + "statusByte*" { + set h1 {*STB? Query Status Byte. The integer returned represents the sum of the bit weighting of the status flag bits that} + set h2 {are set in the Status Byte Register. Acts like a serial poll, but does not reset the register to all zeros.} + set h3 {Bit Bit Weighting Event Name} + set h4 {0 1 New A and B} + set h5 {1 2 New Option} + set h6 {2 4 Settle} + set h7 {3 8 Alarm} + set h8 {4 16 Error} + set h9 {5 32 ESB Event summary bit} + set hA {6 64 SRQ Service request bit} + set hB {7 128 Ramp Done} + if { $::scobj::ls340::ls340_LSmodel == 336 } { + set h4 {0 1 not used} + set h5 {1 2 not used} + set h6 {2 4 not used} + set h7 {3 8 not used} + set h8 {4 16 MAV Message available bit} + set h9 {5 32 ESB Event summary bit} + set hA {6 64 RQS Request service bit} + set hB {7 128 OSB Operation summary bit} + } + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8$CRLF$h9$CRLF$hA$CRLF$hB + } + "alarm_Limits_*" { + set h1 {ALARM «input», [«off/on»], [«source»], [«high value»], [«low value»], [«latch enable»], [«relay»]} + set h2 {Configures the alarm parameters for an input} + set h3 {«off/on» check the alarm for this input} + set h4 {«source» units for high/low values 1:Kelvin, 2:Celsius, 3:Sensor units, 4:Linear data} + set h5 {«high value» Limit to activate the high alarm} + set h6 {«low value» Limit to activate low alarm} + set h7 {«latch enable» enable latched alarm} + set h8 {«relay enable» 0:off, 1:on} + set h9 { } + #«relay enable» Specifies how the activated alarm affects relays. NOTE: Does not + #guarantee the alarm activates the relays. See the RELAY command. + #Example: ALARM A, 0. Turns off alarm checking for Input A. + #ALARM B, 1, 1, 270.0, ,1. Turns on alarm checking for input B, activates high alarm if + #kelvin reading is over 270, and latches the alarm when kelvin reading falls below 270 + if { $::scobj::ls340::ls340_LSmodel == 336 } { + set h1 {ALARM «input», [«off/on»], [«high value»], [«low value»], [«deadband»], [«latch enable»], [«audible»], [«visible»]} + set h2 {Configures alarm parameters for an input} + set h3 {«off/on» check the alarm for this input} + set h4 {«high value» Limit to activate the high alarm} + set h5 {«low value» Limit to activate low alarm} + set h6 {«deadband» sets the value the source must change} + set h7 {«latch enable» enable latched alarm} + set h8 {«audible» alarm 0:off,1:on} + set h9 {«visible» alarm 0:off,1:on} + } + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8$CRLF$h9 + } + "alarm_Limit_Lo*" { + set h1 {ALARM_Lo «input»,«low value»} + set h2 {«low value» Sets the value the source is checked against to activate low alarm.} + set helptext $h1$CRLF$h2 + } + "alarm_Limit_Hi*" { + set h1 {ALARM_Hi «input»,«high value»} + set h2 {«high value» Sets the value the source is checked against to activate the high alarm.} + set helptext $h1$CRLF$h2 + } + "alarmStatus*" { + set h1 {ALARMST? «input» Query Input Alarm Status.} + set h2 {Returned: «high status», «low status». Format: n,n.} + set h3 {Remarks: Returns the alarm status of an input. «input» specifies which input to query.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "calCurveHdr*" { + set h1 {CRVHDR «curve», [«name»], [«SN»], [«format»], [«limit value»], [«coefficient»]} + set h2 {Configures the user curve header} + set h3 {«curve» Specifies which curve to configure (21 - 59)} + set h4 {«name» Curve name (max 15 chars).} + set h5 {«SN» Curve serial number (max 10 chars)} + set h6 {«format» Curve data format (1:mV/K, 2:V/K, 3:Ω/K, 4:log Ω/K, 5:log Ω/log K)} + set h7 {«limit value» Curve temperature limit in kelvin.} + set h8 {«coefficient» Curves temperature coefficient (1:negative,2:positive)} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8 + } + "inpSetup*" { + set h1 {INSET «input», «enable», «compensation»} + set h2 {Configure hardware input setup parameters} + set h3 {«input» Specifies which input to configure (A,B,C,D)} + set h4 {«enable» Specifies whether the input is allowed to be read.} + set h5 {«compensation» For NTC resistors and special sensors only. 0: off, 1: on, 2: pause} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "inpCalCurve*" { + set h1 {INCRV «input», «curve number»} + set h2 {Specifies the curve an input uses for temperature conversion.} + set h3 {«input» Specifies which input to configure (A,B,C,D)} + set h4 {«curve number» Specifies which curve the input uses. If specified curve parameters do not match the input,} + set h5 {the curve number defaults to 0. 0: none, 1 to 20: standard curves, 21 to 59: user curves} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "alarmResetAll*" { + set helptext {ALMRST Clear Alarm Status for All Inputs} + } + "sensorType*" { + set h1 {INTYPE «input», [«type»], [«units»], [«coeff»], [«excitation»], [«range»]} + set h2 {Configure Input Type Parameters} + set h3 {«type» 0:Special,1:SiDiode,2:GaAlAsDiode,3:Platinum100 (250Ω),4:as 3 (500Ω),5:Platinum1000,6:RhodiumIron,7:CarbonGlass,} + set h4 {8:Cernox,9:RuOx,10:Ge,11:Capacitor,12:Thermocpl} + set h5 {«units» Sensor units 1:Volts,2:ohms} + set h6 {«coefficient» input coeff. 1:neg,2:pos} + set h7 {«excitation» 0:Off,1:30nA,2:100nA,3:300nA,..} + set h8 {«range» 1:1mV,2:2.5mV,..} + # Lakeshore 340 model only + # <excitation> 0:Off,1:30nA,2:100nA,3:300nA,4:1µA,5:3µA,6:10µA,7:30µA,8:100µA,9:300µA,10:1mA,11:10mV,12:1mV + # <range> 1:1 mV,2:2.5 mV,3:5 mV,4:10 mV,5:25 mV,6:50 mV,7:100 mV,8:250 mV,9:500 mV,10:1 V,11:2.5V,12:5 V,13:7.5 V + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8 + } + "inputType*" { + set h1 {INTYPE «input», [«type»], [«autorange»], [«range»], [«compensation»], [«units»]} + set h2 {Configure Input Type Parameters} + set h3 {«type» 0:Disabled,1:Diode,2:PlatinumRTD,3:NTC RTD,4:Thermocouple} + set h4 {«autorange» 0:off,1:on} + set h5 {«range» specifies range if autorange is off} + set h6 {«compensation» 0:off,1:on. Reversal for thermal EMF compensation if input} + set h7 { is resistive, room comp. if input is thermocpl. Always 0 if input is diode} + set h8 {«units» Sensor and setpoint units 1:Kelvin,2:Celsius,3:Sensor} + # Lakeshore 336 model only + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8 + } + "minMaxInpFunc*" { + set h1 {MNMX? «input» Query Minimum and Maximum Input Function Parameters} + set h2 {Returned: «on/pause», «source». Format: n,n} + set h3 {«on/pause» Turns on or Pauses the max/min function. Valid entries: 1 : on, 2 : paused.} + set h4 {«source» Specifies input data to process through max/min. Valid entries: 1 : Kelvin,} + set h5 {2 : Celsius, 3 : sensor units, 4 : linear data} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "sensorStatus*" { + set h1 {RDGST? «input» Query Input Status. Returned: «reading bit weighting»} + set h2 {The integer returned represents the sum of the bit weighting of the input status flag bits} + set h3 {Bit Weighting StatusIndicator} + set h4 {0 1 invalid reading} + set h5 {1 2 old reading} + set h6 {4 16 temp underrange} + set h7 {5 32 temp overrange} + set h8 {6 64 units zero} + set h9 {7 128 units overrange} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8$CRLF$h9 + } + "ctrl_Limit*" { + set h1 {CLIMIT «loop», [«SP limit value»],[«pos slope value»],[«neg slope value»],[«max current»],[«max range»]} + set h2 {Control Loop Limit Parameters} + set h3 {«SP limit value» limit at which the loop turns off output} + set h4 {«positive slope value» max pos change in output} + set h5 {«negative slope value» max neg change in output} + set h6 {«max current» Max current for loop 1 heater output (1:0.25A,2:0.5A,3:1.0A,4:2.0A,5:User)} + set h7 {«max range» Max loop 1 heater range (0 to 5)} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7 + } + "ctrl_Mode*" { + set h1 {CMODE «loop», «mode» Configure Control Loop Mode} + set h2 {«mode» Specifies the control mode. Valid entries: 1: Manual PID,2: Zone,3: Open Loop,4: AutoTune PID, 5: AutoTune PI, 6: AutoTune P} + set h3 {Example: CMODE 1, 4. Control Loop 1 uses PID AutoTuning.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "outMode_*" { + set h1 {OUTMODE «output», «mode» «input» «powerup enable» Output Mode Command} + set h2 {«mode» Specifies the control mode. Valid entries: 0:off, 1:Closed loop PID,2:Zone,3:Open Loop,4:Monitor out, 5:Warmup Supply} + set h3 {«input» which input to use for control. 0:none, 1:A, 2:B, 3:C, 4:D} + set h4 {«powerup enable» 0:output shuts off 1:output remains on after power cycle} + set h5 {Remarks: Modes 1 and 2 are only valid for heater outputs, and modes 4 and 5 are only valid for Monitor Out} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "config_Loop*" { + set h1 {CSET «loop», [«input»], [«units»], [«off/on»], [«powerup enable»] Configure Control Loop Parameters} + set h2 {«input» which input to control from. (A,B,..)} + set h3 {«units» Setpoint units. 1: Kelvin, 2: Celsius, 3: sensor units} + set h4 {«off/on» Control loop is on:1 or off:0} + set h5 {«powerup enable» Control loop is on or off after power_up} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "ctrl_Loop*" { + set h1 {Shows the Control Loop Parameters.} + set h2 {Use the /control/config_Loop_x entry to change settings} + if { $::scobj::ls340::ls340_LSmodel == 336 } { + set h1 {Shows the Output Control Parameters.} + set h2 {Use the /control/outMode_x entry to change settings} + } + set helptext $h1$CRLF$h2 + } + "pid_Loop*" { + set h1 {PID «loop», [«P value»], [«I value»], [«D value»] Configure Control Loop PID Values} + if { $::scobj::ls340::ls340_LSmodel == 336 } { + set h1 {PID «output», [«P value»], [«I value»], [«D value»] Configure Control Loop PID Values} + } + set h2 {«P value» The value for P (proportional)} + set h3 {«I value» The value for I (integral)} + set h4 {«D value» The value for D (derivative)} + set h5 {Example: PID 1, 10, 50. Control Loop 1 P is 10 and I is 50.} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "ramp_Loop*" { + set h1 {RAMP «loop», [«off/on»], [«rate value»] Configure Ramp Parameters for specified Control Loop} + if { $::scobj::ls340::ls340_LSmodel == 336 } { + set h1 {RAMP «output», [«off/on»], [«rate value»] Configure Ramp Parameters for specified output} + } + set h2 {«off/on» Specifies whether ramping is off or on} + set h3 {«rate value» Specifies how many kelvin per minute to ramp the setpoint (0.1 - 100)} + set h4 {Example: RAMP 1, 1, 10.5. When Control Loop 1 setpoint is changed, ramp the current setpoint to the target setpoint at 10.5 K/minute.} + if { $::scobj::ls340::ls340_LSmodel == 336 } { + set h4 {Example: RAMP 1, 1, 10.5. When the setpoint for output 1 is changed, ramp the current setpoint to the target setpoint at 10.5 K/minute.} + } + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4 + } + "rampStatus_Loop*" { + set h1 {RAMPST? «loop» Query Ramp Status for specified Control Loop.} + if { $::scobj::ls340::ls340_LSmodel == 336 } { + set h1 {RAMPST? «output» Query Ramp Status for specified output.} + } + set h2 {Returned: «ramp status». Format: n} + set h3 {Remarks: Returns 0 if setpoint is not ramping, and 1 if it is ramping. «loop» specifies loop to query.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "settle*" { + set h1 {SETTLE [«threshold»], [«time»] Sets Loop_1 Settle Parameters} + set h2 {«threshold» Specifies the allowable band around the setpoint. Valid entries: 0.0 to 100.00} + set h3 {«time» Specifies time in seconds the reading must stay within the band. Valid entries: 0 to 86400} + set h4 {Example: SETTLE 10.0, 10. The Control Loop 1 input readings must be within ±10 K of the setpoint for 10 seconds before the Within Control Limit flag is set.} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4 + } + "heaterOutp*" { + set h1 {HTR? Query Heater Output.} + set h2 {Returned: «heater value». Format: nnn.n.} + set h3 {Remarks: Returns the heater output in percent.} + set helptext $h1$CRLF$h2$CRLF$h3 + if { $::scobj::ls340::ls340_LSmodel == 336 } { + set h1 {HTR? «output» Query Heater Output.} + set h2 {«output» is 1 or 2; use command AOUT for output 3 and 4.} + set h3 {Returned: «heater value». Format: nnn.n.} + set h4 {Remarks: Returns the heater output in percent.} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4 + } + } + "manualOut_*" { + set h1 {MOUT «loop», «value» Manual Output Command.} + set h2 {«loop» specifies the control loop to configure: 1-4.} + if { $::scobj::ls340::ls340_LSmodel == 336 } { + set h1 {MOUT «output», «value» Manual Output Command.} + set h2 {«output» specifies output to configure: 1-4.} + } + set h3 {«value» Specifies value for manual output in percent (non-integer allowed).} + set h4 {Remarks: Requires closed-loop-PID, Zone, or Open-Loop mode.} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4 + } + "heaterStatus*" { + set h1 {HTRST? Query Heater Status. Returned: «error code». Format: nn.} + if { $::scobj::ls340::ls340_LSmodel == 336 } { + set h1 {HTRST? «output» Query Heater Status. Returned: «error code». Format: nn.} + } + set h2 {Remarks: Returns the heater error code (User Manual Paragraph 11.8).} + set h3 {A value of zero means that the system is happy.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "heaterRange*" { + if { $::scobj::ls340::ls340_LSmodel == 340 } { + set h1 {RANGE «range» Configure Heater Range} + set h2 {«range» specifies the heater range (0 to 5)} + set h3 {0 : heater switched off} + set h4 {1 : 5 mW} + set h5 {2 : 50 mW} + set h6 {3 : 500 mW} + set h7 {4 : 5 W} + set h8 {5 : 50 W} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8 + } else { + set h1 {RANGE «output» «range» Configure Heater Range} + set h2 {«output» specifies which output (1,2,3,4)} + set h3 {«range» specifies the heater range (0 to 3)} + set h4 {0 : heater switched off} + set h5 {1 : low} + set h6 {2 : medium} + set h7 {3 : high} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7 + } + } + "isInTolerance_Lp*" { + set h1 {A flag that indicates whether the actual temperature is within tolerance} + set h2 {of the setpoint temperature for that control loop.} + set helptext $h1$CRLF$h2 + } + "apply_tolerance*" { + set h1 {A flag that indicates whether the control loop should actively try} + set h2 {to keep the temperature within the set tolerance of the setpoint.} + set helptext $h1$CRLF$h2 + } + "tolerance*" { + set h1 {Tolerance specifies the temperature tolerance in Kelvin applicable to the setpoint.} + set helptext $h1 + } + "monMode_Lp*" { + set h1 {A flag that indicates whether heaters or other active components are attempting to} + set h2 {drive the temperature towards the setpoint or whether they are currently inactive} + set h3 {with the device just monitoring while the temperature is in tolerance with the setpoint.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "errhandler*" { + set h1 {Specifies the default action in case of a serious error.} + set h2 {The default action is always 'pause'.} + set helptext $h1$CRLF$h2 + } + "dateTime*" { + set h1 {DATETIME «MM»,«DD»,«YYYY»,«HH»,«mm»,«SS»,«sss» Configure Date and Time.} + set h2 {«MM» Month} + set h3 {«DD» Day of month} + set h4 {«YYYY» Year} + set h5 {«HH» Hours (0..23)} + set h6 {«mm» Minutes} + set h7 {«SS» Seconds} + set h8 {«sss» milliseconds} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5$CRLF$h6$CRLF$h7$CRLF$h8 + } + "timStamp*" { + set h1 {Time stamp. Seconds since 1st January 2000.} + set h2 {See node /other/dateTime for editing and formatted date/time display.} + set helptext $h1$CRLF$h2 + } + "status*" { + set helptext {Device status. Is 'idle' while in tolerance or 'busy' while driving or resetting} + } + default { + set helptext {Sorry mate. No help available.} + puts "No help info available for node $varName" + } + } + #set sLen [string bytelength $helptext] + #puts "helptext ($sLen bytes) $helptext" + hsetprop $nodeName help $helptext + } message ]} { + return -code error "in helpNotes4user: $message" + } +} + +## +# @brief createNode() creates a node for the given nodename with the properties and virtual +# function names provided +# @param scobj_hpath string variable holding the path to the object's base node in sics (/sample/tc1) +# @param sct_controller name of the ls340 scriptcontext object (typically sct_ls340_tc1 or tc2) +# @param cmdGroup subdirectory (below /sample/tc*/) in which the node is to be created +# @param varName name of the actual node typically representing one device command +# @param readable set to 1 if the node represents a query command, 0 if it is not +# @param writable set to 1 if the node represents a request for a change in settings sent to the device +# @param pollEnabled set to 1 if the node property pollable is to be enabled (node gets read every 5 secs) +# @param drivable if set to 1 it prepares the node to provide a drivable interface +# @param idx indicates which control loop or which input channel the command corresponds to +# @param ls340 set to 1 if this command is supported by device model-340 +# @param ls336 set to 1 if this command is supported by device model-336 +# @param dataType data type of the node, must be one of none, int, float, text +# @param permission defines what user group may read/write to this node (is one of spy, user, manager) +# @param rdCmd actual device query command to be sent to the device +# @param rdFunc nextState Function to be called after the getValue function, typically rdValue() +# @param wrCmd actual device write command to be sent to the device +# @param wrFunc Function to be called to send the wrCmd to the device, typically setValue() +# @param allowedValues allowed values for the node data - does not permit other +# @param klasse Nexus class name (?) +# @return OK +proc createNode {scobj_hpath sct_controller cmdGroup varName readable writable pollEnabled drivable idx ls340 ls336 dataType permission rdCmd rdFunc wrCmd wrFunc allowedValues klasse lsModel} { + #puts "createing node for: $scobj_hpath $cmdGroup $varName $readable $writable $pollEnabled $drivable $idx $dataType $permission $rdCmd $rdFunc $wrCmd $wrFunc" + #puts "createNode, lsModel = $lsModel, ls340=$ls340, ls336=$ls336, varName=$varName " + if {$lsModel == 340 && $ls340 == 0} { + # puts "Command node $cmdGroup/$varName not supported by Lakeshore $lsModel" + return OK + } + # Nothing to do + if {$lsModel == 336 && $ls336 == 0} { + # puts "Info: Command node $cmdGroup/$varName not supported by Lakeshore $lsModel" + return OK + } + # It is a command that is supported by the device + if {[ catch { + set ns ::scobj::ls340 + set nodeName "$scobj_hpath/$cmdGroup/$varName" + if {1 > [string length $cmdGroup]} { + set nodeName "$scobj_hpath/$varName" + } + hfactory $nodeName plain $permission $dataType + if {$readable == 1} { + hsetprop $nodeName read ${ns}::getValue $scobj_hpath $rdFunc $rdCmd $idx + } + if {$pollEnabled == 1} { + # puts "enabling polling for $nodeName" + $sct_controller poll $nodeName + } + hsetprop $nodeName $rdFunc ${ns}::$rdFunc $idx + if {$writable == 1} { + hsetprop $nodeName write ${ns}::$wrFunc $scobj_hpath noResponse $wrCmd $idx + hsetprop $nodeName writestatus UNKNOWN + hsetprop $nodeName noResponse ${ns}::noResponse + if {$pollEnabled == 1} { + $sct_controller write $nodeName + } + } + switch -exact $dataType { + "none" { } + "int" { hsetprop $nodeName oldval -1 } + "float" { hsetprop $nodeName oldval -1.0 } + default { hsetprop $nodeName oldval UNKNOWN } + } + if {1 < [string length $allowedValues]} { + hsetprop $nodeName values $allowedValues + } + # Drive adapter interface + if {$drivable == 1} { + hsetprop $nodeName check ${ns}::check $scobj_hpath 1 + hsetprop $nodeName driving 0 + hsetprop $nodeName checklimits ${ns}::check $scobj_hpath $idx + hsetprop $nodeName checkstatus ${ns}::drivestatus $scobj_hpath + hsetprop $nodeName halt ${ns}::halt $scobj_hpath $idx + } + } message ]} { + return -code error "in createNode $message" + } + helpNotes4user $scobj_hpath $cmdGroup $varName + return OK +} + + + ## + # @brief mk_sct_lakeshore_340() creates a scriptcontext object for a Lakeshore 336 or 340 temperature controller + # @param sct_controller name of the ls340 scriptcontext object (typically sct_ls340_tc1 or tc2) + # @param klasse Nexus class name (?), typically 'environment' + # @param tempobj short name for the temperature controller scriptcontext object (typ. tc1 or tc2) + # @param tol temperature tolerance in Kelvin (typ. 1) + # @return nothing (well, the sct object) + proc mk_sct_lakeshore_340 {sct_controller klasse tempobj LSmodel tol1 tol2 verbose} { + if {[ catch { + set ::scobj::ls340::ls340_driveTolerance1 $tol1 + set ::scobj::ls340::ls340_driveTolerance2 $tol2 + set ::scobj::ls340::ls340_LSmodel $LSmodel + set ::scobj::ls340::ls340_verbose $verbose + set ns ::scobj::ls340 + set ::scobj::ls340::ls340_sct_obj_name $tempobj + + # terminator string for serial communication (empty for ls340, taken care of with the COMM command) + #set CR "\r" + #set LF "\n" + #Wombat uses only CR not CRLF + #set ::scobj::ls340::ls340_term "" ! obsolete + + MakeSICSObj $tempobj SCT_OBJECT + sicslist setatt $tempobj klass $klasse + sicslist setatt $tempobj long_name $tempobj + # Create a base node for all the state machines of this sics object + set scobj_hpath /sics/$tempobj + set ::scobj::ls340::ls340_path2nodes $scobj_hpath + + # Create state machines for the following device commands (non-polled entries are place-holders + # for manually maintained nodes, like the selector for the input that provides the sample temperature + # 'sampleSensor', the sample tempreature reading 'Tsample', the input that provides for the controlLoop + # 'value', and the control loop parameters in human-readable form 'ctrl_Loop_x') + # Note that drivable nodes require the index of the control loop in their call to halt() + # Nodes appear in gumtree in the order in which they are created here. + # + # Initialise the model-dependent list of supported device commands + # RdWrPlDrIdx + # cmdGroup subdirectory (below /sample/tc*/) in which the node is to be created + # varName name of the actual node typically representing one device command + # readable set to 1 if the node represents a query command, 0 if it is not + # writable set to 1 if the node represents a request for a change in settings sent to the device + # pollEnabled set to 1 if the node property pollable is to be enabled (node gets read every 5 secs) + # drivable if set to 1 it prepares the node to provide a drivable interface + # idx indicates which control loop or which input channel the command corresponds to + # ls340 command supported by Lakeshore model 340 + # ls336 command supported by Lakeshore model 336 + # dataType data type of the node, must be one of none, int, float, text + # permission defines what user group may read/write to this node (is one of spy, user, manager) + # rdCmd actual device query command to be sent to the device + # rdFunc nextState Function to be called after the getValue function, typically rdValue() + # wrCmd actual device write command to be sent to the device + # wrFunc Function to be called to send the wrCmd to the device, typically setValue() + # allowedValues allowed values for the node data - does not permit other + set deviceCommandToplevel { + sensor sampleSensor 0 1 0 0 1 1 1 text user {CSET? 1} {rdValue} {InpSample } {setPseudoValue} {A,B,C,D} + sensor Tsample 1 0 0 0 1 1 1 float spy {KRDG? A} {rdValue} {} {setValue} {} + sensor ctrl_Loop_1 1 0 0 0 1 1 1 text user {CSET? 1} {rdValue} {} {setValue} {} + sensor ctrlLp1_value 1 0 0 0 1 1 1 float spy {KRDG? A} {rdValue} {} {setValue} {} + sensor setpoint1 1 1 1 1 1 1 1 float user {SETP? 1} {rdValue} {SETP 1,} {setPoint} {} + sensor ctrl_Loop_2 1 0 0 0 2 1 1 text user {CSET? 2} {rdValue} {} {setValue} {} + sensor ctrlLp2_value 1 0 0 0 2 1 1 float spy {KRDG? B} {rdValue} {} {setValue} {} + sensor setpoint2 1 1 1 1 2 1 1 float user {SETP? 2} {rdValue} {SETP 2,} {setPoint} {} + sensor ctrl_Loop_3 1 0 0 0 3 0 1 text user {CSET? 3} {rdValue} {} {setValue} {} + sensor ctrlLp3_value 1 0 0 0 3 0 1 float spy {KRDG? C} {rdValue} {} {setValue} {} + sensor setpoint3 1 1 1 1 3 0 1 float user {SETP? 3} {rdValue} {SETP 3,} {setPoint} {} + sensor ctrl_Loop_4 1 0 0 0 4 0 1 text user {CSET? 4} {rdValue} {} {setValue} {} + sensor ctrlLp4_value 1 0 0 0 4 0 1 float spy {KRDG? D} {rdValue} {} {setValue} {} + sensor setpoint4 1 1 1 1 4 0 1 float user {SETP? 4} {rdValue} {SETP 4,} {setPoint} {} + sensor sensorValueA 1 0 1 0 A 1 1 float spy {KRDG? A} {rdInpValue} {} {setValue} {} + sensor sensorValueB 1 0 1 0 B 1 1 float spy {KRDG? B} {rdInpValue} {} {setValue} {} + sensor sensorValueC 1 0 1 0 C 1 1 float spy {KRDG? C} {rdInpValue} {} {setValue} {} + sensor sensorValueD 1 0 1 0 D 1 1 float spy {KRDG? D} {rdInpValue} {} {setValue} {} + sensor timStamp 1 0 0 0 0 1 0 int user {DATETIME?} {rdValue} {} {setValue} {} + } + set deviceCommand { + input alarm_Limits_A 1 1 1 0 A 1 1 text spy {ALARM? A} {rdAlarmVal} {ALARM A,} {setValue} {} + input alarm_Limits_B 1 1 1 0 B 1 1 text spy {ALARM? B} {rdAlarmVal} {ALARM B,} {setValue} {} + input alarm_Limits_C 1 1 1 0 C 1 1 text spy {ALARM? C} {rdAlarmVal} {ALARM C,} {setValue} {} + input alarm_Limits_D 1 1 1 0 D 1 1 text spy {ALARM? D} {rdAlarmVal} {ALARM D,} {setValue} {} + input alarmStatusA 1 0 1 0 A 1 1 text spy {ALARMST? A} {rdValue} {} {setValue} {} + input alarmStatusB 1 0 1 0 B 1 1 text spy {ALARMST? B} {rdValue} {} {setValue} {} + input alarmStatusC 1 0 1 0 C 1 1 text spy {ALARMST? C} {rdValue} {} {setValue} {} + input alarmStatusD 1 0 1 0 D 1 1 text spy {ALARMST? D} {rdValue} {} {setValue} {} + input inpSetup_A 1 1 1 0 A 1 0 text user {INSET? A} {rdValue} {INSET A,} {setValue} {} + input inpSetup_B 1 1 1 0 B 1 0 text user {INSET? B} {rdValue} {INSET B,} {setValue} {} + input inpSetup_C 1 1 1 0 C 1 0 text user {INSET? C} {rdValue} {INSET C,} {setValue} {} + input inpSetup_D 1 1 1 0 D 1 0 text user {INSET? D} {rdValue} {INSET D,} {setValue} {} + input inpCalCurve_A 1 1 1 0 A 1 1 int user {INCRV? A} {rdCrvValue} {INCRV A,} {setValue} {} + input calCurveHdr_A 1 0 1 0 A 1 1 text user {CRVHDR? } {rdValue} {} {setValue} {} + input inpCalCurve_B 1 1 1 0 B 1 1 int user {INCRV? B} {rdCrvValue} {INCRV B,} {setValue} {} + input calCurveHdr_B 1 0 1 0 B 1 1 text user {CRVHDR? } {rdValue} {} {setValue} {} + input inpCalCurve_C 1 1 1 0 C 1 1 int user {INCRV? C} {rdCrvValue} {INCRV C,} {setValue} {} + input calCurveHdr_C 1 0 1 0 C 1 1 text user {CRVHDR? } {rdValue} {} {setValue} {} + input inpCalCurve_D 1 1 1 0 D 1 1 int user {INCRV? D} {rdCrvValue} {INCRV D,} {setValue} {} + input calCurveHdr_D 1 0 1 0 D 1 1 text user {CRVHDR? } {rdValue} {} {setValue} {} + input sensorTypeA 1 1 1 0 A 1 0 text user {INTYPE? A} {rdValue} {INTYPE A,} {setValue} {} + input sensorTypeB 1 1 1 0 B 1 0 text user {INTYPE? B} {rdValue} {INTYPE B,} {setValue} {} + input sensorTypeC 1 1 1 0 C 1 0 text user {INTYPE? C} {rdValue} {INTYPE C,} {setValue} {} + input sensorTypeD 1 1 1 0 D 1 0 text user {INTYPE? D} {rdValue} {INTYPE D,} {setValue} {} + input inputTypeA 1 1 1 0 A 0 1 text user {INTYPE? A} {rdValue} {INTYPE A,} {setValue} {} + input inputTypeB 1 1 1 0 B 0 1 text user {INTYPE? B} {rdValue} {INTYPE B,} {setValue} {} + input inputTypeC 1 1 1 0 C 0 1 text user {INTYPE? C} {rdValue} {INTYPE C,} {setValue} {} + input inputTypeD 1 1 1 0 D 0 1 text user {INTYPE? D} {rdValue} {INTYPE D,} {setValue} {} + input minMaxInpFuncA 1 0 1 0 A 1 0 int user {MNMX? A} {rdValue} {} {setValue} {} + input minMaxInpFuncB 1 0 1 0 B 1 0 int user {MNMX? B} {rdValue} {} {setValue} {} + input minMaxInpFuncC 1 0 1 0 C 1 0 int user {MNMX? C} {rdValue} {} {setValue} {} + input minMaxInpFuncD 1 0 1 0 D 1 0 int user {MNMX? D} {rdValue} {} {setValue} {} + input sensorStatusA 1 0 1 0 A 1 1 int spy {RDGST? A} {rdBitValue} {} {setValue} {} + input sensorStatusB 1 0 1 0 B 1 1 int spy {RDGST? B} {rdBitValue} {} {setValue} {} + input sensorStatusC 1 0 1 0 C 1 1 int spy {RDGST? C} {rdBitValue} {} {setValue} {} + input sensorStatusD 1 0 1 0 D 1 1 int spy {RDGST? D} {rdBitValue} {} {setValue} {} + control config_Loop_1 1 1 1 0 1 1 0 text user {CSET? 1} {rdCfgValue} {CSET 1,} {setValue} {} + control config_Loop_2 1 1 1 0 2 1 0 text user {CSET? 2} {rdCfgValue} {CSET 2,} {setValue} {} + control ctrl_Limit_1 1 1 1 0 1 1 0 text user {CLIMIT? 1} {rdValue} {CLIMIT 1,} {setValue} {} + control ctrl_Limit_2 1 1 1 0 2 1 0 text user {CLIMIT? 2} {rdValue} {CLIMIT 2,} {setValue} {} + control ctrl_Mode_1 1 1 1 0 1 1 0 int user {CMODE? 1} {rdValue} {CMODE 1,} {setValue} {} + control ctrl_Mode_2 1 1 1 0 2 1 0 int user {CMODE? 2} {rdValue} {CMODE 2,} {setValue} {} + control outMode_1 1 1 1 0 1 0 1 text user {OUTMODE? 1} {rdCfgValue} {OUTMODE 1,} {setValue} {} + control outMode_2 1 1 1 0 2 0 1 text user {OUTMODE? 2} {rdCfgValue} {OUTMODE 2,} {setValue} {} + control outMode_3 1 1 1 0 3 0 1 text user {OUTMODE? 3} {rdCfgValue} {OUTMODE 3,} {setValue} {} + control outMode_4 1 1 1 0 4 0 1 text user {OUTMODE? 4} {rdCfgValue} {OUTMODE 4,} {setValue} {} + control manualOut_1 1 1 1 0 1 1 1 text user {MOUT? 1} {rdValue} {MOUT 1,} {setValue} {} + control manualOut_2 1 1 1 0 2 1 1 text user {MOUT? 2} {rdValue} {MOUT 2,} {setValue} {} + control manualOut_3 1 1 1 0 3 0 1 text user {MOUT? 3} {rdValue} {MOUT 3,} {setValue} {} + control manualOut_4 1 1 1 0 4 0 1 text user {MOUT? 4} {rdValue} {MOUT 4,} {setValue} {} + control pid_Loop_1 1 1 1 0 1 1 1 text user {PID? 1} {rdValue} {PID 1,} {setValue} {} + control pid_Loop_2 1 1 1 0 2 1 1 text user {PID? 2} {rdValue} {PID 2,} {setValue} {} + control ramp_Loop_1 1 1 1 0 1 1 1 text user {RAMP? 1} {rdValue} {RAMP 1,} {setValue} {} + control ramp_Loop_2 1 1 1 0 2 1 1 text user {RAMP? 2} {rdValue} {RAMP 2,} {setValue} {} + control rampStatus_Loop_1 1 0 1 0 1 1 1 int spy {RAMPST? 1} {rdValue} {} {setValue} {} + control rampStatus_Loop_2 1 0 1 0 2 1 1 int spy {RAMPST? 2} {rdValue} {} {setValue} {} + control settleThr_Loop_1 1 1 1 0 1 1 0 float user {SETTLE?} {rdValue} {SETTLE } {setValue} {} + control settleTime_Loop_1 1 1 1 0 1 1 0 int user {SETTLE?} {rdValue} {SETTLE ,} {setValue} {} + heater heaterOutpPercent 1 1 1 0 0 1 0 float user {HTR?} {rdValue} {} {setValue} {} + heater heaterOutput_1 1 1 1 0 1 0 1 float user {HTR? 1} {rdValue} {} {setValue} {} + heater heaterOutput_2 1 1 1 0 2 0 1 float user {HTR? 2} {rdValue} {} {setValue} {} + heater heaterStatus 1 0 1 0 0 1 0 int spy {HTRST?} {rdValue} {} {setValue} {} + heater heaterStatus_1 1 0 1 0 1 0 1 int spy {HTRST? 1} {rdValue} {} {setValue} {} + heater heaterStatus_2 1 0 1 0 2 0 1 int spy {HTRST? 2} {rdValue} {} {setValue} {} + heater heaterRange 1 1 1 0 0 1 0 int user {RANGE?} {rdValue} {RANGE } {setValue} {0,1,2,3,4,5} + heater heaterRange_1 1 1 1 0 0 0 1 int user {RANGE? 1} {rdValue} {RANGE 1,} {setValue} {0,1,2,3} + heater heaterRange_2 1 1 1 0 0 0 1 int user {RANGE? 2} {rdValue} {RANGE 2,} {setValue} {0,1,2,3} + heater heaterRange_3 1 1 1 0 0 0 1 int user {RANGE? 3} {rdValue} {RANGE 3,} {setValue} {0,1} + heater heaterRange_4 1 1 1 0 0 0 1 int user {RANGE? 4} {rdValue} {RANGE 4,} {setValue} {0,1} + other dateTime 1 1 1 0 0 1 0 text user {DATETIME?} {rdValue} {DATETIME } {setValue} {} + other device_busy 1 0 1 0 0 1 0 int spy {BUSY?} {rdValue} {} {setValue} {} + other cfgProtocol_comm 1 1 0 0 0 1 0 text user {COMM?} {rdValue} {COMM } {setValue} {} + other deviceID_idn 1 0 1 0 0 1 1 text spy {*IDN?} {inTolerance} {} {setValue} {} + other selftest 1 0 0 0 0 1 1 int user {*TST?} {rdValue} {} {setValue} {} + other relayStatusHi 1 0 1 0 1 1 1 int spy {RELAYST? 1} {rdValue} {} {setValue} {} + other relayStatusLo 1 0 1 0 2 1 1 int spy {RELAYST? 2} {rdValue} {} {setValue} {} + other relayCtrlParmHi 1 1 1 0 0 1 1 int spy {RELAY? 1} {rdValue} {RELAY 1,} {setValue} {} + other relayCtrlParmLo 1 1 1 0 0 1 1 int spy {RELAY? 2} {rdValue} {RELAY 2,} {setValue} {} + other statusByte 1 0 1 0 0 1 1 int spy {*STB?} {rdValue} {} {setValue} {} + } + # The following 2 commands take no parameter - this makes them difficult to implement in a hipadaba structure + # because they would be nodes without values... + # input alarmResetAll 0 1 0 0 0 1 1 text user {} {rdValue} {ALMRST} {setValue} {} + # other reset_rst 0 1 0 0 0 1 1 text user {} {rdValue} {*RST} {setValue} {} + + hfactory $scobj_hpath/status plain spy text + hsetprop $scobj_hpath/status values busy,idle + hset $scobj_hpath/status "idle" + hfactory $scobj_hpath/status_Ctrl_Lp2 plain spy text + hsetprop $scobj_hpath/status_Ctrl_Lp2 values busy,idle + hset $scobj_hpath/status_Ctrl_Lp2 "idle" + helpNotes4user $scobj_hpath "" "status" + + hfactory $scobj_hpath/sensor plain spy none + # Flags ls340 and ls336 indicate whether this command is support by Lakeshore model ls340 and ls336, respectively + foreach {cmdGroup varName readable writable pollEnabled drivable idx ls340 ls336 dataType permission rdCmd rdFunc wrCmd wrFunc allowedValues} $deviceCommandToplevel { + createNode $scobj_hpath $sct_controller $cmdGroup $varName $readable $writable $pollEnabled $drivable $idx $ls340 $ls336 $dataType $permission $rdCmd $rdFunc $wrCmd $wrFunc $allowedValues $klasse $LSmodel + } + + # create a base node for each commandGroup element - these are all polled + hfactory $scobj_hpath/emon plain spy none + hfactory $scobj_hpath/control plain spy none + hfactory $scobj_hpath/heater plain spy none + hfactory $scobj_hpath/input plain spy none + hfactory $scobj_hpath/other plain spy none + + foreach {cmdGroup varName readable writable pollEnabled drivable idx ls340 ls336 dataType permission rdCmd rdFunc wrCmd wrFunc allowedValues} $deviceCommand { + createNode $scobj_hpath $sct_controller $cmdGroup $varName $readable $writable $pollEnabled $drivable $idx $ls340 $ls336 $dataType $permission $rdCmd $rdFunc $wrCmd $wrFunc $allowedValues $klasse $LSmodel + # helpNotes4user $scobj_hpath $cmdGroup $varName + } + + hsetprop $scobj_hpath/input/alarm_Limits_A units $::scobj::ls340::ls340_tempUnits + hsetprop $scobj_hpath/input/alarm_Limits_B units $::scobj::ls340::ls340_tempUnits + hsetprop $scobj_hpath/input/alarm_Limits_C units $::scobj::ls340::ls340_tempUnits + hsetprop $scobj_hpath/input/alarm_Limits_D units $::scobj::ls340::ls340_tempUnits + + # Create state machines for the following required nodes that do not correspond + # to device commands. So far we only provide one tolerance for both control loops. + hfactory $scobj_hpath/control/apply_tolerance plain user int + hsetprop $scobj_hpath/control/apply_tolerance values 0,1 + hset $scobj_hpath/control/apply_tolerance 1 + helpNotes4user $scobj_hpath "control" "apply_tolerance" + + hfactory $scobj_hpath/control/tolerance1 plain user float + hsetprop $scobj_hpath/control/tolerance1 units $::scobj::ls340::ls340_tempUnits + # hsetprop $scobj_hpath/control/tolerance units "K" + hset $scobj_hpath/control/tolerance1 $tol1 + helpNotes4user $scobj_hpath "control" "tolerance1" + + hfactory $scobj_hpath/control/tolerance2 plain user float + hsetprop $scobj_hpath/control/tolerance2 units $::scobj::ls340::ls340_tempUnits + hset $scobj_hpath/control/tolerance2 $tol2 + helpNotes4user $scobj_hpath "control" "tolerance2" + + # hfactory $scobj_hpath/lowerlimit plain mugger float + # hsetprop $scobj_hpath/lowerlimit units $::scobj::ls340::ls340_tempUnits + # hset $scobj_hpath/lowerlimit $::scobj::ls340::ls340_lowerlimit + # hfactory $scobj_hpath/upperlimit plain mugger float + # hsetprop $scobj_hpath/upperlimit units $::scobj::ls340::ls340_tempUnits + # hset $scobj_hpath/upperlimit $::scobj::ls340::ls340_upperlimit + + # environment monitoring flags: shows if setpoints of loop 1 and 2 are in tolerance + hfactory $scobj_hpath/emon/monMode_Lp1 plain user text + hsetprop $scobj_hpath/emon/monMode_Lp1 values idle,drive,monitor,error + hset $scobj_hpath/emon/monMode_Lp1 "idle" + helpNotes4user $scobj_hpath "emon" "monMode_Lp1" + hfactory $scobj_hpath/emon/monMode_Lp2 plain user text + hsetprop $scobj_hpath/emon/monMode_Lp2 values idle,drive,monitor,error + hset $scobj_hpath/emon/monMode_Lp2 "idle" + helpNotes4user $scobj_hpath "emon" "monMode_Lp2" + hfactory $scobj_hpath/emon/errhandler plain user text + hset $scobj_hpath/emon/errhandler "lazy" + helpNotes4user $scobj_hpath "emon" "errhandler" + + hfactory $scobj_hpath/emon/isInTolerance_Lp1 plain spy text + hsetprop $scobj_hpath/emon/isInTolerance_Lp1 values idle,drive,monitor,error + hset $scobj_hpath/emon/isInTolerance_Lp1 "inTolerance" + helpNotes4user $scobj_hpath "emon" "isInTolerance_Lp1" + hfactory $scobj_hpath/emon/isInTolerance_Lp2 plain spy text + hsetprop $scobj_hpath/emon/isInTolerance_Lp2 values idle,drive,monitor,error + hset $scobj_hpath/emon/isInTolerance_Lp2 "inTolerance" + helpNotes4user $scobj_hpath "emon" "isInTolerance_Lp2" + + # Temperature controllers must have at least the following nodes (for data logging?) + # /tempcont/setpoint + # /tempcont/sensor/ctrlLp1_value + + ::scobj::hinitprops $tempobj + hsetprop $scobj_hpath klass NXenvironment + ::scobj::set_required_props $scobj_hpath + # These are loggable parameters that need additional nexus properties + # Note: node names longer than 8 characters cause eror messages - avoid + set nxProperties " + $scobj_hpath sensor NXsensor spy + $scobj_hpath sensor/Tsample sensor user + $scobj_hpath sensor/ctrlLp1_value sensor user + $scobj_hpath sensor/ctrlLp2_value sensor user + $scobj_hpath sensor/timStamp sensor user + $scobj_hpath sensor/sensorValueA sensor user + $scobj_hpath sensor/sensorValueB sensor user + $scobj_hpath sensor/sensorValueC sensor user + $scobj_hpath sensor/sensorValueD sensor user + " + set aliasProperties " + $scobj_hpath Tsample sensor _sensor_Tsample + $scobj_hpath ctrlLp1_value sensor _sensor_ctrlLp1_value + $scobj_hpath ctrlLp2_value sensor _sensor_ctrlLp2_value + $scobj_hpath timStamp sensor _sensor_timStamp + $scobj_hpath sensorValueA sensor _sensor_sensorValueA + $scobj_hpath sensorValueB sensor _sensor_sensorValueB + $scobj_hpath sensorValueC sensor _sensor_sensorValueC + $scobj_hpath sensorValueD sensor _sensor_sensorValueD + " + # from nexus.dic file + # tc1_sensor_Tsample = /entry1,NXentry/sample,NXsample/tc1,NXenvironment/sensor,NXsensor/SDS Tsample -type NX_FLOAT32 -rank 1 -dim {-1} + # tc1_sensor_value = /entry1,NXentry/sample,NXsample/tc1,NXenvironment/sensor,NXsensor/SDS ctrlLp1_value -type NX_FLOAT32 -rank 1 -dim {-1} + if { $::scobj::ls340::ls340_LSmodel == 336 } { + # $scobj_hpath sensor/sampleSensor sensor user + set nxProperties " + $scobj_hpath sensor NXsensor spy + $scobj_hpath sensor/Tsample sensor user + $scobj_hpath sensor/ctrlLp1_value sensor user + $scobj_hpath sensor/ctrlLp2_value sensor user + $scobj_hpath sensor/sensorValueA sensor user + $scobj_hpath sensor/sensorValueB sensor user + $scobj_hpath sensor/sensorValueC sensor user + $scobj_hpath sensor/sensorValueD sensor user + " + # $scobj_hpath sampleSensor _sensor_sampleSensor + set aliasProperties " + $scobj_hpath Tsample sensor _sensor_Tsample + $scobj_hpath ctrlLp1_value sensor _sensor_ctrlLp1_value + $scobj_hpath ctrlLp2_value sensor _sensor_ctrlLp2_value + $scobj_hpath sensorValueA sensor _sensor_sensorValueA + $scobj_hpath sensorValueB sensor _sensor_sensorValueB + $scobj_hpath sensorValueC sensor _sensor_sensorValueC + $scobj_hpath sensorValueD sensor _sensor_sensorValueD + " + } + foreach {rootpath hpath klasse priv} $nxProperties { + hsetprop $rootpath/$hpath klass $klasse + hsetprop $rootpath/$hpath privilege $priv + hsetprop $rootpath/$hpath control true + hsetprop $rootpath/$hpath data true + hsetprop $rootpath/$hpath nxsave true + } + hsetprop $scobj_hpath type part + hsetprop $scobj_hpath/sensor type part + foreach {rootpath node groupy myalias} $aliasProperties { + hsetprop $scobj_hpath/$groupy/$node nxalias $tempobj$myalias + hsetprop $scobj_hpath/$groupy/$node mutable true + hsetprop $scobj_hpath/$groupy/$node sdsinfo ::nexus::scobj::sdsinfo + } + + hsetprop $scobj_hpath privilege spy + # call hinitprops from script_context_util.tcl which initialises the hdb properties required + # for generating the GumTree interface and saving data for script context objects (hdf file) + # @param scobj, name of script context object (path to a node) + # @param par, optional parameter (name of the node variable) + + # Changed ffr 20100625 due to change in SICServer + #::scobj::hinitprops $tempobj/sensor setpoint1 + #::scobj::hinitprops $tempobj/sensor setpoint2 + + # Problem: Does not write non-numbers to the hdf file unless told differently - but how? + # How can I make it write the NAME (or any character/string variable) to the hdf file? + # ::scobj::hinitprops $tempobj/sensor sampleSensor + + ansto_makesctdrive ${tempobj}_driveable $scobj_hpath/sensor/setpoint1 $scobj_hpath/sensor/ctrlLp1_value $sct_controller + ansto_makesctdrive ${tempobj}_driveable2 $scobj_hpath/sensor/setpoint2 $scobj_hpath/sensor/ctrlLp2_value $sct_controller + + # initialise the device + ls340_init $sct_controller $scobj_hpath + puts "Lakeshore $::scobj::ls340::ls340_LSmodel temperature controller ready at /sample/$tempobj (Driver 2010-06-25)" + } message ]} { + return -code error "in mk_sct_lakeshore_340 $message" + } + } + # endproc mk_sct_lakeshore_340 sct_controller klasse tempobj tol ls340_LSmodel + + namespace export mk_sct_lakeshore_340 +} +# end of namespace mk_sct_lakeshore_340 + +## +# @brief add_ls340() adds a scriptcontext object for a Lakeshore 336 o 340 temperature controller +# and makes it available to SICServer +# @param name short name for the temperature controller scriptcontext object (typ. tc1 or tc2) +# @param IP IP address of the device (e.g. IP of moxabox that hooks up to the Lakeshore 340) +# @param port port number on the moxabox (typ. 4001, 4002, 4003, or 4004) +# @param tol temperature tolerance in Kelvin (typ. 1) +# @return nothing (well, the sct object) +proc add_sct_ls340 {name IP port terminator {_tol1 1.0} {_tol2 1.0} {_verbose 0} } { + # ffr 2009-11-09, Don't create a temperature controller for the script validator, this causes the + # lakeshore to lock up. + # NOTE: I put this outside the catch block because "return" raises an exception + if {[SplitReply [environment_simulation]]} { + return + } + if {[ catch { + set _ls340_LSmodel 340 + puts "\nadd_ls340: makesctcontroller $name std ${IP}:$port for Lakeshore model $_ls340_LSmodel" + makesctcontroller sct_ls340_$name std ${IP}:$port $terminator + mk_sct_lakeshore_340 sct_ls340_$name environment $name $_ls340_LSmodel $_tol1 $_tol2 $_verbose + makesctemon $name /sics/$name/emon/monMode_Lp1 /sics/$name/emon/isInTolerance_Lp1 /sics/$name/emon/errhandler + # set m2 "_2" + # makesctemon $name$m2 /sics/$name/emon/monMode_Lp2 /sics/$name/emon/isInTolerance_Lp2 /sics/$name/emon/errhandler + } message ]} { + return -code error "in add_ls340: $message" + } +} + +namespace import ::scobj::ls340::*